diff --git a/.gitignore b/.gitignore index f308b34a..6ca9e19f 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ /objs/ /nextpnr-generic* /nextpnr-ice40* +/nextpnr-ecp5* cmake-build-*/ Makefile cmake_install.cmake diff --git a/3rdparty/QtPropertyBrowser/src/qttreepropertybrowser.cpp b/3rdparty/QtPropertyBrowser/src/qttreepropertybrowser.cpp index a92ab537..673252d2 100644 --- a/3rdparty/QtPropertyBrowser/src/qttreepropertybrowser.cpp +++ b/3rdparty/QtPropertyBrowser/src/qttreepropertybrowser.cpp @@ -74,6 +74,7 @@ public: QtProperty *indexToProperty(const QModelIndex &index) const; QTreeWidgetItem *indexToItem(const QModelIndex &index) const; QtBrowserItem *indexToBrowserItem(const QModelIndex &index) const; + QtBrowserItem *itemToBrowserItem(QTreeWidgetItem *item) const { return m_itemToIndex.value(item); }; bool lastColumn(int column) const; void disableItem(QTreeWidgetItem *item) const; void enableItem(QTreeWidgetItem *item) const; @@ -1068,6 +1069,16 @@ void QtTreePropertyBrowser::editItem(QtBrowserItem *item) d_ptr->editItem(item); } +QTreeWidget *QtTreePropertyBrowser::treeWidget() const +{ + return d_ptr->treeWidget(); +} + +QtBrowserItem *QtTreePropertyBrowser::itemToBrowserItem(QTreeWidgetItem *item) +{ + return d_ptr->itemToBrowserItem(item); +} + #if QT_VERSION >= 0x040400 QT_END_NAMESPACE #endif diff --git a/3rdparty/QtPropertyBrowser/src/qttreepropertybrowser.h b/3rdparty/QtPropertyBrowser/src/qttreepropertybrowser.h index c5f7fa88..7bc96b69 100644 --- a/3rdparty/QtPropertyBrowser/src/qttreepropertybrowser.h +++ b/3rdparty/QtPropertyBrowser/src/qttreepropertybrowser.h @@ -47,6 +47,7 @@ QT_BEGIN_NAMESPACE #endif +class QTreeWidget; class QTreeWidgetItem; class QtTreePropertyBrowserPrivate; @@ -107,6 +108,9 @@ public: void editItem(QtBrowserItem *item); + //ADDED:miodrag + QTreeWidget *treeWidget() const; + QtBrowserItem *itemToBrowserItem(QTreeWidgetItem *item); Q_SIGNALS: void collapsed(QtBrowserItem *item); diff --git a/3rdparty/python-console/modified/pyconsole.cc b/3rdparty/python-console/modified/pyconsole.cc deleted file mode 100644 index d724553b..00000000 --- a/3rdparty/python-console/modified/pyconsole.cc +++ /dev/null @@ -1,322 +0,0 @@ -#include "pyconsole.h" -#include "pyinterpreter.h" -#include "ColumnFormatter.h" - -#include -#include -#include - -#include "Utils.h" - -const QString PythonConsole::PROMPT = ">>> "; -const QString PythonConsole::MULTILINE_PROMPT = "... "; -const QColor PythonConsole::NORMAL_COLOR = QColor::fromRgbF( 0, 0, 0 ); -const QColor PythonConsole::ERROR_COLOR = QColor::fromRgbF( 1.0, 0, 0 ); -const QColor PythonConsole::OUTPUT_COLOR = QColor::fromRgbF( 0, 0, 1.0 ); - -PythonConsole::PythonConsole( QWidget* parent ): - QTextEdit( parent ) -{ - QFont font("unexistent"); - font.setStyleHint(QFont::Monospace); - setFont(font); - m_parseHelper.subscribe( this ); -} - -void PythonConsole::keyPressEvent( QKeyEvent* e ) -{ - switch ( e->key() ) - { - case Qt::Key_Return: - handleReturnKeyPress( ); - return; - - case Qt::Key_Tab: - autocomplete( ); - return; - - case Qt::Key_Backspace: - if ( ! canBackspace( ) ) - return; - break; - - case Qt::Key_Up: - previousHistory( ); - return; - - case Qt::Key_Down: - nextHistory( ); - return; - - case Qt::Key_Left: - if ( ! canGoLeft( ) ) - return; - } - if (!cursorIsOnInputLine()) return; - if (textCursor().columnNumber() < PythonConsole::PROMPT.size()) return; - QTextEdit::keyPressEvent( e ); -} - -void PythonConsole::handleReturnKeyPress( ) -{ - if ( ! cursorIsOnInputLine( ) ) - { - return; - } - - QString line = getLine( ); - - m_parseHelper.process( line.toStdString( ) ); - if ( m_parseHelper.buffered( ) ) - { - append(""); - displayPrompt( ); - } - if ( line.size( ) ) - { - m_historyBuffer.push_back( line.toStdString( ) ); - m_historyIt = m_historyBuffer.end(); - } - moveCursorToEnd( ); -} - -void PythonConsole::parseEvent( const ParseMessage& message ) -{ - // handle invalid user input - if ( message.errorCode ) - { - setTextColor( ERROR_COLOR ); - append(message.message.c_str()); - - setTextColor( NORMAL_COLOR ); - append(""); - displayPrompt( ); - return; - } - - // interpret valid user input - int errorCode = 0; - std::string res; - if ( message.message.size() ) - res = pyinterpreter_execute( message.message, &errorCode ); - if ( errorCode ) - { - setTextColor( ERROR_COLOR ); - } - else - { - setTextColor( OUTPUT_COLOR ); - } - - if ( res.size( ) ) - { - append(res.c_str()); - } - - setTextColor( NORMAL_COLOR ); - - // set up the next line on the console - append(""); - displayPrompt( ); -} - -QString PythonConsole::getLine( ) -{ - QTextCursor cursor = textCursor(); - cursor.movePosition( QTextCursor::StartOfLine ); - cursor.movePosition( QTextCursor::Right, QTextCursor::MoveAnchor, PythonConsole::PROMPT.size( ) ); - cursor.movePosition( QTextCursor::EndOfLine, QTextCursor::KeepAnchor ); - QString line = cursor.selectedText( ); - cursor.clearSelection( ); - return line; -} - -bool PythonConsole::cursorIsOnInputLine( ) -{ - int cursorBlock = textCursor( ).blockNumber( ); - QTextCursor bottomCursor = textCursor( ); - bottomCursor.movePosition( QTextCursor::End ); - int bottomBlock = bottomCursor.blockNumber( ); - return ( cursorBlock == bottomBlock ); -} - -bool PythonConsole::inputLineIsEmpty( ) -{ - QTextCursor bottomCursor = textCursor( ); - bottomCursor.movePosition( QTextCursor::End ); - int col = bottomCursor.columnNumber( ); - return ( col == PythonConsole::PROMPT.size( ) ); -} - -bool PythonConsole::canBackspace( ) -{ - if ( ! cursorIsOnInputLine( ) ) - { - return false; - } - - if ( inputLineIsEmpty( ) ) - { - return false; - } - - return true; -} - -bool PythonConsole::canGoLeft( ) -{ - if ( cursorIsOnInputLine( ) ) - { - QTextCursor bottomCursor = textCursor( ); - int col = bottomCursor.columnNumber( ); - return (col > PythonConsole::PROMPT.size( )); - } - return true; -} - -void PythonConsole::displayPrompt( ) -{ - QTextCursor cursor = textCursor(); - cursor.movePosition( QTextCursor::End ); - if ( m_parseHelper.buffered( ) ) - { - cursor.insertText( PythonConsole::MULTILINE_PROMPT ); - } - else - { - cursor.insertText( PythonConsole::PROMPT ); - } - cursor.movePosition( QTextCursor::EndOfLine ); -} - -void PythonConsole::displayString(QString text) -{ - QTextCursor cursor = textCursor(); - cursor.movePosition( QTextCursor::End ); - cursor.insertText( text ); - cursor.movePosition( QTextCursor::EndOfLine ); -} - -void PythonConsole::autocomplete( ) -{ - if ( ! cursorIsOnInputLine( ) ) - return; - - QString line = getLine( ); - const std::list& suggestions = - pyinterpreter_suggest( line.toStdString( ) ); - if (suggestions.size() == 1) - { - line = suggestions.back().c_str(); - } - else - { - // try to complete to longest common prefix - std::string prefix = - LongestCommonPrefix(suggestions.begin(), suggestions.end()); - if (prefix.size() > (size_t)line.size()) - { - line = prefix.c_str(); - } - else - { - ColumnFormatter fmt; - fmt.setItems(suggestions.begin(), suggestions.end()); - fmt.format(width() / 10); - setTextColor( OUTPUT_COLOR ); - const std::list& formatted = fmt.formattedOutput(); - for (std::list::const_iterator it = formatted.begin(); - it != formatted.end(); ++it) - { - append(it->c_str()); - } - setTextColor( NORMAL_COLOR ); - } - } - - // set up the next line on the console - append(""); - displayPrompt( ); - moveCursorToEnd( ); - QTextCursor cursor = textCursor(); - cursor.insertText( line ); - moveCursorToEnd( ); -} - -void PythonConsole::previousHistory( ) -{ - if ( ! cursorIsOnInputLine( ) ) - return; - - if ( ! m_historyBuffer.size( ) ) - return; - - QTextCursor cursor = textCursor(); - cursor.movePosition( QTextCursor::StartOfLine ); - cursor.movePosition( QTextCursor::Right, QTextCursor::MoveAnchor, PythonConsole::PROMPT.size( ) ); - cursor.movePosition( QTextCursor::EndOfLine, QTextCursor::KeepAnchor ); - cursor.removeSelectedText( ); - if ( m_historyIt != m_historyBuffer.begin( ) ) - { - --m_historyIt; - } - cursor.insertText( m_historyIt->c_str() ); -} - -void PythonConsole::nextHistory( ) -{ - if ( ! cursorIsOnInputLine( ) ) - return; - - if ( ! m_historyBuffer.size( ) ) - return; - if ( m_historyIt == m_historyBuffer.end( ) ) - { - return; - } - QTextCursor cursor = textCursor(); - cursor.movePosition( QTextCursor::StartOfLine ); - cursor.movePosition( QTextCursor::Right, QTextCursor::MoveAnchor, PythonConsole::PROMPT.size( ) ); - cursor.movePosition( QTextCursor::EndOfLine, QTextCursor::KeepAnchor ); - cursor.removeSelectedText( ); - ++m_historyIt; - if ( m_historyIt == m_historyBuffer.end( ) ) - { - return; - } - cursor.insertText( m_historyIt->c_str() ); -} - -void PythonConsole::moveCursorToEnd( ) -{ - QTextCursor cursor = textCursor(); - cursor.movePosition( QTextCursor::End ); - setTextCursor( cursor ); -} - -void PythonConsole::insertFromMimeData(const QMimeData *src) -{ - if (src->hasText()) { - QStringList list = src->text().split("\n",QString::KeepEmptyParts); - bool lastends = src->text().endsWith("\n"); - for (int i=0;i -#include -#include -#include "ParseHelper.h" -#include "ParseListener.h" - -class QWidget; -class QKeyEvent; - -class PythonConsole : public QTextEdit, ParseListener -{ - Q_OBJECT - - public: - PythonConsole(QWidget *parent = 0); - - void displayPrompt(); - void displayString(QString text); - - protected: - // override QTextEdit - virtual void keyPressEvent(QKeyEvent *e); - - virtual void handleReturnKeyPress(); - - virtual void insertFromMimeData(const QMimeData *src); - - /** - Handle a compilable chunk of Python user input. - */ - virtual void parseEvent(const ParseMessage &message); - - QString getLine(); - bool cursorIsOnInputLine(); - bool inputLineIsEmpty(); - bool canBackspace(); - bool canGoLeft(); - void autocomplete(); - void previousHistory(); - void nextHistory(); - void moveCursorToEnd(); - - static const QString PROMPT; - static const QString MULTILINE_PROMPT; - - static const QColor NORMAL_COLOR; - static const QColor ERROR_COLOR; - static const QColor OUTPUT_COLOR; - - ParseHelper m_parseHelper; - std::list m_historyBuffer; - std::list::const_iterator m_historyIt; -}; - -#endif // PYCONSOLE_H diff --git a/CMakeLists.txt b/CMakeLists.txt index b4493486..3ca7935e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,7 +7,31 @@ option(BUILD_PYTHON "Build Python Integration" ON) option(BUILD_TESTS "Build GUI" OFF) # List of families to build -set(FAMILIES generic ice40) +set(FAMILIES generic ice40 ecp5) + +set(ARCH "" CACHE STRING "Architecture family for nextpnr build") +set_property(CACHE ARCH PROPERTY STRINGS ${FAMILIES}) + +if (NOT ARCH) + message(STATUS "Architecture needs to be set, set desired one with -DARCH=xxx") + message(STATUS "Supported architectures are :") + message(STATUS " all") + foreach(item ${FAMILIES}) + message(STATUS " ${item}") + endforeach() + message(FATAL_ERROR "Architecture setting is mandatory") +endif () + +if (ARCH STREQUAL "all") + SET(ARCH ${FAMILIES}) +endif() + +foreach(item ${ARCH}) + if (NOT item IN_LIST FAMILIES) + message(FATAL_ERROR "Architecture '${item}' not in list of supported architectures") + endif() +endforeach() + set(CMAKE_CXX_STANDARD 11) if (MSVC) set(CMAKE_CONFIGURATION_TYPES "Debug;Release" CACHE STRING "" FORCE) @@ -30,6 +54,10 @@ find_package(Sanitizers) # List of Boost libraries to include set(boost_libs filesystem thread program_options) +if (BUILD_GUI AND NOT BUILD_PYTHON) + message(FATAL_ERROR "GUI requires Python to build") +endif() + if (BUILD_PYTHON) # TODO: sensible minimum Python version find_package(PythonInterp 3.5 REQUIRED) @@ -62,7 +90,7 @@ if (BUILD_TESTS) endif() if (BUILD_GUI) - add_subdirectory(3rdparty/QtPropertyBrowser ${CMAKE_CURRENT_BINARY_DIR}/generated/3rdparty/QtPropertyBrowser) + add_subdirectory(3rdparty/QtPropertyBrowser ${CMAKE_CURRENT_BINARY_DIR}/generated/3rdparty/QtPropertyBrowser EXCLUDE_FROM_ALL) endif() add_definitions("-DGIT_COMMIT_HASH=${GIT_COMMIT_HASH}") @@ -109,6 +137,13 @@ if (BUILD_PYTHON) endif () endif () + if (NOT Boost_PYTHON_FOUND) + find_package(Boost COMPONENTS python37 ${boost_libs}) + if ("${Boost_LIBRARIES}" MATCHES ".*(python|PYTHON).*" ) + set(Boost_PYTHON_FOUND TRUE) + endif () + endif () + if (NOT Boost_PYTHON_FOUND) STRING(REGEX REPLACE "([0-9]+\\.[0-9]+).*" "\\1" gentoo_version ${PYTHONLIBS_VERSION_STRING}) find_package(Boost COMPONENTS python-${gentoo_version} ${boost_libs}) @@ -132,22 +167,23 @@ if(MINGW) add_definitions("-Wa,-mbig-obj") endif(MINGW) -foreach (family ${FAMILIES}) +foreach (family ${ARCH}) + message(STATUS "Configuring architecture : ${family}") string(TOUPPER ${family} ufamily) aux_source_directory(${family}/ ${ufamily}_FILES) if (BUILD_GUI) - add_subdirectory(gui ${CMAKE_CURRENT_BINARY_DIR}/generated/gui/${family}) + add_subdirectory(gui ${CMAKE_CURRENT_BINARY_DIR}/generated/gui/${family} EXCLUDE_FROM_ALL) endif() # Add the CLI binary target - add_executable(nextpnr-${family} ${COMMON_FILES} ${${ufamily}_FILES} ) + add_executable(nextpnr-${family} ${COMMON_FILES} ${${ufamily}_FILES}) install(TARGETS nextpnr-${family} RUNTIME DESTINATION bin) target_compile_definitions(nextpnr-${family} PRIVATE MAIN_EXECUTABLE) if (BUILD_PYTHON) # Add the importable Python module target - PYTHON_ADD_MODULE(nextpnrpy_${family} EXCLUDE_FROM_ALL ${COMMON_FILES} ${${ufamily}_FILES}) + PYTHON_ADD_MODULE(nextpnrpy_${family} ${COMMON_FILES} ${${ufamily}_FILES}) endif() # Add any new per-architecture targets here @@ -178,7 +214,7 @@ foreach (family ${FAMILIES}) # Include family-specific source files to all family targets and set defines appropriately target_include_directories(${target} PRIVATE ${family}/ ${CMAKE_CURRENT_BINARY_DIR}/generated/) target_compile_definitions(${target} PRIVATE NEXTPNR_NAMESPACE=nextpnr_${family} ARCH_${ufamily} ARCHNAME=${family}) - target_link_libraries(${target} LINK_PUBLIC ${Boost_LIBRARIES}) + target_link_libraries(${target} LINK_PUBLIC ${Boost_LIBRARIES} pthread) add_sanitizers(${target}) if (BUILD_GUI) target_include_directories(${target} PRIVATE gui/${family}/ gui/) @@ -193,6 +229,7 @@ endforeach (family) file(GLOB_RECURSE CLANGFORMAT_FILES *.cc *.h) string(REGEX REPLACE "[^;]*/ice40/chipdbs/chipdb-[^;]*.cc" "" CLANGFORMAT_FILES "${CLANGFORMAT_FILES}") +string(REGEX REPLACE "[^;]*/ecp5/chipdbs/chipdb-[^;]*.cc" "" CLANGFORMAT_FILES "${CLANGFORMAT_FILES}") string(REGEX REPLACE "[^;]*/3rdparty[^;]*" "" CLANGFORMAT_FILES "${CLANGFORMAT_FILES}") string(REGEX REPLACE "[^;]*/generated[^;]*" "" CLANGFORMAT_FILES "${CLANGFORMAT_FILES}") diff --git a/README.md b/README.md index 195f08c3..da38500d 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ Supported Architectures ----------------------- - iCE40 +- ECP5 Prequisites ----------- @@ -23,23 +24,30 @@ Prequisites - For building on macOS, brew utility is needed. - Install all needed packages `brew install cmake python boost boost-python3 qt5` - Do not forget to add qt5 in path as well `echo 'export PATH="/usr/local/opt/qt/bin:$PATH"' >> ~/.bash_profile` - + - For ECP5 support, you must download [Project Trellis](https://github.com/SymbiFlow/prjtrellis), then follow its instructions to + download the latest database and build _libtrellis_. + + Building -------- + - Specifying target architecture is mandatory use ARCH parameter to set it. It is semicolon separated list. + - Use `cmake . -DARCH=all` to build all supported targets + - For example `cmake . -DARCH=ice40` would build just ICE40 support - Use CMake to generate the Makefiles (only needs to be done when `CMakeLists.txt` changes) - - For a debug build, run `cmake -DCMAKE_BUILD_TYPE=Debug .` - - For a debug build with HX1K support only, run ` cmake -DCMAKE_BUILD_TYPE=Debug -DICE40_HX1K_ONLY=1 .` - - For a release build, run `cmake .` + - For an iCE40 debug build, run `cmake -DARCH=ice40 -DCMAKE_BUILD_TYPE=Debug .` + - For an iCE40 debug build with HX1K support only, run `cmake -DARCH=ice40 -DCMAKE_BUILD_TYPE=Debug -DICE40_HX1K_ONLY=1 .` + - For an iCE40 and ECP5 release build, run `cmake -DARCH="ice40;ecp5" .` - Add `-DCMAKE_INSTALL_PREFIX=/your/install/prefix` to use a different install prefix to the default `/usr/local` - - For MSVC build with vcpkg use `cmake . -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake` using your vcpkg location + - For MSVC build with vcpkg use `-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake` using your vcpkg location - For MSVC x64 build adding `-G"Visual Studio 14 2015 Win64"` is needed. + - For ECP5 support, you must also specify the path to Project Trellis using `-DTRELLIS_ROOT=/path/trellis` - Use Make to run the build itself - For all binary targets, just run `make` - For just the iCE40 CLI&GUI binary, run `make nextpnr-ice40` - - To build binary without Python support, run `cmake -DBUILD_PYTHON=OFF .` - - To build binary without GUI, run `cmake -DBUILD_GUI=OFF .` - - For minimal binary without Python and GUI, run `cmake -DBUILD_PYTHON=OFF -DBUILD_GUI=OFF .` + - To build binary without Python support, use `-DBUILD_PYTHON=OFF` + - To build binary without GUI, use `-DBUILD_GUI=OFF` + - For minimal binary without Python and GUI, use `-DBUILD_PYTHON=OFF -DBUILD_GUI=OFF` - For just the iCE40 Python module, run `make nextpnrpy_ice40` - Using too many parallel jobs may lead to out-of-memory issues due to the significant memory needed to build the chipdbs - To install nextpnr, run `make install` @@ -47,12 +55,12 @@ Building Testing ------- - - To build test binaries as well, run `cmake -DBUILD_TESTS=OFF .` and after run `make tests` to run them, or you can run separate binaries. - - To use code sanitizers use: - - `cmake . -DSANITIZE_ADDRESS=ON` - - `cmake . -DSANITIZE_MEMORY=ON -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++` - - `cmake . -DSANITIZE_THREAD=ON` - - `cmake . -DSANITIZE_UNDEFINED=ON` + - To build test binaries as well, use `-DBUILD_TESTS=OFF` and after run `make tests` to run them, or you can run separate binaries. + - To use code sanitizers use the `cmake` options: + - `-DSANITIZE_ADDRESS=ON` + - `-DSANITIZE_MEMORY=ON -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++` + - `-DSANITIZE_THREAD=ON` + - `-DSANITIZE_UNDEFINED=ON` - Running valgrind example `valgrind --leak-check=yes --tool=memcheck ./nextpnr-ice40 --json ice40/blinky.json` Running @@ -65,6 +73,12 @@ Running produce `blinky.json`. - To place-and-route the blinky using nextpnr, run `./nextpnr-ice40 --hx1k --json ice40/blinky.json --pcf ice40/blinky.pcf --asc blinky.asc` + - For an ECP5 blinky, first synthesise using `yosys blinky.ys` in `ecp5/synth`. + - Then run ECP5 place-and route using + `./nextpnr-ecp5 --json ecp5/synth/blinky.json --basecfg ecp5/synth/ulx3s_empty.config --bit ecp5/synth/ulx3s.bit` + - Note that `ulx3s_empty.config` contains fixed/unknown bits to be copied to the output bitstream + - You can also use `--textcfg out.config` to write a text file describing the bitstream for debugging + Notes ------- diff --git a/common/nextpnr.h b/common/nextpnr.h index 37e193b9..50465869 100644 --- a/common/nextpnr.h +++ b/common/nextpnr.h @@ -136,7 +136,7 @@ NEXTPNR_NAMESPACE_BEGIN struct GraphicElement { - enum + enum type_t { G_NONE, G_LINE, @@ -145,6 +145,14 @@ struct GraphicElement G_LABEL } type = G_NONE; + enum style_t + { + G_FRAME, + G_HIDDEN, + G_INACTIVE, + G_ACTIVE, + } style = G_FRAME; + float x1 = 0, y1 = 0, x2 = 0, y2 = 0, z = 0; std::string text; }; @@ -155,6 +163,12 @@ NEXTPNR_NAMESPACE_END NEXTPNR_NAMESPACE_BEGIN +struct DecalXY +{ + DecalId decal; + float x = 0, y = 0; +}; + struct BelPin { BelId bel; @@ -253,6 +267,31 @@ struct BaseCtx delete idstring_str_to_idx; delete idstring_idx_to_str; } + + Context *getCtx() { return reinterpret_cast(this); } + + const Context *getCtx() const { return reinterpret_cast(this); } + + // -------------------------------------------------------------- + + bool allUiReload = false; + bool frameUiReload = false; + std::unordered_set belUiReload; + std::unordered_set wireUiReload; + std::unordered_set pipUiReload; + std::unordered_set groupUiReload; + + void refreshUi() { allUiReload = true; } + + void refreshUiFrame() { frameUiReload = true; } + + void refreshUiBel(BelId bel) { belUiReload.insert(bel); } + + void refreshUiWire(WireId wire) { wireUiReload.insert(wire); } + + void refreshUiPip(PipId pip) { pipUiReload.insert(pip); } + + void refreshUiGroup(GroupId group) { groupUiReload.insert(group); } }; NEXTPNR_NAMESPACE_END @@ -273,6 +312,11 @@ struct Context : Arch // -------------------------------------------------------------- + // provided by router1.cc + bool getActualRouteDelay(WireId src_wire, WireId dst_wire, delay_t &delay); + + // -------------------------------------------------------------- + uint64_t rngstate = 0x3141592653589793; uint64_t rng64() diff --git a/common/place_sa.cc b/common/placer1.cc similarity index 99% rename from common/place_sa.cc rename to common/placer1.cc index ab161c57..53295a91 100644 --- a/common/place_sa.cc +++ b/common/placer1.cc @@ -21,7 +21,7 @@ * */ -#include "place_sa.h" +#include "placer1.h" #include #include #include @@ -429,7 +429,7 @@ class SAPlacer const float post_legalise_dia_scale = 2; }; -bool place_design_sa(Context *ctx) +bool placer1(Context *ctx) { try { SAPlacer placer(ctx); diff --git a/common/place_sa.h b/common/placer1.h similarity index 95% rename from common/place_sa.h rename to common/placer1.h index 1fd8c712..477fae56 100644 --- a/common/place_sa.h +++ b/common/placer1.h @@ -23,7 +23,7 @@ NEXTPNR_NAMESPACE_BEGIN -extern bool place_design_sa(Context *ctx); +extern bool placer1(Context *ctx); NEXTPNR_NAMESPACE_END diff --git a/common/pybindings.cc b/common/pybindings.cc index 64055755..061dfc47 100644 --- a/common/pybindings.cc +++ b/common/pybindings.cc @@ -110,13 +110,13 @@ BOOST_PYTHON_MODULE(MODULE_NAME) readwrite_wrapper, pass_through>::def_wrap(ci_cls, "belStrength"); readonly_wrapper>::def_wrap(ci_cls, - "pins"); + "pins"); auto pi_cls = class_>("PortInfo", no_init); readwrite_wrapper, conv_from_str>::def_wrap(pi_cls, "name"); readonly_wrapper>::def_wrap(pi_cls, - "net"); + "net"); readwrite_wrapper, pass_through>::def_wrap(pi_cls, "type"); @@ -131,11 +131,11 @@ BOOST_PYTHON_MODULE(MODULE_NAME) readonly_wrapper>::def_wrap( ni_cls, "users"); readonly_wrapper>::def_wrap(ni_cls, - "wires"); + "wires"); auto pr_cls = class_>("PortRef", no_init); readonly_wrapper>::def_wrap(pr_cls, - "cell"); + "cell"); readwrite_wrapper, conv_from_str>::def_wrap(pr_cls, "port"); readwrite_wrapper, @@ -151,7 +151,9 @@ BOOST_PYTHON_MODULE(MODULE_NAME) arch_wrap_python(); } +#ifdef MAIN_EXECUTABLE static wchar_t *program; +#endif void init_python(const char *executable, bool first) { diff --git a/common/route.cc b/common/router1.cc similarity index 99% rename from common/route.cc rename to common/router1.cc index 82525fcc..94c7070e 100644 --- a/common/route.cc +++ b/common/router1.cc @@ -21,7 +21,7 @@ #include #include "log.h" -#include "route.h" +#include "router1.h" namespace { @@ -402,7 +402,7 @@ struct Router NEXTPNR_NAMESPACE_BEGIN -bool route_design(Context *ctx) +bool router1(Context *ctx) { try { int totalVisitCnt = 0, totalRevisitCnt = 0, totalOvertimeRevisitCnt = 0; @@ -643,10 +643,10 @@ bool route_design(Context *ctx) } } -bool get_actual_route_delay(Context *ctx, WireId src_wire, WireId dst_wire, delay_t &delay) +bool Context::getActualRouteDelay(WireId src_wire, WireId dst_wire, delay_t &delay) { RipupScoreboard scores; - Router router(ctx, scores, src_wire, dst_wire); + Router router(this, scores, src_wire, dst_wire); if (router.routedOkay) delay = router.visited.at(dst_wire).delay; return router.routedOkay; diff --git a/ice40/pack.h b/common/router1.h similarity index 87% rename from ice40/pack.h rename to common/router1.h index cdebdd79..38552c58 100644 --- a/ice40/pack.h +++ b/common/router1.h @@ -2,7 +2,6 @@ * nextpnr -- Next Generation Place and Route * * Copyright (C) 2018 Clifford Wolf - * Copyright (C) 2018 David Shah * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -18,15 +17,15 @@ * */ -#ifndef PACK_H -#define PACK_H +#ifndef ROUTER1_H +#define ROUTER1_H #include "nextpnr.h" NEXTPNR_NAMESPACE_BEGIN -bool pack_design(Context *ctx); +extern bool router1(Context *ctx); NEXTPNR_NAMESPACE_END -#endif // ROUTE_H +#endif // ROUTER1_H diff --git a/common/util.h b/common/util.h index 60eb35af..8f361dc8 100644 --- a/common/util.h +++ b/common/util.h @@ -39,6 +39,18 @@ ValueType get_or_default(const Container &ct, const KeyType &key, ValueType def return found->second; }; +// Get a value from a map-style container, returning default if value is not +// found (forces string) +template +std::string str_or_default(const Container &ct, const KeyType &key, std::string def = "") +{ + auto found = ct.find(key); + if (found == ct.end()) + return def; + else + return found->second; +}; + // Get a value from a map-style container, converting to int, and returning // default if value is not found template int int_or_default(const Container &ct, const KeyType &key, int def = 0) @@ -84,6 +96,7 @@ inline const NetInfo *get_net_or_empty(const CellInfo *cell, const IdString port else return nullptr; }; + NEXTPNR_NAMESPACE_END #endif diff --git a/ecp5/.gitignore b/ecp5/.gitignore new file mode 100644 index 00000000..3249a7bb --- /dev/null +++ b/ecp5/.gitignore @@ -0,0 +1,2 @@ +__pycache__ +chipdbs/ diff --git a/ecp5/arch.cc b/ecp5/arch.cc new file mode 100644 index 00000000..51f4db84 --- /dev/null +++ b/ecp5/arch.cc @@ -0,0 +1,333 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Clifford Wolf + * Copyright (C) 2018 David Shah + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include +#include +#include +#include "log.h" +#include "nextpnr.h" +#include "placer1.h" +#include "router1.h" +#include "util.h" + +NEXTPNR_NAMESPACE_BEGIN + +static std::tuple split_identifier_name(const std::string &name) +{ + size_t first_slash = name.find('/'); + NPNR_ASSERT(first_slash != std::string::npos); + size_t second_slash = name.find('/', first_slash + 1); + NPNR_ASSERT(second_slash != std::string::npos); + return std::make_tuple(std::stoi(name.substr(1, first_slash)), + std::stoi(name.substr(first_slash + 2, second_slash - first_slash)), + name.substr(second_slash + 1)); +}; + +// ----------------------------------------------------------------------- + +IdString Arch::belTypeToId(BelType type) const +{ + if (type == TYPE_TRELLIS_SLICE) + return id("TRELLIS_SLICE"); + if (type == TYPE_TRELLIS_IO) + return id("TRELLIS_IO"); + return IdString(); +} + +BelType Arch::belTypeFromId(IdString type) const +{ + if (type == id("TRELLIS_SLICE")) + return TYPE_TRELLIS_SLICE; + if (type == id("TRELLIS_IO")) + return TYPE_TRELLIS_IO; + return TYPE_NONE; +} + +// ----------------------------------------------------------------------- + +void IdString::initialize_arch(const BaseCtx *ctx) +{ +#define X(t) initialize_add(ctx, #t, PIN_##t); + +#include "portpins.inc" + +#undef X +} + +IdString Arch::portPinToId(PortPin type) const +{ + IdString ret; + if (type > 0 && type < PIN_MAXIDX) + ret.index = type; + return ret; +} + +PortPin Arch::portPinFromId(IdString type) const +{ + if (type.index > 0 && type.index < PIN_MAXIDX) + return PortPin(type.index); + return PIN_NONE; +} + +// ----------------------------------------------------------------------- + +static const ChipInfoPOD *get_chip_info(const RelPtr *ptr) { return ptr->get(); } + +#if defined(_MSC_VER) +void load_chipdb(); +#endif + +#define LFE5U_45F_ONLY + +Arch::Arch(ArchArgs args) : args(args) +{ +#if defined(_MSC_VER) + load_chipdb(); +#endif +#ifdef LFE5U_45F_ONLY + if (args.type == ArchArgs::LFE5U_45F) { + chip_info = get_chip_info(reinterpret_cast *>(chipdb_blob_45k)); + } else { + log_error("Unsupported ECP5 chip type.\n"); + } +#else + if (args.type == ArchArgs::LFE5U_25F) { + chip_info = get_chip_info(reinterpret_cast *>(chipdb_blob_25k)); + } else if (args.type == ArchArgs::LFE5U_45F) { + chip_info = get_chip_info(reinterpret_cast *>(chipdb_blob_45k)); + } else if (args.type == ArchArgs::LFE5U_85F) { + chip_info = get_chip_info(reinterpret_cast *>(chipdb_blob_85k)); + } else { + log_error("Unsupported ECP5 chip type.\n"); + } +#endif +} + +// ----------------------------------------------------------------------- + +std::string Arch::getChipName() +{ + + if (args.type == ArchArgs::LFE5U_25F) { + return "LFE5U-25F"; + } else if (args.type == ArchArgs::LFE5U_45F) { + return "LFE5U-45F"; + } else if (args.type == ArchArgs::LFE5U_85F) { + return "LFE5U-85F"; + } else { + log_error("Unknown chip\n"); + } +} + +// ----------------------------------------------------------------------- + +IdString Arch::archArgsToId(ArchArgs args) const +{ + if (args.type == ArchArgs::LFE5U_25F) + return id("lfe5u_25f"); + if (args.type == ArchArgs::LFE5U_45F) + return id("lfe5u_45f"); + if (args.type == ArchArgs::LFE5U_85F) + return id("lfe5u_85f"); + return IdString(); +} + +// ----------------------------------------------------------------------- + +BelId Arch::getBelByName(IdString name) const +{ + BelId ret; + auto it = bel_by_name.find(name); + if (it != bel_by_name.end()) + return it->second; + + Location loc; + std::string basename; + std::tie(loc.x, loc.y, basename) = split_identifier_name(name.str(this)); + ret.location = loc; + const LocationTypePOD *loci = locInfo(ret); + for (int i = 0; i < loci->num_bels; i++) { + if (std::strcmp(loci->bel_data[i].name.get(), basename.c_str()) == 0) { + ret.index = i; + break; + } + } + if (ret.index >= 0) + bel_by_name[name] = ret; + return ret; +} + +BelRange Arch::getBelsAtSameTile(BelId bel) const +{ + BelRange br; + NPNR_ASSERT(bel != BelId()); + br.b.cursor_tile = bel.location.y * chip_info->width + bel.location.x; + br.e.cursor_tile = bel.location.y * chip_info->width + bel.location.x; + br.b.cursor_index = 0; + br.e.cursor_index = locInfo(bel)->num_bels; + return br; +} + +WireId Arch::getWireBelPin(BelId bel, PortPin pin) const +{ + WireId ret; + + NPNR_ASSERT(bel != BelId()); + + int num_bel_wires = locInfo(bel)->bel_data[bel.index].num_bel_wires; + const BelWirePOD *bel_wires = locInfo(bel)->bel_data[bel.index].bel_wires.get(); + for (int i = 0; i < num_bel_wires; i++) + if (bel_wires[i].port == pin) { + ret.location = bel.location + bel_wires[i].rel_wire_loc; + ret.index = bel_wires[i].wire_index; + break; + } + + return ret; +} + +// ----------------------------------------------------------------------- + +WireId Arch::getWireByName(IdString name) const +{ + WireId ret; + auto it = wire_by_name.find(name); + if (it != wire_by_name.end()) + return it->second; + + Location loc; + std::string basename; + std::tie(loc.x, loc.y, basename) = split_identifier_name(name.str(this)); + ret.location = loc; + const LocationTypePOD *loci = locInfo(ret); + for (int i = 0; i < loci->num_wires; i++) { + if (std::strcmp(loci->wire_data[i].name.get(), basename.c_str()) == 0) { + ret.index = i; + ret.location = loc; + break; + } + } + if (ret.index >= 0) + wire_by_name[name] = ret; + else + ret.location = Location(); + return ret; +} + +// ----------------------------------------------------------------------- + +PipId Arch::getPipByName(IdString name) const +{ + auto it = pip_by_name.find(name); + if (it != pip_by_name.end()) + return it->second; + + PipId ret; + Location loc; + std::string basename; + std::tie(loc.x, loc.y, basename) = split_identifier_name(name.str(this)); + const LocationTypePOD *loci = locInfo(ret); + for (int i = 0; i < loci->num_pips; i++) { + PipId curr; + curr.location = loc; + curr.index = i; + pip_by_name[getPipName(curr)] = curr; + } + return pip_by_name[name]; +} + +IdString Arch::getPipName(PipId pip) const +{ + NPNR_ASSERT(pip != PipId()); + + int x = pip.location.x; + int y = pip.location.y; + + std::string src_name = getWireName(getPipSrcWire(pip)).str(this); + std::replace(src_name.begin(), src_name.end(), '/', '.'); + + std::string dst_name = getWireName(getPipDstWire(pip)).str(this); + std::replace(dst_name.begin(), dst_name.end(), '/', '.'); + + return id("X" + std::to_string(x) + "/Y" + std::to_string(y) + "/" + src_name + ".->." + dst_name); +} + +// ----------------------------------------------------------------------- + +BelId Arch::getPackagePinBel(const std::string &pin) const { return BelId(); } + +std::string Arch::getBelPackagePin(BelId bel) const { return ""; } +// ----------------------------------------------------------------------- + +void Arch::estimatePosition(BelId bel, int &x, int &y, bool &gb) const +{ + x = bel.location.x; + y = bel.location.y; + gb = false; +} + +delay_t Arch::estimateDelay(WireId src, WireId dst) const +{ + return abs(src.location.x - dst.location.x) + abs(src.location.y - dst.location.y); +} + +// ----------------------------------------------------------------------- + +bool Arch::place() { return placer1(getCtx()); } + +bool Arch::route() { return router1(getCtx()); } + +// ----------------------------------------------------------------------- + +std::vector Arch::getDecalGraphics(DecalId decalId) const +{ + std::vector ret; + // FIXME + return ret; +} + +DecalXY Arch::getFrameDecal() const { return {}; } + +DecalXY Arch::getBelDecal(BelId bel) const { return {}; } + +DecalXY Arch::getWireDecal(WireId wire) const { return {}; } + +DecalXY Arch::getPipDecal(PipId pip) const { return {}; }; + +DecalXY Arch::getGroupDecal(GroupId pip) const { return {}; }; + +// ----------------------------------------------------------------------- + +bool Arch::isValidBelForCell(CellInfo *cell, BelId bel) const { return true; } + +bool Arch::isBelLocationValid(BelId bel) const { return true; } + +// ----------------------------------------------------------------------- + +bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, delay_t &delay) const +{ + return false; +} + +IdString Arch::getPortClock(const CellInfo *cell, IdString port) const { return IdString(); } + +bool Arch::isClockPort(const CellInfo *cell, IdString port) const { return false; } + +NEXTPNR_NAMESPACE_END diff --git a/ecp5/arch.h b/ecp5/arch.h new file mode 100644 index 00000000..930c488e --- /dev/null +++ b/ecp5/arch.h @@ -0,0 +1,765 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Clifford Wolf + * Copyright (C) 2018 David Shah + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#ifndef NEXTPNR_H +#error Include "arch.h" via "nextpnr.h" only. +#endif + +#include + +NEXTPNR_NAMESPACE_BEGIN + +/**** Everything in this section must be kept in sync with chipdb.py ****/ + +template struct RelPtr +{ + int32_t offset; + + // void set(const T *ptr) { + // offset = reinterpret_cast(ptr) - + // reinterpret_cast(this); + // } + + const T *get() const { return reinterpret_cast(reinterpret_cast(this) + offset); } + + const T &operator[](size_t index) const { return get()[index]; } + + const T &operator*() const { return *(get()); } + + const T *operator->() const { return get(); } +}; + +NPNR_PACKED_STRUCT(struct BelWirePOD { + LocationPOD rel_wire_loc; + int32_t wire_index; + PortPin port; +}); + +NPNR_PACKED_STRUCT(struct BelInfoPOD { + RelPtr name; + BelType type; + int32_t num_bel_wires; + RelPtr bel_wires; +}); + +NPNR_PACKED_STRUCT(struct BelPortPOD { + LocationPOD rel_bel_loc; + int32_t bel_index; + PortPin port; +}); + +NPNR_PACKED_STRUCT(struct PipInfoPOD { + LocationPOD rel_src_loc, rel_dst_loc; + int32_t src_idx, dst_idx; + int32_t delay; + int16_t tile_type; + int8_t pip_type; + int8_t padding_0; +}); + +NPNR_PACKED_STRUCT(struct PipLocatorPOD { + LocationPOD rel_loc; + int32_t index; +}); + +NPNR_PACKED_STRUCT(struct WireInfoPOD { + RelPtr name; + int32_t num_uphill, num_downhill; + RelPtr pips_uphill, pips_downhill; + + int32_t num_bels_downhill; + BelPortPOD bel_uphill; + RelPtr bels_downhill; +}); + +NPNR_PACKED_STRUCT(struct LocationTypePOD { + int32_t num_bels, num_wires, num_pips; + RelPtr bel_data; + RelPtr wire_data; + RelPtr pip_data; +}); + +NPNR_PACKED_STRUCT(struct ChipInfoPOD { + int32_t width, height; + int32_t num_tiles; + int32_t num_location_types; + RelPtr locations; + RelPtr location_type; + RelPtr> tiletype_names; +}); + +#if defined(_MSC_VER) +extern const char *chipdb_blob_25k; +extern const char *chipdb_blob_45k; +extern const char *chipdb_blob_85k; +#else +extern const char chipdb_blob_25k[]; +extern const char chipdb_blob_45k[]; +extern const char chipdb_blob_85k[]; +#endif + +/************************ End of chipdb section. ************************/ + +struct BelIterator +{ + const ChipInfoPOD *chip; + int cursor_index; + int cursor_tile; + + BelIterator operator++() + { + cursor_index++; + while (cursor_tile < chip->num_tiles && + cursor_index >= chip->locations[chip->location_type[cursor_tile]].num_bels) { + cursor_index = 0; + cursor_tile++; + } + return *this; + } + BelIterator operator++(int) + { + BelIterator prior(*this); + ++(*this); + return prior; + } + + bool operator!=(const BelIterator &other) const + { + return cursor_index != other.cursor_index || cursor_tile != other.cursor_tile; + } + + bool operator==(const BelIterator &other) const + { + return cursor_index == other.cursor_index && cursor_tile == other.cursor_tile; + } + + BelId operator*() const + { + BelId ret; + ret.location.x = cursor_tile % chip->width; + ret.location.y = cursor_tile / chip->width; + ret.index = cursor_index; + return ret; + } +}; + +struct BelRange +{ + BelIterator b, e; + BelIterator begin() const { return b; } + BelIterator end() const { return e; } +}; + +// ----------------------------------------------------------------------- + +struct BelPinIterator +{ + const BelPortPOD *ptr = nullptr; + Location wire_loc; + void operator++() { ptr++; } + bool operator!=(const BelPinIterator &other) const { return ptr != other.ptr; } + + BelPin operator*() const + { + BelPin ret; + ret.bel.index = ptr->bel_index; + ret.bel.location = wire_loc + ptr->rel_bel_loc; + ret.pin = ptr->port; + return ret; + } +}; + +struct BelPinRange +{ + BelPinIterator b, e; + BelPinIterator begin() const { return b; } + BelPinIterator end() const { return e; } +}; + +// ----------------------------------------------------------------------- + +struct WireIterator +{ + const ChipInfoPOD *chip; + int cursor_index; + int cursor_tile; + + WireIterator operator++() + { + cursor_index++; + while (cursor_tile < chip->num_tiles && + cursor_index >= chip->locations[chip->location_type[cursor_tile]].num_wires) { + cursor_index = 0; + cursor_tile++; + } + return *this; + } + WireIterator operator++(int) + { + WireIterator prior(*this); + ++(*this); + return prior; + } + + bool operator!=(const WireIterator &other) const + { + return cursor_index != other.cursor_index || cursor_tile != other.cursor_tile; + } + + bool operator==(const WireIterator &other) const + { + return cursor_index == other.cursor_index && cursor_tile == other.cursor_tile; + } + + WireId operator*() const + { + WireId ret; + ret.location.x = cursor_tile % chip->width; + ret.location.y = cursor_tile / chip->width; + ret.index = cursor_index; + return ret; + } +}; + +struct WireRange +{ + WireIterator b, e; + WireIterator begin() const { return b; } + WireIterator end() const { return e; } +}; + +// ----------------------------------------------------------------------- + +struct AllPipIterator +{ + const ChipInfoPOD *chip; + int cursor_index; + int cursor_tile; + + AllPipIterator operator++() + { + cursor_index++; + while (cursor_tile < chip->num_tiles && + cursor_index >= chip->locations[chip->location_type[cursor_tile]].num_pips) { + cursor_index = 0; + cursor_tile++; + } + return *this; + } + AllPipIterator operator++(int) + { + AllPipIterator prior(*this); + ++(*this); + return prior; + } + + bool operator!=(const AllPipIterator &other) const + { + return cursor_index != other.cursor_index || cursor_tile != other.cursor_tile; + } + + bool operator==(const AllPipIterator &other) const + { + return cursor_index == other.cursor_index && cursor_tile == other.cursor_tile; + } + + PipId operator*() const + { + PipId ret; + ret.location.x = cursor_tile % chip->width; + ret.location.y = cursor_tile / chip->width; + ret.index = cursor_index; + return ret; + } +}; + +struct AllPipRange +{ + AllPipIterator b, e; + AllPipIterator begin() const { return b; } + AllPipIterator end() const { return e; } +}; + +// ----------------------------------------------------------------------- + +struct PipIterator +{ + + const PipLocatorPOD *cursor = nullptr; + Location wire_loc; + + void operator++() { cursor++; } + bool operator!=(const PipIterator &other) const { return cursor != other.cursor; } + + PipId operator*() const + { + PipId ret; + ret.index = cursor->index; + ret.location = wire_loc + cursor->rel_loc; + return ret; + } +}; + +struct PipRange +{ + PipIterator b, e; + PipIterator begin() const { return b; } + PipIterator end() const { return e; } +}; + +struct ArchArgs +{ + enum + { + NONE, + LFE5U_25F, + LFE5U_45F, + LFE5U_85F, + } type = NONE; + std::string package; + int speed = 6; +}; + +struct Arch : BaseCtx +{ + const ChipInfoPOD *chip_info; + + mutable std::unordered_map bel_by_name; + mutable std::unordered_map wire_by_name; + mutable std::unordered_map pip_by_name; + + std::unordered_map bel_to_cell; + std::unordered_map wire_to_net; + std::unordered_map pip_to_net; + std::unordered_map switches_locked; + + ArchArgs args; + Arch(ArchArgs args); + + std::string getChipName(); + + IdString archId() const { return id("ecp5"); } + IdString archArgsToId(ArchArgs args) const; + + IdString belTypeToId(BelType type) const; + BelType belTypeFromId(IdString id) const; + + IdString portPinToId(PortPin type) const; + PortPin portPinFromId(IdString id) const; + + // ------------------------------------------------- + + BelId getBelByName(IdString name) const; + + template const LocationTypePOD *locInfo(Id &id) const + { + return &(chip_info->locations[chip_info->location_type[id.location.y * chip_info->width + id.location.x]]); + } + + IdString getBelName(BelId bel) const + { + NPNR_ASSERT(bel != BelId()); + std::stringstream name; + name << "X" << bel.location.x << "/Y" << bel.location.y << "/" << locInfo(bel)->bel_data[bel.index].name.get(); + return id(name.str()); + } + + uint32_t getBelChecksum(BelId bel) const { return bel.index; } + + void bindBel(BelId bel, IdString cell, PlaceStrength strength) + { + NPNR_ASSERT(bel != BelId()); + NPNR_ASSERT(bel_to_cell[bel] == IdString()); + bel_to_cell[bel] = cell; + cells[cell]->bel = bel; + cells[cell]->belStrength = strength; + } + + void unbindBel(BelId bel) + { + NPNR_ASSERT(bel != BelId()); + NPNR_ASSERT(bel_to_cell[bel] != IdString()); + cells[bel_to_cell[bel]]->bel = BelId(); + cells[bel_to_cell[bel]]->belStrength = STRENGTH_NONE; + bel_to_cell[bel] = IdString(); + } + + bool checkBelAvail(BelId bel) const + { + NPNR_ASSERT(bel != BelId()); + return bel_to_cell.find(bel) == bel_to_cell.end() || bel_to_cell.at(bel) == IdString(); + } + + IdString getBoundBelCell(BelId bel) const + { + NPNR_ASSERT(bel != BelId()); + if (bel_to_cell.find(bel) == bel_to_cell.end()) + return IdString(); + else + return bel_to_cell.at(bel); + } + + IdString getConflictingBelCell(BelId bel) const + { + NPNR_ASSERT(bel != BelId()); + if (bel_to_cell.find(bel) == bel_to_cell.end()) + return IdString(); + else + return bel_to_cell.at(bel); + } + + BelRange getBels() const + { + BelRange range; + range.b.cursor_tile = 0; + range.b.cursor_index = -1; + range.b.chip = chip_info; + ++range.b; //-1 and then ++ deals with the case of no Bels in the first tile + range.e.cursor_tile = chip_info->width * chip_info->height; + range.e.cursor_index = 0; + range.e.chip = chip_info; + return range; + } + + BelRange getBelsByType(BelType type) const + { + BelRange range; +// FIXME +#if 0 + if (type == "TYPE_A") { + range.b.cursor = bels_type_a_begin; + range.e.cursor = bels_type_a_end; + } + ... +#endif + return range; + } + + BelRange getBelsAtSameTile(BelId bel) const; + + BelType getBelType(BelId bel) const + { + NPNR_ASSERT(bel != BelId()); + return locInfo(bel)->bel_data[bel.index].type; + } + + WireId getWireBelPin(BelId bel, PortPin pin) const; + + BelPin getBelPinUphill(WireId wire) const + { + BelPin ret; + NPNR_ASSERT(wire != WireId()); + + if (locInfo(wire)->wire_data[wire.index].bel_uphill.bel_index >= 0) { + ret.bel.index = locInfo(wire)->wire_data[wire.index].bel_uphill.bel_index; + ret.bel.location = wire.location + locInfo(wire)->wire_data[wire.index].bel_uphill.rel_bel_loc; + ret.pin = locInfo(wire)->wire_data[wire.index].bel_uphill.port; + } + + return ret; + } + + BelPinRange getBelPinsDownhill(WireId wire) const + { + BelPinRange range; + NPNR_ASSERT(wire != WireId()); + range.b.ptr = locInfo(wire)->wire_data[wire.index].bels_downhill.get(); + range.b.wire_loc = wire.location; + range.e.ptr = range.b.ptr + locInfo(wire)->wire_data[wire.index].num_bels_downhill; + range.e.wire_loc = wire.location; + return range; + } + + // ------------------------------------------------- + + WireId getWireByName(IdString name) const; + + IdString getWireName(WireId wire) const + { + NPNR_ASSERT(wire != WireId()); + + std::stringstream name; + name << "X" << wire.location.x << "/Y" << wire.location.y << "/" + << locInfo(wire)->wire_data[wire.index].name.get(); + return id(name.str()); + } + + uint32_t getWireChecksum(WireId wire) const { return wire.index; } + + void bindWire(WireId wire, IdString net, PlaceStrength strength) + { + NPNR_ASSERT(wire != WireId()); + NPNR_ASSERT(wire_to_net[wire] == IdString()); + wire_to_net[wire] = net; + nets[net]->wires[wire].pip = PipId(); + nets[net]->wires[wire].strength = strength; + } + + void unbindWire(WireId wire) + { + NPNR_ASSERT(wire != WireId()); + NPNR_ASSERT(wire_to_net[wire] != IdString()); + + auto &net_wires = nets[wire_to_net[wire]]->wires; + auto it = net_wires.find(wire); + NPNR_ASSERT(it != net_wires.end()); + + auto pip = it->second.pip; + if (pip != PipId()) { + pip_to_net[pip] = IdString(); + } + + net_wires.erase(it); + wire_to_net[wire] = IdString(); + } + + bool checkWireAvail(WireId wire) const + { + NPNR_ASSERT(wire != WireId()); + return wire_to_net.find(wire) == wire_to_net.end() || wire_to_net.at(wire) == IdString(); + } + + IdString getBoundWireNet(WireId wire) const + { + NPNR_ASSERT(wire != WireId()); + if (wire_to_net.find(wire) == wire_to_net.end()) + return IdString(); + else + return wire_to_net.at(wire); + } + + IdString getConflictingWireNet(WireId wire) const + { + NPNR_ASSERT(wire != WireId()); + if (wire_to_net.find(wire) == wire_to_net.end()) + return IdString(); + else + return wire_to_net.at(wire); + } + + WireRange getWires() const + { + WireRange range; + range.b.cursor_tile = 0; + range.b.cursor_index = -1; + range.b.chip = chip_info; + ++range.b; //-1 and then ++ deals with the case of no wries in the first tile + range.e.cursor_tile = chip_info->width * chip_info->height; + range.e.cursor_index = 0; + range.e.chip = chip_info; + return range; + } + + // ------------------------------------------------- + + PipId getPipByName(IdString name) const; + IdString getPipName(PipId pip) const; + + uint32_t getPipChecksum(PipId pip) const { return pip.index; } + + void bindPip(PipId pip, IdString net, PlaceStrength strength) + { + NPNR_ASSERT(pip != PipId()); + NPNR_ASSERT(pip_to_net[pip] == IdString()); + + pip_to_net[pip] = net; + + WireId dst; + dst.index = locInfo(pip)->pip_data[pip.index].dst_idx; + dst.location = pip.location + locInfo(pip)->pip_data[pip.index].rel_dst_loc; + NPNR_ASSERT(wire_to_net[dst] == IdString()); + wire_to_net[dst] = net; + nets[net]->wires[dst].pip = pip; + nets[net]->wires[dst].strength = strength; + } + + void unbindPip(PipId pip) + { + NPNR_ASSERT(pip != PipId()); + NPNR_ASSERT(pip_to_net[pip] != IdString()); + + WireId dst; + dst.index = locInfo(pip)->pip_data[pip.index].dst_idx; + dst.location = pip.location + locInfo(pip)->pip_data[pip.index].rel_dst_loc; + NPNR_ASSERT(wire_to_net[dst] != IdString()); + wire_to_net[dst] = IdString(); + nets[pip_to_net[pip]]->wires.erase(dst); + + pip_to_net[pip] = IdString(); + } + + bool checkPipAvail(PipId pip) const + { + NPNR_ASSERT(pip != PipId()); + return pip_to_net.find(pip) == pip_to_net.end() || pip_to_net.at(pip) == IdString(); + } + + IdString getBoundPipNet(PipId pip) const + { + NPNR_ASSERT(pip != PipId()); + if (pip_to_net.find(pip) == pip_to_net.end()) + return IdString(); + else + return pip_to_net.at(pip); + } + + IdString getConflictingPipNet(PipId pip) const + { + NPNR_ASSERT(pip != PipId()); + if (pip_to_net.find(pip) == pip_to_net.end()) + return IdString(); + else + return pip_to_net.at(pip); + } + + AllPipRange getPips() const + { + AllPipRange range; + range.b.cursor_tile = 0; + range.b.cursor_index = -1; + range.b.chip = chip_info; + ++range.b; //-1 and then ++ deals with the case of no wries in the first tile + range.e.cursor_tile = chip_info->width * chip_info->height; + range.e.cursor_index = 0; + range.e.chip = chip_info; + return range; + } + + WireId getPipSrcWire(PipId pip) const + { + WireId wire; + NPNR_ASSERT(pip != PipId()); + wire.index = locInfo(pip)->pip_data[pip.index].src_idx; + wire.location = pip.location + locInfo(pip)->pip_data[pip.index].rel_src_loc; + return wire; + } + + WireId getPipDstWire(PipId pip) const + { + WireId wire; + NPNR_ASSERT(pip != PipId()); + wire.index = locInfo(pip)->pip_data[pip.index].dst_idx; + wire.location = pip.location + locInfo(pip)->pip_data[pip.index].rel_dst_loc; + return wire; + } + + DelayInfo getPipDelay(PipId pip) const + { + DelayInfo delay; + NPNR_ASSERT(pip != PipId()); + delay.delay = locInfo(pip)->pip_data[pip.index].delay; + return delay; + } + + PipRange getPipsDownhill(WireId wire) const + { + PipRange range; + NPNR_ASSERT(wire != WireId()); + range.b.cursor = locInfo(wire)->wire_data[wire.index].pips_downhill.get(); + range.b.wire_loc = wire.location; + range.e.cursor = range.b.cursor + locInfo(wire)->wire_data[wire.index].num_downhill; + range.e.wire_loc = wire.location; + return range; + } + + PipRange getPipsUphill(WireId wire) const + { + PipRange range; + NPNR_ASSERT(wire != WireId()); + range.b.cursor = locInfo(wire)->wire_data[wire.index].pips_uphill.get(); + range.b.wire_loc = wire.location; + range.e.cursor = range.b.cursor + locInfo(wire)->wire_data[wire.index].num_uphill; + range.e.wire_loc = wire.location; + return range; + } + + PipRange getWireAliases(WireId wire) const + { + PipRange range; + NPNR_ASSERT(wire != WireId()); + range.b.cursor = nullptr; + range.e.cursor = nullptr; + return range; + } + + std::string getPipTiletype(PipId pip) const + { + return chip_info->tiletype_names[locInfo(pip)->pip_data[pip.index].tile_type].get(); + } + + int8_t getPipType(PipId pip) const { return locInfo(pip)->pip_data[pip.index].pip_type; } + + BelId getPackagePinBel(const std::string &pin) const; + std::string getBelPackagePin(BelId bel) const; + + // ------------------------------------------------- + + GroupId getGroupByName(IdString name) const { return GroupId(); } + IdString getGroupName(GroupId group) const { return IdString(); } + std::vector getGroups() const { return std::vector(); } + std::vector getGroupBels(GroupId group) const { return std::vector(); } + std::vector getGroupWires(GroupId group) const { return std::vector(); } + std::vector getGroupPips(GroupId group) const { return std::vector(); } + std::vector getGroupGroups(GroupId group) const { return std::vector(); } + + // ------------------------------------------------- + + void estimatePosition(BelId bel, int &x, int &y, bool &gb) const; + delay_t estimateDelay(WireId src, WireId dst) const; + delay_t getDelayEpsilon() const { return 20; } + delay_t getRipupDelayPenalty() const { return 200; } + float getDelayNS(delay_t v) const { return v * 0.001; } + uint32_t getDelayChecksum(delay_t v) const { return v; } + + // ------------------------------------------------- + + bool pack(); + bool place(); + bool route(); + + // ------------------------------------------------- + + std::vector getDecalGraphics(DecalId decal) const; + + DecalXY getFrameDecal() const; + DecalXY getBelDecal(BelId bel) const; + DecalXY getWireDecal(WireId wire) const; + DecalXY getPipDecal(PipId pip) const; + DecalXY getGroupDecal(GroupId group) const; + + // ------------------------------------------------- + + // Get the delay through a cell from one port to another, returning false + // if no path exists + bool getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, delay_t &delay) const; + // Get the associated clock to a port, or empty if the port is combinational + IdString getPortClock(const CellInfo *cell, IdString port) const; + // Return true if a port is a clock + bool isClockPort(const CellInfo *cell, IdString port) const; + // Return true if a port is a net + bool isGlobalNet(const NetInfo *net) const; + + // ------------------------------------------------- + // Placement validity checks + bool isValidBelForCell(CellInfo *cell, BelId bel) const; + bool isBelLocationValid(BelId bel) const; +}; + +NEXTPNR_NAMESPACE_END diff --git a/common/route.h b/ecp5/arch_pybindings.cc similarity index 83% rename from common/route.h rename to ecp5/arch_pybindings.cc index 1da9edc2..8310c3a1 100644 --- a/common/route.h +++ b/ecp5/arch_pybindings.cc @@ -2,6 +2,7 @@ * nextpnr -- Next Generation Place and Route * * Copyright (C) 2018 Clifford Wolf + * Copyright (C) 2018 David Shah * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -17,16 +18,15 @@ * */ -#ifndef ROUTE_H -#define ROUTE_H +#ifndef NO_PYTHON #include "nextpnr.h" +#include "pybindings.h" NEXTPNR_NAMESPACE_BEGIN -extern bool route_design(Context *ctx); -extern bool get_actual_route_delay(Context *ctx, WireId src_wire, WireId dst_wire, delay_t &delay); +void arch_wrap_python() {} NEXTPNR_NAMESPACE_END -#endif // ROUTE_H +#endif diff --git a/ecp5/arch_pybindings.h b/ecp5/arch_pybindings.h new file mode 100644 index 00000000..a5044f29 --- /dev/null +++ b/ecp5/arch_pybindings.h @@ -0,0 +1,75 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Clifford Wolf + * Copyright (C) 2018 David Shah + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ +#ifndef ARCH_PYBINDINGS_H +#define ARCH_PYBINDINGS_H +#ifndef NO_PYTHON + +#include "nextpnr.h" +#include "pybindings.h" + +NEXTPNR_NAMESPACE_BEGIN + +namespace PythonConversion { + +template <> struct string_converter +{ + BelId from_str(Context *ctx, std::string name) { return ctx->getBelByName(ctx->id(name)); } + + std::string to_str(Context *ctx, BelId id) + { + if (id == BelId()) + throw bad_wrap(); + return ctx->getBelName(id).str(ctx); + } +}; + +template <> struct string_converter +{ + BelType from_str(Context *ctx, std::string name) { return ctx->belTypeFromId(ctx->id(name)); } + + std::string to_str(Context *ctx, BelType typ) { return ctx->belTypeToId(typ).str(ctx); } +}; + +template <> struct string_converter +{ + WireId from_str(Context *ctx, std::string name) { return ctx->getWireByName(ctx->id(name)); } + + std::string to_str(Context *ctx, WireId id) { return ctx->getWireName(id).str(ctx); } +}; + +template <> struct string_converter +{ + PipId from_str(Context *ctx, std::string name) { return ctx->getPipByName(ctx->id(name)); } + + std::string to_str(Context *ctx, PipId id) { return ctx->getPipName(id).str(ctx); } +}; + +template <> struct string_converter +{ + PortPin from_str(Context *ctx, std::string name) { return ctx->portPinFromId(ctx->id(name)); } + + std::string to_str(Context *ctx, PortPin id) { return ctx->portPinToId(id).str(ctx); } +}; + +} // namespace PythonConversion + +NEXTPNR_NAMESPACE_END +#endif +#endif diff --git a/ecp5/archdefs.h b/ecp5/archdefs.h new file mode 100644 index 00000000..84a431fd --- /dev/null +++ b/ecp5/archdefs.h @@ -0,0 +1,202 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 David Shah + * Copyright (C) 2018 Clifford Wolf + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#ifndef NEXTPNR_H +#error Include "archdefs.h" via "nextpnr.h" only. +#endif + +#include + +NEXTPNR_NAMESPACE_BEGIN + +typedef int delay_t; + +struct DelayInfo +{ + delay_t delay = 0; + + delay_t raiseDelay() const { return delay; } + delay_t fallDelay() const { return delay; } + delay_t avgDelay() const { return delay; } + + DelayInfo operator+(const DelayInfo &other) const + { + DelayInfo ret; + ret.delay = this->delay + other.delay; + return ret; + } +}; + +// ----------------------------------------------------------------------- + +enum BelType : int32_t +{ + TYPE_NONE, + TYPE_TRELLIS_SLICE, + TYPE_TRELLIS_IO +}; + +enum PortPin : int32_t +{ + PIN_NONE, +#define X(t) PIN_##t, +#include "portpins.inc" +#undef X + PIN_MAXIDX +}; + +NPNR_PACKED_STRUCT(struct LocationPOD { int16_t x, y; }); + +struct Location +{ + int16_t x = -1, y = -1; + Location() : x(-1), y(-1){}; + Location(int16_t x, int16_t y) : x(x), y(y){}; + Location(const LocationPOD &pod) : x(pod.x), y(pod.y){}; + Location(const Location &loc) : x(loc.x), y(loc.y){}; + + bool operator==(const Location &other) const { return x == other.x && y == other.y; } + bool operator!=(const Location &other) const { return x != other.x || y != other.y; } +}; + +inline Location operator+(const Location &a, const Location &b) { return Location(a.x + b.x, a.y + b.y); } + +struct BelId +{ + Location location; + int32_t index = -1; + + bool operator==(const BelId &other) const { return index == other.index && location == other.location; } + bool operator!=(const BelId &other) const { return index != other.index || location != other.location; } +}; + +struct WireId +{ + Location location; + int32_t index = -1; + + bool operator==(const WireId &other) const { return index == other.index && location == other.location; } + bool operator!=(const WireId &other) const { return index != other.index || location != other.location; } +}; + +struct PipId +{ + Location location; + int32_t index = -1; + + bool operator==(const PipId &other) const { return index == other.index && location == other.location; } + bool operator!=(const PipId &other) const { return index != other.index || location != other.location; } +}; + +struct GroupId +{ + int32_t index = -1; + + bool operator==(const GroupId &other) const { return index == other.index; } + bool operator!=(const GroupId &other) const { return index != other.index; } +}; + +struct DecalId +{ + char type = 0; // Bel/Wire/Pip/Frame (b/w/p/f) + Location location; + uint32_t z = 0; + + bool operator==(const DecalId &other) const + { + return type == other.type && location == other.location && z == other.z; + } + bool operator!=(const DecalId &other) const + { + return type != other.type || location != other.location || z != other.z; + } +}; + +NEXTPNR_NAMESPACE_END + +namespace std { +template <> struct hash +{ + std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX Location &loc) const noexcept + { + std::size_t seed = std::hash()(loc.x); + seed ^= std::hash()(loc.y) + 0x9e3779b9 + (seed << 6) + (seed >> 2); + return seed; + } +}; + +template <> struct hash +{ + std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX BelId &bel) const noexcept + { + std::size_t seed = std::hash()(bel.location); + seed ^= std::hash()(bel.index) + 0x9e3779b9 + (seed << 6) + (seed >> 2); + return seed; + } +}; + +template <> struct hash +{ + std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX WireId &wire) const noexcept + { + std::size_t seed = std::hash()(wire.location); + seed ^= std::hash()(wire.index) + 0x9e3779b9 + (seed << 6) + (seed >> 2); + return seed; + } +}; + +template <> struct hash +{ + std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX PipId &pip) const noexcept + { + std::size_t seed = std::hash()(pip.location); + seed ^= std::hash()(pip.index) + 0x9e3779b9 + (seed << 6) + (seed >> 2); + return seed; + } +}; + +template <> struct hash +{ + std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX GroupId &group) const noexcept + { + return std::hash()(group.index); + } +}; + +template <> struct hash +{ + std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX DecalId &decal) const noexcept + { + std::size_t seed = 0; + boost::hash_combine(seed, hash()(decal.type)); + boost::hash_combine(seed, hash()(decal.location)); + boost::hash_combine(seed, hash()(decal.z)); + return seed; + } +}; + +template <> struct hash : hash +{ +}; + +template <> struct hash : hash +{ +}; +} // namespace std diff --git a/ecp5/bitstream.cc b/ecp5/bitstream.cc new file mode 100644 index 00000000..e70d6bb2 --- /dev/null +++ b/ecp5/bitstream.cc @@ -0,0 +1,257 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 David Shah + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "bitstream.h" + +// From Project Trellis +#include "BitDatabase.hpp" +#include "Bitstream.hpp" +#include "Chip.hpp" +#include "ChipConfig.hpp" +#include "Tile.hpp" +#include "TileConfig.hpp" + +#include +#include + +#include "log.h" +#include "util.h" + +#define fmt_str(x) (static_cast(std::ostringstream() << x).str()) + +NEXTPNR_NAMESPACE_BEGIN + +// Convert an absolute wire name to a relative Trellis one +static std::string get_trellis_wirename(Context *ctx, Location loc, WireId wire) +{ + std::string basename = ctx->locInfo(wire)->wire_data[wire.index].name.get(); + std::string prefix2 = basename.substr(0, 2); + if (prefix2 == "G_" || prefix2 == "L_" || prefix2 == "R_") + return basename; + if (loc == wire.location) + return basename; + std::string rel_prefix; + if (wire.location.y < loc.y) + rel_prefix += "N" + to_string(loc.y - wire.location.y); + if (wire.location.y > loc.y) + rel_prefix += "S" + to_string(wire.location.y - loc.y); + if (wire.location.x > loc.x) + rel_prefix += "E" + to_string(wire.location.x - loc.x); + if (wire.location.x < loc.x) + rel_prefix += "W" + to_string(loc.x - wire.location.x); + return rel_prefix + "_" + basename; +} + +static std::vector int_to_bitvector(int val, int size) +{ + std::vector bv; + for (int i = 0; i < size; i++) { + bv.push_back((val & (1 << i)) != 0); + } + return bv; +} + +// Get the PIO tile corresponding to a PIO bel +static std::string get_pio_tile(Context *ctx, Trellis::Chip &chip, BelId bel) +{ + static const std::set pioabcd_l = {"PICL1", "PICL1_DQS0", "PICL1_DQS3"}; + static const std::set pioabcd_r = {"PICR1", "PICR1_DQS0", "PICR1_DQS3"}; + static const std::set pioa_b = {"PICB0", "EFB0_PICB0", "EFB2_PICB0"}; + static const std::set piob_b = {"PICB1", "EFB1_PICB1", "EFB3_PICB1"}; + + std::string pio_name = ctx->locInfo(bel)->bel_data[bel.index].name.get(); + if (bel.location.y == 0) { + if (pio_name == "PIOA") { + return chip.get_tile_by_position_and_type(0, bel.location.x, "PIOT0"); + } else if (pio_name == "PIOB") { + return chip.get_tile_by_position_and_type(0, bel.location.x + 1, "PIOT1"); + } else { + NPNR_ASSERT_FALSE("bad PIO location"); + } + } else if (bel.location.y == ctx->chip_info->height - 1) { + if (pio_name == "PIOA") { + return chip.get_tile_by_position_and_type(bel.location.y, bel.location.x, pioa_b); + } else if (pio_name == "PIOB") { + return chip.get_tile_by_position_and_type(bel.location.y, bel.location.x + 1, piob_b); + } else { + NPNR_ASSERT_FALSE("bad PIO location"); + } + } else if (bel.location.x == 0) { + return chip.get_tile_by_position_and_type(bel.location.y + 1, bel.location.x, pioabcd_l); + } else if (bel.location.x == ctx->chip_info->width - 1) { + return chip.get_tile_by_position_and_type(bel.location.y + 1, bel.location.x, pioabcd_r); + } else { + NPNR_ASSERT_FALSE("bad PIO location"); + } +} + +// Get the PIC tile corresponding to a PIO bel +static std::string get_pic_tile(Context *ctx, Trellis::Chip &chip, BelId bel) +{ + static const std::set picab_l = {"PICL0", "PICL0_DQS2"}; + static const std::set piccd_l = {"PICL2", "PICL2_DQS1", "MIB_CIB_LR"}; + static const std::set picab_r = {"PICR0", "PICR0_DQS2"}; + static const std::set piccd_r = {"PICR2", "PICR2_DQS1", "MIB_CIB_LR_A"}; + + static const std::set pica_b = {"PICB0", "EFB0_PICB0", "EFB2_PICB0"}; + static const std::set picb_b = {"PICB1", "EFB1_PICB1", "EFB3_PICB1"}; + + std::string pio_name = ctx->locInfo(bel)->bel_data[bel.index].name.get(); + if (bel.location.y == 0) { + if (pio_name == "PIOA") { + return chip.get_tile_by_position_and_type(1, bel.location.x, "PICT0"); + } else if (pio_name == "PIOB") { + return chip.get_tile_by_position_and_type(1, bel.location.x + 1, "PICT1"); + } else { + NPNR_ASSERT_FALSE("bad PIO location"); + } + } else if (bel.location.y == ctx->chip_info->height - 1) { + if (pio_name == "PIOA") { + return chip.get_tile_by_position_and_type(bel.location.y, bel.location.x, pica_b); + } else if (pio_name == "PIOB") { + return chip.get_tile_by_position_and_type(bel.location.y, bel.location.x + 1, picb_b); + } else { + NPNR_ASSERT_FALSE("bad PIO location"); + } + } else if (bel.location.x == 0) { + if (pio_name == "PIOA" || pio_name == "PIOB") { + return chip.get_tile_by_position_and_type(bel.location.y, bel.location.x, picab_l); + } else if (pio_name == "PIOC" || pio_name == "PIOD") { + return chip.get_tile_by_position_and_type(bel.location.y + 2, bel.location.x, piccd_l); + } else { + NPNR_ASSERT_FALSE("bad PIO location"); + } + } else if (bel.location.x == ctx->chip_info->width - 1) { + if (pio_name == "PIOA" || pio_name == "PIOB") { + return chip.get_tile_by_position_and_type(bel.location.y, bel.location.x, picab_r); + } else if (pio_name == "PIOC" || pio_name == "PIOD") { + return chip.get_tile_by_position_and_type(bel.location.y + 2, bel.location.x, piccd_r); + } else { + NPNR_ASSERT_FALSE("bad PIO location"); + } + } else { + NPNR_ASSERT_FALSE("bad PIO location"); + } +} + +void write_bitstream(Context *ctx, std::string base_config_file, std::string text_config_file, + std::string bitstream_file) +{ + Trellis::Chip empty_chip(ctx->getChipName()); + Trellis::ChipConfig cc; + + std::set cib_tiles = {"CIB", "CIB_LR", "CIB_LR_S", "CIB_EFB0", "CIB_EFB1"}; + + if (!base_config_file.empty()) { + std::ifstream config_file(base_config_file); + if (!config_file) { + log_error("failed to open base config file '%s'\n", base_config_file.c_str()); + } + std::string str((std::istreambuf_iterator(config_file)), std::istreambuf_iterator()); + cc = Trellis::ChipConfig::from_string(str); + } else { + cc.chip_name = ctx->getChipName(); + // TODO: .bit metadata + } + + // Add all set, configurable pips to the config + for (auto pip : ctx->getPips()) { + if (ctx->getBoundPipNet(pip) != IdString()) { + if (ctx->getPipType(pip) == 0) { // ignore fixed pips + std::string tile = empty_chip.get_tile_by_position_and_type(pip.location.y, pip.location.x, + ctx->getPipTiletype(pip)); + std::string source = get_trellis_wirename(ctx, pip.location, ctx->getPipSrcWire(pip)); + std::string sink = get_trellis_wirename(ctx, pip.location, ctx->getPipDstWire(pip)); + cc.tiles[tile].add_arc(sink, source); + } + } + } + + // Set all bankref tiles to 3.3V (TODO) + for (const auto &tile : empty_chip.tiles) { + std::string type = tile.second->info.type; + if (type.find("BANKREF") != std::string::npos && type != "BANKREF8") { + cc.tiles[tile.first].add_enum("BANK.VCCIO", "3V3"); + } + } + + // Configure slices + for (auto &cell : ctx->cells) { + CellInfo *ci = cell.second.get(); + if (ci->bel == BelId()) { + log_warning("found unplaced cell '%s' during bitstream gen\n", ci->name.c_str(ctx)); + } + BelId bel = ci->bel; + if (ci->type == ctx->id("TRELLIS_SLICE")) { + std::string tname = empty_chip.get_tile_by_position_and_type(bel.location.y, bel.location.x, "PLC2"); + std::string slice = ctx->locInfo(bel)->bel_data[bel.index].name.get(); + int lut0_init = int_or_default(ci->params, ctx->id("LUT0_INITVAL")); + int lut1_init = int_or_default(ci->params, ctx->id("LUT1_INITVAL")); + cc.tiles[tname].add_word(slice + ".K0.INIT", int_to_bitvector(lut0_init, 16)); + cc.tiles[tname].add_word(slice + ".K1.INIT", int_to_bitvector(lut1_init, 16)); + cc.tiles[tname].add_enum(slice + ".MODE", str_or_default(ci->params, ctx->id("MODE"), "LOGIC")); + cc.tiles[tname].add_enum(slice + ".GSR", str_or_default(ci->params, ctx->id("GSR"), "ENABLED")); + cc.tiles[tname].add_enum(slice + ".REG0.SD", str_or_default(ci->params, ctx->id("REG0_SD"), "0")); + cc.tiles[tname].add_enum(slice + ".REG1.SD", str_or_default(ci->params, ctx->id("REG1_SD"), "0")); + cc.tiles[tname].add_enum(slice + ".REG0.REGSET", + str_or_default(ci->params, ctx->id("REG0_REGSET"), "RESET")); + cc.tiles[tname].add_enum(slice + ".REG1.REGSET", + str_or_default(ci->params, ctx->id("REG1_REGSET"), "RESET")); + cc.tiles[tname].add_enum(slice + ".CEMUX", str_or_default(ci->params, ctx->id("CEMUX"), "1")); + // TODO: CLKMUX, CEMUX, carry + } else if (ci->type == ctx->id("TRELLIS_IO")) { + std::string pio = ctx->locInfo(bel)->bel_data[bel.index].name.get(); + std::string iotype = str_or_default(ci->attrs, ctx->id("IO_TYPE"), "LVCMOS33"); + std::string dir = str_or_default(ci->params, ctx->id("DIR"), "INPUT"); + std::string pio_tile = get_pio_tile(ctx, empty_chip, bel); + std::string pic_tile = get_pic_tile(ctx, empty_chip, bel); + cc.tiles[pio_tile].add_enum(pio + ".BASE_TYPE", dir + "_" + iotype); + cc.tiles[pic_tile].add_enum(pio + ".BASE_TYPE", dir + "_" + iotype); + if (dir != "INPUT" && + (ci->ports.find(ctx->id("T")) == ci->ports.end() || ci->ports.at(ctx->id("T")).net == nullptr)) { + // Tie tristate low if unconnected for outputs or bidir + std::string jpt = fmt_str("X" << bel.location.x << "/Y" << bel.location.y << "/JPADDT" << pio.back()); + WireId jpt_wire = ctx->getWireByName(ctx->id(jpt)); + PipId jpt_pip = *ctx->getPipsUphill(jpt_wire).begin(); + WireId cib_wire = ctx->getPipSrcWire(jpt_pip); + std::string cib_tile = + empty_chip.get_tile_by_position_and_type(cib_wire.location.y, cib_wire.location.x, cib_tiles); + std::string cib_wirename = ctx->locInfo(cib_wire)->wire_data[cib_wire.index].name.get(); + cc.tiles[cib_tile].add_enum("CIB." + cib_wirename + "MUX", "0"); + } + if (dir == "INPUT") { + cc.tiles[pio_tile].add_enum(pio + ".HYSTERESIS", "ON"); + } + } else { + NPNR_ASSERT_FALSE("unsupported cell type"); + } + } + + // Configure chip + Trellis::Chip cfg_chip = cc.to_chip(); + if (!bitstream_file.empty()) { + Trellis::Bitstream::serialise_chip(cfg_chip).write_bit_py(bitstream_file); + } + if (!text_config_file.empty()) { + std::ofstream out_config(text_config_file); + out_config << cc.to_string(); + } +} + +NEXTPNR_NAMESPACE_END diff --git a/ecp5/bitstream.h b/ecp5/bitstream.h new file mode 100644 index 00000000..62617470 --- /dev/null +++ b/ecp5/bitstream.h @@ -0,0 +1,32 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 David Shah + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#ifndef BITSTREAM_H +#define BITSTREAM_H + +#include "nextpnr.h" + +NEXTPNR_NAMESPACE_BEGIN + +void write_bitstream(Context *ctx, std::string base_config_file = "", std::string text_config_file = "", + std::string bitstream_file = ""); + +NEXTPNR_NAMESPACE_END + +#endif // BITSTREAM_H diff --git a/ecp5/family.cmake b/ecp5/family.cmake new file mode 100644 index 00000000..f4d0bf87 --- /dev/null +++ b/ecp5/family.cmake @@ -0,0 +1,60 @@ + +set(devices 45k) + +if (NOT DEFINED TRELLIS_ROOT) + message(FATAL_ERROR "you must define TRELLIS_ROOT using -DTRELLIS_ROOT=/path/to/prjtrellis for ECP5 support") +endif() + + +file( GLOB found_pytrellis ${TRELLIS_ROOT}/libtrellis/pytrellis.*) + +if ("${found_pytrellis}" STREQUAL "") + message(FATAL_ERROR "failed to find pytrellis library in ${TRELLIS_ROOT}/libtrellis/") +endif() + +set(DB_PY ${CMAKE_CURRENT_SOURCE_DIR}/ecp5/trellis_import.py) + +file(MAKE_DIRECTORY ecp5/chipdbs/) +add_library(ecp5_chipdb OBJECT ecp5/chipdbs/) +target_compile_definitions(ecp5_chipdb PRIVATE NEXTPNR_NAMESPACE=nextpnr_${family}) +target_include_directories(ecp5_chipdb PRIVATE ${family}/) +set(ENV_CMD ${CMAKE_COMMAND} -E env "PYTHONPATH=${TRELLIS_ROOT}/libtrellis:${TRELLIS_ROOT}/util/common") +if (MSVC) + target_sources(ecp5_chipdb PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/ecp5/resource/embed.cc) + set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/ecp5/resources/chipdb.rc PROPERTIES LANGUAGE RC) + foreach (dev ${devices}) + set(DEV_CC_DB ${CMAKE_CURRENT_SOURCE_DIR}/ecp5/chipdbs/chipdb-${dev}.bin) + set(DEV_PORTS_INC ${CMAKE_CURRENT_SOURCE_DIR}/ecp5/portpins.inc) + add_custom_command(OUTPUT ${DEV_CC_DB} + COMMAND ${ENV_CMD} python3 ${DB_PY} -b -p ${DEV_PORTS_INC} ${dev} ${DEV_CC_DB} + DEPENDS ${DB_PY} + ) + target_sources(ecp5_chipdb PRIVATE ${DEV_CC_DB}) + set_source_files_properties(${DEV_CC_DB} PROPERTIES HEADER_FILE_ONLY TRUE) + foreach (target ${family_targets}) + target_sources(${target} PRIVATE $ ${CMAKE_CURRENT_SOURCE_DIR}/ecp5/resource/chipdb.rc) + endforeach (target) + endforeach (dev) +else() + target_compile_options(ecp5_chipdb PRIVATE -g0 -O0 -w) + foreach (dev ${devices}) + set(DEV_CC_DB ${CMAKE_CURRENT_SOURCE_DIR}/ecp5/chipdbs/chipdb-${dev}.cc) + set(DEV_PORTS_INC ${CMAKE_CURRENT_SOURCE_DIR}/ecp5/portpins.inc) + add_custom_command(OUTPUT ${DEV_CC_DB} + COMMAND ${ENV_CMD} python3 ${DB_PY} -c -p ${DEV_PORTS_INC} ${dev} ${DEV_CC_DB} + DEPENDS ${DB_PY} + ) + target_sources(ecp5_chipdb PRIVATE ${DEV_CC_DB}) + foreach (target ${family_targets}) + target_sources(${target} PRIVATE $) + endforeach (target) + endforeach (dev) +endif() + +find_library(TRELLIS_LIB trellis PATHS ${TRELLIS_ROOT}/libtrellis) + +foreach (target ${family_targets}) + target_compile_definitions(${target} PRIVATE TRELLIS_ROOT="${TRELLIS_ROOT}") + target_include_directories(${target} PRIVATE ${TRELLIS_ROOT}/libtrellis/include) + target_link_libraries(${target} PRIVATE ${TRELLIS_LIB}) +endforeach (target) diff --git a/ecp5/main.cc b/ecp5/main.cc new file mode 100644 index 00000000..4cb2f10d --- /dev/null +++ b/ecp5/main.cc @@ -0,0 +1,199 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Clifford Wolf + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#ifdef MAIN_EXECUTABLE + +#ifndef NO_GUI +#include +#include "application.h" +#include "mainwindow.h" +#endif +#ifndef NO_PYTHON +#include "pybindings.h" +#endif +#include +#include +#include +#include + +#include "Chip.hpp" +#include "Database.hpp" +#include "Tile.hpp" + +#include "log.h" +#include "nextpnr.h" +#include "version.h" + +#include "bitstream.h" +#include "design_utils.h" +#include "jsonparse.h" +#include "timing.h" + +USING_NEXTPNR_NAMESPACE + +int main(int argc, char *argv[]) +{ + try { + + namespace po = boost::program_options; + int rc = 0; + + log_files.push_back(stdout); + + po::options_description options("Allowed options"); + options.add_options()("help,h", "show help"); + options.add_options()("verbose,v", "verbose output"); + options.add_options()("force,f", "keep running after errors"); +#ifndef NO_GUI + options.add_options()("gui", "start gui"); +#endif + options.add_options()("json", po::value(), "JSON design file to ingest"); + options.add_options()("seed", po::value(), "seed value for random number generator"); + + options.add_options()("basecfg", po::value(), "base chip configuration in Trellis text format"); + options.add_options()("bit", po::value(), "bitstream file to write"); + options.add_options()("textcfg", po::value(), "textual configuration in Trellis format to write"); + + po::positional_options_description pos; +#ifndef NO_PYTHON + options.add_options()("run", po::value>(), "python file to execute"); + pos.add("run", -1); +#endif + options.add_options()("version,V", "show version"); + + po::variables_map vm; + try { + po::parsed_options parsed = po::command_line_parser(argc, argv).options(options).positional(pos).run(); + + po::store(parsed, vm); + + po::notify(vm); + } + + catch (std::exception &e) { + std::cout << e.what() << "\n"; + return 1; + } + + if (vm.count("help") || argc == 1) { + std::cout << boost::filesystem::basename(argv[0]) + << " -- Next Generation Place and Route (git " + "sha1 " GIT_COMMIT_HASH_STR ")\n"; + std::cout << "\n"; + std::cout << options << "\n"; + return argc != 1; + } + + if (vm.count("version")) { + std::cout << boost::filesystem::basename(argv[0]) + << " -- Next Generation Place and Route (git " + "sha1 " GIT_COMMIT_HASH_STR ")\n"; + return 1; + } + + Trellis::load_database(TRELLIS_ROOT "/database"); + + ArchArgs args; + args.type = ArchArgs::LFE5U_45F; + args.package = "CABGA381"; + args.speed = 6; + std::unique_ptr ctx = std::unique_ptr(new Context(args)); + + if (vm.count("verbose")) { + ctx->verbose = true; + } + + if (vm.count("force")) { + ctx->force = true; + } + + if (vm.count("seed")) { + ctx->rngseed(vm["seed"].as()); + } + + ctx->timing_driven = true; + if (vm.count("no-tmdriv")) + ctx->timing_driven = false; + +#ifndef NO_GUI + if (vm.count("gui")) { + Application a(argc, argv); + MainWindow w(std::move(ctx)); + w.show(); + + return a.exec(); + } +#endif + if (vm.count("json")) { + std::string filename = vm["json"].as(); + std::ifstream f(filename); + if (!parse_json_file(f, filename, ctx.get())) + log_error("Loading design failed.\n"); + + if (!ctx->pack() && !ctx->force) + log_error("Packing design failed.\n"); + if (vm.count("freq")) + ctx->target_freq = vm["freq"].as() * 1e6; + assign_budget(ctx.get()); + ctx->check(); + print_utilisation(ctx.get()); + + if (!ctx->place() && !ctx->force) + log_error("Placing design failed.\n"); + ctx->check(); + if (!ctx->route() && !ctx->force) + log_error("Routing design failed.\n"); + + std::string basecfg; + if (vm.count("basecfg")) + basecfg = vm["basecfg"].as(); + + std::string bitstream; + if (vm.count("bit")) + bitstream = vm["bit"].as(); + + std::string textcfg; + if (vm.count("textcfg")) + textcfg = vm["textcfg"].as(); + write_bitstream(ctx.get(), basecfg, textcfg, bitstream); + } + +#ifndef NO_PYTHON + if (vm.count("run")) { + init_python(argv[0], true); + python_export_global("ctx", ctx); + + std::vector files = vm["run"].as>(); + for (auto filename : files) + execute_python_file(filename.c_str()); + + deinit_python(); + } +#endif + return rc; + } catch (log_execution_error_exception) { +#if defined(_MSC_VER) + _exit(EXIT_FAILURE); +#else + _Exit(EXIT_FAILURE); +#endif + } +} + +#endif diff --git a/ecp5/pack.cc b/ecp5/pack.cc new file mode 100644 index 00000000..e3ddc07d --- /dev/null +++ b/ecp5/pack.cc @@ -0,0 +1,99 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 David Shah + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include +#include +#include +#include "design_utils.h" +#include "log.h" +#include "util.h" + +NEXTPNR_NAMESPACE_BEGIN + +static bool is_nextpnr_iob(Context *ctx, CellInfo *cell) +{ + return cell->type == ctx->id("$nextpnr_ibuf") || cell->type == ctx->id("$nextpnr_obuf") || + cell->type == ctx->id("$nextpnr_iobuf"); +} + +static bool is_trellis_io(const Context *ctx, const CellInfo *cell) { return cell->type == ctx->id("TRELLIS_IO"); } + +// Simple "packer" to remove nextpnr IOBUFs, this assumes IOBUFs are manually instantiated +void pack_io(Context *ctx) +{ + std::unordered_set packed_cells; + std::vector> new_cells; + log_info("Packing IOs..\n"); + + for (auto cell : sorted(ctx->cells)) { + CellInfo *ci = cell.second; + if (is_nextpnr_iob(ctx, ci)) { + CellInfo *trio = nullptr; + if (ci->type == ctx->id("$nextpnr_ibuf") || ci->type == ctx->id("$nextpnr_iobuf")) { + trio = net_only_drives(ctx, ci->ports.at(ctx->id("O")).net, is_trellis_io, ctx->id("B"), true, ci); + + } else if (ci->type == ctx->id("$nextpnr_obuf")) { + trio = net_only_drives(ctx, ci->ports.at(ctx->id("I")).net, is_trellis_io, ctx->id("B"), true, ci); + } + if (trio != nullptr) { + // Trivial case, TRELLIS_IO used. Just destroy the net and the + // iobuf + log_info("%s feeds TRELLIS_IO %s, removing %s %s.\n", ci->name.c_str(ctx), trio->name.c_str(ctx), + ci->type.c_str(ctx), ci->name.c_str(ctx)); + NetInfo *net = trio->ports.at(ctx->id("B")).net; + if (net != nullptr) { + ctx->nets.erase(net->name); + trio->ports.at(ctx->id("B")).net = nullptr; + } + if (ci->type == ctx->id("$nextpnr_iobuf")) { + NetInfo *net2 = ci->ports.at(ctx->id("I")).net; + if (net2 != nullptr) { + ctx->nets.erase(net2->name); + } + } + } else { + log_error("TRELLIS_IO required on all top level IOs...\n"); + } + packed_cells.insert(ci->name); + std::copy(ci->attrs.begin(), ci->attrs.end(), std::inserter(trio->attrs, trio->attrs.begin())); + } + } + for (auto pcell : packed_cells) { + ctx->cells.erase(pcell); + } + for (auto &ncell : new_cells) { + ctx->cells[ncell->name] = std::move(ncell); + } +} + +// Main pack function +bool Arch::pack() +{ + Context *ctx = getCtx(); + try { + log_break(); + pack_io(ctx); + log_info("Checksum: 0x%08x\n", ctx->checksum()); + return true; + } catch (log_execution_error_exception) { + return false; + } +} + +NEXTPNR_NAMESPACE_END diff --git a/ecp5/place_legaliser.cc b/ecp5/place_legaliser.cc new file mode 100644 index 00000000..0d23f15b --- /dev/null +++ b/ecp5/place_legaliser.cc @@ -0,0 +1,26 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 David Shah + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "place_legaliser.h" + +NEXTPNR_NAMESPACE_BEGIN + +bool legalise_design(Context *ctx) { return true; } + +NEXTPNR_NAMESPACE_END diff --git a/ecp5/place_legaliser.h b/ecp5/place_legaliser.h new file mode 100644 index 00000000..5f4df6aa --- /dev/null +++ b/ecp5/place_legaliser.h @@ -0,0 +1,31 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 David Shah + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#ifndef PLACE_LEGALISER_H +#define PLACE_LEGALISER_H + +#include "nextpnr.h" + +NEXTPNR_NAMESPACE_BEGIN + +bool legalise_design(Context *ctx); + +NEXTPNR_NAMESPACE_END + +#endif diff --git a/ecp5/portpins.inc b/ecp5/portpins.inc new file mode 100644 index 00000000..ab0ebe9f --- /dev/null +++ b/ecp5/portpins.inc @@ -0,0 +1,46 @@ +X(A0) +X(B0) +X(C0) +X(D0) +X(A1) +X(B1) +X(C1) +X(D1) +X(M0) +X(M1) +X(FCI) +X(FXA) +X(FXB) +X(CLK) +X(LSR) +X(CE) +X(DI0) +X(DI1) +X(WD0) +X(WD1) +X(WAD0) +X(WAD1) +X(WAD2) +X(WAD3) +X(WRE) +X(WCK) +X(F0) +X(Q0) +X(F1) +X(Q1) +X(FCO) +X(OFX0) +X(OFX1) +X(WDO0) +X(WDO1) +X(WDO2) +X(WDO3) +X(WADO0) +X(WADO1) +X(WADO2) +X(WADO3) + +X(I) +X(O) +X(T) +X(B) diff --git a/ecp5/synth/.gitignore b/ecp5/synth/.gitignore new file mode 100644 index 00000000..5b3bf578 --- /dev/null +++ b/ecp5/synth/.gitignore @@ -0,0 +1 @@ +*.bit diff --git a/ecp5/synth/blinky.v b/ecp5/synth/blinky.v new file mode 100644 index 00000000..ac7c6ea3 --- /dev/null +++ b/ecp5/synth/blinky.v @@ -0,0 +1,77 @@ +module top(input clk_pin, input btn_pin, output [3:0] led_pin, output gpio0_pin); + + wire clk; + wire [7:0] led; + wire btn; + wire gpio0; + + (* BEL="X0/Y35/PIOA" *) (* IO_TYPE="LVCMOS33" *) + TRELLIS_IO #(.DIR("INPUT")) clk_buf (.B(clk_pin), .O(clk)); + + (* BEL="X4/Y71/PIOA" *) (* IO_TYPE="LVCMOS33" *) + TRELLIS_IO #(.DIR("INPUT")) btn_buf (.B(btn_pin), .O(btn)); + + (* BEL="X0/Y23/PIOC" *) (* IO_TYPE="LVCMOS33" *) + TRELLIS_IO #(.DIR("OUTPUT")) led_buf_0 (.B(led_pin[0]), .I(led[0])); + (* BEL="X0/Y23/PIOD" *) (* IO_TYPE="LVCMOS33" *) + TRELLIS_IO #(.DIR("OUTPUT")) led_buf_1 (.B(led_pin[1]), .I(led[1])); + (* BEL="X0/Y26/PIOA" *) (* IO_TYPE="LVCMOS33" *) + TRELLIS_IO #(.DIR("OUTPUT")) led_buf_2 (.B(led_pin[2]), .I(led[2])); + (* BEL="X0/Y26/PIOC" *) (* IO_TYPE="LVCMOS33" *) + TRELLIS_IO #(.DIR("OUTPUT")) led_buf_3 (.B(led_pin[3]), .I(led[3])); + + (* BEL="X0/Y26/PIOB" *) (* IO_TYPE="LVCMOS33" *) + TRELLIS_IO #(.DIR("OUTPUT")) led_buf_4 (.B(led_pin[4]), .I(led[4])); + (* BEL="X0/Y32/PIOD" *) (* IO_TYPE="LVCMOS33" *) + TRELLIS_IO #(.DIR("OUTPUT")) led_buf_5 (.B(led_pin[5]), .I(led[5])); + (* BEL="X0/Y26/PIOD" *) (* IO_TYPE="LVCMOS33" *) + TRELLIS_IO #(.DIR("OUTPUT")) led_buf_6 (.B(led_pin[6]), .I(led[6])); + (* BEL="X0/Y29/PIOD" *) (* IO_TYPE="LVCMOS33" *) + TRELLIS_IO #(.DIR("OUTPUT")) led_buf_7 (.B(led_pin[7]), .I(led[7])); + + + (* BEL="X0/Y62/PIOD" *) (* IO_TYPE="LVCMOS33" *) + TRELLIS_IO #(.DIR("OUTPUT")) gpio0_buf (.B(gpio0_pin), .I(gpio0)); + + localparam ctr_width = 24; + localparam ctr_max = 2**ctr_width - 1; + reg [ctr_width-1:0] ctr = 0; + reg [9:0] pwm_ctr = 0; + reg dir = 0; + + always@(posedge clk) begin + ctr <= btn ? ctr : (dir ? ctr - 1'b1 : ctr + 1'b1); + if (ctr[ctr_width-1 : ctr_width-3] == 0 && dir == 1) + dir <= 1'b0; + else if (ctr[ctr_width-1 : ctr_width-3] == 7 && dir == 0) + dir <= 1'b1; + pwm_ctr <= pwm_ctr + 1'b1; + end + + reg [9:0] brightness [0:7]; + localparam bright_max = 2**10 - 1; + reg [7:0] led_reg; + + genvar i; + generate + for (i = 0; i < 8; i=i+1) begin + always @ (posedge clk) begin + if (ctr[ctr_width-1 : ctr_width-3] == i) + brightness[i] <= bright_max; + else if (ctr[ctr_width-1 : ctr_width-3] == (i - 1)) + brightness[i] <= ctr[ctr_width-4:ctr_width-13]; + else if (ctr[ctr_width-1 : ctr_width-3] == (i + 1)) + brightness[i] <= bright_max - ctr[ctr_width-4:ctr_width-13]; + else + brightness[i] <= 0; + led_reg[i] <= pwm_ctr < brightness[i]; + end + end + endgenerate + + assign led = led_reg; + + // Tie GPIO0, keep board from rebooting + TRELLIS_SLICE #(.MODE("LOGIC"), .LUT0_INITVAL(16'hFFFF)) vcc (.F0(gpio0)); + +endmodule diff --git a/ecp5/synth/blinky.ys b/ecp5/synth/blinky.ys new file mode 100644 index 00000000..c0b74636 --- /dev/null +++ b/ecp5/synth/blinky.ys @@ -0,0 +1,9 @@ +read_verilog blinky.v +read_verilog -lib cells.v +synth -top top +abc -lut 4 +techmap -map simple_map.v +splitnets +opt_clean +stat +write_json blinky.json diff --git a/ecp5/synth/cells.v b/ecp5/synth/cells.v new file mode 100644 index 00000000..353b8ada --- /dev/null +++ b/ecp5/synth/cells.v @@ -0,0 +1,49 @@ +(* blackbox *) +module TRELLIS_SLICE( + input A0, B0, C0, D0, + input A1, B1, C1, D1, + input M0, M1, + input FCI, FXA, FXB, + + input CLK, LSR, CE, + input DI0, DI1, + + input WD0, WD1, + input WAD0, WAD1, WAD2, WAD3, + input WRE, WCK, + + output F0, Q0, + output F1, Q1, + output FCO, OFX0, OFX1, + + output WDO0, WDO1, WDO2, WDO3, + output WADO0, WADO1, WADO2, WADO3 +); + +parameter MODE = "LOGIC"; +parameter GSR = "ENABLED"; +parameter SRMODE = "LSR_OVER_CE"; +parameter CEMUX = "1"; +parameter CLKMUX = "CLK"; +parameter LSRMUX = "LSR"; +parameter LUT0_INITVAL = 16'h0000; +parameter LUT1_INITVAL = 16'h0000; +parameter REG0_SD = "0"; +parameter REG1_SD = "0"; +parameter REG0_REGSET = "RESET"; +parameter REG1_REGSET = "RESET"; +parameter CCU2_INJECT1_0 = "NO"; +parameter CCU2_INJECT1_1 = "NO"; + +endmodule + +(* blackbox *) (* keep *) +module TRELLIS_IO( + inout B, + input I, + input T, + output O, +); +parameter DIR = "INPUT"; + +endmodule diff --git a/ecp5/synth/simple_map.v b/ecp5/synth/simple_map.v new file mode 100644 index 00000000..550fa92c --- /dev/null +++ b/ecp5/synth/simple_map.v @@ -0,0 +1,68 @@ +module \$_DFF_P_ (input D, C, output Q); + TRELLIS_SLICE #( + .MODE("LOGIC"), + .CLKMUX("CLK"), + .CEMUX("1"), + .REG0_SD("0"), + .REG0_REGSET("RESET"), + .SRMODE("LSR_OVER_CE"), + .GSR("DISABLED") + ) _TECHMAP_REPLACE_ ( + .CLK(C), + .M0(D), + .Q0(Q) + ); +endmodule + +module \$lut (A, Y); + parameter WIDTH = 0; + parameter LUT = 0; + + input [WIDTH-1:0] A; + output Y; + + generate + if (WIDTH == 1) begin + TRELLIS_SLICE #( + .MODE("LOGIC"), + .LUT0_INITVAL({8{LUT[1:0]}}) + ) _TECHMAP_REPLACE_ ( + .A0(A[0]), + .F0(Y) + ); + end + if (WIDTH == 2) begin + TRELLIS_SLICE #( + .MODE("LOGIC"), + .LUT0_INITVAL({4{LUT[3:0]}}) + ) _TECHMAP_REPLACE_ ( + .A0(A[0]), + .B0(A[1]), + .F0(Y) + ); + end + if (WIDTH == 3) begin + TRELLIS_SLICE #( + .MODE("LOGIC"), + .LUT0_INITVAL({2{LUT[7:0]}}) + ) _TECHMAP_REPLACE_ ( + .A0(A[0]), + .B0(A[1]), + .C0(A[2]), + .F0(Y) + ); + end + if (WIDTH == 4) begin + TRELLIS_SLICE #( + .MODE("LOGIC"), + .LUT0_INITVAL(LUT) + ) _TECHMAP_REPLACE_ ( + .A0(A[0]), + .B0(A[1]), + .C0(A[2]), + .D0(A[3]), + .F0(Y) + ); + end + endgenerate +endmodule diff --git a/ecp5/synth/ulx3s.v b/ecp5/synth/ulx3s.v new file mode 100644 index 00000000..08f6e65b --- /dev/null +++ b/ecp5/synth/ulx3s.v @@ -0,0 +1,18 @@ +module top(input a_pin, output led_pin, output led2_pin, output gpio0_pin); + + wire a; + wire led, led2; + wire gpio0; + (* BEL="X4/Y71/PIOA" *) (* IO_TYPE="LVCMOS33" *) + TRELLIS_IO #(.DIR("INPUT")) a_buf (.B(a_pin), .O(a)); + (* BEL="X0/Y23/PIOC" *) (* IO_TYPE="LVCMOS33" *) + TRELLIS_IO #(.DIR("OUTPUT")) led_buf (.B(led_pin), .I(led)); + (* BEL="X0/Y26/PIOA" *) (* IO_TYPE="LVCMOS33" *) + TRELLIS_IO #(.DIR("OUTPUT")) led2_buf (.B(led2_pin), .I(led2)); + (* BEL="X0/Y62/PIOD" *) (* IO_TYPE="LVCMOS33" *) + TRELLIS_IO #(.DIR("OUTPUT")) gpio0_buf (.B(gpio0_pin), .I(gpio0)); + assign led = a; + assign led2 = !a; + + TRELLIS_SLICE #(.MODE("LOGIC"), .LUT0_INITVAL(16'hFFFF)) vcc (.F0(gpio0)); +endmodule diff --git a/ecp5/synth/ulx3s.ys b/ecp5/synth/ulx3s.ys new file mode 100644 index 00000000..d741c985 --- /dev/null +++ b/ecp5/synth/ulx3s.ys @@ -0,0 +1,9 @@ +read_verilog ulx3s.v +read_verilog -lib cells.v +synth -top top +abc -lut 4 +techmap -map simple_map.v +splitnets +opt_clean +stat +write_json ulx3s.json diff --git a/ecp5/synth/ulx3s_empty.config b/ecp5/synth/ulx3s_empty.config new file mode 100644 index 00000000..815e7f0d --- /dev/null +++ b/ecp5/synth/ulx3s_empty.config @@ -0,0 +1,439 @@ +.device LFE5U-45F + +.tile CIB_R10C3:PVT_COUNT2 +unknown: F2B0 +unknown: F3B0 +unknown: F5B0 +unknown: F11B0 +unknown: F13B0 + +.tile CIB_R5C1:CIB_PLL1 +enum: CIB.JA3MUX 0 +enum: CIB.JB3MUX 0 + + +.tile CIB_R5C89:CIB_PLL1 +enum: CIB.JA3MUX 0 +enum: CIB.JB3MUX 0 + + +.tile CIB_R70C3:CIB_PLL3 +enum: CIB.JA3MUX 0 +enum: CIB.JB3MUX 0 + + +.tile CIB_R70C42:VCIB_DCU0 +enum: CIB.JA1MUX 0 +enum: CIB.JA3MUX 0 +enum: CIB.JA5MUX 0 +enum: CIB.JA7MUX 0 +enum: CIB.JB1MUX 0 +enum: CIB.JB3MUX 0 +enum: CIB.JB5MUX 0 +enum: CIB.JB7MUX 0 +enum: CIB.JC0MUX 0 +enum: CIB.JC2MUX 0 +enum: CIB.JC4MUX 0 +enum: CIB.JC6MUX 0 +enum: CIB.JD0MUX 0 +enum: CIB.JD2MUX 0 +enum: CIB.JD4MUX 0 +enum: CIB.JD6MUX 0 + + +.tile CIB_R70C43:VCIB_DCUA +enum: CIB.JA1MUX 0 +enum: CIB.JA3MUX 0 +enum: CIB.JA5MUX 0 +enum: CIB.JA7MUX 0 +enum: CIB.JB1MUX 0 +enum: CIB.JB3MUX 0 +enum: CIB.JB5MUX 0 +enum: CIB.JB7MUX 0 +enum: CIB.JC0MUX 0 +enum: CIB.JC2MUX 0 +enum: CIB.JC4MUX 0 +enum: CIB.JC6MUX 0 +enum: CIB.JD0MUX 0 +enum: CIB.JD2MUX 0 +enum: CIB.JD4MUX 0 +enum: CIB.JD6MUX 0 + + +.tile CIB_R70C44:VCIB_DCUB +enum: CIB.JA1MUX 0 +enum: CIB.JA3MUX 0 +enum: CIB.JA5MUX 0 +enum: CIB.JA7MUX 0 +enum: CIB.JB1MUX 0 +enum: CIB.JB3MUX 0 +enum: CIB.JB5MUX 0 +enum: CIB.JB7MUX 0 +enum: CIB.JC0MUX 0 +enum: CIB.JC2MUX 0 +enum: CIB.JC4MUX 0 +enum: CIB.JC6MUX 0 +enum: CIB.JD0MUX 0 +enum: CIB.JD2MUX 0 +enum: CIB.JD4MUX 0 +enum: CIB.JD6MUX 0 + + +.tile CIB_R70C45:VCIB_DCUC +enum: CIB.JA1MUX 0 +enum: CIB.JA3MUX 0 +enum: CIB.JA5MUX 0 +enum: CIB.JA7MUX 0 +enum: CIB.JB1MUX 0 +enum: CIB.JB3MUX 0 +enum: CIB.JB5MUX 0 +enum: CIB.JB7MUX 0 +enum: CIB.JC0MUX 0 +enum: CIB.JC2MUX 0 +enum: CIB.JC4MUX 0 +enum: CIB.JC6MUX 0 +enum: CIB.JD0MUX 0 +enum: CIB.JD2MUX 0 +enum: CIB.JD4MUX 0 +enum: CIB.JD6MUX 0 + + +.tile CIB_R70C46:VCIB_DCUD +enum: CIB.JA1MUX 0 +enum: CIB.JA5MUX 0 +enum: CIB.JA7MUX 0 +enum: CIB.JB1MUX 0 +enum: CIB.JB3MUX 0 +enum: CIB.JB5MUX 0 +enum: CIB.JB7MUX 0 +enum: CIB.JC0MUX 0 +enum: CIB.JC2MUX 0 +enum: CIB.JC4MUX 0 +enum: CIB.JC6MUX 0 +enum: CIB.JD0MUX 0 +enum: CIB.JD2MUX 0 +enum: CIB.JD4MUX 0 +enum: CIB.JD6MUX 0 + + +.tile CIB_R70C47:VCIB_DCUF +enum: CIB.JA1MUX 0 +enum: CIB.JA3MUX 0 +enum: CIB.JA5MUX 0 +enum: CIB.JA7MUX 0 +enum: CIB.JB1MUX 0 +enum: CIB.JB3MUX 0 +enum: CIB.JB5MUX 0 +enum: CIB.JB7MUX 0 +enum: CIB.JC0MUX 0 +enum: CIB.JC2MUX 0 +enum: CIB.JC4MUX 0 +enum: CIB.JC6MUX 0 +enum: CIB.JD0MUX 0 +enum: CIB.JD2MUX 0 +enum: CIB.JD4MUX 0 +enum: CIB.JD6MUX 0 + + +.tile CIB_R70C48:VCIB_DCU3 +enum: CIB.JA5MUX 0 +enum: CIB.JA7MUX 0 +enum: CIB.JB1MUX 0 +enum: CIB.JB3MUX 0 +enum: CIB.JB5MUX 0 +enum: CIB.JB7MUX 0 +enum: CIB.JC0MUX 0 +enum: CIB.JC4MUX 0 +enum: CIB.JC6MUX 0 +enum: CIB.JD0MUX 0 +enum: CIB.JD2MUX 0 +enum: CIB.JD4MUX 0 +enum: CIB.JD6MUX 0 + + +.tile CIB_R70C49:VCIB_DCU2 +enum: CIB.JB1MUX 0 +enum: CIB.JB3MUX 0 +enum: CIB.JB5MUX 0 +enum: CIB.JB7MUX 0 +enum: CIB.JD0MUX 0 +enum: CIB.JD2MUX 0 +enum: CIB.JD4MUX 0 +enum: CIB.JD6MUX 0 + + +.tile CIB_R70C50:VCIB_DCUG +enum: CIB.JB1MUX 0 +enum: CIB.JB3MUX 0 +enum: CIB.JB5MUX 0 +enum: CIB.JB7MUX 0 +enum: CIB.JD0MUX 0 +enum: CIB.JD2MUX 0 +enum: CIB.JD4MUX 0 +enum: CIB.JD6MUX 0 + + +.tile CIB_R70C51:VCIB_DCUH +enum: CIB.JB1MUX 0 +enum: CIB.JB3MUX 0 +enum: CIB.JB5MUX 0 +enum: CIB.JB7MUX 0 +enum: CIB.JD0MUX 0 +enum: CIB.JD2MUX 0 +enum: CIB.JD4MUX 0 +enum: CIB.JD6MUX 0 + + +.tile CIB_R70C52:VCIB_DCUI +enum: CIB.JB1MUX 0 +enum: CIB.JB3MUX 0 +enum: CIB.JB5MUX 0 +enum: CIB.JB7MUX 0 +enum: CIB.JD0MUX 0 +enum: CIB.JD2MUX 0 +enum: CIB.JD4MUX 0 +enum: CIB.JD6MUX 0 + + +.tile CIB_R70C53:VCIB_DCU1 +enum: CIB.JB1MUX 0 +enum: CIB.JB3MUX 0 +enum: CIB.JB5MUX 0 +enum: CIB.JD0MUX 0 +enum: CIB.JD2MUX 0 + + +.tile CIB_R70C69:VCIB_DCU0 +enum: CIB.JA1MUX 0 +enum: CIB.JA3MUX 0 +enum: CIB.JA5MUX 0 +enum: CIB.JA7MUX 0 +enum: CIB.JB1MUX 0 +enum: CIB.JB3MUX 0 +enum: CIB.JB5MUX 0 +enum: CIB.JB7MUX 0 +enum: CIB.JC0MUX 0 +enum: CIB.JC2MUX 0 +enum: CIB.JC4MUX 0 +enum: CIB.JC6MUX 0 +enum: CIB.JD0MUX 0 +enum: CIB.JD2MUX 0 +enum: CIB.JD4MUX 0 +enum: CIB.JD6MUX 0 + + +.tile CIB_R70C6:CIB_EFB0 +enum: CIB.JB3MUX 0 +enum: CIB.JC6MUX 0 +enum: CIB.JD6MUX 0 + + +.tile CIB_R70C70:VCIB_DCUA +enum: CIB.JA1MUX 0 +enum: CIB.JA3MUX 0 +enum: CIB.JA5MUX 0 +enum: CIB.JA7MUX 0 +enum: CIB.JB1MUX 0 +enum: CIB.JB3MUX 0 +enum: CIB.JB5MUX 0 +enum: CIB.JB7MUX 0 +enum: CIB.JC0MUX 0 +enum: CIB.JC2MUX 0 +enum: CIB.JC4MUX 0 +enum: CIB.JC6MUX 0 +enum: CIB.JD0MUX 0 +enum: CIB.JD2MUX 0 +enum: CIB.JD4MUX 0 +enum: CIB.JD6MUX 0 + + +.tile CIB_R70C71:VCIB_DCUB +enum: CIB.JA1MUX 0 +enum: CIB.JA3MUX 0 +enum: CIB.JA5MUX 0 +enum: CIB.JA7MUX 0 +enum: CIB.JB1MUX 0 +enum: CIB.JB3MUX 0 +enum: CIB.JB5MUX 0 +enum: CIB.JB7MUX 0 +enum: CIB.JC0MUX 0 +enum: CIB.JC2MUX 0 +enum: CIB.JC4MUX 0 +enum: CIB.JC6MUX 0 +enum: CIB.JD0MUX 0 +enum: CIB.JD2MUX 0 +enum: CIB.JD4MUX 0 +enum: CIB.JD6MUX 0 + + +.tile CIB_R70C72:VCIB_DCUC +enum: CIB.JA1MUX 0 +enum: CIB.JA3MUX 0 +enum: CIB.JA5MUX 0 +enum: CIB.JA7MUX 0 +enum: CIB.JB1MUX 0 +enum: CIB.JB3MUX 0 +enum: CIB.JB5MUX 0 +enum: CIB.JB7MUX 0 +enum: CIB.JC0MUX 0 +enum: CIB.JC2MUX 0 +enum: CIB.JC4MUX 0 +enum: CIB.JC6MUX 0 +enum: CIB.JD0MUX 0 +enum: CIB.JD2MUX 0 +enum: CIB.JD4MUX 0 +enum: CIB.JD6MUX 0 + + +.tile CIB_R70C73:VCIB_DCUD +enum: CIB.JA1MUX 0 +enum: CIB.JA5MUX 0 +enum: CIB.JA7MUX 0 +enum: CIB.JB1MUX 0 +enum: CIB.JB3MUX 0 +enum: CIB.JB5MUX 0 +enum: CIB.JB7MUX 0 +enum: CIB.JC0MUX 0 +enum: CIB.JC2MUX 0 +enum: CIB.JC4MUX 0 +enum: CIB.JC6MUX 0 +enum: CIB.JD0MUX 0 +enum: CIB.JD2MUX 0 +enum: CIB.JD4MUX 0 +enum: CIB.JD6MUX 0 + + +.tile CIB_R70C74:VCIB_DCUF +enum: CIB.JA1MUX 0 +enum: CIB.JA3MUX 0 +enum: CIB.JA5MUX 0 +enum: CIB.JA7MUX 0 +enum: CIB.JB1MUX 0 +enum: CIB.JB3MUX 0 +enum: CIB.JB5MUX 0 +enum: CIB.JB7MUX 0 +enum: CIB.JC0MUX 0 +enum: CIB.JC2MUX 0 +enum: CIB.JC4MUX 0 +enum: CIB.JC6MUX 0 +enum: CIB.JD0MUX 0 +enum: CIB.JD2MUX 0 +enum: CIB.JD4MUX 0 +enum: CIB.JD6MUX 0 + + +.tile CIB_R70C75:VCIB_DCU3 +enum: CIB.JA5MUX 0 +enum: CIB.JA7MUX 0 +enum: CIB.JB1MUX 0 +enum: CIB.JB3MUX 0 +enum: CIB.JB5MUX 0 +enum: CIB.JB7MUX 0 +enum: CIB.JC0MUX 0 +enum: CIB.JC4MUX 0 +enum: CIB.JC6MUX 0 +enum: CIB.JD0MUX 0 +enum: CIB.JD2MUX 0 +enum: CIB.JD4MUX 0 +enum: CIB.JD6MUX 0 + + +.tile CIB_R70C76:VCIB_DCU2 +enum: CIB.JB1MUX 0 +enum: CIB.JB3MUX 0 +enum: CIB.JB5MUX 0 +enum: CIB.JB7MUX 0 +enum: CIB.JD0MUX 0 +enum: CIB.JD2MUX 0 +enum: CIB.JD4MUX 0 +enum: CIB.JD6MUX 0 + + +.tile CIB_R70C77:VCIB_DCUG +enum: CIB.JB1MUX 0 +enum: CIB.JB3MUX 0 +enum: CIB.JB5MUX 0 +enum: CIB.JB7MUX 0 +enum: CIB.JD0MUX 0 +enum: CIB.JD2MUX 0 +enum: CIB.JD4MUX 0 +enum: CIB.JD6MUX 0 + + +.tile CIB_R70C78:VCIB_DCUH +enum: CIB.JB1MUX 0 +enum: CIB.JB3MUX 0 +enum: CIB.JB5MUX 0 +enum: CIB.JB7MUX 0 +enum: CIB.JD0MUX 0 +enum: CIB.JD2MUX 0 +enum: CIB.JD4MUX 0 +enum: CIB.JD6MUX 0 + + +.tile CIB_R70C79:VCIB_DCUI +enum: CIB.JB1MUX 0 +enum: CIB.JB3MUX 0 +enum: CIB.JB5MUX 0 +enum: CIB.JB7MUX 0 +enum: CIB.JD0MUX 0 +enum: CIB.JD2MUX 0 +enum: CIB.JD4MUX 0 +enum: CIB.JD6MUX 0 + + +.tile CIB_R70C7:CIB_EFB1 +enum: CIB.JA3MUX 0 +enum: CIB.JA4MUX 0 +enum: CIB.JA5MUX 0 +enum: CIB.JA6MUX 0 +enum: CIB.JB3MUX 0 +enum: CIB.JB4MUX 0 +enum: CIB.JB5MUX 0 +enum: CIB.JB6MUX 0 +enum: CIB.JC3MUX 0 +enum: CIB.JC4MUX 0 +enum: CIB.JC5MUX 0 +enum: CIB.JD3MUX 0 +enum: CIB.JD4MUX 0 +enum: CIB.JD5MUX 0 + + +.tile CIB_R70C80:VCIB_DCU1 +enum: CIB.JB1MUX 0 +enum: CIB.JB3MUX 0 +enum: CIB.JB5MUX 0 +enum: CIB.JD0MUX 0 +enum: CIB.JD2MUX 0 + + +.tile CIB_R70C87:CIB_PLL3 +enum: CIB.JA3MUX 0 +enum: CIB.JB3MUX 0 + + +.tile MIB_R10C40:CMUX_UL_0 +arc: G_DCS0CLK0 G_VPFN0000 + + +.tile MIB_R10C41:CMUX_UR_0 +arc: G_DCS0CLK1 G_VPFN0000 + + +.tile MIB_R58C40:CMUX_LL_0 +arc: G_DCS1CLK0 G_VPFN0000 + + +.tile MIB_R58C41:CMUX_LR_0 +arc: G_DCS1CLK1 G_VPFN0000 + + +.tile MIB_R71C4:EFB0_PICB0 +unknown: F54B1 +unknown: F56B1 +unknown: F82B1 +unknown: F94B1 + +.tile MIB_R71C3:BANKREF8 +unknown: F18B0 + diff --git a/ecp5/synth/wire.v b/ecp5/synth/wire.v new file mode 100644 index 00000000..2af68ed2 --- /dev/null +++ b/ecp5/synth/wire.v @@ -0,0 +1,11 @@ +module top(input a_pin, output [3:0] led_pin); + + wire a; + wire [3:0] led; + + TRELLIS_IO #(.DIR("INPUT")) a_buf (.B(a_pin), .O(a)); + TRELLIS_IO #(.DIR("OUTPUT")) led_buf [3:0] (.B(led_pin), .I(led)); + + //assign led[0] = !a; + always @(posedge a) led[0] <= !led[0]; +endmodule diff --git a/ecp5/synth/wire.ys b/ecp5/synth/wire.ys new file mode 100644 index 00000000..f916588b --- /dev/null +++ b/ecp5/synth/wire.ys @@ -0,0 +1,9 @@ +read_verilog wire.v +read_verilog -lib cells.v +synth -top top +abc -lut 4 +techmap -map simple_map.v +splitnets +opt_clean +stat +write_json wire.json diff --git a/ecp5/trellis_import.py b/ecp5/trellis_import.py new file mode 100755 index 00000000..5eca057a --- /dev/null +++ b/ecp5/trellis_import.py @@ -0,0 +1,701 @@ +#!/usr/bin/env python3 +import pytrellis +import database +import argparse + +location_types = dict() +type_at_location = dict() +tiletype_names = dict() + +parser = argparse.ArgumentParser(description="import ECP5 routing and bels from Project Trellis") +group = parser.add_mutually_exclusive_group() +group.add_argument("-b", "--binary", action="store_true") +group.add_argument("-c", "--c_file", action="store_true") +parser.add_argument("device", type=str, help="target device") +parser.add_argument("outfile", type=argparse.FileType('w'), help="output filename") +parser.add_argument("-p", "--portspins", type=str, help="path to portpins.inc") +args = parser.parse_args() + + +def is_global(loc): + return loc.x == -2 and loc.y == -2 + + +# Get the index for a tiletype +def get_tiletype_index(name): + if name in tiletype_names: + return tiletype_names[name] + idx = len(tiletype_names) + tiletype_names[name] = idx + return idx + + +portpins = dict() + +loc_wire_indices = dict() +loc_wires = dict() + +loc_bels = dict() +wire_bel_pins_uphill = dict() +wire_bel_pins_downhill = dict() + + +# Import all wire names at all locations +def import_location_wires(rg, x, y): + loc_wire_indices[x, y] = dict() + loc_wires[x, y] = list() + wire_bel_pins_uphill[x, y] = list() + wire_bel_pins_downhill[x, y] = list() + rtile = rg.tiles[pytrellis.Location(x, y)] + for wire in rtile.wires: + name = rg.to_str(wire.key()) + idx = len(loc_wires[x, y]) + loc_wires[x, y].append(name) + loc_wire_indices[x, y][name] = idx + wire_bel_pins_uphill[x, y].append([]) + wire_bel_pins_downhill[x, y].append([]) + + +# Take a RoutingId from Trellis and make into a (relx, rely, name) tuple +def resolve_wirename(rg, rid, cur_x, cur_y): + if is_global(rid.loc): + return (cur_x, cur_y, rg.to_str(rid.id)) + else: + x = rid.loc.x + y = rid.loc.y + widx = loc_wire_indices[x, y][rg.to_str(rid.id)] + return (x - cur_x, y - cur_y, widx) + + +loc_arc_indices = dict() # Map RoutingId index to nextpnr index +loc_arcs = dict() + + +# Import all arc indices at a location +def index_location_arcs(rg, x, y): + loc_arc_indices[x, y] = dict() + loc_arcs[x, y] = list() + rtile = rg.tiles[pytrellis.Location(x, y)] + for arc in rtile.arcs: + idx = len(loc_arcs[x, y]) + trid = arc.key() + loc_arcs[x, y].append(trid) + loc_arc_indices[x, y][trid] = idx + + +def add_bel_input(bel_x, bel_y, bel_idx, bel_pin, wire_x, wire_y, wire_name): + bel_pin = portpins[bel_pin] + loc_bels[bel_x, bel_y][bel_idx][2].append((bel_pin, (wire_x, wire_y, loc_wire_indices[wire_x, wire_y][wire_name]))) + wire_bel_pins_downhill[wire_x, wire_y][loc_wire_indices[wire_x, wire_y][wire_name]].append(( + (bel_x, bel_y, bel_idx), bel_pin)) + + +def add_bel_output(bel_x, bel_y, bel_idx, bel_pin, wire_x, wire_y, wire_name): + bel_pin = portpins[bel_pin] + loc_bels[bel_x, bel_y][bel_idx][2].append((bel_pin, (wire_x, wire_y, loc_wire_indices[wire_x, wire_y][wire_name]))) + wire_bel_pins_uphill[wire_x, wire_y][loc_wire_indices[wire_x, wire_y][wire_name]].append(( + (bel_x, bel_y, bel_idx), bel_pin)) + + +def add_slice(x, y, z): + idx = len(loc_bels[x, y]) + l = ("A", "B", "C", "D")[z] + name = "SLICE" + l + loc_bels[x, y].append((name, "SLICE", [])) + lc0 = z * 2 + lc1 = z * 2 + 1 + add_bel_input(x, y, idx, "A0", x, y, "A{}_SLICE".format(lc0)) + add_bel_input(x, y, idx, "B0", x, y, "B{}_SLICE".format(lc0)) + add_bel_input(x, y, idx, "C0", x, y, "C{}_SLICE".format(lc0)) + add_bel_input(x, y, idx, "D0", x, y, "D{}_SLICE".format(lc0)) + add_bel_input(x, y, idx, "M0", x, y, "M{}_SLICE".format(lc0)) + + add_bel_input(x, y, idx, "A1", x, y, "A{}_SLICE".format(lc1)) + add_bel_input(x, y, idx, "B1", x, y, "B{}_SLICE".format(lc1)) + add_bel_input(x, y, idx, "C1", x, y, "C{}_SLICE".format(lc1)) + add_bel_input(x, y, idx, "D1", x, y, "D{}_SLICE".format(lc1)) + add_bel_input(x, y, idx, "M1", x, y, "M{}_SLICE".format(lc1)) + + add_bel_input(x, y, idx, "FCI", x, y, "FCI{}_SLICE".format(l if z > 0 else "")) + add_bel_input(x, y, idx, "FXA", x, y, "FXA{}_SLICE".format(l)) + add_bel_input(x, y, idx, "FXB", x, y, "FXB{}_SLICE".format(l)) + + add_bel_input(x, y, idx, "CLK", x, y, "CLK{}_SLICE".format(z)) + add_bel_input(x, y, idx, "LSR", x, y, "LSR{}_SLICE".format(z)) + add_bel_input(x, y, idx, "CE", x, y, "CE{}_SLICE".format(z)) + + add_bel_input(x, y, idx, "DI0", x, y, "DI{}_SLICE".format(lc0)) + add_bel_input(x, y, idx, "DI1", x, y, "DI{}_SLICE".format(lc1)) + + if z == 0 or z == 1: + add_bel_input(x, y, idx, "WD0", x, y, "WD0{}_SLICE".format(l)) + add_bel_input(x, y, idx, "WD1", x, y, "WD1{}_SLICE".format(l)) + + add_bel_input(x, y, idx, "WAD0", x, y, "WAD0{}_SLICE".format(l)) + add_bel_input(x, y, idx, "WAD1", x, y, "WAD1{}_SLICE".format(l)) + add_bel_input(x, y, idx, "WAD2", x, y, "WAD2{}_SLICE".format(l)) + add_bel_input(x, y, idx, "WAD3", x, y, "WAD3{}_SLICE".format(l)) + + add_bel_input(x, y, idx, "WRE", x, y, "WRE{}_SLICE".format(z)) + add_bel_input(x, y, idx, "WCK", x, y, "WCK{}_SLICE".format(z)) + + add_bel_output(x, y, idx, "F0", x, y, "F{}_SLICE".format(lc0)) + add_bel_output(x, y, idx, "Q0", x, y, "Q{}_SLICE".format(lc0)) + + add_bel_output(x, y, idx, "F1", x, y, "F{}_SLICE".format(lc1)) + add_bel_output(x, y, idx, "Q1", x, y, "Q{}_SLICE".format(lc1)) + + add_bel_output(x, y, idx, "OFX0", x, y, "F5{}_SLICE".format(l)) + add_bel_output(x, y, idx, "OFX1", x, y, "FX{}_SLICE".format(l)) + + add_bel_output(x, y, idx, "FCO", x, y, "FCO{}_SLICE".format(l if z < 3 else "")) + + if z == 2: + add_bel_output(x, y, idx, "WDO0", x, y, "WDO0C_SLICE") + add_bel_output(x, y, idx, "WDO1", x, y, "WDO1C_SLICE") + add_bel_output(x, y, idx, "WDO2", x, y, "WDO2C_SLICE") + add_bel_output(x, y, idx, "WDO3", x, y, "WDO3C_SLICE") + + add_bel_output(x, y, idx, "WADO0", x, y, "WADO0C_SLICE") + add_bel_output(x, y, idx, "WADO1", x, y, "WADO1C_SLICE") + add_bel_output(x, y, idx, "WADO2", x, y, "WADO2C_SLICE") + add_bel_output(x, y, idx, "WADO3", x, y, "WADO3C_SLICE") + + +def add_pio(x, y, z): + idx = len(loc_bels[x, y]) + l = ("A", "B", "C", "D")[z] + name = "PIO" + l + loc_bels[x, y].append((name, "PIO", [])) + add_bel_input(x, y, idx, "I", x, y, "PADDO{}_PIO".format(l)) + add_bel_input(x, y, idx, "T", x, y, "PADDT{}_PIO".format(l)) + add_bel_output(x, y, idx, "O", x, y, "JPADDI{}_PIO".format(l)) + + +def add_bels(chip, x, y): + loc_bels[x, y] = [] + tiles = chip.get_tiles_by_position(y, x) + num_slices = 0 + num_pios = 0 + for tile in tiles: + tt = tile.info.type + if tt == "PLC2": + num_slices = 4 + elif "PICL0" in tt or "PICR0" in tt: + num_pios = 4 + elif "PIOT0" in tt or ("PICB0" in tt and "SPICB" not in tt): + num_pios = 2 + for i in range(num_slices): + add_slice(x, y, i) + for i in range(num_pios): + add_pio(x, y, i) + + +# Import a location, deduplicating if appropriate +def import_location(rg, x, y): + rtile = rg.tiles[pytrellis.Location(x, y)] + arcs = [] # (src, dst, configurable, tiletype) + wires = [] # (name, uphill, downhill, belpin_uphill, belpins_downhill) + bels = [] # (name, [(pin, wire)]) + for name in loc_wires[x, y]: + w = rtile.wires[rg.ident(name)] + arcs_uphill = [] + arcs_downhill = [] + belpins_uphill = [] + belpins_downhill = [] + for uh in w.uphill: + arcidx = loc_arc_indices[uh.loc.x, uh.loc.y][uh.id] + arcs_uphill.append((uh.loc.x - x, uh.loc.y - y, arcidx)) + for dh in w.downhill: + arcidx = loc_arc_indices[dh.loc.x, dh.loc.y][dh.id] + arcs_downhill.append((dh.loc.x - x, dh.loc.y - y, arcidx)) + for bp in wire_bel_pins_uphill[x, y][loc_wire_indices[x, y][name]]: + bel, pin = bp + bel_x, bel_y, bel_idx = bel + belpins_uphill.append(((bel_x - x, bel_y - y, bel_idx), pin)) + for bp in wire_bel_pins_downhill[x, y][loc_wire_indices[x, y][name]]: + bel, pin = bp + bel_x, bel_y, bel_idx = bel + belpins_downhill.append(((bel_x - x, bel_y - y, bel_idx), pin)) + assert len(belpins_uphill) <= 1 + wires.append((name, tuple(arcs_downhill), tuple(arcs_uphill), tuple(belpins_uphill), tuple(belpins_downhill))) + + for bel in loc_bels[x, y]: + name, beltype, pins = bel + xformed_pins = tuple((p[0], (p[1][0] - x, p[1][1] - y, p[1][2])) for p in pins) + bels.append((name, beltype, xformed_pins)) + + for arcidx in loc_arcs[x, y]: + a = rtile.arcs[arcidx] + source_wire = resolve_wirename(rg, a.source, x, y) + dest_wire = resolve_wirename(rg, a.sink, x, y) + arcs.append((source_wire, dest_wire, a.configurable, get_tiletype_index(rg.to_str(a.tiletype)))) + + tile_data = (tuple(wires), tuple(arcs), tuple(bels)) + if tile_data in location_types: + type_at_location[x, y] = location_types[tile_data] + else: + idx = len(location_types) + location_types[tile_data] = idx + type_at_location[x, y] = idx + + +class BinaryBlobAssembler: + def __init__(self, cname, endianness, nodebug=False): + assert endianness in ["le", "be"] + self.cname = cname + self.endianness = endianness + self.finalized = False + self.data = bytearray() + self.comments = dict() + self.labels = dict() + self.exports = set() + self.labels_byaddr = dict() + self.ltypes_byaddr = dict() + self.strings = dict() + self.refs = dict() + self.nodebug = nodebug + + def l(self, name, ltype=None, export=False): + assert not self.finalized + assert name not in self.labels + assert len(self.data) not in self.labels_byaddr + self.labels[name] = len(self.data) + if ltype is not None: + self.ltypes_byaddr[len(self.data)] = ltype + self.labels_byaddr[len(self.data)] = name + if export: + assert ltype is not None + self.exports.add(len(self.data)) + + def r(self, name, comment): + assert not self.finalized + assert len(self.data) % 4 == 0 + assert len(self.data) not in self.refs + if self.nodebug: + comment = None + if name is not None: + self.refs[len(self.data)] = (name, comment) + self.data.append(0) + self.data.append(0) + self.data.append(0) + self.data.append(0) + if (name is None) and (comment is not None): + self.comments[len(self.data)] = comment + " (null reference)" + + def s(self, s, comment): + assert not self.finalized + if self.nodebug: + comment = None + if s not in self.strings: + index = len(self.strings) + self.strings[s] = index + else: + index = self.strings[s] + if comment is not None: + self.r("str%d" % index, '%s: "%s"' % (comment, s)) + else: + self.r("str%d" % index, None) + + def u8(self, v, comment): + assert not self.finalized + if self.nodebug: + comment = None + self.data.append(v) + if comment is not None: + self.comments[len(self.data)] = comment + + def u16(self, v, comment): + assert not self.finalized + assert len(self.data) % 2 == 0 + if self.nodebug: + comment = None + if self.endianness == "le": + self.data.append(v & 255) + self.data.append((v >> 8) & 255) + elif self.endianness == "be": + self.data.append((v >> 8) & 255) + self.data.append(v & 255) + else: + assert 0 + if comment is not None: + self.comments[len(self.data)] = comment + + def s16(self, v, comment): + assert not self.finalized + assert len(self.data) % 2 == 0 + if self.nodebug: + comment = None + c2val = (((-v) ^ 0xffff) + 1) if v < 0 else v + if self.endianness == "le": + self.data.append(c2val & 255) + self.data.append((c2val >> 8) & 255) + elif self.endianness == "be": + self.data.append((c2val >> 8) & 255) + self.data.append(c2val & 255) + else: + assert 0 + if comment is not None: + self.comments[len(self.data)] = comment + + def u32(self, v, comment): + assert not self.finalized + assert len(self.data) % 4 == 0 + if self.nodebug: + comment = None + if self.endianness == "le": + self.data.append(v & 255) + self.data.append((v >> 8) & 255) + self.data.append((v >> 16) & 255) + self.data.append((v >> 24) & 255) + elif self.endianness == "be": + self.data.append((v >> 24) & 255) + self.data.append((v >> 16) & 255) + self.data.append((v >> 8) & 255) + self.data.append(v & 255) + else: + assert 0 + if comment is not None: + self.comments[len(self.data)] = comment + + def finalize(self): + assert not self.finalized + for s, index in sorted(self.strings.items()): + self.l("str%d" % index, "char") + for c in s: + self.data.append(ord(c)) + self.data.append(0) + self.finalized = True + cursor = 0 + while cursor < len(self.data): + if cursor in self.refs: + v = self.labels[self.refs[cursor][0]] - cursor + if self.endianness == "le": + self.data[cursor + 0] = (v & 255) + self.data[cursor + 1] = ((v >> 8) & 255) + self.data[cursor + 2] = ((v >> 16) & 255) + self.data[cursor + 3] = ((v >> 24) & 255) + elif self.endianness == "be": + self.data[cursor + 0] = ((v >> 24) & 255) + self.data[cursor + 1] = ((v >> 16) & 255) + self.data[cursor + 2] = ((v >> 8) & 255) + self.data[cursor + 3] = (v & 255) + else: + assert 0 + cursor += 4 + else: + cursor += 1 + + def write_verbose_c(self, f, ctype="const unsigned char"): + assert self.finalized + print("%s %s[%d] = {" % (ctype, self.cname, len(self.data)), file=f) + cursor = 0 + bytecnt = 0 + while cursor < len(self.data): + if cursor in self.comments: + if bytecnt == 0: + print(" ", end="", file=f) + print(" // %s" % self.comments[cursor], file=f) + bytecnt = 0 + if cursor in self.labels_byaddr: + if bytecnt != 0: + print(file=f) + if cursor in self.exports: + print("#define %s ((%s*)(%s+%d))" % ( + self.labels_byaddr[cursor], self.ltypes_byaddr[cursor], self.cname, cursor), file=f) + else: + print(" // [%d] %s" % (cursor, self.labels_byaddr[cursor]), file=f) + bytecnt = 0 + if cursor in self.refs: + if bytecnt != 0: + print(file=f) + print(" ", end="", file=f) + print(" %-4s" % ("%d," % self.data[cursor + 0]), end="", file=f) + print(" %-4s" % ("%d," % self.data[cursor + 1]), end="", file=f) + print(" %-4s" % ("%d," % self.data[cursor + 2]), end="", file=f) + print(" %-4s" % ("%d," % self.data[cursor + 3]), end="", file=f) + print(" // [%d] %s (reference to %s)" % (cursor, self.refs[cursor][1], self.refs[cursor][0]), file=f) + bytecnt = 0 + cursor += 4 + else: + if bytecnt == 0: + print(" ", end="", file=f) + print(" %-4s" % ("%d," % self.data[cursor]), end=("" if bytecnt < 15 else "\n"), file=f) + bytecnt = (bytecnt + 1) & 15 + cursor += 1 + if bytecnt != 0: + print(file=f) + print("};", file=f) + + def write_compact_c(self, f, ctype="const unsigned char"): + assert self.finalized + print("%s %s[%d] = {" % (ctype, self.cname, len(self.data)), file=f) + column = 0 + for v in self.data: + if column == 0: + print(" ", end="", file=f) + column += 2 + s = "%d," % v + print(s, end="", file=f) + column += len(s) + if column > 75: + print(file=f) + column = 0 + if column != 0: + print(file=f) + for cursor in self.exports: + print("#define %s ((%s*)(%s+%d))" % ( + self.labels_byaddr[cursor], self.ltypes_byaddr[cursor], self.cname, cursor), file=f) + print("};", file=f) + + def write_uint64_c(self, f, ctype="const uint64_t"): + assert self.finalized + print("%s %s[%d] = {" % (ctype, self.cname, (len(self.data) + 7) // 8), file=f) + column = 0 + for i in range((len(self.data) + 7) // 8): + v0 = self.data[8 * i + 0] if 8 * i + 0 < len(self.data) else 0 + v1 = self.data[8 * i + 1] if 8 * i + 1 < len(self.data) else 0 + v2 = self.data[8 * i + 2] if 8 * i + 2 < len(self.data) else 0 + v3 = self.data[8 * i + 3] if 8 * i + 3 < len(self.data) else 0 + v4 = self.data[8 * i + 4] if 8 * i + 4 < len(self.data) else 0 + v5 = self.data[8 * i + 5] if 8 * i + 5 < len(self.data) else 0 + v6 = self.data[8 * i + 6] if 8 * i + 6 < len(self.data) else 0 + v7 = self.data[8 * i + 7] if 8 * i + 7 < len(self.data) else 0 + if self.endianness == "le": + v = v0 << 0 + v |= v1 << 8 + v |= v2 << 16 + v |= v3 << 24 + v |= v4 << 32 + v |= v5 << 40 + v |= v6 << 48 + v |= v7 << 56 + elif self.endianness == "be": + v = v7 << 0 + v |= v6 << 8 + v |= v5 << 16 + v |= v4 << 24 + v |= v3 << 32 + v |= v2 << 40 + v |= v1 << 48 + v |= v0 << 56 + else: + assert 0 + if column == 3: + print(" 0x%016x," % v, file=f) + column = 0 + else: + if column == 0: + print(" ", end="", file=f) + print(" 0x%016x," % v, end="", file=f) + column += 1 + if column != 0: + print("", file=f) + print("};", file=f) + + def write_string_c(self, f, ctype="const char"): + assert self.finalized + assert self.data[len(self.data) - 1] == 0 + print("%s %s[%d] =" % (ctype, self.cname, len(self.data)), file=f) + print(" \"", end="", file=f) + column = 0 + for i in range(len(self.data) - 1): + if (self.data[i] < 32) or (self.data[i] > 126): + print("\\%03o" % self.data[i], end="", file=f) + column += 4 + elif self.data[i] == ord('"') or self.data[i] == ord('\\'): + print("\\" + chr(self.data[i]), end="", file=f) + column += 2 + else: + print(chr(self.data[i]), end="", file=f) + column += 1 + if column > 70 and (i != len(self.data) - 2): + print("\"\n \"", end="", file=f) + column = 0 + print("\";", file=f) + + def write_binary(self, f): + assert self.finalized + assert self.data[len(self.data) - 1] == 0 + f.buffer.write(self.data) + + +bel_types = { + "NONE": 0, + "SLICE": 1, + "PIO": 2 +} + +def write_database(dev_name, endianness): + def write_loc(x, y, sym_name): + bba.s16(x, "%s.x" % sym_name) + bba.s16(y, "%s.y" % sym_name) + + bba = BinaryBlobAssembler("chipdb_blob_%s" % dev_name, endianness) + bba.r("chip_info", "chip_info") + + for loctype, idx in sorted(location_types.items(), key=lambda x: x[1]): + wires, arcs, bels = loctype + if len(arcs) > 0: + bba.l("loc%d_pips" % idx, "PipInfoPOD") + for arc in arcs: + src_wire, dst_wire, configurable, tile_type = arc + write_loc(src_wire[0], src_wire[1], "src") + write_loc(dst_wire[0], dst_wire[1], "dst") + bba.u32(src_wire[2], "src_idx") + bba.u32(dst_wire[2], "dst_idx") + bba.u32(1, "delay") # TODO:delay + bba.u16(tile_type, "tile_type") + bba.u8(1 if not configurable else 0, "pip_type") + bba.u8(0, "padding") + if len(wires) > 0: + for wire_idx in range(len(wires)): + wire = wires[wire_idx] + name, downpips, uppips, downbels, upbels = wire + if len(downpips) > 0: + bba.l("loc%d_wire%d_downpips" % (idx, wire_idx), "PipLocatorPOD") + for dp in downpips: + write_loc(dp[0], dp[1], "rel_loc") + bba.u32(dp[2], "index") + if len(uppips) > 0: + bba.l("loc%d_wire%d_uppips" % (idx, wire_idx), "PipLocatorPOD") + for up in uppips: + write_loc(up[0], up[1], "rel_loc") + bba.u32(up[2], "index") + if len(downbels) > 0: + bba.l("loc%d_wire%d_downbels" % (idx, wire_idx), "BelPortPOD") + for db in downbels: + bel, pin = db + write_loc(bel[0], bel[1], "rel_bel_loc") + bba.u32(bel[2], "bel_index") + bba.u32(pin, "port") + bba.l("loc%d_wires" % idx, "WireInfoPOD") + for wire_idx in range(len(wires)): + wire = wires[wire_idx] + name, downpips, uppips, downbels, upbels = wire + bba.s(name, "name") + bba.u32(len(uppips), "num_uphill") + bba.u32(len(downpips), "num_downhill") + bba.r("loc%d_wire%d_uppips" % (idx, wire_idx) if len(uppips) > 0 else None, "pips_uphill") + bba.r("loc%d_wire%d_downpips" % (idx, wire_idx) if len(downpips) > 0 else None, "pips_downhill") + bba.u32(len(downbels), "num_bels_downhill") + if len(upbels) == 1: + bel, pin = upbels[0] + write_loc(bel[0], bel[1], "uphill_bel_loc") + bba.u32(bel[2], "uphill_bel_idx") + bba.u32(pin, "uphill_bel_pin") + else: + write_loc(-1, -1, "bel_uphill.rel_bel_loc") + bba.u32(0xFFFFFFFF, "bel_uphill.bel_index") + bba.u32(0, "bel_uphill.port") + bba.r("loc%d_wire%d_downbels" % (idx, wire_idx) if len(downbels) > 0 else None, "bels_downhill") + if len(bels) > 0: + for bel_idx in range(len(bels)): + bel, beltype, pins = bels[bel_idx] + bba.l("loc%d_bel%d_wires" % (idx, bel_idx), "BelPortPOD") + for pin in pins: + port, wire = pin + write_loc(wire[0], wire[1], "rel_wire_loc") + bba.u32(wire[2], "wire_index") + bba.u32(port, "port") + bba.l("loc%d_bels" % idx, "BelInfoPOD") + for bel_idx in range(len(bels)): + bel, beltype, pins = bels[bel_idx] + bba.s(bel, "name") + bba.u32(bel_types[beltype], "type") + bba.u32(len(pins), "num_bel_wires") + bba.r("loc%d_bel%d_wires" % (idx, bel_idx), "bel_wires") + + bba.l("locations", "LocationTypePOD") + for loctype, idx in sorted(location_types.items(), key=lambda x: x[1]): + wires, arcs, bels = loctype + bba.u32(len(bels), "num_bels") + bba.u32(len(wires), "num_wires") + bba.u32(len(arcs), "num_pips") + bba.r("loc%d_bels" % idx if len(bels) > 0 else None, "bel_data") + bba.r("loc%d_wires" % idx if len(wires) > 0 else None, "wire_data") + bba.r("loc%d_pips" % idx if len(arcs) > 0 else None, "pips_data") + + bba.l("location_types", "int32_t") + for y in range(0, max_row+1): + for x in range(0, max_col+1): + bba.u32(type_at_location[x, y], "loctype") + + bba.l("tiletype_names", "RelPtr") + for tt in tiletype_names: + bba.s(tt, "name") + + bba.l("chip_info") + bba.u32(max_col + 1, "width") + bba.u32(max_row + 1, "height") + bba.u32((max_col + 1) * (max_row + 1), "num_tiles") + bba.u32(len(location_types), "num_location_types") + bba.r("locations", "locations") + bba.r("location_types", "location_type") + bba.r("tiletype_names", "tiletype_names") + bba.finalize() + return bba + +dev_names = {"25k": "LFE5U-25F", "45k": "LFE5U-45F", "85k": "LFE5U-85F"} + +def main(): + global max_row, max_col + pytrellis.load_database(database.get_db_root()) + args = parser.parse_args() + + # Read port pin file + with open(args.portspins) as f: + for line in f: + line = line.replace("(", " ") + line = line.replace(")", " ") + line = line.split() + if len(line) == 0: + continue + assert len(line) == 2 + assert line[0] == "X" + idx = len(portpins) + 1 + portpins[line[1]] = idx + + print("Initialising chip...") + chip = pytrellis.Chip(dev_names[args.device]) + print("Building routing graph...") + rg = chip.get_routing_graph() + max_row = chip.get_max_row() + max_col = chip.get_max_col() + print("Indexing wires...") + for y in range(0, max_row + 1): + for x in range(0, max_col + 1): + import_location_wires(rg, x, y) + print("Indexing arcs...") + for y in range(0, max_row + 1): + for x in range(0, max_col + 1): + index_location_arcs(rg, x, y) + print("Adding bels...") + for y in range(0, max_row + 1): + for x in range(0, max_col + 1): + add_bels(chip, x, y) + print("Importing tiles...") + for y in range(0, max_row + 1): + for x in range(0, max_col + 1): + print(" At R{}C{}".format(y, x)) + import_location(rg, x, y) + print("{} unique location types".format(len(location_types))) + bba = write_database(args.device, "le") + + + if args.c_file: + print('#include "nextpnr.h"', file=args.outfile) + print('NEXTPNR_NAMESPACE_BEGIN', file=args.outfile) + + + if args.binary: + bba.write_binary(args.outfile) + + if args.c_file: + bba.write_string_c(args.outfile) + + if args.c_file: + print('NEXTPNR_NAMESPACE_END', file=args.outfile) + +if __name__ == "__main__": + main() diff --git a/generic/arch.cc b/generic/arch.cc index 8f897604..390830aa 100644 --- a/generic/arch.cc +++ b/generic/arch.cc @@ -19,6 +19,8 @@ #include #include "nextpnr.h" +#include "placer1.h" +#include "router1.h" NEXTPNR_NAMESPACE_BEGIN @@ -107,28 +109,48 @@ void Arch::addBelInout(IdString bel, IdString name, IdString wire) wires.at(wire).downhill_bel_pins.push_back(BelPin{bel, name}); } -void Arch::addFrameGraphic(const GraphicElement &graphic) +void Arch::addGroupBel(IdString group, IdString bel) { groups[group].bels.push_back(bel); } + +void Arch::addGroupWire(IdString group, IdString wire) { groups[group].wires.push_back(wire); } + +void Arch::addGroupPip(IdString group, IdString pip) { groups[group].pips.push_back(pip); } + +void Arch::addGroupGroup(IdString group, IdString grp) { groups[group].groups.push_back(grp); } + +void Arch::addDecalGraphic(DecalId decal, const GraphicElement &graphic) { - frame_graphics.push_back(graphic); - frameGraphicsReload = true; + decal_graphics[decal].push_back(graphic); + refreshUi(); } -void Arch::addWireGraphic(WireId wire, const GraphicElement &graphic) +void Arch::setFrameDecal(DecalXY decalxy) { - wires.at(wire).graphics.push_back(graphic); - wireGraphicsReload.insert(wire); + frame_decalxy = decalxy; + refreshUiFrame(); } -void Arch::addPipGraphic(PipId pip, const GraphicElement &graphic) +void Arch::setWireDecal(WireId wire, DecalXY decalxy) { - pips.at(pip).graphics.push_back(graphic); - pipGraphicsReload.insert(pip); + wires.at(wire).decalxy = decalxy; + refreshUiWire(wire); } -void Arch::addBelGraphic(BelId bel, const GraphicElement &graphic) +void Arch::setPipDecal(PipId pip, DecalXY decalxy) { - bels.at(bel).graphics.push_back(graphic); - belGraphicsReload.insert(bel); + pips.at(pip).decalxy = decalxy; + refreshUiPip(pip); +} + +void Arch::setBelDecal(BelId bel, DecalXY decalxy) +{ + bels.at(bel).decalxy = decalxy; + refreshUiBel(bel); +} + +void Arch::setGroupDecal(GroupId group, DecalXY decalxy) +{ + groups[group].decalxy = decalxy; + refreshUiGroup(group); } // --------------------------------------------------------------- @@ -159,6 +181,7 @@ void Arch::bindBel(BelId bel, IdString cell, PlaceStrength strength) bels.at(bel).bound_cell = cell; cells.at(cell)->bel = bel; cells.at(cell)->belStrength = strength; + refreshUiBel(bel); } void Arch::unbindBel(BelId bel) @@ -166,6 +189,7 @@ void Arch::unbindBel(BelId bel) cells.at(bels.at(bel).bound_cell)->bel = BelId(); cells.at(bels.at(bel).bound_cell)->belStrength = STRENGTH_NONE; bels.at(bel).bound_cell = IdString(); + refreshUiBel(bel); } bool Arch::checkBelAvail(BelId bel) const { return bels.at(bel).bound_cell == IdString(); } @@ -214,6 +238,7 @@ void Arch::bindWire(WireId wire, IdString net, PlaceStrength strength) wires.at(wire).bound_net = net; nets.at(net)->wires[wire].pip = PipId(); nets.at(net)->wires[wire].strength = strength; + refreshUiWire(wire); } void Arch::unbindWire(WireId wire) @@ -221,11 +246,14 @@ void Arch::unbindWire(WireId wire) auto &net_wires = nets[wires.at(wire).bound_net]->wires; auto pip = net_wires.at(wire).pip; - if (pip != PipId()) + if (pip != PipId()) { pips.at(pip).bound_net = IdString(); + refreshUiPip(pip); + } net_wires.erase(wire); wires.at(wire).bound_net = IdString(); + refreshUiWire(wire); } bool Arch::checkWireAvail(WireId wire) const { return wires.at(wire).bound_net == IdString(); } @@ -260,6 +288,8 @@ void Arch::bindPip(PipId pip, IdString net, PlaceStrength strength) wires.at(wire).bound_net = net; nets.at(net)->wires[wire].pip = pip; nets.at(net)->wires[wire].strength = strength; + refreshUiPip(pip); + refreshUiWire(wire); } void Arch::unbindPip(PipId pip) @@ -268,6 +298,8 @@ void Arch::unbindPip(PipId pip) nets.at(wires.at(wire).bound_net)->wires.erase(wire); pips.at(pip).bound_net = IdString(); wires.at(wire).bound_net = IdString(); + refreshUiPip(pip); + refreshUiWire(wire); } bool Arch::checkPipAvail(PipId pip) const { return pips.at(pip).bound_net == IdString(); } @@ -292,6 +324,28 @@ const std::vector &Arch::getWireAliases(WireId wire) const { return wires // --------------------------------------------------------------- +GroupId Arch::getGroupByName(IdString name) const { return name; } + +IdString Arch::getGroupName(GroupId group) const { return group; } + +std::vector Arch::getGroups() const +{ + std::vector ret; + for (auto &it : groups) + ret.push_back(it.first); + return ret; +} + +const std::vector &Arch::getGroupBels(GroupId group) const { return groups.at(group).bels; } + +const std::vector &Arch::getGroupWires(GroupId group) const { return groups.at(group).wires; } + +const std::vector &Arch::getGroupPips(GroupId group) const { return groups.at(group).pips; } + +const std::vector &Arch::getGroupGroups(GroupId group) const { return groups.at(group).groups; } + +// --------------------------------------------------------------- + void Arch::estimatePosition(BelId bel, int &x, int &y, bool &gb) const { x = bels.at(bel).grid_x; @@ -310,13 +364,23 @@ delay_t Arch::estimateDelay(WireId src, WireId dst) const // --------------------------------------------------------------- -const std::vector &Arch::getFrameGraphics() const { return frame_graphics; } +bool Arch::place() { return placer1(getCtx()); } -const std::vector &Arch::getBelGraphics(BelId bel) const { return bels.at(bel).graphics; } +bool Arch::route() { return router1(getCtx()); } -const std::vector &Arch::getWireGraphics(WireId wire) const { return wires.at(wire).graphics; } +// --------------------------------------------------------------- -const std::vector &Arch::getPipGraphics(PipId pip) const { return pips.at(pip).graphics; } +const std::vector &Arch::getDecalGraphics(DecalId decal) const { return decal_graphics.at(decal); } + +DecalXY Arch::getFrameDecal() const { return frame_decalxy; } + +DecalXY Arch::getBelDecal(BelId bel) const { return bels.at(bel).decalxy; } + +DecalXY Arch::getWireDecal(WireId wire) const { return wires.at(wire).decalxy; } + +DecalXY Arch::getPipDecal(PipId pip) const { return pips.at(pip).decalxy; } + +DecalXY Arch::getGroupDecal(GroupId group) const { return groups.at(group).decalxy; } // --------------------------------------------------------------- diff --git a/generic/arch.h b/generic/arch.h index e739cfab..5d7ac540 100644 --- a/generic/arch.h +++ b/generic/arch.h @@ -34,16 +34,16 @@ struct PipInfo IdString name, bound_net; WireId srcWire, dstWire; DelayInfo delay; - std::vector graphics; + DecalXY decalxy; }; struct WireInfo { IdString name, bound_net; - std::vector graphics; std::vector downhill, uphill, aliases; BelPin uphill_bel_pin; std::vector downhill_bel_pins; + DecalXY decalxy; int grid_x, grid_y; }; @@ -58,11 +58,21 @@ struct BelInfo { IdString name, type, bound_cell; std::unordered_map pins; - std::vector graphics; + DecalXY decalxy; int grid_x, grid_y; bool gb; }; +struct GroupInfo +{ + IdString name; + std::vector bels; + std::vector wires; + std::vector pips; + std::vector groups; + DecalXY decalxy; +}; + struct Arch : BaseCtx { std::string chipName; @@ -70,11 +80,14 @@ struct Arch : BaseCtx std::unordered_map wires; std::unordered_map pips; std::unordered_map bels; + std::unordered_map groups; std::vector bel_ids, wire_ids, pip_ids; std::unordered_map> bel_ids_by_type; - std::vector frame_graphics; + std::unordered_map> decal_graphics; + DecalXY frame_decalxy; + float grid_distance_to_delay; void addWire(IdString name, int x, int y); @@ -86,10 +99,17 @@ struct Arch : BaseCtx void addBelOutput(IdString bel, IdString name, IdString wire); void addBelInout(IdString bel, IdString name, IdString wire); - void addFrameGraphic(const GraphicElement &graphic); - void addWireGraphic(WireId wire, const GraphicElement &graphic); - void addPipGraphic(PipId pip, const GraphicElement &graphic); - void addBelGraphic(BelId bel, const GraphicElement &graphic); + void addGroupBel(IdString group, IdString bel); + void addGroupWire(IdString group, IdString wire); + void addGroupPip(IdString group, IdString pip); + void addGroupGroup(IdString group, IdString grp); + + void addDecalGraphic(DecalId decal, const GraphicElement &graphic); + void setFrameDecal(DecalXY decalxy); + void setWireDecal(WireId wire, DecalXY decalxy); + void setPipDecal(PipId pip, DecalXY decalxy); + void setBelDecal(BelId bel, DecalXY decalxy); + void setGroupDecal(GroupId group, DecalXY decalxy); // --------------------------------------------------------------- // Common Arch API. Every arch must provide the following methods. @@ -148,6 +168,14 @@ struct Arch : BaseCtx const std::vector &getPipsUphill(WireId wire) const; const std::vector &getWireAliases(WireId wire) const; + GroupId getGroupByName(IdString name) const; + IdString getGroupName(GroupId group) const; + std::vector getGroups() const; + const std::vector &getGroupBels(GroupId group) const; + const std::vector &getGroupWires(GroupId group) const; + const std::vector &getGroupPips(GroupId group) const; + const std::vector &getGroupGroups(GroupId group) const; + void estimatePosition(BelId bel, int &x, int &y, bool &gb) const; delay_t estimateDelay(WireId src, WireId dst) const; delay_t getDelayEpsilon() const { return 0.01; } @@ -155,16 +183,16 @@ struct Arch : BaseCtx float getDelayNS(delay_t v) const { return v; } uint32_t getDelayChecksum(delay_t v) const { return 0; } - const std::vector &getFrameGraphics() const; - const std::vector &getBelGraphics(BelId bel) const; - const std::vector &getWireGraphics(WireId wire) const; - const std::vector &getPipGraphics(PipId pip) const; + bool pack() { return true; } + bool place(); + bool route(); - bool allGraphicsReload = false; - bool frameGraphicsReload = false; - std::unordered_set belGraphicsReload; - std::unordered_set wireGraphicsReload; - std::unordered_set pipGraphicsReload; + const std::vector &getDecalGraphics(DecalId decal) const; + DecalXY getFrameDecal() const; + DecalXY getBelDecal(BelId bel) const; + DecalXY getWireDecal(WireId wire) const; + DecalXY getPipDecal(PipId pip) const; + DecalXY getGroupDecal(GroupId group) const; bool getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, delay_t &delay) const; IdString getPortClock(const CellInfo *cell, IdString port) const; diff --git a/generic/archdefs.h b/generic/archdefs.h index 9e8462e0..9969014b 100644 --- a/generic/archdefs.h +++ b/generic/archdefs.h @@ -49,5 +49,7 @@ typedef IdString PortPin; typedef IdString BelId; typedef IdString WireId; typedef IdString PipId; +typedef IdString GroupId; +typedef IdString DecalId; NEXTPNR_NAMESPACE_END diff --git a/generic/main.cc b/generic/main.cc index d025d8d4..3b8b3fe6 100644 --- a/generic/main.cc +++ b/generic/main.cc @@ -90,24 +90,34 @@ int main(int argc, char *argv[]) return 1; } - Context ctx(ArchArgs{}); + std::unique_ptr ctx = std::unique_ptr(new Context(ArchArgs{})); if (vm.count("verbose")) { - ctx.verbose = true; + ctx->verbose = true; } if (vm.count("force")) { - ctx.force = true; + ctx->force = true; } if (vm.count("seed")) { - ctx.rngseed(vm["seed"].as()); + ctx->rngseed(vm["seed"].as()); } +#ifndef NO_GUI + if (vm.count("gui")) { + Application a(argc, argv); + MainWindow w(std::move(ctx)); + w.show(); + + return a.exec(); + } +#endif + #ifndef NO_PYTHON if (vm.count("run")) { init_python(argv[0], true); - python_export_global("ctx", ctx); + python_export_global("ctx", *ctx.get()); std::vector files = vm["run"].as>(); for (auto filename : files) @@ -117,15 +127,6 @@ int main(int argc, char *argv[]) } #endif -#ifndef NO_GUI - if (vm.count("gui")) { - Application a(argc, argv); - MainWindow w; - w.show(); - - rc = a.exec(); - } -#endif return rc; } catch (log_execution_error_exception) { #if defined(_MSC_VER) diff --git a/gui/CMakeLists.txt b/gui/CMakeLists.txt index 68fd0229..2e8e367e 100644 --- a/gui/CMakeLists.txt +++ b/gui/CMakeLists.txt @@ -12,7 +12,6 @@ if (BUILD_PYTHON) ../3rdparty/python-console/modified/pyredirector.cc ../3rdparty/python-console/modified/pyinterpreter.cc - ../3rdparty/python-console/modified/pyconsole.cc ) endif() @@ -26,6 +25,7 @@ qt5_add_resources(GUI_RESOURCE_FILES ${_RESOURCES}) set(GUI_LIBRARY_FILES_${ufamily} Qt5::Widgets Qt5::OpenGL ${OPENGL_LIBRARIES} QtPropertyBrowser PARENT_SCOPE) add_library(gui_${family} STATIC ${GUI_SOURCE_FILES} ${PYTHON_CONSOLE_SRC} ${GUI_RESOURCE_FILES}) +include(${family}/family.cmake) target_include_directories(gui_${family} PRIVATE ../${family} ${family} ../3rdparty/QtPropertyBrowser/src) if (BUILD_PYTHON) diff --git a/gui/base.qrc b/gui/base.qrc index b9e2f237..bf21986b 100644 --- a/gui/base.qrc +++ b/gui/base.qrc @@ -4,5 +4,11 @@ resources/open.png resources/save.png resources/exit.png + resources/zoom.png + resources/resultset_first.png + resources/resultset_previous.png + resources/resultset_next.png + resources/resultset_last.png + resources/splash.png diff --git a/gui/basewindow.cc b/gui/basewindow.cc index 2463027a..07b71105 100644 --- a/gui/basewindow.cc +++ b/gui/basewindow.cc @@ -18,6 +18,7 @@ */ #include +#include #include #include #include @@ -27,16 +28,14 @@ #include "jsonparse.h" #include "log.h" #include "mainwindow.h" - -#ifndef NO_PYTHON #include "pythontab.h" -#endif static void initBasenameResource() { Q_INIT_RESOURCE(base); } NEXTPNR_NAMESPACE_BEGIN -BaseMainWindow::BaseMainWindow(QWidget *parent) : QMainWindow(parent), ctx(nullptr) +BaseMainWindow::BaseMainWindow(std::unique_ptr context, QWidget *parent) + : QMainWindow(parent), ctx(std::move(context)) { initBasenameResource(); qRegisterMetaType(); @@ -44,7 +43,7 @@ BaseMainWindow::BaseMainWindow(QWidget *parent) : QMainWindow(parent), ctx(nullp log_files.clear(); log_streams.clear(); - setObjectName(QStringLiteral("BaseMainWindow")); + setObjectName("BaseMainWindow"); resize(1024, 768); createMenusAndBars(); @@ -63,70 +62,80 @@ BaseMainWindow::BaseMainWindow(QWidget *parent) : QMainWindow(parent), ctx(nullp setCentralWidget(centralWidget); - DesignWidget *designview = new DesignWidget(); + designview = new DesignWidget(); designview->setMinimumWidth(300); - designview->setMaximumWidth(300); splitter_h->addWidget(designview); - connect(this, SIGNAL(contextChanged(Context *)), designview, SLOT(newContext(Context *))); - connect(this, SIGNAL(updateTreeView()), designview, SLOT(updateTree())); - - connect(designview, SIGNAL(info(std::string)), this, SLOT(writeInfo(std::string))); - tabWidget = new QTabWidget(); -#ifndef NO_PYTHON - PythonTab *pythontab = new PythonTab(); - tabWidget->addTab(pythontab, "Python"); - connect(this, SIGNAL(contextChanged(Context *)), pythontab, SLOT(newContext(Context *))); -#endif - info = new InfoTab(); - tabWidget->addTab(info, "Info"); + + console = new PythonTab(); + tabWidget->addTab(console, "Console"); + connect(this, SIGNAL(contextChanged(Context *)), console, SLOT(newContext(Context *))); centralTabWidget = new QTabWidget(); FPGAViewWidget *fpgaView = new FPGAViewWidget(); centralTabWidget->addTab(fpgaView, "Graphics"); connect(this, SIGNAL(contextChanged(Context *)), fpgaView, SLOT(newContext(Context *))); + connect(designview, SIGNAL(selected(std::vector)), fpgaView, + SLOT(onSelectedArchItem(std::vector))); + + connect(designview, SIGNAL(highlight(std::vector, int)), fpgaView, + SLOT(onHighlightGroupChanged(std::vector, int))); + + connect(this, SIGNAL(contextChanged(Context *)), designview, SLOT(newContext(Context *))); + connect(this, SIGNAL(updateTreeView()), designview, SLOT(updateTree())); + + connect(designview, SIGNAL(info(std::string)), this, SLOT(writeInfo(std::string))); splitter_v->addWidget(centralTabWidget); splitter_v->addWidget(tabWidget); + displaySplash(); } BaseMainWindow::~BaseMainWindow() {} -void BaseMainWindow::writeInfo(std::string text) { info->info(text); } +void BaseMainWindow::displaySplash() +{ + splash = new QSplashScreen(); + splash->setPixmap(QPixmap(":/icons/resources/splash.png")); + splash->show(); + connect(designview, SIGNAL(finishContextLoad()), splash, SLOT(close())); + connect(designview, SIGNAL(contextLoadStatus(std::string)), this, SLOT(displaySplashMessage(std::string))); + QCoreApplication::instance()->processEvents(); +} + +void BaseMainWindow::displaySplashMessage(std::string msg) +{ + splash->showMessage(msg.c_str(), Qt::AlignCenter | Qt::AlignBottom, Qt::white); + QCoreApplication::instance()->processEvents(); +} + +void BaseMainWindow::writeInfo(std::string text) { console->info(text); } void BaseMainWindow::createMenusAndBars() { actionNew = new QAction("New", this); - QIcon iconNew; - iconNew.addFile(QStringLiteral(":/icons/resources/new.png")); - actionNew->setIcon(iconNew); + actionNew->setIcon(QIcon(":/icons/resources/new.png")); actionNew->setShortcuts(QKeySequence::New); actionNew->setStatusTip("New project file"); connect(actionNew, SIGNAL(triggered()), this, SLOT(new_proj())); actionOpen = new QAction("Open", this); - QIcon iconOpen; - iconOpen.addFile(QStringLiteral(":/icons/resources/open.png")); - actionOpen->setIcon(iconOpen); + actionOpen->setIcon(QIcon(":/icons/resources/open.png")); actionOpen->setShortcuts(QKeySequence::Open); actionOpen->setStatusTip("Open an existing project file"); connect(actionOpen, SIGNAL(triggered()), this, SLOT(open_proj())); QAction *actionSave = new QAction("Save", this); - QIcon iconSave; - iconSave.addFile(QStringLiteral(":/icons/resources/save.png")); - actionSave->setIcon(iconSave); + actionSave->setIcon(QIcon(":/icons/resources/save.png")); actionSave->setShortcuts(QKeySequence::Save); actionSave->setStatusTip("Save existing project to disk"); - connect(actionSave, SIGNAL(triggered()), this, SLOT(save_proj())); actionSave->setEnabled(false); + connect(actionSave, SIGNAL(triggered()), this, SLOT(save_proj())); QAction *actionExit = new QAction("Exit", this); - QIcon iconExit; - iconExit.addFile(QStringLiteral(":/icons/resources/exit.png")); - actionExit->setIcon(iconExit); + actionExit->setIcon(QIcon(":/icons/resources/exit.png")); actionExit->setShortcuts(QKeySequence::Quit); actionExit->setStatusTip("Exit the application"); connect(actionExit, SIGNAL(triggered()), this, SLOT(close())); @@ -145,6 +154,12 @@ void BaseMainWindow::createMenusAndBars() addToolBar(Qt::TopToolBarArea, mainToolBar); statusBar = new QStatusBar(); + progressBar = new QProgressBar(statusBar); + progressBar->setAlignment(Qt::AlignRight); + progressBar->setMaximumSize(180, 19); + statusBar->addPermanentWidget(progressBar); + progressBar->setValue(0); + progressBar->setEnabled(false); setStatusBar(statusBar); menu_File->addAction(actionNew); diff --git a/gui/basewindow.h b/gui/basewindow.h index 5c06fa6e..18b5339e 100644 --- a/gui/basewindow.h +++ b/gui/basewindow.h @@ -20,34 +20,41 @@ #ifndef BASEMAINWINDOW_H #define BASEMAINWINDOW_H -#include "infotab.h" #include "nextpnr.h" #include #include #include +#include +#include #include #include #include Q_DECLARE_METATYPE(std::string) +Q_DECLARE_METATYPE(NEXTPNR_NAMESPACE_PREFIX DecalXY) NEXTPNR_NAMESPACE_BEGIN +class PythonTab; +class DesignWidget; + class BaseMainWindow : public QMainWindow { Q_OBJECT public: - explicit BaseMainWindow(QWidget *parent = 0); + explicit BaseMainWindow(std::unique_ptr context, QWidget *parent = 0); virtual ~BaseMainWindow(); - Context *getContext() { return ctx; } + Context *getContext() { return ctx.get(); } protected: void createMenusAndBars(); + void displaySplash(); protected Q_SLOTS: void writeInfo(std::string text); + void displaySplashMessage(std::string msg); virtual void new_proj() = 0; virtual void open_proj() = 0; @@ -58,16 +65,19 @@ class BaseMainWindow : public QMainWindow void updateTreeView(); protected: - Context *ctx; + std::unique_ptr ctx; QTabWidget *tabWidget; QTabWidget *centralTabWidget; - InfoTab *info; + PythonTab *console; QMenuBar *menuBar; QToolBar *mainToolBar; QStatusBar *statusBar; QAction *actionNew; QAction *actionOpen; + QProgressBar *progressBar; + QSplashScreen *splash; + DesignWidget *designview; }; NEXTPNR_NAMESPACE_END diff --git a/gui/designwidget.cc b/gui/designwidget.cc index 4922074b..335ed929 100644 --- a/gui/designwidget.cc +++ b/gui/designwidget.cc @@ -20,23 +20,15 @@ #include "designwidget.h" #include #include +#include #include #include +#include #include #include "fpgaviewwidget.h" NEXTPNR_NAMESPACE_BEGIN -enum class ElementType -{ - NONE, - BEL, - WIRE, - PIP, - NET, - CELL -}; - class ElementTreeItem : public QTreeWidgetItem { public: @@ -85,12 +77,88 @@ DesignWidget::DesignWidget(QWidget *parent) : QWidget(parent), ctx(nullptr), net propertyEditor = new QtTreePropertyBrowser(this); propertyEditor->setFactoryForManager(variantManager, variantFactory); propertyEditor->setPropertiesWithoutValueMarked(true); - propertyEditor->show(); + propertyEditor->treeWidget()->setContextMenuPolicy(Qt::CustomContextMenu); + + QLineEdit *lineEdit = new QLineEdit(); + lineEdit->setClearButtonEnabled(true); + lineEdit->addAction(QIcon(":/icons/resources/zoom.png"), QLineEdit::LeadingPosition); + lineEdit->setPlaceholderText("Search..."); + + actionFirst = new QAction("", this); + actionFirst->setIcon(QIcon(":/icons/resources/resultset_first.png")); + actionFirst->setEnabled(false); + connect(actionFirst, &QAction::triggered, this, [this] { + history_ignore = true; + history_index = 0; + treeWidget->setCurrentItem(history.at(history_index)); + updateButtons(); + }); + + actionPrev = new QAction("", this); + actionPrev->setIcon(QIcon(":/icons/resources/resultset_previous.png")); + actionPrev->setEnabled(false); + connect(actionPrev, &QAction::triggered, this, [this] { + history_ignore = true; + history_index--; + treeWidget->setCurrentItem(history.at(history_index)); + updateButtons(); + }); + + actionNext = new QAction("", this); + actionNext->setIcon(QIcon(":/icons/resources/resultset_next.png")); + actionNext->setEnabled(false); + connect(actionNext, &QAction::triggered, this, [this] { + history_ignore = true; + history_index++; + treeWidget->setCurrentItem(history.at(history_index)); + updateButtons(); + }); + + actionLast = new QAction("", this); + actionLast->setIcon(QIcon(":/icons/resources/resultset_last.png")); + actionLast->setEnabled(false); + connect(actionLast, &QAction::triggered, this, [this] { + history_ignore = true; + history_index = int(history.size() - 1); + treeWidget->setCurrentItem(history.at(history_index)); + updateButtons(); + }); + + QToolBar *toolbar = new QToolBar(); + toolbar->addAction(actionFirst); + toolbar->addAction(actionPrev); + toolbar->addAction(actionNext); + toolbar->addAction(actionLast); + + QWidget *topWidget = new QWidget(); + QVBoxLayout *vbox1 = new QVBoxLayout(); + topWidget->setLayout(vbox1); + vbox1->setSpacing(5); + vbox1->setContentsMargins(0, 0, 0, 0); + vbox1->addWidget(lineEdit); + vbox1->addWidget(treeWidget); + + QWidget *toolbarWidget = new QWidget(); + QHBoxLayout *hbox = new QHBoxLayout; + hbox->setAlignment(Qt::AlignCenter); + toolbarWidget->setLayout(hbox); + hbox->setSpacing(0); + hbox->setContentsMargins(0, 0, 0, 0); + hbox->addWidget(toolbar); + + QWidget *btmWidget = new QWidget(); + + QVBoxLayout *vbox2 = new QVBoxLayout(); + btmWidget->setLayout(vbox2); + vbox2->setSpacing(0); + vbox2->setContentsMargins(0, 0, 0, 0); + vbox2->addWidget(toolbarWidget); + vbox2->addWidget(propertyEditor); QSplitter *splitter = new QSplitter(Qt::Vertical); - splitter->addWidget(treeWidget); - splitter->addWidget(propertyEditor); + splitter->addWidget(topWidget); + splitter->addWidget(btmWidget); QGridLayout *mainLayout = new QGridLayout(); mainLayout->setSpacing(0); @@ -99,16 +167,61 @@ DesignWidget::DesignWidget(QWidget *parent) : QWidget(parent), ctx(nullptr), net setLayout(mainLayout); // Connection - connect(treeWidget, &QTreeWidget::customContextMenuRequested, this, &DesignWidget::prepareMenu); + connect(propertyEditor->treeWidget(), &QTreeWidget::customContextMenuRequested, this, + &DesignWidget::prepareMenuProperty); + connect(propertyEditor->treeWidget(), &QTreeWidget::itemDoubleClicked, this, &DesignWidget::onItemDoubleClicked); - connect(treeWidget, SIGNAL(itemClicked(QTreeWidgetItem *, int)), SLOT(onItemClicked(QTreeWidgetItem *, int))); + connect(treeWidget, SIGNAL(itemSelectionChanged()), SLOT(onItemSelectionChanged())); + connect(treeWidget, &QTreeWidget::customContextMenuRequested, this, &DesignWidget::prepareMenuTree); + + history_index = -1; + history_ignore = false; + + highlightColors[0] = QColor("#6495ed"); + highlightColors[1] = QColor("#7fffd4"); + highlightColors[2] = QColor("#98fb98"); + highlightColors[3] = QColor("#ffd700"); + highlightColors[4] = QColor("#cd5c5c"); + highlightColors[5] = QColor("#fa8072"); + highlightColors[6] = QColor("#ff69b4"); + highlightColors[7] = QColor("#da70d6"); } DesignWidget::~DesignWidget() {} +void DesignWidget::updateButtons() +{ + int count = int(history.size()); + actionFirst->setEnabled(history_index > 0); + actionPrev->setEnabled(history_index > 0); + actionNext->setEnabled(history_index < (count - 1)); + actionLast->setEnabled(history_index < (count - 1)); +} + +void DesignWidget::addToHistory(QTreeWidgetItem *item) +{ + if (!history_ignore) { + int count = int(history.size()); + for (int i = count - 1; i > history_index; i--) + history.pop_back(); + history.push_back(item); + history_index++; + } + history_ignore = false; + updateButtons(); +} + void DesignWidget::newContext(Context *ctx) { treeWidget->clear(); + history_ignore = false; + history_index = -1; + history.clear(); + updateButtons(); + + for (int i = 0; i < 6; i++) + nameToItem[i].clear(); + this->ctx = ctx; // Add bels to tree @@ -117,6 +230,7 @@ void DesignWidget::newContext(Context *ctx) bel_root->setText(0, "Bels"); treeWidget->insertTopLevelItem(0, bel_root); if (ctx) { + Q_EMIT contextLoadStatus("Configuring bels..."); for (auto bel : ctx->getBels()) { auto id = ctx->getBelName(bel); QStringList items = QString(id.c_str(ctx)).split("/"); @@ -128,7 +242,7 @@ void DesignWidget::newContext(Context *ctx) name += items.at(i); if (!bel_items.contains(name)) { if (i == items.size() - 1) - bel_items.insert(name, new IdStringTreeItem(id, ElementType::BEL, items.at(i), parent)); + nameToItem[0].insert(name, new IdStringTreeItem(id, ElementType::BEL, items.at(i), parent)); else bel_items.insert(name, new ElementTreeItem(ElementType::NONE, items.at(i), parent)); } @@ -139,6 +253,9 @@ void DesignWidget::newContext(Context *ctx) for (auto bel : bel_items.toStdMap()) { bel_root->addChild(bel.second); } + for (auto bel : nameToItem[0].toStdMap()) { + bel_root->addChild(bel.second); + } // Add wires to tree QTreeWidgetItem *wire_root = new QTreeWidgetItem(treeWidget); @@ -146,6 +263,7 @@ void DesignWidget::newContext(Context *ctx) wire_root->setText(0, "Wires"); treeWidget->insertTopLevelItem(0, wire_root); if (ctx) { + Q_EMIT contextLoadStatus("Configuring wires..."); for (auto wire : ctx->getWires()) { auto id = ctx->getWireName(wire); QStringList items = QString(id.c_str(ctx)).split("/"); @@ -157,7 +275,7 @@ void DesignWidget::newContext(Context *ctx) name += items.at(i); if (!wire_items.contains(name)) { if (i == items.size() - 1) - wire_items.insert(name, new IdStringTreeItem(id, ElementType::WIRE, items.at(i), parent)); + nameToItem[1].insert(name, new IdStringTreeItem(id, ElementType::WIRE, items.at(i), parent)); else wire_items.insert(name, new ElementTreeItem(ElementType::NONE, items.at(i), parent)); } @@ -168,13 +286,16 @@ void DesignWidget::newContext(Context *ctx) for (auto wire : wire_items.toStdMap()) { wire_root->addChild(wire.second); } - + for (auto wire : nameToItem[1].toStdMap()) { + wire_root->addChild(wire.second); + } // Add pips to tree QTreeWidgetItem *pip_root = new QTreeWidgetItem(treeWidget); QMap pip_items; pip_root->setText(0, "Pips"); treeWidget->insertTopLevelItem(0, pip_root); if (ctx) { + Q_EMIT contextLoadStatus("Configuring pips..."); for (auto pip : ctx->getPips()) { auto id = ctx->getPipName(pip); QStringList items = QString(id.c_str(ctx)).split("/"); @@ -186,7 +307,7 @@ void DesignWidget::newContext(Context *ctx) name += items.at(i); if (!pip_items.contains(name)) { if (i == items.size() - 1) - pip_items.insert(name, new IdStringTreeItem(id, ElementType::PIP, items.at(i), parent)); + nameToItem[2].insert(name, new IdStringTreeItem(id, ElementType::PIP, items.at(i), parent)); else pip_items.insert(name, new ElementTreeItem(ElementType::NONE, items.at(i), parent)); } @@ -197,6 +318,9 @@ void DesignWidget::newContext(Context *ctx) for (auto pip : pip_items.toStdMap()) { pip_root->addChild(pip.second); } + for (auto pip : nameToItem[2].toStdMap()) { + pip_root->addChild(pip.second); + } // Add nets to tree nets_root = new QTreeWidgetItem(treeWidget); @@ -207,6 +331,8 @@ void DesignWidget::newContext(Context *ctx) cells_root = new QTreeWidgetItem(treeWidget); cells_root->setText(0, "Cells"); treeWidget->insertTopLevelItem(0, cells_root); + + Q_EMIT finishContextLoad(); } void DesignWidget::updateTree() @@ -214,45 +340,48 @@ void DesignWidget::updateTree() clearProperties(); delete nets_root; delete cells_root; + nameToItem[3].clear(); + nameToItem[4].clear(); // Add nets to tree nets_root = new QTreeWidgetItem(treeWidget); - QMap nets_items; nets_root->setText(0, "Nets"); treeWidget->insertTopLevelItem(0, nets_root); if (ctx) { for (auto &item : ctx->nets) { auto id = item.first; QString name = QString(id.c_str(ctx)); - nets_items.insert(name, new IdStringTreeItem(id, ElementType::NET, name, nullptr)); + IdStringTreeItem *newItem = new IdStringTreeItem(id, ElementType::NET, name, nullptr); + nameToItem[3].insert(name, newItem); } } - for (auto item : nets_items.toStdMap()) { + for (auto item : nameToItem[3].toStdMap()) { nets_root->addChild(item.second); } // Add cells to tree cells_root = new QTreeWidgetItem(treeWidget); - QMap cells_items; cells_root->setText(0, "Cells"); treeWidget->insertTopLevelItem(0, cells_root); if (ctx) { for (auto &item : ctx->cells) { auto id = item.first; QString name = QString(id.c_str(ctx)); - cells_items.insert(name, new IdStringTreeItem(id, ElementType::CELL, name, nullptr)); + IdStringTreeItem *newItem = new IdStringTreeItem(id, ElementType::CELL, name, nullptr); + nameToItem[4].insert(name, newItem); } } - for (auto item : cells_items.toStdMap()) { + for (auto item : nameToItem[4].toStdMap()) { cells_root->addChild(item.second); } } - -void DesignWidget::addProperty(QtProperty *property, const QString &id) +QtProperty *DesignWidget::addTopLevelProperty(const QString &id) { - propertyToId[property] = id; - idToProperty[id] = property; - propertyEditor->addProperty(property); + QtProperty *topItem = groupManager->addProperty(id); + propertyToId[topItem] = id; + idToProperty[id] = topItem; + propertyEditor->addProperty(topItem); + return topItem; } void DesignWidget::clearProperties() @@ -266,8 +395,75 @@ void DesignWidget::clearProperties() idToProperty.clear(); } -void DesignWidget::onItemClicked(QTreeWidgetItem *clickItem, int pos) +QString DesignWidget::getElementTypeName(ElementType type) { + if (type == ElementType::NONE) + return ""; + if (type == ElementType::BEL) + return "BEL"; + if (type == ElementType::WIRE) + return "WIRE"; + if (type == ElementType::PIP) + return "PIP"; + if (type == ElementType::NET) + return "NET"; + if (type == ElementType::CELL) + return "CELL"; + return ""; +} +int DesignWidget::getElementIndex(ElementType type) +{ + if (type == ElementType::BEL) + return 0; + if (type == ElementType::WIRE) + return 1; + if (type == ElementType::PIP) + return 2; + if (type == ElementType::NET) + return 3; + if (type == ElementType::CELL) + return 4; + return -1; +} + +ElementType DesignWidget::getElementTypeByName(QString type) +{ + if (type == "BEL") + return ElementType::BEL; + if (type == "WIRE") + return ElementType::WIRE; + if (type == "PIP") + return ElementType::PIP; + if (type == "NET") + return ElementType::NET; + if (type == "CELL") + return ElementType::CELL; + return ElementType::NONE; +} + +void DesignWidget::addProperty(QtProperty *topItem, int propertyType, const QString &name, QVariant value, + const ElementType &type) +{ + QtVariantProperty *item = readOnlyManager->addProperty(propertyType, name); + item->setValue(value); + item->setPropertyId(getElementTypeName(type)); + topItem->addSubProperty(item); +} + +QtProperty *DesignWidget::addSubGroup(QtProperty *topItem, const QString &name) +{ + QtProperty *item = groupManager->addProperty(name); + topItem->addSubProperty(item); + return item; +} + +void DesignWidget::onItemSelectionChanged() +{ + if (treeWidget->selectedItems().size() == 0) + return; + + QTreeWidgetItem *clickItem = treeWidget->selectedItems().at(0); + if (!clickItem->parent()) return; @@ -276,305 +472,193 @@ void DesignWidget::onItemClicked(QTreeWidgetItem *clickItem, int pos) return; } + std::vector decals; + + addToHistory(clickItem); + clearProperties(); if (type == ElementType::BEL) { IdString c = static_cast(clickItem)->getData(); BelId bel = ctx->getBelByName(c); - QtProperty *topItem = groupManager->addProperty("Bel"); - addProperty(topItem, "Bel"); + decals.push_back(ctx->getBelDecal(bel)); + Q_EMIT selected(decals); - QtVariantProperty *nameItem = readOnlyManager->addProperty(QVariant::String, "Name"); - nameItem->setValue(c.c_str(ctx)); - topItem->addSubProperty(nameItem); + QtProperty *topItem = addTopLevelProperty("Bel"); - QtVariantProperty *typeItem = readOnlyManager->addProperty(QVariant::String, "Type"); - typeItem->setValue(ctx->belTypeToId(ctx->getBelType(bel)).c_str(ctx)); - topItem->addSubProperty(typeItem); - - QtVariantProperty *availItem = readOnlyManager->addProperty(QVariant::Bool, "Available"); - availItem->setValue(ctx->checkBelAvail(bel)); - topItem->addSubProperty(availItem); - - QtVariantProperty *cellItem = readOnlyManager->addProperty(QVariant::String, "Bound Cell"); - cellItem->setValue(ctx->getBoundBelCell(bel).c_str(ctx)); - topItem->addSubProperty(cellItem); - - QtVariantProperty *conflictItem = readOnlyManager->addProperty(QVariant::String, "Conflicting Cell"); - conflictItem->setValue(ctx->getConflictingBelCell(bel).c_str(ctx)); - topItem->addSubProperty(conflictItem); + addProperty(topItem, QVariant::String, "Name", c.c_str(ctx)); + addProperty(topItem, QVariant::String, "Type", ctx->belTypeToId(ctx->getBelType(bel)).c_str(ctx)); + addProperty(topItem, QVariant::Bool, "Available", ctx->checkBelAvail(bel)); + addProperty(topItem, QVariant::String, "Bound Cell", ctx->getBoundBelCell(bel).c_str(ctx), ElementType::CELL); + addProperty(topItem, QVariant::String, "Conflicting Cell", ctx->getConflictingBelCell(bel).c_str(ctx), + ElementType::CELL); } else if (type == ElementType::WIRE) { IdString c = static_cast(clickItem)->getData(); WireId wire = ctx->getWireByName(c); - QtProperty *topItem = groupManager->addProperty("Wire"); - addProperty(topItem, "Wire"); + decals.push_back(ctx->getWireDecal(wire)); + Q_EMIT selected(decals); - QtVariantProperty *nameItem = readOnlyManager->addProperty(QVariant::String, "Name"); - nameItem->setValue(c.c_str(ctx)); - topItem->addSubProperty(nameItem); + QtProperty *topItem = addTopLevelProperty("Wire"); - QtVariantProperty *availItem = readOnlyManager->addProperty(QVariant::Bool, "Available"); - availItem->setValue(ctx->checkWireAvail(wire)); - topItem->addSubProperty(availItem); - - QtVariantProperty *cellItem = readOnlyManager->addProperty(QVariant::String, "Bound Net"); - cellItem->setValue(ctx->getBoundWireNet(wire).c_str(ctx)); - topItem->addSubProperty(cellItem); - - QtVariantProperty *conflictItem = readOnlyManager->addProperty(QVariant::String, "Conflicting Net"); - conflictItem->setValue(ctx->getConflictingWireNet(wire).c_str(ctx)); - topItem->addSubProperty(conflictItem); + addProperty(topItem, QVariant::String, "Name", c.c_str(ctx)); + addProperty(topItem, QVariant::Bool, "Available", ctx->checkWireAvail(wire)); + addProperty(topItem, QVariant::String, "Bound Net", ctx->getBoundWireNet(wire).c_str(ctx), ElementType::NET); + addProperty(topItem, QVariant::String, "Conflicting Net", ctx->getConflictingWireNet(wire).c_str(ctx), + ElementType::NET); + QtProperty *belpinItem = addSubGroup(topItem, "BelPin Uphill"); BelPin uphill = ctx->getBelPinUphill(wire); - QtProperty *belpinItem = groupManager->addProperty("BelPin Uphill"); - topItem->addSubProperty(belpinItem); - - QtVariantProperty *belUphillItem = readOnlyManager->addProperty(QVariant::String, "Bel"); if (uphill.bel != BelId()) - belUphillItem->setValue(ctx->getBelName(uphill.bel).c_str(ctx)); + addProperty(belpinItem, QVariant::String, "Bel", ctx->getBelName(uphill.bel).c_str(ctx), ElementType::BEL); else - belUphillItem->setValue(""); - belpinItem->addSubProperty(belUphillItem); + addProperty(belpinItem, QVariant::String, "Bel", "", ElementType::BEL); - QtVariantProperty *portUphillItem = readOnlyManager->addProperty(QVariant::String, "PortPin"); - portUphillItem->setValue(ctx->portPinToId(uphill.pin).c_str(ctx)); - belpinItem->addSubProperty(portUphillItem); + addProperty(belpinItem, QVariant::String, "PortPin", ctx->portPinToId(uphill.pin).c_str(ctx), ElementType::BEL); - QtProperty *downhillItem = groupManager->addProperty("BelPins Downhill"); - topItem->addSubProperty(downhillItem); + QtProperty *downhillItem = addSubGroup(topItem, "BelPin Downhill"); for (const auto &item : ctx->getBelPinsDownhill(wire)) { QString belname = ""; if (item.bel != BelId()) belname = ctx->getBelName(item.bel).c_str(ctx); QString pinname = ctx->portPinToId(item.pin).c_str(ctx); - QtProperty *dhItem = groupManager->addProperty(belname + "-" + pinname); - downhillItem->addSubProperty(dhItem); - - QtVariantProperty *belItem = readOnlyManager->addProperty(QVariant::String, "Bel"); - belItem->setValue(belname); - dhItem->addSubProperty(belItem); - - QtVariantProperty *portItem = readOnlyManager->addProperty(QVariant::String, "PortPin"); - portItem->setValue(pinname); - dhItem->addSubProperty(portItem); + QtProperty *dhItem = addSubGroup(downhillItem, belname + "-" + pinname); + addProperty(dhItem, QVariant::String, "Bel", belname, ElementType::BEL); + addProperty(dhItem, QVariant::String, "PortPin", pinname); } - QtProperty *pipsDownItem = groupManager->addProperty("Pips Downhill"); - topItem->addSubProperty(pipsDownItem); + int counter = 0; + QtProperty *pipsDownItem = addSubGroup(downhillItem, "Pips Downhill"); for (const auto &item : ctx->getPipsDownhill(wire)) { - QtVariantProperty *pipItem = readOnlyManager->addProperty(QVariant::String, ""); - pipItem->setValue(ctx->getPipName(item).c_str(ctx)); - pipsDownItem->addSubProperty(pipItem); + addProperty(pipsDownItem, QVariant::String, "", ctx->getPipName(item).c_str(ctx), ElementType::PIP); + counter++; + if (counter == 50) { + addProperty(pipsDownItem, QVariant::String, "Warning", "Too many items...", ElementType::NONE); + break; + } } - QtProperty *pipsUpItem = groupManager->addProperty("Pips Uphill"); - topItem->addSubProperty(pipsUpItem); + counter = 0; + QtProperty *pipsUpItem = addSubGroup(downhillItem, "Pips Uphill"); for (const auto &item : ctx->getPipsUphill(wire)) { - QtVariantProperty *pipItem = readOnlyManager->addProperty(QVariant::String, ""); - pipItem->setValue(ctx->getPipName(item).c_str(ctx)); - pipsUpItem->addSubProperty(pipItem); + addProperty(pipsUpItem, QVariant::String, "", ctx->getPipName(item).c_str(ctx), ElementType::PIP); + counter++; + if (counter == 50) { + addProperty(pipsUpItem, QVariant::String, "Warning", "Too many items...", ElementType::NONE); + break; + } } - } else if (type == ElementType::PIP) { IdString c = static_cast(clickItem)->getData(); PipId pip = ctx->getPipByName(c); - QtProperty *topItem = groupManager->addProperty("Pip"); - addProperty(topItem, "Pip"); + decals.push_back(ctx->getPipDecal(pip)); + Q_EMIT selected(decals); - QtVariantProperty *nameItem = readOnlyManager->addProperty(QVariant::String, "Name"); - nameItem->setValue(c.c_str(ctx)); - topItem->addSubProperty(nameItem); + QtProperty *topItem = addTopLevelProperty("Pip"); - QtVariantProperty *availItem = readOnlyManager->addProperty(QVariant::Bool, "Available"); - availItem->setValue(ctx->checkPipAvail(pip)); - topItem->addSubProperty(availItem); - - QtVariantProperty *cellItem = readOnlyManager->addProperty(QVariant::String, "Bound Net"); - cellItem->setValue(ctx->getBoundPipNet(pip).c_str(ctx)); - topItem->addSubProperty(cellItem); - - QtVariantProperty *conflictItem = readOnlyManager->addProperty(QVariant::String, "Conflicting Net"); - conflictItem->setValue(ctx->getConflictingPipNet(pip).c_str(ctx)); - topItem->addSubProperty(conflictItem); - - QtVariantProperty *srcWireItem = readOnlyManager->addProperty(QVariant::String, "Src Wire"); - srcWireItem->setValue(ctx->getWireName(ctx->getPipSrcWire(pip)).c_str(ctx)); - topItem->addSubProperty(srcWireItem); - - QtVariantProperty *destWireItem = readOnlyManager->addProperty(QVariant::String, "Dest Wire"); - destWireItem->setValue(ctx->getWireName(ctx->getPipDstWire(pip)).c_str(ctx)); - topItem->addSubProperty(destWireItem); + addProperty(topItem, QVariant::String, "Name", c.c_str(ctx)); + addProperty(topItem, QVariant::Bool, "Available", ctx->checkPipAvail(pip)); + addProperty(topItem, QVariant::String, "Bound Net", ctx->getBoundPipNet(pip).c_str(ctx), ElementType::NET); + addProperty(topItem, QVariant::String, "Conflicting Net", ctx->getConflictingPipNet(pip).c_str(ctx), + ElementType::NET); + addProperty(topItem, QVariant::String, "Src Wire", ctx->getWireName(ctx->getPipSrcWire(pip)).c_str(ctx), + ElementType::WIRE); + addProperty(topItem, QVariant::String, "Dest Wire", ctx->getWireName(ctx->getPipDstWire(pip)).c_str(ctx), + ElementType::WIRE); DelayInfo delay = ctx->getPipDelay(pip); - QtProperty *delayItem = groupManager->addProperty("Delay"); - topItem->addSubProperty(delayItem); - - QtVariantProperty *raiseDelayItem = readOnlyManager->addProperty(QVariant::Double, "Raise"); - raiseDelayItem->setValue(delay.raiseDelay()); - delayItem->addSubProperty(raiseDelayItem); - - QtVariantProperty *fallDelayItem = readOnlyManager->addProperty(QVariant::Double, "Fall"); - fallDelayItem->setValue(delay.fallDelay()); - delayItem->addSubProperty(fallDelayItem); - - QtVariantProperty *avgDelayItem = readOnlyManager->addProperty(QVariant::Double, "Average"); - avgDelayItem->setValue(delay.avgDelay()); - delayItem->addSubProperty(avgDelayItem); + QtProperty *delayItem = addSubGroup(topItem, "Delay"); + addProperty(delayItem, QVariant::Double, "Raise", delay.raiseDelay()); + addProperty(delayItem, QVariant::Double, "Fall", delay.fallDelay()); + addProperty(delayItem, QVariant::Double, "Average", delay.avgDelay()); } else if (type == ElementType::NET) { IdString c = static_cast(clickItem)->getData(); NetInfo *net = ctx->nets.at(c).get(); - QtProperty *topItem = groupManager->addProperty("Net"); - addProperty(topItem, "Net"); + QtProperty *topItem = addTopLevelProperty("Net"); - QtVariantProperty *nameItem = readOnlyManager->addProperty(QVariant::String, "Name"); - nameItem->setValue(net->name.c_str(ctx)); - topItem->addSubProperty(nameItem); + addProperty(topItem, QVariant::String, "Name", net->name.c_str(ctx)); - QtProperty *driverItem = groupManager->addProperty("Driver"); - topItem->addSubProperty(driverItem); - - QtVariantProperty *portItem = readOnlyManager->addProperty(QVariant::String, "Port"); - portItem->setValue(net->driver.port.c_str(ctx)); - driverItem->addSubProperty(portItem); - - QtVariantProperty *budgetItem = readOnlyManager->addProperty(QVariant::Double, "Budget"); - budgetItem->setValue(net->driver.budget); - driverItem->addSubProperty(budgetItem); - - QtVariantProperty *cellNameItem = readOnlyManager->addProperty(QVariant::String, "Cell"); + QtProperty *driverItem = addSubGroup(topItem, "Driver"); + addProperty(driverItem, QVariant::String, "Port", net->driver.port.c_str(ctx)); + addProperty(driverItem, QVariant::Double, "Budget", net->driver.budget); if (net->driver.cell) - cellNameItem->setValue(net->driver.cell->name.c_str(ctx)); + addProperty(driverItem, QVariant::String, "Cell", net->driver.cell->name.c_str(ctx), ElementType::CELL); else - cellNameItem->setValue(""); - driverItem->addSubProperty(cellNameItem); + addProperty(driverItem, QVariant::String, "Cell", "", ElementType::CELL); - QtProperty *usersItem = groupManager->addProperty("Users"); - topItem->addSubProperty(usersItem); + QtProperty *usersItem = addSubGroup(topItem, "Users"); for (auto &item : net->users) { - QtProperty *portItem = groupManager->addProperty(item.port.c_str(ctx)); - usersItem->addSubProperty(portItem); + QtProperty *portItem = addSubGroup(usersItem, item.port.c_str(ctx)); - QtVariantProperty *nameItem = readOnlyManager->addProperty(QVariant::String, "Port"); - nameItem->setValue(item.port.c_str(ctx)); - portItem->addSubProperty(nameItem); - - QtVariantProperty *budgetItem = readOnlyManager->addProperty(QVariant::Double, "Budget"); - budgetItem->setValue(item.budget); - portItem->addSubProperty(budgetItem); - - QtVariantProperty *userItem = readOnlyManager->addProperty(QVariant::String, "Cell"); + addProperty(portItem, QVariant::String, "Port", item.port.c_str(ctx)); + addProperty(portItem, QVariant::Double, "Budget", item.budget); if (item.cell) - userItem->setValue(item.cell->name.c_str(ctx)); + addProperty(portItem, QVariant::String, "Cell", item.cell->name.c_str(ctx), ElementType::CELL); else - userItem->setValue(""); - portItem->addSubProperty(userItem); + addProperty(portItem, QVariant::String, "Cell", "", ElementType::CELL); } - QtProperty *attrsItem = groupManager->addProperty("Attributes"); - topItem->addSubProperty(attrsItem); + QtProperty *attrsItem = addSubGroup(topItem, "Attributes"); for (auto &item : net->attrs) { - QtVariantProperty *attrItem = readOnlyManager->addProperty(QVariant::String, item.first.c_str(ctx)); - attrItem->setValue(item.second.c_str()); - attrsItem->addSubProperty(attrItem); + addProperty(attrsItem, QVariant::String, item.first.c_str(ctx), item.second.c_str()); } - QtProperty *wiresItem = groupManager->addProperty("Wires"); - topItem->addSubProperty(wiresItem); + QtProperty *wiresItem = addSubGroup(topItem, "Wires"); for (auto &item : net->wires) { auto name = ctx->getWireName(item.first).c_str(ctx); - QtProperty *wireItem = groupManager->addProperty(name); - - QtVariantProperty *nameItem = readOnlyManager->addProperty(QVariant::String, "Name"); - nameItem->setValue(name); - wireItem->addSubProperty(nameItem); - - QtVariantProperty *pipItem = readOnlyManager->addProperty(QVariant::String, "Pip"); + QtProperty *wireItem = addSubGroup(wiresItem, name); + addProperty(wireItem, QVariant::String, "Name", name); if (item.second.pip != PipId()) - pipItem->setValue(ctx->getPipName(item.second.pip).c_str(ctx)); + addProperty(wireItem, QVariant::String, "Pip", ctx->getPipName(item.second.pip).c_str(ctx), + ElementType::PIP); else - pipItem->setValue(""); - wireItem->addSubProperty(pipItem); + addProperty(wireItem, QVariant::String, "Pip", "", ElementType::PIP); - QtVariantProperty *strengthItem = readOnlyManager->addProperty(QVariant::Int, "Strength"); - strengthItem->setValue((int)item.second.strength); - wireItem->addSubProperty(strengthItem); - - wiresItem->addSubProperty(wireItem); + addProperty(wireItem, QVariant::Int, "Strength", (int)item.second.strength); } } else if (type == ElementType::CELL) { IdString c = static_cast(clickItem)->getData(); CellInfo *cell = ctx->cells.at(c).get(); - QtProperty *topItem = groupManager->addProperty("Cell"); - addProperty(topItem, "Cell"); + QtProperty *topItem = addTopLevelProperty("Cell"); - QtVariantProperty *cellNameItem = readOnlyManager->addProperty(QVariant::String, "Name"); - cellNameItem->setValue(cell->name.c_str(ctx)); - topItem->addSubProperty(cellNameItem); - - QtVariantProperty *cellTypeItem = readOnlyManager->addProperty(QVariant::String, "Type"); - cellTypeItem->setValue(cell->type.c_str(ctx)); - topItem->addSubProperty(cellTypeItem); - - QtVariantProperty *cellBelItem = readOnlyManager->addProperty(QVariant::String, "Bel"); + addProperty(topItem, QVariant::String, "Name", cell->name.c_str(ctx)); + addProperty(topItem, QVariant::String, "Type", cell->type.c_str(ctx)); if (cell->bel != BelId()) - cellBelItem->setValue(ctx->getBelName(cell->bel).c_str(ctx)); + addProperty(topItem, QVariant::String, "Bel", ctx->getBelName(cell->bel).c_str(ctx), ElementType::BEL); else - cellBelItem->setValue(""); - topItem->addSubProperty(cellBelItem); + addProperty(topItem, QVariant::String, "Bel", "", ElementType::BEL); + addProperty(topItem, QVariant::Int, "Bel strength", int(cell->belStrength)); - QtVariantProperty *cellBelStrItem = readOnlyManager->addProperty(QVariant::Int, "Bel strength"); - cellBelStrItem->setValue(int(cell->belStrength)); - topItem->addSubProperty(cellBelStrItem); - - QtProperty *cellPortsItem = groupManager->addProperty("Ports"); - topItem->addSubProperty(cellPortsItem); + QtProperty *cellPortsItem = addSubGroup(topItem, "Ports"); for (auto &item : cell->ports) { PortInfo p = item.second; - QtProperty *portInfoItem = groupManager->addProperty(p.name.c_str(ctx)); - - QtVariantProperty *portInfoNameItem = readOnlyManager->addProperty(QVariant::String, "Name"); - portInfoNameItem->setValue(p.name.c_str(ctx)); - portInfoItem->addSubProperty(portInfoNameItem); - - QtVariantProperty *portInfoTypeItem = readOnlyManager->addProperty(QVariant::Int, "Type"); - portInfoTypeItem->setValue(int(p.type)); - portInfoItem->addSubProperty(portInfoTypeItem); - - QtVariantProperty *portInfoNetItem = readOnlyManager->addProperty(QVariant::String, "Net"); + QtProperty *portInfoItem = addSubGroup(cellPortsItem, p.name.c_str(ctx)); + addProperty(portInfoItem, QVariant::String, "Name", p.name.c_str(ctx)); + addProperty(portInfoItem, QVariant::Int, "Type", int(p.type)); if (p.net) - portInfoNetItem->setValue(p.net->name.c_str(ctx)); + addProperty(portInfoItem, QVariant::String, "Net", p.net->name.c_str(ctx), ElementType::NET); else - portInfoNetItem->setValue(""); - portInfoItem->addSubProperty(portInfoNetItem); - - cellPortsItem->addSubProperty(portInfoItem); + addProperty(portInfoItem, QVariant::String, "Net", "", ElementType::NET); } - QtProperty *cellAttrItem = groupManager->addProperty("Attributes"); - topItem->addSubProperty(cellAttrItem); + QtProperty *cellAttrItem = addSubGroup(topItem, "Attributes"); for (auto &item : cell->attrs) { - QtVariantProperty *attrItem = readOnlyManager->addProperty(QVariant::String, item.first.c_str(ctx)); - attrItem->setValue(item.second.c_str()); - cellAttrItem->addSubProperty(attrItem); + addProperty(cellAttrItem, QVariant::String, item.first.c_str(ctx), item.second.c_str()); } - QtProperty *cellParamsItem = groupManager->addProperty("Parameters"); - topItem->addSubProperty(cellParamsItem); + QtProperty *cellParamsItem = addSubGroup(topItem, "Parameters"); for (auto &item : cell->params) { - QtVariantProperty *paramItem = readOnlyManager->addProperty(QVariant::String, item.first.c_str(ctx)); - paramItem->setValue(item.second.c_str()); - cellParamsItem->addSubProperty(paramItem); + addProperty(cellParamsItem, QVariant::String, item.first.c_str(ctx), item.second.c_str()); } QtProperty *cellPinsItem = groupManager->addProperty("Pins"); @@ -583,38 +667,159 @@ void DesignWidget::onItemClicked(QTreeWidgetItem *clickItem, int pos) std::string cell_port = item.first.c_str(ctx); std::string bel_pin = item.second.c_str(ctx); - QtProperty *pinGroupItem = groupManager->addProperty((cell_port + " -> " + bel_pin).c_str()); + QtProperty *pinGroupItem = addSubGroup(cellPortsItem, (cell_port + " -> " + bel_pin).c_str()); - QtVariantProperty *cellItem = readOnlyManager->addProperty(QVariant::String, "Cell"); - cellItem->setValue(cell_port.c_str()); - pinGroupItem->addSubProperty(cellItem); - - QtVariantProperty *belItem = readOnlyManager->addProperty(QVariant::String, "Bel"); - belItem->setValue(bel_pin.c_str()); - pinGroupItem->addSubProperty(belItem); - - cellPinsItem->addSubProperty(pinGroupItem); + addProperty(pinGroupItem, QVariant::String, "Cell", cell_port.c_str(), ElementType::CELL); + addProperty(pinGroupItem, QVariant::String, "Bel", bel_pin.c_str(), ElementType::BEL); } } } -void DesignWidget::prepareMenu(const QPoint &pos) +std::vector DesignWidget::getDecals(ElementType type, IdString value) +{ + std::vector decals; + switch (type) { + case ElementType::BEL: { + BelId bel = ctx->getBelByName(value); + if (bel != BelId()) { + decals.push_back(ctx->getBelDecal(bel)); + } + } break; + case ElementType::WIRE: { + WireId wire = ctx->getWireByName(value); + if (wire != WireId()) { + decals.push_back(ctx->getWireDecal(wire)); + Q_EMIT selected(decals); + } + } break; + case ElementType::PIP: { + PipId pip = ctx->getPipByName(value); + if (pip != PipId()) { + decals.push_back(ctx->getPipDecal(pip)); + Q_EMIT selected(decals); + } + } break; + case ElementType::NET: { + } break; + case ElementType::CELL: { + } break; + default: + break; + } + return decals; +} + +void DesignWidget::updateHighlightGroup(QTreeWidgetItem *item, int group) +{ + if (highlightSelected.contains(item)) { + if (highlightSelected[item] == group) { + highlightSelected.remove(item); + } else + highlightSelected[item] = group; + } else + highlightSelected.insert(item, group); + + std::vector decals; + + for (auto it : highlightSelected.toStdMap()) { + if (it.second == group) { + ElementType type = static_cast(it.first)->getType(); + IdString value = static_cast(it.first)->getData(); + std::vector d = getDecals(type, value); + std::move(d.begin(), d.end(), std::back_inserter(decals)); + } + } + + Q_EMIT highlight(decals, group); +} + +void DesignWidget::prepareMenuProperty(const QPoint &pos) +{ + QTreeWidget *tree = propertyEditor->treeWidget(); + + itemContextMenu = tree->itemAt(pos); + if (itemContextMenu->parent() == nullptr) + return; + + QtBrowserItem *browserItem = propertyEditor->itemToBrowserItem(itemContextMenu); + if (!browserItem) + return; + QtProperty *selectedProperty = browserItem->property(); + ElementType type = getElementTypeByName(selectedProperty->propertyId()); + if (type == ElementType::NONE) + return; + IdString value = ctx->id(selectedProperty->valueText().toStdString()); + + QTreeWidgetItem *item = nameToItem[getElementIndex(type)].value(value.c_str(ctx)); + + QMenu menu(this); + QAction *selectAction = new QAction("&Select", this); + connect(selectAction, &QAction::triggered, this, [this, type, value] { Q_EMIT selected(getDecals(type, value)); }); + menu.addAction(selectAction); + + QMenu *subMenu = menu.addMenu("Highlight"); + QActionGroup *group = new QActionGroup(this); + group->setExclusive(true); + for (int i = 0; i < 8; i++) { + QPixmap pixmap(32, 32); + pixmap.fill(QColor(highlightColors[i])); + QAction *action = new QAction(QIcon(pixmap), ("Group " + std::to_string(i)).c_str(), this); + action->setCheckable(true); + subMenu->addAction(action); + group->addAction(action); + if (highlightSelected.contains(item) && highlightSelected[item] == i) + action->setChecked(true); + connect(action, &QAction::triggered, this, [this, i, item] { updateHighlightGroup(item, i); }); + } + menu.exec(tree->mapToGlobal(pos)); +} + +void DesignWidget::prepareMenuTree(const QPoint &pos) { QTreeWidget *tree = treeWidget; itemContextMenu = tree->itemAt(pos); - QAction *selectAction = new QAction("&Select", this); - selectAction->setStatusTip("Select item on view"); + ElementType type = static_cast(itemContextMenu)->getType(); + IdString value = static_cast(itemContextMenu)->getData(); - connect(selectAction, SIGNAL(triggered()), this, SLOT(selectObject())); + if (type == ElementType::NONE) + return; + + QTreeWidgetItem *item = nameToItem[getElementIndex(type)].value(value.c_str(ctx)); QMenu menu(this); - menu.addAction(selectAction); - + QMenu *subMenu = menu.addMenu("Highlight"); + QActionGroup *group = new QActionGroup(this); + group->setExclusive(true); + for (int i = 0; i < 8; i++) { + QPixmap pixmap(32, 32); + pixmap.fill(QColor(highlightColors[i])); + QAction *action = new QAction(QIcon(pixmap), ("Group " + std::to_string(i)).c_str(), this); + action->setCheckable(true); + subMenu->addAction(action); + group->addAction(action); + if (highlightSelected.contains(item) && highlightSelected[item] == i) + action->setChecked(true); + connect(action, &QAction::triggered, this, [this, i, item] { updateHighlightGroup(item, i); }); + } menu.exec(tree->mapToGlobal(pos)); } -void DesignWidget::selectObject() { Q_EMIT info("selected " + itemContextMenu->text(0).toStdString() + "\n"); } +void DesignWidget::onItemDoubleClicked(QTreeWidgetItem *item, int column) +{ + QtProperty *selectedProperty = propertyEditor->itemToBrowserItem(item)->property(); + ElementType type = getElementTypeByName(selectedProperty->propertyId()); + QString value = selectedProperty->valueText(); + int index = getElementIndex(type); + switch (type) { + case ElementType::NONE: + return; + default: { + if (nameToItem[index].contains(value)) + treeWidget->setCurrentItem(nameToItem[index].value(value)); + } break; + } +} NEXTPNR_NAMESPACE_END diff --git a/gui/designwidget.h b/gui/designwidget.h index 7785513a..1afe817d 100644 --- a/gui/designwidget.h +++ b/gui/designwidget.h @@ -21,6 +21,7 @@ #define DESIGNWIDGET_H #include +#include #include "nextpnr.h" #include "qtgroupboxpropertybrowser.h" #include "qtpropertymanager.h" @@ -29,6 +30,16 @@ NEXTPNR_NAMESPACE_BEGIN +enum class ElementType +{ + NONE, + BEL, + WIRE, + PIP, + NET, + CELL +}; + class DesignWidget : public QWidget { Q_OBJECT @@ -38,16 +49,30 @@ class DesignWidget : public QWidget ~DesignWidget(); private: - void addProperty(QtProperty *property, const QString &id); void clearProperties(); - + QtProperty *addTopLevelProperty(const QString &id); + QtProperty *addSubGroup(QtProperty *topItem, const QString &name); + void addProperty(QtProperty *topItem, int propertyType, const QString &name, QVariant value, + const ElementType &type = ElementType::NONE); + QString getElementTypeName(ElementType type); + ElementType getElementTypeByName(QString type); + int getElementIndex(ElementType type); + void updateButtons(); + void addToHistory(QTreeWidgetItem *item); + std::vector getDecals(ElementType type, IdString value); + void updateHighlightGroup(QTreeWidgetItem *item, int group); Q_SIGNALS: void info(std::string text); + void selected(std::vector decal); + void highlight(std::vector decal, int group); + void finishContextLoad(); + void contextLoadStatus(std::string text); private Q_SLOTS: - void prepareMenu(const QPoint &pos); - void onItemClicked(QTreeWidgetItem *item, int); - void selectObject(); + void prepareMenuProperty(const QPoint &pos); + void prepareMenuTree(const QPoint &pos); + void onItemSelectionChanged(); + void onItemDoubleClicked(QTreeWidgetItem *item, int column); public Q_SLOTS: void newContext(Context *ctx); void updateTree(); @@ -66,8 +91,22 @@ class DesignWidget : public QWidget QMap propertyToId; QMap idToProperty; + + QMap nameToItem[6]; + std::vector history; + int history_index; + bool history_ignore; + QTreeWidgetItem *nets_root; QTreeWidgetItem *cells_root; + + QAction *actionFirst; + QAction *actionPrev; + QAction *actionNext; + QAction *actionLast; + + QColor highlightColors[8]; + QMap highlightSelected; }; NEXTPNR_NAMESPACE_END diff --git a/gui/ecp5/family.cmake b/gui/ecp5/family.cmake new file mode 100644 index 00000000..e69de29b diff --git a/gui/ecp5/mainwindow.cc b/gui/ecp5/mainwindow.cc new file mode 100644 index 00000000..1168a55c --- /dev/null +++ b/gui/ecp5/mainwindow.cc @@ -0,0 +1,51 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Miodrag Milanovic + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "mainwindow.h" + +static void initMainResource() { Q_INIT_RESOURCE(nextpnr); } + +NEXTPNR_NAMESPACE_BEGIN + +MainWindow::MainWindow(std::unique_ptr context, QWidget *parent) : BaseMainWindow(std::move(context), parent) +{ + initMainResource(); + + std::string title = "nextpnr-ecp5 - [EMPTY]"; + setWindowTitle(title.c_str()); + + createMenu(); + Q_EMIT contextChanged(ctx.get()); +} + +MainWindow::~MainWindow() {} + +void MainWindow::createMenu() +{ + QMenu *menu_Custom = new QMenu("&Generic", menuBar); + menuBar->addAction(menu_Custom->menuAction()); +} + +void MainWindow::new_proj() {} + +void MainWindow::open_proj() {} + +bool MainWindow::save_proj() { return false; } + +NEXTPNR_NAMESPACE_END diff --git a/gui/ecp5/mainwindow.h b/gui/ecp5/mainwindow.h new file mode 100644 index 00000000..e97bb4e7 --- /dev/null +++ b/gui/ecp5/mainwindow.h @@ -0,0 +1,46 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Miodrag Milanovic + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include "../basewindow.h" + +NEXTPNR_NAMESPACE_BEGIN + +class MainWindow : public BaseMainWindow +{ + Q_OBJECT + + public: + explicit MainWindow(std::unique_ptr context, QWidget *parent = 0); + virtual ~MainWindow(); + + public: + void createMenu(); + + protected Q_SLOTS: + virtual void new_proj(); + virtual void open_proj(); + virtual bool save_proj(); +}; + +NEXTPNR_NAMESPACE_END + +#endif // MAINWINDOW_H diff --git a/gui/ecp5/nextpnr.qrc b/gui/ecp5/nextpnr.qrc new file mode 100644 index 00000000..03585ec0 --- /dev/null +++ b/gui/ecp5/nextpnr.qrc @@ -0,0 +1,2 @@ + + diff --git a/gui/fpgaviewwidget.cc b/gui/fpgaviewwidget.cc index 0c6b1a98..2d8d4cef 100644 --- a/gui/fpgaviewwidget.cc +++ b/gui/fpgaviewwidget.cc @@ -195,7 +195,7 @@ bool LineShader::compile(void) return true; } -void LineShader::draw(const LineShaderData &line, const QMatrix4x4 &projection) +void LineShader::draw(const LineShaderData &line, const QColor &color, float thickness, const QMatrix4x4 &projection) { auto gl = QOpenGLContext::currentContext()->functions(); vao_.bind(); @@ -214,8 +214,8 @@ void LineShader::draw(const LineShaderData &line, const QMatrix4x4 &projection) buffers_.index.allocate(&line.indices[0], sizeof(GLuint) * line.indices.size()); program_->setUniformValue(uniforms_.projection, projection); - program_->setUniformValue(uniforms_.thickness, line.thickness); - program_->setUniformValue(uniforms_.color, line.color.r, line.color.g, line.color.b, line.color.a); + program_->setUniformValue(uniforms_.thickness, thickness); + program_->setUniformValue(uniforms_.color, color.redF(), color.greenF(), color.blueF(), color.alphaF()); buffers_.position.bind(); program_->enableAttributeArray("position"); @@ -241,8 +241,27 @@ void LineShader::draw(const LineShaderData &line, const QMatrix4x4 &projection) } FPGAViewWidget::FPGAViewWidget(QWidget *parent) - : QOpenGLWidget(parent), moveX_(0), moveY_(0), zoom_(10.0f), lineShader_(this), ctx_(nullptr) + : QOpenGLWidget(parent), lineShader_(this), zoom_(500.f), ctx_(nullptr), selectedItemsChanged_(false) { + backgroundColor_ = QColor("#000000"); + gridColor_ = QColor("#333"); + gFrameColor_ = QColor("#d0d0d0"); + gHiddenColor_ = QColor("#606060"); + gInactiveColor_ = QColor("#303030"); + gActiveColor_ = QColor("#f0f0f0"); + gSelectedColor_ = QColor("#ff6600"); + frameColor_ = QColor("#0066ba"); + highlightColors[0] = QColor("#6495ed"); + highlightColors[1] = QColor("#7fffd4"); + highlightColors[2] = QColor("#98fb98"); + highlightColors[3] = QColor("#ffd700"); + highlightColors[4] = QColor("#cd5c5c"); + highlightColors[5] = QColor("#fa8072"); + highlightColors[6] = QColor("#ff69b4"); + highlightColors[7] = QColor("#da70d6"); + for (int i = 0; i < 8; i++) + highlightItemsChanged_[i] = false; + auto fmt = format(); fmt.setMajorVersion(3); fmt.setMinorVersion(1); @@ -264,6 +283,7 @@ FPGAViewWidget::~FPGAViewWidget() {} void FPGAViewWidget::newContext(Context *ctx) { ctx_ = ctx; + selectedItems_.clear(); update(); } @@ -271,66 +291,92 @@ QSize FPGAViewWidget::minimumSizeHint() const { return QSize(640, 480); } QSize FPGAViewWidget::sizeHint() const { return QSize(640, 480); } -void FPGAViewWidget::setXTranslation(float t_x) -{ - if (t_x == moveX_) - return; - - moveX_ = t_x; - update(); -} - -void FPGAViewWidget::setYTranslation(float t_y) -{ - if (t_y == moveY_) - return; - - moveY_ = t_y; - update(); -} - -void FPGAViewWidget::setZoom(float t_z) -{ - if (t_z == zoom_) - return; - zoom_ = t_z; - - if (zoom_ < 1.0f) - zoom_ = 1.0f; - if (zoom_ > 100.f) - zoom_ = 100.0f; - - update(); -} - void FPGAViewWidget::initializeGL() { if (!lineShader_.compile()) { log_error("Could not compile shader.\n"); } initializeOpenGLFunctions(); - glClearColor(1.0, 1.0, 1.0, 0.0); + glClearColor(backgroundColor_.red() / 255, backgroundColor_.green() / 255, backgroundColor_.blue() / 255, 0.0); } -void FPGAViewWidget::drawElement(LineShaderData &out, const GraphicElement &el) +void FPGAViewWidget::drawDecal(LineShaderData &out, const DecalXY &decal) { - const float scale = 1.0, offset = 0.0; + const float scale = 1.0; + float offsetX = 0.0, offsetY = 0.0; - if (el.type == GraphicElement::G_BOX) { - auto line = PolyLine(true); - line.point(offset + scale * el.x1, offset + scale * el.y1); - line.point(offset + scale * el.x2, offset + scale * el.y1); - line.point(offset + scale * el.x2, offset + scale * el.y2); - line.point(offset + scale * el.x1, offset + scale * el.y2); - line.build(out); - } + for (auto &el : ctx_->getDecalGraphics(decal.decal)) { + offsetX = decal.x; + offsetY = decal.y; - if (el.type == GraphicElement::G_LINE) { - PolyLine(offset + scale * el.x1, offset + scale * el.y1, offset + scale * el.x2, offset + scale * el.y2) - .build(out); + if (el.type == GraphicElement::G_BOX) { + auto line = PolyLine(true); + line.point(offsetX + scale * el.x1, offsetY + scale * el.y1); + line.point(offsetX + scale * el.x2, offsetY + scale * el.y1); + line.point(offsetX + scale * el.x2, offsetY + scale * el.y2); + line.point(offsetX + scale * el.x1, offsetY + scale * el.y2); + line.build(out); + } + + if (el.type == GraphicElement::G_LINE) { + PolyLine(offsetX + scale * el.x1, offsetY + scale * el.y1, offsetX + scale * el.x2, offsetY + scale * el.y2) + .build(out); + } } } +void FPGAViewWidget::drawDecal(LineShaderData out[], const DecalXY &decal) +{ + const float scale = 1.0; + float offsetX = 0.0, offsetY = 0.0; + + for (auto &el : ctx_->getDecalGraphics(decal.decal)) { + offsetX = decal.x; + offsetY = decal.y; + + if (el.type == GraphicElement::G_BOX) { + auto line = PolyLine(true); + line.point(offsetX + scale * el.x1, offsetY + scale * el.y1); + line.point(offsetX + scale * el.x2, offsetY + scale * el.y1); + line.point(offsetX + scale * el.x2, offsetY + scale * el.y2); + line.point(offsetX + scale * el.x1, offsetY + scale * el.y2); + switch (el.style) { + case GraphicElement::G_FRAME: + case GraphicElement::G_INACTIVE: + case GraphicElement::G_ACTIVE: + line.build(out[el.style]); + break; + default: + break; + } + } + + if (el.type == GraphicElement::G_LINE) { + auto line = PolyLine(offsetX + scale * el.x1, offsetY + scale * el.y1, offsetX + scale * el.x2, + offsetY + scale * el.y2); + switch (el.style) { + case GraphicElement::G_FRAME: + case GraphicElement::G_INACTIVE: + case GraphicElement::G_ACTIVE: + line.build(out[el.style]); + break; + default: + break; + } + } + } +} + +QMatrix4x4 FPGAViewWidget::getProjection(void) +{ + QMatrix4x4 matrix; + + const float aspect = float(width()) / float(height()); + matrix.perspective(3.14 / 2, aspect, zoomNear_, zoomFar_); + matrix.translate(0.0f, 0.0f, -zoom_); + return matrix; +} + void FPGAViewWidget::paintGL() { auto gl = QOpenGLContext::currentContext()->functions(); @@ -338,65 +384,112 @@ void FPGAViewWidget::paintGL() gl->glViewport(0, 0, width() * retinaScale, height() * retinaScale); gl->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - const float aspect = float(width()) / float(height()); + QMatrix4x4 matrix = getProjection(); - QMatrix4x4 matrix; - matrix.ortho(QRectF(-aspect / 2.0, -0.5, aspect, 1.0f)); - matrix.translate(moveX_, moveY_, -0.5); - matrix.scale(zoom_ * 0.01f, zoom_ * 0.01f, 0); + matrix *= viewMove_; + + // Calculate world thickness to achieve a screen 1px/1.1px line. + float thick1Px = mouseToWorldCoordinates(1, 0).x(); + float thick11Px = mouseToWorldCoordinates(1.1, 0).x(); // Draw grid. - auto grid = LineShaderData(0.01f, QColor("#DDD")); + auto grid = LineShaderData(); for (float i = -100.0f; i < 100.0f; i += 1.0f) { PolyLine(-100.0f, i, 100.0f, i).build(grid); PolyLine(i, -100.0f, i, 100.0f).build(grid); } - lineShader_.draw(grid, matrix); + lineShader_.draw(grid, gridColor_, thick1Px, matrix); + + LineShaderData shaders[4] = {[GraphicElement::G_FRAME] = LineShaderData(), + [GraphicElement::G_HIDDEN] = LineShaderData(), + [GraphicElement::G_INACTIVE] = LineShaderData(), + [GraphicElement::G_ACTIVE] = LineShaderData()}; - // Draw Bels. - auto bels = LineShaderData(0.02f, QColor("#b000ba")); if (ctx_) { + // Draw Bels. for (auto bel : ctx_->getBels()) { - for (auto &el : ctx_->getBelGraphics(bel)) - drawElement(bels, el); + drawDecal(shaders, ctx_->getBelDecal(bel)); + } + // Draw Wires. + for (auto wire : ctx_->getWires()) { + drawDecal(shaders, ctx_->getWireDecal(wire)); + } + // Draw Pips. + for (auto pip : ctx_->getPips()) { + drawDecal(shaders, ctx_->getPipDecal(pip)); + } + // Draw Groups. + for (auto group : ctx_->getGroups()) { + drawDecal(shaders, ctx_->getGroupDecal(group)); + } + + if (selectedItemsChanged_) { + selectedItemsChanged_ = false; + selectedShader_.clear(); + for (auto decal : selectedItems_) { + drawDecal(selectedShader_, decal); + } + } + for (int i = 0; i < 8; i++) { + if (highlightItemsChanged_[i]) { + highlightItemsChanged_[i] = false; + highlightShader_[i].clear(); + for (auto decal : highlightItems_[i]) { + drawDecal(highlightShader_[i], decal); + } + } } - lineShader_.draw(bels, matrix); } - // Draw Frame Graphics. - auto frames = LineShaderData(0.02f, QColor("#0066ba")); - if (ctx_) { - for (auto &el : ctx_->getFrameGraphics()) { - drawElement(frames, el); - } - lineShader_.draw(frames, matrix); - } + lineShader_.draw(shaders[0], gFrameColor_, thick11Px, matrix); + lineShader_.draw(shaders[1], gHiddenColor_, thick11Px, matrix); + lineShader_.draw(shaders[2], gInactiveColor_, thick11Px, matrix); + lineShader_.draw(shaders[3], gActiveColor_, thick11Px, matrix); + for (int i = 0; i < 8; i++) + lineShader_.draw(highlightShader_[i], highlightColors[i], thick11Px, matrix); + lineShader_.draw(selectedShader_, gSelectedColor_, thick11Px, matrix); +} + +void FPGAViewWidget::onSelectedArchItem(std::vector decals) +{ + selectedItems_ = decals; + selectedItemsChanged_ = true; + update(); +} + +void FPGAViewWidget::onHighlightGroupChanged(std::vector decals, int group) +{ + highlightItems_[group] = decals; + highlightItemsChanged_[group] = true; + update(); } void FPGAViewWidget::resizeGL(int width, int height) {} -void FPGAViewWidget::mousePressEvent(QMouseEvent *event) +void FPGAViewWidget::mousePressEvent(QMouseEvent *event) { lastPos_ = event->pos(); } + +// Invert the projection matrix to calculate screen/mouse to world/grid +// coordinates. +QVector4D FPGAViewWidget::mouseToWorldCoordinates(int x, int y) { - startDragX_ = moveX_; - startDragY_ = moveY_; - lastPos_ = event->pos(); + QMatrix4x4 p = getProjection(); + QVector2D unit = p.map(QVector4D(1, 1, 0, 1)).toVector2DAffine(); + + float sx = (((float)x) / (width() / 2)); + float sy = (((float)y) / (height() / 2)); + return QVector4D(sx / unit.x(), sy / unit.y(), 0, 1); } void FPGAViewWidget::mouseMoveEvent(QMouseEvent *event) { const int dx = event->x() - lastPos_.x(); const int dy = event->y() - lastPos_.y(); + lastPos_ = event->pos(); - const qreal retinaScale = devicePixelRatio(); - float aspect = float(width()) / float(height()); - const float dx_scale = dx * (1 / (float)width() * retinaScale * aspect); - const float dy_scale = dy * (1 / (float)height() * retinaScale); + auto world = mouseToWorldCoordinates(dx, dy); + viewMove_.translate(world.x(), -world.y()); - float xpos = dx_scale + startDragX_; - float ypos = dy_scale + startDragY_; - - setXTranslation(xpos); - setYTranslation(ypos); + update(); } void FPGAViewWidget::wheelEvent(QWheelEvent *event) @@ -404,8 +497,19 @@ void FPGAViewWidget::wheelEvent(QWheelEvent *event) QPoint degree = event->angleDelta() / 8; if (!degree.isNull()) { - float steps = degree.y() / 15.0; - setZoom(zoom_ + steps); + + if (zoom_ < zoomNear_) { + zoom_ = zoomNear_; + } else if (zoom_ < zoomLvl1_) { + zoom_ -= degree.y() / 10.0; + } else if (zoom_ < zoomLvl2_) { + zoom_ -= degree.y() / 5.0; + } else if (zoom_ < zoomFar_) { + zoom_ -= degree.y(); + } else { + zoom_ = zoomFar_; + } + update(); } } diff --git a/gui/fpgaviewwidget.h b/gui/fpgaviewwidget.h index c281fd77..33eb2800 100644 --- a/gui/fpgaviewwidget.h +++ b/gui/fpgaviewwidget.h @@ -41,18 +41,6 @@ NPNR_PACKED_STRUCT(struct Vertex2DPOD { Vertex2DPOD(GLfloat X, GLfloat Y) : x(X), y(Y) {} }); -// Vertex2DPOD is a structure of R, G, B, A values that can be passed to OpenGL -// directly. -NPNR_PACKED_STRUCT(struct ColorPOD { - GLfloat r; - GLfloat g; - GLfloat b; - GLfloat a; - - ColorPOD(GLfloat R, GLfloat G, GLfloat B, GLfloat A) : r(R), g(G), b(B), a(A) {} - ColorPOD(const QColor &color) : r(color.redF()), g(color.greenF()), b(color.blueF()), a(color.alphaF()) {} -}); - // LineShaderData is a built set of vertices that can be rendered by the // LineShader. // Each LineShaderData can have its' own color and thickness. @@ -63,10 +51,15 @@ struct LineShaderData std::vector miters; std::vector indices; - GLfloat thickness; - ColorPOD color; + LineShaderData(void) {} - LineShaderData(GLfloat Thickness, QColor Color) : thickness(Thickness), color(Color) {} + void clear(void) + { + vertices.clear(); + normals.clear(); + miters.clear(); + indices.clear(); + } }; // PolyLine is a set of segments defined by points, that can be built to a @@ -210,12 +203,20 @@ class LineShader bool compile(void); // Render a LineShaderData with a given M/V/P transformation. - void draw(const LineShaderData &data, const QMatrix4x4 &projection); + void draw(const LineShaderData &data, const QColor &color, float thickness, const QMatrix4x4 &projection); }; class FPGAViewWidget : public QOpenGLWidget, protected QOpenGLFunctions { Q_OBJECT + Q_PROPERTY(QColor backgroundColor MEMBER backgroundColor_ DESIGNABLE true) + Q_PROPERTY(QColor gridColor MEMBER gridColor_ DESIGNABLE true) + Q_PROPERTY(QColor gFrameColor MEMBER gFrameColor_ DESIGNABLE true) + Q_PROPERTY(QColor gHiddenColor MEMBER gHiddenColor_ DESIGNABLE true) + Q_PROPERTY(QColor gInactiveColor MEMBER gInactiveColor_ DESIGNABLE true) + Q_PROPERTY(QColor gActiveColor MEMBER gActiveColor_ DESIGNABLE true) + Q_PROPERTY(QColor gSelectedColor MEMBER gSelectedColor_ DESIGNABLE true) + Q_PROPERTY(QColor frameColor MEMBER frameColor_ DESIGNABLE true) public: FPGAViewWidget(QWidget *parent = 0); @@ -239,20 +240,46 @@ class FPGAViewWidget : public QOpenGLWidget, protected QOpenGLFunctions void mousePressEvent(QMouseEvent *event) Q_DECL_OVERRIDE; void mouseMoveEvent(QMouseEvent *event) Q_DECL_OVERRIDE; void wheelEvent(QWheelEvent *event) Q_DECL_OVERRIDE; - void drawElement(LineShaderData &data, const GraphicElement &el); + void drawDecal(LineShaderData &data, const DecalXY &decal); + void drawDecal(LineShaderData out[], const DecalXY &decal); public Q_SLOTS: void newContext(Context *ctx); + void onSelectedArchItem(std::vector decals); + void onHighlightGroupChanged(std::vector decals, int group); private: QPoint lastPos_; - float moveX_; - float moveY_; - float zoom_; LineShader lineShader_; + QMatrix4x4 viewMove_; + float zoom_; + QMatrix4x4 getProjection(void); + QVector4D mouseToWorldCoordinates(int x, int y); + + const float zoomNear_ = 1.0f; // do not zoom closer than this + const float zoomFar_ = 10000.0f; // do not zoom further than this + + const float zoomLvl1_ = 100.0f; + const float zoomLvl2_ = 50.0f; - float startDragX_; - float startDragY_; Context *ctx_; + + QColor backgroundColor_; + QColor gridColor_; + QColor gFrameColor_; + QColor gHiddenColor_; + QColor gInactiveColor_; + QColor gActiveColor_; + QColor gSelectedColor_; + QColor frameColor_; + + LineShaderData selectedShader_; + std::vector selectedItems_; + bool selectedItemsChanged_; + + LineShaderData highlightShader_[8]; + std::vector highlightItems_[8]; + bool highlightItemsChanged_[8]; + QColor highlightColors[8]; }; NEXTPNR_NAMESPACE_END diff --git a/gui/generic/family.cmake b/gui/generic/family.cmake new file mode 100644 index 00000000..e69de29b diff --git a/gui/generic/mainwindow.cc b/gui/generic/mainwindow.cc index fef63094..88e291e6 100644 --- a/gui/generic/mainwindow.cc +++ b/gui/generic/mainwindow.cc @@ -23,7 +23,7 @@ static void initMainResource() { Q_INIT_RESOURCE(nextpnr); } NEXTPNR_NAMESPACE_BEGIN -MainWindow::MainWindow(QWidget *parent) : BaseMainWindow(parent) +MainWindow::MainWindow(std::unique_ptr context, QWidget *parent) : BaseMainWindow(std::move(context), parent) { initMainResource(); @@ -31,6 +31,7 @@ MainWindow::MainWindow(QWidget *parent) : BaseMainWindow(parent) setWindowTitle(title.c_str()); createMenu(); + Q_EMIT contextChanged(ctx.get()); } MainWindow::~MainWindow() {} diff --git a/gui/generic/mainwindow.h b/gui/generic/mainwindow.h index fd9812cd..e97bb4e7 100644 --- a/gui/generic/mainwindow.h +++ b/gui/generic/mainwindow.h @@ -29,7 +29,7 @@ class MainWindow : public BaseMainWindow Q_OBJECT public: - explicit MainWindow(QWidget *parent = 0); + explicit MainWindow(std::unique_ptr context, QWidget *parent = 0); virtual ~MainWindow(); public: diff --git a/gui/ice40/family.cmake b/gui/ice40/family.cmake new file mode 100644 index 00000000..ede5b805 --- /dev/null +++ b/gui/ice40/family.cmake @@ -0,0 +1,3 @@ +if(ICE40_HX1K_ONLY) + target_compile_definitions(gui_${family} PRIVATE ICE40_HX1K_ONLY=1) +endif() diff --git a/gui/ice40/mainwindow.cc b/gui/ice40/mainwindow.cc index f423ee37..4b1f2c57 100644 --- a/gui/ice40/mainwindow.cc +++ b/gui/ice40/mainwindow.cc @@ -27,16 +27,14 @@ #include "design_utils.h" #include "jsonparse.h" #include "log.h" -#include "pack.h" #include "pcf.h" -#include "place_sa.h" -#include "route.h" static void initMainResource() { Q_INIT_RESOURCE(nextpnr); } NEXTPNR_NAMESPACE_BEGIN -MainWindow::MainWindow(QWidget *parent) : BaseMainWindow(parent), timing_driven(false) +MainWindow::MainWindow(std::unique_ptr context, ArchArgs args, QWidget *parent) + : BaseMainWindow(std::move(context), parent), timing_driven(false), chipArgs(args) { initMainResource(); @@ -62,6 +60,8 @@ MainWindow::MainWindow(QWidget *parent) : BaseMainWindow(parent), timing_driven( connect(this, SIGNAL(contextChanged(Context *)), task, SIGNAL(contextChanged(Context *))); createMenu(); + + Q_EMIT contextChanged(ctx.get()); } MainWindow::~MainWindow() { delete task; } @@ -72,60 +72,46 @@ void MainWindow::createMenu() menuBar->addAction(menu_Design->menuAction()); actionLoadJSON = new QAction("Open JSON", this); - QIcon iconLoadJSON; - iconLoadJSON.addFile(QStringLiteral(":/icons/resources/open_json.png")); - actionLoadJSON->setIcon(iconLoadJSON); + actionLoadJSON->setIcon(QIcon(":/icons/resources/open_json.png")); actionLoadJSON->setStatusTip("Open an existing JSON file"); + actionLoadJSON->setEnabled(true); connect(actionLoadJSON, SIGNAL(triggered()), this, SLOT(open_json())); - actionLoadJSON->setEnabled(false); actionLoadPCF = new QAction("Open PCF", this); - QIcon iconLoadPCF; - iconLoadPCF.addFile(QStringLiteral(":/icons/resources/open_pcf.png")); - actionLoadPCF->setIcon(iconLoadPCF); + actionLoadPCF->setIcon(QIcon(":/icons/resources/open_pcf.png")); actionLoadPCF->setStatusTip("Open PCF file"); - connect(actionLoadPCF, SIGNAL(triggered()), this, SLOT(open_pcf())); actionLoadPCF->setEnabled(false); + connect(actionLoadPCF, SIGNAL(triggered()), this, SLOT(open_pcf())); actionPack = new QAction("Pack", this); - QIcon iconPack; - iconPack.addFile(QStringLiteral(":/icons/resources/pack.png")); - actionPack->setIcon(iconPack); + actionPack->setIcon(QIcon(":/icons/resources/pack.png")); actionPack->setStatusTip("Pack current design"); - connect(actionPack, SIGNAL(triggered()), task, SIGNAL(pack())); actionPack->setEnabled(false); + connect(actionPack, SIGNAL(triggered()), task, SIGNAL(pack())); actionAssignBudget = new QAction("Assign Budget", this); - QIcon iconAssignBudget; - iconAssignBudget.addFile(QStringLiteral(":/icons/resources/time_add.png")); - actionAssignBudget->setIcon(iconAssignBudget); + actionAssignBudget->setIcon(QIcon(":/icons/resources/time_add.png")); actionAssignBudget->setStatusTip("Assign time budget for current design"); - connect(actionAssignBudget, SIGNAL(triggered()), this, SLOT(budget())); actionAssignBudget->setEnabled(false); + connect(actionAssignBudget, SIGNAL(triggered()), this, SLOT(budget())); actionPlace = new QAction("Place", this); - QIcon iconPlace; - iconPlace.addFile(QStringLiteral(":/icons/resources/place.png")); - actionPlace->setIcon(iconPlace); + actionPlace->setIcon(QIcon(":/icons/resources/place.png")); actionPlace->setStatusTip("Place current design"); - connect(actionPlace, SIGNAL(triggered()), this, SLOT(place())); actionPlace->setEnabled(false); + connect(actionPlace, SIGNAL(triggered()), this, SLOT(place())); actionRoute = new QAction("Route", this); - QIcon iconRoute; - iconRoute.addFile(QStringLiteral(":/icons/resources/route.png")); - actionRoute->setIcon(iconRoute); + actionRoute->setIcon(QIcon(":/icons/resources/route.png")); actionRoute->setStatusTip("Route current design"); - connect(actionRoute, SIGNAL(triggered()), task, SIGNAL(route())); actionRoute->setEnabled(false); + connect(actionRoute, SIGNAL(triggered()), task, SIGNAL(route())); actionSaveAsc = new QAction("Save ASC", this); - QIcon iconSaveAsc; - iconSaveAsc.addFile(QStringLiteral(":/icons/resources/save_asc.png")); - actionSaveAsc->setIcon(iconSaveAsc); + actionSaveAsc->setIcon(QIcon(":/icons/resources/save_asc.png")); actionSaveAsc->setStatusTip("Save ASC file"); - connect(actionSaveAsc, SIGNAL(triggered()), this, SLOT(save_asc())); actionSaveAsc->setEnabled(false); + connect(actionSaveAsc, SIGNAL(triggered()), this, SLOT(save_asc())); QToolBar *taskFPGABar = new QToolBar(); addToolBar(Qt::TopToolBarArea, taskFPGABar); @@ -147,28 +133,22 @@ void MainWindow::createMenu() menu_Design->addAction(actionSaveAsc); actionPlay = new QAction("Play", this); - QIcon iconPlay; - iconPlay.addFile(QStringLiteral(":/icons/resources/control_play.png")); - actionPlay->setIcon(iconPlay); + actionPlay->setIcon(QIcon(":/icons/resources/control_play.png")); actionPlay->setStatusTip("Continue running task"); - connect(actionPlay, SIGNAL(triggered()), task, SLOT(continue_thread())); actionPlay->setEnabled(false); + connect(actionPlay, SIGNAL(triggered()), task, SLOT(continue_thread())); actionPause = new QAction("Pause", this); - QIcon iconPause; - iconPause.addFile(QStringLiteral(":/icons/resources/control_pause.png")); - actionPause->setIcon(iconPause); + actionPause->setIcon(QIcon(":/icons/resources/control_pause.png")); actionPause->setStatusTip("Pause running task"); - connect(actionPause, SIGNAL(triggered()), task, SLOT(pause_thread())); actionPause->setEnabled(false); + connect(actionPause, SIGNAL(triggered()), task, SLOT(pause_thread())); actionStop = new QAction("Stop", this); - QIcon iconStop; - iconStop.addFile(QStringLiteral(":/icons/resources/control_stop.png")); - actionStop->setIcon(iconStop); + actionStop->setIcon(QIcon(":/icons/resources/control_stop.png")); actionStop->setStatusTip("Stop running task"); - connect(actionStop, SIGNAL(triggered()), task, SLOT(terminate_thread())); actionStop->setEnabled(false); + connect(actionStop, SIGNAL(triggered()), task, SLOT(terminate_thread())); QToolBar *taskToolBar = new QToolBar(); addToolBar(Qt::TopToolBarArea, taskToolBar); @@ -220,12 +200,16 @@ QStringList getSupportedPackages(ArchArgs::ArchArgsTypes chip) void MainWindow::new_proj() { QMap arch; +#ifdef ICE40_HX1K_ONLY + arch.insert("Lattice HX1K", ArchArgs::HX1K); +#else arch.insert("Lattice LP384", ArchArgs::LP384); arch.insert("Lattice LP1K", ArchArgs::LP1K); arch.insert("Lattice HX1K", ArchArgs::HX1K); arch.insert("Lattice UP5K", ArchArgs::UP5K); arch.insert("Lattice LP8K", ArchArgs::LP8K); arch.insert("Lattice HX8K", ArchArgs::HX8K); +#endif bool ok; QString item = QInputDialog::getItem(this, "Select new context", "Chip:", arch.keys(), 0, false, &ok); if (ok && !item.isEmpty()) { @@ -237,18 +221,30 @@ void MainWindow::new_proj() if (ok && !item.isEmpty()) { disableActions(); + preload_pcf = ""; chipArgs.package = package.toStdString().c_str(); - if (ctx) - delete ctx; - ctx = new Context(chipArgs); - - Q_EMIT contextChanged(ctx); - + ctx = std::unique_ptr(new Context(chipArgs)); actionLoadJSON->setEnabled(true); + + Q_EMIT displaySplash(); + Q_EMIT contextChanged(ctx.get()); } } } +void MainWindow::load_json(std::string filename, std::string pcf) +{ + preload_pcf = pcf; + disableActions(); + Q_EMIT task->loadfile(filename); +} + +void MainWindow::load_pcf(std::string filename) +{ + disableActions(); + Q_EMIT task->loadpcf(filename); +} + void MainWindow::newContext(Context *ctx) { std::string title = "nextpnr-ice40 - " + ctx->getChipName() + " ( " + chipArgs.package + " )"; @@ -259,8 +255,6 @@ void MainWindow::open_proj() { QString fileName = QFileDialog::getOpenFileName(this, QString("Open Project"), QString(), QString("*.proj")); if (!fileName.isEmpty()) { - tabWidget->setCurrentWidget(info); - std::string fn = fileName.toStdString(); disableActions(); } @@ -270,12 +264,7 @@ void MainWindow::open_json() { QString fileName = QFileDialog::getOpenFileName(this, QString("Open JSON"), QString(), QString("*.json")); if (!fileName.isEmpty()) { - tabWidget->setCurrentWidget(info); - - std::string fn = fileName.toStdString(); - disableActions(); - timing_driven = false; - Q_EMIT task->loadfile(fn); + load_json(fileName.toStdString(), ""); } } @@ -283,11 +272,7 @@ void MainWindow::open_pcf() { QString fileName = QFileDialog::getOpenFileName(this, QString("Open PCF"), QString(), QString("*.pcf")); if (!fileName.isEmpty()) { - tabWidget->setCurrentWidget(info); - - std::string fn = fileName.toStdString(); - disableActions(); - Q_EMIT task->loadpcf(fn); + load_pcf(fileName.toStdString()); } } @@ -328,9 +313,12 @@ void MainWindow::loadfile_finished(bool status) log("Loading design successful.\n"); actionLoadPCF->setEnabled(true); actionPack->setEnabled(true); + if (!preload_pcf.empty()) + load_pcf(preload_pcf); Q_EMIT updateTreeView(); } else { log("Loading design failed.\n"); + preload_pcf = ""; } } @@ -440,4 +428,4 @@ void MainWindow::budget() void MainWindow::place() { Q_EMIT task->place(timing_driven); } -NEXTPNR_NAMESPACE_END \ No newline at end of file +NEXTPNR_NAMESPACE_END diff --git a/gui/ice40/mainwindow.h b/gui/ice40/mainwindow.h index 8e847adc..cfd938f8 100644 --- a/gui/ice40/mainwindow.h +++ b/gui/ice40/mainwindow.h @@ -30,12 +30,13 @@ class MainWindow : public BaseMainWindow Q_OBJECT public: - explicit MainWindow(QWidget *parent = 0); + explicit MainWindow(std::unique_ptr context, ArchArgs args, QWidget *parent = 0); virtual ~MainWindow(); public: void createMenu(); - + void load_json(std::string filename, std::string pcf); + void load_pcf(std::string filename); protected Q_SLOTS: virtual void new_proj(); virtual void open_proj(); @@ -78,6 +79,7 @@ class MainWindow : public BaseMainWindow bool timing_driven; ArchArgs chipArgs; + std::string preload_pcf; }; NEXTPNR_NAMESPACE_END diff --git a/gui/ice40/worker.cc b/gui/ice40/worker.cc index ab82b6bb..09093ec8 100644 --- a/gui/ice40/worker.cc +++ b/gui/ice40/worker.cc @@ -23,10 +23,7 @@ #include "design_utils.h" #include "jsonparse.h" #include "log.h" -#include "pack.h" #include "pcf.h" -#include "place_sa.h" -#include "route.h" #include "timing.h" NEXTPNR_NAMESPACE_BEGIN @@ -99,7 +96,7 @@ void Worker::pack() { Q_EMIT taskStarted(); try { - bool res = pack_design(ctx); + bool res = ctx->pack(); print_utilisation(ctx); Q_EMIT pack_finished(res); } catch (WorkerInterruptionRequested) { @@ -124,7 +121,7 @@ void Worker::place(bool timing_driven) Q_EMIT taskStarted(); try { ctx->timing_driven = timing_driven; - Q_EMIT place_finished(place_design_sa(ctx)); + Q_EMIT place_finished(ctx->place()); } catch (WorkerInterruptionRequested) { Q_EMIT taskCanceled(); } @@ -134,7 +131,7 @@ void Worker::route() { Q_EMIT taskStarted(); try { - Q_EMIT route_finished(route_design(ctx)); + Q_EMIT route_finished(ctx->route()); } catch (WorkerInterruptionRequested) { Q_EMIT taskCanceled(); } diff --git a/gui/infotab.h b/gui/infotab.h index 0116755e..41529973 100644 --- a/gui/infotab.h +++ b/gui/infotab.h @@ -33,9 +33,10 @@ class InfoTab : public QWidget public: explicit InfoTab(QWidget *parent = 0); void info(std::string str); + public Q_SLOTS: + void clearBuffer(); private Q_SLOTS: void showContextMenu(const QPoint &pt); - void clearBuffer(); private: QPlainTextEdit *plainTextEdit; diff --git a/gui/line_editor.cc b/gui/line_editor.cc index 9d9dac25..425f2876 100644 --- a/gui/line_editor.cc +++ b/gui/line_editor.cc @@ -2,6 +2,7 @@ * nextpnr -- Next Generation Place and Route * * Copyright (C) 2018 Miodrag Milanovic + * Copyright (C) 2018 Alex Tsui * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -19,10 +20,14 @@ #include "line_editor.h" #include +#include +#include "ColumnFormatter.h" +#include "Utils.h" +#include "pyinterpreter.h" NEXTPNR_NAMESPACE_BEGIN -LineEditor::LineEditor(QWidget *parent) : QLineEdit(parent), index(0) +LineEditor::LineEditor(ParseHelper *helper, QWidget *parent) : QLineEdit(parent), index(0), parseHelper(helper) { setContextMenuPolicy(Qt::CustomContextMenu); QAction *clearAction = new QAction("Clear &history", this); @@ -38,10 +43,12 @@ LineEditor::LineEditor(QWidget *parent) : QLineEdit(parent), index(0) void LineEditor::keyPressEvent(QKeyEvent *ev) { + if (ev->key() == Qt::Key_Up || ev->key() == Qt::Key_Down) { + QToolTip::hideText(); if (lines.empty()) return; - + printf("Key_Up\n"); if (ev->key() == Qt::Key_Up) index--; if (ev->key() == Qt::Key_Down) @@ -56,12 +63,21 @@ void LineEditor::keyPressEvent(QKeyEvent *ev) } setText(lines[index]); } else if (ev->key() == Qt::Key_Escape) { + QToolTip::hideText(); clear(); return; + } else if (ev->key() == Qt::Key_Tab) { + autocomplete(); + return; } + QToolTip::hideText(); + QLineEdit::keyPressEvent(ev); } +// This makes TAB work +bool LineEditor::focusNextPrevChild(bool next) { return false; } + void LineEditor::textInserted() { if (lines.empty() || lines.back() != text()) @@ -82,4 +98,34 @@ void LineEditor::clearHistory() clear(); } -NEXTPNR_NAMESPACE_END \ No newline at end of file +void LineEditor::autocomplete() +{ + QString line = text(); + const std::list &suggestions = pyinterpreter_suggest(line.toStdString()); + if (suggestions.size() == 1) { + line = suggestions.back().c_str(); + } else { + // try to complete to longest common prefix + std::string prefix = LongestCommonPrefix(suggestions.begin(), suggestions.end()); + if (prefix.size() > (size_t)line.size()) { + line = prefix.c_str(); + } else { + ColumnFormatter fmt; + fmt.setItems(suggestions.begin(), suggestions.end()); + fmt.format(width() / 5); + QString out = ""; + for (auto &it : fmt.formattedOutput()) { + if (!out.isEmpty()) + out += "\n"; + out += it.c_str(); + } + QToolTip::setFont(font()); + if (!out.trimmed().isEmpty()) + QToolTip::showText(mapToGlobal(QPoint(0, 0)), out); + } + } + // set up the next line on the console + setText(line); +} + +NEXTPNR_NAMESPACE_END diff --git a/gui/line_editor.h b/gui/line_editor.h index 91837182..a779072f 100644 --- a/gui/line_editor.h +++ b/gui/line_editor.h @@ -2,6 +2,7 @@ * nextpnr -- Next Generation Place and Route * * Copyright (C) 2018 Miodrag Milanovic + * Copyright (C) 2018 Alex Tsui * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -22,6 +23,7 @@ #include #include +#include "ParseHelper.h" #include "nextpnr.h" NEXTPNR_NAMESPACE_BEGIN @@ -31,7 +33,7 @@ class LineEditor : public QLineEdit Q_OBJECT public: - explicit LineEditor(QWidget *parent = 0); + explicit LineEditor(ParseHelper *helper, QWidget *parent = 0); private Q_SLOTS: void textInserted(); @@ -43,11 +45,14 @@ class LineEditor : public QLineEdit protected: void keyPressEvent(QKeyEvent *) Q_DECL_OVERRIDE; + bool focusNextPrevChild(bool next) Q_DECL_OVERRIDE; + void autocomplete(); private: int index; QStringList lines; QMenu *contextMenu; + ParseHelper *parseHelper; }; NEXTPNR_NAMESPACE_END diff --git a/gui/pyconsole.cc b/gui/pyconsole.cc new file mode 100644 index 00000000..0ee393ce --- /dev/null +++ b/gui/pyconsole.cc @@ -0,0 +1,79 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Miodrag Milanovic + * Copyright (C) 2018 Alex Tsui + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "pyconsole.h" +#include "pyinterpreter.h" + +NEXTPNR_NAMESPACE_BEGIN + +const QColor PythonConsole::NORMAL_COLOR = QColor::fromRgbF(0, 0, 0); +const QColor PythonConsole::ERROR_COLOR = QColor::fromRgbF(1.0, 0, 0); +const QColor PythonConsole::OUTPUT_COLOR = QColor::fromRgbF(0, 0, 1.0); + +PythonConsole::PythonConsole(QWidget *parent) : QTextEdit(parent) {} + +void PythonConsole::parseEvent(const ParseMessage &message) +{ + // handle invalid user input + if (message.errorCode) { + setTextColor(ERROR_COLOR); + append(message.message.c_str()); + + setTextColor(NORMAL_COLOR); + append(""); + return; + } + // interpret valid user input + int errorCode = 0; + std::string res; + if (message.message.size()) + res = pyinterpreter_execute(message.message, &errorCode); + if (errorCode) { + setTextColor(ERROR_COLOR); + } else { + setTextColor(OUTPUT_COLOR); + } + + if (res.size()) { + append(res.c_str()); + } + setTextColor(NORMAL_COLOR); + append(""); + moveCursorToEnd(); +} + +void PythonConsole::displayString(QString text) +{ + QTextCursor cursor = textCursor(); + cursor.movePosition(QTextCursor::End); + setTextColor(NORMAL_COLOR); + cursor.insertText(text); + cursor.movePosition(QTextCursor::EndOfLine); + moveCursorToEnd(); +} + +void PythonConsole::moveCursorToEnd() +{ + QTextCursor cursor = textCursor(); + cursor.movePosition(QTextCursor::End); + setTextCursor(cursor); +} + +NEXTPNR_NAMESPACE_END diff --git a/gui/pyconsole.h b/gui/pyconsole.h new file mode 100644 index 00000000..9dbd3b95 --- /dev/null +++ b/gui/pyconsole.h @@ -0,0 +1,55 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Miodrag Milanovic + * Copyright (C) 2018 Alex Tsui + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#ifndef PYCONSOLE_H +#define PYCONSOLE_H + +#include +#include +#include +#include "ParseHelper.h" +#include "ParseListener.h" +#include "nextpnr.h" + +class QWidget; +class QKeyEvent; + +NEXTPNR_NAMESPACE_BEGIN + +class PythonConsole : public QTextEdit, public ParseListener +{ + Q_OBJECT + + public: + PythonConsole(QWidget *parent = 0); + + void displayString(QString text); + void moveCursorToEnd(); + virtual void parseEvent(const ParseMessage &message); + + protected: + static const QColor NORMAL_COLOR; + static const QColor ERROR_COLOR; + static const QColor OUTPUT_COLOR; +}; + +NEXTPNR_NAMESPACE_END + +#endif // PYCONSOLE_H diff --git a/gui/pythontab.cc b/gui/pythontab.cc index 897f87b3..e761128d 100644 --- a/gui/pythontab.cc +++ b/gui/pythontab.cc @@ -16,7 +16,6 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * */ -#ifndef NO_PYTHON #include "pythontab.h" #include @@ -25,12 +24,20 @@ NEXTPNR_NAMESPACE_BEGIN +const QString PythonTab::PROMPT = ">>> "; +const QString PythonTab::MULTILINE_PROMPT = "... "; + PythonTab::PythonTab(QWidget *parent) : QWidget(parent), initialized(false) { + QFont f("unexistent"); + f.setStyleHint(QFont::Monospace); + // Add text area for Python output and input line console = new PythonConsole(); console->setMinimumHeight(100); - console->setEnabled(false); + console->setReadOnly(true); + console->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); + console->setFont(f); console->setContextMenuPolicy(Qt::CustomContextMenu); QAction *clearAction = new QAction("Clear &buffer", this); @@ -41,9 +48,21 @@ PythonTab::PythonTab(QWidget *parent) : QWidget(parent), initialized(false) contextMenu->addAction(clearAction); connect(console, SIGNAL(customContextMenuRequested(const QPoint)), this, SLOT(showContextMenu(const QPoint))); + lineEdit = new LineEditor(&parseHelper); + lineEdit->setMinimumHeight(30); + lineEdit->setMaximumHeight(30); + lineEdit->setFont(f); + lineEdit->setPlaceholderText(PythonTab::PROMPT); + connect(lineEdit, SIGNAL(textLineInserted(QString)), this, SLOT(editLineReturnPressed(QString))); + QGridLayout *mainLayout = new QGridLayout(); mainLayout->addWidget(console, 0, 0); + mainLayout->addWidget(lineEdit, 1, 0); setLayout(mainLayout); + + parseHelper.subscribe(console); + + prompt = PythonTab::PROMPT; } PythonTab::~PythonTab() @@ -54,13 +73,26 @@ PythonTab::~PythonTab() } } +void PythonTab::editLineReturnPressed(QString text) +{ + console->displayString(prompt + text + "\n"); + + parseHelper.process(text.toStdString()); + + if (parseHelper.buffered()) + prompt = PythonTab::MULTILINE_PROMPT; + else + prompt = PythonTab::PROMPT; + + lineEdit->setPlaceholderText(prompt); +} + void PythonTab::newContext(Context *ctx) { if (initialized) { pyinterpreter_finalize(); deinit_python(); } - console->setEnabled(true); console->clear(); pyinterpreter_preinit(); @@ -74,13 +106,12 @@ void PythonTab::newContext(Context *ctx) QString version = QString("Python %1 on %2\n").arg(Py_GetVersion(), Py_GetPlatform()); console->displayString(version); - console->displayPrompt(); } void PythonTab::showContextMenu(const QPoint &pt) { contextMenu->exec(mapToGlobal(pt)); } void PythonTab::clearBuffer() { console->clear(); } -NEXTPNR_NAMESPACE_END +void PythonTab::info(std::string str) { console->displayString(str.c_str()); } -#endif \ No newline at end of file +NEXTPNR_NAMESPACE_END diff --git a/gui/pythontab.h b/gui/pythontab.h index 4b22e6a9..134874b6 100644 --- a/gui/pythontab.h +++ b/gui/pythontab.h @@ -20,11 +20,10 @@ #ifndef PYTHONTAB_H #define PYTHONTAB_H -#ifndef NO_PYTHON - #include #include #include +#include "ParseHelper.h" #include "line_editor.h" #include "nextpnr.h" #include "pyconsole.h" @@ -41,17 +40,24 @@ class PythonTab : public QWidget private Q_SLOTS: void showContextMenu(const QPoint &pt); - void clearBuffer(); + void editLineReturnPressed(QString text); public Q_SLOTS: void newContext(Context *ctx); + void info(std::string str); + void clearBuffer(); private: PythonConsole *console; + LineEditor *lineEdit; QMenu *contextMenu; bool initialized; + ParseHelper parseHelper; + QString prompt; + + static const QString PROMPT; + static const QString MULTILINE_PROMPT; }; NEXTPNR_NAMESPACE_END -#endif // NO_PYTHON #endif // PYTHONTAB_H diff --git a/gui/resources/resultset_first.png b/gui/resources/resultset_first.png new file mode 100644 index 00000000..b03eaf8b Binary files /dev/null and b/gui/resources/resultset_first.png differ diff --git a/gui/resources/resultset_last.png b/gui/resources/resultset_last.png new file mode 100644 index 00000000..8ec89478 Binary files /dev/null and b/gui/resources/resultset_last.png differ diff --git a/gui/resources/resultset_next.png b/gui/resources/resultset_next.png new file mode 100644 index 00000000..e252606d Binary files /dev/null and b/gui/resources/resultset_next.png differ diff --git a/gui/resources/resultset_previous.png b/gui/resources/resultset_previous.png new file mode 100644 index 00000000..18f9cc10 Binary files /dev/null and b/gui/resources/resultset_previous.png differ diff --git a/gui/resources/splash.png b/gui/resources/splash.png new file mode 100644 index 00000000..14d2842b Binary files /dev/null and b/gui/resources/splash.png differ diff --git a/gui/resources/zoom.png b/gui/resources/zoom.png new file mode 100644 index 00000000..908612e3 Binary files /dev/null and b/gui/resources/zoom.png differ diff --git a/ice40/arch.cc b/ice40/arch.cc index 72f9c1f3..adc37dbd 100644 --- a/ice40/arch.cc +++ b/ice40/arch.cc @@ -19,9 +19,13 @@ #include #include +#include "gfx.h" #include "log.h" #include "nextpnr.h" +#include "placer1.h" +#include "router1.h" #include "util.h" + NEXTPNR_NAMESPACE_BEGIN // ----------------------------------------------------------------------- @@ -365,6 +369,49 @@ std::string Arch::getBelPackagePin(BelId bel) const } return ""; } + +// ----------------------------------------------------------------------- + +GroupId Arch::getGroupByName(IdString name) const +{ + for (auto g : getGroups()) + if (getGroupName(g) == name) + return g; + return GroupId(); +} + +IdString Arch::getGroupName(GroupId group) const { return IdString(); } + +std::vector Arch::getGroups() const +{ + std::vector ret; + return ret; +} + +std::vector Arch::getGroupBels(GroupId group) const +{ + std::vector ret; + return ret; +} + +std::vector Arch::getGroupWires(GroupId group) const +{ + std::vector ret; + return ret; +} + +std::vector Arch::getGroupPips(GroupId group) const +{ + std::vector ret; + return ret; +} + +std::vector Arch::getGroupGroups(GroupId group) const +{ + std::vector ret; + return ret; +} + // ----------------------------------------------------------------------- void Arch::estimatePosition(BelId bel, int &x, int &y, bool &gb) const @@ -398,100 +445,206 @@ delay_t Arch::estimateDelay(WireId src, WireId dst) const // ----------------------------------------------------------------------- -std::vector Arch::getFrameGraphics() const +bool Arch::place() { return placer1(getCtx()); } + +bool Arch::route() { return router1(getCtx()); } + +// ----------------------------------------------------------------------- + +DecalXY Arch::getFrameDecal() const { - std::vector ret; - - for (int x = 0; x <= chip_info->width; x++) - for (int y = 0; y <= chip_info->height; y++) { - GraphicElement el; - el.type = GraphicElement::G_LINE; - el.x1 = x - 0.05, el.x2 = x + 0.05, el.y1 = y, el.y2 = y, el.z = 0; - ret.push_back(el); - el.x1 = x, el.x2 = x, el.y1 = y - 0.05, el.y2 = y + 0.05, el.z = 0; - ret.push_back(el); - } - - return ret; + DecalXY decalxy; + decalxy.decal.type = DecalId::TYPE_FRAME; + decalxy.decal.active = true; + return decalxy; } -std::vector Arch::getBelGraphics(BelId bel) const +DecalXY Arch::getBelDecal(BelId bel) const { - std::vector ret; - - auto bel_type = getBelType(bel); - - if (bel_type == TYPE_ICESTORM_LC) { - GraphicElement el; - el.type = GraphicElement::G_BOX; - el.x1 = chip_info->bel_data[bel.index].x + 0.1; - el.x2 = chip_info->bel_data[bel.index].x + 0.9; - el.y1 = chip_info->bel_data[bel.index].y + 0.10 + (chip_info->bel_data[bel.index].z) * (0.8 / 8); - el.y2 = chip_info->bel_data[bel.index].y + 0.18 + (chip_info->bel_data[bel.index].z) * (0.8 / 8); - el.z = 0; - ret.push_back(el); - } - - if (bel_type == TYPE_SB_IO) { - if (chip_info->bel_data[bel.index].x == 0 || chip_info->bel_data[bel.index].x == chip_info->width - 1) { - GraphicElement el; - el.type = GraphicElement::G_BOX; - el.x1 = chip_info->bel_data[bel.index].x + 0.1; - el.x2 = chip_info->bel_data[bel.index].x + 0.9; - if (chip_info->bel_data[bel.index].z == 0) { - el.y1 = chip_info->bel_data[bel.index].y + 0.10; - el.y2 = chip_info->bel_data[bel.index].y + 0.45; - } else { - el.y1 = chip_info->bel_data[bel.index].y + 0.55; - el.y2 = chip_info->bel_data[bel.index].y + 0.90; - } - el.z = 0; - ret.push_back(el); - } else { - GraphicElement el; - el.type = GraphicElement::G_BOX; - if (chip_info->bel_data[bel.index].z == 0) { - el.x1 = chip_info->bel_data[bel.index].x + 0.10; - el.x2 = chip_info->bel_data[bel.index].x + 0.45; - } else { - el.x1 = chip_info->bel_data[bel.index].x + 0.55; - el.x2 = chip_info->bel_data[bel.index].x + 0.90; - } - el.y1 = chip_info->bel_data[bel.index].y + 0.1; - el.y2 = chip_info->bel_data[bel.index].y + 0.9; - el.z = 0; - ret.push_back(el); - } - } - - if (bel_type == TYPE_ICESTORM_RAM) { - GraphicElement el; - el.type = GraphicElement::G_BOX; - el.x1 = chip_info->bel_data[bel.index].x + 0.1; - el.x2 = chip_info->bel_data[bel.index].x + 0.9; - el.y1 = chip_info->bel_data[bel.index].y + 0.1; - el.y2 = chip_info->bel_data[bel.index].y + 1.9; - el.z = 0; - ret.push_back(el); - } - - return ret; + DecalXY decalxy; + decalxy.decal.type = DecalId::TYPE_BEL; + decalxy.decal.index = bel.index; + decalxy.decal.active = bel_to_cell.at(bel.index) != IdString(); + return decalxy; } -std::vector Arch::getWireGraphics(WireId wire) const +DecalXY Arch::getWireDecal(WireId wire) const { - std::vector ret; - // FIXME - return ret; + DecalXY decalxy; + decalxy.decal.type = DecalId::TYPE_WIRE; + decalxy.decal.index = wire.index; + decalxy.decal.active = wire_to_net.at(wire.index) != IdString(); + return decalxy; } -std::vector Arch::getPipGraphics(PipId pip) const +DecalXY Arch::getPipDecal(PipId pip) const { - std::vector ret; - // FIXME - return ret; + DecalXY decalxy; + decalxy.decal.type = DecalId::TYPE_PIP; + decalxy.decal.index = pip.index; + decalxy.decal.active = pip_to_net.at(pip.index) != IdString(); + return decalxy; }; +DecalXY Arch::getGroupDecal(GroupId group) const +{ + DecalXY decalxy; + decalxy.decal.type = DecalId::TYPE_GROUP; + decalxy.decal.index = (group.type << 16) | (group.x << 8) | (group.y); + decalxy.decal.active = true; + return decalxy; +}; + +std::vector Arch::getDecalGraphics(DecalId decal) const +{ + std::vector ret; + + if (decal.type == DecalId::TYPE_FRAME) { + for (int x = 0; x <= chip_info->width; x++) + for (int y = 0; y <= chip_info->height; y++) { + GraphicElement el; + el.type = GraphicElement::G_LINE; + el.x1 = x - 0.05, el.x2 = x + 0.05, el.y1 = y, el.y2 = y, el.z = 0; + ret.push_back(el); + el.x1 = x, el.x2 = x, el.y1 = y - 0.05, el.y2 = y + 0.05, el.z = 0; + ret.push_back(el); + } + } + + if (decal.type == DecalId::TYPE_WIRE) { + WireId wire; + wire.index = decal.index; + + int n = chip_info->wire_data[wire.index].num_segments; + const WireSegmentPOD *p = chip_info->wire_data[wire.index].segments.get(); + + GraphicElement::style_t style = decal.active ? GraphicElement::G_ACTIVE : GraphicElement::G_INACTIVE; + + for (int i = 0; i < n; i++) + gfxTileWire(ret, p[i].x, p[i].y, GfxTileWireId(p[i].index), style); + } + + if (decal.type == DecalId::TYPE_BEL) { + BelId bel; + bel.index = decal.index; + + auto bel_type = getBelType(bel); + + if (bel_type == TYPE_ICESTORM_LC) { + GraphicElement el; + el.type = GraphicElement::G_BOX; + el.style = decal.active ? GraphicElement::G_ACTIVE : GraphicElement::G_INACTIVE; + el.x1 = chip_info->bel_data[bel.index].x + logic_cell_x1; + el.x2 = chip_info->bel_data[bel.index].x + logic_cell_x2; + el.y1 = chip_info->bel_data[bel.index].y + logic_cell_y1 + + (chip_info->bel_data[bel.index].z) * logic_cell_pitch; + el.y2 = chip_info->bel_data[bel.index].y + logic_cell_y2 + + (chip_info->bel_data[bel.index].z) * logic_cell_pitch; + el.z = 0; + ret.push_back(el); + + if (chip_info->bel_data[bel.index].z == 0) { + int tx = chip_info->bel_data[bel.index].x; + int ty = chip_info->bel_data[bel.index].y; + + // Main switchbox + GraphicElement main_sw; + main_sw.type = GraphicElement::G_BOX; + main_sw.style = GraphicElement::G_FRAME; + main_sw.x1 = tx + main_swbox_x1; + main_sw.x2 = tx + main_swbox_x2; + main_sw.y1 = ty + main_swbox_y1; + main_sw.y2 = ty + main_swbox_y2; + ret.push_back(main_sw); + + // Local tracks to LUT input switchbox + GraphicElement local_sw; + local_sw.type = GraphicElement::G_BOX; + local_sw.style = GraphicElement::G_FRAME; + local_sw.x1 = tx + local_swbox_x1; + local_sw.x2 = tx + local_swbox_x2; + local_sw.y1 = ty + local_swbox_y1; + local_sw.y2 = ty + local_swbox_y2; + local_sw.z = 0; + ret.push_back(local_sw); + } + } + + if (bel_type == TYPE_SB_IO) { + if (chip_info->bel_data[bel.index].x == 0 || chip_info->bel_data[bel.index].x == chip_info->width - 1) { + GraphicElement el; + el.type = GraphicElement::G_BOX; + el.x1 = chip_info->bel_data[bel.index].x + 0.1; + el.x2 = chip_info->bel_data[bel.index].x + 0.9; + if (chip_info->bel_data[bel.index].z == 0) { + el.y1 = chip_info->bel_data[bel.index].y + 0.10; + el.y2 = chip_info->bel_data[bel.index].y + 0.45; + } else { + el.y1 = chip_info->bel_data[bel.index].y + 0.55; + el.y2 = chip_info->bel_data[bel.index].y + 0.90; + } + el.z = 0; + ret.push_back(el); + } else { + GraphicElement el; + el.type = GraphicElement::G_BOX; + if (chip_info->bel_data[bel.index].z == 0) { + el.x1 = chip_info->bel_data[bel.index].x + 0.10; + el.x2 = chip_info->bel_data[bel.index].x + 0.45; + } else { + el.x1 = chip_info->bel_data[bel.index].x + 0.55; + el.x2 = chip_info->bel_data[bel.index].x + 0.90; + } + el.y1 = chip_info->bel_data[bel.index].y + 0.1; + el.y2 = chip_info->bel_data[bel.index].y + 0.9; + el.z = 0; + ret.push_back(el); + } + } + + if (bel_type == TYPE_ICESTORM_RAM) { + for (int i = 0; i < 2; i++) + { + int tx = chip_info->bel_data[bel.index].x; + int ty = chip_info->bel_data[bel.index].y + i; + + GraphicElement el; + el.type = GraphicElement::G_BOX; + el.style = decal.active ? GraphicElement::G_ACTIVE : GraphicElement::G_INACTIVE; + el.x1 = chip_info->bel_data[bel.index].x + logic_cell_x1; + el.x2 = chip_info->bel_data[bel.index].x + logic_cell_x2; + el.y1 = chip_info->bel_data[bel.index].y + logic_cell_y1; + el.y2 = chip_info->bel_data[bel.index].y + logic_cell_y2 + 7*logic_cell_pitch; + el.z = 0; + ret.push_back(el); + + // Main switchbox + GraphicElement main_sw; + main_sw.type = GraphicElement::G_BOX; + main_sw.style = GraphicElement::G_FRAME; + main_sw.x1 = tx + main_swbox_x1; + main_sw.x2 = tx + main_swbox_x2; + main_sw.y1 = ty + main_swbox_y1; + main_sw.y2 = ty + main_swbox_y2; + ret.push_back(main_sw); + + // Local tracks to LUT input switchbox + GraphicElement local_sw; + local_sw.type = GraphicElement::G_BOX; + local_sw.style = GraphicElement::G_FRAME; + local_sw.x1 = tx + local_swbox_x1; + local_sw.x2 = tx + local_swbox_x2; + local_sw.y1 = ty + local_swbox_y1; + local_sw.y2 = ty + local_swbox_y2; + local_sw.z = 0; + ret.push_back(local_sw); + } + } + } + + return ret; +} + // ----------------------------------------------------------------------- bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, delay_t &delay) const diff --git a/ice40/arch.h b/ice40/arch.h index 43aa0829..a02e0ced 100644 --- a/ice40/arch.h +++ b/ice40/arch.h @@ -70,6 +70,11 @@ NPNR_PACKED_STRUCT(struct PipInfoPOD { int32_t switch_index; }); +NPNR_PACKED_STRUCT(struct WireSegmentPOD { + int8_t x, y; + int16_t index; +}); + NPNR_PACKED_STRUCT(struct WireInfoPOD { RelPtr name; int32_t num_uphill, num_downhill; @@ -79,6 +84,9 @@ NPNR_PACKED_STRUCT(struct WireInfoPOD { BelPortPOD bel_uphill; RelPtr bels_downhill; + int32_t num_segments; + RelPtr segments; + int8_t x, y; WireType type; int8_t padding_0; @@ -515,9 +523,6 @@ struct Arch : BaseCtx // ------------------------------------------------- PipId getPipByName(IdString name) const; - IdString getPipName(PipId pip) const; - - uint32_t getPipChecksum(PipId pip) const { return pip.index; } void bindPip(PipId pip, IdString net, PlaceStrength strength) { @@ -577,6 +582,10 @@ struct Arch : BaseCtx range.e.cursor = chip_info->num_pips; return range; } + + IdString getPipName(PipId pip) const; + + uint32_t getPipChecksum(PipId pip) const { return pip.index; } WireId getPipSrcWire(PipId pip) const { @@ -634,6 +643,16 @@ struct Arch : BaseCtx // ------------------------------------------------- + GroupId getGroupByName(IdString name) const; + IdString getGroupName(GroupId group) const; + std::vector getGroups() const; + std::vector getGroupBels(GroupId group) const; + std::vector getGroupWires(GroupId group) const; + std::vector getGroupPips(GroupId group) const; + std::vector getGroupGroups(GroupId group) const; + + // ------------------------------------------------- + void estimatePosition(BelId bel, int &x, int &y, bool &gb) const; delay_t estimateDelay(WireId src, WireId dst) const; delay_t getDelayEpsilon() const { return 20; } @@ -643,16 +662,19 @@ struct Arch : BaseCtx // ------------------------------------------------- - std::vector getFrameGraphics() const; - std::vector getBelGraphics(BelId bel) const; - std::vector getWireGraphics(WireId wire) const; - std::vector getPipGraphics(PipId pip) const; + bool pack(); + bool place(); + bool route(); - bool allGraphicsReload = false; - bool frameGraphicsReload = false; - std::unordered_set belGraphicsReload; - std::unordered_set wireGraphicsReload; - std::unordered_set pipGraphicsReload; + // ------------------------------------------------- + + std::vector getDecalGraphics(DecalId decal) const; + + DecalXY getFrameDecal() const; + DecalXY getBelDecal(BelId bel) const; + DecalXY getWireDecal(WireId wire) const; + DecalXY getPipDecal(PipId pip) const; + DecalXY getGroupDecal(GroupId group) const; // ------------------------------------------------- diff --git a/ice40/arch_pybindings.cc b/ice40/arch_pybindings.cc index 67a37983..fd5109b4 100644 --- a/ice40/arch_pybindings.cc +++ b/ice40/arch_pybindings.cc @@ -58,7 +58,10 @@ void arch_wrap_python() auto arch_cls = class_, boost::noncopyable>("Arch", init()); auto ctx_cls = class_, boost::noncopyable>("Context", no_init) - .def("checksum", &Context::checksum); + .def("checksum", &Context::checksum) + .def("pack", &Context::pack) + .def("place", &Context::place) + .def("route", &Context::route); fn_wrapper_1a, conv_from_str>::def_wrap(ctx_cls, "getBelType"); @@ -75,7 +78,7 @@ void arch_wrap_python() fn_wrapper_1a, conv_from_str>::def_wrap(ctx_cls, "getConflictingBelCell"); fn_wrapper_0a>::def_wrap(ctx_cls, - "getBels"); + "getBels"); fn_wrapper_1a, conv_from_str>::def_wrap(ctx_cls, "getBelsAtSameTile"); @@ -139,15 +142,15 @@ void arch_wrap_python() fn_wrapper_0a>::def_wrap( ctx_cls, "getChipName"); fn_wrapper_0a>::def_wrap(ctx_cls, - "archId"); + "archId"); typedef std::unordered_map> CellMap; typedef std::unordered_map> NetMap; readonly_wrapper>::def_wrap(ctx_cls, - "cells"); + "cells"); readonly_wrapper>::def_wrap(ctx_cls, - "nets"); + "nets"); WRAP_RANGE(Bel, conv_to_str); WRAP_RANGE(Wire, conv_to_str); WRAP_RANGE(AllPip, conv_to_str); diff --git a/ice40/archdefs.h b/ice40/archdefs.h index be2e406d..75df678a 100644 --- a/ice40/archdefs.h +++ b/ice40/archdefs.h @@ -21,6 +21,8 @@ #error Include "archdefs.h" via "nextpnr.h" only. #endif +#include + NEXTPNR_NAMESPACE_BEGIN typedef int delay_t; @@ -107,6 +109,47 @@ struct PipId bool operator!=(const PipId &other) const { return index != other.index; } }; +struct GroupId +{ + enum : int8_t + { + TYPE_NONE, + TYPE_FRAME, + TYPE_MAIN_SW, + TYPE_LOCAL_SW, + TYPE_LC0_SW, + TYPE_LC1_SW, + TYPE_LC2_SW, + TYPE_LC3_SW, + TYPE_LC4_SW, + TYPE_LC5_SW, + TYPE_LC6_SW, + TYPE_LC7_SW + } type = TYPE_NONE; + int8_t x = 0, y = 0; + + bool operator==(const GroupId &other) const { return (type == other.type) && (x == other.x) && (y == other.y); } + bool operator!=(const GroupId &other) const { return (type != other.type) || (x != other.x) || (y == other.y); } +}; + +struct DecalId +{ + enum : int8_t + { + TYPE_NONE, + TYPE_FRAME, + TYPE_BEL, + TYPE_WIRE, + TYPE_PIP, + TYPE_GROUP + } type = TYPE_NONE; + int32_t index = -1; + bool active = false; + + bool operator==(const DecalId &other) const { return (type == other.type) && (index == other.index); } + bool operator!=(const DecalId &other) const { return (type != other.type) || (index != other.index); } +}; + NEXTPNR_NAMESPACE_END namespace std { @@ -135,4 +178,28 @@ template <> struct hash : hash template <> struct hash : hash { }; + +template <> struct hash +{ + std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX GroupId &group) const noexcept + { + std::size_t seed = 0; + boost::hash_combine(seed, hash()(group.type)); + boost::hash_combine(seed, hash()(group.x)); + boost::hash_combine(seed, hash()(group.y)); + return seed; + } +}; + +template <> struct hash +{ + std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX DecalId &decal) const noexcept + { + std::size_t seed = 0; + boost::hash_combine(seed, hash()(decal.type)); + boost::hash_combine(seed, hash()(decal.index)); + return seed; + } +}; + } // namespace std diff --git a/ice40/bitstream.cc b/ice40/bitstream.cc index 98a7a0e4..a62c6c09 100644 --- a/ice40/bitstream.cc +++ b/ice40/bitstream.cc @@ -341,9 +341,8 @@ void write_asc(const Context *ctx, std::ostream &out) set_config(ti, config.at(y).at(x), "Cascade.IPCON_LC0" + std::to_string(lc_idx) + "_inmux02_5", true); else - set_config(ti, config.at(y).at(x), - "Cascade.MULT" + std::to_string(int(tile - TILE_DSP0)) + "_LC0" + - std::to_string(lc_idx) + "_inmux02_5", + set_config(ti, config.at(y).at(x), "Cascade.MULT" + std::to_string(int(tile - TILE_DSP0)) + + "_LC0" + std::to_string(lc_idx) + "_inmux02_5", true); } } diff --git a/ice40/chipdb.py b/ice40/chipdb.py index 931c73d1..51fe169c 100644 --- a/ice40/chipdb.py +++ b/ice40/chipdb.py @@ -11,6 +11,7 @@ group.add_argument("-b", "--binary", action="store_true") group.add_argument("-c", "--c_file", action="store_true") parser.add_argument("filename", type=str, help="chipdb input filename") parser.add_argument("-p", "--portspins", type=str, help="path to portpins.inc") +parser.add_argument("-g", "--gfxh", type=str, help="path to gfx.h") args = parser.parse_args() endianness = "le" @@ -54,6 +55,9 @@ beltypes = dict() tiletypes = dict() wiretypes = dict() +gfx_wire_ids = dict() +wire_segments = dict() + with open(args.portspins) as f: for line in f: line = line.replace("(", " ") @@ -66,6 +70,20 @@ with open(args.portspins) as f: idx = len(portpins) + 1 portpins[line[1]] = idx +with open(args.gfxh) as f: + state = 0 + for line in f: + if state == 0 and line.startswith("enum GfxTileWireId"): + state = 1 + elif state == 1 and line.startswith("};"): + state = 0 + elif state == 1 and (line.startswith("{") or line.strip() == ""): + pass + elif state == 1: + idx = len(gfx_wire_ids) + name = line.strip().rstrip(",") + gfx_wire_ids[name] = idx + beltypes["ICESTORM_LC"] = 1 beltypes["ICESTORM_RAM"] = 2 beltypes["SB_IO"] = 3 @@ -371,6 +389,10 @@ with open(args.filename, "r") as f: if mode[1] not in wire_xy: wire_xy[mode[1]] = list() wire_xy[mode[1]].append((int(line[0]), int(line[1]))) + if mode[1] not in wire_segments: + wire_segments[mode[1]] = set() + if ("TILE_WIRE_" + wname[2].upper().replace("/", "_")) in gfx_wire_ids: + wire_segments[mode[1]].add((wname[0], wname[1], gfx_wire_ids["TILE_WIRE_" + wname[2].upper().replace("/", "_")])) continue if mode[0] in ("buffer", "routing"): @@ -712,7 +734,7 @@ class BinaryBlobAssembler: def finalize(self): assert not self.finalized - for s, index in self.strings.items(): + for s, index in sorted(self.strings.items()): self.l("str%d" % index, "char") for c in s: self.data.append(ord(c)) @@ -947,7 +969,7 @@ for wire in range(num_wires): if wire in wire_downhill_belports: num_bels_downhill = len(wire_downhill_belports[wire]) bba.l("wire%d_downbels" % wire, "BelPortPOD") - for belport in wire_downhill_belports[wire]: + for belport in sorted(wire_downhill_belports[wire]): bba.u32(belport[0], "bel_index") bba.u32(portpins[belport[1]], "port") else: @@ -1040,7 +1062,7 @@ for t in range(num_tile_types): tileinfo.append(ti) bba.l("wire_data_%s" % dev_name, "WireInfoPOD") -for info in wireinfo: +for wire, info in enumerate(wireinfo): bba.s(info["name"], "name") bba.u32(info["num_uphill"], "num_uphill") bba.u32(info["num_downhill"], "num_downhill") @@ -1050,11 +1072,24 @@ for info in wireinfo: bba.u32(info["uphill_bel"], "bel_uphill.bel_index") bba.u32(info["uphill_pin"], "bel_uphill.port") bba.r(info["list_bels_downhill"], "bels_downhill") + bba.u32(len(wire_segments[wire]), "num_segments") + if len(wire_segments[wire]): + bba.r("wire_segments_%d" % wire, "segments") + else: + bba.u32(0, "segments") bba.u8(info["x"], "x") bba.u8(info["y"], "y") bba.u8(wiretypes[wire_type(info["name"])], "type") bba.u8(0, "padding") +for wire in range(num_wires): + if len(wire_segments[wire]): + bba.l("wire_segments_%d" % wire, "WireSegmentPOD") + for seg in sorted(wire_segments[wire]): + bba.u8(seg[0], "x") + bba.u8(seg[1], "y") + bba.u16(seg[2], "index") + bba.l("pip_data_%s" % dev_name, "PipInfoPOD") for info in pipinfo: bba.u32(info["src"], "src") diff --git a/ice40/family.cmake b/ice40/family.cmake index e6cefecb..9af06f82 100644 --- a/ice40/family.cmake +++ b/ice40/family.cmake @@ -21,8 +21,9 @@ if (MSVC) set(DEV_TXT_DB ${ICEBOX_ROOT}/chipdb-${dev}.txt) set(DEV_CC_DB ${CMAKE_CURRENT_SOURCE_DIR}/ice40/chipdbs/chipdb-${dev}.bin) set(DEV_PORTS_INC ${CMAKE_CURRENT_SOURCE_DIR}/ice40/portpins.inc) + set(DEV_GFXH ${CMAKE_CURRENT_SOURCE_DIR}/ice40/gfx.h) add_custom_command(OUTPUT ${DEV_CC_DB} - COMMAND ${PYTHON_EXECUTABLE} ${DB_PY} -b -p ${DEV_PORTS_INC} ${DEV_TXT_DB} > ${DEV_CC_DB} + COMMAND ${PYTHON_EXECUTABLE} ${DB_PY} -b -p ${DEV_PORTS_INC} -g ${DEV_GFXH} ${DEV_TXT_DB} > ${DEV_CC_DB} DEPENDS ${DEV_TXT_DB} ${DB_PY} ) target_sources(ice40_chipdb PRIVATE ${DEV_CC_DB}) @@ -37,8 +38,9 @@ else() set(DEV_TXT_DB ${ICEBOX_ROOT}/chipdb-${dev}.txt) set(DEV_CC_DB ${CMAKE_CURRENT_SOURCE_DIR}/ice40/chipdbs/chipdb-${dev}.cc) set(DEV_PORTS_INC ${CMAKE_CURRENT_SOURCE_DIR}/ice40/portpins.inc) + set(DEV_GFXH ${CMAKE_CURRENT_SOURCE_DIR}/ice40/gfx.h) add_custom_command(OUTPUT ${DEV_CC_DB} - COMMAND ${PYTHON_EXECUTABLE} ${DB_PY} -c -p ${DEV_PORTS_INC} ${DEV_TXT_DB} > ${DEV_CC_DB}.new + COMMAND ${PYTHON_EXECUTABLE} ${DB_PY} -c -p ${DEV_PORTS_INC} -g ${DEV_GFXH} ${DEV_TXT_DB} > ${DEV_CC_DB}.new COMMAND mv ${DEV_CC_DB}.new ${DEV_CC_DB} DEPENDS ${DEV_TXT_DB} ${DB_PY} ) diff --git a/ice40/gfx.cc b/ice40/gfx.cc new file mode 100644 index 00000000..19aaed13 --- /dev/null +++ b/ice40/gfx.cc @@ -0,0 +1,488 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Clifford Wolf + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "gfx.h" + +NEXTPNR_NAMESPACE_BEGIN + +void gfxTileWire(std::vector &g, int x, int y, GfxTileWireId id, GraphicElement::style_t style) +{ + GraphicElement el; + el.type = GraphicElement::G_LINE; + el.style = style; + + // Horizontal Span-4 Wires + + if (id >= TILE_WIRE_SP4_H_L_36 && id <= TILE_WIRE_SP4_H_L_47) { + int idx = (id - TILE_WIRE_SP4_H_L_36) + 48; + + float y1 = y + 1.0 - (0.03 + 0.0025 * (60 - (idx ^ 1))); + float y2 = y + 1.0 - (0.03 + 0.0025 * (60 - idx)); + + el.x1 = x; + el.x2 = x + 0.01; + el.y1 = y1; + el.y2 = y1; + g.push_back(el); + + el.x1 = x + 0.01; + el.x2 = x + 0.02; + el.y1 = y1; + el.y2 = y2; + g.push_back(el); + + el.x1 = x + 0.02; + el.x2 = x + 0.9; + el.y1 = y2; + el.y2 = y2; + g.push_back(el); + + el.x1 = x + main_swbox_x1 + 0.0025 * (idx + 35); + el.x2 = el.x1; + el.y1 = y2; + el.y2 = y + main_swbox_y2; + g.push_back(el); + } + + if (id >= TILE_WIRE_SP4_H_R_0 && id <= TILE_WIRE_SP4_H_R_47) { + int idx = id - TILE_WIRE_SP4_H_R_0; + + float y1 = y + 1.0 - (0.03 + 0.0025 * (60 - idx)); + float y2 = y + 1.0 - (0.03 + 0.0025 * (60 - (idx ^ 1))); + float y3 = y + 1.0 - (0.03 + 0.0025 * (60 - (idx ^ 1) - 12)); + + if (idx >= 12) { + el.x1 = x; + el.x2 = x + 0.01; + el.y1 = y1; + el.y2 = y1; + g.push_back(el); + + el.x1 = x + 0.01; + el.x2 = x + 0.02; + el.y1 = y1; + el.y2 = y2; + g.push_back(el); + } + + el.x1 = x + 0.02; + el.x2 = x + 0.9; + el.y1 = y2; + el.y2 = y2; + g.push_back(el); + + el.x1 = x + 0.9; + el.x2 = x + 1.0; + el.y1 = y2; + el.y2 = y3; + g.push_back(el); + + el.x1 = x + main_swbox_x1 + 0.0025 * ((idx ^ 1) + 35); + el.x2 = el.x1; + el.y1 = y2; + el.y2 = y + main_swbox_y2; + g.push_back(el); + } + + // Vertical Span-4 Wires + + if (id >= TILE_WIRE_SP4_V_T_36 && id <= TILE_WIRE_SP4_V_T_47) { + int idx = (id - TILE_WIRE_SP4_V_T_36) + 48; + + float x1 = x + 0.03 + 0.0025 * (60 - (idx ^ 1)); + float x2 = x + 0.03 + 0.0025 * (60 - idx); + + el.y1 = y + 1.00; + el.y2 = y + 0.99; + el.x1 = x1; + el.x2 = x1; + g.push_back(el); + + el.y1 = y + 0.99; + el.y2 = y + 0.98; + el.x1 = x1; + el.x2 = x2; + g.push_back(el); + + el.y1 = y + 0.98; + el.y2 = y + 0.10; + el.x1 = x2; + el.x2 = x2; + g.push_back(el); + + el.y1 = y + 1.0 - (0.03 + 0.0025 * (270 - idx)); + el.y2 = el.y1; + el.x1 = x2; + el.x2 = x + main_swbox_x1; + g.push_back(el); + } + + if (id >= TILE_WIRE_SP4_V_B_0 && id <= TILE_WIRE_SP4_V_B_47) { + int idx = id - TILE_WIRE_SP4_V_B_0; + + float x1 = x + 0.03 + 0.0025 * (60 - idx); + float x2 = x + 0.03 + 0.0025 * (60 - (idx ^ 1)); + float x3 = x + 0.03 + 0.0025 * (60 - (idx ^ 1) - 12); + + if (idx >= 12) { + el.y1 = y + 1.00; + el.y2 = y + 0.99; + el.x1 = x1; + el.x2 = x1; + g.push_back(el); + + el.y1 = y + 0.99; + el.y2 = y + 0.98; + el.x1 = x1; + el.x2 = x2; + g.push_back(el); + } + + el.y1 = y + 0.98; + el.y2 = y + 0.10; + el.x1 = x2; + el.x2 = x2; + g.push_back(el); + + el.y1 = y + 0.10; + el.y2 = y; + el.x1 = x2; + el.x2 = x3; + g.push_back(el); + + el.y1 = y + 1.0 - (0.03 + 0.0025 * (145 - (idx ^ 1))); + el.y2 = el.y1; + el.x1 = x; + el.x2 = x2; + g.push_back(el); + + el.y1 = y + 1.0 - (0.03 + 0.0025 * (270 - (idx ^ 1))); + el.y2 = el.y1; + el.x1 = x2; + el.x2 = x + main_swbox_x1; + g.push_back(el); + } + + // Horizontal Span-12 Wires + + if (id >= TILE_WIRE_SP12_H_L_22 && id <= TILE_WIRE_SP12_H_L_23) { + int idx = (id - TILE_WIRE_SP12_H_L_22) + 24; + + float y1 = y + 1.0 - (0.03 + 0.0025 * (90 - (idx ^ 1))); + float y2 = y + 1.0 - (0.03 + 0.0025 * (90 - idx)); + + el.x1 = x; + el.x2 = x + 0.01; + el.y1 = y1; + el.y2 = y1; + g.push_back(el); + + el.x1 = x + 0.01; + el.x2 = x + 0.02; + el.y1 = y1; + el.y2 = y2; + g.push_back(el); + + el.x1 = x + 0.02; + el.x2 = x + 0.98333; + el.y1 = y2; + el.y2 = y2; + g.push_back(el); + + el.x1 = x + main_swbox_x1 + 0.0025 * (idx + 5); + el.x2 = el.x1; + el.y1 = y2; + el.y2 = y + main_swbox_y2; + g.push_back(el); + } + + if (id >= TILE_WIRE_SP12_H_R_0 && id <= TILE_WIRE_SP12_H_R_23) { + int idx = id - TILE_WIRE_SP12_H_R_0; + + float y1 = y + 1.0 - (0.03 + 0.0025 * (90 - idx)); + float y2 = y + 1.0 - (0.03 + 0.0025 * (90 - (idx ^ 1))); + float y3 = y + 1.0 - (0.03 + 0.0025 * (90 - (idx ^ 1) - 2)); + + if (idx >= 2) { + el.x1 = x; + el.x2 = x + 0.01; + el.y1 = y1; + el.y2 = y1; + g.push_back(el); + + el.x1 = x + 0.01; + el.x2 = x + 0.02; + el.y1 = y1; + el.y2 = y2; + g.push_back(el); + } + + el.x1 = x + 0.02; + el.x2 = x + 0.98333; + el.y1 = y2; + el.y2 = y2; + g.push_back(el); + + el.x1 = x + 0.98333; + el.x2 = x + 1.0; + el.y1 = y2; + el.y2 = y3; + g.push_back(el); + + el.x1 = x + main_swbox_x1 + 0.0025 * ((idx ^ 1) + 5); + el.x2 = el.x1; + el.y1 = y2; + el.y2 = y + main_swbox_y2; + g.push_back(el); + } + + // Vertical Right Span-4 + + if (id >= TILE_WIRE_SP4_R_V_B_0 && id <= TILE_WIRE_SP4_R_V_B_47) { + int idx = id - TILE_WIRE_SP4_R_V_B_0; + + float y1 = y + 1.0 - (0.03 + 0.0025 * (145 - (idx ^ 1))); + + el.y1 = y1; + el.y2 = y1; + el.x1 = x + main_swbox_x2; + el.x2 = x + 1.0; + g.push_back(el); + } + + // Vertical Span-12 Wires + + if (id >= TILE_WIRE_SP12_V_T_22 && id <= TILE_WIRE_SP12_V_T_23) { + int idx = (id - TILE_WIRE_SP12_V_T_22) + 24; + + float x1 = x + 0.03 + 0.0025 * (90 - (idx ^ 1)); + float x2 = x + 0.03 + 0.0025 * (90 - idx); + + el.y1 = y + 1.00; + el.y2 = y + 0.99; + el.x1 = x1; + el.x2 = x1; + g.push_back(el); + + el.y1 = y + 0.99; + el.y2 = y + 0.98; + el.x1 = x1; + el.x2 = x2; + g.push_back(el); + + el.y1 = y + 0.98; + el.y2 = y + 0.01667; + el.x1 = x2; + el.x2 = x2; + g.push_back(el); + + el.y1 = y + 1.0 - (0.03 + 0.0025 * (300 - idx)); + el.y2 = el.y1; + el.x1 = x2; + el.x2 = x + main_swbox_x1; + g.push_back(el); + } + + if (id >= TILE_WIRE_SP12_V_B_0 && id <= TILE_WIRE_SP12_V_B_23) { + int idx = id - TILE_WIRE_SP12_V_B_0; + + float x1 = x + 0.03 + 0.0025 * (90 - idx); + float x2 = x + 0.03 + 0.0025 * (90 - (idx ^ 1)); + float x3 = x + 0.03 + 0.0025 * (90 - (idx ^ 1) - 2); + + if (idx >= 2) { + el.y1 = y + 1.00; + el.y2 = y + 0.99; + el.x1 = x1; + el.x2 = x1; + g.push_back(el); + + el.y1 = y + 0.99; + el.y2 = y + 0.98; + el.x1 = x1; + el.x2 = x2; + g.push_back(el); + } + + el.y1 = y + 0.98; + el.y2 = y + 0.01667; + el.x1 = x2; + el.x2 = x2; + g.push_back(el); + + el.y1 = y + 0.01667; + el.y2 = y; + el.x1 = x2; + el.x2 = x3; + g.push_back(el); + + el.y1 = y + 1.0 - (0.03 + 0.0025 * (300 - (idx ^ 1))); + el.y2 = el.y1; + el.x1 = x2; + el.x2 = x + main_swbox_x1; + g.push_back(el); + } + + // Global2Local + + if (id >= TILE_WIRE_GLB2LOCAL_0 && id <= TILE_WIRE_GLB2LOCAL_3) { + int idx = id - TILE_WIRE_GLB2LOCAL_0; + el.x1 = x + main_swbox_x1 + 0.005 * (idx + 5); + el.x2 = el.x1; + el.y1 = y + main_swbox_y1; + el.y2 = el.y1 - 0.02; + g.push_back(el); + } + + // GlobalNets + + if (id >= TILE_WIRE_GLB_NETWK_0 && id <= TILE_WIRE_GLB_NETWK_7) { + int idx = id - TILE_WIRE_GLB_NETWK_0; + el.x1 = x + main_swbox_x1 - 0.05; + el.x2 = x + main_swbox_x1; + el.y1 = y + main_swbox_y1 + 0.005 * (13 - idx); + el.y2 = el.y1; + g.push_back(el); + } + + // Neighbours + + if (id >= TILE_WIRE_NEIGH_OP_BNL_0 && id <= TILE_WIRE_NEIGH_OP_TOP_7) { + int idx = id - TILE_WIRE_NEIGH_OP_BNL_0; + el.y1 = y + main_swbox_y2 - (0.0025 * (idx + 10) + 0.01 * (idx / 8)); + el.y2 = el.y1; + el.x1 = x + main_swbox_x1 - 0.05; + el.x2 = x + main_swbox_x1; + g.push_back(el); + } + + // Local Tracks + + if (id >= TILE_WIRE_LOCAL_G0_0 && id <= TILE_WIRE_LOCAL_G3_7) { + int idx = id - TILE_WIRE_LOCAL_G0_0; + el.x1 = x + main_swbox_x2; + el.x2 = x + local_swbox_x1; + float yoff = y + (local_swbox_y1 + local_swbox_y2) / 2 - 0.005 * 16 - 0.075; + el.y1 = yoff + 0.005 * idx + 0.05 * (idx / 8); + el.y2 = el.y1; + g.push_back(el); + } + + // LC Inputs + + if (id >= TILE_WIRE_LUTFF_0_IN_0 && id <= TILE_WIRE_LUTFF_7_IN_3) { + int idx = id - TILE_WIRE_LUTFF_0_IN_0; + int z = idx / 4; + int input = idx % 4; + el.x1 = x + local_swbox_x2; + el.x2 = x + logic_cell_x1; + el.y1 = y + (logic_cell_y1 + logic_cell_y2) / 2 - 0.0075 + (0.005 * input) + z * logic_cell_pitch; + el.y2 = el.y1; + g.push_back(el); + } + + // LC Outputs + + if (id >= TILE_WIRE_LUTFF_0_OUT && id <= TILE_WIRE_LUTFF_7_OUT) { + int idx = id - TILE_WIRE_LUTFF_0_OUT; + + float y1 = y + 1.0 - (0.03 + 0.0025 * (152 + idx)); + + el.y1 = y1; + el.y2 = y1; + el.x1 = x + main_swbox_x2; + el.x2 = x + 0.97 + 0.0025 * (7 - idx); + g.push_back(el); + + el.y1 = y1; + el.y2 = y + (logic_cell_y1 + logic_cell_y2) / 2 + idx * logic_cell_pitch; + el.x1 = el.x2; + g.push_back(el); + + el.y1 = el.y2; + el.x1 = x + logic_cell_x2; + g.push_back(el); + } + + // LC Control + + if (id >= TILE_WIRE_LUTFF_GLOBAL_CEN && id <= TILE_WIRE_LUTFF_GLOBAL_S_R) { + int idx = id - TILE_WIRE_LUTFF_GLOBAL_CEN; + + el.x1 = x + main_swbox_x2 - 0.005 * (idx + 5); + el.x2 = el.x1; + el.y1 = y + main_swbox_y1; + el.y2 = el.y1 - 0.005 * (idx + 2); + g.push_back(el); + + el.y1 = el.y2; + el.x2 = x + logic_cell_x2 - 0.005 * (2 - idx + 5); + g.push_back(el); + + el.y2 = y + logic_cell_y1; + el.x1 = el.x2; + g.push_back(el); + + for (int i = 0; i < 7; i++) { + el.y1 = y + logic_cell_y2 + i * logic_cell_pitch; + el.y2 = y + logic_cell_y1 + (i + 1) * logic_cell_pitch; + g.push_back(el); + } + } + + // LC Cascade + + if (id >= TILE_WIRE_LUTFF_0_LOUT && id <= TILE_WIRE_LUTFF_6_LOUT) { + int idx = id - TILE_WIRE_LUTFF_0_LOUT; + el.x1 = x + logic_cell_x1 + 0.005 * 5; + el.x2 = el.x1; + el.y1 = y + logic_cell_y2 + idx * logic_cell_pitch; + el.y2 = y + logic_cell_y1 + (idx + 1) * logic_cell_pitch; + g.push_back(el); + } + + // Carry Chain + + if (id >= TILE_WIRE_LUTFF_0_COUT && id <= TILE_WIRE_LUTFF_7_COUT) { + int idx = id - TILE_WIRE_LUTFF_0_COUT; + el.x1 = x + logic_cell_x1 + 0.005 * 3; + el.x2 = el.x1; + el.y1 = y + logic_cell_y2 + idx * logic_cell_pitch; + el.y2 = y + (idx < 7 ? logic_cell_y1 + (idx + 1) * logic_cell_pitch : 1.0); + g.push_back(el); + } + + if (id == TILE_WIRE_CARRY_IN) { + el.x1 = x + logic_cell_x1 + 0.005 * 3; + el.x2 = el.x1; + el.y1 = y; + el.y2 = y + 0.01; + g.push_back(el); + } + + if (id == TILE_WIRE_CARRY_IN_MUX) { + el.x1 = x + logic_cell_x1 + 0.005 * 3; + el.x2 = el.x1; + el.y1 = y + 0.02; + el.y2 = y + logic_cell_y1; + g.push_back(el); + } +} + +NEXTPNR_NAMESPACE_END diff --git a/ice40/gfx.h b/ice40/gfx.h new file mode 100644 index 00000000..a65f7683 --- /dev/null +++ b/ice40/gfx.h @@ -0,0 +1,474 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Clifford Wolf + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#ifndef GFX_H +#define GFX_H + +#include "nextpnr.h" + +NEXTPNR_NAMESPACE_BEGIN + +const float main_swbox_x1 = 0.35; +const float main_swbox_x2 = 0.60; +const float main_swbox_y1 = 0.05; +const float main_swbox_y2 = 0.73; + +const float local_swbox_x1 = 0.63; +const float local_swbox_x2 = 0.73; +const float local_swbox_y1 = 0.05; +const float local_swbox_y2 = 0.55; + +const float logic_cell_x1 = 0.76; +const float logic_cell_x2 = 0.95; +const float logic_cell_y1 = 0.05; +const float logic_cell_y2 = 0.10; +const float logic_cell_pitch = 0.0625; + +enum GfxTileWireId +{ + TILE_WIRE_GLB2LOCAL_0, + TILE_WIRE_GLB2LOCAL_1, + TILE_WIRE_GLB2LOCAL_2, + TILE_WIRE_GLB2LOCAL_3, + + TILE_WIRE_GLB_NETWK_0, + TILE_WIRE_GLB_NETWK_1, + TILE_WIRE_GLB_NETWK_2, + TILE_WIRE_GLB_NETWK_3, + TILE_WIRE_GLB_NETWK_4, + TILE_WIRE_GLB_NETWK_5, + TILE_WIRE_GLB_NETWK_6, + TILE_WIRE_GLB_NETWK_7, + + TILE_WIRE_LOCAL_G0_0, + TILE_WIRE_LOCAL_G0_1, + TILE_WIRE_LOCAL_G0_2, + TILE_WIRE_LOCAL_G0_3, + TILE_WIRE_LOCAL_G0_4, + TILE_WIRE_LOCAL_G0_5, + TILE_WIRE_LOCAL_G0_6, + TILE_WIRE_LOCAL_G0_7, + + TILE_WIRE_LOCAL_G1_0, + TILE_WIRE_LOCAL_G1_1, + TILE_WIRE_LOCAL_G1_2, + TILE_WIRE_LOCAL_G1_3, + TILE_WIRE_LOCAL_G1_4, + TILE_WIRE_LOCAL_G1_5, + TILE_WIRE_LOCAL_G1_6, + TILE_WIRE_LOCAL_G1_7, + + TILE_WIRE_LOCAL_G2_0, + TILE_WIRE_LOCAL_G2_1, + TILE_WIRE_LOCAL_G2_2, + TILE_WIRE_LOCAL_G2_3, + TILE_WIRE_LOCAL_G2_4, + TILE_WIRE_LOCAL_G2_5, + TILE_WIRE_LOCAL_G2_6, + TILE_WIRE_LOCAL_G2_7, + + TILE_WIRE_LOCAL_G3_0, + TILE_WIRE_LOCAL_G3_1, + TILE_WIRE_LOCAL_G3_2, + TILE_WIRE_LOCAL_G3_3, + TILE_WIRE_LOCAL_G3_4, + TILE_WIRE_LOCAL_G3_5, + TILE_WIRE_LOCAL_G3_6, + TILE_WIRE_LOCAL_G3_7, + + TILE_WIRE_CARRY_IN, + TILE_WIRE_CARRY_IN_MUX, + + TILE_WIRE_LUTFF_0_IN_0, + TILE_WIRE_LUTFF_0_IN_1, + TILE_WIRE_LUTFF_0_IN_2, + TILE_WIRE_LUTFF_0_IN_3, + + TILE_WIRE_LUTFF_1_IN_0, + TILE_WIRE_LUTFF_1_IN_1, + TILE_WIRE_LUTFF_1_IN_2, + TILE_WIRE_LUTFF_1_IN_3, + + TILE_WIRE_LUTFF_2_IN_0, + TILE_WIRE_LUTFF_2_IN_1, + TILE_WIRE_LUTFF_2_IN_2, + TILE_WIRE_LUTFF_2_IN_3, + + TILE_WIRE_LUTFF_3_IN_0, + TILE_WIRE_LUTFF_3_IN_1, + TILE_WIRE_LUTFF_3_IN_2, + TILE_WIRE_LUTFF_3_IN_3, + + TILE_WIRE_LUTFF_4_IN_0, + TILE_WIRE_LUTFF_4_IN_1, + TILE_WIRE_LUTFF_4_IN_2, + TILE_WIRE_LUTFF_4_IN_3, + + TILE_WIRE_LUTFF_5_IN_0, + TILE_WIRE_LUTFF_5_IN_1, + TILE_WIRE_LUTFF_5_IN_2, + TILE_WIRE_LUTFF_5_IN_3, + + TILE_WIRE_LUTFF_6_IN_0, + TILE_WIRE_LUTFF_6_IN_1, + TILE_WIRE_LUTFF_6_IN_2, + TILE_WIRE_LUTFF_6_IN_3, + + TILE_WIRE_LUTFF_7_IN_0, + TILE_WIRE_LUTFF_7_IN_1, + TILE_WIRE_LUTFF_7_IN_2, + TILE_WIRE_LUTFF_7_IN_3, + + TILE_WIRE_LUTFF_0_LOUT, + TILE_WIRE_LUTFF_1_LOUT, + TILE_WIRE_LUTFF_2_LOUT, + TILE_WIRE_LUTFF_3_LOUT, + TILE_WIRE_LUTFF_4_LOUT, + TILE_WIRE_LUTFF_5_LOUT, + TILE_WIRE_LUTFF_6_LOUT, + + TILE_WIRE_LUTFF_0_OUT, + TILE_WIRE_LUTFF_1_OUT, + TILE_WIRE_LUTFF_2_OUT, + TILE_WIRE_LUTFF_3_OUT, + TILE_WIRE_LUTFF_4_OUT, + TILE_WIRE_LUTFF_5_OUT, + TILE_WIRE_LUTFF_6_OUT, + TILE_WIRE_LUTFF_7_OUT, + + TILE_WIRE_LUTFF_0_COUT, + TILE_WIRE_LUTFF_1_COUT, + TILE_WIRE_LUTFF_2_COUT, + TILE_WIRE_LUTFF_3_COUT, + TILE_WIRE_LUTFF_4_COUT, + TILE_WIRE_LUTFF_5_COUT, + TILE_WIRE_LUTFF_6_COUT, + TILE_WIRE_LUTFF_7_COUT, + + TILE_WIRE_LUTFF_GLOBAL_CEN, + TILE_WIRE_LUTFF_GLOBAL_CLK, + TILE_WIRE_LUTFF_GLOBAL_S_R, + + TILE_WIRE_NEIGH_OP_BNL_0, + TILE_WIRE_NEIGH_OP_BNL_1, + TILE_WIRE_NEIGH_OP_BNL_2, + TILE_WIRE_NEIGH_OP_BNL_3, + TILE_WIRE_NEIGH_OP_BNL_4, + TILE_WIRE_NEIGH_OP_BNL_5, + TILE_WIRE_NEIGH_OP_BNL_6, + TILE_WIRE_NEIGH_OP_BNL_7, + + TILE_WIRE_NEIGH_OP_BNR_0, + TILE_WIRE_NEIGH_OP_BNR_1, + TILE_WIRE_NEIGH_OP_BNR_2, + TILE_WIRE_NEIGH_OP_BNR_3, + TILE_WIRE_NEIGH_OP_BNR_4, + TILE_WIRE_NEIGH_OP_BNR_5, + TILE_WIRE_NEIGH_OP_BNR_6, + TILE_WIRE_NEIGH_OP_BNR_7, + + TILE_WIRE_NEIGH_OP_BOT_0, + TILE_WIRE_NEIGH_OP_BOT_1, + TILE_WIRE_NEIGH_OP_BOT_2, + TILE_WIRE_NEIGH_OP_BOT_3, + TILE_WIRE_NEIGH_OP_BOT_4, + TILE_WIRE_NEIGH_OP_BOT_5, + TILE_WIRE_NEIGH_OP_BOT_6, + TILE_WIRE_NEIGH_OP_BOT_7, + + TILE_WIRE_NEIGH_OP_LFT_0, + TILE_WIRE_NEIGH_OP_LFT_1, + TILE_WIRE_NEIGH_OP_LFT_2, + TILE_WIRE_NEIGH_OP_LFT_3, + TILE_WIRE_NEIGH_OP_LFT_4, + TILE_WIRE_NEIGH_OP_LFT_5, + TILE_WIRE_NEIGH_OP_LFT_6, + TILE_WIRE_NEIGH_OP_LFT_7, + + TILE_WIRE_NEIGH_OP_RGT_0, + TILE_WIRE_NEIGH_OP_RGT_1, + TILE_WIRE_NEIGH_OP_RGT_2, + TILE_WIRE_NEIGH_OP_RGT_3, + TILE_WIRE_NEIGH_OP_RGT_4, + TILE_WIRE_NEIGH_OP_RGT_5, + TILE_WIRE_NEIGH_OP_RGT_6, + TILE_WIRE_NEIGH_OP_RGT_7, + + TILE_WIRE_NEIGH_OP_TNL_0, + TILE_WIRE_NEIGH_OP_TNL_1, + TILE_WIRE_NEIGH_OP_TNL_2, + TILE_WIRE_NEIGH_OP_TNL_3, + TILE_WIRE_NEIGH_OP_TNL_4, + TILE_WIRE_NEIGH_OP_TNL_5, + TILE_WIRE_NEIGH_OP_TNL_6, + TILE_WIRE_NEIGH_OP_TNL_7, + + TILE_WIRE_NEIGH_OP_TNR_0, + TILE_WIRE_NEIGH_OP_TNR_1, + TILE_WIRE_NEIGH_OP_TNR_2, + TILE_WIRE_NEIGH_OP_TNR_3, + TILE_WIRE_NEIGH_OP_TNR_4, + TILE_WIRE_NEIGH_OP_TNR_5, + TILE_WIRE_NEIGH_OP_TNR_6, + TILE_WIRE_NEIGH_OP_TNR_7, + + TILE_WIRE_NEIGH_OP_TOP_0, + TILE_WIRE_NEIGH_OP_TOP_1, + TILE_WIRE_NEIGH_OP_TOP_2, + TILE_WIRE_NEIGH_OP_TOP_3, + TILE_WIRE_NEIGH_OP_TOP_4, + TILE_WIRE_NEIGH_OP_TOP_5, + TILE_WIRE_NEIGH_OP_TOP_6, + TILE_WIRE_NEIGH_OP_TOP_7, + + TILE_WIRE_SP4_V_B_0, + TILE_WIRE_SP4_V_B_1, + TILE_WIRE_SP4_V_B_2, + TILE_WIRE_SP4_V_B_3, + TILE_WIRE_SP4_V_B_4, + TILE_WIRE_SP4_V_B_5, + TILE_WIRE_SP4_V_B_6, + TILE_WIRE_SP4_V_B_7, + TILE_WIRE_SP4_V_B_8, + TILE_WIRE_SP4_V_B_9, + TILE_WIRE_SP4_V_B_10, + TILE_WIRE_SP4_V_B_11, + TILE_WIRE_SP4_V_B_12, + TILE_WIRE_SP4_V_B_13, + TILE_WIRE_SP4_V_B_14, + TILE_WIRE_SP4_V_B_15, + TILE_WIRE_SP4_V_B_16, + TILE_WIRE_SP4_V_B_17, + TILE_WIRE_SP4_V_B_18, + TILE_WIRE_SP4_V_B_19, + TILE_WIRE_SP4_V_B_20, + TILE_WIRE_SP4_V_B_21, + TILE_WIRE_SP4_V_B_22, + TILE_WIRE_SP4_V_B_23, + TILE_WIRE_SP4_V_B_24, + TILE_WIRE_SP4_V_B_25, + TILE_WIRE_SP4_V_B_26, + TILE_WIRE_SP4_V_B_27, + TILE_WIRE_SP4_V_B_28, + TILE_WIRE_SP4_V_B_29, + TILE_WIRE_SP4_V_B_30, + TILE_WIRE_SP4_V_B_31, + TILE_WIRE_SP4_V_B_32, + TILE_WIRE_SP4_V_B_33, + TILE_WIRE_SP4_V_B_34, + TILE_WIRE_SP4_V_B_35, + TILE_WIRE_SP4_V_B_36, + TILE_WIRE_SP4_V_B_37, + TILE_WIRE_SP4_V_B_38, + TILE_WIRE_SP4_V_B_39, + TILE_WIRE_SP4_V_B_40, + TILE_WIRE_SP4_V_B_41, + TILE_WIRE_SP4_V_B_42, + TILE_WIRE_SP4_V_B_43, + TILE_WIRE_SP4_V_B_44, + TILE_WIRE_SP4_V_B_45, + TILE_WIRE_SP4_V_B_46, + TILE_WIRE_SP4_V_B_47, + + TILE_WIRE_SP4_V_T_36, + TILE_WIRE_SP4_V_T_37, + TILE_WIRE_SP4_V_T_38, + TILE_WIRE_SP4_V_T_39, + TILE_WIRE_SP4_V_T_40, + TILE_WIRE_SP4_V_T_41, + TILE_WIRE_SP4_V_T_42, + TILE_WIRE_SP4_V_T_43, + TILE_WIRE_SP4_V_T_44, + TILE_WIRE_SP4_V_T_45, + TILE_WIRE_SP4_V_T_46, + TILE_WIRE_SP4_V_T_47, + + TILE_WIRE_SP4_R_V_B_0, + TILE_WIRE_SP4_R_V_B_1, + TILE_WIRE_SP4_R_V_B_2, + TILE_WIRE_SP4_R_V_B_3, + TILE_WIRE_SP4_R_V_B_4, + TILE_WIRE_SP4_R_V_B_5, + TILE_WIRE_SP4_R_V_B_6, + TILE_WIRE_SP4_R_V_B_7, + TILE_WIRE_SP4_R_V_B_8, + TILE_WIRE_SP4_R_V_B_9, + TILE_WIRE_SP4_R_V_B_10, + TILE_WIRE_SP4_R_V_B_11, + TILE_WIRE_SP4_R_V_B_12, + TILE_WIRE_SP4_R_V_B_13, + TILE_WIRE_SP4_R_V_B_14, + TILE_WIRE_SP4_R_V_B_15, + TILE_WIRE_SP4_R_V_B_16, + TILE_WIRE_SP4_R_V_B_17, + TILE_WIRE_SP4_R_V_B_18, + TILE_WIRE_SP4_R_V_B_19, + TILE_WIRE_SP4_R_V_B_20, + TILE_WIRE_SP4_R_V_B_21, + TILE_WIRE_SP4_R_V_B_22, + TILE_WIRE_SP4_R_V_B_23, + TILE_WIRE_SP4_R_V_B_24, + TILE_WIRE_SP4_R_V_B_25, + TILE_WIRE_SP4_R_V_B_26, + TILE_WIRE_SP4_R_V_B_27, + TILE_WIRE_SP4_R_V_B_28, + TILE_WIRE_SP4_R_V_B_29, + TILE_WIRE_SP4_R_V_B_30, + TILE_WIRE_SP4_R_V_B_31, + TILE_WIRE_SP4_R_V_B_32, + TILE_WIRE_SP4_R_V_B_33, + TILE_WIRE_SP4_R_V_B_34, + TILE_WIRE_SP4_R_V_B_35, + TILE_WIRE_SP4_R_V_B_36, + TILE_WIRE_SP4_R_V_B_37, + TILE_WIRE_SP4_R_V_B_38, + TILE_WIRE_SP4_R_V_B_39, + TILE_WIRE_SP4_R_V_B_40, + TILE_WIRE_SP4_R_V_B_41, + TILE_WIRE_SP4_R_V_B_42, + TILE_WIRE_SP4_R_V_B_43, + TILE_WIRE_SP4_R_V_B_44, + TILE_WIRE_SP4_R_V_B_45, + TILE_WIRE_SP4_R_V_B_46, + TILE_WIRE_SP4_R_V_B_47, + + TILE_WIRE_SP4_H_L_36, + TILE_WIRE_SP4_H_L_37, + TILE_WIRE_SP4_H_L_38, + TILE_WIRE_SP4_H_L_39, + TILE_WIRE_SP4_H_L_40, + TILE_WIRE_SP4_H_L_41, + TILE_WIRE_SP4_H_L_42, + TILE_WIRE_SP4_H_L_43, + TILE_WIRE_SP4_H_L_44, + TILE_WIRE_SP4_H_L_45, + TILE_WIRE_SP4_H_L_46, + TILE_WIRE_SP4_H_L_47, + + TILE_WIRE_SP4_H_R_0, + TILE_WIRE_SP4_H_R_1, + TILE_WIRE_SP4_H_R_2, + TILE_WIRE_SP4_H_R_3, + TILE_WIRE_SP4_H_R_4, + TILE_WIRE_SP4_H_R_5, + TILE_WIRE_SP4_H_R_6, + TILE_WIRE_SP4_H_R_7, + TILE_WIRE_SP4_H_R_8, + TILE_WIRE_SP4_H_R_9, + TILE_WIRE_SP4_H_R_10, + TILE_WIRE_SP4_H_R_11, + TILE_WIRE_SP4_H_R_12, + TILE_WIRE_SP4_H_R_13, + TILE_WIRE_SP4_H_R_14, + TILE_WIRE_SP4_H_R_15, + TILE_WIRE_SP4_H_R_16, + TILE_WIRE_SP4_H_R_17, + TILE_WIRE_SP4_H_R_18, + TILE_WIRE_SP4_H_R_19, + TILE_WIRE_SP4_H_R_20, + TILE_WIRE_SP4_H_R_21, + TILE_WIRE_SP4_H_R_22, + TILE_WIRE_SP4_H_R_23, + TILE_WIRE_SP4_H_R_24, + TILE_WIRE_SP4_H_R_25, + TILE_WIRE_SP4_H_R_26, + TILE_WIRE_SP4_H_R_27, + TILE_WIRE_SP4_H_R_28, + TILE_WIRE_SP4_H_R_29, + TILE_WIRE_SP4_H_R_30, + TILE_WIRE_SP4_H_R_31, + TILE_WIRE_SP4_H_R_32, + TILE_WIRE_SP4_H_R_33, + TILE_WIRE_SP4_H_R_34, + TILE_WIRE_SP4_H_R_35, + TILE_WIRE_SP4_H_R_36, + TILE_WIRE_SP4_H_R_37, + TILE_WIRE_SP4_H_R_38, + TILE_WIRE_SP4_H_R_39, + TILE_WIRE_SP4_H_R_40, + TILE_WIRE_SP4_H_R_41, + TILE_WIRE_SP4_H_R_42, + TILE_WIRE_SP4_H_R_43, + TILE_WIRE_SP4_H_R_44, + TILE_WIRE_SP4_H_R_45, + TILE_WIRE_SP4_H_R_46, + TILE_WIRE_SP4_H_R_47, + + TILE_WIRE_SP12_V_B_0, + TILE_WIRE_SP12_V_B_1, + TILE_WIRE_SP12_V_B_2, + TILE_WIRE_SP12_V_B_3, + TILE_WIRE_SP12_V_B_4, + TILE_WIRE_SP12_V_B_5, + TILE_WIRE_SP12_V_B_6, + TILE_WIRE_SP12_V_B_7, + TILE_WIRE_SP12_V_B_8, + TILE_WIRE_SP12_V_B_9, + TILE_WIRE_SP12_V_B_10, + TILE_WIRE_SP12_V_B_11, + TILE_WIRE_SP12_V_B_12, + TILE_WIRE_SP12_V_B_13, + TILE_WIRE_SP12_V_B_14, + TILE_WIRE_SP12_V_B_15, + TILE_WIRE_SP12_V_B_16, + TILE_WIRE_SP12_V_B_17, + TILE_WIRE_SP12_V_B_18, + TILE_WIRE_SP12_V_B_19, + TILE_WIRE_SP12_V_B_20, + TILE_WIRE_SP12_V_B_21, + TILE_WIRE_SP12_V_B_22, + TILE_WIRE_SP12_V_B_23, + + TILE_WIRE_SP12_V_T_22, + TILE_WIRE_SP12_V_T_23, + + TILE_WIRE_SP12_H_R_0, + TILE_WIRE_SP12_H_R_1, + TILE_WIRE_SP12_H_R_2, + TILE_WIRE_SP12_H_R_3, + TILE_WIRE_SP12_H_R_4, + TILE_WIRE_SP12_H_R_5, + TILE_WIRE_SP12_H_R_6, + TILE_WIRE_SP12_H_R_7, + TILE_WIRE_SP12_H_R_8, + TILE_WIRE_SP12_H_R_9, + TILE_WIRE_SP12_H_R_10, + TILE_WIRE_SP12_H_R_11, + TILE_WIRE_SP12_H_R_12, + TILE_WIRE_SP12_H_R_13, + TILE_WIRE_SP12_H_R_14, + TILE_WIRE_SP12_H_R_15, + TILE_WIRE_SP12_H_R_16, + TILE_WIRE_SP12_H_R_17, + TILE_WIRE_SP12_H_R_18, + TILE_WIRE_SP12_H_R_19, + TILE_WIRE_SP12_H_R_20, + TILE_WIRE_SP12_H_R_21, + TILE_WIRE_SP12_H_R_22, + TILE_WIRE_SP12_H_R_23, + + TILE_WIRE_SP12_H_L_22, + TILE_WIRE_SP12_H_L_23 +}; + +void gfxTileWire(std::vector &g, int x, int y, GfxTileWireId id, GraphicElement::style_t style); + +NEXTPNR_NAMESPACE_END + +#endif // GFX_H diff --git a/ice40/main.cc b/ice40/main.cc index 87a32ded..e77bdd34 100644 --- a/ice40/main.cc +++ b/ice40/main.cc @@ -39,30 +39,30 @@ #include "jsonparse.h" #include "log.h" #include "nextpnr.h" -#include "pack.h" #include "pcf.h" #include "place_legaliser.h" -#include "place_sa.h" -#include "route.h" #include "timing.h" #include "version.h" USING_NEXTPNR_NAMESPACE -void svg_dump_el(const GraphicElement &el) +void svg_dump_decal(const Context *ctx, const DecalXY &decal) { - float scale = 10.0, offset = 10.0; - std::string style = "stroke=\"black\" stroke-width=\"0.1\" fill=\"none\""; + const float scale = 10.0, offset = 10.0; + const std::string style = "stroke=\"black\" stroke-width=\"0.1\" fill=\"none\""; - if (el.type == GraphicElement::G_BOX) { - std::cout << "\n"; - } + for (auto &el : ctx->getDecalGraphics(decal.decal)) { + if (el.type == GraphicElement::G_BOX) { + std::cout << "\n"; + } - if (el.type == GraphicElement::G_LINE) { - std::cout << "\n"; + if (el.type == GraphicElement::G_LINE) { + std::cout << "\n"; + } } } @@ -98,12 +98,16 @@ int main(int argc, char *argv[]) options.add_options()("seed", po::value(), "seed value for random number generator"); options.add_options()("version,V", "show version"); options.add_options()("tmfuzz", "run path delay estimate fuzzer"); +#ifdef ICE40_HX1K_ONLY + options.add_options()("hx1k", "set device type to iCE40HX1K"); +#else options.add_options()("lp384", "set device type to iCE40LP384"); options.add_options()("lp1k", "set device type to iCE40LP1K"); options.add_options()("lp8k", "set device type to iCE40LP8K"); options.add_options()("hx1k", "set device type to iCE40HX1K"); options.add_options()("hx8k", "set device type to iCE40HX8K"); options.add_options()("up5k", "set device type to iCE40UP5K"); +#endif options.add_options()("freq", po::value(), "set target frequency for design in MHz"); options.add_options()("no-tmdriv", "disable timing-driven placement"); options.add_options()("package", po::value(), "set device package"); @@ -267,112 +271,126 @@ int main(int argc, char *argv[]) return 1; } - Context ctx(chipArgs); + std::unique_ptr ctx = std::unique_ptr(new Context(chipArgs)); if (vm.count("verbose")) { - ctx.verbose = true; + ctx->verbose = true; } if (vm.count("debug")) { - ctx.verbose = true; - ctx.debug = true; + ctx->verbose = true; + ctx->debug = true; } if (vm.count("force")) { - ctx.force = true; + ctx->force = true; } if (vm.count("seed")) { - ctx.rngseed(vm["seed"].as()); + ctx->rngseed(vm["seed"].as()); } if (vm.count("svg")) { std::cout << "\n"; - for (auto bel : ctx.getBels()) { - std::cout << "\n"; - for (auto &el : ctx.getBelGraphics(bel)) - svg_dump_el(el); + for (auto bel : ctx->getBels()) { + std::cout << "\n"; + svg_dump_decal(ctx.get(), ctx->getBelDecal(bel)); } std::cout << "\n"; - for (auto &el : ctx.getFrameGraphics()) - svg_dump_el(el); + svg_dump_decal(ctx.get(), ctx->getFrameDecal()); std::cout << "\n"; } if (vm.count("tmfuzz")) { std::vector src_wires, dst_wires; - /*for (auto w : ctx.getWires()) + /*for (auto w : ctx->getWires()) src_wires.push_back(w);*/ - for (auto b : ctx.getBels()) { - if (ctx.getBelType(b) == TYPE_ICESTORM_LC) { - src_wires.push_back(ctx.getWireBelPin(b, PIN_O)); + for (auto b : ctx->getBels()) { + if (ctx->getBelType(b) == TYPE_ICESTORM_LC) { + src_wires.push_back(ctx->getWireBelPin(b, PIN_O)); } - if (ctx.getBelType(b) == TYPE_SB_IO) { - src_wires.push_back(ctx.getWireBelPin(b, PIN_D_IN_0)); + if (ctx->getBelType(b) == TYPE_SB_IO) { + src_wires.push_back(ctx->getWireBelPin(b, PIN_D_IN_0)); } } - for (auto b : ctx.getBels()) { - if (ctx.getBelType(b) == TYPE_ICESTORM_LC) { - dst_wires.push_back(ctx.getWireBelPin(b, PIN_I0)); - dst_wires.push_back(ctx.getWireBelPin(b, PIN_I1)); - dst_wires.push_back(ctx.getWireBelPin(b, PIN_I2)); - dst_wires.push_back(ctx.getWireBelPin(b, PIN_I3)); - dst_wires.push_back(ctx.getWireBelPin(b, PIN_CEN)); - dst_wires.push_back(ctx.getWireBelPin(b, PIN_CIN)); + for (auto b : ctx->getBels()) { + if (ctx->getBelType(b) == TYPE_ICESTORM_LC) { + dst_wires.push_back(ctx->getWireBelPin(b, PIN_I0)); + dst_wires.push_back(ctx->getWireBelPin(b, PIN_I1)); + dst_wires.push_back(ctx->getWireBelPin(b, PIN_I2)); + dst_wires.push_back(ctx->getWireBelPin(b, PIN_I3)); + dst_wires.push_back(ctx->getWireBelPin(b, PIN_CEN)); + dst_wires.push_back(ctx->getWireBelPin(b, PIN_CIN)); } - if (ctx.getBelType(b) == TYPE_SB_IO) { - dst_wires.push_back(ctx.getWireBelPin(b, PIN_D_OUT_0)); - dst_wires.push_back(ctx.getWireBelPin(b, PIN_OUTPUT_ENABLE)); + if (ctx->getBelType(b) == TYPE_SB_IO) { + dst_wires.push_back(ctx->getWireBelPin(b, PIN_D_OUT_0)); + dst_wires.push_back(ctx->getWireBelPin(b, PIN_OUTPUT_ENABLE)); } } - ctx.shuffle(src_wires); - ctx.shuffle(dst_wires); + ctx->shuffle(src_wires); + ctx->shuffle(dst_wires); for (int i = 0; i < int(src_wires.size()) && i < int(dst_wires.size()); i++) { delay_t actual_delay; WireId src = src_wires[i], dst = dst_wires[i]; - if (!get_actual_route_delay(&ctx, src, dst, actual_delay)) + if (!ctx->getActualRouteDelay(src, dst, actual_delay)) continue; - printf("%s %s %.3f %.3f %d %d %d %d %d %d\n", ctx.getWireName(src).c_str(&ctx), - ctx.getWireName(dst).c_str(&ctx), ctx.getDelayNS(actual_delay), - ctx.getDelayNS(ctx.estimateDelay(src, dst)), ctx.chip_info->wire_data[src.index].x, - ctx.chip_info->wire_data[src.index].y, ctx.chip_info->wire_data[src.index].type, - ctx.chip_info->wire_data[dst.index].x, ctx.chip_info->wire_data[dst.index].y, - ctx.chip_info->wire_data[dst.index].type); + printf("%s %s %.3f %.3f %d %d %d %d %d %d\n", ctx->getWireName(src).c_str(ctx.get()), + ctx->getWireName(dst).c_str(ctx.get()), ctx->getDelayNS(actual_delay), + ctx->getDelayNS(ctx->estimateDelay(src, dst)), ctx->chip_info->wire_data[src.index].x, + ctx->chip_info->wire_data[src.index].y, ctx->chip_info->wire_data[src.index].type, + ctx->chip_info->wire_data[dst.index].x, ctx->chip_info->wire_data[dst.index].y, + ctx->chip_info->wire_data[dst.index].type); } } + if (vm.count("freq")) + ctx->target_freq = vm["freq"].as() * 1e6; + ctx->timing_driven = true; + if (vm.count("no-tmdriv")) + ctx->timing_driven = false; +#ifndef NO_GUI + if (vm.count("gui")) { + Application a(argc, argv); + MainWindow w(std::move(ctx), chipArgs); + if (vm.count("json")) { + std::string filename = vm["json"].as(); + std::string pcf = ""; + if (vm.count("pcf")) + pcf = vm["pcf"].as(); + w.load_json(filename, pcf); + } + w.show(); + + return a.exec(); + } +#endif if (vm.count("json")) { std::string filename = vm["json"].as(); std::ifstream f(filename); - if (!parse_json_file(f, filename, &ctx)) + if (!parse_json_file(f, filename, ctx.get())) log_error("Loading design failed.\n"); if (vm.count("pcf")) { std::ifstream pcf(vm["pcf"].as()); - if (!apply_pcf(&ctx, pcf)) + if (!apply_pcf(ctx.get(), pcf)) log_error("Loading PCF failed.\n"); } - if (!pack_design(&ctx) && !ctx.force) + if (!ctx->pack() && !ctx->force) log_error("Packing design failed.\n"); - if (vm.count("freq")) - ctx.target_freq = vm["freq"].as() * 1e6; - assign_budget(&ctx); - ctx.check(); - print_utilisation(&ctx); - ctx.timing_driven = true; - if (vm.count("no-tmdriv")) - ctx.timing_driven = false; + assign_budget(ctx.get()); + ctx->check(); + print_utilisation(ctx.get()); if (!vm.count("pack-only")) { - if (!place_design_sa(&ctx) && !ctx.force) + if (!ctx->place() && !ctx->force) log_error("Placing design failed.\n"); - ctx.check(); - if (!route_design(&ctx) && !ctx.force) + ctx->check(); + if (!ctx->route() && !ctx->force) log_error("Routing design failed.\n"); } } @@ -380,13 +398,13 @@ int main(int argc, char *argv[]) if (vm.count("asc")) { std::string filename = vm["asc"].as(); std::ofstream f(filename); - write_asc(&ctx, f); + write_asc(ctx.get(), f); } #ifndef NO_PYTHON if (vm.count("run")) { init_python(argv[0], true); - python_export_global("ctx", ctx); + python_export_global("ctx", *ctx.get()); std::vector files = vm["run"].as>(); for (auto filename : files) @@ -395,16 +413,6 @@ int main(int argc, char *argv[]) deinit_python(); } #endif - -#ifndef NO_GUI - if (vm.count("gui")) { - Application a(argc, argv); - MainWindow w; - w.show(); - - rc = a.exec(); - } -#endif return rc; } catch (log_execution_error_exception) { #if defined(_MSC_VER) diff --git a/ice40/pack.cc b/ice40/pack.cc index d1be4a29..76a52be0 100644 --- a/ice40/pack.cc +++ b/ice40/pack.cc @@ -18,7 +18,6 @@ * */ -#include "pack.h" #include #include #include @@ -577,8 +576,9 @@ static void pack_special(Context *ctx) } // Main pack function -bool pack_design(Context *ctx) +bool Arch::pack() { + Context *ctx = getCtx(); try { log_break(); pack_constants(ctx); diff --git a/tests/dummy/main.cc b/tests/generic/main.cc similarity index 100% rename from tests/dummy/main.cc rename to tests/generic/main.cc