diff options
71 files changed, 7378 insertions, 1751 deletions
@@ -25,3 +25,5 @@ build/ /Testing/* CTestTestfile.cmake install_manifest.txt +/bbasm +/ImportExecutables.cmake diff --git a/3rdparty/QtPropertyBrowser/src/qtpropertybrowser.cpp b/3rdparty/QtPropertyBrowser/src/qtpropertybrowser.cpp index 63260fe4..9533fb5b 100644 --- a/3rdparty/QtPropertyBrowser/src/qtpropertybrowser.cpp +++ b/3rdparty/QtPropertyBrowser/src/qtpropertybrowser.cpp @@ -54,7 +54,7 @@ QT_BEGIN_NAMESPACE class QtPropertyPrivate { public: - QtPropertyPrivate(QtAbstractPropertyManager *manager) : m_enabled(true), m_modified(false), m_manager(manager) {} + QtPropertyPrivate(QtAbstractPropertyManager *manager) : m_enabled(true), m_selectable(true), m_modified(false), m_manager(manager) {} QtProperty *q_ptr; QSet<QtProperty *> m_parentItems; @@ -66,6 +66,7 @@ public: QString m_name; QString m_id; bool m_enabled; + bool m_selectable; bool m_modified; QtAbstractPropertyManager * const m_manager; @@ -260,6 +261,11 @@ bool QtProperty::isEnabled() const return d_ptr->m_enabled; } +bool QtProperty::isSelectable() const +{ + return d_ptr->m_selectable; +} + /*! Returns whether the property is modified. @@ -409,6 +415,15 @@ void QtProperty::setEnabled(bool enable) propertyChanged(); } +void QtProperty::setSelectable(bool selectable) +{ + if (d_ptr->m_selectable == selectable) + return; + + d_ptr->m_selectable = selectable; + propertyChanged(); +} + /*! Sets the property's modified state according to the passed \a modified value. diff --git a/3rdparty/QtPropertyBrowser/src/qtpropertybrowser.h b/3rdparty/QtPropertyBrowser/src/qtpropertybrowser.h index c4c6275b..1ca87830 100644 --- a/3rdparty/QtPropertyBrowser/src/qtpropertybrowser.h +++ b/3rdparty/QtPropertyBrowser/src/qtpropertybrowser.h @@ -83,6 +83,7 @@ public: QString propertyName() const; QString propertyId() const; bool isEnabled() const; + bool isSelectable() const; bool isModified() const; bool hasValue() const; @@ -97,6 +98,7 @@ public: void setPropertyName(const QString &text); void setPropertyId(const QString &text); void setEnabled(bool enable); + void setSelectable(bool selectable); void setModified(bool modified); bool isSubProperty()const; diff --git a/3rdparty/QtPropertyBrowser/src/qttreepropertybrowser.cpp b/3rdparty/QtPropertyBrowser/src/qttreepropertybrowser.cpp index 673252d2..523856eb 100644 --- a/3rdparty/QtPropertyBrowser/src/qttreepropertybrowser.cpp +++ b/3rdparty/QtPropertyBrowser/src/qttreepropertybrowser.cpp @@ -651,6 +651,11 @@ void QtTreePropertyBrowserPrivate::updateItem(QTreeWidgetItem *item) else disableItem(item); } + if (property->isSelectable()) { + item->setFlags(item->flags() | Qt::ItemIsSelectable); + } else { + item->setFlags(item->flags() & ~Qt::ItemIsSelectable); + } m_treeWidget->viewport()->update(); } diff --git a/3rdparty/QtPropertyBrowser/src/qtvariantproperty.cpp b/3rdparty/QtPropertyBrowser/src/qtvariantproperty.cpp index c41730c8..03f9688c 100644 --- a/3rdparty/QtPropertyBrowser/src/qtvariantproperty.cpp +++ b/3rdparty/QtPropertyBrowser/src/qtvariantproperty.cpp @@ -1339,6 +1339,7 @@ void addPropertyRecusively(QtVariantPropertyManager * manager, newProp->setWhatsThis(prop->whatsThis()); newProp->setModified(prop->isModified()); newProp->setEnabled(prop->isEnabled()); + newProp->setSelectable(prop->isSelectable()); newProp->setValue(prop->value()); foreach(QtProperty * subProp, prop->subProperties()) diff --git a/CMakeLists.txt b/CMakeLists.txt index c8ee6805..55e57763 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -167,6 +167,8 @@ if(MINGW) add_definitions("-Wa,-mbig-obj") endif(MINGW) +include(bba/bba.cmake) + foreach (family ${ARCH}) message(STATUS "Configuring architecture : ${family}") string(TOUPPER ${family} ufamily) diff --git a/bba/README.md b/bba/README.md new file mode 100644 index 00000000..166ff7ff --- /dev/null +++ b/bba/README.md @@ -0,0 +1,74 @@ +Binary Blob Assembler (bba) +=========================== + +This tools read a text file describing binary data, and write that binary data +file. The usual flow is that the input to bbasm is generated by a python +script, and the output is linked or loaded into a C program, using (packed) +structs to interpret the binary data. + +All references (pointers) are encoded als 32 bit byte offset relative to the +location of the pointer. This way the resulting binary blob is position +independent. + +Valid commands for the input are as follows. + +pre \<string\> +-------------- + +When a C file is generated as output, all the "pre" strings will be included +before the binary blob. + +post \<string\> +--------------- + +When a C file is generated as output, all the "post" strings will be included +after the binary blob. + +push \<name\> +------------- + +All following commands up until the matching "pop" will be writen to stream +\<name\>. Everything written to the same stream will end up in a continous +region of the output. The statements `pop`, `label`, `ref`, `u8`, `u16`, +`u32`, and `str` are only valid within such a block. The name used in the +first push statement also determines the name of the variable in the generated +C output (when C is selected as output file format). + +pop +--- + +End of a push..pop block. + +label \<name\> \[\<comment\>\] +------------------------------ + +Add a label for the current position. + +ref \<name\> \[\<comment\>\] +---------------------------- + +Add a 32-bit reference to the specified label. The reference will be a byte +offset relative to the memory location of the reference itself. + +u8 \<value\> \[\<comment\>\] +---------------------------- + +Add a 8-bit value to the binary blob. + +u16 \<value\> \[\<comment\>\] +----------------------------- + +Add a 16-bit value to the binary blob. Note that the input must be structured +in a way that ensures that all u16 are aligned to 2-byte addresses. + +u32 \<value\> \[\<comment\>\] +----------------------------- + +Add a 32-bit value to the binary blob. Note that the input must be structured +in a way that ensures that all u32 are aligned to 4-byte addresses. + +str "\<string\>" \[\<comment\>\] +-------------------------------- + +Add a reference to a zero-terminated copy of that string. Any character may be +used to quote the string, but the most common choices are `"` and `|`. diff --git a/bba/bba.cmake b/bba/bba.cmake new file mode 100644 index 00000000..3e094277 --- /dev/null +++ b/bba/bba.cmake @@ -0,0 +1,13 @@ +IF(CMAKE_CROSSCOMPILING) + SET(IMPORT_EXECUTABLES "IMPORTFILE-NOTFOUND" CACHE FILEPATH "Point it to the export file from a native build") + INCLUDE(${IMPORT_EXECUTABLES}) +ENDIF(CMAKE_CROSSCOMPILING) + +IF(NOT CMAKE_CROSSCOMPILING) + ADD_EXECUTABLE(bbasm bba/main.cc) + target_link_libraries(bbasm LINK_PUBLIC ${Boost_PROGRAM_OPTIONS_LIBRARY}) +ENDIF(NOT CMAKE_CROSSCOMPILING) + +IF(NOT CMAKE_CROSSCOMPILING) + EXPORT(TARGETS bbasm FILE ${CMAKE_BINARY_DIR}/ImportExecutables.cmake ) +ENDIF(NOT CMAKE_CROSSCOMPILING) diff --git a/bba/main.cc b/bba/main.cc new file mode 100644 index 00000000..263cf39e --- /dev/null +++ b/bba/main.cc @@ -0,0 +1,427 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Clifford Wolf <clifford@symbioticeda.com> + * Copyright (C) 2018 Miodrag Milanovic <miodrag@symbioticeda.com> + * + * 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 <assert.h> +#include <boost/program_options.hpp> +#include <iostream> +#include <map> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <string> +#include <vector> + +enum TokenType : int8_t +{ + TOK_LABEL, + TOK_REF, + TOK_U8, + TOK_U16, + TOK_U32 +}; + +struct Stream +{ + std::string name; + std::vector<TokenType> tokenTypes; + std::vector<uint32_t> tokenValues; + std::vector<std::string> tokenComments; +}; + +Stream stringStream; +std::vector<Stream> streams; +std::map<std::string, int> streamIndex; +std::vector<int> streamStack; + +std::vector<int> labels; +std::vector<std::string> labelNames; +std::map<std::string, int> labelIndex; + +std::vector<std::string> preText, postText; + +const char *skipWhitespace(const char *p) +{ + if (p == nullptr) + return ""; + while (*p == ' ' || *p == '\t') + p++; + return p; +} + +int main(int argc, char **argv) +{ + bool debug = false; + bool verbose = false; + bool bigEndian = false; + bool writeC = false; + char buffer[512]; + + namespace po = boost::program_options; + po::positional_options_description pos; + po::options_description options("Allowed options"); + options.add_options()("v", "verbose output"); + options.add_options()("d", "debug output"); + options.add_options()("b", "big endian"); + options.add_options()("c", "write c strings"); + options.add_options()("files", po::value<std::vector<std::string>>(), "file parameters"); + pos.add("files", -1); + + 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("v")) + verbose = true; + if (vm.count("d")) + debug = true; + if (vm.count("b")) + bigEndian = true; + if (vm.count("c")) + writeC = true; + + if (vm.count("files") == 0) { + printf("File parameters are mandatory\n"); + exit(-1); + } + std::vector<std::string> files = vm["files"].as<std::vector<std::string>>(); + if (files.size() != 2) { + printf("Input and output parameters must be set\n"); + exit(-1); + } + + FILE *fileIn = fopen(files.at(0).c_str(), "rt"); + assert(fileIn != nullptr); + + FILE *fileOut = fopen(files.at(1).c_str(), writeC ? "wt" : "wb"); + assert(fileOut != nullptr); + + while (fgets(buffer, 512, fileIn) != nullptr) { + std::string cmd = strtok(buffer, " \t\r\n"); + + if (cmd == "pre") { + const char *p = skipWhitespace(strtok(nullptr, "\r\n")); + preText.push_back(p); + continue; + } + + if (cmd == "post") { + const char *p = skipWhitespace(strtok(nullptr, "\r\n")); + postText.push_back(p); + continue; + } + + if (cmd == "push") { + const char *p = strtok(nullptr, " \t\r\n"); + if (streamIndex.count(p) == 0) { + streamIndex[p] = streams.size(); + streams.resize(streams.size() + 1); + streams.back().name = p; + } + streamStack.push_back(streamIndex.at(p)); + continue; + } + + if (cmd == "pop") { + streamStack.pop_back(); + continue; + } + + if (cmd == "label" || cmd == "ref") { + const char *label = strtok(nullptr, " \t\r\n"); + const char *comment = skipWhitespace(strtok(nullptr, "\r\n")); + Stream &s = streams.at(streamStack.back()); + if (labelIndex.count(label) == 0) { + labelIndex[label] = labels.size(); + if (debug) + labelNames.push_back(label); + labels.push_back(-1); + } + s.tokenTypes.push_back(cmd == "label" ? TOK_LABEL : TOK_REF); + s.tokenValues.push_back(labelIndex.at(label)); + if (debug) + s.tokenComments.push_back(comment); + continue; + } + + if (cmd == "u8" || cmd == "u16" || cmd == "u32") { + const char *value = strtok(nullptr, " \t\r\n"); + const char *comment = skipWhitespace(strtok(nullptr, "\r\n")); + Stream &s = streams.at(streamStack.back()); + s.tokenTypes.push_back(cmd == "u8" ? TOK_U8 : cmd == "u16" ? TOK_U16 : TOK_U32); + s.tokenValues.push_back(atoll(value)); + if (debug) + s.tokenComments.push_back(comment); + continue; + } + + if (cmd == "str") { + const char *value = skipWhitespace(strtok(nullptr, "\r\n")); + char terminator[2] = {*value, 0}; + assert(terminator[0] != 0); + value = strtok((char *)value + 1, terminator); + const char *comment = skipWhitespace(strtok(nullptr, "\r\n")); + std::string label = std::string("str:") + value; + Stream &s = streams.at(streamStack.back()); + if (labelIndex.count(label) == 0) { + labelIndex[label] = labels.size(); + if (debug) + labelNames.push_back(label); + labels.push_back(-1); + } + s.tokenTypes.push_back(TOK_REF); + s.tokenValues.push_back(labelIndex.at(label)); + if (debug) + s.tokenComments.push_back(comment); + stringStream.tokenTypes.push_back(TOK_LABEL); + stringStream.tokenValues.push_back(labelIndex.at(label)); + stringStream.tokenComments.push_back(""); + while (1) { + stringStream.tokenTypes.push_back(TOK_U8); + stringStream.tokenValues.push_back(*value); + if (debug) { + char char_comment[4] = {'\'', *value, '\'', 0}; + if (*value < 32 || *value >= 127) + char_comment[0] = 0; + stringStream.tokenComments.push_back(char_comment); + } + if (*value == 0) + break; + value++; + } + continue; + } + + assert(0); + } + + if (verbose) { + printf("Constructed %d streams:\n", int(streams.size())); + for (auto &s : streams) + printf(" stream '%s' with %d tokens\n", s.name.c_str(), int(s.tokenTypes.size())); + } + + assert(!streams.empty()); + assert(streamStack.empty()); + streams.push_back(Stream()); + streams.back().name = "strings"; + streams.back().tokenTypes.swap(stringStream.tokenTypes); + streams.back().tokenValues.swap(stringStream.tokenValues); + streams.back().tokenComments.swap(stringStream.tokenComments); + + int cursor = 0; + for (auto &s : streams) { + for (int i = 0; i < int(s.tokenTypes.size()); i++) { + switch (s.tokenTypes[i]) { + case TOK_LABEL: + labels[s.tokenValues[i]] = cursor; + break; + case TOK_REF: + cursor += 4; + break; + case TOK_U8: + cursor += 1; + break; + case TOK_U16: + assert(cursor % 2 == 0); + cursor += 2; + break; + case TOK_U32: + assert(cursor % 4 == 0); + cursor += 4; + break; + default: + assert(0); + } + } + } + + if (verbose) { + printf("resolved positions for %d labels.\n", int(labels.size())); + printf("total data (including strings): %.2f MB\n", double(cursor) / (1024 * 1024)); + } + + std::vector<uint8_t> data(cursor); + + cursor = 0; + for (auto &s : streams) { + if (debug) + printf("-- %s --\n", s.name.c_str()); + + for (int i = 0; i < int(s.tokenTypes.size()); i++) { + uint32_t value = s.tokenValues[i]; + int numBytes = 0; + + switch (s.tokenTypes[i]) { + case TOK_LABEL: + break; + case TOK_REF: + value = labels[value] - cursor; + numBytes = 4; + break; + case TOK_U8: + numBytes = 1; + break; + case TOK_U16: + numBytes = 2; + break; + case TOK_U32: + numBytes = 4; + break; + default: + assert(0); + } + + if (bigEndian) { + switch (numBytes) { + case 4: + data[cursor++] = value >> 24; + data[cursor++] = value >> 16; + /* fall-through */ + case 2: + data[cursor++] = value >> 8; + /* fall-through */ + case 1: + data[cursor++] = value; + /* fall-through */ + case 0: + break; + default: + assert(0); + } + } else { + switch (numBytes) { + case 4: + data[cursor + 3] = value >> 24; + data[cursor + 2] = value >> 16; + /* fall-through */ + case 2: + data[cursor + 1] = value >> 8; + /* fall-through */ + case 1: + data[cursor] = value; + /* fall-through */ + case 0: + break; + default: + assert(0); + } + cursor += numBytes; + } + + if (debug) { + printf("%08x ", cursor - numBytes); + for (int k = cursor - numBytes; k < cursor; k++) + printf("%02x ", data[k]); + for (int k = numBytes; k < 4; k++) + printf(" "); + + unsigned long long v = s.tokenValues[i]; + + switch (s.tokenTypes[i]) { + case TOK_LABEL: + if (s.tokenComments[i].empty()) + printf("label %s\n", labelNames[v].c_str()); + else + printf("label %-24s %s\n", labelNames[v].c_str(), s.tokenComments[i].c_str()); + break; + case TOK_REF: + if (s.tokenComments[i].empty()) + printf("ref %s\n", labelNames[v].c_str()); + else + printf("ref %-26s %s\n", labelNames[v].c_str(), s.tokenComments[i].c_str()); + break; + case TOK_U8: + if (s.tokenComments[i].empty()) + printf("u8 %llu\n", v); + else + printf("u8 %-27llu %s\n", v, s.tokenComments[i].c_str()); + break; + case TOK_U16: + if (s.tokenComments[i].empty()) + printf("u16 %-26llu\n", v); + else + printf("u16 %-26llu %s\n", v, s.tokenComments[i].c_str()); + break; + case TOK_U32: + if (s.tokenComments[i].empty()) + printf("u32 %-26llu\n", v); + else + printf("u32 %-26llu %s\n", v, s.tokenComments[i].c_str()); + break; + default: + assert(0); + } + } + } + } + + assert(cursor == int(data.size())); + + if (writeC) { + for (auto &s : preText) + fprintf(fileOut, "%s\n", s.c_str()); + + fprintf(fileOut, "const char %s[%d] =\n\"", streams[0].name.c_str(), int(data.size()) + 1); + + cursor = 1; + for (int i = 0; i < int(data.size()); i++) { + auto d = data[i]; + if (cursor > 70) { + fputc('\"', fileOut); + fputc('\n', fileOut); + cursor = 0; + } + if (cursor == 0) { + fputc('\"', fileOut); + cursor = 1; + } + if (d < 32 || d >= 127) { + if (i + 1 < int(data.size()) && (data[i + 1] < '0' || '9' < data[i + 1])) + cursor += fprintf(fileOut, "\\%o", int(d)); + else + cursor += fprintf(fileOut, "\\%03o", int(d)); + } else if (d == '\"' || d == '\'' || d == '\\') { + fputc('\\', fileOut); + fputc(d, fileOut); + cursor += 2; + } else { + fputc(d, fileOut); + cursor++; + } + } + + fprintf(fileOut, "\";\n"); + + for (auto &s : postText) + fprintf(fileOut, "%s\n", s.c_str()); + } else { + fwrite(data.data(), int(data.size()), 1, fileOut); + } + + return 0; +} diff --git a/common/archcheck.cc b/common/archcheck.cc index 5c4ef26c..5059066d 100644 --- a/common/archcheck.cc +++ b/common/archcheck.cc @@ -17,8 +17,8 @@ * */ -#include "nextpnr.h" #include "log.h" +#include "nextpnr.h" #if 0 #define dbg(...) log(__VA_ARGS__) @@ -84,8 +84,7 @@ void archcheck_locs(const Context *ctx) log_info("Checking all locations..\n"); for (int x = 0; x < ctx->getGridDimX(); x++) - for (int y = 0; y < ctx->getGridDimY(); y++) - { + for (int y = 0; y < ctx->getGridDimY(); y++) { dbg("> %d %d\n", x, y); std::unordered_set<int> usedz; diff --git a/common/nextpnr.h b/common/nextpnr.h index 021772fe..c4c05ae0 100644 --- a/common/nextpnr.h +++ b/common/nextpnr.h @@ -23,10 +23,10 @@ #include <condition_variable> #include <memory> #include <mutex> -#include <pthread.h> #include <stdexcept> #include <stdint.h> #include <string> +#include <thread> #include <unordered_map> #include <unordered_set> #include <vector> @@ -79,19 +79,19 @@ class assertion_failure : public std::runtime_error }; NPNR_NORETURN -inline bool assert_fail_impl(const char *message, const char *expr_str, const char *filename, int line) +inline void assert_fail_impl(const char *message, const char *expr_str, const char *filename, int line) { throw assertion_failure(message, expr_str, filename, line); } NPNR_NORETURN -inline bool assert_fail_impl_str(std::string message, const char *expr_str, const char *filename, int line) +inline void assert_fail_impl_str(std::string message, const char *expr_str, const char *filename, int line) { throw assertion_failure(message, expr_str, filename, line); } -#define NPNR_ASSERT(cond) ((void)((cond) || (assert_fail_impl(#cond, #cond, __FILE__, __LINE__)))) -#define NPNR_ASSERT_MSG(cond, msg) ((void)((cond) || (assert_fail_impl(msg, #cond, __FILE__, __LINE__)))) +#define NPNR_ASSERT(cond) (!(cond) ? assert_fail_impl(#cond, #cond, __FILE__, __LINE__) : (void)true) +#define NPNR_ASSERT_MSG(cond, msg) (!(cond) ? assert_fail_impl(msg, #cond, __FILE__, __LINE__) : (void)true) #define NPNR_ASSERT_FALSE(msg) (assert_fail_impl(msg, "false", __FILE__, __LINE__)) #define NPNR_ASSERT_FALSE_STR(msg) (assert_fail_impl_str(msg, "false", __FILE__, __LINE__)) @@ -145,20 +145,25 @@ struct GraphicElement { enum type_t { - G_NONE, - G_LINE, - G_BOX, - G_CIRCLE, - G_LABEL - } type = G_NONE; + TYPE_NONE, + TYPE_LINE, + TYPE_ARROW, + TYPE_BOX, + TYPE_CIRCLE, + TYPE_LABEL, + + TYPE_MAX + } type = TYPE_NONE; enum style_t { - G_FRAME, - G_HIDDEN, - G_INACTIVE, - G_ACTIVE, - } style = G_FRAME; + STYLE_FRAME, // Static "frame". Contrast between STYLE_INACTIVE and STYLE_ACTIVE + STYLE_HIDDEN, // Only display when object is selected or highlighted + STYLE_INACTIVE, // Render using low-contrast color + STYLE_ACTIVE, // Render using high-contast color + + STYLE_MAX + } style = STYLE_FRAME; float x1 = 0, y1 = 0, x2 = 0, y2 = 0, z = 0; std::string text; @@ -272,6 +277,16 @@ struct CellInfo : ArchCellInfo // cell_port -> bel_pin std::unordered_map<IdString, IdString> pins; + + // placement constraints + CellInfo *constr_parent; + std::vector<CellInfo *> constr_children; + const int UNCONSTR = INT_MIN; + int constr_x = UNCONSTR; // this.x - parent.x + int constr_y = UNCONSTR; // this.y - parent.y + int constr_z = UNCONSTR; // this.z - parent.z + bool constr_abs_z = false; // parent.z := 0 + // parent.[xyz] := 0 when (constr_parent == nullptr) }; struct DeterministicRNG @@ -343,7 +358,7 @@ struct BaseCtx { // Lock to perform mutating actions on the Context. std::mutex mutex; - pthread_t mutex_owner; + std::thread::id mutex_owner; // Lock to be taken by UI when wanting to access context - the yield() // method will lock/unlock it when its' released the main mutex to make @@ -376,12 +391,12 @@ struct BaseCtx void lock(void) { mutex.lock(); - mutex_owner = pthread_self(); + mutex_owner = std::this_thread::get_id(); } void unlock(void) { - NPNR_ASSERT(pthread_equal(pthread_self(), mutex_owner) != 0); + NPNR_ASSERT(std::this_thread::get_id() == mutex_owner); mutex.unlock(); } @@ -455,6 +470,7 @@ struct Context : Arch, DeterministicRNG bool force = false; bool timing_driven = true; float target_freq = 12e6; + bool user_freq = false; Context(ArchArgs args) : Arch(args) {} diff --git a/common/place_common.cc b/common/place_common.cc index 95b7b2aa..5673c847 100644 --- a/common/place_common.cc +++ b/common/place_common.cc @@ -28,19 +28,20 @@ NEXTPNR_NAMESPACE_BEGIN wirelen_t get_net_metric(const Context *ctx, const NetInfo *net, MetricType type, float &tns) { wirelen_t wirelength = 0; - int driver_x, driver_y; + Loc driver_loc; bool driver_gb; CellInfo *driver_cell = net->driver.cell; if (!driver_cell) return 0; if (driver_cell->bel == BelId()) return 0; - ctx->estimatePosition(driver_cell->bel, driver_x, driver_y, driver_gb); + driver_gb = ctx->getBelGlobalBuf(driver_cell->bel); + driver_loc = ctx->getBelLocation(driver_cell->bel); WireId drv_wire = ctx->getBelPinWire(driver_cell->bel, ctx->portPinFromId(net->driver.port)); if (driver_gb) return 0; float worst_slack = 1000; - int xmin = driver_x, xmax = driver_x, ymin = driver_y, ymax = driver_y; + int xmin = driver_loc.x, xmax = driver_loc.x, ymin = driver_loc.y, ymax = driver_loc.y; for (auto load : net->users) { if (load.cell == nullptr) continue; @@ -56,15 +57,14 @@ wirelen_t get_net_metric(const Context *ctx, const NetInfo *net, MetricType type worst_slack = std::min(slack, worst_slack); } - int load_x, load_y; - bool load_gb; - ctx->estimatePosition(load_cell->bel, load_x, load_y, load_gb); - if (load_gb) + if (ctx->getBelGlobalBuf(load_cell->bel)) continue; - xmin = std::min(xmin, load_x); - ymin = std::min(ymin, load_y); - xmax = std::max(xmax, load_x); - ymax = std::max(ymax, load_y); + Loc load_loc = ctx->getBelLocation(load_cell->bel); + + xmin = std::min(xmin, load_loc.x); + ymin = std::min(ymin, load_loc.y); + xmax = std::max(xmax, load_loc.x); + ymax = std::max(ymax, load_loc.y); } if (ctx->timing_driven && type == MetricType::COST) { wirelength = wirelen_t((((ymax - ymin) + (xmax - xmin)) * std::min(5.0, (1.0 + std::exp(-worst_slack / 5))))); diff --git a/common/placer1.cc b/common/placer1.cc index 461fc4e8..264f1eba 100644 --- a/common/placer1.cc +++ b/common/placer1.cc @@ -50,9 +50,7 @@ class SAPlacer { int num_bel_types = 0; for (auto bel : ctx->getBels()) { - int x, y; - bool gb; - ctx->estimatePosition(bel, x, y, gb); + Loc loc = ctx->getBelLocation(bel); BelType type = ctx->getBelType(bel); int type_idx; if (bel_types.find(type) == bel_types.end()) { @@ -63,13 +61,13 @@ class SAPlacer } if (int(fast_bels.size()) < type_idx + 1) fast_bels.resize(type_idx + 1); - if (int(fast_bels.at(type_idx).size()) < (x + 1)) - fast_bels.at(type_idx).resize(x + 1); - if (int(fast_bels.at(type_idx).at(x).size()) < (y + 1)) - fast_bels.at(type_idx).at(x).resize(y + 1); - max_x = std::max(max_x, x); - max_y = std::max(max_y, y); - fast_bels.at(type_idx).at(x).at(y).push_back(bel); + if (int(fast_bels.at(type_idx).size()) < (loc.x + 1)) + fast_bels.at(type_idx).resize(loc.x + 1); + if (int(fast_bels.at(type_idx).at(loc.x).size()) < (loc.y + 1)) + fast_bels.at(type_idx).at(loc.x).resize(loc.y + 1); + max_x = std::max(max_x, loc.x); + max_y = std::max(max_y, loc.y); + fast_bels.at(type_idx).at(loc.x).at(loc.y).push_back(bel); } diameter = std::max(max_x, max_y) + 1; } @@ -96,7 +94,13 @@ class SAPlacer BelType bel_type = ctx->getBelType(bel); if (bel_type != ctx->belTypeFromId(cell->type)) { log_error("Bel \'%s\' of type \'%s\' does not match cell " - "\'%s\' of type \'%s\'", + "\'%s\' of type \'%s\'\n", + loc_name.c_str(), ctx->belTypeToId(bel_type).c_str(ctx), cell->name.c_str(ctx), + cell->type.c_str(ctx)); + } + if (!ctx->isValidBelForCell(cell, bel)) { + log_error("Bel \'%s\' of type \'%s\' is not valid for cell " + "\'%s\' of type \'%s\'\n", loc_name.c_str(), ctx->belTypeToId(bel_type).c_str(ctx), cell->name.c_str(ctx), cell->type.c_str(ctx)); } @@ -235,8 +239,7 @@ class SAPlacer diameter *= post_legalise_dia_scale; ctx->shuffle(autoplaced); assign_budget(ctx); - } - else { + } else { update_budget(ctx); } @@ -272,6 +275,7 @@ class SAPlacer } } } + compute_fmax(ctx, true /* print_fmax */); ctx->unlock(); return true; } @@ -387,8 +391,6 @@ class SAPlacer // SA acceptance criterea if (delta < 0 || (temp > 1e-6 && (ctx->rng() / float(0x3fffffff)) <= std::exp(-delta / temp))) { n_accept++; - //if (delta < 2) - // improved = true; } else { if (other != IdString()) ctx->unbindBel(oldBel); @@ -413,12 +415,10 @@ class SAPlacer BelId random_bel_for_cell(CellInfo *cell) { BelType targetType = ctx->belTypeFromId(cell->type); - int x, y; - bool gb; - ctx->estimatePosition(cell->bel, x, y, gb); + Loc curr_loc = ctx->getBelLocation(cell->bel); while (true) { - int nx = ctx->rng(2 * diameter + 1) + std::max(x - diameter, 0); - int ny = ctx->rng(2 * diameter + 1) + std::max(y - diameter, 0); + int nx = ctx->rng(2 * diameter + 1) + std::max(curr_loc.x - diameter, 0); + int ny = ctx->rng(2 * diameter + 1) + std::max(curr_loc.y - diameter, 0); int beltype_idx = bel_types.at(targetType); if (nx >= int(fast_bels.at(beltype_idx).size())) continue; diff --git a/common/router1.cc b/common/router1.cc index 431770da..2ae54245 100644 --- a/common/router1.cc +++ b/common/router1.cc @@ -613,38 +613,10 @@ bool router1(Context *ctx) std::unordered_set<IdString> normalRouteNets, ripupQueue; - if (iterCnt == 1) { - if (ctx->verbose) - log_info("routing queue contains %d jobs.\n", int(jobQueue.size())); - } else { - static auto actual_delay = [](Context *ctx, WireId src, WireId dst) { - delay_t total_delay = 0; - WireId last = dst; - auto net_name = ctx->getBoundWireNet(src); - if (net_name != IdString()) { - auto net = ctx->nets.at(net_name).get(); - while (last != src) { - total_delay += ctx->getWireDelay(last).maxDelay(); - auto pip = net->wires.at(last).pip; - NPNR_ASSERT(ctx->getBoundPipNet(pip) == net_name); - total_delay += ctx->getPipDelay(pip).maxDelay(); - last = ctx->getPipSrcWire(pip); - if (ctx->getBoundWireNet(last) != net_name) { - log_warning("Wire %s bound to %s not %s!\n", ctx->getWireName(last).c_str(ctx), ctx->getBoundWireNet(last).c_str(ctx), net_name.c_str(ctx)); - break; - } - NPNR_ASSERT(ctx->getBoundWireNet(last) == net_name); - } - NPNR_ASSERT(last != WireId()); - } - if (last != src) - total_delay += ctx->estimateDelay(src, last); - else - total_delay += ctx->getWireDelay(last).maxDelay(); - return total_delay; - }; - update_budget(ctx, actual_delay); - } + if (ctx->verbose || iterCnt == 1) + log_info("routing queue contains %d jobs.\n", int(jobQueue.size())); + + update_budget(ctx); bool printNets = ctx->verbose && (jobQueue.size() < 10); @@ -841,14 +813,15 @@ bool router1(Context *ctx) log_info("Checksum: 0x%08x\n", ctx->checksum()); #ifndef NDEBUG ctx->check(); - ctx->unlock(); #endif + compute_fmax(ctx, true /* print_fmax */, true /* print_path */); + ctx->unlock(); return true; } catch (log_execution_error_exception) { #ifndef NDEBUG ctx->check(); - ctx->unlock(); #endif + ctx->unlock(); return false; } } diff --git a/common/timing.cc b/common/timing.cc index 0e84dded..afaddbdf 100644 --- a/common/timing.cc +++ b/common/timing.cc @@ -26,16 +26,26 @@ NEXTPNR_NAMESPACE_BEGIN -static delay_t follow_net(Context *ctx, NetInfo *net, int path_length, delay_t slack); +typedef std::unordered_map<const PortInfo *, delay_t> UpdateMap; +typedef std::list<const PortRef *> PortRefList; + +static delay_t follow_net(Context *ctx, NetInfo *net, int path_length, delay_t slack, UpdateMap *updates, + delay_t &min_slack, PortRefList *current_path, PortRefList *crit_path); // Follow a path, returning budget to annotate -static delay_t follow_user_port(Context *ctx, PortRef &user, int path_length, delay_t slack) +static delay_t follow_user_port(Context *ctx, PortRef &user, int path_length, delay_t slack, UpdateMap *updates, + delay_t &min_slack, PortRefList *current_path, PortRefList *crit_path) { delay_t value; if (ctx->getPortClock(user.cell, user.port) != IdString()) { // At the end of a timing path (arguably, should check setup time // here too) value = slack / path_length; + if (slack < min_slack) { + min_slack = slack; + if (crit_path) + *crit_path = *current_path; + } } else { // Default to the path ending here, if no further paths found value = slack / path_length; @@ -48,7 +58,8 @@ static delay_t follow_user_port(Context *ctx, PortRef &user, int path_length, de if (is_path) { NetInfo *net = port.second.net; if (net) { - delay_t path_budget = follow_net(ctx, net, path_length, slack - comb_delay); + delay_t path_budget = follow_net(ctx, net, path_length, slack - comb_delay, updates, min_slack, + current_path, crit_path); value = std::min(value, path_budget); } } @@ -56,50 +67,107 @@ static delay_t follow_user_port(Context *ctx, PortRef &user, int path_length, de } } - if (value < user.budget) { - user.budget = value; + if (updates) { + auto ret = updates->emplace(&user.cell->ports.at(user.port), value); + if (!ret.second) + ret.first->second = std::min(value, ret.first->second); } return value; } -static delay_t follow_net(Context *ctx, NetInfo *net, int path_length, delay_t slack) +static delay_t follow_net(Context *ctx, NetInfo *net, int path_length, delay_t slack, UpdateMap *updates, + delay_t &min_slack, PortRefList *current_path, PortRefList *crit_path) { delay_t net_budget = slack / (path_length + 1); - for (auto &usr : net->users) { - net_budget = std::min(net_budget, follow_user_port(ctx, usr, path_length + 1, slack)); + for (unsigned i = 0; i < net->users.size(); ++i) { + auto &usr = net->users[i]; + if (crit_path) + current_path->push_back(&usr); + // If budget override is less than existing budget, then do not increment path length + int pl = path_length + 1; + auto budget = ctx->getBudgetOverride(net, i, net_budget); + if (budget < net_budget) { + net_budget = budget; + pl = std::max(1, path_length); + } + net_budget = std::min(net_budget, + follow_user_port(ctx, usr, pl, slack - ctx->getNetinfoRouteDelay(net, i), + updates, min_slack, current_path, crit_path)); + if (crit_path) + current_path->pop_back(); } return net_budget; } -void assign_budget(Context *ctx) +static delay_t compute_min_slack(Context *ctx, UpdateMap *updates, PortRefList *crit_path) { - log_break(); - log_info("Annotating ports with timing budgets\n"); - // Clear delays to a very high value first delay_t default_slack = delay_t(1.0e12 / ctx->target_freq); - for (auto &net : ctx->nets) { - for (auto &usr : net.second->users) { - usr.budget = default_slack; - } - } - // Go through all clocked drivers and set up paths + delay_t min_slack = default_slack; + + PortRefList current_path; + + // Go through all clocked drivers and distribute the available path + // slack evenly into the budget of every sink on the path --- + // record this value into the UpdateMap for (auto &cell : ctx->cells) { for (auto port : cell.second->ports) { if (port.second.type == PORT_OUT) { IdString clock_domain = ctx->getPortClock(cell.second.get(), port.first); if (clock_domain != IdString()) { - delay_t slack = delay_t(1.0e12 / ctx->target_freq); // TODO: clock constraints + delay_t slack = default_slack; // TODO: clock constraints + delay_t clkToQ; + if (ctx->getCellDelay(cell.second.get(), clock_domain, port.first, clkToQ)) + slack -= clkToQ; if (port.second.net) - follow_net(ctx, port.second.net, 0, slack); + follow_net(ctx, port.second.net, 0, slack, updates, min_slack, ¤t_path, crit_path); } } } } - // Post-allocation check + return min_slack; +} + +void assign_budget(Context *ctx) +{ + log_break(); + log_info("Annotating ports with timing budgets\n"); + // Clear delays to a very high value first + delay_t default_slack = delay_t(1.0e12 / ctx->target_freq); + for (auto &net : ctx->nets) { + for (auto &usr : net.second->users) { + usr.budget = default_slack; + } + } + + UpdateMap updates; + delay_t min_slack = compute_min_slack(ctx, &updates, nullptr); + + // If user has not specified a frequency, adjust the target frequency dynamically + // TODO(eddieh): Tune these factors + if (!ctx->user_freq) { + if (min_slack < 0) + ctx->target_freq = 1e12 / (default_slack - 0.95 * min_slack); + else + ctx->target_freq = 1e12 / (default_slack - 1.2 * min_slack); + if (ctx->verbose) + log_info("minimum slack for this assign = %d, target Fmax for next update = %.2f MHz\n", min_slack, + ctx->target_freq / 1e6); + } + + // Update the budgets for (auto &net : ctx->nets) { - for (auto user : net.second->users) { - if (user.budget < 0) + for (size_t i = 0; i < net.second->users.size(); ++i) { + auto &user = net.second->users[i]; + auto pi = &user.cell->ports.at(user.port); + auto it = updates.find(pi); + if (it == updates.end()) + continue; + auto budget = ctx->getNetinfoRouteDelay(net.second.get(), i) - it->second; + user.budget = ctx->getBudgetOverride(net.second.get(), i, budget); + + // Post-update check + if (ctx->user_freq && user.budget < 0) log_warning("port %s.%s, connected to net '%s', has negative " "timing budget of %fns\n", user.cell->name.c_str(ctx), user.port.c_str(ctx), net.first.c_str(ctx), @@ -115,112 +183,25 @@ void assign_budget(Context *ctx) log_info("Checksum: 0x%08x\n", ctx->checksum()); } -typedef std::unordered_map<const PortInfo*, delay_t> updates_t; -typedef std::unordered_map<const PortInfo*, delay_t> delays_t; - -static delay_t follow_net_update(Context *ctx, NetInfo *net, int path_length, delay_t slack, const delays_t& delays, updates_t& updates); - -// Follow a path, returning budget to annotate -static delay_t follow_user_port_update(Context *ctx, PortRef &user, int path_length, delay_t slack, const delays_t& delays, updates_t& updates) -{ - delay_t value; - if (ctx->getPortClock(user.cell, user.port) != IdString()) { - // At the end of a timing path (arguably, should check setup time - // here too) - value = slack / path_length; - } else { - // Default to the path ending here, if no further paths found - value = slack / path_length; - // Follow outputs of the user - for (auto& port : user.cell->ports) { - if (port.second.type == PORT_OUT) { - delay_t comb_delay; - // Look up delay through this path - bool is_path = ctx->getCellDelay(user.cell, user.port, port.first, comb_delay); - if (is_path) { - NetInfo *net = port.second.net; - if (net) { - delay_t path_budget = follow_net_update(ctx, net, path_length, slack - comb_delay, delays, updates); - value = std::min(value, path_budget); - } - } - } - } - } - - auto ret = updates.emplace(&user.cell->ports.at(user.port), value); - if (!ret.second && value < ret.first->second) { - ret.first->second = value; - } - return value; -} - -static delay_t follow_net_update(Context *ctx, NetInfo *net, int path_length, delay_t slack, const delays_t& delays,updates_t& updates) -{ - delay_t net_budget = slack / (path_length + 1); - for (auto& usr : net->users) { - net_budget = std::min(net_budget, follow_user_port_update(ctx, usr, path_length + 1, slack - get_or_default(delays, &usr.cell->ports.at(usr.port), 0.), delays, updates)); - } - return net_budget; -} - -void update_budget(Context *ctx, std::function<delay_t(Context*,WireId,WireId)> delay_fn) +void update_budget(Context *ctx) { - delays_t delays; - updates_t updates; - - // Compute the delay for every pin on every net - for (auto &n : ctx->nets) { - auto net = n.second.get(); - - int driver_x, driver_y; - bool driver_gb; - CellInfo *driver_cell = net->driver.cell; - if (!driver_cell) - continue; - if (driver_cell->bel == BelId()) - continue; - ctx->estimatePosition(driver_cell->bel, driver_x, driver_y, driver_gb); - WireId drv_wire = ctx->getWireBelPin(driver_cell->bel, ctx->portPinFromId(net->driver.port)); - if (driver_gb) - continue; - for (auto& load : net->users) { - if (load.cell == nullptr) - continue; - CellInfo *load_cell = load.cell; - if (load_cell->bel == BelId()) - continue; - WireId user_wire = ctx->getWireBelPin(load_cell->bel, ctx->portPinFromId(load.port)); - delay_t raw_wl = delay_fn(ctx, drv_wire, user_wire); - delays.emplace(&load_cell->ports.at(load.port), raw_wl); - } - } - - // Go through all clocked drivers and distribute the available path slack evenly into every budget - for (auto &cell : ctx->cells) { - for (auto& port : cell.second->ports) { - if (port.second.type == PORT_OUT) { - IdString clock_domain = ctx->getPortClock(cell.second.get(), port.first); - if (clock_domain != IdString()) { - if (port.second.net) - follow_net_update(ctx, port.second.net, 0, delay_t(1.0e12 / ctx->target_freq) - get_or_default(delays, &port.second, 0.), delays, updates); - } - } - } - } + UpdateMap updates; + delay_t min_slack = compute_min_slack(ctx, &updates, nullptr); // Update the budgets for (auto &net : ctx->nets) { - for (auto& user : net.second->users) { + for (size_t i = 0; i < net.second->users.size(); ++i) { + auto &user = net.second->users[i]; auto pi = &user.cell->ports.at(user.port); + auto budget = ctx->getNetinfoRouteDelay(net.second.get(), i); auto it = updates.find(pi); - if (it == updates.end()) continue; - auto budget = delays.at(pi) + it->second; - user.budget = ctx->getBudgetOverride(net.second->driver, budget); + if (it != updates.end()) + budget -= it->second; + user.budget = ctx->getBudgetOverride(net.second.get(), i, budget); // Post-update check if (ctx->verbose) { - if (user.budget < 0) + if (ctx->user_freq && user.budget < 0) log_warning("port %s.%s, connected to net '%s', has negative " "timing budget of %fns\n", user.cell->name.c_str(ctx), user.port.c_str(ctx), net.first.c_str(ctx), @@ -233,6 +214,59 @@ void update_budget(Context *ctx, std::function<delay_t(Context*,WireId,WireId)> } } } + + // If user has not specified a frequency, adjust the frequency dynamically: + if (!ctx->user_freq) { + delay_t default_slack = delay_t(1.0e12 / ctx->target_freq); + ctx->target_freq = 1e12 / (default_slack - min_slack); + if (ctx->verbose) + log_info("minimum slack for this update = %d, target Fmax for next update = %.2f MHz\n", min_slack, + ctx->target_freq / 1e6); + } +} + +void compute_fmax(Context *ctx, bool print_fmax, bool print_path) +{ + delay_t default_slack = delay_t(1.0e12 / ctx->target_freq); + PortRefList crit_path; + delay_t min_slack = compute_min_slack(ctx, nullptr, &crit_path); + if (print_path) { + delay_t total = 0; + log_break(); + log_info("Critical path report:\n"); + log_info("curr total\n"); + auto &front = crit_path.front(); + auto &front_port = front->cell->ports.at(front->port); + auto &front_driver = front_port.net->driver; + auto last_port = ctx->getPortClock(front_driver.cell, front_driver.port); + for (auto sink : crit_path) { + auto sink_cell = sink->cell; + auto &port = sink_cell->ports.at(sink->port); + auto net = port.net; + unsigned i = 0; + for (auto &usr : net->users) + if (&usr == sink) break; + else ++i; + auto &driver = net->driver; + auto driver_cell = driver.cell; + delay_t comb_delay; + ctx->getCellDelay(sink_cell, last_port, driver.port, comb_delay); + total += comb_delay; + log_info("%4d %4d Source %s.%s\n", comb_delay, total, driver_cell->name.c_str(ctx), + driver.port.c_str(ctx)); + delay_t net_delay = ctx->getNetinfoRouteDelay(net, i); + total += net_delay; + auto driver_loc = ctx->getBelLocation(driver_cell->bel); + auto sink_loc = ctx->getBelLocation(sink_cell->bel); + log_info("%4d %4d Net %s budget %d (%d,%d) -> (%d,%d)\n", net_delay, total, net->name.c_str(ctx), + sink->budget, driver_loc.x, driver_loc.y, sink_loc.x, sink_loc.y); + log_info(" Sink %s.%s\n", sink_cell->name.c_str(ctx), sink->port.c_str(ctx)); + last_port = sink->port; + } + log_break(); + } + if (print_fmax) + log_info("estimated Fmax = %.2f MHz\n", 1e6 / (default_slack - min_slack)); } NEXTPNR_NAMESPACE_END diff --git a/common/timing.h b/common/timing.h index 8c098963..a1e12ab3 100644 --- a/common/timing.h +++ b/common/timing.h @@ -27,7 +27,10 @@ NEXTPNR_NAMESPACE_BEGIN // Assign "budget" values for all user ports in the design void assign_budget(Context *ctx); -void update_budget(Context *ctx, std::function<delay_t(Context*,WireId,WireId)> delay_fn=&Context::estimateDelay); +// Evenly redistribute the total path slack amongst all sinks on each path +void update_budget(Context *ctx); + +void compute_fmax(Context *ctx, bool print_fmax = false, bool print_path = false); NEXTPNR_NAMESPACE_END diff --git a/ecp5/arch.cc b/ecp5/arch.cc index d887aa69..b070014e 100644 --- a/ecp5/arch.cc +++ b/ecp5/arch.cc @@ -192,17 +192,20 @@ BelId Arch::getBelByName(IdString name) const return ret; } -BelRange Arch::getBelsAtSameTile(BelId bel) const +BelRange Arch::getBelsByTile(int x, int y) 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_tile = y * chip_info->width + x; + br.e.cursor_tile = y * chip_info->width + x; br.b.cursor_index = 0; - br.e.cursor_index = locInfo(bel)->num_bels - 1; + br.e.cursor_index = chip_info->locations[chip_info->location_type[br.b.cursor_tile]].num_bels - 1; br.b.chip = chip_info; br.e.chip = chip_info; - ++br.e; + if (br.e.cursor_index == -1) + ++br.e.cursor_index; + else + ++br.e; return br; } @@ -278,6 +281,7 @@ PipId Arch::getPipByName(IdString name) const 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_pips; i++) { PipId curr; @@ -285,6 +289,8 @@ PipId Arch::getPipByName(IdString name) const curr.index = i; pip_by_name[getPipName(curr)] = curr; } + if (pip_by_name.find(name) == pip_by_name.end()) + NPNR_ASSERT_FALSE_STR("no pip named " + name.str(this)); return pip_by_name[name]; } @@ -322,13 +328,52 @@ BelId Arch::getPackagePinBel(const std::string &pin) const std::string Arch::getBelPackagePin(BelId bel) const { for (int i = 0; i < package_info->num_pins; i++) { - if (package_info->pin_data[i].abs_loc == bel.location && package_info->pin_data[i].bel_index == bel.index) { + if (Location(package_info->pin_data[i].abs_loc) == bel.location && + package_info->pin_data[i].bel_index == bel.index) { return package_info->pin_data[i].name.get(); } } return ""; } +int Arch::getPioBelBank(BelId bel) const +{ + for (int i = 0; i < chip_info->num_pios; i++) { + if (Location(chip_info->pio_info[i].abs_loc) == bel.location && chip_info->pio_info[i].bel_index == bel.index) { + return chip_info->pio_info[i].bank; + } + } + NPNR_ASSERT_FALSE("failed to find PIO"); +} + +std::string Arch::getPioFunctionName(BelId bel) const +{ + for (int i = 0; i < chip_info->num_pios; i++) { + if (Location(chip_info->pio_info[i].abs_loc) == bel.location && chip_info->pio_info[i].bel_index == bel.index) { + const char *func = chip_info->pio_info[i].function_name.get(); + if (func == nullptr) + return ""; + else + return func; + } + } + NPNR_ASSERT_FALSE("failed to find PIO"); +} + +BelId Arch::getPioByFunctionName(const std::string &name) const +{ + for (int i = 0; i < chip_info->num_pios; i++) { + const char *func = chip_info->pio_info[i].function_name.get(); + if (func != nullptr && func == name) { + BelId bel; + bel.location = chip_info->pio_info[i].abs_loc; + bel.index = chip_info->pio_info[i].bel_index; + return bel; + } + } + return BelId(); +} + std::vector<PortPin> Arch::getBelPins(BelId bel) const { @@ -361,45 +406,14 @@ BelId Arch::getBelByLocation(Loc loc) const return BelId(); } -BelRange Arch::getBelsByTile(int x, int y) const -{ - BelRange br; - - int num_bels = 0; - - if (x < chip_info->width && y < chip_info->height) { - const LocationTypePOD &locI = chip_info->locations[chip_info->location_type[y * chip_info->width + x]]; - num_bels = locI.num_bels; - } - - br.b.cursor_tile = y * chip_info->width + x; - br.e.cursor_tile = y * chip_info->width + x; - br.b.cursor_index = 0; - br.e.cursor_index = num_bels - 1; - br.b.chip = chip_info; - br.e.chip = chip_info; - ++br.e; - return br; -} - // ----------------------------------------------------------------------- -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 200 * (abs(src.location.x - dst.location.x) + abs(src.location.y - dst.location.y)); } -delay_t Arch::getBudgetOverride(const PortRef& pr, delay_t v) const -{ - return v; -} +delay_t Arch::getBudgetOverride(NetInfo *net_info, int user_idx, delay_t budget) const { return budget; } // ----------------------------------------------------------------------- diff --git a/ecp5/arch.h b/ecp5/arch.h index 38362d6b..445f0dbf 100644 --- a/ecp5/arch.h +++ b/ecp5/arch.h @@ -98,7 +98,7 @@ NPNR_PACKED_STRUCT(struct LocationTypePOD { }); NPNR_PACKED_STRUCT(struct PIOInfoPOD { - Location abs_loc; + LocationPOD abs_loc; int32_t bel_index; RelPtr<char> function_name; int16_t bank; @@ -107,7 +107,7 @@ NPNR_PACKED_STRUCT(struct PIOInfoPOD { NPNR_PACKED_STRUCT(struct PackagePinPOD { RelPtr<char> name; - Location abs_loc; + LocationPOD abs_loc; int32_t bel_index; }); @@ -117,6 +117,26 @@ NPNR_PACKED_STRUCT(struct PackageInfoPOD { RelPtr<PackagePinPOD> pin_data; }); +enum TapDirection : int8_t +{ + TAP_DIR_LEFT = 0, + TAP_DIR_RIGHT = 1 +}; + +enum GlobalQuadrant : int8_t +{ + QUAD_UL = 0, + QUAD_UR = 1, + QUAD_LL = 2, + QUAD_LR = 3, +}; + +NPNR_PACKED_STRUCT(struct GlobalInfoPOD { + int16_t tap_col; + TapDirection tap_dir; + GlobalQuadrant quad; +}); + NPNR_PACKED_STRUCT(struct ChipInfoPOD { int32_t width, height; int32_t num_tiles; @@ -124,6 +144,7 @@ NPNR_PACKED_STRUCT(struct ChipInfoPOD { int32_t num_packages, num_pios; RelPtr<LocationTypePOD> locations; RelPtr<int32_t> location_type; + RelPtr<GlobalInfoPOD> location_glbinfo; RelPtr<RelPtr<char>> tiletype_names; RelPtr<PackageInfoPOD> package_info; RelPtr<PIOInfoPOD> pio_info; @@ -482,8 +503,6 @@ struct Arch : BaseCtx return range; } - BelRange getBelsAtSameTile(BelId bel) const; - BelType getBelType(BelId bel) const { NPNR_ASSERT(bel != BelId()); @@ -519,6 +538,8 @@ struct Arch : BaseCtx return id(name.str()); } + IdString getWireType(WireId wire) const { return IdString(); } + uint32_t getWireChecksum(WireId wire) const { return wire.index; } void bindWire(WireId wire, IdString net, PlaceStrength strength) @@ -597,6 +618,8 @@ struct Arch : BaseCtx PipId getPipByName(IdString name) const; IdString getPipName(PipId pip) const; + IdString getPipType(PipId pip) const { return IdString(); } + uint32_t getPipChecksum(PipId pip) const { return pip.index; } void bindPip(PipId pip, IdString net, PlaceStrength strength) @@ -729,10 +752,14 @@ struct Arch : BaseCtx 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; } + int8_t getPipClass(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; + int getPioBelBank(BelId bel) const; + // For getting GCLK, PLL, Vref, etc, pins + std::string getPioFunctionName(BelId bel) const; + BelId getPioByFunctionName(const std::string &name) const; PortType getBelPinType(BelId bel, PortPin pin) const; @@ -748,13 +775,12 @@ struct Arch : BaseCtx // ------------------------------------------------- - 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; } - delay_t getBudgetOverride(const PortRef& pr, delay_t v) const; + delay_t getBudgetOverride(NetInfo *net_info, int user_idx, delay_t budget) const; // ------------------------------------------------- diff --git a/ecp5/arch_place.cc b/ecp5/arch_place.cc index 22ebab67..84432043 100644 --- a/ecp5/arch_place.cc +++ b/ecp5/arch_place.cc @@ -66,7 +66,8 @@ bool Arch::isBelLocationValid(BelId bel) const { if (getBelType(bel) == TYPE_TRELLIS_SLICE) { std::vector<const CellInfo *> bel_cells; - for (auto bel_other : getBelsAtSameTile(bel)) { + Loc bel_loc = getBelLocation(bel); + for (auto bel_other : getBelsByTile(bel_loc.x, bel_loc.y)) { IdString cell_other = getBoundBelCell(bel_other); if (cell_other != IdString()) { const CellInfo *ci_other = cells.at(cell_other).get(); @@ -89,8 +90,8 @@ bool Arch::isValidBelForCell(CellInfo *cell, BelId bel) const NPNR_ASSERT(getBelType(bel) == TYPE_TRELLIS_SLICE); std::vector<const CellInfo *> bel_cells; - - for (auto bel_other : getBelsAtSameTile(bel)) { + Loc bel_loc = getBelLocation(bel); + for (auto bel_other : getBelsByTile(bel_loc.x, bel_loc.y)) { IdString cell_other = getBoundBelCell(bel_other); if (cell_other != IdString() && bel_other != bel) { const CellInfo *ci_other = cells.at(cell_other).get(); diff --git a/ecp5/arch_pybindings.cc b/ecp5/arch_pybindings.cc index 8310c3a1..c261c3ec 100644 --- a/ecp5/arch_pybindings.cc +++ b/ecp5/arch_pybindings.cc @@ -2,7 +2,7 @@ * nextpnr -- Next Generation Place and Route * * Copyright (C) 2018 Clifford Wolf <clifford@symbioticeda.com> - * Copyright (C) 2018 David Shah <dave@ds0.me> + * Copyright (C) 2018 David Shah <david@symbioticeda.com> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -20,13 +20,132 @@ #ifndef NO_PYTHON +#include "arch_pybindings.h" #include "nextpnr.h" #include "pybindings.h" NEXTPNR_NAMESPACE_BEGIN -void arch_wrap_python() {} +void arch_wrap_python() +{ + using namespace PythonConversion; + class_<ArchArgs>("ArchArgs").def_readwrite("type", &ArchArgs::type); + + class_<BelId>("BelId").def_readwrite("index", &BelId::index); + + class_<WireId>("WireId").def_readwrite("index", &WireId::index); + + class_<PipId>("PipId").def_readwrite("index", &PipId::index); + + class_<BelPin>("BelPin").def_readwrite("bel", &BelPin::bel).def_readwrite("pin", &BelPin::pin); + + enum_<PortPin>("PortPin") +#define X(t) .value("PIN_" #t, PIN_##t) + +#include "portpins.inc" + ; +#undef X + + auto arch_cls = class_<Arch, Arch *, bases<BaseCtx>, boost::noncopyable>("Arch", init<ArchArgs>()); + auto ctx_cls = class_<Context, Context *, bases<Arch>, boost::noncopyable>("Context", no_init) + .def("checksum", &Context::checksum) + .def("pack", &Context::pack) + .def("place", &Context::place) + .def("route", &Context::route); + + fn_wrapper_1a<Context, decltype(&Context::getBelType), &Context::getBelType, conv_to_str<BelType>, + conv_from_str<BelId>>::def_wrap(ctx_cls, "getBelType"); + fn_wrapper_1a<Context, decltype(&Context::checkBelAvail), &Context::checkBelAvail, pass_through<bool>, + conv_from_str<BelId>>::def_wrap(ctx_cls, "checkBelAvail"); + fn_wrapper_1a<Context, decltype(&Context::getBelChecksum), &Context::getBelChecksum, pass_through<uint32_t>, + conv_from_str<BelId>>::def_wrap(ctx_cls, "getBelChecksum"); + fn_wrapper_3a_v<Context, decltype(&Context::bindBel), &Context::bindBel, conv_from_str<BelId>, + conv_from_str<IdString>, pass_through<PlaceStrength>>::def_wrap(ctx_cls, "bindBel"); + fn_wrapper_1a_v<Context, decltype(&Context::unbindBel), &Context::unbindBel, conv_from_str<BelId>>::def_wrap( + ctx_cls, "unbindBel"); + fn_wrapper_1a<Context, decltype(&Context::getBoundBelCell), &Context::getBoundBelCell, conv_to_str<IdString>, + conv_from_str<BelId>>::def_wrap(ctx_cls, "getBoundBelCell"); + fn_wrapper_1a<Context, decltype(&Context::getConflictingBelCell), &Context::getConflictingBelCell, + conv_to_str<IdString>, conv_from_str<BelId>>::def_wrap(ctx_cls, "getConflictingBelCell"); + fn_wrapper_0a<Context, decltype(&Context::getBels), &Context::getBels, wrap_context<BelRange>>::def_wrap(ctx_cls, + "getBels"); + + fn_wrapper_2a<Context, decltype(&Context::getBelPinWire), &Context::getBelPinWire, conv_to_str<WireId>, + conv_from_str<BelId>, conv_from_str<PortPin>>::def_wrap(ctx_cls, "getBelPinWire"); + fn_wrapper_1a<Context, decltype(&Context::getWireBelPins), &Context::getWireBelPins, wrap_context<BelPinRange>, + conv_from_str<WireId>>::def_wrap(ctx_cls, "getWireBelPins"); + + fn_wrapper_1a<Context, decltype(&Context::getWireChecksum), &Context::getWireChecksum, pass_through<uint32_t>, + conv_from_str<WireId>>::def_wrap(ctx_cls, "getWireChecksum"); + fn_wrapper_3a_v<Context, decltype(&Context::bindWire), &Context::bindWire, conv_from_str<WireId>, + conv_from_str<IdString>, pass_through<PlaceStrength>>::def_wrap(ctx_cls, "bindWire"); + fn_wrapper_1a_v<Context, decltype(&Context::unbindWire), &Context::unbindWire, conv_from_str<WireId>>::def_wrap( + ctx_cls, "unbindWire"); + fn_wrapper_1a<Context, decltype(&Context::checkWireAvail), &Context::checkWireAvail, pass_through<bool>, + conv_from_str<WireId>>::def_wrap(ctx_cls, "checkWireAvail"); + fn_wrapper_1a<Context, decltype(&Context::getBoundWireNet), &Context::getBoundWireNet, conv_to_str<IdString>, + conv_from_str<WireId>>::def_wrap(ctx_cls, "getBoundWireNet"); + fn_wrapper_1a<Context, decltype(&Context::getConflictingWireNet), &Context::getConflictingWireNet, + conv_to_str<IdString>, conv_from_str<WireId>>::def_wrap(ctx_cls, "getConflictingWireNet"); + + fn_wrapper_0a<Context, decltype(&Context::getWires), &Context::getWires, wrap_context<WireRange>>::def_wrap( + ctx_cls, "getWires"); + + fn_wrapper_0a<Context, decltype(&Context::getPips), &Context::getPips, wrap_context<AllPipRange>>::def_wrap( + ctx_cls, "getPips"); + fn_wrapper_1a<Context, decltype(&Context::getPipChecksum), &Context::getPipChecksum, pass_through<uint32_t>, + conv_from_str<PipId>>::def_wrap(ctx_cls, "getPipChecksum"); + fn_wrapper_3a_v<Context, decltype(&Context::bindPip), &Context::bindPip, conv_from_str<PipId>, + conv_from_str<IdString>, pass_through<PlaceStrength>>::def_wrap(ctx_cls, "bindPip"); + fn_wrapper_1a_v<Context, decltype(&Context::unbindPip), &Context::unbindPip, conv_from_str<PipId>>::def_wrap( + ctx_cls, "unbindPip"); + fn_wrapper_1a<Context, decltype(&Context::checkPipAvail), &Context::checkPipAvail, pass_through<bool>, + conv_from_str<PipId>>::def_wrap(ctx_cls, "checkPipAvail"); + fn_wrapper_1a<Context, decltype(&Context::getBoundPipNet), &Context::getBoundPipNet, conv_to_str<IdString>, + conv_from_str<PipId>>::def_wrap(ctx_cls, "getBoundPipNet"); + fn_wrapper_1a<Context, decltype(&Context::getConflictingPipNet), &Context::getConflictingPipNet, + conv_to_str<IdString>, conv_from_str<PipId>>::def_wrap(ctx_cls, "getConflictingPipNet"); + + fn_wrapper_1a<Context, decltype(&Context::getPipsDownhill), &Context::getPipsDownhill, wrap_context<PipRange>, + conv_from_str<WireId>>::def_wrap(ctx_cls, "getPipsDownhill"); + fn_wrapper_1a<Context, decltype(&Context::getPipsUphill), &Context::getPipsUphill, wrap_context<PipRange>, + conv_from_str<WireId>>::def_wrap(ctx_cls, "getPipsUphill"); + fn_wrapper_1a<Context, decltype(&Context::getWireAliases), &Context::getWireAliases, wrap_context<PipRange>, + conv_from_str<WireId>>::def_wrap(ctx_cls, "getWireAliases"); + + fn_wrapper_1a<Context, decltype(&Context::getPipSrcWire), &Context::getPipSrcWire, conv_to_str<WireId>, + conv_from_str<PipId>>::def_wrap(ctx_cls, "getPipSrcWire"); + fn_wrapper_1a<Context, decltype(&Context::getPipDstWire), &Context::getPipDstWire, conv_to_str<WireId>, + conv_from_str<PipId>>::def_wrap(ctx_cls, "getPipDstWire"); + fn_wrapper_1a<Context, decltype(&Context::getPipDelay), &Context::getPipDelay, pass_through<DelayInfo>, + conv_from_str<PipId>>::def_wrap(ctx_cls, "getPipDelay"); + + fn_wrapper_1a<Context, decltype(&Context::getPackagePinBel), &Context::getPackagePinBel, conv_to_str<BelId>, + pass_through<std::string>>::def_wrap(ctx_cls, "getPackagePinBel"); + fn_wrapper_1a<Context, decltype(&Context::getBelPackagePin), &Context::getBelPackagePin, pass_through<std::string>, + conv_from_str<BelId>>::def_wrap(ctx_cls, "getBelPackagePin"); + + fn_wrapper_0a<Context, decltype(&Context::getChipName), &Context::getChipName, pass_through<std::string>>::def_wrap( + ctx_cls, "getChipName"); + fn_wrapper_0a<Context, decltype(&Context::archId), &Context::archId, conv_to_str<IdString>>::def_wrap(ctx_cls, + "archId"); + + typedef std::unordered_map<IdString, std::unique_ptr<CellInfo>> CellMap; + typedef std::unordered_map<IdString, std::unique_ptr<NetInfo>> NetMap; + + readonly_wrapper<Context, decltype(&Context::cells), &Context::cells, wrap_context<CellMap &>>::def_wrap(ctx_cls, + "cells"); + readonly_wrapper<Context, decltype(&Context::nets), &Context::nets, wrap_context<NetMap &>>::def_wrap(ctx_cls, + "nets"); + WRAP_RANGE(Bel, conv_to_str<BelId>); + WRAP_RANGE(Wire, conv_to_str<WireId>); + WRAP_RANGE(AllPip, conv_to_str<PipId>); + WRAP_RANGE(Pip, conv_to_str<PipId>); + + WRAP_MAP_UPTR(CellMap, "IdCellMap"); + WRAP_MAP_UPTR(NetMap, "IdNetMap"); +} NEXTPNR_NAMESPACE_END -#endif +#endif // NO_PYTHON diff --git a/ecp5/bitstream.cc b/ecp5/bitstream.cc index 19ddb9f9..df9b12d5 100644 --- a/ecp5/bitstream.cc +++ b/ecp5/bitstream.cc @@ -30,6 +30,7 @@ #include <fstream> #include <streambuf> +#include "io.h" #include "log.h" #include "util.h" @@ -173,7 +174,7 @@ void write_bitstream(Context *ctx, std::string base_config_file, std::string tex // 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 + if (ctx->getPipClass(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)); @@ -182,12 +183,47 @@ void write_bitstream(Context *ctx, std::string base_config_file, std::string tex } } } + // Find bank voltages + std::unordered_map<int, IOVoltage> bankVcc; + std::unordered_map<int, bool> bankLvds; - // Set all bankref tiles to 3.3V (TODO) + for (auto &cell : ctx->cells) { + CellInfo *ci = cell.second.get(); + if (ci->bel != BelId() && ci->type == ctx->id("TRELLIS_IO")) { + int bank = ctx->getPioBelBank(ci->bel); + std::string dir = str_or_default(ci->params, ctx->id("DIR"), "INPUT"); + std::string iotype = str_or_default(ci->attrs, ctx->id("IO_TYPE"), "LVCMOS33"); + + if (dir != "INPUT") { + IOVoltage vcc = get_vccio(ioType_from_str(iotype)); + if (bankVcc.find(bank) != bankVcc.end()) { + // TODO: strong and weak constraints + if (bankVcc[bank] != vcc) { + log_error("Error processing '%s': incompatible IO voltages %s and %s on bank %d.", + cell.first.c_str(ctx), iovoltage_to_str(bankVcc[bank]).c_str(), + iovoltage_to_str(vcc).c_str(), bank); + } + } else { + bankVcc[bank] = vcc; + } + } + + if (iotype == "LVDS") + bankLvds[bank] = true; + } + } + + // Set all bankref tiles to appropriate VccIO 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"); + int bank = std::stoi(type.substr(7)); + if (bankVcc.find(bank) != bankVcc.end()) + cc.tiles[tile.first].add_enum("BANK.VCCIO", iovoltage_to_str(bankVcc[bank])); + if (bankLvds[bank]) { + cc.tiles[tile.first].add_enum("BANK.DIFF_REF", "ON"); + cc.tiles[tile.first].add_enum("BANK.LVDSO", "ON"); + } } } @@ -235,6 +271,20 @@ void write_bitstream(Context *ctx, std::string base_config_file, std::string tex 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 (is_differential(ioType_from_str(iotype))) { + // Explicitly disable other pair + std::string other; + if (pio == "PIOA") + other = "PIOB"; + else if (pio == "PIOC") + other = "PIOD"; + else + log_error("cannot place differential IO at location %s\n", pio.c_str()); + // cc.tiles[pio_tile].add_enum(other + ".BASE_TYPE", "_NONE_"); + // cc.tiles[pic_tile].add_enum(other + ".BASE_TYPE", "_NONE_"); + cc.tiles[pio_tile].add_enum(other + ".PULLMODE", "NONE"); + cc.tiles[pio_tile].add_enum(pio + ".PULLMODE", "NONE"); + } 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 @@ -247,7 +297,7 @@ void write_bitstream(Context *ctx, std::string base_config_file, std::string tex 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") { + if (dir == "INPUT" && !is_differential(ioType_from_str(iotype))) { cc.tiles[pio_tile].add_enum(pio + ".HYSTERESIS", "ON"); } } else { diff --git a/ecp5/family.cmake b/ecp5/family.cmake index 97e5d66b..1c388e99 100644 --- a/ecp5/family.cmake +++ b/ecp5/family.cmake @@ -24,11 +24,16 @@ if (MSVC) 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_CC_BBA_DB ${CMAKE_CURRENT_SOURCE_DIR}/ecp5/chipdbs/chipdb-${dev}.bba) 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} + add_custom_command(OUTPUT ${DEV_CC_BBA_DB} + COMMAND ${ENV_CMD} python3 ${DB_PY} -p ${DEV_PORTS_INC} ${dev} > ${DEV_CC_BBA_DB} DEPENDS ${DB_PY} ) + add_custom_command(OUTPUT ${DEV_CC_DB} + COMMAND bbasm ${DEV_CC_BBA_DB} ${DEV_CC_DB} + DEPENDS bbasm ${DEV_CC_BBA_DB} + ) target_sources(ecp5_chipdb PRIVATE ${DEV_CC_DB}) set_source_files_properties(${DEV_CC_DB} PROPERTIES HEADER_FILE_ONLY TRUE) foreach (target ${family_targets}) @@ -39,11 +44,18 @@ 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_CC_BBA_DB ${CMAKE_CURRENT_SOURCE_DIR}/ecp5/chipdbs/chipdb-${dev}.bba) 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} + add_custom_command(OUTPUT ${DEV_CC_BBA_DB} + COMMAND ${ENV_CMD} python3 ${DB_PY} -p ${DEV_PORTS_INC} ${dev} > ${DEV_CC_BBA_DB}.new + COMMAND mv ${DEV_CC_BBA_DB}.new ${DEV_CC_BBA_DB} DEPENDS ${DB_PY} ) + add_custom_command(OUTPUT ${DEV_CC_DB} + COMMAND bbasm --c ${DEV_CC_BBA_DB} ${DEV_CC_DB}.new + COMMAND mv ${DEV_CC_DB}.new ${DEV_CC_DB} + DEPENDS bbasm ${DEV_CC_BBA_DB} + ) target_sources(ecp5_chipdb PRIVATE ${DEV_CC_DB}) foreach (target ${family_targets}) target_sources(${target} PRIVATE $<TARGET_OBJECTS:ecp5_chipdb>) diff --git a/ecp5/io.cc b/ecp5/io.cc new file mode 100644 index 00000000..e86c84f6 --- /dev/null +++ b/ecp5/io.cc @@ -0,0 +1,217 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 David Shah <david@symbioticeda.com> + * + * 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 "io.h" + +NEXTPNR_NAMESPACE_BEGIN + +std::string iovoltage_to_str(IOVoltage v) +{ + switch (v) { + case IOVoltage::VCC_3V3: + return "3V3"; + case IOVoltage::VCC_2V5: + return "2V5"; + case IOVoltage::VCC_1V8: + return "1V8"; + case IOVoltage::VCC_1V5: + return "1V5"; + case IOVoltage::VCC_1V35: + return "1V35"; + case IOVoltage::VCC_1V2: + return "1V2"; + } + NPNR_ASSERT_FALSE("unknown IO voltage"); +} + +IOVoltage iovoltage_from_str(const std::string &name) +{ + if (name == "3V3") + return IOVoltage::VCC_3V3; + if (name == "2V5") + return IOVoltage::VCC_2V5; + if (name == "1V8") + return IOVoltage::VCC_1V8; + if (name == "1V5") + return IOVoltage::VCC_1V5; + if (name == "1V35") + return IOVoltage::VCC_1V35; + if (name == "1V2") + return IOVoltage::VCC_1V2; + NPNR_ASSERT_FALSE("unknown IO voltage"); +} + +std::string iotype_to_str(IOType type) +{ + if (type == IOType::TYPE_NONE) + return "NONE"; +#define X(t) \ + if (type == IOType::t) \ + return #t; +#include "iotypes.inc" +#undef X + if (type == IOType::TYPE_UNKNOWN) + return "<unknown>"; + NPNR_ASSERT_FALSE("unknown IO type"); +} + +IOType ioType_from_str(const std::string &name) +{ + if (name == "NONE") + return IOType::TYPE_NONE; +#define X(t) \ + if (name == #t) \ + return IOType::t; +#include "iotypes.inc" + return IOType::TYPE_UNKNOWN; +} + +IOVoltage get_vccio(IOType type) +{ + switch (type) { + case IOType::LVTTL33: + case IOType::LVCMOS33: + case IOType::LVCMOS33D: + case IOType::LVPECL33: + case IOType::LVPECL33E: + return IOVoltage::VCC_3V3; + case IOType::LVCMOS25: + case IOType::LVCMOS25D: + case IOType::LVDS: + case IOType::SLVS: + case IOType::SUBLVDS: + case IOType::LVDS25E: + case IOType::MLVDS25: + case IOType::MLVDS25E: + case IOType::BLVDS25: + return IOVoltage::VCC_2V5; + case IOType::LVCMOS18: + case IOType::LVCMOS18D: + case IOType::SSTL18_I: + case IOType::SSTL18_II: + case IOType::SSTL18D_I: + case IOType::SSTL18D_II: + return IOVoltage::VCC_1V8; + case IOType::LVCMOS15: + case IOType::SSTL15_I: + case IOType::SSTL15_II: + case IOType::SSTL15D_I: + case IOType::SSTL15D_II: + return IOVoltage::VCC_1V5; + case IOType::SSTL135_I: + case IOType::SSTL135_II: + case IOType::SSTL135D_I: + case IOType::SSTL135D_II: + return IOVoltage::VCC_1V35; + case IOType::LVCMOS12: + case IOType::HSUL12: + case IOType::HSUL12D: + return IOVoltage::VCC_1V2; + default: + NPNR_ASSERT_FALSE("unknown IO type, unable to determine VccIO"); + } +} + +bool is_strong_vccio_constraint(IOType type, PortType dir, IOSide side) +{ + if (dir == PORT_OUT || dir == PORT_INOUT) + return true; + switch (type) { + case IOType::TYPE_NONE: + case IOType::LVCMOS33D: + case IOType::LVPECL33: + case IOType::LVDS: + case IOType::MLVDS25: + case IOType::BLVDS25: + case IOType::SLVS: + case IOType::SUBLVDS: + case IOType::LVCMOS12: + case IOType::HSUL12: + case IOType::HSUL12D: + return false; + case IOType::LVCMOS33: + case IOType::LVTTL33: + case IOType::LVCMOS25: + return (side == IOSide::LEFT || side == IOSide::RIGHT); + default: + return true; + } +} + +bool is_differential(IOType type) +{ + switch (type) { + case IOType::LVCMOS33D: + case IOType::LVCMOS25D: + case IOType::LVPECL33: + case IOType::LVDS: + case IOType::MLVDS25: + case IOType::BLVDS25: + case IOType::SLVS: + case IOType::SUBLVDS: + case IOType::LVCMOS18D: + case IOType::SSTL18D_I: + case IOType::SSTL18D_II: + case IOType::SSTL15D_I: + case IOType::SSTL15D_II: + case IOType::SSTL135D_I: + case IOType::SSTL135D_II: + case IOType::HSUL12D: + return true; + default: + return false; + } +} + +bool is_referenced(IOType type) +{ + switch (type) { + case IOType::SSTL18_I: + case IOType::SSTL18_II: + case IOType::SSTL18D_I: + case IOType::SSTL18D_II: + case IOType::SSTL15_I: + case IOType::SSTL15_II: + case IOType::SSTL15D_I: + case IOType::SSTL15D_II: + case IOType::SSTL135_I: + case IOType::SSTL135_II: + case IOType::SSTL135D_I: + case IOType::SSTL135D_II: + case IOType::HSUL12: + case IOType::HSUL12D: + return true; + default: + return false; + } +} + +bool valid_loc_for_io(IOType type, PortType dir, IOSide side, int z) +{ + bool is_lr = side == IOSide::LEFT || side == IOSide::RIGHT; + if (is_referenced(type) && !is_lr) + return false; + if (is_differential(type) && (!is_lr || ((z % 2) == 1))) + return false; + if ((type == IOType::LVCMOS18D || type == IOType::LVDS) && (dir == PORT_OUT || PORT_INOUT) && z != 0) + return false; + return true; +} + +NEXTPNR_NAMESPACE_END diff --git a/ecp5/io.h b/ecp5/io.h new file mode 100644 index 00000000..f0d941d9 --- /dev/null +++ b/ecp5/io.h @@ -0,0 +1,70 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 David Shah <david@symbioticeda.com> + * + * 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 IO_H +#define IO_H + +#include "nextpnr.h" + +NEXTPNR_NAMESPACE_BEGIN + +enum class IOVoltage +{ + VCC_3V3, + VCC_2V5, + VCC_1V8, + VCC_1V5, + VCC_1V35, + VCC_1V2 +}; + +std::string iovoltage_to_str(IOVoltage v); +IOVoltage iovoltage_from_str(const std::string &name); + +enum class IOType +{ + TYPE_NONE, +#define X(t) t, +#include "iotypes.inc" +#undef X + TYPE_UNKNOWN, +}; + +enum class IOSide +{ + LEFT, + RIGHT, + TOP, + BOTTOM, +}; + +std::string iotype_to_str(IOType type); +IOType ioType_from_str(const std::string &name); + +// IO related functions +IOVoltage get_vccio(IOType type); +bool is_strong_vccio_constraint(IOType type, PortType dir, IOSide side); +bool is_differential(IOType type); +bool is_referenced(IOType type); + +bool valid_loc_for_io(IOType type, PortType dir, IOSide side, int z); + +NEXTPNR_NAMESPACE_END + +#endif diff --git a/ecp5/iotypes.inc b/ecp5/iotypes.inc new file mode 100644 index 00000000..5984e79e --- /dev/null +++ b/ecp5/iotypes.inc @@ -0,0 +1,37 @@ +X(LVTTL33) +X(LVCMOS33) +X(LVCMOS25) +X(LVCMOS18) +X(LVCMOS15) +X(LVCMOS12) + +X(SSTL18_I) +X(SSTL18_II) +X(SSTL15_I) +X(SSTL15_II) +X(SSTL135_I) +X(SSTL135_II) +X(HSUL12) + +X(SSTL18D_I) +X(SSTL18D_II) +X(SSTL135D_I) +X(SSTL135D_II) +X(SSTL15D_I) +X(SSTL15D_II) +X(HSUL12D) +X(LVCMOS33D) +X(LVCMOS25D) + +X(LVDS) +X(BLVDS25) +X(MLVDS25) +X(LVPECL33) +X(SLVS) +X(SUBLVDS) +X(LVCMOS18D) + +X(LVDS25E) +X(BLVDS25E) +X(MLVDS25E) +X(LVPECL33E) diff --git a/ecp5/main.cc b/ecp5/main.cc index 5a4a900a..9f683a7b 100644 --- a/ecp5/main.cc +++ b/ecp5/main.cc @@ -63,6 +63,7 @@ int main(int argc, char *argv[]) #ifndef NO_GUI options.add_options()("gui", "start gui"); #endif + options.add_options()("test", "check architecture database integrity"); options.add_options()("25k", "set device type to LFE5U-25F"); options.add_options()("45k", "set device type to LFE5U-45F"); @@ -99,18 +100,16 @@ int main(int argc, char *argv[]) } 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 << 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"; + std::cout << boost::filesystem::basename(argv[0]) << " -- Next Generation Place and Route (git " + "sha1 " GIT_COMMIT_HASH_STR ")\n"; return 1; } @@ -148,6 +147,9 @@ int main(int argc, char *argv[]) if (vm.count("no-tmdriv")) ctx->timing_driven = false; + if (vm.count("test")) + ctx->archcheck(); + #ifndef NO_GUI if (vm.count("gui")) { Application a(argc, argv); @@ -165,8 +167,12 @@ int main(int argc, char *argv[]) if (!ctx->pack() && !ctx->force) log_error("Packing design failed.\n"); - if (vm.count("freq")) + if (vm.count("freq")) { ctx->target_freq = vm["freq"].as<double>() * 1e6; + ctx->user_freq = true; + } else { + log_warning("Target frequency not specified. Will optimise for max frequency.\n"); + } assign_budget(ctx.get()); ctx->check(); print_utilisation(ctx.get()); diff --git a/ecp5/trellis_import.py b/ecp5/trellis_import.py index b5cd53f1..cf12e775 100755 --- a/ecp5/trellis_import.py +++ b/ecp5/trellis_import.py @@ -10,11 +10,7 @@ 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() @@ -36,284 +32,50 @@ portpins = dict() 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 l(self, name, ltype = None, export = False): + if ltype is None: + print("label %s" % (name,)) + else: + print("label %s %s" % (name, ltype)) 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)" + if comment is None: + print("ref %s" % (name,)) + else: + print("ref %s %s" % (name, comment)) 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) + print("str %s" % s) 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 + if comment is None: + print("u8 %d" % (v,)) + else: + print("u8 %d %s" % (v, 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) + if comment is None: + print("u16 %d" % (v,)) else: - assert 0 - if comment is not None: - self.comments[len(self.data)] = comment + print("u16 %d %s" % (v, 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) + if comment is None: + print("u32 %d" % (v,)) 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) + print("u32 %d %s" % (v, comment)) + + def pre(self, s): + print("pre %s" % s) - def write_binary(self, f): - assert self.finalized - assert self.data[len(self.data) - 1] == 0 - f.buffer.write(self.data) + def post(self, s): + print("post %s" % s) + def push(self, name): + print("push %s" % name) + + def pop(self): + print("pop") bel_types = { "NONE": 0, @@ -364,13 +126,25 @@ def process_pio_db(ddrg, device): if bel_idx is not None: pindata.append((loc, bel_idx, bank, pinfunc)) +global_data = {} +quadrants = ["UL", "UR", "LL", "LR"] +def process_loc_globals(chip): + for y in range(0, max_row+1): + for x in range(0, max_col+1): + quad = chip.global_data.get_quadrant(y, x) + tapdrv = chip.global_data.get_tap_driver(y, x) + global_data[x, y] = (quadrants.index(quad), int(tapdrv.dir), tapdrv.col) def write_database(dev_name, ddrg, endianness): def write_loc(loc, sym_name): - bba.s16(loc.x, "%s.x" % sym_name) - bba.s16(loc.y, "%s.y" % sym_name) - - bba = BinaryBlobAssembler("chipdb_blob_%s" % dev_name, endianness) + bba.u16(loc.x, "%s.x" % sym_name) + bba.u16(loc.y, "%s.y" % sym_name) + + bba = BinaryBlobAssembler() + bba.pre('#include "nextpnr.h"') + bba.pre('NEXTPNR_NAMESPACE_BEGIN') + bba.post('NEXTPNR_NAMESPACE_END') + bba.push("chipdb_blob_%s" % dev_name) bba.r("chip_info", "chip_info") loctypes = list([_.key() for _ in ddrg.locationTypes]) @@ -450,6 +224,14 @@ def write_database(dev_name, ddrg, endianness): for y in range(0, max_row+1): for x in range(0, max_col+1): bba.u32(loctypes.index(ddrg.typeAtLocation[pytrellis.Location(x, y)]), "loctype") + + bba.l("location_glbinfo", "GlobalInfoPOD") + for y in range(0, max_row+1): + for x in range(0, max_col+1): + bba.u16(global_data[x, y][2], "tap_col") + bba.u8(global_data[x, y][1], "tap_dir") + bba.u8(global_data[x, y][0], "quad") + for package, pkgdata in sorted(packages.items()): bba.l("package_data_%s" % package, "PackagePinPOD") for pin in pkgdata: @@ -491,11 +273,12 @@ def write_database(dev_name, ddrg, endianness): bba.r("locations", "locations") bba.r("location_types", "location_type") + bba.r("location_glbinfo", "location_glbinfo") bba.r("tiletype_names", "tiletype_names") bba.r("package_data", "package_info") bba.r("pio_info", "pio_info") - bba.finalize() + bba.pop() return bba dev_names = {"25k": "LFE5U-25F", "45k": "LFE5U-45F", "85k": "LFE5U-85F"} @@ -518,30 +301,18 @@ def main(): idx = len(portpins) + 1 portpins[line[1]] = idx - print("Initialising chip...") + # print("Initialising chip...") chip = pytrellis.Chip(dev_names[args.device]) - print("Building routing graph...") + # print("Building routing graph...") ddrg = pytrellis.make_dedup_chipdb(chip) max_row = chip.get_max_row() max_col = chip.get_max_col() process_pio_db(ddrg, args.device) - print("{} unique location types".format(len(ddrg.locationTypes))) + process_loc_globals(chip) + # print("{} unique location types".format(len(ddrg.locationTypes))) bba = write_database(args.device, ddrg, "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 f5e94778..ce6fe50a 100644 --- a/generic/arch.cc +++ b/generic/arch.cc @@ -24,22 +24,24 @@ NEXTPNR_NAMESPACE_BEGIN -void Arch::addWire(IdString name, int x, int y) +void Arch::addWire(IdString name, IdString type, int x, int y) { NPNR_ASSERT(wires.count(name) == 0); WireInfo &wi = wires[name]; wi.name = name; + wi.type = type; wi.x = x; wi.y = y; wire_ids.push_back(name); } -void Arch::addPip(IdString name, IdString srcWire, IdString dstWire, DelayInfo delay) +void Arch::addPip(IdString name, IdString type, IdString srcWire, IdString dstWire, DelayInfo delay) { NPNR_ASSERT(pips.count(name) == 0); PipInfo &pi = pips[name]; pi.name = name; + pi.type = type; pi.srcWire = srcWire; pi.dstWire = dstWire; pi.delay = delay; @@ -49,11 +51,12 @@ void Arch::addPip(IdString name, IdString srcWire, IdString dstWire, DelayInfo d pip_ids.push_back(name); } -void Arch::addAlias(IdString name, IdString srcWire, IdString dstWire, DelayInfo delay) +void Arch::addAlias(IdString name, IdString type, IdString srcWire, IdString dstWire, DelayInfo delay) { NPNR_ASSERT(pips.count(name) == 0); PipInfo &pi = pips[name]; pi.name = name; + pi.type = type; pi.srcWire = srcWire; pi.dstWire = dstWire; pi.delay = delay; @@ -77,18 +80,18 @@ void Arch::addBel(IdString name, IdString type, Loc loc, bool gb) bel_ids.push_back(name); bel_by_loc[loc] = name; - if (bels_by_tile.size() <= loc.x) + if (int(bels_by_tile.size()) <= loc.x) bels_by_tile.resize(loc.x + 1); - if (bels_by_tile[loc.x].size() <= loc.y) + if (int(bels_by_tile[loc.x].size()) <= loc.y) bels_by_tile[loc.x].resize(loc.y + 1); bels_by_tile[loc.x][loc.y].push_back(name); - if (tileDimZ.size() <= loc.x) + if (int(tileDimZ.size()) <= loc.x) tileDimZ.resize(loc.x + 1); - if (tileDimZ[loc.x].size() <= loc.y) + if (int(tileDimZ[loc.x].size()) <= loc.y) tileDimZ[loc.x].resize(loc.y + 1); gridDimX = std::max(gridDimX, loc.x + 1); @@ -193,6 +196,24 @@ BelId Arch::getBelByName(IdString name) const IdString Arch::getBelName(BelId bel) const { return bel; } +Loc Arch::getBelLocation(BelId bel) const +{ + auto &info = bels.at(bel); + return Loc(info.x, info.y, info.z); +} + +BelId Arch::getBelByLocation(Loc loc) const +{ + auto it = bel_by_loc.find(loc); + if (it != bel_by_loc.end()) + return it->second; + return BelId(); +} + +const std::vector<BelId> &Arch::getBelsByTile(int x, int y) const { return bels_by_tile.at(x).at(y); } + +bool Arch::getBelGlobalBuf(BelId bel) const { return bels.at(bel).gb; } + uint32_t Arch::getBelChecksum(BelId bel) const { // FIXME @@ -234,6 +255,7 @@ std::vector<PortPin> Arch::getBelPins(BelId bel) const std::vector<PortPin> ret; for (auto &it : bels.at(bel).pins) ret.push_back(it.first); + return ret; } // --------------------------------------------------------------- @@ -247,6 +269,8 @@ WireId Arch::getWireByName(IdString name) const IdString Arch::getWireName(WireId wire) const { return wire; } +IdString Arch::getWireType(WireId wire) const { return wires.at(wire).type; } + uint32_t Arch::getWireChecksum(WireId wire) const { // FIXME @@ -297,6 +321,8 @@ PipId Arch::getPipByName(IdString name) const IdString Arch::getPipName(PipId pip) const { return pip; } +IdString Arch::getPipType(PipId pip) const { return pips.at(pip).type; } + uint32_t Arch::getPipChecksum(PipId wire) const { // FIXME @@ -368,13 +394,6 @@ const std::vector<GroupId> &Arch::getGroupGroups(GroupId group) const { return g // --------------------------------------------------------------- -void Arch::estimatePosition(BelId bel, int &x, int &y, bool &gb) const -{ - x = bels.at(bel).x; - y = bels.at(bel).y; - gb = bels.at(bel).gb; -} - delay_t Arch::estimateDelay(WireId src, WireId dst) const { const WireInfo &s = wires.at(src); @@ -384,10 +403,7 @@ delay_t Arch::estimateDelay(WireId src, WireId dst) const return (dx + dy) * grid_distance_to_delay; } -delay_t Arch::getBudgetOverride(const PortRef& pr, delay_t v) const -{ - return v; -} +delay_t Arch::getBudgetOverride(NetInfo *net_info, int user_idx, delay_t budget) const { return budget; } // --------------------------------------------------------------- diff --git a/generic/arch.h b/generic/arch.h index 2b952da6..b6892c85 100644 --- a/generic/arch.h +++ b/generic/arch.h @@ -31,7 +31,7 @@ struct WireInfo; struct PipInfo { - IdString name, bound_net; + IdString name, type, bound_net; WireId srcWire, dstWire; DelayInfo delay; DecalXY decalxy; @@ -39,7 +39,7 @@ struct PipInfo struct WireInfo { - IdString name, bound_net; + IdString name, type, bound_net; std::vector<PipId> downhill, uphill, aliases; BelPin uphill_bel_pin; std::vector<BelPin> downhill_bel_pins; @@ -96,9 +96,9 @@ struct Arch : BaseCtx float grid_distance_to_delay; - void addWire(IdString name, int x, int y); - void addPip(IdString name, IdString srcWire, IdString dstWire, DelayInfo delay); - void addAlias(IdString name, IdString srcWire, IdString dstWire, DelayInfo delay); + void addWire(IdString name, IdString type, int x, int y); + void addPip(IdString name, IdString type, IdString srcWire, IdString dstWire, DelayInfo delay); + void addAlias(IdString name, IdString type, IdString srcWire, IdString dstWire, DelayInfo delay); void addBel(IdString name, IdString type, Loc loc, bool gb); void addBelInput(IdString bel, IdString name, IdString wire); @@ -141,7 +141,7 @@ struct Arch : BaseCtx IdString getBelName(BelId bel) const; Loc getBelLocation(BelId bel) const; BelId getBelByLocation(Loc loc) const; - std::vector<BelId> getBelsByTile(int x, int y) const; + const std::vector<BelId> &getBelsByTile(int x, int y) const; bool getBelGlobalBuf(BelId bel) const; uint32_t getBelChecksum(BelId bel) const; void bindBel(BelId bel, IdString cell, PlaceStrength strength); @@ -157,6 +157,7 @@ struct Arch : BaseCtx WireId getWireByName(IdString name) const; IdString getWireName(WireId wire) const; + IdString getWireType(WireId wire) const; uint32_t getWireChecksum(WireId wire) const; void bindWire(WireId wire, IdString net, PlaceStrength strength); void unbindWire(WireId wire); @@ -169,6 +170,7 @@ struct Arch : BaseCtx PipId getPipByName(IdString name) const; IdString getPipName(PipId pip) const; + IdString getPipType(PipId pip) const; uint32_t getPipChecksum(PipId pip) const; void bindPip(PipId pip, IdString net, PlaceStrength strength); void unbindPip(PipId pip); @@ -191,13 +193,12 @@ struct Arch : BaseCtx const std::vector<PipId> &getGroupPips(GroupId group) const; const std::vector<GroupId> &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; } delay_t getRipupDelayPenalty() const { return 1.0; } float getDelayNS(delay_t v) const { return v; } uint32_t getDelayChecksum(delay_t v) const { return 0; } - delay_t getBudgetOverride(const PortRef& pr, delay_t v) const; + delay_t getBudgetOverride(NetInfo *net_info, int user_idx, delay_t budget) const; bool pack() { return true; } bool place(); diff --git a/generic/arch_pybindings.cc b/generic/arch_pybindings.cc index a99723f2..186b2c13 100644 --- a/generic/arch_pybindings.cc +++ b/generic/arch_pybindings.cc @@ -20,12 +20,22 @@ #ifndef NO_PYTHON +#include "arch_pybindings.h" #include "nextpnr.h" #include "pybindings.h" NEXTPNR_NAMESPACE_BEGIN -void arch_wrap_python() { class_<ArchArgs>("ArchArgs"); } +void arch_wrap_python() +{ + using namespace PythonConversion; + auto arch_cls = class_<Arch, Arch *, bases<BaseCtx>, boost::noncopyable>("Arch", init<ArchArgs>()); + auto ctx_cls = class_<Context, Context *, bases<Arch>, boost::noncopyable>("Context", no_init) + .def("checksum", &Context::checksum) + .def("pack", &Context::pack) + .def("place", &Context::place) + .def("route", &Context::route); +} NEXTPNR_NAMESPACE_END diff --git a/generic/main.cc b/generic/main.cc index 3b8b3fe6..d5a65102 100644 --- a/generic/main.cc +++ b/generic/main.cc @@ -75,18 +75,16 @@ int main(int argc, char *argv[]) } 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 << 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"; + std::cout << boost::filesystem::basename(argv[0]) << " -- Next Generation Place and Route (git " + "sha1 " GIT_COMMIT_HASH_STR ")\n"; return 1; } diff --git a/gui/base.qrc b/gui/base.qrc index 1a848f54..7b3fa55c 100644 --- a/gui/base.qrc +++ b/gui/base.qrc @@ -10,5 +10,9 @@ <file>resources/resultset_next.png</file> <file>resources/resultset_last.png</file> <file>resources/cross.png</file> + <file>resources/zoom_in.png</file> + <file>resources/zoom_out.png</file> + <file>resources/shape_handles.png</file> + <file>resources/shape_square.png</file> </qresource> </RCC> diff --git a/gui/basewindow.cc b/gui/basewindow.cc index 78c2fe3a..e07200de 100644 --- a/gui/basewindow.cc +++ b/gui/basewindow.cc @@ -25,7 +25,6 @@ #include <QSplitter>
#include "designwidget.h"
#include "fpgaviewwidget.h"
-#include "jsonparse.h"
#include "log.h"
#include "mainwindow.h"
#include "pythontab.h"
@@ -76,7 +75,7 @@ BaseMainWindow::BaseMainWindow(std::unique_ptr<Context> context, QWidget *parent centralTabWidget->setTabsClosable(true);
connect(centralTabWidget, SIGNAL(tabCloseRequested(int)), this, SLOT(closeTab(int)));
- FPGAViewWidget *fpgaView = new FPGAViewWidget();
+ fpgaView = new FPGAViewWidget();
centralTabWidget->addTab(fpgaView, "Graphics");
centralTabWidget->tabBar()->tabButton(0, QTabBar::RightSide)->resize(0, 0);
@@ -163,4 +162,30 @@ void BaseMainWindow::createMenusAndBars() mainToolBar->addAction(actionSave);
}
+void BaseMainWindow::createGraphicsBar()
+{
+ QAction *actionZoomIn = new QAction("Zoom In", this);
+ actionZoomIn->setIcon(QIcon(":/icons/resources/zoom_in.png"));
+ connect(actionZoomIn, SIGNAL(triggered()), fpgaView, SLOT(zoomIn()));
+
+ QAction *actionZoomOut = new QAction("Zoom Out", this);
+ actionZoomOut->setIcon(QIcon(":/icons/resources/zoom_out.png"));
+ connect(actionZoomOut, SIGNAL(triggered()), fpgaView, SLOT(zoomOut()));
+
+ QAction *actionZoomSelected = new QAction("Zoom Selected", this);
+ actionZoomSelected->setIcon(QIcon(":/icons/resources/shape_handles.png"));
+ connect(actionZoomSelected, SIGNAL(triggered()), fpgaView, SLOT(zoomSelected()));
+
+ QAction *actionZoomOutbound = new QAction("Zoom Outbound", this);
+ actionZoomOutbound->setIcon(QIcon(":/icons/resources/shape_square.png"));
+ connect(actionZoomOutbound, SIGNAL(triggered()), fpgaView, SLOT(zoomOutbound()));
+
+ graphicsToolBar = new QToolBar();
+ addToolBar(Qt::TopToolBarArea, graphicsToolBar);
+ graphicsToolBar->addAction(actionZoomIn);
+ graphicsToolBar->addAction(actionZoomOut);
+ graphicsToolBar->addAction(actionZoomSelected);
+ graphicsToolBar->addAction(actionZoomOutbound);
+}
+
NEXTPNR_NAMESPACE_END
diff --git a/gui/basewindow.h b/gui/basewindow.h index 1184fa80..a25a2854 100644 --- a/gui/basewindow.h +++ b/gui/basewindow.h @@ -37,6 +37,7 @@ NEXTPNR_NAMESPACE_BEGIN class PythonTab;
class DesignWidget;
+class FPGAViewWidget;
class BaseMainWindow : public QMainWindow
{
@@ -49,6 +50,7 @@ class BaseMainWindow : public QMainWindow protected:
void createMenusAndBars();
+ void createGraphicsBar();
protected Q_SLOTS:
void writeInfo(std::string text);
@@ -70,12 +72,14 @@ class BaseMainWindow : public QMainWindow QMenuBar *menuBar;
QToolBar *mainToolBar;
+ QToolBar *graphicsToolBar;
QStatusBar *statusBar;
QAction *actionNew;
QAction *actionOpen;
QAction *actionSave;
QProgressBar *progressBar;
DesignWidget *designview;
+ FPGAViewWidget *fpgaView;
};
NEXTPNR_NAMESPACE_END
diff --git a/gui/designwidget.cc b/gui/designwidget.cc index 91ec5163..43964edf 100644 --- a/gui/designwidget.cc +++ b/gui/designwidget.cc @@ -35,6 +35,7 @@ class ElementTreeItem : public QTreeWidgetItem ElementTreeItem(ElementType t, QString str, QTreeWidgetItem *parent)
: QTreeWidgetItem(parent, QStringList(str)), type(t)
{
+ this->setFlags(this->flags() & ~Qt::ItemIsSelectable);
}
virtual ~ElementTreeItem(){};
@@ -49,6 +50,7 @@ class IdStringTreeItem : public ElementTreeItem public:
IdStringTreeItem(IdString d, ElementType t, QString str, QTreeWidgetItem *parent) : ElementTreeItem(t, str, parent)
{
+ this->setFlags(this->flags() | Qt::ItemIsSelectable);
this->data = d;
}
virtual ~IdStringTreeItem(){};
@@ -68,6 +70,7 @@ DesignWidget::DesignWidget(QWidget *parent) : QWidget(parent), ctx(nullptr), net treeWidget->setColumnCount(1);
treeWidget->setHeaderLabel("Items");
treeWidget->setContextMenuPolicy(Qt::CustomContextMenu);
+ treeWidget->setSelectionMode(QAbstractItemView::ExtendedSelection);
// Add property view
variantManager = new QtVariantPropertyManager(this);
@@ -79,6 +82,7 @@ DesignWidget::DesignWidget(QWidget *parent) : QWidget(parent), ctx(nullptr), net propertyEditor->setPropertiesWithoutValueMarked(true);
propertyEditor->show();
propertyEditor->treeWidget()->setContextMenuPolicy(Qt::CustomContextMenu);
+ propertyEditor->treeWidget()->setSelectionMode(QAbstractItemView::ExtendedSelection);
QLineEdit *lineEdit = new QLineEdit();
lineEdit->setClearButtonEnabled(true);
@@ -229,6 +233,7 @@ void DesignWidget::addToHistory(QTreeWidgetItem *item) void DesignWidget::newContext(Context *ctx)
{
+ highlightSelected.clear();
treeWidget->clear();
// reset pointers since they are not valid after clear
nets_root = nullptr;
@@ -247,6 +252,7 @@ void DesignWidget::newContext(Context *ctx) QTreeWidgetItem *bel_root = new QTreeWidgetItem(treeWidget);
QMap<QString, QTreeWidgetItem *> bel_items;
bel_root->setText(0, "Bels");
+ bel_root->setFlags(bel_root->flags() & ~Qt::ItemIsSelectable);
treeWidget->insertTopLevelItem(0, bel_root);
if (ctx) {
for (auto bel : ctx->getBels()) {
@@ -279,6 +285,7 @@ void DesignWidget::newContext(Context *ctx) QTreeWidgetItem *wire_root = new QTreeWidgetItem(treeWidget);
QMap<QString, QTreeWidgetItem *> wire_items;
wire_root->setText(0, "Wires");
+ wire_root->setFlags(wire_root->flags() & ~Qt::ItemIsSelectable);
treeWidget->insertTopLevelItem(0, wire_root);
if (ctx) {
for (auto wire : ctx->getWires()) {
@@ -310,7 +317,9 @@ void DesignWidget::newContext(Context *ctx) QTreeWidgetItem *pip_root = new QTreeWidgetItem(treeWidget);
QMap<QString, QTreeWidgetItem *> pip_items;
pip_root->setText(0, "Pips");
+ pip_root->setFlags(pip_root->flags() & ~Qt::ItemIsSelectable);
treeWidget->insertTopLevelItem(0, pip_root);
+#ifndef ARCH_ECP5
if (ctx) {
for (auto pip : ctx->getPips()) {
auto id = ctx->getPipName(pip);
@@ -337,54 +346,88 @@ void DesignWidget::newContext(Context *ctx) for (auto pip : nameToItem[2].toStdMap()) {
pip_root->addChild(pip.second);
}
+#endif
+
+ nets_root = new QTreeWidgetItem(treeWidget);
+ nets_root->setText(0, "Nets");
+ nets_root->setFlags(nets_root->flags() & ~Qt::ItemIsSelectable);
+ treeWidget->insertTopLevelItem(0, nets_root);
+
+ cells_root = new QTreeWidgetItem(treeWidget);
+ cells_root->setText(0, "Cells");
+ cells_root->setFlags(cells_root->flags() & ~Qt::ItemIsSelectable);
+ treeWidget->insertTopLevelItem(0, cells_root);
+
updateTree();
}
void DesignWidget::updateTree()
{
+ if (!ctx)
+ return;
+
clearProperties();
- delete nets_root;
- delete cells_root;
- nameToItem[3].clear();
- nameToItem[4].clear();
+ // treeWidget->setSortingEnabled(false);
+
+ // Remove nets not existing any more
+ QMap<QString, QTreeWidgetItem *>::iterator i = nameToItem[3].begin();
+ while (i != nameToItem[3].end()) {
+ QMap<QString, QTreeWidgetItem *>::iterator prev = i;
+ ++i;
+ if (ctx->nets.find(ctx->id(prev.key().toStdString())) == ctx->nets.end()) {
+ if (treeWidget->currentItem() == prev.value())
+ treeWidget->setCurrentItem(nets_root);
+ if (highlightSelected.contains(prev.value()))
+ highlightSelected.remove(prev.value());
+ delete prev.value();
+ nameToItem[3].erase(prev);
+ }
+ }
// Add nets to tree
- nets_root = new QTreeWidgetItem(treeWidget);
- 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));
+ for (auto &item : ctx->nets) {
+ auto id = item.first;
+ QString name = QString(id.c_str(ctx));
+ if (!nameToItem[3].contains(name)) {
IdStringTreeItem *newItem = new IdStringTreeItem(id, ElementType::NET, name, nullptr);
+ nets_root->addChild(newItem);
nameToItem[3].insert(name, newItem);
}
}
- for (auto item : nameToItem[3].toStdMap()) {
- nets_root->addChild(item.second);
- }
+ // Remove cells not existing any more
+ i = nameToItem[4].begin();
+ while (i != nameToItem[4].end()) {
+ QMap<QString, QTreeWidgetItem *>::iterator prev = i;
+ ++i;
+ if (ctx->cells.find(ctx->id(prev.key().toStdString())) == ctx->cells.end()) {
+ if (treeWidget->currentItem() == prev.value())
+ treeWidget->setCurrentItem(cells_root);
+ if (highlightSelected.contains(prev.value()))
+ highlightSelected.remove(prev.value());
+ delete prev.value();
+ nameToItem[4].erase(prev);
+ }
+ }
// Add cells to tree
- cells_root = new QTreeWidgetItem(treeWidget);
- 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));
+ for (auto &item : ctx->cells) {
+ auto id = item.first;
+ QString name = QString(id.c_str(ctx));
+ if (!nameToItem[4].contains(name)) {
IdStringTreeItem *newItem = new IdStringTreeItem(id, ElementType::CELL, name, nullptr);
+ cells_root->addChild(newItem);
nameToItem[4].insert(name, newItem);
}
}
- for (auto item : nameToItem[4].toStdMap()) {
- cells_root->addChild(item.second);
- }
+ // treeWidget->sortByColumn(0, Qt::AscendingOrder);
+ // treeWidget->setSortingEnabled(true);
}
QtProperty *DesignWidget::addTopLevelProperty(const QString &id)
{
QtProperty *topItem = groupManager->addProperty(id);
propertyToId[topItem] = id;
idToProperty[id] = topItem;
+ topItem->setSelectable(false);
propertyEditor->addProperty(topItem);
return topItem;
}
@@ -452,12 +495,14 @@ void DesignWidget::addProperty(QtProperty *topItem, int propertyType, const QStr QtVariantProperty *item = readOnlyManager->addProperty(propertyType, name);
item->setValue(value);
item->setPropertyId(getElementTypeName(type));
+ item->setSelectable(type != ElementType::NONE);
topItem->addSubProperty(item);
}
QtProperty *DesignWidget::addSubGroup(QtProperty *topItem, const QString &name)
{
QtProperty *item = groupManager->addProperty(name);
+ item->setSelectable(false);
topItem->addSubProperty(item);
return item;
}
@@ -467,6 +512,18 @@ void DesignWidget::onItemSelectionChanged() if (treeWidget->selectedItems().size() == 0)
return;
+ if (treeWidget->selectedItems().size() > 1) {
+ std::vector<DecalXY> decals;
+ for (auto clickItem : treeWidget->selectedItems()) {
+ IdString value = static_cast<IdStringTreeItem *>(clickItem)->getData();
+ ElementType type = static_cast<ElementTreeItem *>(clickItem)->getType();
+ std::vector<DecalXY> d = getDecals(type, value);
+ std::move(d.begin(), d.end(), std::back_inserter(decals));
+ }
+ Q_EMIT selected(decals);
+ return;
+ }
+
QTreeWidgetItem *clickItem = treeWidget->selectedItems().at(0);
if (!clickItem->parent())
@@ -510,6 +567,7 @@ void DesignWidget::onItemSelectionChanged() QtProperty *topItem = addTopLevelProperty("Wire");
addProperty(topItem, QVariant::String, "Name", c.c_str(ctx));
+ addProperty(topItem, QVariant::String, "Type", ctx->getWireType(wire).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),
@@ -561,6 +619,7 @@ void DesignWidget::onItemSelectionChanged() QtProperty *topItem = addTopLevelProperty("Pip");
addProperty(topItem, QVariant::String, "Name", c.c_str(ctx));
+ addProperty(topItem, QVariant::String, "Type", ctx->getPipType(pip).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),
@@ -718,52 +777,64 @@ std::vector<DecalXY> DesignWidget::getDecals(ElementType type, IdString value) return decals;
}
-void DesignWidget::updateHighlightGroup(QTreeWidgetItem *item, int group)
+void DesignWidget::updateHighlightGroup(QList<QTreeWidgetItem *> items, int group)
{
- if (highlightSelected.contains(item)) {
- if (highlightSelected[item] == group) {
- highlightSelected.remove(item);
+ const bool shouldClear = items.size() == 1;
+ for (auto item : items) {
+ if (highlightSelected.contains(item)) {
+ if (shouldClear && highlightSelected[item] == group) {
+ highlightSelected.remove(item);
+ } else
+ highlightSelected[item] = group;
} else
- highlightSelected[item] = group;
- } else
- highlightSelected.insert(item, group);
-
- std::vector<DecalXY> decals;
+ highlightSelected.insert(item, group);
+ }
+ std::vector<DecalXY> decals[8];
for (auto it : highlightSelected.toStdMap()) {
- if (it.second == group) {
- ElementType type = static_cast<ElementTreeItem *>(it.first)->getType();
- IdString value = static_cast<IdStringTreeItem *>(it.first)->getData();
- std::vector<DecalXY> d = getDecals(type, value);
- std::move(d.begin(), d.end(), std::back_inserter(decals));
- }
+ ElementType type = static_cast<ElementTreeItem *>(it.first)->getType();
+ IdString value = static_cast<IdStringTreeItem *>(it.first)->getData();
+ std::vector<DecalXY> d = getDecals(type, value);
+ std::move(d.begin(), d.end(), std::back_inserter(decals[it.second]));
}
-
- Q_EMIT highlight(decals, group);
+ for (int i = 0; i < 8; i++)
+ Q_EMIT highlight(decals[i], i);
}
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));
+ QList<QTreeWidgetItem *> items;
+ for (auto itemContextMenu : tree->selectedItems()) {
+ QtBrowserItem *browserItem = propertyEditor->itemToBrowserItem(itemContextMenu);
+ if (!browserItem)
+ continue;
+ QtProperty *selectedProperty = browserItem->property();
+ ElementType type = getElementTypeByName(selectedProperty->propertyId());
+ if (type == ElementType::NONE)
+ continue;
+ IdString value = ctx->id(selectedProperty->valueText().toStdString());
+ items.append(nameToItem[getElementIndex(type)].value(value.c_str(ctx)));
+ }
+ int selectedIndex = -1;
+ if (items.size() == 1) {
+ QTreeWidgetItem *item = items.at(0);
+ if (highlightSelected.contains(item))
+ selectedIndex = highlightSelected[item];
+ }
QMenu menu(this);
QAction *selectAction = new QAction("&Select", this);
- connect(selectAction, &QAction::triggered, this, [this, type, value] { Q_EMIT selected(getDecals(type, value)); });
+ connect(selectAction, &QAction::triggered, this, [this, items] {
+ std::vector<DecalXY> decals;
+ for (auto clickItem : items) {
+ IdString value = static_cast<IdStringTreeItem *>(clickItem)->getData();
+ ElementType type = static_cast<ElementTreeItem *>(clickItem)->getType();
+ std::vector<DecalXY> d = getDecals(type, value);
+ std::move(d.begin(), d.end(), std::back_inserter(decals));
+ }
+ Q_EMIT selected(decals);
+ });
menu.addAction(selectAction);
QMenu *subMenu = menu.addMenu("Highlight");
@@ -776,27 +847,24 @@ void DesignWidget::prepareMenuProperty(const QPoint &pos) action->setCheckable(true);
subMenu->addAction(action);
group->addAction(action);
- if (highlightSelected.contains(item) && highlightSelected[item] == i)
+ if (selectedIndex == i)
action->setChecked(true);
- connect(action, &QAction::triggered, this, [this, i, item] { updateHighlightGroup(item, i); });
+ connect(action, &QAction::triggered, this, [this, i, items] { updateHighlightGroup(items, i); });
}
menu.exec(tree->mapToGlobal(pos));
}
void DesignWidget::prepareMenuTree(const QPoint &pos)
{
- QTreeWidget *tree = treeWidget;
-
- itemContextMenu = tree->itemAt(pos);
-
- ElementType type = static_cast<ElementTreeItem *>(itemContextMenu)->getType();
- IdString value = static_cast<IdStringTreeItem *>(itemContextMenu)->getData();
-
- if (type == ElementType::NONE)
+ if (treeWidget->selectedItems().size() == 0)
return;
-
- QTreeWidgetItem *item = nameToItem[getElementIndex(type)].value(value.c_str(ctx));
-
+ int selectedIndex = -1;
+ QList<QTreeWidgetItem *> items = treeWidget->selectedItems();
+ if (treeWidget->selectedItems().size() == 1) {
+ QTreeWidgetItem *item = treeWidget->selectedItems().at(0);
+ if (highlightSelected.contains(item))
+ selectedIndex = highlightSelected[item];
+ }
QMenu menu(this);
QMenu *subMenu = menu.addMenu("Highlight");
QActionGroup *group = new QActionGroup(this);
@@ -808,11 +876,11 @@ void DesignWidget::prepareMenuTree(const QPoint &pos) action->setCheckable(true);
subMenu->addAction(action);
group->addAction(action);
- if (highlightSelected.contains(item) && highlightSelected[item] == i)
+ if (selectedIndex == i)
action->setChecked(true);
- connect(action, &QAction::triggered, this, [this, i, item] { updateHighlightGroup(item, i); });
+ connect(action, &QAction::triggered, this, [this, i, items] { updateHighlightGroup(items, i); });
}
- menu.exec(tree->mapToGlobal(pos));
+ menu.exec(treeWidget->mapToGlobal(pos));
}
void DesignWidget::onItemDoubleClicked(QTreeWidgetItem *item, int column)
@@ -821,14 +889,8 @@ void DesignWidget::onItemDoubleClicked(QTreeWidgetItem *item, int column) 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;
- }
+ if (type != ElementType::NONE && nameToItem[index].contains(value))
+ treeWidget->setCurrentItem(nameToItem[index].value(value));
}
NEXTPNR_NAMESPACE_END
diff --git a/gui/designwidget.h b/gui/designwidget.h index b5877f60..6d4b7fe1 100644 --- a/gui/designwidget.h +++ b/gui/designwidget.h @@ -60,7 +60,7 @@ class DesignWidget : public QWidget void updateButtons();
void addToHistory(QTreeWidgetItem *item);
std::vector<DecalXY> getDecals(ElementType type, IdString value);
- void updateHighlightGroup(QTreeWidgetItem *item, int group);
+ void updateHighlightGroup(QList<QTreeWidgetItem *> item, int group);
Q_SIGNALS:
void info(std::string text);
void selected(std::vector<DecalXY> decal);
@@ -85,7 +85,6 @@ class DesignWidget : public QWidget QtGroupPropertyManager *groupManager;
QtVariantEditorFactory *variantFactory;
QtTreePropertyBrowser *propertyEditor;
- QTreeWidgetItem *itemContextMenu;
QMap<QtProperty *, QString> propertyToId;
QMap<QString, QtProperty *> idToProperty;
diff --git a/gui/fpgaviewwidget.cc b/gui/fpgaviewwidget.cc index 9343419b..de73e27b 100644 --- a/gui/fpgaviewwidget.cc +++ b/gui/fpgaviewwidget.cc @@ -31,222 +31,15 @@ NEXTPNR_NAMESPACE_BEGIN -void PolyLine::buildPoint(LineShaderData *building, const QVector2D *prev, const QVector2D *cur, - const QVector2D *next) const -{ - // buildPoint emits two vertices per line point, along with normals to move - // them the right directio when rendering and miter to compensate for - // bends. - - if (cur == nullptr) { - // BUG - return; - } - - if (prev == nullptr && next == nullptr) { - // BUG - return; - } - - // TODO(q3k): fast path for vertical/horizontal lines? - - // TODO(q3k): consider moving some of the linear algebra to the GPU, - // they're better at this than poor old CPUs. - - // Build two unit vectors pointing in the direction of the two segments - // defined by (prev, cur) and (cur, next) - QVector2D dprev, dnext; - if (prev == nullptr) { - dnext = *next - *cur; - dprev = dnext; - } else if (next == nullptr) { - dprev = *cur - *prev; - dnext = dprev; - } else { - dprev = *cur - *prev; - dnext = *next - *cur; - } - dprev.normalize(); - dnext.normalize(); - - // Calculate tangent unit vector. - QVector2D tangent(dprev + dnext); - tangent.normalize(); - - // Calculate normal to tangent - this is the line on which the vectors need - // to be pushed to build a thickened line. - const QVector2D tangent_normal = QVector2D(-tangent.y(), tangent.x()); - - // Calculate normal to one of the lines. - const QVector2D dprev_normal = QVector2D(-dprev.y(), dprev.x()); - // https://people.eecs.berkeley.edu/~sequin/CS184/IMGS/Sweep_PolyLine.jpg - // (the ^-1 is performed in the shader) - const float miter = QVector2D::dotProduct(tangent_normal, dprev_normal); - - const float x = cur->x(); - const float y = cur->y(); - const float mx = tangent_normal.x(); - const float my = tangent_normal.y(); - - // Push back 'left' vertex. - building->vertices.push_back(Vertex2DPOD(x, y)); - building->normals.push_back(Vertex2DPOD(mx, my)); - building->miters.push_back(miter); - - // Push back 'right' vertex. - building->vertices.push_back(Vertex2DPOD(x, y)); - building->normals.push_back(Vertex2DPOD(mx, my)); - building->miters.push_back(-miter); -} - -void PolyLine::build(LineShaderData &target) const -{ - if (points_.size() < 2) { - return; - } - const QVector2D *first = &points_.front(); - const QVector2D *last = &points_.back(); - - // Index number of vertices, used to build the index buffer. - unsigned int startIndex = target.vertices.size(); - unsigned int index = startIndex; - - // For every point on the line, call buildPoint with (prev, point, next). - // If we're building a closed line, prev/next wrap around. Otherwise - // they are passed as nullptr and buildPoint interprets that accordinglu. - const QVector2D *prev = nullptr; - - // Loop iterator used to ensure next is valid. - unsigned int i = 0; - for (const QVector2D &point : points_) { - const QVector2D *next = nullptr; - if (++i < points_.size()) { - next = (&point + 1); - } - - // If the line is closed, wrap around. Otherwise, pass nullptr. - if (prev == nullptr && closed_) { - buildPoint(&target, last, &point, next); - } else if (next == nullptr && closed_) { - buildPoint(&target, prev, &point, first); - } else { - buildPoint(&target, prev, &point, next); - } - - // If we have a prev point relative to cur, build a pair of triangles - // to render vertices into lines. - if (prev != nullptr) { - target.indices.push_back(index); - target.indices.push_back(index + 1); - target.indices.push_back(index + 2); - - target.indices.push_back(index + 2); - target.indices.push_back(index + 1); - target.indices.push_back(index + 3); - - index += 2; - } - prev = &point; - } - - // If we're closed, build two more vertices that loop the line around. - if (closed_) { - target.indices.push_back(index); - target.indices.push_back(index + 1); - target.indices.push_back(startIndex); - - target.indices.push_back(startIndex); - target.indices.push_back(index + 1); - target.indices.push_back(startIndex + 1); - } -} - -bool LineShader::compile(void) -{ - program_ = new QOpenGLShaderProgram(parent_); - program_->addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSource_); - program_->addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSource_); - if (!program_->link()) { - printf("could not link program: %s\n", program_->log().toStdString().c_str()); - return false; - } - - if (!vao_.create()) - log_abort(); - vao_.bind(); - - if (!buffers_.position.create()) - log_abort(); - if (!buffers_.normal.create()) - log_abort(); - if (!buffers_.miter.create()) - log_abort(); - if (!buffers_.index.create()) - log_abort(); - - attributes_.position = program_->attributeLocation("position"); - attributes_.normal = program_->attributeLocation("normal"); - attributes_.miter = program_->attributeLocation("miter"); - uniforms_.thickness = program_->uniformLocation("thickness"); - uniforms_.projection = program_->uniformLocation("projection"); - uniforms_.color = program_->uniformLocation("color"); - - vao_.release(); - return true; -} - -void LineShader::draw(const LineShaderData &line, const QColor &color, float thickness, const QMatrix4x4 &projection) -{ - auto gl = QOpenGLContext::currentContext()->functions(); - vao_.bind(); - program_->bind(); - - buffers_.position.bind(); - buffers_.position.allocate(&line.vertices[0], sizeof(Vertex2DPOD) * line.vertices.size()); - - buffers_.normal.bind(); - buffers_.normal.allocate(&line.normals[0], sizeof(Vertex2DPOD) * line.normals.size()); - - buffers_.miter.bind(); - buffers_.miter.allocate(&line.miters[0], sizeof(GLfloat) * line.miters.size()); - - buffers_.index.bind(); - buffers_.index.allocate(&line.indices[0], sizeof(GLuint) * line.indices.size()); - - program_->setUniformValue(uniforms_.projection, projection); - program_->setUniformValue(uniforms_.thickness, thickness); - program_->setUniformValue(uniforms_.color, color.redF(), color.greenF(), color.blueF(), color.alphaF()); - - buffers_.position.bind(); - program_->enableAttributeArray("position"); - gl->glVertexAttribPointer(attributes_.position, 2, GL_FLOAT, GL_FALSE, 0, (void *)0); - - buffers_.normal.bind(); - program_->enableAttributeArray("normal"); - gl->glVertexAttribPointer(attributes_.normal, 2, GL_FLOAT, GL_FALSE, 0, (void *)0); - - buffers_.miter.bind(); - program_->enableAttributeArray("miter"); - gl->glVertexAttribPointer(attributes_.miter, 1, GL_FLOAT, GL_FALSE, 0, (void *)0); - - buffers_.index.bind(); - gl->glDrawElements(GL_TRIANGLES, line.indices.size(), GL_UNSIGNED_INT, (void *)0); - - program_->disableAttributeArray("miter"); - program_->disableAttributeArray("normal"); - program_->disableAttributeArray("position"); - - program_->release(); - vao_.release(); -} - -FPGAViewWidget::FPGAViewWidget(QWidget *parent) - : QOpenGLWidget(parent), lineShader_(this), zoom_(500.f), ctx_(nullptr), paintTimer_(this), - rendererData_(new FPGAViewWidget::RendererData), rendererArgs_(new FPGAViewWidget::RendererArgs) +FPGAViewWidget::FPGAViewWidget(QWidget *parent) : + QOpenGLWidget(parent), ctx_(nullptr), paintTimer_(this), + lineShader_(this), zoom_(500.0f), + rendererData_(new FPGAViewWidget::RendererData), + rendererArgs_(new FPGAViewWidget::RendererArgs) { colors_.background = QColor("#000000"); colors_.grid = QColor("#333"); - colors_.frame = QColor("#d0d0d0"); + colors_.frame = QColor("#808080"); colors_.hidden = QColor("#606060"); colors_.inactive = QColor("#303030"); colors_.active = QColor("#f0f0f0"); @@ -289,6 +82,9 @@ FPGAViewWidget::~FPGAViewWidget() {} void FPGAViewWidget::newContext(Context *ctx) { ctx_ = ctx; + onSelectedArchItem(std::vector<DecalXY>()); + for (int i = 0; i < 8; i++) + onHighlightGroupChanged(std::vector<DecalXY>(), i); pokeRenderer(); } @@ -306,69 +102,49 @@ void FPGAViewWidget::initializeGL() 0.0); } -void FPGAViewWidget::drawDecal(LineShaderData &out, const DecalXY &decal) +void FPGAViewWidget::drawGraphicElement(LineShaderData &out, const GraphicElement &el, float x, float y) { 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); - line.build(out); - } + if (el.type == GraphicElement::TYPE_BOX) { + auto line = PolyLine(true); + line.point(x + scale * el.x1, y + scale * el.y1); + line.point(x + scale * el.x2, y + scale * el.y1); + line.point(x + scale * el.x2, y + scale * el.y2); + line.point(x + scale * el.x1, y + 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); - } + if (el.type == GraphicElement::TYPE_LINE || el.type == GraphicElement::TYPE_ARROW) { + PolyLine(x + scale * el.x1, y + scale * el.y1, x + scale * el.x2, y + scale * el.y2) + .build(out); } } -void FPGAViewWidget::drawDecal(LineShaderData out[], const DecalXY &decal) +void FPGAViewWidget::drawDecal(LineShaderData &out, const DecalXY &decal) { - const float scale = 1.0; - float offsetX = 0.0, offsetY = 0.0; + float offsetX = decal.x; + float offsetY = decal.y; 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; - } - } + drawGraphicElement(out, el, offsetX, offsetY); + } +} - 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; - } +void FPGAViewWidget::drawArchDecal(LineShaderData out[GraphicElement::STYLE_MAX], const DecalXY &decal) +{ + float offsetX = decal.x; + float offsetY = decal.y; + + for (auto &el : ctx_->getDecalGraphics(decal.decal)) { + switch (el.style) { + case GraphicElement::STYLE_FRAME: + case GraphicElement::STYLE_INACTIVE: + case GraphicElement::STYLE_ACTIVE: + drawGraphicElement(out[el.style], el, offsetX, offsetY); + break; + default: + break; } } } @@ -398,24 +174,28 @@ void FPGAViewWidget::paintGL() float thick1Px = mouseToWorldCoordinates(1, 0).x(); float thick11Px = mouseToWorldCoordinates(1.1, 0).x(); - // Draw grid. + // Render grid. 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); } + // Draw grid. lineShader_.draw(grid, colors_.grid, thick1Px, matrix); rendererDataLock_.lock(); - lineShader_.draw(rendererData_->decals[0], colors_.frame, thick11Px, matrix); - lineShader_.draw(rendererData_->decals[1], colors_.hidden, thick11Px, matrix); - lineShader_.draw(rendererData_->decals[2], colors_.inactive, thick11Px, matrix); - lineShader_.draw(rendererData_->decals[3], colors_.active, thick11Px, matrix); + // Render Arch graphics. + lineShader_.draw(rendererData_->gfxByStyle[GraphicElement::STYLE_FRAME], colors_.frame, thick11Px, matrix); + lineShader_.draw(rendererData_->gfxByStyle[GraphicElement::STYLE_HIDDEN], colors_.hidden, thick11Px, matrix); + lineShader_.draw(rendererData_->gfxByStyle[GraphicElement::STYLE_INACTIVE], colors_.inactive, thick11Px, matrix); + lineShader_.draw(rendererData_->gfxByStyle[GraphicElement::STYLE_ACTIVE], colors_.active, thick11Px, matrix); + + // Draw highlighted items. for (int i = 0; i < 8; i++) - lineShader_.draw(rendererData_->highlighted[i], colors_.highlight[i], thick11Px, matrix); + lineShader_.draw(rendererData_->gfxHighlighted[i], colors_.highlight[i], thick11Px, matrix); - lineShader_.draw(rendererData_->selected, colors_.selected, thick11Px, matrix); + lineShader_.draw(rendererData_->gfxSelected, colors_.selected, thick11Px, matrix); rendererDataLock_.unlock(); } @@ -426,126 +206,154 @@ void FPGAViewWidget::renderLines(void) if (ctx_ == nullptr) return; - ctx_->lock_ui(); - - // For now, collapse any decal changes into change of all decals. - // TODO(q3k): fix this - bool decalsChanged = false; - if (ctx_->allUiReload) { - ctx_->allUiReload = false; - decalsChanged = true; - } - if (ctx_->frameUiReload) { - ctx_->frameUiReload = false; - decalsChanged = true; - } - if (ctx_->belUiReload.size() > 0) { - ctx_->belUiReload.clear(); - decalsChanged = true; - } - if (ctx_->wireUiReload.size() > 0) { - ctx_->wireUiReload.clear(); - decalsChanged = true; - } - if (ctx_->pipUiReload.size() > 0) { - ctx_->pipUiReload.clear(); - decalsChanged = true; - } - if (ctx_->groupUiReload.size() > 0) { - ctx_->groupUiReload.clear(); - decalsChanged = true; - } - - // Local copy of decals, taken as fast as possible to not block the P&R. + // Data from Context needed to render all decals. std::vector<DecalXY> belDecals; std::vector<DecalXY> wireDecals; std::vector<DecalXY> pipDecals; std::vector<DecalXY> groupDecals; - if (decalsChanged) { - for (auto bel : ctx_->getBels()) { - belDecals.push_back(ctx_->getBelDecal(bel)); + bool decalsChanged = false; + { + // Take the UI/Normal mutex on the Context, copy over all we need as + // fast as we can. + std::lock_guard<std::mutex> lock_ui(ctx_->ui_mutex); + std::lock_guard<std::mutex> lock(ctx_->mutex); + + // For now, collapse any decal changes into change of all decals. + // TODO(q3k): fix this + if (ctx_->allUiReload) { + ctx_->allUiReload = false; + decalsChanged = true; } - for (auto wire : ctx_->getWires()) { - wireDecals.push_back(ctx_->getWireDecal(wire)); + if (ctx_->frameUiReload) { + ctx_->frameUiReload = false; + decalsChanged = true; } - for (auto pip : ctx_->getPips()) { - pipDecals.push_back(ctx_->getPipDecal(pip)); + if (ctx_->belUiReload.size() > 0) { + ctx_->belUiReload.clear(); + decalsChanged = true; } - for (auto group : ctx_->getGroups()) { - groupDecals.push_back(ctx_->getGroupDecal(group)); + if (ctx_->wireUiReload.size() > 0) { + ctx_->wireUiReload.clear(); + decalsChanged = true; + } + if (ctx_->pipUiReload.size() > 0) { + ctx_->pipUiReload.clear(); + decalsChanged = true; + } + if (ctx_->groupUiReload.size() > 0) { + ctx_->groupUiReload.clear(); + decalsChanged = true; + } + + // Local copy of decals, taken as fast as possible to not block the P&R. + if (decalsChanged) { + for (auto bel : ctx_->getBels()) { + belDecals.push_back(ctx_->getBelDecal(bel)); + } + for (auto wire : ctx_->getWires()) { + wireDecals.push_back(ctx_->getWireDecal(wire)); + } + for (auto pip : ctx_->getPips()) { + pipDecals.push_back(ctx_->getPipDecal(pip)); + } + for (auto group : ctx_->getGroups()) { + groupDecals.push_back(ctx_->getGroupDecal(group)); + } } } - ctx_->unlock_ui(); - rendererArgsLock_.lock(); - auto selectedItems = rendererArgs_->selectedItems; - auto highlightedItems = rendererArgs_->highlightedItems; - auto highlightedOrSelectedChanged = rendererArgs_->highlightedOrSelectedChanged; - rendererArgs_->highlightedOrSelectedChanged = false; - rendererArgsLock_.unlock(); + // Arguments from the main UI thread on what we should render. + std::vector<DecalXY> selectedDecals; + std::vector<DecalXY> highlightedDecals[8]; + bool highlightedOrSelectedChanged; + { + // Take the renderer arguments lock, copy over all we need. + QMutexLocker lock(&rendererArgsLock_); + selectedDecals = rendererArgs_->selectedDecals; + for (int i = 0; i < 8; i++) + highlightedDecals[i] = rendererArgs_->highlightedDecals[i]; + highlightedOrSelectedChanged = rendererArgs_->highlightedOrSelectedChanged; + rendererArgs_->highlightedOrSelectedChanged = false; + } + // Render decals if necessary. if (decalsChanged) { auto data = std::unique_ptr<FPGAViewWidget::RendererData>(new FPGAViewWidget::RendererData); // Draw Bels. for (auto const &decal : belDecals) { - drawDecal(data->decals, decal); + drawArchDecal(data->gfxByStyle, decal); } // Draw Wires. for (auto const &decal : wireDecals) { - drawDecal(data->decals, decal); + drawArchDecal(data->gfxByStyle, decal); } // Draw Pips. for (auto const &decal : pipDecals) { - drawDecal(data->decals, decal); + drawArchDecal(data->gfxByStyle, decal); } // Draw Groups. for (auto const &decal : groupDecals) { - drawDecal(data->decals, decal); + drawArchDecal(data->gfxByStyle, decal); } // Swap over. - rendererDataLock_.lock(); - rendererData_ = std::move(data); - rendererDataLock_.unlock(); + { + QMutexLocker lock(&rendererDataLock_); + + // If we're not re-rendering any highlights/selections, let's + // copy them over from teh current object. + if (!highlightedOrSelectedChanged) { + data->gfxSelected = rendererData_->gfxSelected; + for (int i = 0; i < 8; i++) + data->gfxHighlighted[i] = rendererData_->gfxHighlighted[i]; + } + + rendererData_ = std::move(data); + } } - rendererDataLock_.lock(); - if (decalsChanged || highlightedOrSelectedChanged) { - rendererData_->selected.clear(); - for (auto &decal : selectedItems) { - drawDecal(rendererData_->selected, decal); + if (highlightedOrSelectedChanged) { + QMutexLocker locker(&rendererDataLock_); + + // Render selected. + rendererData_->gfxSelected.clear(); + for (auto &decal : selectedDecals) { + drawDecal(rendererData_->gfxSelected, decal); } + + // Render highlighted. for (int i = 0; i < 8; i++) { - rendererData_->highlighted[i].clear(); - for (auto &decal : highlightedItems[i]) { - drawDecal(rendererData_->highlighted[i], decal); + rendererData_->gfxHighlighted[i].clear(); + for (auto &decal : highlightedDecals[i]) { + drawDecal(rendererData_->gfxHighlighted[i], decal); } } } - rendererDataLock_.unlock(); } void FPGAViewWidget::onSelectedArchItem(std::vector<DecalXY> decals) { - rendererArgsLock_.lock(); - rendererArgs_->selectedItems = decals; - rendererArgs_->highlightedOrSelectedChanged = true; - rendererArgsLock_.unlock(); + { + QMutexLocker locker(&rendererArgsLock_); + rendererArgs_->selectedDecals = decals; + rendererArgs_->highlightedOrSelectedChanged = true; + } pokeRenderer(); } void FPGAViewWidget::onHighlightGroupChanged(std::vector<DecalXY> decals, int group) { - rendererArgsLock_.lock(); - rendererArgs_->highlightedItems[group] = decals; - rendererArgs_->highlightedOrSelectedChanged = true; - rendererArgsLock_.unlock(); + { + QMutexLocker locker(&rendererArgsLock_); + rendererArgs_->highlightedDecals[group] = decals; + rendererArgs_->highlightedOrSelectedChanged = true; + } pokeRenderer(); } void FPGAViewWidget::resizeGL(int width, int height) {} -void FPGAViewWidget::mousePressEvent(QMouseEvent *event) { lastPos_ = event->pos(); } +void FPGAViewWidget::mousePressEvent(QMouseEvent *event) { lastDragPos_ = event->pos(); } // Invert the projection matrix to calculate screen/mouse to world/grid // coordinates. @@ -561,9 +369,9 @@ QVector4D FPGAViewWidget::mouseToWorldCoordinates(int x, int y) void FPGAViewWidget::mouseMoveEvent(QMouseEvent *event) { - const int dx = event->x() - lastPos_.x(); - const int dy = event->y() - lastPos_.y(); - lastPos_ = event->pos(); + const int dx = event->x() - lastDragPos_.x(); + const int dy = event->y() - lastDragPos_.y(); + lastDragPos_ = event->pos(); auto world = mouseToWorldCoordinates(dx, dy); viewMove_.translate(world.x(), -world.y()); @@ -575,21 +383,32 @@ void FPGAViewWidget::wheelEvent(QWheelEvent *event) { QPoint degree = event->angleDelta() / 8; - if (!degree.isNull()) { - - 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(); + if (!degree.isNull()) + zoom(degree.y()); +} + +void FPGAViewWidget::zoom(int level) +{ + if (zoom_ < zoomNear_) { + zoom_ = zoomNear_; + } else if (zoom_ < zoomLvl1_) { + zoom_ -= level / 10.0; + } else if (zoom_ < zoomLvl2_) { + zoom_ -= level / 5.0; + } else if (zoom_ < zoomFar_) { + zoom_ -= level; + } else { + zoom_ = zoomFar_; } + update(); } +void FPGAViewWidget::zoomIn() { zoom(10); } + +void FPGAViewWidget::zoomOut() { zoom(-10); } + +void FPGAViewWidget::zoomSelected() {} + +void FPGAViewWidget::zoomOutbound() {} + NEXTPNR_NAMESPACE_END diff --git a/gui/fpgaviewwidget.h b/gui/fpgaviewwidget.h index b87c5d0a..260ebf05 100644 --- a/gui/fpgaviewwidget.h +++ b/gui/fpgaviewwidget.h @@ -33,183 +33,10 @@ #include <QWaitCondition> #include "nextpnr.h" +#include "lineshader.h" NEXTPNR_NAMESPACE_BEGIN -// Vertex2DPOD is a structure of X, Y coordinates that can be passed to OpenGL -// directly. -NPNR_PACKED_STRUCT(struct Vertex2DPOD { - GLfloat x; - GLfloat y; - - Vertex2DPOD(GLfloat X, GLfloat Y) : x(X), y(Y) {} -}); - -// LineShaderData is a built set of vertices that can be rendered by the -// LineShader. -// Each LineShaderData can have its' own color and thickness. -struct LineShaderData -{ - std::vector<Vertex2DPOD> vertices; - std::vector<Vertex2DPOD> normals; - std::vector<GLfloat> miters; - std::vector<GLuint> indices; - - LineShaderData(void) {} - - 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 -// ShaderLine for GPU rendering. -class PolyLine -{ - private: - std::vector<QVector2D> points_; - bool closed_; - - void buildPoint(LineShaderData *building, const QVector2D *prev, const QVector2D *cur, const QVector2D *next) const; - - public: - // Create an empty PolyLine. - PolyLine(bool closed = false) : closed_(closed) {} - - // Create a non-closed polyline consisting of one segment. - PolyLine(float x0, float y0, float x1, float y1) : closed_(false) - { - point(x0, y0); - point(x1, y1); - } - - // Add a point to the PolyLine. - void point(float x, float y) { points_.push_back(QVector2D(x, y)); } - - // Built PolyLine to shader data. - void build(LineShaderData &target) const; - - // Set whether line is closed (ie. a loop). - void setClosed(bool closed) { closed_ = closed; } -}; - -// LineShader is an OpenGL shader program that renders LineShaderData on the -// GPU. -// The LineShader expects two vertices per line point. It will push those -// vertices along the given normal * miter. This is used to 'stretch' the line -// to be as wide as the given thickness. The normal and miter are calculated -// by the PolyLine build method in order to construct a constant thickness line -// with miter edge joints. -// -// +------+------+ -// -// | -// PolyLine.build() -// | -// V -// -// ^ ^ ^ -// | | | <--- normal vectors (x2, pointing in the same -// +/+----+/+----+/+ direction) -// -// | -// vertex shader -// | -// V -// -// +------+------+ ^ by normal * miter * thickness/2 -// | | | -// +------+------+ V by normal * miter * thickness/2 -// -// (miter is flipped for every second vertex generated) -class LineShader -{ - private: - QObject *parent_; - QOpenGLShaderProgram *program_; - - // GL attribute locations. - struct - { - // original position of line vertex - GLuint position; - // normal by which vertex should be translated - GLuint normal; - // scalar defining: - // - how stretched the normal vector should be to - // compensate for bends - // - which way the normal should be applied (+1 for one vertex, -1 - // for the other) - GLuint miter; - } attributes_; - - // GL buffers - struct - { - QOpenGLBuffer position; - QOpenGLBuffer normal; - QOpenGLBuffer miter; - QOpenGLBuffer index; - } buffers_; - - // GL uniform locations. - struct - { - // combines m/v/p matrix to apply - GLuint projection; - // desired thickness of line - GLuint thickness; - // color of line - GLuint color; - } uniforms_; - - QOpenGLVertexArrayObject vao_; - - public: - LineShader(QObject *parent) : parent_(parent), program_(nullptr) - { - buffers_.position = QOpenGLBuffer(QOpenGLBuffer::VertexBuffer); - buffers_.position.setUsagePattern(QOpenGLBuffer::StaticDraw); - - buffers_.normal = QOpenGLBuffer(QOpenGLBuffer::VertexBuffer); - buffers_.normal.setUsagePattern(QOpenGLBuffer::StaticDraw); - - buffers_.miter = QOpenGLBuffer(QOpenGLBuffer::VertexBuffer); - buffers_.miter.setUsagePattern(QOpenGLBuffer::StaticDraw); - - buffers_.index = QOpenGLBuffer(QOpenGLBuffer::IndexBuffer); - buffers_.index.setUsagePattern(QOpenGLBuffer::StaticDraw); - } - - static constexpr const char *vertexShaderSource_ = - "#version 110\n" - "attribute highp vec2 position;\n" - "attribute highp vec2 normal;\n" - "attribute highp float miter;\n" - "uniform highp float thickness;\n" - "uniform highp mat4 projection;\n" - "void main() {\n" - " vec2 p = position.xy + vec2(normal * thickness/2.0 / miter);\n" - " gl_Position = projection * vec4(p, 0.0, 1.0);\n" - "}\n"; - - static constexpr const char *fragmentShaderSource_ = "#version 110\n" - "uniform lowp vec4 color;\n" - "void main() {\n" - " gl_FragColor = color;\n" - "}\n"; - - // Must be called on initialization. - bool compile(void); - - // Render a LineShaderData with a given M/V/P transformation. - void draw(const LineShaderData &data, const QColor &color, float thickness, const QMatrix4x4 &projection); -}; - class PeriodicRunner : public QThread { Q_OBJECT @@ -267,53 +94,43 @@ class FPGAViewWidget : public QOpenGLWidget, protected QOpenGLFunctions FPGAViewWidget(QWidget *parent = 0); ~FPGAViewWidget(); - QSize minimumSizeHint() const override; - QSize sizeHint() const override; - - void setXTranslation(float t_x); - void setYTranslation(float t_y); - void setZoom(float t_z); - - void xRotationChanged(int angle); - void yRotationChanged(int angle); - void zRotationChanged(int angle); - protected: + // Qt callbacks. void initializeGL() Q_DECL_OVERRIDE; void paintGL() Q_DECL_OVERRIDE; void resizeGL(int width, int height) Q_DECL_OVERRIDE; void mousePressEvent(QMouseEvent *event) Q_DECL_OVERRIDE; void mouseMoveEvent(QMouseEvent *event) Q_DECL_OVERRIDE; void wheelEvent(QWheelEvent *event) Q_DECL_OVERRIDE; - void drawDecal(LineShaderData &data, const DecalXY &decal); - void drawDecal(LineShaderData out[], const DecalXY &decal); + QSize minimumSizeHint() const override; + QSize sizeHint() const override; + + public Q_SLOTS: void newContext(Context *ctx); void onSelectedArchItem(std::vector<DecalXY> decals); void onHighlightGroupChanged(std::vector<DecalXY> decals, int group); void pokeRenderer(void); + void zoomIn(); + void zoomOut(); + void zoomSelected(); + void zoomOutbound(); private: - void renderLines(void); - - QPoint lastPos_; - 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; Context *ctx_; QTimer paintTimer_; - std::unique_ptr<PeriodicRunner> renderRunner_; + QPoint lastDragPos_; + LineShader lineShader_; + QMatrix4x4 viewMove_; + float zoom_; + struct { QColor background; @@ -328,22 +145,29 @@ class FPGAViewWidget : public QOpenGLWidget, protected QOpenGLFunctions struct RendererData { - LineShaderData decals[4]; - LineShaderData selected; - LineShaderData highlighted[8]; + LineShaderData gfxByStyle[GraphicElement::STYLE_MAX]; + LineShaderData gfxSelected; + LineShaderData gfxHighlighted[8]; }; + std::unique_ptr<RendererData> rendererData_; + QMutex rendererDataLock_; struct RendererArgs { - std::vector<DecalXY> selectedItems; - std::vector<DecalXY> highlightedItems[8]; + std::vector<DecalXY> selectedDecals; + std::vector<DecalXY> highlightedDecals[8]; bool highlightedOrSelectedChanged; }; - - std::unique_ptr<RendererData> rendererData_; - QMutex rendererDataLock_; std::unique_ptr<RendererArgs> rendererArgs_; QMutex rendererArgsLock_; + + void zoom(int level); + void renderLines(void); + void drawGraphicElement(LineShaderData &out, const GraphicElement &el, float x, float y); + void drawDecal(LineShaderData &out, const DecalXY &decal); + void drawArchDecal(LineShaderData out[GraphicElement::STYLE_MAX], const DecalXY &decal); + QVector4D mouseToWorldCoordinates(int x, int y); + QMatrix4x4 getProjection(void); }; NEXTPNR_NAMESPACE_END diff --git a/gui/ice40/mainwindow.cc b/gui/ice40/mainwindow.cc index 810a98ae..f06971b6 100644 --- a/gui/ice40/mainwindow.cc +++ b/gui/ice40/mainwindow.cc @@ -159,6 +159,8 @@ void MainWindow::createMenu() taskToolBar->addAction(actionPlay);
taskToolBar->addAction(actionPause);
taskToolBar->addAction(actionStop);
+
+ createGraphicsBar();
}
#if defined(_MSC_VER)
diff --git a/gui/ice40/worker.cc b/gui/ice40/worker.cc index 09093ec8..fd46ecad 100644 --- a/gui/ice40/worker.cc +++ b/gui/ice40/worker.cc @@ -109,7 +109,6 @@ void Worker::budget(double freq) Q_EMIT taskStarted(); try { ctx->target_freq = freq; - assign_budget(ctx); Q_EMIT budget_finish(true); } catch (WorkerInterruptionRequested) { Q_EMIT taskCanceled(); @@ -121,6 +120,8 @@ void Worker::place(bool timing_driven) Q_EMIT taskStarted(); try { ctx->timing_driven = timing_driven; + log_info("Assigned budget %0.2f MHz", ctx->target_freq / 1e6); + assign_budget(ctx); Q_EMIT place_finished(ctx->place()); } catch (WorkerInterruptionRequested) { Q_EMIT taskCanceled(); diff --git a/gui/lineshader.cc b/gui/lineshader.cc new file mode 100644 index 00000000..94a7a010 --- /dev/null +++ b/gui/lineshader.cc @@ -0,0 +1,236 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Serge Bazanski <q3k@symbioticeda.com> + * + * 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 "log.h" +#include "lineshader.h" + +NEXTPNR_NAMESPACE_BEGIN + +void PolyLine::buildPoint(LineShaderData *building, const QVector2D *prev, const QVector2D *cur, + const QVector2D *next) const +{ + // buildPoint emits two vertices per line point, along with normals to move + // them the right directio when rendering and miter to compensate for + // bends. + + if (cur == nullptr) { + // BUG + return; + } + + if (prev == nullptr && next == nullptr) { + // BUG + return; + } + + // TODO(q3k): fast path for vertical/horizontal lines? + + // TODO(q3k): consider moving some of the linear algebra to the GPU, + // they're better at this than poor old CPUs. + + // Build two unit vectors pointing in the direction of the two segments + // defined by (prev, cur) and (cur, next) + QVector2D dprev, dnext; + if (prev == nullptr) { + dnext = *next - *cur; + dprev = dnext; + } else if (next == nullptr) { + dprev = *cur - *prev; + dnext = dprev; + } else { + dprev = *cur - *prev; + dnext = *next - *cur; + } + dprev.normalize(); + dnext.normalize(); + + // Calculate tangent unit vector. + QVector2D tangent(dprev + dnext); + tangent.normalize(); + + // Calculate normal to tangent - this is the line on which the vectors need + // to be pushed to build a thickened line. + const QVector2D tangent_normal = QVector2D(-tangent.y(), tangent.x()); + + // Calculate normal to one of the lines. + const QVector2D dprev_normal = QVector2D(-dprev.y(), dprev.x()); + // https://people.eecs.berkeley.edu/~sequin/CS184/IMGS/Sweep_PolyLine.jpg + // (the ^-1 is performed in the shader) + const float miter = QVector2D::dotProduct(tangent_normal, dprev_normal); + + const float x = cur->x(); + const float y = cur->y(); + const float mx = tangent_normal.x(); + const float my = tangent_normal.y(); + + // Push back 'left' vertex. + building->vertices.push_back(Vertex2DPOD(x, y)); + building->normals.push_back(Vertex2DPOD(mx, my)); + building->miters.push_back(miter); + + // Push back 'right' vertex. + building->vertices.push_back(Vertex2DPOD(x, y)); + building->normals.push_back(Vertex2DPOD(mx, my)); + building->miters.push_back(-miter); +} + +void PolyLine::build(LineShaderData &target) const +{ + if (points_.size() < 2) { + return; + } + const QVector2D *first = &points_.front(); + const QVector2D *last = &points_.back(); + + // Index number of vertices, used to build the index buffer. + unsigned int startIndex = target.vertices.size(); + unsigned int index = startIndex; + + // For every point on the line, call buildPoint with (prev, point, next). + // If we're building a closed line, prev/next wrap around. Otherwise + // they are passed as nullptr and buildPoint interprets that accordinglu. + const QVector2D *prev = nullptr; + + // Loop iterator used to ensure next is valid. + unsigned int i = 0; + for (const QVector2D &point : points_) { + const QVector2D *next = nullptr; + if (++i < points_.size()) { + next = (&point + 1); + } + + // If the line is closed, wrap around. Otherwise, pass nullptr. + if (prev == nullptr && closed_) { + buildPoint(&target, last, &point, next); + } else if (next == nullptr && closed_) { + buildPoint(&target, prev, &point, first); + } else { + buildPoint(&target, prev, &point, next); + } + + // If we have a prev point relative to cur, build a pair of triangles + // to render vertices into lines. + if (prev != nullptr) { + target.indices.push_back(index); + target.indices.push_back(index + 1); + target.indices.push_back(index + 2); + + target.indices.push_back(index + 2); + target.indices.push_back(index + 1); + target.indices.push_back(index + 3); + + index += 2; + } + prev = &point; + } + + // If we're closed, build two more vertices that loop the line around. + if (closed_) { + target.indices.push_back(index); + target.indices.push_back(index + 1); + target.indices.push_back(startIndex); + + target.indices.push_back(startIndex); + target.indices.push_back(index + 1); + target.indices.push_back(startIndex + 1); + } +} + +bool LineShader::compile(void) +{ + program_ = new QOpenGLShaderProgram(parent_); + program_->addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSource_); + program_->addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSource_); + if (!program_->link()) { + printf("could not link program: %s\n", program_->log().toStdString().c_str()); + return false; + } + + if (!vao_.create()) + log_abort(); + vao_.bind(); + + if (!buffers_.position.create()) + log_abort(); + if (!buffers_.normal.create()) + log_abort(); + if (!buffers_.miter.create()) + log_abort(); + if (!buffers_.index.create()) + log_abort(); + + attributes_.position = program_->attributeLocation("position"); + attributes_.normal = program_->attributeLocation("normal"); + attributes_.miter = program_->attributeLocation("miter"); + uniforms_.thickness = program_->uniformLocation("thickness"); + uniforms_.projection = program_->uniformLocation("projection"); + uniforms_.color = program_->uniformLocation("color"); + + vao_.release(); + return true; +} + +void LineShader::draw(const LineShaderData &line, const QColor &color, float thickness, const QMatrix4x4 &projection) +{ + auto gl = QOpenGLContext::currentContext()->functions(); + if (line.vertices.size() == 0) + return; + vao_.bind(); + program_->bind(); + + buffers_.position.bind(); + buffers_.position.allocate(&line.vertices[0], sizeof(Vertex2DPOD) * line.vertices.size()); + + buffers_.normal.bind(); + buffers_.normal.allocate(&line.normals[0], sizeof(Vertex2DPOD) * line.normals.size()); + + buffers_.miter.bind(); + buffers_.miter.allocate(&line.miters[0], sizeof(GLfloat) * line.miters.size()); + + buffers_.index.bind(); + buffers_.index.allocate(&line.indices[0], sizeof(GLuint) * line.indices.size()); + + program_->setUniformValue(uniforms_.projection, projection); + program_->setUniformValue(uniforms_.thickness, thickness); + program_->setUniformValue(uniforms_.color, color.redF(), color.greenF(), color.blueF(), color.alphaF()); + + buffers_.position.bind(); + program_->enableAttributeArray("position"); + gl->glVertexAttribPointer(attributes_.position, 2, GL_FLOAT, GL_FALSE, 0, (void *)0); + + buffers_.normal.bind(); + program_->enableAttributeArray("normal"); + gl->glVertexAttribPointer(attributes_.normal, 2, GL_FLOAT, GL_FALSE, 0, (void *)0); + + buffers_.miter.bind(); + program_->enableAttributeArray("miter"); + gl->glVertexAttribPointer(attributes_.miter, 1, GL_FLOAT, GL_FALSE, 0, (void *)0); + + buffers_.index.bind(); + gl->glDrawElements(GL_TRIANGLES, line.indices.size(), GL_UNSIGNED_INT, (void *)0); + + program_->disableAttributeArray("miter"); + program_->disableAttributeArray("normal"); + program_->disableAttributeArray("position"); + + program_->release(); + vao_.release(); +} + +NEXTPNR_NAMESPACE_END diff --git a/gui/lineshader.h b/gui/lineshader.h new file mode 100644 index 00000000..3f4c4057 --- /dev/null +++ b/gui/lineshader.h @@ -0,0 +1,209 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Serge Bazanski <q3k@symbioticeda.com> + * + * 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 LINESHADER_H +#define LINESHADER_H + +#include <QOpenGLBuffer> +#include <QOpenGLFunctions> +#include <QOpenGLShaderProgram> +#include <QOpenGLVertexArrayObject> +#include <QOpenGLWidget> + +#include "nextpnr.h" + +NEXTPNR_NAMESPACE_BEGIN + +// Vertex2DPOD is a structure of X, Y coordinates that can be passed to OpenGL +// directly. +NPNR_PACKED_STRUCT(struct Vertex2DPOD { + GLfloat x; + GLfloat y; + + Vertex2DPOD(GLfloat X, GLfloat Y) : x(X), y(Y) {} +}); + +// LineShaderData is a built set of vertices that can be rendered by the +// LineShader. +// Each LineShaderData can have its' own color and thickness. +struct LineShaderData +{ + std::vector<Vertex2DPOD> vertices; + std::vector<Vertex2DPOD> normals; + std::vector<GLfloat> miters; + std::vector<GLuint> indices; + + LineShaderData(void) {} + + 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 +// ShaderLine for GPU rendering. +class PolyLine +{ + private: + std::vector<QVector2D> points_; + bool closed_; + + void buildPoint(LineShaderData *building, const QVector2D *prev, const QVector2D *cur, const QVector2D *next) const; + + public: + // Create an empty PolyLine. + PolyLine(bool closed = false) : closed_(closed) {} + + // Create a non-closed polyline consisting of one segment. + PolyLine(float x0, float y0, float x1, float y1) : closed_(false) + { + point(x0, y0); + point(x1, y1); + } + + // Add a point to the PolyLine. + void point(float x, float y) { points_.push_back(QVector2D(x, y)); } + + // Built PolyLine to shader data. + void build(LineShaderData &target) const; + + // Set whether line is closed (ie. a loop). + void setClosed(bool closed) { closed_ = closed; } +}; + +// LineShader is an OpenGL shader program that renders LineShaderData on the +// GPU. +// The LineShader expects two vertices per line point. It will push those +// vertices along the given normal * miter. This is used to 'stretch' the line +// to be as wide as the given thickness. The normal and miter are calculated +// by the PolyLine build method in order to construct a constant thickness line +// with miter edge joints. +// +// +------+------+ +// +// | +// PolyLine.build() +// | +// V +// +// ^ ^ ^ +// | | | <--- normal vectors (x2, pointing in the same +// +/+----+/+----+/+ direction) +// +// | +// vertex shader +// | +// V +// +// +------+------+ ^ by normal * miter * thickness/2 +// | | | +// +------+------+ V by normal * miter * thickness/2 +// +// (miter is flipped for every second vertex generated) +class LineShader +{ + private: + QObject *parent_; + QOpenGLShaderProgram *program_; + + // GL attribute locations. + struct + { + // original position of line vertex + GLuint position; + // normal by which vertex should be translated + GLuint normal; + // scalar defining: + // - how stretched the normal vector should be to + // compensate for bends + // - which way the normal should be applied (+1 for one vertex, -1 + // for the other) + GLuint miter; + } attributes_; + + // GL buffers + struct + { + QOpenGLBuffer position; + QOpenGLBuffer normal; + QOpenGLBuffer miter; + QOpenGLBuffer index; + } buffers_; + + // GL uniform locations. + struct + { + // combines m/v/p matrix to apply + GLuint projection; + // desired thickness of line + GLuint thickness; + // color of line + GLuint color; + } uniforms_; + + QOpenGLVertexArrayObject vao_; + + public: + LineShader(QObject *parent) : parent_(parent), program_(nullptr) + { + buffers_.position = QOpenGLBuffer(QOpenGLBuffer::VertexBuffer); + buffers_.position.setUsagePattern(QOpenGLBuffer::StaticDraw); + + buffers_.normal = QOpenGLBuffer(QOpenGLBuffer::VertexBuffer); + buffers_.normal.setUsagePattern(QOpenGLBuffer::StaticDraw); + + buffers_.miter = QOpenGLBuffer(QOpenGLBuffer::VertexBuffer); + buffers_.miter.setUsagePattern(QOpenGLBuffer::StaticDraw); + + buffers_.index = QOpenGLBuffer(QOpenGLBuffer::IndexBuffer); + buffers_.index.setUsagePattern(QOpenGLBuffer::StaticDraw); + } + + static constexpr const char *vertexShaderSource_ = + "#version 110\n" + "attribute highp vec2 position;\n" + "attribute highp vec2 normal;\n" + "attribute highp float miter;\n" + "uniform highp float thickness;\n" + "uniform highp mat4 projection;\n" + "void main() {\n" + " vec2 p = position.xy + vec2(normal * thickness/2.0 / miter);\n" + " gl_Position = projection * vec4(p, 0.0, 1.0);\n" + "}\n"; + + static constexpr const char *fragmentShaderSource_ = "#version 110\n" + "uniform lowp vec4 color;\n" + "void main() {\n" + " gl_FragColor = color;\n" + "}\n"; + + // Must be called on initialization. + bool compile(void); + + // Render a LineShaderData with a given M/V/P transformation. + void draw(const LineShaderData &data, const QColor &color, float thickness, const QMatrix4x4 &projection); +}; + +NEXTPNR_NAMESPACE_END + +#endif diff --git a/gui/resources/shape_handles.png b/gui/resources/shape_handles.png Binary files differnew file mode 100644 index 00000000..ce27fe3a --- /dev/null +++ b/gui/resources/shape_handles.png diff --git a/gui/resources/shape_square.png b/gui/resources/shape_square.png Binary files differnew file mode 100644 index 00000000..33af0460 --- /dev/null +++ b/gui/resources/shape_square.png diff --git a/gui/resources/zoom_in.png b/gui/resources/zoom_in.png Binary files differnew file mode 100644 index 00000000..cdf0a52f --- /dev/null +++ b/gui/resources/zoom_in.png diff --git a/gui/resources/zoom_out.png b/gui/resources/zoom_out.png Binary files differnew file mode 100644 index 00000000..07bf98a7 --- /dev/null +++ b/gui/resources/zoom_out.png diff --git a/ice40/arch.cc b/ice40/arch.cc index 51fa6472..4e9baf7e 100644 --- a/ice40/arch.cc +++ b/ice40/arch.cc @@ -2,6 +2,7 @@ * nextpnr -- Next Generation Place and Route * * Copyright (C) 2018 Clifford Wolf <clifford@symbioticeda.com> + * Copyright (C) 2018 Serge Bazanski <q3k@symbioticeda.com> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -187,6 +188,13 @@ Arch::Arch(ArchArgs args) : args(args) id_i3 = id("I3"); id_dff_en = id("DFF_ENABLE"); id_neg_clk = id("NEG_CLK"); + id_cin = id("CIN"); + id_cout = id("COUT"); + id_o = id("O"); + id_lo = id("LO"); + id_icestorm_ram = id("ICESTORM_RAM"); + id_rclk = id("RCLK"); + id_wclk = id("WCLK"); } // ----------------------------------------------------------------------- @@ -291,23 +299,6 @@ BelRange Arch::getBelsByTile(int x, int y) const return br; } -BelRange Arch::getBelsAtSameTile(BelId bel) const -{ - BelRange br; - NPNR_ASSERT(bel != BelId()); - int x = chip_info->bel_data[bel.index].x; - int y = chip_info->bel_data[bel.index].y; - int start = bel.index, end = bel.index; - while (start >= 0 && chip_info->bel_data[start].x == x && chip_info->bel_data[start].y == y) - start--; - start++; - br.b.cursor = start; - while (end < chip_info->num_bels && chip_info->bel_data[end].x == x && chip_info->bel_data[end].y == y) - end++; - br.e.cursor = end; - return br; -} - PortType Arch::getBelPinType(BelId bel, PortPin pin) const { NPNR_ASSERT(bel != BelId()); @@ -331,11 +322,12 @@ WireId Arch::getBelPinWire(BelId bel, PortPin pin) const int num_bel_wires = chip_info->bel_data[bel.index].num_bel_wires; const BelWirePOD *bel_wires = chip_info->bel_data[bel.index].bel_wires.get(); - for (int i = 0; i < num_bel_wires; i++) + for (int i = 0; i < num_bel_wires; i++) { if (bel_wires[i].port == pin) { ret.index = bel_wires[i].wire_index; break; } + } return ret; } @@ -448,11 +440,103 @@ GroupId Arch::getGroupByName(IdString name) const return GroupId(); } -IdString Arch::getGroupName(GroupId group) const { return IdString(); } +IdString Arch::getGroupName(GroupId group) const +{ + std::string suffix; + + switch (group.type) { + case GroupId::TYPE_FRAME: + suffix = "tile"; + break; + case GroupId::TYPE_MAIN_SW: + suffix = "main_sw"; + break; + case GroupId::TYPE_LOCAL_SW: + suffix = "local_sw"; + break; + case GroupId::TYPE_LC0_SW: + suffix = "lc0_sw"; + break; + case GroupId::TYPE_LC1_SW: + suffix = "lc1_sw"; + break; + case GroupId::TYPE_LC2_SW: + suffix = "lc2_sw"; + break; + case GroupId::TYPE_LC3_SW: + suffix = "lc3_sw"; + break; + case GroupId::TYPE_LC4_SW: + suffix = "lc4_sw"; + break; + case GroupId::TYPE_LC5_SW: + suffix = "lc5_sw"; + break; + case GroupId::TYPE_LC6_SW: + suffix = "lc6_sw"; + break; + case GroupId::TYPE_LC7_SW: + suffix = "lc7_sw"; + break; + default: + return IdString(); + } + + return id("X" + std::to_string(group.x) + "/Y" + std::to_string(group.y) + "/" + suffix); +} std::vector<GroupId> Arch::getGroups() const { std::vector<GroupId> ret; + + for (int y = 0; y < chip_info->height; y++) { + for (int x = 0; x < chip_info->width; x++) { + TileType type = chip_info->tile_grid[y * chip_info->width + x]; + if (type == TILE_NONE) + continue; + + GroupId group; + group.type = GroupId::TYPE_FRAME; + group.x = x; + group.y = y; + // ret.push_back(group); + + group.type = GroupId::TYPE_MAIN_SW; + ret.push_back(group); + + group.type = GroupId::TYPE_LOCAL_SW; + ret.push_back(group); + +#if 0 + if (type == TILE_LOGIC) + { + group.type = GroupId::TYPE_LC0_SW; + ret.push_back(group); + + group.type = GroupId::TYPE_LC1_SW; + ret.push_back(group); + + group.type = GroupId::TYPE_LC2_SW; + ret.push_back(group); + + group.type = GroupId::TYPE_LC3_SW; + ret.push_back(group); + + group.type = GroupId::TYPE_LC4_SW; + ret.push_back(group); + + group.type = GroupId::TYPE_LC5_SW; + ret.push_back(group); + + group.type = GroupId::TYPE_LC6_SW; + ret.push_back(group); + + group.type = GroupId::TYPE_LC7_SW; + ret.push_back(group); + } +#endif + } + } return ret; } @@ -482,14 +566,6 @@ std::vector<GroupId> Arch::getGroupGroups(GroupId group) const // ----------------------------------------------------------------------- -void Arch::estimatePosition(BelId bel, int &x, int &y, bool &gb) const -{ - NPNR_ASSERT(bel != BelId()); - x = chip_info->bel_data[bel.index].x; - y = chip_info->bel_data[bel.index].y; - gb = chip_info->bel_data[bel.index].type == TYPE_SB_GB; -} - delay_t Arch::estimateDelay(WireId src, WireId dst) const { NPNR_ASSERT(src != WireId()); @@ -511,10 +587,18 @@ delay_t Arch::estimateDelay(WireId src, WireId dst) const return xscale * abs(xd) + yscale * abs(yd) + offset; } -delay_t Arch::getBudgetOverride(const PortRef& pr, delay_t v) const +delay_t Arch::getBudgetOverride(NetInfo *net_info, int user_idx, delay_t budget) const { - if (pr.port == id("COUT")) return 0; - return v; + const auto& driver = net_info->driver; + if (driver.port == id_cout) { + const auto& sink = net_info->users[user_idx]; + auto driver_loc = getBelLocation(driver.cell->bel); + auto sink_loc = getBelLocation(sink.cell->bel); + if (driver_loc.y == sink_loc.y) + return 0; + return 250; + } + return budget; } // ----------------------------------------------------------------------- @@ -574,22 +658,70 @@ std::vector<GraphicElement> Arch::getDecalGraphics(DecalId decal) const std::vector<GraphicElement> 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); - } + /* nothing */ + } + + if (decal.type == DecalId::TYPE_GROUP) { + int type = (decal.index >> 16) & 255; + int x = (decal.index >> 8) & 255; + int y = decal.index & 255; + + if (type == GroupId::TYPE_FRAME) { + GraphicElement el; + el.type = GraphicElement::TYPE_LINE; + el.style = GraphicElement::STYLE_FRAME; + + el.x1 = x + 0.01, el.x2 = x + 0.02, el.y1 = y + 0.01, el.y2 = y + 0.01; + ret.push_back(el); + el.x1 = x + 0.01, el.x2 = x + 0.01, el.y1 = y + 0.01, el.y2 = y + 0.02; + ret.push_back(el); + + el.x1 = x + 0.99, el.x2 = x + 0.98, el.y1 = y + 0.01, el.y2 = y + 0.01; + ret.push_back(el); + el.x1 = x + 0.99, el.x2 = x + 0.99, el.y1 = y + 0.01, el.y2 = y + 0.02; + ret.push_back(el); + + el.x1 = x + 0.99, el.x2 = x + 0.98, el.y1 = y + 0.99, el.y2 = y + 0.99; + ret.push_back(el); + el.x1 = x + 0.99, el.x2 = x + 0.99, el.y1 = y + 0.99, el.y2 = y + 0.98; + ret.push_back(el); + + el.x1 = x + 0.01, el.x2 = x + 0.02, el.y1 = y + 0.99, el.y2 = y + 0.99; + ret.push_back(el); + el.x1 = x + 0.01, el.x2 = x + 0.01, el.y1 = y + 0.99, el.y2 = y + 0.98; + ret.push_back(el); + } + + if (type == GroupId::TYPE_MAIN_SW) { + GraphicElement el; + el.type = GraphicElement::TYPE_BOX; + el.style = GraphicElement::STYLE_FRAME; + + el.x1 = x + main_swbox_x1; + el.x2 = x + main_swbox_x2; + el.y1 = y + main_swbox_y1; + el.y2 = y + main_swbox_y2; + ret.push_back(el); + } + + if (type == GroupId::TYPE_LOCAL_SW) { + GraphicElement el; + el.type = GraphicElement::TYPE_BOX; + el.style = GraphicElement::STYLE_FRAME; + + el.x1 = x + local_swbox_x1; + el.x2 = x + local_swbox_x2; + el.y1 = y + local_swbox_y1; + el.y2 = y + local_swbox_y2; + ret.push_back(el); + } } if (decal.type == DecalId::TYPE_WIRE) { int n = chip_info->wire_data[decal.index].num_segments; const WireSegmentPOD *p = chip_info->wire_data[decal.index].segments.get(); - GraphicElement::style_t style = decal.active ? GraphicElement::G_ACTIVE : GraphicElement::G_INACTIVE; + GraphicElement::style_t style = decal.active ? GraphicElement::STYLE_ACTIVE : GraphicElement::STYLE_INACTIVE; for (int i = 0; i < n; i++) gfxTileWire(ret, p[i].x, p[i].y, GfxTileWireId(p[i].index), style); @@ -597,7 +729,7 @@ std::vector<GraphicElement> Arch::getDecalGraphics(DecalId decal) const if (decal.type == DecalId::TYPE_PIP) { const PipInfoPOD &p = chip_info->pip_data[decal.index]; - GraphicElement::style_t style = decal.active ? GraphicElement::G_ACTIVE : GraphicElement::G_HIDDEN; + GraphicElement::style_t style = decal.active ? GraphicElement::STYLE_ACTIVE : GraphicElement::STYLE_HIDDEN; gfxTilePip(ret, p.x, p.y, GfxTileWireId(p.src_seg), GfxTileWireId(p.dst_seg), style); } @@ -609,111 +741,40 @@ std::vector<GraphicElement> Arch::getDecalGraphics(DecalId decal) const if (bel_type == TYPE_ICESTORM_LC) { GraphicElement el; - el.type = GraphicElement::G_BOX; - el.style = decal.active ? GraphicElement::G_ACTIVE : GraphicElement::G_INACTIVE; + el.type = GraphicElement::TYPE_BOX; + el.style = decal.active ? GraphicElement::STYLE_ACTIVE : GraphicElement::STYLE_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); - } + GraphicElement el; + el.type = GraphicElement::TYPE_BOX; + el.style = decal.active ? GraphicElement::STYLE_ACTIVE : GraphicElement::STYLE_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 + + (4 * chip_info->bel_data[bel.index].z) * logic_cell_pitch; + el.y2 = chip_info->bel_data[bel.index].y + logic_cell_y2 + + (4 * chip_info->bel_data[bel.index].z + 3) * logic_cell_pitch; + 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.type = GraphicElement::TYPE_BOX; + el.style = decal.active ? GraphicElement::STYLE_ACTIVE : GraphicElement::STYLE_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; + el.y1 = chip_info->bel_data[bel.index].y + logic_cell_y1 + i; + el.y2 = chip_info->bel_data[bel.index].y + logic_cell_y2 + i + 7 * logic_cell_pitch; 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); } } } @@ -725,20 +786,28 @@ std::vector<GraphicElement> Arch::getDecalGraphics(DecalId decal) const bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, delay_t &delay) const { - if (cell->type == id("ICESTORM_LC")) { - if ((fromPort == id("I0") || fromPort == id("I1") || fromPort == id("I2") || fromPort == id("I3")) && - (toPort == id("O") || toPort == id("LO"))) { + if (cell->type == id_icestorm_lc) { + if ((fromPort == id_i0 || fromPort == id_i1 || fromPort == id_i2 || fromPort == id_i3) && + (toPort == id_o || toPort == id_lo)) { delay = 450; return true; - } else if (fromPort == id("CIN") && toPort == id("COUT")) { + } else if (fromPort == id_cin && toPort == id_cout) { delay = 120; return true; - } else if (fromPort == id("I1") && toPort == id("COUT")) { + } else if (fromPort == id_i1 && toPort == id_cout) { delay = 260; return true; - } else if (fromPort == id("I2") && toPort == id("COUT")) { + } else if (fromPort == id_i2 && toPort == id_cout) { delay = 230; return true; + } else if (fromPort == id_clk && toPort == id_o) { + delay = 540; + return true; + } + } else if (cell->type == id_icestorm_ram) { + if (fromPort == id_rclk) { + delay = 2140; + return true; } } return false; @@ -746,9 +815,14 @@ bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort IdString Arch::getPortClock(const CellInfo *cell, IdString port) const { - if (cell->type == id("ICESTORM_LC") && bool_or_default(cell->params, id("DFF_ENABLE"))) { - if (port != id("LO") && port != id("CIN") && port != id("COUT")) - return id("CLK"); + if (cell->type == id_icestorm_lc && cell->lcInfo.dffEnable) { + if (port != id_lo && port != id_cin && port != id_cout) + return id_clk; + } else if (cell->type == id_icestorm_ram) { + if (port.str(this)[0] == 'R') + return id_rclk; + else + return id_wclk; } return IdString(); } @@ -757,6 +831,8 @@ bool Arch::isClockPort(const CellInfo *cell, IdString port) const { if (cell->type == id("ICESTORM_LC") && port == id("CLK")) return true; + if (cell->type == id("ICESTORM_RAM") && (port == id("RCLK") || (port == id("WCLK")))) + return true; return false; } diff --git a/ice40/arch.h b/ice40/arch.h index 697d4142..e2d14e3c 100644 --- a/ice40/arch.h +++ b/ice40/arch.h @@ -83,10 +83,6 @@ NPNR_PACKED_STRUCT(struct WireInfoPOD { int32_t num_uphill, num_downhill; RelPtr<int32_t> pips_uphill, pips_downhill; - int32_t num_bels_downhill; - BelPortPOD bel_uphill; - RelPtr<BelPortPOD> bels_downhill; - int32_t num_bel_pins; RelPtr<BelPortPOD> bel_pins; @@ -453,8 +449,6 @@ struct Arch : BaseCtx bool getBelGlobalBuf(BelId bel) const { return chip_info->bel_data[bel.index].type == TYPE_SB_GB; } - BelRange getBelsAtSameTile(BelId bel) const NPNR_DEPRECATED; - BelType getBelType(BelId bel) const { NPNR_ASSERT(bel != BelId()); @@ -475,6 +469,8 @@ struct Arch : BaseCtx return id(chip_info->wire_data[wire.index].name.get()); } + IdString getWireType(WireId wire) const { return IdString(); } + uint32_t getWireChecksum(WireId wire) const { return wire.index; } void bindWire(WireId wire, IdString net, PlaceStrength strength) @@ -617,6 +613,8 @@ struct Arch : BaseCtx IdString getPipName(PipId pip) const; + IdString getPipType(PipId pip) const { return IdString(); } + uint32_t getPipChecksum(PipId pip) const { return pip.index; } WireId getPipSrcWire(PipId pip) const @@ -685,13 +683,12 @@ struct Arch : BaseCtx // ------------------------------------------------- - void estimatePosition(BelId bel, int &x, int &y, bool &gb) const NPNR_DEPRECATED; 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; } - delay_t getBudgetOverride(const PortRef& pr, delay_t v) const; + delay_t getBudgetOverride(NetInfo *net_info, int user_idx, delay_t budget) const; // ------------------------------------------------- @@ -747,6 +744,21 @@ struct Arch : BaseCtx IdString id_cen, id_clk, id_sr; IdString id_i0, id_i1, id_i2, id_i3; IdString id_dff_en, id_neg_clk; + IdString id_cin, id_cout; + IdString id_o, id_lo; + IdString id_icestorm_ram, id_rclk, id_wclk; + + // ------------------------------------------------- + BelPin getIOBSharingPLLPin(BelId pll, PortPin pll_pin) const + { + auto wire = getBelPinWire(pll, pll_pin); + for (auto src_bel : getWireBelPins(wire)) { + if (getBelType(src_bel.bel) == TYPE_SB_IO && src_bel.pin == PIN_D_IN_0) { + return src_bel; + } + } + NPNR_ASSERT_FALSE("Expected PLL pin to share an output with an SB_IO D_IN_{0,1}"); + } }; NEXTPNR_NAMESPACE_END diff --git a/ice40/arch_place.cc b/ice40/arch_place.cc index cf1276a7..b3404d6c 100644 --- a/ice40/arch_place.cc +++ b/ice40/arch_place.cc @@ -3,6 +3,7 @@ * * Copyright (C) 2018 Clifford Wolf <clifford@symbioticeda.com> * Copyright (C) 2018 David Shah <david@symbioticeda.com> + * Copyright (C) 2018 Serge Bazanski <q3k@symbioticeda.com> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -71,7 +72,8 @@ bool Arch::isBelLocationValid(BelId bel) const { if (getBelType(bel) == TYPE_ICESTORM_LC) { std::vector<const CellInfo *> bel_cells; - for (auto bel_other : getBelsAtSameTile(bel)) { + Loc bel_loc = getBelLocation(bel); + for (auto bel_other : getBelsByTile(bel_loc.x, bel_loc.y)) { IdString cell_other = getBoundBelCell(bel_other); if (cell_other != IdString()) { const CellInfo *ci_other = cells.at(cell_other).get(); @@ -94,8 +96,8 @@ bool Arch::isValidBelForCell(CellInfo *cell, BelId bel) const NPNR_ASSERT(getBelType(bel) == TYPE_ICESTORM_LC); std::vector<const CellInfo *> bel_cells; - - for (auto bel_other : getBelsAtSameTile(bel)) { + Loc bel_loc = getBelLocation(bel); + for (auto bel_other : getBelsByTile(bel_loc.x, bel_loc.y)) { IdString cell_other = getBoundBelCell(bel_other); if (cell_other != IdString() && bel_other != bel) { const CellInfo *ci_other = cells.at(cell_other).get(); @@ -106,6 +108,36 @@ bool Arch::isValidBelForCell(CellInfo *cell, BelId bel) const bel_cells.push_back(cell); return logicCellsCompatible(bel_cells); } else if (cell->type == id_sb_io) { + // Do not allow placement of input SB_IOs on blocks where there a PLL is outputting to. + + // Find shared PLL by looking for driving bel siblings from D_IN_0 + // that are a PLL clock output. + auto wire = getBelPinWire(bel, PIN_D_IN_0); + PortPin pll_bel_pin; + BelId pll_bel; + for (auto pin : getWireBelPins(wire)) { + if (pin.pin == PIN_PLLOUT_A || pin.pin == PIN_PLLOUT_B) { + pll_bel = pin.bel; + pll_bel_pin = pin.pin; + break; + } + } + // Is there a PLL that shares this IO buffer? + if (pll_bel.index != -1) { + auto pll_cell = getBoundBelCell(pll_bel); + // Is a PLL placed in this PLL bel? + if (pll_cell != IdString()) { + // Is the shared port driving a net? + auto pi = cells.at(pll_cell)->ports[portPinToId(pll_bel_pin)]; + if (pi.net != nullptr) { + // Are we perhaps a PAD INPUT Bel that can be placed here? + if (cells.at(pll_cell)->attrs[id("BEL_PAD_INPUT")] == getBelName(bel).str(this)) { + return true; + } + return false; + } + } + } return getBelPackagePin(bel) != ""; } else if (cell->type == id_sb_gb) { NPNR_ASSERT(cell->ports.at(id_glb_buf_out).net != nullptr); diff --git a/ice40/arch_pybindings.cc b/ice40/arch_pybindings.cc index 246d0f57..98164e87 100644 --- a/ice40/arch_pybindings.cc +++ b/ice40/arch_pybindings.cc @@ -79,8 +79,6 @@ void arch_wrap_python() conv_to_str<IdString>, conv_from_str<BelId>>::def_wrap(ctx_cls, "getConflictingBelCell"); fn_wrapper_0a<Context, decltype(&Context::getBels), &Context::getBels, wrap_context<BelRange>>::def_wrap(ctx_cls, "getBels"); - fn_wrapper_1a<Context, decltype(&Context::getBelsAtSameTile), &Context::getBelsAtSameTile, wrap_context<BelRange>, - conv_from_str<BelId>>::def_wrap(ctx_cls, "getBelsAtSameTile"); fn_wrapper_2a<Context, decltype(&Context::getBelPinWire), &Context::getBelPinWire, conv_to_str<WireId>, conv_from_str<BelId>, conv_from_str<PortPin>>::def_wrap(ctx_cls, "getBelPinWire"); diff --git a/ice40/benchmark/.gitignore b/ice40/benchmark/.gitignore new file mode 100644 index 00000000..306b5c6e --- /dev/null +++ b/ice40/benchmark/.gitignore @@ -0,0 +1,6 @@ +hx8kdemo.log +hx8kdemo.blif +hx8kdemo.json +hx8kdemo_[an][0-9].asc +hx8kdemo_[an][0-9].log +report_[an][0-9].txt diff --git a/ice40/benchmark/Makefile b/ice40/benchmark/Makefile new file mode 100644 index 00000000..5e16d9b0 --- /dev/null +++ b/ice40/benchmark/Makefile @@ -0,0 +1,27 @@ +reports:: + +define mkreport +reports:: report_a$1.txt report_n$1.txt + +report_a$1.txt: hx8kdemo_a$1.asc + icetime -m -r report_a$1.txt -d hx8k hx8kdemo_a$1.asc + +report_n$1.txt: hx8kdemo_n$1.asc + icetime -m -r report_n$1.txt -d hx8k hx8kdemo_n$1.asc + +hx8kdemo_a$1.asc: hx8kdemo.blif + arachne-pnr -d 8k -p hx8kdemo.pcf -o hx8kdemo_a$1.asc -s 1$1 hx8kdemo.blif > hx8kdemo_a$1.log 2>&1 + +hx8kdemo_n$1.asc: hx8kdemo.json + ../../nextpnr-ice40 --asc hx8kdemo_n$1.asc --json hx8kdemo.json --pcf hx8kdemo.pcf --hx8k --seed 1$1 > hx8kdemo_n$1.log 2>&1 +endef + +$(foreach i,0 1 2 3 4 5 6 7 8 9,$(eval $(call mkreport,$(i)))) + +hx8kdemo.blif: hx8kdemo.json +hx8kdemo.json: hx8kdemo.v spimemio.v simpleuart.v picosoc.v picorv32.v + yosys -ql hx8kdemo.log -p 'synth_ice40 -top hx8kdemo -blif hx8kdemo.blif -json hx8kdemo.json' $^ + +clean: + rm -f hx8kdemo.log hx8kdemo.blif hx8kdemo.json + rm -f hx8kdemo_[an][0-9].asc hx8kdemo_[an][0-9].log report_[an][0-9].txt diff --git a/ice40/benchmark/hx8kdemo.pcf b/ice40/benchmark/hx8kdemo.pcf new file mode 100644 index 00000000..418bae89 --- /dev/null +++ b/ice40/benchmark/hx8kdemo.pcf @@ -0,0 +1,40 @@ + +# Pinout for the iCE40-HX8K Breakout Board + +set_io clk J3 + +set_io flash_csb R12 +set_io flash_clk R11 +set_io flash_io0 P12 +set_io flash_io1 P11 + +# for QSPI mode the flash chip on the iCE40-HX8K Breakout Board +# must be replaced with one that supports QSPI and the IO2 and IO3 +# pins must be soldered to T9 and P8 (center on J3) +set_io flash_io2 T9 +set_io flash_io3 P8 + +set_io ser_tx B12 +set_io ser_rx B10 + +# left on J3 +set_io debug_ser_tx T1 +set_io debug_ser_rx R3 + +# right on J3 +set_io debug_flash_csb T15 +set_io debug_flash_clk R16 +set_io debug_flash_io0 N12 +set_io debug_flash_io1 P13 +set_io debug_flash_io2 T13 +set_io debug_flash_io3 T14 + +set_io leds[7] B5 # D9 +set_io leds[6] B4 # D8 +set_io leds[5] A2 # D7 +set_io leds[4] A1 # D6 +set_io leds[3] C5 # D5 +set_io leds[2] C4 # D4 +set_io leds[1] B3 # D3 +set_io leds[0] C3 # D2 + diff --git a/ice40/benchmark/hx8kdemo.v b/ice40/benchmark/hx8kdemo.v new file mode 100644 index 00000000..9b784f8f --- /dev/null +++ b/ice40/benchmark/hx8kdemo.v @@ -0,0 +1,139 @@ +/* + * PicoSoC - A simple example SoC using PicoRV32 + * + * Copyright (C) 2017 Clifford Wolf <clifford@clifford.at> + * + * 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. + * + */ + +module hx8kdemo ( + input clk, + + output ser_tx, + input ser_rx, + + output [7:0] leds, + + output flash_csb, + output flash_clk, + inout flash_io0, + inout flash_io1, + inout flash_io2, + inout flash_io3, + + output debug_ser_tx, + output debug_ser_rx, + + output debug_flash_csb, + output debug_flash_clk, + output debug_flash_io0, + output debug_flash_io1, + output debug_flash_io2, + output debug_flash_io3 +); + reg [5:0] reset_cnt = 0; + wire resetn = &reset_cnt; + + always @(posedge clk) begin + reset_cnt <= reset_cnt + !resetn; + end + + wire flash_io0_oe, flash_io0_do, flash_io0_di; + wire flash_io1_oe, flash_io1_do, flash_io1_di; + wire flash_io2_oe, flash_io2_do, flash_io2_di; + wire flash_io3_oe, flash_io3_do, flash_io3_di; + + SB_IO #( + .PIN_TYPE(6'b 1010_01), + .PULLUP(1'b 0) + ) flash_io_buf [3:0] ( + .PACKAGE_PIN({flash_io3, flash_io2, flash_io1, flash_io0}), + .OUTPUT_ENABLE({flash_io3_oe, flash_io2_oe, flash_io1_oe, flash_io0_oe}), + .D_OUT_0({flash_io3_do, flash_io2_do, flash_io1_do, flash_io0_do}), + .D_IN_0({flash_io3_di, flash_io2_di, flash_io1_di, flash_io0_di}) + ); + + wire iomem_valid; + reg iomem_ready; + wire [3:0] iomem_wstrb; + wire [31:0] iomem_addr; + wire [31:0] iomem_wdata; + reg [31:0] iomem_rdata; + + reg [31:0] gpio; + assign leds = gpio; + + always @(posedge clk) begin + if (!resetn) begin + gpio <= 0; + end else begin + iomem_ready <= 0; + if (iomem_valid && !iomem_ready && iomem_addr[31:24] == 8'h 03) begin + iomem_ready <= 1; + iomem_rdata <= gpio; + if (iomem_wstrb[0]) gpio[ 7: 0] <= iomem_wdata[ 7: 0]; + if (iomem_wstrb[1]) gpio[15: 8] <= iomem_wdata[15: 8]; + if (iomem_wstrb[2]) gpio[23:16] <= iomem_wdata[23:16]; + if (iomem_wstrb[3]) gpio[31:24] <= iomem_wdata[31:24]; + end + end + end + + picosoc soc ( + .clk (clk ), + .resetn (resetn ), + + .ser_tx (ser_tx ), + .ser_rx (ser_rx ), + + .flash_csb (flash_csb ), + .flash_clk (flash_clk ), + + .flash_io0_oe (flash_io0_oe), + .flash_io1_oe (flash_io1_oe), + .flash_io2_oe (flash_io2_oe), + .flash_io3_oe (flash_io3_oe), + + .flash_io0_do (flash_io0_do), + .flash_io1_do (flash_io1_do), + .flash_io2_do (flash_io2_do), + .flash_io3_do (flash_io3_do), + + .flash_io0_di (flash_io0_di), + .flash_io1_di (flash_io1_di), + .flash_io2_di (flash_io2_di), + .flash_io3_di (flash_io3_di), + + .irq_5 (1'b0 ), + .irq_6 (1'b0 ), + .irq_7 (1'b0 ), + + .iomem_valid (iomem_valid ), + .iomem_ready (iomem_ready ), + .iomem_wstrb (iomem_wstrb ), + .iomem_addr (iomem_addr ), + .iomem_wdata (iomem_wdata ), + .iomem_rdata (iomem_rdata ) + ); + + assign debug_ser_tx = ser_tx; + assign debug_ser_rx = ser_rx; + + assign debug_flash_csb = flash_csb; + assign debug_flash_clk = flash_clk; + assign debug_flash_io0 = flash_io0_di; + assign debug_flash_io1 = flash_io1_di; + assign debug_flash_io2 = flash_io2_di; + assign debug_flash_io3 = flash_io3_di; +endmodule diff --git a/ice40/benchmark/picorv32.v b/ice40/benchmark/picorv32.v new file mode 100644 index 00000000..af634b47 --- /dev/null +++ b/ice40/benchmark/picorv32.v @@ -0,0 +1,2977 @@ +/* + * PicoRV32 -- A Small RISC-V (RV32I) Processor Core + * + * Copyright (C) 2015 Clifford Wolf <clifford@clifford.at> + * + * 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. + * + */ + +`timescale 1 ns / 1 ps +// `default_nettype none +// `define DEBUGNETS +// `define DEBUGREGS +// `define DEBUGASM +// `define DEBUG + +`ifdef DEBUG + `define debug(debug_command) debug_command +`else + `define debug(debug_command) +`endif + +`ifdef FORMAL + `define FORMAL_KEEP (* keep *) + `define assert(assert_expr) assert(assert_expr) +`else + `ifdef DEBUGNETS + `define FORMAL_KEEP (* keep *) + `else + `define FORMAL_KEEP + `endif + `define assert(assert_expr) empty_statement +`endif + +// uncomment this for register file in extra module +// `define PICORV32_REGS picorv32_regs + +// this macro can be used to check if the verilog files in your +// design are read in the correct order. +`define PICORV32_V + + +/*************************************************************** + * picorv32 + ***************************************************************/ + +module picorv32 #( + parameter [ 0:0] ENABLE_COUNTERS = 1, + parameter [ 0:0] ENABLE_COUNTERS64 = 1, + parameter [ 0:0] ENABLE_REGS_16_31 = 1, + parameter [ 0:0] ENABLE_REGS_DUALPORT = 1, + parameter [ 0:0] LATCHED_MEM_RDATA = 0, + parameter [ 0:0] TWO_STAGE_SHIFT = 1, + parameter [ 0:0] BARREL_SHIFTER = 0, + parameter [ 0:0] TWO_CYCLE_COMPARE = 0, + parameter [ 0:0] TWO_CYCLE_ALU = 0, + parameter [ 0:0] COMPRESSED_ISA = 0, + parameter [ 0:0] CATCH_MISALIGN = 1, + parameter [ 0:0] CATCH_ILLINSN = 1, + parameter [ 0:0] ENABLE_PCPI = 0, + parameter [ 0:0] ENABLE_MUL = 0, + parameter [ 0:0] ENABLE_FAST_MUL = 0, + parameter [ 0:0] ENABLE_DIV = 0, + parameter [ 0:0] ENABLE_IRQ = 0, + parameter [ 0:0] ENABLE_IRQ_QREGS = 1, + parameter [ 0:0] ENABLE_IRQ_TIMER = 1, + parameter [ 0:0] ENABLE_TRACE = 0, + parameter [ 0:0] REGS_INIT_ZERO = 0, + parameter [31:0] MASKED_IRQ = 32'h 0000_0000, + parameter [31:0] LATCHED_IRQ = 32'h ffff_ffff, + parameter [31:0] PROGADDR_RESET = 32'h 0000_0000, + parameter [31:0] PROGADDR_IRQ = 32'h 0000_0010, + parameter [31:0] STACKADDR = 32'h ffff_ffff +) ( + input clk, resetn, + output reg trap, + + output reg mem_valid, + output reg mem_instr, + input mem_ready, + + output reg [31:0] mem_addr, + output reg [31:0] mem_wdata, + output reg [ 3:0] mem_wstrb, + input [31:0] mem_rdata, + + // Look-Ahead Interface + output mem_la_read, + output mem_la_write, + output [31:0] mem_la_addr, + output reg [31:0] mem_la_wdata, + output reg [ 3:0] mem_la_wstrb, + + // Pico Co-Processor Interface (PCPI) + output reg pcpi_valid, + output reg [31:0] pcpi_insn, + output [31:0] pcpi_rs1, + output [31:0] pcpi_rs2, + input pcpi_wr, + input [31:0] pcpi_rd, + input pcpi_wait, + input pcpi_ready, + + // IRQ Interface + input [31:0] irq, + output reg [31:0] eoi, + +`ifdef RISCV_FORMAL + output reg rvfi_valid, + output reg [63:0] rvfi_order, + output reg [31:0] rvfi_insn, + output reg rvfi_trap, + output reg rvfi_halt, + output reg rvfi_intr, + output reg [ 4:0] rvfi_rs1_addr, + output reg [ 4:0] rvfi_rs2_addr, + output reg [31:0] rvfi_rs1_rdata, + output reg [31:0] rvfi_rs2_rdata, + output reg [ 4:0] rvfi_rd_addr, + output reg [31:0] rvfi_rd_wdata, + output reg [31:0] rvfi_pc_rdata, + output reg [31:0] rvfi_pc_wdata, + output reg [31:0] rvfi_mem_addr, + output reg [ 3:0] rvfi_mem_rmask, + output reg [ 3:0] rvfi_mem_wmask, + output reg [31:0] rvfi_mem_rdata, + output reg [31:0] rvfi_mem_wdata, +`endif + + // Trace Interface + output reg trace_valid, + output reg [35:0] trace_data +); + localparam integer irq_timer = 0; + localparam integer irq_ebreak = 1; + localparam integer irq_buserror = 2; + + localparam integer irqregs_offset = ENABLE_REGS_16_31 ? 32 : 16; + localparam integer regfile_size = (ENABLE_REGS_16_31 ? 32 : 16) + 4*ENABLE_IRQ*ENABLE_IRQ_QREGS; + localparam integer regindex_bits = (ENABLE_REGS_16_31 ? 5 : 4) + ENABLE_IRQ*ENABLE_IRQ_QREGS; + + localparam WITH_PCPI = ENABLE_PCPI || ENABLE_MUL || ENABLE_FAST_MUL || ENABLE_DIV; + + localparam [35:0] TRACE_BRANCH = {4'b 0001, 32'b 0}; + localparam [35:0] TRACE_ADDR = {4'b 0010, 32'b 0}; + localparam [35:0] TRACE_IRQ = {4'b 1000, 32'b 0}; + + reg [63:0] count_cycle, count_instr; + reg [31:0] reg_pc, reg_next_pc, reg_op1, reg_op2, reg_out; + reg [4:0] reg_sh; + + reg [31:0] next_insn_opcode; + reg [31:0] dbg_insn_opcode; + reg [31:0] dbg_insn_addr; + + wire dbg_mem_valid = mem_valid; + wire dbg_mem_instr = mem_instr; + wire dbg_mem_ready = mem_ready; + wire [31:0] dbg_mem_addr = mem_addr; + wire [31:0] dbg_mem_wdata = mem_wdata; + wire [ 3:0] dbg_mem_wstrb = mem_wstrb; + wire [31:0] dbg_mem_rdata = mem_rdata; + + assign pcpi_rs1 = reg_op1; + assign pcpi_rs2 = reg_op2; + + wire [31:0] next_pc; + + reg irq_delay; + reg irq_active; + reg [31:0] irq_mask; + reg [31:0] irq_pending; + reg [31:0] timer; + +`ifndef PICORV32_REGS + reg [31:0] cpuregs [0:regfile_size-1]; + + integer i; + initial begin + if (REGS_INIT_ZERO) begin + for (i = 0; i < regfile_size; i = i+1) + cpuregs[i] = 0; + end + end +`endif + + task empty_statement; + // This task is used by the `assert directive in non-formal mode to + // avoid empty statement (which are unsupported by plain Verilog syntax). + begin end + endtask + +`ifdef DEBUGREGS + wire [31:0] dbg_reg_x0 = 0; + wire [31:0] dbg_reg_x1 = cpuregs[1]; + wire [31:0] dbg_reg_x2 = cpuregs[2]; + wire [31:0] dbg_reg_x3 = cpuregs[3]; + wire [31:0] dbg_reg_x4 = cpuregs[4]; + wire [31:0] dbg_reg_x5 = cpuregs[5]; + wire [31:0] dbg_reg_x6 = cpuregs[6]; + wire [31:0] dbg_reg_x7 = cpuregs[7]; + wire [31:0] dbg_reg_x8 = cpuregs[8]; + wire [31:0] dbg_reg_x9 = cpuregs[9]; + wire [31:0] dbg_reg_x10 = cpuregs[10]; + wire [31:0] dbg_reg_x11 = cpuregs[11]; + wire [31:0] dbg_reg_x12 = cpuregs[12]; + wire [31:0] dbg_reg_x13 = cpuregs[13]; + wire [31:0] dbg_reg_x14 = cpuregs[14]; + wire [31:0] dbg_reg_x15 = cpuregs[15]; + wire [31:0] dbg_reg_x16 = cpuregs[16]; + wire [31:0] dbg_reg_x17 = cpuregs[17]; + wire [31:0] dbg_reg_x18 = cpuregs[18]; + wire [31:0] dbg_reg_x19 = cpuregs[19]; + wire [31:0] dbg_reg_x20 = cpuregs[20]; + wire [31:0] dbg_reg_x21 = cpuregs[21]; + wire [31:0] dbg_reg_x22 = cpuregs[22]; + wire [31:0] dbg_reg_x23 = cpuregs[23]; + wire [31:0] dbg_reg_x24 = cpuregs[24]; + wire [31:0] dbg_reg_x25 = cpuregs[25]; + wire [31:0] dbg_reg_x26 = cpuregs[26]; + wire [31:0] dbg_reg_x27 = cpuregs[27]; + wire [31:0] dbg_reg_x28 = cpuregs[28]; + wire [31:0] dbg_reg_x29 = cpuregs[29]; + wire [31:0] dbg_reg_x30 = cpuregs[30]; + wire [31:0] dbg_reg_x31 = cpuregs[31]; +`endif + + // Internal PCPI Cores + + wire pcpi_mul_wr; + wire [31:0] pcpi_mul_rd; + wire pcpi_mul_wait; + wire pcpi_mul_ready; + + wire pcpi_div_wr; + wire [31:0] pcpi_div_rd; + wire pcpi_div_wait; + wire pcpi_div_ready; + + reg pcpi_int_wr; + reg [31:0] pcpi_int_rd; + reg pcpi_int_wait; + reg pcpi_int_ready; + + generate if (ENABLE_FAST_MUL) begin + picorv32_pcpi_fast_mul pcpi_mul ( + .clk (clk ), + .resetn (resetn ), + .pcpi_valid(pcpi_valid ), + .pcpi_insn (pcpi_insn ), + .pcpi_rs1 (pcpi_rs1 ), + .pcpi_rs2 (pcpi_rs2 ), + .pcpi_wr (pcpi_mul_wr ), + .pcpi_rd (pcpi_mul_rd ), + .pcpi_wait (pcpi_mul_wait ), + .pcpi_ready(pcpi_mul_ready ) + ); + end else if (ENABLE_MUL) begin + picorv32_pcpi_mul pcpi_mul ( + .clk (clk ), + .resetn (resetn ), + .pcpi_valid(pcpi_valid ), + .pcpi_insn (pcpi_insn ), + .pcpi_rs1 (pcpi_rs1 ), + .pcpi_rs2 (pcpi_rs2 ), + .pcpi_wr (pcpi_mul_wr ), + .pcpi_rd (pcpi_mul_rd ), + .pcpi_wait (pcpi_mul_wait ), + .pcpi_ready(pcpi_mul_ready ) + ); + end else begin + assign pcpi_mul_wr = 0; + assign pcpi_mul_rd = 32'bx; + assign pcpi_mul_wait = 0; + assign pcpi_mul_ready = 0; + end endgenerate + + generate if (ENABLE_DIV) begin + picorv32_pcpi_div pcpi_div ( + .clk (clk ), + .resetn (resetn ), + .pcpi_valid(pcpi_valid ), + .pcpi_insn (pcpi_insn ), + .pcpi_rs1 (pcpi_rs1 ), + .pcpi_rs2 (pcpi_rs2 ), + .pcpi_wr (pcpi_div_wr ), + .pcpi_rd (pcpi_div_rd ), + .pcpi_wait (pcpi_div_wait ), + .pcpi_ready(pcpi_div_ready ) + ); + end else begin + assign pcpi_div_wr = 0; + assign pcpi_div_rd = 32'bx; + assign pcpi_div_wait = 0; + assign pcpi_div_ready = 0; + end endgenerate + + always @* begin + pcpi_int_wr = 0; + pcpi_int_rd = 32'bx; + pcpi_int_wait = |{ENABLE_PCPI && pcpi_wait, (ENABLE_MUL || ENABLE_FAST_MUL) && pcpi_mul_wait, ENABLE_DIV && pcpi_div_wait}; + pcpi_int_ready = |{ENABLE_PCPI && pcpi_ready, (ENABLE_MUL || ENABLE_FAST_MUL) && pcpi_mul_ready, ENABLE_DIV && pcpi_div_ready}; + + (* parallel_case *) + case (1'b1) + ENABLE_PCPI && pcpi_ready: begin + pcpi_int_wr = ENABLE_PCPI ? pcpi_wr : 0; + pcpi_int_rd = ENABLE_PCPI ? pcpi_rd : 0; + end + (ENABLE_MUL || ENABLE_FAST_MUL) && pcpi_mul_ready: begin + pcpi_int_wr = pcpi_mul_wr; + pcpi_int_rd = pcpi_mul_rd; + end + ENABLE_DIV && pcpi_div_ready: begin + pcpi_int_wr = pcpi_div_wr; + pcpi_int_rd = pcpi_div_rd; + end + endcase + end + + + // Memory Interface + + reg [1:0] mem_state; + reg [1:0] mem_wordsize; + reg [31:0] mem_rdata_word; + reg [31:0] mem_rdata_q; + reg mem_do_prefetch; + reg mem_do_rinst; + reg mem_do_rdata; + reg mem_do_wdata; + + wire mem_xfer; + reg mem_la_secondword, mem_la_firstword_reg, last_mem_valid; + wire mem_la_firstword = COMPRESSED_ISA && (mem_do_prefetch || mem_do_rinst) && next_pc[1] && !mem_la_secondword; + wire mem_la_firstword_xfer = COMPRESSED_ISA && mem_xfer && (!last_mem_valid ? mem_la_firstword : mem_la_firstword_reg); + + reg prefetched_high_word; + reg clear_prefetched_high_word; + reg [15:0] mem_16bit_buffer; + + wire [31:0] mem_rdata_latched_noshuffle; + wire [31:0] mem_rdata_latched; + + wire mem_la_use_prefetched_high_word = COMPRESSED_ISA && mem_la_firstword && prefetched_high_word && !clear_prefetched_high_word; + assign mem_xfer = (mem_valid && mem_ready) || (mem_la_use_prefetched_high_word && mem_do_rinst); + + wire mem_busy = |{mem_do_prefetch, mem_do_rinst, mem_do_rdata, mem_do_wdata}; + wire mem_done = resetn && ((mem_xfer && |mem_state && (mem_do_rinst || mem_do_rdata || mem_do_wdata)) || (&mem_state && mem_do_rinst)) && + (!mem_la_firstword || (~&mem_rdata_latched[1:0] && mem_xfer)); + + assign mem_la_write = resetn && !mem_state && mem_do_wdata; + assign mem_la_read = resetn && ((!mem_la_use_prefetched_high_word && !mem_state && (mem_do_rinst || mem_do_prefetch || mem_do_rdata)) || + (COMPRESSED_ISA && mem_xfer && (!last_mem_valid ? mem_la_firstword : mem_la_firstword_reg) && !mem_la_secondword && &mem_rdata_latched[1:0])); + assign mem_la_addr = (mem_do_prefetch || mem_do_rinst) ? {next_pc[31:2] + mem_la_firstword_xfer, 2'b00} : {reg_op1[31:2], 2'b00}; + + assign mem_rdata_latched_noshuffle = (mem_xfer || LATCHED_MEM_RDATA) ? mem_rdata : mem_rdata_q; + + assign mem_rdata_latched = COMPRESSED_ISA && mem_la_use_prefetched_high_word ? {16'bx, mem_16bit_buffer} : + COMPRESSED_ISA && mem_la_secondword ? {mem_rdata_latched_noshuffle[15:0], mem_16bit_buffer} : + COMPRESSED_ISA && mem_la_firstword ? {16'bx, mem_rdata_latched_noshuffle[31:16]} : mem_rdata_latched_noshuffle; + + always @(posedge clk) begin + if (!resetn) begin + mem_la_firstword_reg <= 0; + last_mem_valid <= 0; + end else begin + if (!last_mem_valid) + mem_la_firstword_reg <= mem_la_firstword; + last_mem_valid <= mem_valid && !mem_ready; + end + end + + always @* begin + (* full_case *) + case (mem_wordsize) + 0: begin + mem_la_wdata = reg_op2; + mem_la_wstrb = 4'b1111; + mem_rdata_word = mem_rdata; + end + 1: begin + mem_la_wdata = {2{reg_op2[15:0]}}; + mem_la_wstrb = reg_op1[1] ? 4'b1100 : 4'b0011; + case (reg_op1[1]) + 1'b0: mem_rdata_word = {16'b0, mem_rdata[15: 0]}; + 1'b1: mem_rdata_word = {16'b0, mem_rdata[31:16]}; + endcase + end + 2: begin + mem_la_wdata = {4{reg_op2[7:0]}}; + mem_la_wstrb = 4'b0001 << reg_op1[1:0]; + case (reg_op1[1:0]) + 2'b00: mem_rdata_word = {24'b0, mem_rdata[ 7: 0]}; + 2'b01: mem_rdata_word = {24'b0, mem_rdata[15: 8]}; + 2'b10: mem_rdata_word = {24'b0, mem_rdata[23:16]}; + 2'b11: mem_rdata_word = {24'b0, mem_rdata[31:24]}; + endcase + end + endcase + end + + always @(posedge clk) begin + if (mem_xfer) begin + mem_rdata_q <= COMPRESSED_ISA ? mem_rdata_latched : mem_rdata; + next_insn_opcode <= COMPRESSED_ISA ? mem_rdata_latched : mem_rdata; + end + + if (COMPRESSED_ISA && mem_done && (mem_do_prefetch || mem_do_rinst)) begin + case (mem_rdata_latched[1:0]) + 2'b00: begin // Quadrant 0 + case (mem_rdata_latched[15:13]) + 3'b000: begin // C.ADDI4SPN + mem_rdata_q[14:12] <= 3'b000; + mem_rdata_q[31:20] <= {2'b0, mem_rdata_latched[10:7], mem_rdata_latched[12:11], mem_rdata_latched[5], mem_rdata_latched[6], 2'b00}; + end + 3'b010: begin // C.LW + mem_rdata_q[31:20] <= {5'b0, mem_rdata_latched[5], mem_rdata_latched[12:10], mem_rdata_latched[6], 2'b00}; + mem_rdata_q[14:12] <= 3'b 010; + end + 3'b 110: begin // C.SW + {mem_rdata_q[31:25], mem_rdata_q[11:7]} <= {5'b0, mem_rdata_latched[5], mem_rdata_latched[12:10], mem_rdata_latched[6], 2'b00}; + mem_rdata_q[14:12] <= 3'b 010; + end + endcase + end + 2'b01: begin // Quadrant 1 + case (mem_rdata_latched[15:13]) + 3'b 000: begin // C.ADDI + mem_rdata_q[14:12] <= 3'b000; + mem_rdata_q[31:20] <= $signed({mem_rdata_latched[12], mem_rdata_latched[6:2]}); + end + 3'b 010: begin // C.LI + mem_rdata_q[14:12] <= 3'b000; + mem_rdata_q[31:20] <= $signed({mem_rdata_latched[12], mem_rdata_latched[6:2]}); + end + 3'b 011: begin + if (mem_rdata_latched[11:7] == 2) begin // C.ADDI16SP + mem_rdata_q[14:12] <= 3'b000; + mem_rdata_q[31:20] <= $signed({mem_rdata_latched[12], mem_rdata_latched[4:3], + mem_rdata_latched[5], mem_rdata_latched[2], mem_rdata_latched[6], 4'b 0000}); + end else begin // C.LUI + mem_rdata_q[31:12] <= $signed({mem_rdata_latched[12], mem_rdata_latched[6:2]}); + end + end + 3'b100: begin + if (mem_rdata_latched[11:10] == 2'b00) begin // C.SRLI + mem_rdata_q[31:25] <= 7'b0000000; + mem_rdata_q[14:12] <= 3'b 101; + end + if (mem_rdata_latched[11:10] == 2'b01) begin // C.SRAI + mem_rdata_q[31:25] <= 7'b0100000; + mem_rdata_q[14:12] <= 3'b 101; + end + if (mem_rdata_latched[11:10] == 2'b10) begin // C.ANDI + mem_rdata_q[14:12] <= 3'b111; + mem_rdata_q[31:20] <= $signed({mem_rdata_latched[12], mem_rdata_latched[6:2]}); + end + if (mem_rdata_latched[12:10] == 3'b011) begin // C.SUB, C.XOR, C.OR, C.AND + if (mem_rdata_latched[6:5] == 2'b00) mem_rdata_q[14:12] <= 3'b000; + if (mem_rdata_latched[6:5] == 2'b01) mem_rdata_q[14:12] <= 3'b100; + if (mem_rdata_latched[6:5] == 2'b10) mem_rdata_q[14:12] <= 3'b110; + if (mem_rdata_latched[6:5] == 2'b11) mem_rdata_q[14:12] <= 3'b111; + mem_rdata_q[31:25] <= mem_rdata_latched[6:5] == 2'b00 ? 7'b0100000 : 7'b0000000; + end + end + 3'b 110: begin // C.BEQZ + mem_rdata_q[14:12] <= 3'b000; + { mem_rdata_q[31], mem_rdata_q[7], mem_rdata_q[30:25], mem_rdata_q[11:8] } <= + $signed({mem_rdata_latched[12], mem_rdata_latched[6:5], mem_rdata_latched[2], + mem_rdata_latched[11:10], mem_rdata_latched[4:3]}); + end + 3'b 111: begin // C.BNEZ + mem_rdata_q[14:12] <= 3'b001; + { mem_rdata_q[31], mem_rdata_q[7], mem_rdata_q[30:25], mem_rdata_q[11:8] } <= + $signed({mem_rdata_latched[12], mem_rdata_latched[6:5], mem_rdata_latched[2], + mem_rdata_latched[11:10], mem_rdata_latched[4:3]}); + end + endcase + end + 2'b10: begin // Quadrant 2 + case (mem_rdata_latched[15:13]) + 3'b000: begin // C.SLLI + mem_rdata_q[31:25] <= 7'b0000000; + mem_rdata_q[14:12] <= 3'b 001; + end + 3'b010: begin // C.LWSP + mem_rdata_q[31:20] <= {4'b0, mem_rdata_latched[3:2], mem_rdata_latched[12], mem_rdata_latched[6:4], 2'b00}; + mem_rdata_q[14:12] <= 3'b 010; + end + 3'b100: begin + if (mem_rdata_latched[12] == 0 && mem_rdata_latched[6:2] == 0) begin // C.JR + mem_rdata_q[14:12] <= 3'b000; + mem_rdata_q[31:20] <= 12'b0; + end + if (mem_rdata_latched[12] == 0 && mem_rdata_latched[6:2] != 0) begin // C.MV + mem_rdata_q[14:12] <= 3'b000; + mem_rdata_q[31:25] <= 7'b0000000; + end + if (mem_rdata_latched[12] != 0 && mem_rdata_latched[11:7] != 0 && mem_rdata_latched[6:2] == 0) begin // C.JALR + mem_rdata_q[14:12] <= 3'b000; + mem_rdata_q[31:20] <= 12'b0; + end + if (mem_rdata_latched[12] != 0 && mem_rdata_latched[6:2] != 0) begin // C.ADD + mem_rdata_q[14:12] <= 3'b000; + mem_rdata_q[31:25] <= 7'b0000000; + end + end + 3'b110: begin // C.SWSP + {mem_rdata_q[31:25], mem_rdata_q[11:7]} <= {4'b0, mem_rdata_latched[8:7], mem_rdata_latched[12:9], 2'b00}; + mem_rdata_q[14:12] <= 3'b 010; + end + endcase + end + endcase + end + end + + always @(posedge clk) begin + if (resetn && !trap) begin + if (mem_do_prefetch || mem_do_rinst || mem_do_rdata) + `assert(!mem_do_wdata); + + if (mem_do_prefetch || mem_do_rinst) + `assert(!mem_do_rdata); + + if (mem_do_rdata) + `assert(!mem_do_prefetch && !mem_do_rinst); + + if (mem_do_wdata) + `assert(!(mem_do_prefetch || mem_do_rinst || mem_do_rdata)); + + if (mem_state == 2 || mem_state == 3) + `assert(mem_valid || mem_do_prefetch); + end + end + + always @(posedge clk) begin + if (!resetn || trap) begin + if (!resetn) + mem_state <= 0; + if (!resetn || mem_ready) + mem_valid <= 0; + mem_la_secondword <= 0; + prefetched_high_word <= 0; + end else begin + if (mem_la_read || mem_la_write) begin + mem_addr <= mem_la_addr; + mem_wstrb <= mem_la_wstrb & {4{mem_la_write}}; + end + if (mem_la_write) begin + mem_wdata <= mem_la_wdata; + end + case (mem_state) + 0: begin + if (mem_do_prefetch || mem_do_rinst || mem_do_rdata) begin + mem_valid <= !mem_la_use_prefetched_high_word; + mem_instr <= mem_do_prefetch || mem_do_rinst; + mem_wstrb <= 0; + mem_state <= 1; + end + if (mem_do_wdata) begin + mem_valid <= 1; + mem_instr <= 0; + mem_state <= 2; + end + end + 1: begin + `assert(mem_wstrb == 0); + `assert(mem_do_prefetch || mem_do_rinst || mem_do_rdata); + `assert(mem_valid == !mem_la_use_prefetched_high_word); + `assert(mem_instr == (mem_do_prefetch || mem_do_rinst)); + if (mem_xfer) begin + if (COMPRESSED_ISA && mem_la_read) begin + mem_valid <= 1; + mem_la_secondword <= 1; + if (!mem_la_use_prefetched_high_word) + mem_16bit_buffer <= mem_rdata[31:16]; + end else begin + mem_valid <= 0; + mem_la_secondword <= 0; + if (COMPRESSED_ISA && !mem_do_rdata) begin + if (~&mem_rdata[1:0] || mem_la_secondword) begin + mem_16bit_buffer <= mem_rdata[31:16]; + prefetched_high_word <= 1; + end else begin + prefetched_high_word <= 0; + end + end + mem_state <= mem_do_rinst || mem_do_rdata ? 0 : 3; + end + end + end + 2: begin + `assert(mem_wstrb != 0); + `assert(mem_do_wdata); + if (mem_xfer) begin + mem_valid <= 0; + mem_state <= 0; + end + end + 3: begin + `assert(mem_wstrb == 0); + `assert(mem_do_prefetch); + if (mem_do_rinst) begin + mem_state <= 0; + end + end + endcase + end + + if (clear_prefetched_high_word) + prefetched_high_word <= 0; + end + + + // Instruction Decoder + + reg instr_lui, instr_auipc, instr_jal, instr_jalr; + reg instr_beq, instr_bne, instr_blt, instr_bge, instr_bltu, instr_bgeu; + reg instr_lb, instr_lh, instr_lw, instr_lbu, instr_lhu, instr_sb, instr_sh, instr_sw; + reg instr_addi, instr_slti, instr_sltiu, instr_xori, instr_ori, instr_andi, instr_slli, instr_srli, instr_srai; + reg instr_add, instr_sub, instr_sll, instr_slt, instr_sltu, instr_xor, instr_srl, instr_sra, instr_or, instr_and; + reg instr_rdcycle, instr_rdcycleh, instr_rdinstr, instr_rdinstrh, instr_ecall_ebreak; + reg instr_getq, instr_setq, instr_retirq, instr_maskirq, instr_waitirq, instr_timer; + wire instr_trap; + + reg [regindex_bits-1:0] decoded_rd, decoded_rs1, decoded_rs2; + reg [31:0] decoded_imm, decoded_imm_uj; + reg decoder_trigger; + reg decoder_trigger_q; + reg decoder_pseudo_trigger; + reg decoder_pseudo_trigger_q; + reg compressed_instr; + + reg is_lui_auipc_jal; + reg is_lb_lh_lw_lbu_lhu; + reg is_slli_srli_srai; + reg is_jalr_addi_slti_sltiu_xori_ori_andi; + reg is_sb_sh_sw; + reg is_sll_srl_sra; + reg is_lui_auipc_jal_jalr_addi_add_sub; + reg is_slti_blt_slt; + reg is_sltiu_bltu_sltu; + reg is_beq_bne_blt_bge_bltu_bgeu; + reg is_lbu_lhu_lw; + reg is_alu_reg_imm; + reg is_alu_reg_reg; + reg is_compare; + + assign instr_trap = (CATCH_ILLINSN || WITH_PCPI) && !{instr_lui, instr_auipc, instr_jal, instr_jalr, + instr_beq, instr_bne, instr_blt, instr_bge, instr_bltu, instr_bgeu, + instr_lb, instr_lh, instr_lw, instr_lbu, instr_lhu, instr_sb, instr_sh, instr_sw, + instr_addi, instr_slti, instr_sltiu, instr_xori, instr_ori, instr_andi, instr_slli, instr_srli, instr_srai, + instr_add, instr_sub, instr_sll, instr_slt, instr_sltu, instr_xor, instr_srl, instr_sra, instr_or, instr_and, + instr_rdcycle, instr_rdcycleh, instr_rdinstr, instr_rdinstrh, + instr_getq, instr_setq, instr_retirq, instr_maskirq, instr_waitirq, instr_timer}; + + wire is_rdcycle_rdcycleh_rdinstr_rdinstrh; + assign is_rdcycle_rdcycleh_rdinstr_rdinstrh = |{instr_rdcycle, instr_rdcycleh, instr_rdinstr, instr_rdinstrh}; + + reg [63:0] new_ascii_instr; + `FORMAL_KEEP reg [63:0] dbg_ascii_instr; + `FORMAL_KEEP reg [31:0] dbg_insn_imm; + `FORMAL_KEEP reg [4:0] dbg_insn_rs1; + `FORMAL_KEEP reg [4:0] dbg_insn_rs2; + `FORMAL_KEEP reg [4:0] dbg_insn_rd; + `FORMAL_KEEP reg [31:0] dbg_rs1val; + `FORMAL_KEEP reg [31:0] dbg_rs2val; + `FORMAL_KEEP reg dbg_rs1val_valid; + `FORMAL_KEEP reg dbg_rs2val_valid; + + always @* begin + new_ascii_instr = ""; + + if (instr_lui) new_ascii_instr = "lui"; + if (instr_auipc) new_ascii_instr = "auipc"; + if (instr_jal) new_ascii_instr = "jal"; + if (instr_jalr) new_ascii_instr = "jalr"; + + if (instr_beq) new_ascii_instr = "beq"; + if (instr_bne) new_ascii_instr = "bne"; + if (instr_blt) new_ascii_instr = "blt"; + if (instr_bge) new_ascii_instr = "bge"; + if (instr_bltu) new_ascii_instr = "bltu"; + if (instr_bgeu) new_ascii_instr = "bgeu"; + + if (instr_lb) new_ascii_instr = "lb"; + if (instr_lh) new_ascii_instr = "lh"; + if (instr_lw) new_ascii_instr = "lw"; + if (instr_lbu) new_ascii_instr = "lbu"; + if (instr_lhu) new_ascii_instr = "lhu"; + if (instr_sb) new_ascii_instr = "sb"; + if (instr_sh) new_ascii_instr = "sh"; + if (instr_sw) new_ascii_instr = "sw"; + + if (instr_addi) new_ascii_instr = "addi"; + if (instr_slti) new_ascii_instr = "slti"; + if (instr_sltiu) new_ascii_instr = "sltiu"; + if (instr_xori) new_ascii_instr = "xori"; + if (instr_ori) new_ascii_instr = "ori"; + if (instr_andi) new_ascii_instr = "andi"; + if (instr_slli) new_ascii_instr = "slli"; + if (instr_srli) new_ascii_instr = "srli"; + if (instr_srai) new_ascii_instr = "srai"; + + if (instr_add) new_ascii_instr = "add"; + if (instr_sub) new_ascii_instr = "sub"; + if (instr_sll) new_ascii_instr = "sll"; + if (instr_slt) new_ascii_instr = "slt"; + if (instr_sltu) new_ascii_instr = "sltu"; + if (instr_xor) new_ascii_instr = "xor"; + if (instr_srl) new_ascii_instr = "srl"; + if (instr_sra) new_ascii_instr = "sra"; + if (instr_or) new_ascii_instr = "or"; + if (instr_and) new_ascii_instr = "and"; + + if (instr_rdcycle) new_ascii_instr = "rdcycle"; + if (instr_rdcycleh) new_ascii_instr = "rdcycleh"; + if (instr_rdinstr) new_ascii_instr = "rdinstr"; + if (instr_rdinstrh) new_ascii_instr = "rdinstrh"; + + if (instr_getq) new_ascii_instr = "getq"; + if (instr_setq) new_ascii_instr = "setq"; + if (instr_retirq) new_ascii_instr = "retirq"; + if (instr_maskirq) new_ascii_instr = "maskirq"; + if (instr_waitirq) new_ascii_instr = "waitirq"; + if (instr_timer) new_ascii_instr = "timer"; + end + + reg [63:0] q_ascii_instr; + reg [31:0] q_insn_imm; + reg [31:0] q_insn_opcode; + reg [4:0] q_insn_rs1; + reg [4:0] q_insn_rs2; + reg [4:0] q_insn_rd; + reg dbg_next; + + wire launch_next_insn; + reg dbg_valid_insn; + + reg [63:0] cached_ascii_instr; + reg [31:0] cached_insn_imm; + reg [31:0] cached_insn_opcode; + reg [4:0] cached_insn_rs1; + reg [4:0] cached_insn_rs2; + reg [4:0] cached_insn_rd; + + always @(posedge clk) begin + q_ascii_instr <= dbg_ascii_instr; + q_insn_imm <= dbg_insn_imm; + q_insn_opcode <= dbg_insn_opcode; + q_insn_rs1 <= dbg_insn_rs1; + q_insn_rs2 <= dbg_insn_rs2; + q_insn_rd <= dbg_insn_rd; + dbg_next <= launch_next_insn; + + if (!resetn || trap) + dbg_valid_insn <= 0; + else if (launch_next_insn) + dbg_valid_insn <= 1; + + if (decoder_trigger_q) begin + cached_ascii_instr <= new_ascii_instr; + cached_insn_imm <= decoded_imm; + if (&next_insn_opcode[1:0]) + cached_insn_opcode <= next_insn_opcode; + else + cached_insn_opcode <= {16'b0, next_insn_opcode[15:0]}; + cached_insn_rs1 <= decoded_rs1; + cached_insn_rs2 <= decoded_rs2; + cached_insn_rd <= decoded_rd; + end + + if (launch_next_insn) begin + dbg_insn_addr <= next_pc; + end + end + + always @* begin + dbg_ascii_instr = q_ascii_instr; + dbg_insn_imm = q_insn_imm; + dbg_insn_opcode = q_insn_opcode; + dbg_insn_rs1 = q_insn_rs1; + dbg_insn_rs2 = q_insn_rs2; + dbg_insn_rd = q_insn_rd; + + if (dbg_next) begin + if (decoder_pseudo_trigger_q) begin + dbg_ascii_instr = cached_ascii_instr; + dbg_insn_imm = cached_insn_imm; + dbg_insn_opcode = cached_insn_opcode; + dbg_insn_rs1 = cached_insn_rs1; + dbg_insn_rs2 = cached_insn_rs2; + dbg_insn_rd = cached_insn_rd; + end else begin + dbg_ascii_instr = new_ascii_instr; + if (&next_insn_opcode[1:0]) + dbg_insn_opcode = next_insn_opcode; + else + dbg_insn_opcode = {16'b0, next_insn_opcode[15:0]}; + dbg_insn_imm = decoded_imm; + dbg_insn_rs1 = decoded_rs1; + dbg_insn_rs2 = decoded_rs2; + dbg_insn_rd = decoded_rd; + end + end + end + +`ifdef DEBUGASM + always @(posedge clk) begin + if (dbg_next) begin + $display("debugasm %x %x %s", dbg_insn_addr, dbg_insn_opcode, dbg_ascii_instr ? dbg_ascii_instr : "*"); + end + end +`endif + +`ifdef DEBUG + always @(posedge clk) begin + if (dbg_next) begin + if (&dbg_insn_opcode[1:0]) + $display("DECODE: 0x%08x 0x%08x %-0s", dbg_insn_addr, dbg_insn_opcode, dbg_ascii_instr ? dbg_ascii_instr : "UNKNOWN"); + else + $display("DECODE: 0x%08x 0x%04x %-0s", dbg_insn_addr, dbg_insn_opcode[15:0], dbg_ascii_instr ? dbg_ascii_instr : "UNKNOWN"); + end + end +`endif + + always @(posedge clk) begin + is_lui_auipc_jal <= |{instr_lui, instr_auipc, instr_jal}; + is_lui_auipc_jal_jalr_addi_add_sub <= |{instr_lui, instr_auipc, instr_jal, instr_jalr, instr_addi, instr_add, instr_sub}; + is_slti_blt_slt <= |{instr_slti, instr_blt, instr_slt}; + is_sltiu_bltu_sltu <= |{instr_sltiu, instr_bltu, instr_sltu}; + is_lbu_lhu_lw <= |{instr_lbu, instr_lhu, instr_lw}; + is_compare <= |{is_beq_bne_blt_bge_bltu_bgeu, instr_slti, instr_slt, instr_sltiu, instr_sltu}; + + if (mem_do_rinst && mem_done) begin + instr_lui <= mem_rdata_latched[6:0] == 7'b0110111; + instr_auipc <= mem_rdata_latched[6:0] == 7'b0010111; + instr_jal <= mem_rdata_latched[6:0] == 7'b1101111; + instr_jalr <= mem_rdata_latched[6:0] == 7'b1100111 && mem_rdata_latched[14:12] == 3'b000; + instr_retirq <= mem_rdata_latched[6:0] == 7'b0001011 && mem_rdata_latched[31:25] == 7'b0000010 && ENABLE_IRQ; + instr_waitirq <= mem_rdata_latched[6:0] == 7'b0001011 && mem_rdata_latched[31:25] == 7'b0000100 && ENABLE_IRQ; + + is_beq_bne_blt_bge_bltu_bgeu <= mem_rdata_latched[6:0] == 7'b1100011; + is_lb_lh_lw_lbu_lhu <= mem_rdata_latched[6:0] == 7'b0000011; + is_sb_sh_sw <= mem_rdata_latched[6:0] == 7'b0100011; + is_alu_reg_imm <= mem_rdata_latched[6:0] == 7'b0010011; + is_alu_reg_reg <= mem_rdata_latched[6:0] == 7'b0110011; + + { decoded_imm_uj[31:20], decoded_imm_uj[10:1], decoded_imm_uj[11], decoded_imm_uj[19:12], decoded_imm_uj[0] } <= $signed({mem_rdata_latched[31:12], 1'b0}); + + decoded_rd <= mem_rdata_latched[11:7]; + decoded_rs1 <= mem_rdata_latched[19:15]; + decoded_rs2 <= mem_rdata_latched[24:20]; + + if (mem_rdata_latched[6:0] == 7'b0001011 && mem_rdata_latched[31:25] == 7'b0000000 && ENABLE_IRQ && ENABLE_IRQ_QREGS) + decoded_rs1[regindex_bits-1] <= 1; // instr_getq + + if (mem_rdata_latched[6:0] == 7'b0001011 && mem_rdata_latched[31:25] == 7'b0000010 && ENABLE_IRQ) + decoded_rs1 <= ENABLE_IRQ_QREGS ? irqregs_offset : 3; // instr_retirq + + compressed_instr <= 0; + if (COMPRESSED_ISA && mem_rdata_latched[1:0] != 2'b11) begin + compressed_instr <= 1; + decoded_rd <= 0; + decoded_rs1 <= 0; + decoded_rs2 <= 0; + + { decoded_imm_uj[31:11], decoded_imm_uj[4], decoded_imm_uj[9:8], decoded_imm_uj[10], decoded_imm_uj[6], + decoded_imm_uj[7], decoded_imm_uj[3:1], decoded_imm_uj[5], decoded_imm_uj[0] } <= $signed({mem_rdata_latched[12:2], 1'b0}); + + case (mem_rdata_latched[1:0]) + 2'b00: begin // Quadrant 0 + case (mem_rdata_latched[15:13]) + 3'b000: begin // C.ADDI4SPN + is_alu_reg_imm <= |mem_rdata_latched[12:5]; + decoded_rs1 <= 2; + decoded_rd <= 8 + mem_rdata_latched[4:2]; + end + 3'b010: begin // C.LW + is_lb_lh_lw_lbu_lhu <= 1; + decoded_rs1 <= 8 + mem_rdata_latched[9:7]; + decoded_rd <= 8 + mem_rdata_latched[4:2]; + end + 3'b110: begin // C.SW + is_sb_sh_sw <= 1; + decoded_rs1 <= 8 + mem_rdata_latched[9:7]; + decoded_rs2 <= 8 + mem_rdata_latched[4:2]; + end + endcase + end + 2'b01: begin // Quadrant 1 + case (mem_rdata_latched[15:13]) + 3'b000: begin // C.NOP / C.ADDI + is_alu_reg_imm <= 1; + decoded_rd <= mem_rdata_latched[11:7]; + decoded_rs1 <= mem_rdata_latched[11:7]; + end + 3'b001: begin // C.JAL + instr_jal <= 1; + decoded_rd <= 1; + end + 3'b 010: begin // C.LI + is_alu_reg_imm <= 1; + decoded_rd <= mem_rdata_latched[11:7]; + decoded_rs1 <= 0; + end + 3'b 011: begin + if (mem_rdata_latched[12] || mem_rdata_latched[6:2]) begin + if (mem_rdata_latched[11:7] == 2) begin // C.ADDI16SP + is_alu_reg_imm <= 1; + decoded_rd <= mem_rdata_latched[11:7]; + decoded_rs1 <= mem_rdata_latched[11:7]; + end else begin // C.LUI + instr_lui <= 1; + decoded_rd <= mem_rdata_latched[11:7]; + decoded_rs1 <= 0; + end + end + end + 3'b100: begin + if (!mem_rdata_latched[11] && !mem_rdata_latched[12]) begin // C.SRLI, C.SRAI + is_alu_reg_imm <= 1; + decoded_rd <= 8 + mem_rdata_latched[9:7]; + decoded_rs1 <= 8 + mem_rdata_latched[9:7]; + decoded_rs2 <= {mem_rdata_latched[12], mem_rdata_latched[6:2]}; + end + if (mem_rdata_latched[11:10] == 2'b10) begin // C.ANDI + is_alu_reg_imm <= 1; + decoded_rd <= 8 + mem_rdata_latched[9:7]; + decoded_rs1 <= 8 + mem_rdata_latched[9:7]; + end + if (mem_rdata_latched[12:10] == 3'b011) begin // C.SUB, C.XOR, C.OR, C.AND + is_alu_reg_reg <= 1; + decoded_rd <= 8 + mem_rdata_latched[9:7]; + decoded_rs1 <= 8 + mem_rdata_latched[9:7]; + decoded_rs2 <= 8 + mem_rdata_latched[4:2]; + end + end + 3'b101: begin // C.J + instr_jal <= 1; + end + 3'b110: begin // C.BEQZ + is_beq_bne_blt_bge_bltu_bgeu <= 1; + decoded_rs1 <= 8 + mem_rdata_latched[9:7]; + decoded_rs2 <= 0; + end + 3'b111: begin // C.BNEZ + is_beq_bne_blt_bge_bltu_bgeu <= 1; + decoded_rs1 <= 8 + mem_rdata_latched[9:7]; + decoded_rs2 <= 0; + end + endcase + end + 2'b10: begin // Quadrant 2 + case (mem_rdata_latched[15:13]) + 3'b000: begin // C.SLLI + if (!mem_rdata_latched[12]) begin + is_alu_reg_imm <= 1; + decoded_rd <= mem_rdata_latched[11:7]; + decoded_rs1 <= mem_rdata_latched[11:7]; + decoded_rs2 <= {mem_rdata_latched[12], mem_rdata_latched[6:2]}; + end + end + 3'b010: begin // C.LWSP + if (mem_rdata_latched[11:7]) begin + is_lb_lh_lw_lbu_lhu <= 1; + decoded_rd <= mem_rdata_latched[11:7]; + decoded_rs1 <= 2; + end + end + 3'b100: begin + if (mem_rdata_latched[12] == 0 && mem_rdata_latched[11:7] != 0 && mem_rdata_latched[6:2] == 0) begin // C.JR + instr_jalr <= 1; + decoded_rd <= 0; + decoded_rs1 <= mem_rdata_latched[11:7]; + end + if (mem_rdata_latched[12] == 0 && mem_rdata_latched[6:2] != 0) begin // C.MV + is_alu_reg_reg <= 1; + decoded_rd <= mem_rdata_latched[11:7]; + decoded_rs1 <= 0; + decoded_rs2 <= mem_rdata_latched[6:2]; + end + if (mem_rdata_latched[12] != 0 && mem_rdata_latched[11:7] != 0 && mem_rdata_latched[6:2] == 0) begin // C.JALR + instr_jalr <= 1; + decoded_rd <= 1; + decoded_rs1 <= mem_rdata_latched[11:7]; + end + if (mem_rdata_latched[12] != 0 && mem_rdata_latched[6:2] != 0) begin // C.ADD + is_alu_reg_reg <= 1; + decoded_rd <= mem_rdata_latched[11:7]; + decoded_rs1 <= mem_rdata_latched[11:7]; + decoded_rs2 <= mem_rdata_latched[6:2]; + end + end + 3'b110: begin // C.SWSP + is_sb_sh_sw <= 1; + decoded_rs1 <= 2; + decoded_rs2 <= mem_rdata_latched[6:2]; + end + endcase + end + endcase + end + end + + if (decoder_trigger && !decoder_pseudo_trigger) begin + pcpi_insn <= WITH_PCPI ? mem_rdata_q : 'bx; + + instr_beq <= is_beq_bne_blt_bge_bltu_bgeu && mem_rdata_q[14:12] == 3'b000; + instr_bne <= is_beq_bne_blt_bge_bltu_bgeu && mem_rdata_q[14:12] == 3'b001; + instr_blt <= is_beq_bne_blt_bge_bltu_bgeu && mem_rdata_q[14:12] == 3'b100; + instr_bge <= is_beq_bne_blt_bge_bltu_bgeu && mem_rdata_q[14:12] == 3'b101; + instr_bltu <= is_beq_bne_blt_bge_bltu_bgeu && mem_rdata_q[14:12] == 3'b110; + instr_bgeu <= is_beq_bne_blt_bge_bltu_bgeu && mem_rdata_q[14:12] == 3'b111; + + instr_lb <= is_lb_lh_lw_lbu_lhu && mem_rdata_q[14:12] == 3'b000; + instr_lh <= is_lb_lh_lw_lbu_lhu && mem_rdata_q[14:12] == 3'b001; + instr_lw <= is_lb_lh_lw_lbu_lhu && mem_rdata_q[14:12] == 3'b010; + instr_lbu <= is_lb_lh_lw_lbu_lhu && mem_rdata_q[14:12] == 3'b100; + instr_lhu <= is_lb_lh_lw_lbu_lhu && mem_rdata_q[14:12] == 3'b101; + + instr_sb <= is_sb_sh_sw && mem_rdata_q[14:12] == 3'b000; + instr_sh <= is_sb_sh_sw && mem_rdata_q[14:12] == 3'b001; + instr_sw <= is_sb_sh_sw && mem_rdata_q[14:12] == 3'b010; + + instr_addi <= is_alu_reg_imm && mem_rdata_q[14:12] == 3'b000; + instr_slti <= is_alu_reg_imm && mem_rdata_q[14:12] == 3'b010; + instr_sltiu <= is_alu_reg_imm && mem_rdata_q[14:12] == 3'b011; + instr_xori <= is_alu_reg_imm && mem_rdata_q[14:12] == 3'b100; + instr_ori <= is_alu_reg_imm && mem_rdata_q[14:12] == 3'b110; + instr_andi <= is_alu_reg_imm && mem_rdata_q[14:12] == 3'b111; + + instr_slli <= is_alu_reg_imm && mem_rdata_q[14:12] == 3'b001 && mem_rdata_q[31:25] == 7'b0000000; + instr_srli <= is_alu_reg_imm && mem_rdata_q[14:12] == 3'b101 && mem_rdata_q[31:25] == 7'b0000000; + instr_srai <= is_alu_reg_imm && mem_rdata_q[14:12] == 3'b101 && mem_rdata_q[31:25] == 7'b0100000; + + instr_add <= is_alu_reg_reg && mem_rdata_q[14:12] == 3'b000 && mem_rdata_q[31:25] == 7'b0000000; + instr_sub <= is_alu_reg_reg && mem_rdata_q[14:12] == 3'b000 && mem_rdata_q[31:25] == 7'b0100000; + instr_sll <= is_alu_reg_reg && mem_rdata_q[14:12] == 3'b001 && mem_rdata_q[31:25] == 7'b0000000; + instr_slt <= is_alu_reg_reg && mem_rdata_q[14:12] == 3'b010 && mem_rdata_q[31:25] == 7'b0000000; + instr_sltu <= is_alu_reg_reg && mem_rdata_q[14:12] == 3'b011 && mem_rdata_q[31:25] == 7'b0000000; + instr_xor <= is_alu_reg_reg && mem_rdata_q[14:12] == 3'b100 && mem_rdata_q[31:25] == 7'b0000000; + instr_srl <= is_alu_reg_reg && mem_rdata_q[14:12] == 3'b101 && mem_rdata_q[31:25] == 7'b0000000; + instr_sra <= is_alu_reg_reg && mem_rdata_q[14:12] == 3'b101 && mem_rdata_q[31:25] == 7'b0100000; + instr_or <= is_alu_reg_reg && mem_rdata_q[14:12] == 3'b110 && mem_rdata_q[31:25] == 7'b0000000; + instr_and <= is_alu_reg_reg && mem_rdata_q[14:12] == 3'b111 && mem_rdata_q[31:25] == 7'b0000000; + + instr_rdcycle <= ((mem_rdata_q[6:0] == 7'b1110011 && mem_rdata_q[31:12] == 'b11000000000000000010) || + (mem_rdata_q[6:0] == 7'b1110011 && mem_rdata_q[31:12] == 'b11000000000100000010)) && ENABLE_COUNTERS; + instr_rdcycleh <= ((mem_rdata_q[6:0] == 7'b1110011 && mem_rdata_q[31:12] == 'b11001000000000000010) || + (mem_rdata_q[6:0] == 7'b1110011 && mem_rdata_q[31:12] == 'b11001000000100000010)) && ENABLE_COUNTERS && ENABLE_COUNTERS64; + instr_rdinstr <= (mem_rdata_q[6:0] == 7'b1110011 && mem_rdata_q[31:12] == 'b11000000001000000010) && ENABLE_COUNTERS; + instr_rdinstrh <= (mem_rdata_q[6:0] == 7'b1110011 && mem_rdata_q[31:12] == 'b11001000001000000010) && ENABLE_COUNTERS && ENABLE_COUNTERS64; + + instr_ecall_ebreak <= ((mem_rdata_q[6:0] == 7'b1110011 && !mem_rdata_q[31:21] && !mem_rdata_q[19:7]) || + (COMPRESSED_ISA && mem_rdata_q[15:0] == 16'h9002)); + + instr_getq <= mem_rdata_q[6:0] == 7'b0001011 && mem_rdata_q[31:25] == 7'b0000000 && ENABLE_IRQ && ENABLE_IRQ_QREGS; + instr_setq <= mem_rdata_q[6:0] == 7'b0001011 && mem_rdata_q[31:25] == 7'b0000001 && ENABLE_IRQ && ENABLE_IRQ_QREGS; + instr_maskirq <= mem_rdata_q[6:0] == 7'b0001011 && mem_rdata_q[31:25] == 7'b0000011 && ENABLE_IRQ; + instr_timer <= mem_rdata_q[6:0] == 7'b0001011 && mem_rdata_q[31:25] == 7'b0000101 && ENABLE_IRQ && ENABLE_IRQ_TIMER; + + is_slli_srli_srai <= is_alu_reg_imm && |{ + mem_rdata_q[14:12] == 3'b001 && mem_rdata_q[31:25] == 7'b0000000, + mem_rdata_q[14:12] == 3'b101 && mem_rdata_q[31:25] == 7'b0000000, + mem_rdata_q[14:12] == 3'b101 && mem_rdata_q[31:25] == 7'b0100000 + }; + + is_jalr_addi_slti_sltiu_xori_ori_andi <= instr_jalr || is_alu_reg_imm && |{ + mem_rdata_q[14:12] == 3'b000, + mem_rdata_q[14:12] == 3'b010, + mem_rdata_q[14:12] == 3'b011, + mem_rdata_q[14:12] == 3'b100, + mem_rdata_q[14:12] == 3'b110, + mem_rdata_q[14:12] == 3'b111 + }; + + is_sll_srl_sra <= is_alu_reg_reg && |{ + mem_rdata_q[14:12] == 3'b001 && mem_rdata_q[31:25] == 7'b0000000, + mem_rdata_q[14:12] == 3'b101 && mem_rdata_q[31:25] == 7'b0000000, + mem_rdata_q[14:12] == 3'b101 && mem_rdata_q[31:25] == 7'b0100000 + }; + + is_lui_auipc_jal_jalr_addi_add_sub <= 0; + is_compare <= 0; + + (* parallel_case *) + case (1'b1) + instr_jal: + decoded_imm <= decoded_imm_uj; + |{instr_lui, instr_auipc}: + decoded_imm <= mem_rdata_q[31:12] << 12; + |{instr_jalr, is_lb_lh_lw_lbu_lhu, is_alu_reg_imm}: + decoded_imm <= $signed(mem_rdata_q[31:20]); + is_beq_bne_blt_bge_bltu_bgeu: + decoded_imm <= $signed({mem_rdata_q[31], mem_rdata_q[7], mem_rdata_q[30:25], mem_rdata_q[11:8], 1'b0}); + is_sb_sh_sw: + decoded_imm <= $signed({mem_rdata_q[31:25], mem_rdata_q[11:7]}); + default: + decoded_imm <= 1'bx; + endcase + end + + if (!resetn) begin + is_beq_bne_blt_bge_bltu_bgeu <= 0; + is_compare <= 0; + + instr_beq <= 0; + instr_bne <= 0; + instr_blt <= 0; + instr_bge <= 0; + instr_bltu <= 0; + instr_bgeu <= 0; + + instr_addi <= 0; + instr_slti <= 0; + instr_sltiu <= 0; + instr_xori <= 0; + instr_ori <= 0; + instr_andi <= 0; + + instr_add <= 0; + instr_sub <= 0; + instr_sll <= 0; + instr_slt <= 0; + instr_sltu <= 0; + instr_xor <= 0; + instr_srl <= 0; + instr_sra <= 0; + instr_or <= 0; + instr_and <= 0; + end + end + + + // Main State Machine + + localparam cpu_state_trap = 8'b10000000; + localparam cpu_state_fetch = 8'b01000000; + localparam cpu_state_ld_rs1 = 8'b00100000; + localparam cpu_state_ld_rs2 = 8'b00010000; + localparam cpu_state_exec = 8'b00001000; + localparam cpu_state_shift = 8'b00000100; + localparam cpu_state_stmem = 8'b00000010; + localparam cpu_state_ldmem = 8'b00000001; + + reg [7:0] cpu_state; + reg [1:0] irq_state; + + `FORMAL_KEEP reg [127:0] dbg_ascii_state; + + always @* begin + dbg_ascii_state = ""; + if (cpu_state == cpu_state_trap) dbg_ascii_state = "trap"; + if (cpu_state == cpu_state_fetch) dbg_ascii_state = "fetch"; + if (cpu_state == cpu_state_ld_rs1) dbg_ascii_state = "ld_rs1"; + if (cpu_state == cpu_state_ld_rs2) dbg_ascii_state = "ld_rs2"; + if (cpu_state == cpu_state_exec) dbg_ascii_state = "exec"; + if (cpu_state == cpu_state_shift) dbg_ascii_state = "shift"; + if (cpu_state == cpu_state_stmem) dbg_ascii_state = "stmem"; + if (cpu_state == cpu_state_ldmem) dbg_ascii_state = "ldmem"; + end + + reg set_mem_do_rinst; + reg set_mem_do_rdata; + reg set_mem_do_wdata; + + reg latched_store; + reg latched_stalu; + reg latched_branch; + reg latched_compr; + reg latched_trace; + reg latched_is_lu; + reg latched_is_lh; + reg latched_is_lb; + reg [regindex_bits-1:0] latched_rd; + + reg [31:0] current_pc; + assign next_pc = latched_store && latched_branch ? reg_out & ~1 : reg_next_pc; + + reg [3:0] pcpi_timeout_counter; + reg pcpi_timeout; + + reg [31:0] next_irq_pending; + reg do_waitirq; + + reg [31:0] alu_out, alu_out_q; + reg alu_out_0, alu_out_0_q; + reg alu_wait, alu_wait_2; + + reg [31:0] alu_add_sub; + reg [31:0] alu_shl, alu_shr; + reg alu_eq, alu_ltu, alu_lts; + + generate if (TWO_CYCLE_ALU) begin + always @(posedge clk) begin + alu_add_sub <= instr_sub ? reg_op1 - reg_op2 : reg_op1 + reg_op2; + alu_eq <= reg_op1 == reg_op2; + alu_lts <= $signed(reg_op1) < $signed(reg_op2); + alu_ltu <= reg_op1 < reg_op2; + alu_shl <= reg_op1 << reg_op2[4:0]; + alu_shr <= $signed({instr_sra || instr_srai ? reg_op1[31] : 1'b0, reg_op1}) >>> reg_op2[4:0]; + end + end else begin + always @* begin + alu_add_sub = instr_sub ? reg_op1 - reg_op2 : reg_op1 + reg_op2; + alu_eq = reg_op1 == reg_op2; + alu_lts = $signed(reg_op1) < $signed(reg_op2); + alu_ltu = reg_op1 < reg_op2; + alu_shl = reg_op1 << reg_op2[4:0]; + alu_shr = $signed({instr_sra || instr_srai ? reg_op1[31] : 1'b0, reg_op1}) >>> reg_op2[4:0]; + end + end endgenerate + + always @* begin + alu_out_0 = 'bx; + (* parallel_case, full_case *) + case (1'b1) + instr_beq: + alu_out_0 = alu_eq; + instr_bne: + alu_out_0 = !alu_eq; + instr_bge: + alu_out_0 = !alu_lts; + instr_bgeu: + alu_out_0 = !alu_ltu; + is_slti_blt_slt && (!TWO_CYCLE_COMPARE || !{instr_beq,instr_bne,instr_bge,instr_bgeu}): + alu_out_0 = alu_lts; + is_sltiu_bltu_sltu && (!TWO_CYCLE_COMPARE || !{instr_beq,instr_bne,instr_bge,instr_bgeu}): + alu_out_0 = alu_ltu; + endcase + + alu_out = 'bx; + (* parallel_case, full_case *) + case (1'b1) + is_lui_auipc_jal_jalr_addi_add_sub: + alu_out = alu_add_sub; + is_compare: + alu_out = alu_out_0; + instr_xori || instr_xor: + alu_out = reg_op1 ^ reg_op2; + instr_ori || instr_or: + alu_out = reg_op1 | reg_op2; + instr_andi || instr_and: + alu_out = reg_op1 & reg_op2; + BARREL_SHIFTER && (instr_sll || instr_slli): + alu_out = alu_shl; + BARREL_SHIFTER && (instr_srl || instr_srli || instr_sra || instr_srai): + alu_out = alu_shr; + endcase + +`ifdef RISCV_FORMAL_BLACKBOX_ALU + alu_out_0 = $anyseq; + alu_out = $anyseq; +`endif + end + + reg clear_prefetched_high_word_q; + always @(posedge clk) clear_prefetched_high_word_q <= clear_prefetched_high_word; + + always @* begin + clear_prefetched_high_word = clear_prefetched_high_word_q; + if (!prefetched_high_word) + clear_prefetched_high_word = 0; + if (latched_branch || irq_state || !resetn) + clear_prefetched_high_word = COMPRESSED_ISA; + end + + reg cpuregs_write; + reg [31:0] cpuregs_wrdata; + reg [31:0] cpuregs_rs1; + reg [31:0] cpuregs_rs2; + reg [regindex_bits-1:0] decoded_rs; + + always @* begin + cpuregs_write = 0; + cpuregs_wrdata = 'bx; + + if (cpu_state == cpu_state_fetch) begin + (* parallel_case *) + case (1'b1) + latched_branch: begin + cpuregs_wrdata = reg_pc + (latched_compr ? 2 : 4); + cpuregs_write = 1; + end + latched_store && !latched_branch: begin + cpuregs_wrdata = latched_stalu ? alu_out_q : reg_out; + cpuregs_write = 1; + end + ENABLE_IRQ && irq_state[0]: begin + cpuregs_wrdata = reg_next_pc | latched_compr; + cpuregs_write = 1; + end + ENABLE_IRQ && irq_state[1]: begin + cpuregs_wrdata = irq_pending & ~irq_mask; + cpuregs_write = 1; + end + endcase + end + end + +`ifndef PICORV32_REGS + always @(posedge clk) begin + if (resetn && cpuregs_write && latched_rd) + cpuregs[latched_rd] <= cpuregs_wrdata; + end + + always @* begin + decoded_rs = 'bx; + if (ENABLE_REGS_DUALPORT) begin +`ifndef RISCV_FORMAL_BLACKBOX_REGS + cpuregs_rs1 = decoded_rs1 ? cpuregs[decoded_rs1] : 0; + cpuregs_rs2 = decoded_rs2 ? cpuregs[decoded_rs2] : 0; +`else + cpuregs_rs1 = decoded_rs1 ? $anyseq : 0; + cpuregs_rs2 = decoded_rs2 ? $anyseq : 0; +`endif + end else begin + decoded_rs = (cpu_state == cpu_state_ld_rs2) ? decoded_rs2 : decoded_rs1; +`ifndef RISCV_FORMAL_BLACKBOX_REGS + cpuregs_rs1 = decoded_rs ? cpuregs[decoded_rs] : 0; +`else + cpuregs_rs1 = decoded_rs ? $anyseq : 0; +`endif + cpuregs_rs2 = cpuregs_rs1; + end + end +`else + wire[31:0] cpuregs_rdata1; + wire[31:0] cpuregs_rdata2; + + wire [5:0] cpuregs_waddr = latched_rd; + wire [5:0] cpuregs_raddr1 = ENABLE_REGS_DUALPORT ? decoded_rs1 : decoded_rs; + wire [5:0] cpuregs_raddr2 = ENABLE_REGS_DUALPORT ? decoded_rs2 : 0; + + `PICORV32_REGS cpuregs ( + .clk(clk), + .wen(resetn && cpuregs_write && latched_rd), + .waddr(cpuregs_waddr), + .raddr1(cpuregs_raddr1), + .raddr2(cpuregs_raddr2), + .wdata(cpuregs_wrdata), + .rdata1(cpuregs_rdata1), + .rdata2(cpuregs_rdata2) + ); + + always @* begin + decoded_rs = 'bx; + if (ENABLE_REGS_DUALPORT) begin + cpuregs_rs1 = decoded_rs1 ? cpuregs_rdata1 : 0; + cpuregs_rs2 = decoded_rs2 ? cpuregs_rdata2 : 0; + end else begin + decoded_rs = (cpu_state == cpu_state_ld_rs2) ? decoded_rs2 : decoded_rs1; + cpuregs_rs1 = decoded_rs ? cpuregs_rdata1 : 0; + cpuregs_rs2 = cpuregs_rs1; + end + end +`endif + + assign launch_next_insn = cpu_state == cpu_state_fetch && decoder_trigger && (!ENABLE_IRQ || irq_delay || irq_active || !(irq_pending & ~irq_mask)); + + always @(posedge clk) begin + trap <= 0; + reg_sh <= 'bx; + reg_out <= 'bx; + set_mem_do_rinst = 0; + set_mem_do_rdata = 0; + set_mem_do_wdata = 0; + + alu_out_0_q <= alu_out_0; + alu_out_q <= alu_out; + + alu_wait <= 0; + alu_wait_2 <= 0; + + if (launch_next_insn) begin + dbg_rs1val <= 'bx; + dbg_rs2val <= 'bx; + dbg_rs1val_valid <= 0; + dbg_rs2val_valid <= 0; + end + + if (WITH_PCPI && CATCH_ILLINSN) begin + if (resetn && pcpi_valid && !pcpi_int_wait) begin + if (pcpi_timeout_counter) + pcpi_timeout_counter <= pcpi_timeout_counter - 1; + end else + pcpi_timeout_counter <= ~0; + pcpi_timeout <= !pcpi_timeout_counter; + end + + if (ENABLE_COUNTERS) begin + count_cycle <= resetn ? count_cycle + 1 : 0; + if (!ENABLE_COUNTERS64) count_cycle[63:32] <= 0; + end else begin + count_cycle <= 'bx; + count_instr <= 'bx; + end + + next_irq_pending = ENABLE_IRQ ? irq_pending & LATCHED_IRQ : 'bx; + + if (ENABLE_IRQ && ENABLE_IRQ_TIMER && timer) begin + if (timer - 1 == 0) + next_irq_pending[irq_timer] = 1; + timer <= timer - 1; + end + + if (ENABLE_IRQ) begin + next_irq_pending = next_irq_pending | irq; + end + + decoder_trigger <= mem_do_rinst && mem_done; + decoder_trigger_q <= decoder_trigger; + decoder_pseudo_trigger <= 0; + decoder_pseudo_trigger_q <= decoder_pseudo_trigger; + do_waitirq <= 0; + + trace_valid <= 0; + + if (!ENABLE_TRACE) + trace_data <= 'bx; + + if (!resetn) begin + reg_pc <= PROGADDR_RESET; + reg_next_pc <= PROGADDR_RESET; + if (ENABLE_COUNTERS) + count_instr <= 0; + latched_store <= 0; + latched_stalu <= 0; + latched_branch <= 0; + latched_trace <= 0; + latched_is_lu <= 0; + latched_is_lh <= 0; + latched_is_lb <= 0; + pcpi_valid <= 0; + pcpi_timeout <= 0; + irq_active <= 0; + irq_delay <= 0; + irq_mask <= ~0; + next_irq_pending = 0; + irq_state <= 0; + eoi <= 0; + timer <= 0; + if (~STACKADDR) begin + latched_store <= 1; + latched_rd <= 2; + reg_out <= STACKADDR; + end + cpu_state <= cpu_state_fetch; + end else + (* parallel_case, full_case *) + case (cpu_state) + cpu_state_trap: begin + trap <= 1; + end + + cpu_state_fetch: begin + mem_do_rinst <= !decoder_trigger && !do_waitirq; + mem_wordsize <= 0; + + current_pc = reg_next_pc; + + (* parallel_case *) + case (1'b1) + latched_branch: begin + current_pc = latched_store ? (latched_stalu ? alu_out_q : reg_out) & ~1 : reg_next_pc; + `debug($display("ST_RD: %2d 0x%08x, BRANCH 0x%08x", latched_rd, reg_pc + (latched_compr ? 2 : 4), current_pc);) + end + latched_store && !latched_branch: begin + `debug($display("ST_RD: %2d 0x%08x", latched_rd, latched_stalu ? alu_out_q : reg_out);) + end + ENABLE_IRQ && irq_state[0]: begin + current_pc = PROGADDR_IRQ; + irq_active <= 1; + mem_do_rinst <= 1; + end + ENABLE_IRQ && irq_state[1]: begin + eoi <= irq_pending & ~irq_mask; + next_irq_pending = next_irq_pending & irq_mask; + end + endcase + + if (ENABLE_TRACE && latched_trace) begin + latched_trace <= 0; + trace_valid <= 1; + if (latched_branch) + trace_data <= (irq_active ? TRACE_IRQ : 0) | TRACE_BRANCH | (current_pc & 32'hfffffffe); + else + trace_data <= (irq_active ? TRACE_IRQ : 0) | (latched_stalu ? alu_out_q : reg_out); + end + + reg_pc <= current_pc; + reg_next_pc <= current_pc; + + latched_store <= 0; + latched_stalu <= 0; + latched_branch <= 0; + latched_is_lu <= 0; + latched_is_lh <= 0; + latched_is_lb <= 0; + latched_rd <= decoded_rd; + latched_compr <= compressed_instr; + + if (ENABLE_IRQ && ((decoder_trigger && !irq_active && !irq_delay && |(irq_pending & ~irq_mask)) || irq_state)) begin + irq_state <= + irq_state == 2'b00 ? 2'b01 : + irq_state == 2'b01 ? 2'b10 : 2'b00; + latched_compr <= latched_compr; + if (ENABLE_IRQ_QREGS) + latched_rd <= irqregs_offset | irq_state[0]; + else + latched_rd <= irq_state[0] ? 4 : 3; + end else + if (ENABLE_IRQ && (decoder_trigger || do_waitirq) && instr_waitirq) begin + if (irq_pending) begin + latched_store <= 1; + reg_out <= irq_pending; + reg_next_pc <= current_pc + (compressed_instr ? 2 : 4); + mem_do_rinst <= 1; + end else + do_waitirq <= 1; + end else + if (decoder_trigger) begin + `debug($display("-- %-0t", $time);) + irq_delay <= irq_active; + reg_next_pc <= current_pc + (compressed_instr ? 2 : 4); + if (ENABLE_TRACE) + latched_trace <= 1; + if (ENABLE_COUNTERS) begin + count_instr <= count_instr + 1; + if (!ENABLE_COUNTERS64) count_instr[63:32] <= 0; + end + if (instr_jal) begin + mem_do_rinst <= 1; + reg_next_pc <= current_pc + decoded_imm_uj; + latched_branch <= 1; + end else begin + mem_do_rinst <= 0; + mem_do_prefetch <= !instr_jalr && !instr_retirq; + cpu_state <= cpu_state_ld_rs1; + end + end + end + + cpu_state_ld_rs1: begin + reg_op1 <= 'bx; + reg_op2 <= 'bx; + + (* parallel_case *) + case (1'b1) + (CATCH_ILLINSN || WITH_PCPI) && instr_trap: begin + if (WITH_PCPI) begin + `debug($display("LD_RS1: %2d 0x%08x", decoded_rs1, cpuregs_rs1);) + reg_op1 <= cpuregs_rs1; + dbg_rs1val <= cpuregs_rs1; + dbg_rs1val_valid <= 1; + if (ENABLE_REGS_DUALPORT) begin + pcpi_valid <= 1; + `debug($display("LD_RS2: %2d 0x%08x", decoded_rs2, cpuregs_rs2);) + reg_sh <= cpuregs_rs2; + reg_op2 <= cpuregs_rs2; + dbg_rs2val <= cpuregs_rs2; + dbg_rs2val_valid <= 1; + if (pcpi_int_ready) begin + mem_do_rinst <= 1; + pcpi_valid <= 0; + reg_out <= pcpi_int_rd; + latched_store <= pcpi_int_wr; + cpu_state <= cpu_state_fetch; + end else + if (CATCH_ILLINSN && (pcpi_timeout || instr_ecall_ebreak)) begin + pcpi_valid <= 0; + `debug($display("EBREAK OR UNSUPPORTED INSN AT 0x%08x", reg_pc);) + if (ENABLE_IRQ && !irq_mask[irq_ebreak] && !irq_active) begin + next_irq_pending[irq_ebreak] = 1; + cpu_state <= cpu_state_fetch; + end else + cpu_state <= cpu_state_trap; + end + end else begin + cpu_state <= cpu_state_ld_rs2; + end + end else begin + `debug($display("EBREAK OR UNSUPPORTED INSN AT 0x%08x", reg_pc);) + if (ENABLE_IRQ && !irq_mask[irq_ebreak] && !irq_active) begin + next_irq_pending[irq_ebreak] = 1; + cpu_state <= cpu_state_fetch; + end else + cpu_state <= cpu_state_trap; + end + end + ENABLE_COUNTERS && is_rdcycle_rdcycleh_rdinstr_rdinstrh: begin + (* parallel_case, full_case *) + case (1'b1) + instr_rdcycle: + reg_out <= count_cycle[31:0]; + instr_rdcycleh && ENABLE_COUNTERS64: + reg_out <= count_cycle[63:32]; + instr_rdinstr: + reg_out <= count_instr[31:0]; + instr_rdinstrh && ENABLE_COUNTERS64: + reg_out <= count_instr[63:32]; + endcase + latched_store <= 1; + cpu_state <= cpu_state_fetch; + end + is_lui_auipc_jal: begin + reg_op1 <= instr_lui ? 0 : reg_pc; + reg_op2 <= decoded_imm; + if (TWO_CYCLE_ALU) + alu_wait <= 1; + else + mem_do_rinst <= mem_do_prefetch; + cpu_state <= cpu_state_exec; + end + ENABLE_IRQ && ENABLE_IRQ_QREGS && instr_getq: begin + `debug($display("LD_RS1: %2d 0x%08x", decoded_rs1, cpuregs_rs1);) + reg_out <= cpuregs_rs1; + dbg_rs1val <= cpuregs_rs1; + dbg_rs1val_valid <= 1; + latched_store <= 1; + cpu_state <= cpu_state_fetch; + end + ENABLE_IRQ && ENABLE_IRQ_QREGS && instr_setq: begin + `debug($display("LD_RS1: %2d 0x%08x", decoded_rs1, cpuregs_rs1);) + reg_out <= cpuregs_rs1; + dbg_rs1val <= cpuregs_rs1; + dbg_rs1val_valid <= 1; + latched_rd <= latched_rd | irqregs_offset; + latched_store <= 1; + cpu_state <= cpu_state_fetch; + end + ENABLE_IRQ && instr_retirq: begin + eoi <= 0; + irq_active <= 0; + latched_branch <= 1; + latched_store <= 1; + `debug($display("LD_RS1: %2d 0x%08x", decoded_rs1, cpuregs_rs1);) + reg_out <= CATCH_MISALIGN ? (cpuregs_rs1 & 32'h fffffffe) : cpuregs_rs1; + dbg_rs1val <= cpuregs_rs1; + dbg_rs1val_valid <= 1; + cpu_state <= cpu_state_fetch; + end + ENABLE_IRQ && instr_maskirq: begin + latched_store <= 1; + reg_out <= irq_mask; + `debug($display("LD_RS1: %2d 0x%08x", decoded_rs1, cpuregs_rs1);) + irq_mask <= cpuregs_rs1 | MASKED_IRQ; + dbg_rs1val <= cpuregs_rs1; + dbg_rs1val_valid <= 1; + cpu_state <= cpu_state_fetch; + end + ENABLE_IRQ && ENABLE_IRQ_TIMER && instr_timer: begin + latched_store <= 1; + reg_out <= timer; + `debug($display("LD_RS1: %2d 0x%08x", decoded_rs1, cpuregs_rs1);) + timer <= cpuregs_rs1; + dbg_rs1val <= cpuregs_rs1; + dbg_rs1val_valid <= 1; + cpu_state <= cpu_state_fetch; + end + is_lb_lh_lw_lbu_lhu && !instr_trap: begin + `debug($display("LD_RS1: %2d 0x%08x", decoded_rs1, cpuregs_rs1);) + reg_op1 <= cpuregs_rs1; + dbg_rs1val <= cpuregs_rs1; + dbg_rs1val_valid <= 1; + cpu_state <= cpu_state_ldmem; + mem_do_rinst <= 1; + end + is_slli_srli_srai && !BARREL_SHIFTER: begin + `debug($display("LD_RS1: %2d 0x%08x", decoded_rs1, cpuregs_rs1);) + reg_op1 <= cpuregs_rs1; + dbg_rs1val <= cpuregs_rs1; + dbg_rs1val_valid <= 1; + reg_sh <= decoded_rs2; + cpu_state <= cpu_state_shift; + end + is_jalr_addi_slti_sltiu_xori_ori_andi, is_slli_srli_srai && BARREL_SHIFTER: begin + `debug($display("LD_RS1: %2d 0x%08x", decoded_rs1, cpuregs_rs1);) + reg_op1 <= cpuregs_rs1; + dbg_rs1val <= cpuregs_rs1; + dbg_rs1val_valid <= 1; + reg_op2 <= is_slli_srli_srai && BARREL_SHIFTER ? decoded_rs2 : decoded_imm; + if (TWO_CYCLE_ALU) + alu_wait <= 1; + else + mem_do_rinst <= mem_do_prefetch; + cpu_state <= cpu_state_exec; + end + default: begin + `debug($display("LD_RS1: %2d 0x%08x", decoded_rs1, cpuregs_rs1);) + reg_op1 <= cpuregs_rs1; + dbg_rs1val <= cpuregs_rs1; + dbg_rs1val_valid <= 1; + if (ENABLE_REGS_DUALPORT) begin + `debug($display("LD_RS2: %2d 0x%08x", decoded_rs2, cpuregs_rs2);) + reg_sh <= cpuregs_rs2; + reg_op2 <= cpuregs_rs2; + dbg_rs2val <= cpuregs_rs2; + dbg_rs2val_valid <= 1; + (* parallel_case *) + case (1'b1) + is_sb_sh_sw: begin + cpu_state <= cpu_state_stmem; + mem_do_rinst <= 1; + end + is_sll_srl_sra && !BARREL_SHIFTER: begin + cpu_state <= cpu_state_shift; + end + default: begin + if (TWO_CYCLE_ALU || (TWO_CYCLE_COMPARE && is_beq_bne_blt_bge_bltu_bgeu)) begin + alu_wait_2 <= TWO_CYCLE_ALU && (TWO_CYCLE_COMPARE && is_beq_bne_blt_bge_bltu_bgeu); + alu_wait <= 1; + end else + mem_do_rinst <= mem_do_prefetch; + cpu_state <= cpu_state_exec; + end + endcase + end else + cpu_state <= cpu_state_ld_rs2; + end + endcase + end + + cpu_state_ld_rs2: begin + `debug($display("LD_RS2: %2d 0x%08x", decoded_rs2, cpuregs_rs2);) + reg_sh <= cpuregs_rs2; + reg_op2 <= cpuregs_rs2; + dbg_rs2val <= cpuregs_rs2; + dbg_rs2val_valid <= 1; + + (* parallel_case *) + case (1'b1) + WITH_PCPI && instr_trap: begin + pcpi_valid <= 1; + if (pcpi_int_ready) begin + mem_do_rinst <= 1; + pcpi_valid <= 0; + reg_out <= pcpi_int_rd; + latched_store <= pcpi_int_wr; + cpu_state <= cpu_state_fetch; + end else + if (CATCH_ILLINSN && (pcpi_timeout || instr_ecall_ebreak)) begin + pcpi_valid <= 0; + `debug($display("EBREAK OR UNSUPPORTED INSN AT 0x%08x", reg_pc);) + if (ENABLE_IRQ && !irq_mask[irq_ebreak] && !irq_active) begin + next_irq_pending[irq_ebreak] = 1; + cpu_state <= cpu_state_fetch; + end else + cpu_state <= cpu_state_trap; + end + end + is_sb_sh_sw: begin + cpu_state <= cpu_state_stmem; + mem_do_rinst <= 1; + end + is_sll_srl_sra && !BARREL_SHIFTER: begin + cpu_state <= cpu_state_shift; + end + default: begin + if (TWO_CYCLE_ALU || (TWO_CYCLE_COMPARE && is_beq_bne_blt_bge_bltu_bgeu)) begin + alu_wait_2 <= TWO_CYCLE_ALU && (TWO_CYCLE_COMPARE && is_beq_bne_blt_bge_bltu_bgeu); + alu_wait <= 1; + end else + mem_do_rinst <= mem_do_prefetch; + cpu_state <= cpu_state_exec; + end + endcase + end + + cpu_state_exec: begin + reg_out <= reg_pc + decoded_imm; + if ((TWO_CYCLE_ALU || TWO_CYCLE_COMPARE) && (alu_wait || alu_wait_2)) begin + mem_do_rinst <= mem_do_prefetch && !alu_wait_2; + alu_wait <= alu_wait_2; + end else + if (is_beq_bne_blt_bge_bltu_bgeu) begin + latched_rd <= 0; + latched_store <= TWO_CYCLE_COMPARE ? alu_out_0_q : alu_out_0; + latched_branch <= TWO_CYCLE_COMPARE ? alu_out_0_q : alu_out_0; + if (mem_done) + cpu_state <= cpu_state_fetch; + if (TWO_CYCLE_COMPARE ? alu_out_0_q : alu_out_0) begin + decoder_trigger <= 0; + set_mem_do_rinst = 1; + end + end else begin + latched_branch <= instr_jalr; + latched_store <= 1; + latched_stalu <= 1; + cpu_state <= cpu_state_fetch; + end + end + + cpu_state_shift: begin + latched_store <= 1; + if (reg_sh == 0) begin + reg_out <= reg_op1; + mem_do_rinst <= mem_do_prefetch; + cpu_state <= cpu_state_fetch; + end else if (TWO_STAGE_SHIFT && reg_sh >= 4) begin + (* parallel_case, full_case *) + case (1'b1) + instr_slli || instr_sll: reg_op1 <= reg_op1 << 4; + instr_srli || instr_srl: reg_op1 <= reg_op1 >> 4; + instr_srai || instr_sra: reg_op1 <= $signed(reg_op1) >>> 4; + endcase + reg_sh <= reg_sh - 4; + end else begin + (* parallel_case, full_case *) + case (1'b1) + instr_slli || instr_sll: reg_op1 <= reg_op1 << 1; + instr_srli || instr_srl: reg_op1 <= reg_op1 >> 1; + instr_srai || instr_sra: reg_op1 <= $signed(reg_op1) >>> 1; + endcase + reg_sh <= reg_sh - 1; + end + end + + cpu_state_stmem: begin + if (ENABLE_TRACE) + reg_out <= reg_op2; + if (!mem_do_prefetch || mem_done) begin + if (!mem_do_wdata) begin + (* parallel_case, full_case *) + case (1'b1) + instr_sb: mem_wordsize <= 2; + instr_sh: mem_wordsize <= 1; + instr_sw: mem_wordsize <= 0; + endcase + if (ENABLE_TRACE) begin + trace_valid <= 1; + trace_data <= (irq_active ? TRACE_IRQ : 0) | TRACE_ADDR | ((reg_op1 + decoded_imm) & 32'hffffffff); + end + reg_op1 <= reg_op1 + decoded_imm; + set_mem_do_wdata = 1; + end + if (!mem_do_prefetch && mem_done) begin + cpu_state <= cpu_state_fetch; + decoder_trigger <= 1; + decoder_pseudo_trigger <= 1; + end + end + end + + cpu_state_ldmem: begin + latched_store <= 1; + if (!mem_do_prefetch || mem_done) begin + if (!mem_do_rdata) begin + (* parallel_case, full_case *) + case (1'b1) + instr_lb || instr_lbu: mem_wordsize <= 2; + instr_lh || instr_lhu: mem_wordsize <= 1; + instr_lw: mem_wordsize <= 0; + endcase + latched_is_lu <= is_lbu_lhu_lw; + latched_is_lh <= instr_lh; + latched_is_lb <= instr_lb; + if (ENABLE_TRACE) begin + trace_valid <= 1; + trace_data <= (irq_active ? TRACE_IRQ : 0) | TRACE_ADDR | ((reg_op1 + decoded_imm) & 32'hffffffff); + end + reg_op1 <= reg_op1 + decoded_imm; + set_mem_do_rdata = 1; + end + if (!mem_do_prefetch && mem_done) begin + (* parallel_case, full_case *) + case (1'b1) + latched_is_lu: reg_out <= mem_rdata_word; + latched_is_lh: reg_out <= $signed(mem_rdata_word[15:0]); + latched_is_lb: reg_out <= $signed(mem_rdata_word[7:0]); + endcase + decoder_trigger <= 1; + decoder_pseudo_trigger <= 1; + cpu_state <= cpu_state_fetch; + end + end + end + endcase + + if (CATCH_MISALIGN && resetn && (mem_do_rdata || mem_do_wdata)) begin + if (mem_wordsize == 0 && reg_op1[1:0] != 0) begin + `debug($display("MISALIGNED WORD: 0x%08x", reg_op1);) + if (ENABLE_IRQ && !irq_mask[irq_buserror] && !irq_active) begin + next_irq_pending[irq_buserror] = 1; + end else + cpu_state <= cpu_state_trap; + end + if (mem_wordsize == 1 && reg_op1[0] != 0) begin + `debug($display("MISALIGNED HALFWORD: 0x%08x", reg_op1);) + if (ENABLE_IRQ && !irq_mask[irq_buserror] && !irq_active) begin + next_irq_pending[irq_buserror] = 1; + end else + cpu_state <= cpu_state_trap; + end + end + if (CATCH_MISALIGN && resetn && mem_do_rinst && (COMPRESSED_ISA ? reg_pc[0] : |reg_pc[1:0])) begin + `debug($display("MISALIGNED INSTRUCTION: 0x%08x", reg_pc);) + if (ENABLE_IRQ && !irq_mask[irq_buserror] && !irq_active) begin + next_irq_pending[irq_buserror] = 1; + end else + cpu_state <= cpu_state_trap; + end + if (!CATCH_ILLINSN && decoder_trigger_q && !decoder_pseudo_trigger_q && instr_ecall_ebreak) begin + cpu_state <= cpu_state_trap; + end + + if (!resetn || mem_done) begin + mem_do_prefetch <= 0; + mem_do_rinst <= 0; + mem_do_rdata <= 0; + mem_do_wdata <= 0; + end + + if (set_mem_do_rinst) + mem_do_rinst <= 1; + if (set_mem_do_rdata) + mem_do_rdata <= 1; + if (set_mem_do_wdata) + mem_do_wdata <= 1; + + irq_pending <= next_irq_pending & ~MASKED_IRQ; + + if (!CATCH_MISALIGN) begin + if (COMPRESSED_ISA) begin + reg_pc[0] <= 0; + reg_next_pc[0] <= 0; + end else begin + reg_pc[1:0] <= 0; + reg_next_pc[1:0] <= 0; + end + end + current_pc = 'bx; + end + +`ifdef RISCV_FORMAL + reg dbg_irq_call; + reg dbg_irq_enter; + reg [31:0] dbg_irq_ret; + always @(posedge clk) begin + rvfi_valid <= resetn && (launch_next_insn || trap) && dbg_valid_insn; + rvfi_order <= resetn ? rvfi_order + rvfi_valid : 0; + + rvfi_insn <= dbg_insn_opcode; + rvfi_rs1_addr <= dbg_rs1val_valid ? dbg_insn_rs1 : 0; + rvfi_rs2_addr <= dbg_rs2val_valid ? dbg_insn_rs2 : 0; + rvfi_pc_rdata <= dbg_insn_addr; + rvfi_rs1_rdata <= dbg_rs1val_valid ? dbg_rs1val : 0; + rvfi_rs2_rdata <= dbg_rs2val_valid ? dbg_rs2val : 0; + rvfi_trap <= trap; + rvfi_halt <= trap; + rvfi_intr <= dbg_irq_enter; + + if (!resetn) begin + dbg_irq_call <= 0; + dbg_irq_enter <= 0; + end else + if (rvfi_valid) begin + dbg_irq_call <= 0; + dbg_irq_enter <= dbg_irq_call; + end else + if (irq_state == 1) begin + dbg_irq_call <= 1; + dbg_irq_ret <= next_pc; + end + + if (!resetn) begin + rvfi_rd_addr <= 0; + rvfi_rd_wdata <= 0; + end else + if (cpuregs_write && !irq_state) begin + rvfi_rd_addr <= latched_rd; + rvfi_rd_wdata <= latched_rd ? cpuregs_wrdata : 0; + end else + if (rvfi_valid) begin + rvfi_rd_addr <= 0; + rvfi_rd_wdata <= 0; + end + + casez (dbg_insn_opcode) + 32'b 0000000_?????_000??_???_?????_0001011: begin // getq + rvfi_rs1_addr <= 0; + rvfi_rs1_rdata <= 0; + end + 32'b 0000001_?????_?????_???_000??_0001011: begin // setq + rvfi_rd_addr <= 0; + rvfi_rd_wdata <= 0; + end + 32'b 0000010_?????_00000_???_00000_0001011: begin // retirq + rvfi_rs1_addr <= 0; + rvfi_rs1_rdata <= 0; + end + endcase + + if (!dbg_irq_call) begin + if (dbg_mem_instr) begin + rvfi_mem_addr <= 0; + rvfi_mem_rmask <= 0; + rvfi_mem_wmask <= 0; + rvfi_mem_rdata <= 0; + rvfi_mem_wdata <= 0; + end else + if (dbg_mem_valid && dbg_mem_ready) begin + rvfi_mem_addr <= dbg_mem_addr; + rvfi_mem_rmask <= dbg_mem_wstrb ? 0 : ~0; + rvfi_mem_wmask <= dbg_mem_wstrb; + rvfi_mem_rdata <= dbg_mem_rdata; + rvfi_mem_wdata <= dbg_mem_wdata; + end + end + end + + always @* begin + rvfi_pc_wdata = dbg_irq_call ? dbg_irq_ret : dbg_insn_addr; + end +`endif + + // Formal Verification +`ifdef FORMAL + reg [3:0] last_mem_nowait; + always @(posedge clk) + last_mem_nowait <= {last_mem_nowait, mem_ready || !mem_valid}; + + // stall the memory interface for max 4 cycles + restrict property (|last_mem_nowait || mem_ready || !mem_valid); + + // resetn low in first cycle, after that resetn high + restrict property (resetn != $initstate); + + // this just makes it much easier to read traces. uncomment as needed. + // assume property (mem_valid || !mem_ready); + + reg ok; + always @* begin + if (resetn) begin + // instruction fetches are read-only + if (mem_valid && mem_instr) + assert (mem_wstrb == 0); + + // cpu_state must be valid + ok = 0; + if (cpu_state == cpu_state_trap) ok = 1; + if (cpu_state == cpu_state_fetch) ok = 1; + if (cpu_state == cpu_state_ld_rs1) ok = 1; + if (cpu_state == cpu_state_ld_rs2) ok = !ENABLE_REGS_DUALPORT; + if (cpu_state == cpu_state_exec) ok = 1; + if (cpu_state == cpu_state_shift) ok = 1; + if (cpu_state == cpu_state_stmem) ok = 1; + if (cpu_state == cpu_state_ldmem) ok = 1; + assert (ok); + end + end + + reg last_mem_la_read = 0; + reg last_mem_la_write = 0; + reg [31:0] last_mem_la_addr; + reg [31:0] last_mem_la_wdata; + reg [3:0] last_mem_la_wstrb = 0; + + always @(posedge clk) begin + last_mem_la_read <= mem_la_read; + last_mem_la_write <= mem_la_write; + last_mem_la_addr <= mem_la_addr; + last_mem_la_wdata <= mem_la_wdata; + last_mem_la_wstrb <= mem_la_wstrb; + + if (last_mem_la_read) begin + assert(mem_valid); + assert(mem_addr == last_mem_la_addr); + assert(mem_wstrb == 0); + end + if (last_mem_la_write) begin + assert(mem_valid); + assert(mem_addr == last_mem_la_addr); + assert(mem_wdata == last_mem_la_wdata); + assert(mem_wstrb == last_mem_la_wstrb); + end + if (mem_la_read || mem_la_write) begin + assert(!mem_valid || mem_ready); + end + end +`endif +endmodule + +// This is a simple example implementation of PICORV32_REGS. +// Use the PICORV32_REGS mechanism if you want to use custom +// memory resources to implement the processor register file. +// Note that your implementation must match the requirements of +// the PicoRV32 configuration. (e.g. QREGS, etc) +module picorv32_regs ( + input clk, wen, + input [5:0] waddr, + input [5:0] raddr1, + input [5:0] raddr2, + input [31:0] wdata, + output [31:0] rdata1, + output [31:0] rdata2 +); + reg [31:0] regs [0:30]; + + always @(posedge clk) + if (wen) regs[~waddr[4:0]] <= wdata; + + assign rdata1 = regs[~raddr1[4:0]]; + assign rdata2 = regs[~raddr2[4:0]]; +endmodule + + +/*************************************************************** + * picorv32_pcpi_mul + ***************************************************************/ + +module picorv32_pcpi_mul #( + parameter STEPS_AT_ONCE = 1, + parameter CARRY_CHAIN = 4 +) ( + input clk, resetn, + + input pcpi_valid, + input [31:0] pcpi_insn, + input [31:0] pcpi_rs1, + input [31:0] pcpi_rs2, + output reg pcpi_wr, + output reg [31:0] pcpi_rd, + output reg pcpi_wait, + output reg pcpi_ready +); + reg instr_mul, instr_mulh, instr_mulhsu, instr_mulhu; + wire instr_any_mul = |{instr_mul, instr_mulh, instr_mulhsu, instr_mulhu}; + wire instr_any_mulh = |{instr_mulh, instr_mulhsu, instr_mulhu}; + wire instr_rs1_signed = |{instr_mulh, instr_mulhsu}; + wire instr_rs2_signed = |{instr_mulh}; + + reg pcpi_wait_q; + wire mul_start = pcpi_wait && !pcpi_wait_q; + + always @(posedge clk) begin + instr_mul <= 0; + instr_mulh <= 0; + instr_mulhsu <= 0; + instr_mulhu <= 0; + + if (resetn && pcpi_valid && pcpi_insn[6:0] == 7'b0110011 && pcpi_insn[31:25] == 7'b0000001) begin + case (pcpi_insn[14:12]) + 3'b000: instr_mul <= 1; + 3'b001: instr_mulh <= 1; + 3'b010: instr_mulhsu <= 1; + 3'b011: instr_mulhu <= 1; + endcase + end + + pcpi_wait <= instr_any_mul; + pcpi_wait_q <= pcpi_wait; + end + + reg [63:0] rs1, rs2, rd, rdx; + reg [63:0] next_rs1, next_rs2, this_rs2; + reg [63:0] next_rd, next_rdx, next_rdt; + reg [6:0] mul_counter; + reg mul_waiting; + reg mul_finish; + integer i, j; + + // carry save accumulator + always @* begin + next_rd = rd; + next_rdx = rdx; + next_rs1 = rs1; + next_rs2 = rs2; + + for (i = 0; i < STEPS_AT_ONCE; i=i+1) begin + this_rs2 = next_rs1[0] ? next_rs2 : 0; + if (CARRY_CHAIN == 0) begin + next_rdt = next_rd ^ next_rdx ^ this_rs2; + next_rdx = ((next_rd & next_rdx) | (next_rd & this_rs2) | (next_rdx & this_rs2)) << 1; + next_rd = next_rdt; + end else begin + next_rdt = 0; + for (j = 0; j < 64; j = j + CARRY_CHAIN) + {next_rdt[j+CARRY_CHAIN-1], next_rd[j +: CARRY_CHAIN]} = + next_rd[j +: CARRY_CHAIN] + next_rdx[j +: CARRY_CHAIN] + this_rs2[j +: CARRY_CHAIN]; + next_rdx = next_rdt << 1; + end + next_rs1 = next_rs1 >> 1; + next_rs2 = next_rs2 << 1; + end + end + + always @(posedge clk) begin + mul_finish <= 0; + if (!resetn) begin + mul_waiting <= 1; + end else + if (mul_waiting) begin + if (instr_rs1_signed) + rs1 <= $signed(pcpi_rs1); + else + rs1 <= $unsigned(pcpi_rs1); + + if (instr_rs2_signed) + rs2 <= $signed(pcpi_rs2); + else + rs2 <= $unsigned(pcpi_rs2); + + rd <= 0; + rdx <= 0; + mul_counter <= (instr_any_mulh ? 63 - STEPS_AT_ONCE : 31 - STEPS_AT_ONCE); + mul_waiting <= !mul_start; + end else begin + rd <= next_rd; + rdx <= next_rdx; + rs1 <= next_rs1; + rs2 <= next_rs2; + + mul_counter <= mul_counter - STEPS_AT_ONCE; + if (mul_counter[6]) begin + mul_finish <= 1; + mul_waiting <= 1; + end + end + end + + always @(posedge clk) begin + pcpi_wr <= 0; + pcpi_ready <= 0; + if (mul_finish && resetn) begin + pcpi_wr <= 1; + pcpi_ready <= 1; + pcpi_rd <= instr_any_mulh ? rd >> 32 : rd; + end + end +endmodule + +module picorv32_pcpi_fast_mul #( + parameter EXTRA_MUL_FFS = 0, + parameter EXTRA_INSN_FFS = 0, + parameter MUL_CLKGATE = 0 +) ( + input clk, resetn, + + input pcpi_valid, + input [31:0] pcpi_insn, + input [31:0] pcpi_rs1, + input [31:0] pcpi_rs2, + output pcpi_wr, + output [31:0] pcpi_rd, + output pcpi_wait, + output pcpi_ready +); + reg instr_mul, instr_mulh, instr_mulhsu, instr_mulhu; + wire instr_any_mul = |{instr_mul, instr_mulh, instr_mulhsu, instr_mulhu}; + wire instr_any_mulh = |{instr_mulh, instr_mulhsu, instr_mulhu}; + wire instr_rs1_signed = |{instr_mulh, instr_mulhsu}; + wire instr_rs2_signed = |{instr_mulh}; + + reg shift_out; + reg [3:0] active; + reg [32:0] rs1, rs2, rs1_q, rs2_q; + reg [63:0] rd, rd_q; + + wire pcpi_insn_valid = pcpi_valid && pcpi_insn[6:0] == 7'b0110011 && pcpi_insn[31:25] == 7'b0000001; + reg pcpi_insn_valid_q; + + always @* begin + instr_mul = 0; + instr_mulh = 0; + instr_mulhsu = 0; + instr_mulhu = 0; + + if (resetn && (EXTRA_INSN_FFS ? pcpi_insn_valid_q : pcpi_insn_valid)) begin + case (pcpi_insn[14:12]) + 3'b000: instr_mul = 1; + 3'b001: instr_mulh = 1; + 3'b010: instr_mulhsu = 1; + 3'b011: instr_mulhu = 1; + endcase + end + end + + always @(posedge clk) begin + pcpi_insn_valid_q <= pcpi_insn_valid; + if (!MUL_CLKGATE || active[0]) begin + rs1_q <= rs1; + rs2_q <= rs2; + end + if (!MUL_CLKGATE || active[1]) begin + rd <= $signed(EXTRA_MUL_FFS ? rs1_q : rs1) * $signed(EXTRA_MUL_FFS ? rs2_q : rs2); + end + if (!MUL_CLKGATE || active[2]) begin + rd_q <= rd; + end + end + + always @(posedge clk) begin + if (instr_any_mul && !(EXTRA_MUL_FFS ? active[3:0] : active[1:0])) begin + if (instr_rs1_signed) + rs1 <= $signed(pcpi_rs1); + else + rs1 <= $unsigned(pcpi_rs1); + + if (instr_rs2_signed) + rs2 <= $signed(pcpi_rs2); + else + rs2 <= $unsigned(pcpi_rs2); + active[0] <= 1; + end else begin + active[0] <= 0; + end + + active[3:1] <= active; + shift_out <= instr_any_mulh; + + if (!resetn) + active <= 0; + end + + assign pcpi_wr = active[EXTRA_MUL_FFS ? 3 : 1]; + assign pcpi_wait = 0; + assign pcpi_ready = active[EXTRA_MUL_FFS ? 3 : 1]; +`ifdef RISCV_FORMAL_ALTOPS + assign pcpi_rd = + instr_mul ? (pcpi_rs1 + pcpi_rs2) ^ 32'h5876063e : + instr_mulh ? (pcpi_rs1 + pcpi_rs2) ^ 32'hf6583fb7 : + instr_mulhsu ? (pcpi_rs1 - pcpi_rs2) ^ 32'hecfbe137 : + instr_mulhu ? (pcpi_rs1 + pcpi_rs2) ^ 32'h949ce5e8 : 1'bx; +`else + assign pcpi_rd = shift_out ? (EXTRA_MUL_FFS ? rd_q : rd) >> 32 : (EXTRA_MUL_FFS ? rd_q : rd); +`endif +endmodule + + +/*************************************************************** + * picorv32_pcpi_div + ***************************************************************/ + +module picorv32_pcpi_div ( + input clk, resetn, + + input pcpi_valid, + input [31:0] pcpi_insn, + input [31:0] pcpi_rs1, + input [31:0] pcpi_rs2, + output reg pcpi_wr, + output reg [31:0] pcpi_rd, + output reg pcpi_wait, + output reg pcpi_ready +); + reg instr_div, instr_divu, instr_rem, instr_remu; + wire instr_any_div_rem = |{instr_div, instr_divu, instr_rem, instr_remu}; + + reg pcpi_wait_q; + wire start = pcpi_wait && !pcpi_wait_q; + + always @(posedge clk) begin + instr_div <= 0; + instr_divu <= 0; + instr_rem <= 0; + instr_remu <= 0; + + if (resetn && pcpi_valid && !pcpi_ready && pcpi_insn[6:0] == 7'b0110011 && pcpi_insn[31:25] == 7'b0000001) begin + case (pcpi_insn[14:12]) + 3'b100: instr_div <= 1; + 3'b101: instr_divu <= 1; + 3'b110: instr_rem <= 1; + 3'b111: instr_remu <= 1; + endcase + end + + pcpi_wait <= instr_any_div_rem && resetn; + pcpi_wait_q <= pcpi_wait && resetn; + end + + reg [31:0] dividend; + reg [62:0] divisor; + reg [31:0] quotient; + reg [31:0] quotient_msk; + reg running; + reg outsign; + + always @(posedge clk) begin + pcpi_ready <= 0; + pcpi_wr <= 0; + pcpi_rd <= 'bx; + + if (!resetn) begin + running <= 0; + end else + if (start) begin + running <= 1; + dividend <= (instr_div || instr_rem) && pcpi_rs1[31] ? -pcpi_rs1 : pcpi_rs1; + divisor <= ((instr_div || instr_rem) && pcpi_rs2[31] ? -pcpi_rs2 : pcpi_rs2) << 31; + outsign <= (instr_div && (pcpi_rs1[31] != pcpi_rs2[31]) && |pcpi_rs2) || (instr_rem && pcpi_rs1[31]); + quotient <= 0; + quotient_msk <= 1 << 31; + end else + if (!quotient_msk && running) begin + running <= 0; + pcpi_ready <= 1; + pcpi_wr <= 1; +`ifdef RISCV_FORMAL_ALTOPS + case (1) + instr_div: pcpi_rd <= (pcpi_rs1 - pcpi_rs2) ^ 32'h7f8529ec; + instr_divu: pcpi_rd <= (pcpi_rs1 - pcpi_rs2) ^ 32'h10e8fd70; + instr_rem: pcpi_rd <= (pcpi_rs1 - pcpi_rs2) ^ 32'h8da68fa5; + instr_remu: pcpi_rd <= (pcpi_rs1 - pcpi_rs2) ^ 32'h3138d0e1; + endcase +`else + if (instr_div || instr_divu) + pcpi_rd <= outsign ? -quotient : quotient; + else + pcpi_rd <= outsign ? -dividend : dividend; +`endif + end else begin + if (divisor <= dividend) begin + dividend <= dividend - divisor; + quotient <= quotient | quotient_msk; + end + divisor <= divisor >> 1; +`ifdef RISCV_FORMAL_ALTOPS + quotient_msk <= quotient_msk >> 5; +`else + quotient_msk <= quotient_msk >> 1; +`endif + end + end +endmodule + + +/*************************************************************** + * picorv32_axi + ***************************************************************/ + +module picorv32_axi #( + parameter [ 0:0] ENABLE_COUNTERS = 1, + parameter [ 0:0] ENABLE_COUNTERS64 = 1, + parameter [ 0:0] ENABLE_REGS_16_31 = 1, + parameter [ 0:0] ENABLE_REGS_DUALPORT = 1, + parameter [ 0:0] TWO_STAGE_SHIFT = 1, + parameter [ 0:0] BARREL_SHIFTER = 0, + parameter [ 0:0] TWO_CYCLE_COMPARE = 0, + parameter [ 0:0] TWO_CYCLE_ALU = 0, + parameter [ 0:0] COMPRESSED_ISA = 0, + parameter [ 0:0] CATCH_MISALIGN = 1, + parameter [ 0:0] CATCH_ILLINSN = 1, + parameter [ 0:0] ENABLE_PCPI = 0, + parameter [ 0:0] ENABLE_MUL = 0, + parameter [ 0:0] ENABLE_FAST_MUL = 0, + parameter [ 0:0] ENABLE_DIV = 0, + parameter [ 0:0] ENABLE_IRQ = 0, + parameter [ 0:0] ENABLE_IRQ_QREGS = 1, + parameter [ 0:0] ENABLE_IRQ_TIMER = 1, + parameter [ 0:0] ENABLE_TRACE = 0, + parameter [ 0:0] REGS_INIT_ZERO = 0, + parameter [31:0] MASKED_IRQ = 32'h 0000_0000, + parameter [31:0] LATCHED_IRQ = 32'h ffff_ffff, + parameter [31:0] PROGADDR_RESET = 32'h 0000_0000, + parameter [31:0] PROGADDR_IRQ = 32'h 0000_0010, + parameter [31:0] STACKADDR = 32'h ffff_ffff +) ( + input clk, resetn, + output trap, + + // AXI4-lite master memory interface + + output mem_axi_awvalid, + input mem_axi_awready, + output [31:0] mem_axi_awaddr, + output [ 2:0] mem_axi_awprot, + + output mem_axi_wvalid, + input mem_axi_wready, + output [31:0] mem_axi_wdata, + output [ 3:0] mem_axi_wstrb, + + input mem_axi_bvalid, + output mem_axi_bready, + + output mem_axi_arvalid, + input mem_axi_arready, + output [31:0] mem_axi_araddr, + output [ 2:0] mem_axi_arprot, + + input mem_axi_rvalid, + output mem_axi_rready, + input [31:0] mem_axi_rdata, + + // Pico Co-Processor Interface (PCPI) + output pcpi_valid, + output [31:0] pcpi_insn, + output [31:0] pcpi_rs1, + output [31:0] pcpi_rs2, + input pcpi_wr, + input [31:0] pcpi_rd, + input pcpi_wait, + input pcpi_ready, + + // IRQ interface + input [31:0] irq, + output [31:0] eoi, + +`ifdef RISCV_FORMAL + output rvfi_valid, + output [63:0] rvfi_order, + output [31:0] rvfi_insn, + output rvfi_trap, + output rvfi_halt, + output rvfi_intr, + output [ 4:0] rvfi_rs1_addr, + output [ 4:0] rvfi_rs2_addr, + output [31:0] rvfi_rs1_rdata, + output [31:0] rvfi_rs2_rdata, + output [ 4:0] rvfi_rd_addr, + output [31:0] rvfi_rd_wdata, + output [31:0] rvfi_pc_rdata, + output [31:0] rvfi_pc_wdata, + output [31:0] rvfi_mem_addr, + output [ 3:0] rvfi_mem_rmask, + output [ 3:0] rvfi_mem_wmask, + output [31:0] rvfi_mem_rdata, + output [31:0] rvfi_mem_wdata, +`endif + + // Trace Interface + output trace_valid, + output [35:0] trace_data +); + wire mem_valid; + wire [31:0] mem_addr; + wire [31:0] mem_wdata; + wire [ 3:0] mem_wstrb; + wire mem_instr; + wire mem_ready; + wire [31:0] mem_rdata; + + picorv32_axi_adapter axi_adapter ( + .clk (clk ), + .resetn (resetn ), + .mem_axi_awvalid(mem_axi_awvalid), + .mem_axi_awready(mem_axi_awready), + .mem_axi_awaddr (mem_axi_awaddr ), + .mem_axi_awprot (mem_axi_awprot ), + .mem_axi_wvalid (mem_axi_wvalid ), + .mem_axi_wready (mem_axi_wready ), + .mem_axi_wdata (mem_axi_wdata ), + .mem_axi_wstrb (mem_axi_wstrb ), + .mem_axi_bvalid (mem_axi_bvalid ), + .mem_axi_bready (mem_axi_bready ), + .mem_axi_arvalid(mem_axi_arvalid), + .mem_axi_arready(mem_axi_arready), + .mem_axi_araddr (mem_axi_araddr ), + .mem_axi_arprot (mem_axi_arprot ), + .mem_axi_rvalid (mem_axi_rvalid ), + .mem_axi_rready (mem_axi_rready ), + .mem_axi_rdata (mem_axi_rdata ), + .mem_valid (mem_valid ), + .mem_instr (mem_instr ), + .mem_ready (mem_ready ), + .mem_addr (mem_addr ), + .mem_wdata (mem_wdata ), + .mem_wstrb (mem_wstrb ), + .mem_rdata (mem_rdata ) + ); + + picorv32 #( + .ENABLE_COUNTERS (ENABLE_COUNTERS ), + .ENABLE_COUNTERS64 (ENABLE_COUNTERS64 ), + .ENABLE_REGS_16_31 (ENABLE_REGS_16_31 ), + .ENABLE_REGS_DUALPORT(ENABLE_REGS_DUALPORT), + .TWO_STAGE_SHIFT (TWO_STAGE_SHIFT ), + .BARREL_SHIFTER (BARREL_SHIFTER ), + .TWO_CYCLE_COMPARE (TWO_CYCLE_COMPARE ), + .TWO_CYCLE_ALU (TWO_CYCLE_ALU ), + .COMPRESSED_ISA (COMPRESSED_ISA ), + .CATCH_MISALIGN (CATCH_MISALIGN ), + .CATCH_ILLINSN (CATCH_ILLINSN ), + .ENABLE_PCPI (ENABLE_PCPI ), + .ENABLE_MUL (ENABLE_MUL ), + .ENABLE_FAST_MUL (ENABLE_FAST_MUL ), + .ENABLE_DIV (ENABLE_DIV ), + .ENABLE_IRQ (ENABLE_IRQ ), + .ENABLE_IRQ_QREGS (ENABLE_IRQ_QREGS ), + .ENABLE_IRQ_TIMER (ENABLE_IRQ_TIMER ), + .ENABLE_TRACE (ENABLE_TRACE ), + .REGS_INIT_ZERO (REGS_INIT_ZERO ), + .MASKED_IRQ (MASKED_IRQ ), + .LATCHED_IRQ (LATCHED_IRQ ), + .PROGADDR_RESET (PROGADDR_RESET ), + .PROGADDR_IRQ (PROGADDR_IRQ ), + .STACKADDR (STACKADDR ) + ) picorv32_core ( + .clk (clk ), + .resetn (resetn), + .trap (trap ), + + .mem_valid(mem_valid), + .mem_addr (mem_addr ), + .mem_wdata(mem_wdata), + .mem_wstrb(mem_wstrb), + .mem_instr(mem_instr), + .mem_ready(mem_ready), + .mem_rdata(mem_rdata), + + .pcpi_valid(pcpi_valid), + .pcpi_insn (pcpi_insn ), + .pcpi_rs1 (pcpi_rs1 ), + .pcpi_rs2 (pcpi_rs2 ), + .pcpi_wr (pcpi_wr ), + .pcpi_rd (pcpi_rd ), + .pcpi_wait (pcpi_wait ), + .pcpi_ready(pcpi_ready), + + .irq(irq), + .eoi(eoi), + +`ifdef RISCV_FORMAL + .rvfi_valid (rvfi_valid ), + .rvfi_order (rvfi_order ), + .rvfi_insn (rvfi_insn ), + .rvfi_trap (rvfi_trap ), + .rvfi_halt (rvfi_halt ), + .rvfi_intr (rvfi_intr ), + .rvfi_rs1_addr (rvfi_rs1_addr ), + .rvfi_rs2_addr (rvfi_rs2_addr ), + .rvfi_rs1_rdata(rvfi_rs1_rdata), + .rvfi_rs2_rdata(rvfi_rs2_rdata), + .rvfi_rd_addr (rvfi_rd_addr ), + .rvfi_rd_wdata (rvfi_rd_wdata ), + .rvfi_pc_rdata (rvfi_pc_rdata ), + .rvfi_pc_wdata (rvfi_pc_wdata ), + .rvfi_mem_addr (rvfi_mem_addr ), + .rvfi_mem_rmask(rvfi_mem_rmask), + .rvfi_mem_wmask(rvfi_mem_wmask), + .rvfi_mem_rdata(rvfi_mem_rdata), + .rvfi_mem_wdata(rvfi_mem_wdata), +`endif + + .trace_valid(trace_valid), + .trace_data (trace_data) + ); +endmodule + + +/*************************************************************** + * picorv32_axi_adapter + ***************************************************************/ + +module picorv32_axi_adapter ( + input clk, resetn, + + // AXI4-lite master memory interface + + output mem_axi_awvalid, + input mem_axi_awready, + output [31:0] mem_axi_awaddr, + output [ 2:0] mem_axi_awprot, + + output mem_axi_wvalid, + input mem_axi_wready, + output [31:0] mem_axi_wdata, + output [ 3:0] mem_axi_wstrb, + + input mem_axi_bvalid, + output mem_axi_bready, + + output mem_axi_arvalid, + input mem_axi_arready, + output [31:0] mem_axi_araddr, + output [ 2:0] mem_axi_arprot, + + input mem_axi_rvalid, + output mem_axi_rready, + input [31:0] mem_axi_rdata, + + // Native PicoRV32 memory interface + + input mem_valid, + input mem_instr, + output mem_ready, + input [31:0] mem_addr, + input [31:0] mem_wdata, + input [ 3:0] mem_wstrb, + output [31:0] mem_rdata +); + reg ack_awvalid; + reg ack_arvalid; + reg ack_wvalid; + reg xfer_done; + + assign mem_axi_awvalid = mem_valid && |mem_wstrb && !ack_awvalid; + assign mem_axi_awaddr = mem_addr; + assign mem_axi_awprot = 0; + + assign mem_axi_arvalid = mem_valid && !mem_wstrb && !ack_arvalid; + assign mem_axi_araddr = mem_addr; + assign mem_axi_arprot = mem_instr ? 3'b100 : 3'b000; + + assign mem_axi_wvalid = mem_valid && |mem_wstrb && !ack_wvalid; + assign mem_axi_wdata = mem_wdata; + assign mem_axi_wstrb = mem_wstrb; + + assign mem_ready = mem_axi_bvalid || mem_axi_rvalid; + assign mem_axi_bready = mem_valid && |mem_wstrb; + assign mem_axi_rready = mem_valid && !mem_wstrb; + assign mem_rdata = mem_axi_rdata; + + always @(posedge clk) begin + if (!resetn) begin + ack_awvalid <= 0; + end else begin + xfer_done <= mem_valid && mem_ready; + if (mem_axi_awready && mem_axi_awvalid) + ack_awvalid <= 1; + if (mem_axi_arready && mem_axi_arvalid) + ack_arvalid <= 1; + if (mem_axi_wready && mem_axi_wvalid) + ack_wvalid <= 1; + if (xfer_done || !mem_valid) begin + ack_awvalid <= 0; + ack_arvalid <= 0; + ack_wvalid <= 0; + end + end + end +endmodule + + +/*************************************************************** + * picorv32_wb + ***************************************************************/ + +module picorv32_wb #( + parameter [ 0:0] ENABLE_COUNTERS = 1, + parameter [ 0:0] ENABLE_COUNTERS64 = 1, + parameter [ 0:0] ENABLE_REGS_16_31 = 1, + parameter [ 0:0] ENABLE_REGS_DUALPORT = 1, + parameter [ 0:0] TWO_STAGE_SHIFT = 1, + parameter [ 0:0] BARREL_SHIFTER = 0, + parameter [ 0:0] TWO_CYCLE_COMPARE = 0, + parameter [ 0:0] TWO_CYCLE_ALU = 0, + parameter [ 0:0] COMPRESSED_ISA = 0, + parameter [ 0:0] CATCH_MISALIGN = 1, + parameter [ 0:0] CATCH_ILLINSN = 1, + parameter [ 0:0] ENABLE_PCPI = 0, + parameter [ 0:0] ENABLE_MUL = 0, + parameter [ 0:0] ENABLE_FAST_MUL = 0, + parameter [ 0:0] ENABLE_DIV = 0, + parameter [ 0:0] ENABLE_IRQ = 0, + parameter [ 0:0] ENABLE_IRQ_QREGS = 1, + parameter [ 0:0] ENABLE_IRQ_TIMER = 1, + parameter [ 0:0] ENABLE_TRACE = 0, + parameter [ 0:0] REGS_INIT_ZERO = 0, + parameter [31:0] MASKED_IRQ = 32'h 0000_0000, + parameter [31:0] LATCHED_IRQ = 32'h ffff_ffff, + parameter [31:0] PROGADDR_RESET = 32'h 0000_0000, + parameter [31:0] PROGADDR_IRQ = 32'h 0000_0010, + parameter [31:0] STACKADDR = 32'h ffff_ffff +) ( + output trap, + + // Wishbone interfaces + input wb_rst_i, + input wb_clk_i, + + output reg [31:0] wbm_adr_o, + output reg [31:0] wbm_dat_o, + input [31:0] wbm_dat_i, + output reg wbm_we_o, + output reg [3:0] wbm_sel_o, + output reg wbm_stb_o, + input wbm_ack_i, + output reg wbm_cyc_o, + + // Pico Co-Processor Interface (PCPI) + output pcpi_valid, + output [31:0] pcpi_insn, + output [31:0] pcpi_rs1, + output [31:0] pcpi_rs2, + input pcpi_wr, + input [31:0] pcpi_rd, + input pcpi_wait, + input pcpi_ready, + + // IRQ interface + input [31:0] irq, + output [31:0] eoi, + +`ifdef RISCV_FORMAL + output rvfi_valid, + output [63:0] rvfi_order, + output [31:0] rvfi_insn, + output rvfi_trap, + output rvfi_halt, + output rvfi_intr, + output [ 4:0] rvfi_rs1_addr, + output [ 4:0] rvfi_rs2_addr, + output [31:0] rvfi_rs1_rdata, + output [31:0] rvfi_rs2_rdata, + output [ 4:0] rvfi_rd_addr, + output [31:0] rvfi_rd_wdata, + output [31:0] rvfi_pc_rdata, + output [31:0] rvfi_pc_wdata, + output [31:0] rvfi_mem_addr, + output [ 3:0] rvfi_mem_rmask, + output [ 3:0] rvfi_mem_wmask, + output [31:0] rvfi_mem_rdata, + output [31:0] rvfi_mem_wdata, +`endif + + // Trace Interface + output trace_valid, + output [35:0] trace_data, + + output mem_instr +); + wire mem_valid; + wire [31:0] mem_addr; + wire [31:0] mem_wdata; + wire [ 3:0] mem_wstrb; + reg mem_ready; + reg [31:0] mem_rdata; + + wire clk; + wire resetn; + + assign clk = wb_clk_i; + assign resetn = ~wb_rst_i; + + picorv32 #( + .ENABLE_COUNTERS (ENABLE_COUNTERS ), + .ENABLE_COUNTERS64 (ENABLE_COUNTERS64 ), + .ENABLE_REGS_16_31 (ENABLE_REGS_16_31 ), + .ENABLE_REGS_DUALPORT(ENABLE_REGS_DUALPORT), + .TWO_STAGE_SHIFT (TWO_STAGE_SHIFT ), + .BARREL_SHIFTER (BARREL_SHIFTER ), + .TWO_CYCLE_COMPARE (TWO_CYCLE_COMPARE ), + .TWO_CYCLE_ALU (TWO_CYCLE_ALU ), + .COMPRESSED_ISA (COMPRESSED_ISA ), + .CATCH_MISALIGN (CATCH_MISALIGN ), + .CATCH_ILLINSN (CATCH_ILLINSN ), + .ENABLE_PCPI (ENABLE_PCPI ), + .ENABLE_MUL (ENABLE_MUL ), + .ENABLE_FAST_MUL (ENABLE_FAST_MUL ), + .ENABLE_DIV (ENABLE_DIV ), + .ENABLE_IRQ (ENABLE_IRQ ), + .ENABLE_IRQ_QREGS (ENABLE_IRQ_QREGS ), + .ENABLE_IRQ_TIMER (ENABLE_IRQ_TIMER ), + .ENABLE_TRACE (ENABLE_TRACE ), + .REGS_INIT_ZERO (REGS_INIT_ZERO ), + .MASKED_IRQ (MASKED_IRQ ), + .LATCHED_IRQ (LATCHED_IRQ ), + .PROGADDR_RESET (PROGADDR_RESET ), + .PROGADDR_IRQ (PROGADDR_IRQ ), + .STACKADDR (STACKADDR ) + ) picorv32_core ( + .clk (clk ), + .resetn (resetn), + .trap (trap ), + + .mem_valid(mem_valid), + .mem_addr (mem_addr ), + .mem_wdata(mem_wdata), + .mem_wstrb(mem_wstrb), + .mem_instr(mem_instr), + .mem_ready(mem_ready), + .mem_rdata(mem_rdata), + + .pcpi_valid(pcpi_valid), + .pcpi_insn (pcpi_insn ), + .pcpi_rs1 (pcpi_rs1 ), + .pcpi_rs2 (pcpi_rs2 ), + .pcpi_wr (pcpi_wr ), + .pcpi_rd (pcpi_rd ), + .pcpi_wait (pcpi_wait ), + .pcpi_ready(pcpi_ready), + + .irq(irq), + .eoi(eoi), + +`ifdef RISCV_FORMAL + .rvfi_valid (rvfi_valid ), + .rvfi_order (rvfi_order ), + .rvfi_insn (rvfi_insn ), + .rvfi_trap (rvfi_trap ), + .rvfi_halt (rvfi_halt ), + .rvfi_intr (rvfi_intr ), + .rvfi_rs1_addr (rvfi_rs1_addr ), + .rvfi_rs2_addr (rvfi_rs2_addr ), + .rvfi_rs1_rdata(rvfi_rs1_rdata), + .rvfi_rs2_rdata(rvfi_rs2_rdata), + .rvfi_rd_addr (rvfi_rd_addr ), + .rvfi_rd_wdata (rvfi_rd_wdata ), + .rvfi_pc_rdata (rvfi_pc_rdata ), + .rvfi_pc_wdata (rvfi_pc_wdata ), + .rvfi_mem_addr (rvfi_mem_addr ), + .rvfi_mem_rmask(rvfi_mem_rmask), + .rvfi_mem_wmask(rvfi_mem_wmask), + .rvfi_mem_rdata(rvfi_mem_rdata), + .rvfi_mem_wdata(rvfi_mem_wdata), +`endif + + .trace_valid(trace_valid), + .trace_data (trace_data) + ); + + localparam IDLE = 2'b00; + localparam WBSTART = 2'b01; + localparam WBEND = 2'b10; + + reg [1:0] state; + + wire we; + assign we = (mem_wstrb[0] | mem_wstrb[1] | mem_wstrb[2] | mem_wstrb[3]); + + always @(posedge wb_clk_i) begin + if (wb_rst_i) begin + wbm_adr_o <= 0; + wbm_dat_o <= 0; + wbm_we_o <= 0; + wbm_sel_o <= 0; + wbm_stb_o <= 0; + wbm_cyc_o <= 0; + state <= IDLE; + end else begin + case (state) + IDLE: begin + if (mem_valid) begin + wbm_adr_o <= mem_addr; + wbm_dat_o <= mem_wdata; + wbm_we_o <= we; + wbm_sel_o <= mem_wstrb; + + wbm_stb_o <= 1'b1; + wbm_cyc_o <= 1'b1; + state <= WBSTART; + end else begin + mem_ready <= 1'b0; + + wbm_stb_o <= 1'b0; + wbm_cyc_o <= 1'b0; + wbm_we_o <= 1'b0; + end + end + WBSTART:begin + if (wbm_ack_i) begin + mem_rdata <= wbm_dat_i; + mem_ready <= 1'b1; + + state <= WBEND; + + wbm_stb_o <= 1'b0; + wbm_cyc_o <= 1'b0; + wbm_we_o <= 1'b0; + end + end + WBEND: begin + mem_ready <= 1'b0; + + state <= IDLE; + end + default: + state <= IDLE; + endcase + end + end +endmodule diff --git a/ice40/benchmark/picosoc.v b/ice40/benchmark/picosoc.v new file mode 100644 index 00000000..6298a8ef --- /dev/null +++ b/ice40/benchmark/picosoc.v @@ -0,0 +1,241 @@ +/* + * PicoSoC - A simple example SoC using PicoRV32 + * + * Copyright (C) 2017 Clifford Wolf <clifford@clifford.at> + * + * 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 PICORV32_REGS +`ifdef PICORV32_V +`error "picosoc.v must be read before picorv32.v!" +`endif + +`define PICORV32_REGS picosoc_regs +`endif + +module picosoc ( + input clk, + input resetn, + + output iomem_valid, + input iomem_ready, + output [ 3:0] iomem_wstrb, + output [31:0] iomem_addr, + output [31:0] iomem_wdata, + input [31:0] iomem_rdata, + + input irq_5, + input irq_6, + input irq_7, + + output ser_tx, + input ser_rx, + + output flash_csb, + output flash_clk, + + output flash_io0_oe, + output flash_io1_oe, + output flash_io2_oe, + output flash_io3_oe, + + output flash_io0_do, + output flash_io1_do, + output flash_io2_do, + output flash_io3_do, + + input flash_io0_di, + input flash_io1_di, + input flash_io2_di, + input flash_io3_di +); + parameter integer MEM_WORDS = 256; + parameter [31:0] STACKADDR = (4*MEM_WORDS); // end of memory + parameter [31:0] PROGADDR_RESET = 32'h 0010_0000; // 1 MB into flash + + reg [31:0] irq; + wire irq_stall = 0; + wire irq_uart = 0; + + always @* begin + irq = 0; + irq[3] = irq_stall; + irq[4] = irq_uart; + irq[5] = irq_5; + irq[6] = irq_6; + irq[7] = irq_7; + end + + wire mem_valid; + wire mem_instr; + wire mem_ready; + wire [31:0] mem_addr; + wire [31:0] mem_wdata; + wire [3:0] mem_wstrb; + wire [31:0] mem_rdata; + + wire spimem_ready; + wire [31:0] spimem_rdata; + + reg ram_ready; + wire [31:0] ram_rdata; + + assign iomem_valid = mem_valid && (mem_addr[31:24] > 8'h 01); + assign iomem_wstrb = mem_wstrb; + assign iomem_addr = mem_addr; + assign iomem_wdata = mem_wdata; + + wire spimemio_cfgreg_sel = mem_valid && (mem_addr == 32'h 0200_0000); + wire [31:0] spimemio_cfgreg_do; + + wire simpleuart_reg_div_sel = mem_valid && (mem_addr == 32'h 0200_0004); + wire [31:0] simpleuart_reg_div_do; + + wire simpleuart_reg_dat_sel = mem_valid && (mem_addr == 32'h 0200_0008); + wire [31:0] simpleuart_reg_dat_do; + wire simpleuart_reg_dat_wait; + + assign mem_ready = (iomem_valid && iomem_ready) || spimem_ready || ram_ready || spimemio_cfgreg_sel || + simpleuart_reg_div_sel || (simpleuart_reg_dat_sel && !simpleuart_reg_dat_wait); + + assign mem_rdata = (iomem_valid && iomem_ready) ? iomem_rdata : spimem_ready ? spimem_rdata : ram_ready ? ram_rdata : + spimemio_cfgreg_sel ? spimemio_cfgreg_do : simpleuart_reg_div_sel ? simpleuart_reg_div_do : + simpleuart_reg_dat_sel ? simpleuart_reg_dat_do : 32'h 0000_0000; + + picorv32 #( + .STACKADDR(STACKADDR), + .PROGADDR_RESET(PROGADDR_RESET), + .PROGADDR_IRQ(32'h 0000_0000), + .BARREL_SHIFTER(1), + .COMPRESSED_ISA(1), + .ENABLE_MUL(1), + .ENABLE_DIV(1), + .ENABLE_IRQ(1), + .ENABLE_IRQ_QREGS(0) + ) cpu ( + .clk (clk ), + .resetn (resetn ), + .mem_valid (mem_valid ), + .mem_instr (mem_instr ), + .mem_ready (mem_ready ), + .mem_addr (mem_addr ), + .mem_wdata (mem_wdata ), + .mem_wstrb (mem_wstrb ), + .mem_rdata (mem_rdata ), + .irq (irq ) + ); + + spimemio spimemio ( + .clk (clk), + .resetn (resetn), + .valid (mem_valid && mem_addr >= 4*MEM_WORDS && mem_addr < 32'h 0200_0000), + .ready (spimem_ready), + .addr (mem_addr[23:0]), + .rdata (spimem_rdata), + + .flash_csb (flash_csb ), + .flash_clk (flash_clk ), + + .flash_io0_oe (flash_io0_oe), + .flash_io1_oe (flash_io1_oe), + .flash_io2_oe (flash_io2_oe), + .flash_io3_oe (flash_io3_oe), + + .flash_io0_do (flash_io0_do), + .flash_io1_do (flash_io1_do), + .flash_io2_do (flash_io2_do), + .flash_io3_do (flash_io3_do), + + .flash_io0_di (flash_io0_di), + .flash_io1_di (flash_io1_di), + .flash_io2_di (flash_io2_di), + .flash_io3_di (flash_io3_di), + + .cfgreg_we(spimemio_cfgreg_sel ? mem_wstrb : 4'b 0000), + .cfgreg_di(mem_wdata), + .cfgreg_do(spimemio_cfgreg_do) + ); + + simpleuart simpleuart ( + .clk (clk ), + .resetn (resetn ), + + .ser_tx (ser_tx ), + .ser_rx (ser_rx ), + + .reg_div_we (simpleuart_reg_div_sel ? mem_wstrb : 4'b 0000), + .reg_div_di (mem_wdata), + .reg_div_do (simpleuart_reg_div_do), + + .reg_dat_we (simpleuart_reg_dat_sel ? mem_wstrb[0] : 1'b 0), + .reg_dat_re (simpleuart_reg_dat_sel && !mem_wstrb), + .reg_dat_di (mem_wdata), + .reg_dat_do (simpleuart_reg_dat_do), + .reg_dat_wait(simpleuart_reg_dat_wait) + ); + + always @(posedge clk) + ram_ready <= mem_valid && !mem_ready && mem_addr < 4*MEM_WORDS; + + picosoc_mem #(.WORDS(MEM_WORDS)) memory ( + .clk(clk), + .wen((mem_valid && !mem_ready && mem_addr < 4*MEM_WORDS) ? mem_wstrb : 4'b0), + .addr(mem_addr[23:2]), + .wdata(mem_wdata), + .rdata(ram_rdata) + ); +endmodule + +// Implementation note: +// Replace the following two modules with wrappers for your SRAM cells. + +module picosoc_regs ( + input clk, wen, + input [5:0] waddr, + input [5:0] raddr1, + input [5:0] raddr2, + input [31:0] wdata, + output [31:0] rdata1, + output [31:0] rdata2 +); + reg [31:0] regs [0:31]; + + always @(posedge clk) + if (wen) regs[waddr[4:0]] <= wdata; + + assign rdata1 = regs[raddr1[4:0]]; + assign rdata2 = regs[raddr2[4:0]]; +endmodule + +module picosoc_mem #( + parameter integer WORDS = 256 +) ( + input clk, + input [3:0] wen, + input [21:0] addr, + input [31:0] wdata, + output reg [31:0] rdata +); + reg [31:0] mem [0:WORDS-1]; + + always @(posedge clk) begin + rdata <= mem[addr]; + if (wen[0]) mem[addr][ 7: 0] <= wdata[ 7: 0]; + if (wen[1]) mem[addr][15: 8] <= wdata[15: 8]; + if (wen[2]) mem[addr][23:16] <= wdata[23:16]; + if (wen[3]) mem[addr][31:24] <= wdata[31:24]; + end +endmodule + diff --git a/ice40/benchmark/report.ipynb b/ice40/benchmark/report.ipynb new file mode 100644 index 00000000..3232f38c --- /dev/null +++ b/ice40/benchmark/report.ipynb @@ -0,0 +1,69 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "import subprocess\n", + "\n", + "gitrev = subprocess.getoutput(\"git rev-parse --short HEAD\")\n", + "\n", + "data_a = 1 + np.zeros(10)\n", + "data_n = 1 + np.zeros(10)\n", + "\n", + "for i in range(10):\n", + " try:\n", + " with open(\"report_a%d.txt\" % i, \"r\") as f:\n", + " for line in f:\n", + " if line.startswith(\"Total path delay:\"):\n", + " data_a[i] = float(line.split()[3])\n", + " except:\n", + " pass\n", + " try:\n", + " with open(\"report_n%d.txt\" % i, \"r\") as f:\n", + " for line in f:\n", + " if line.startswith(\"Total path delay:\"):\n", + " data_n[i] = float(line.split()[3])\n", + " except:\n", + " pass\n", + "\n", + "plt.figure(figsize=(9,3))\n", + "plt.title(\"nextpnr -- ice40/benchmark/ -- %s\" % gitrev)\n", + "plt.bar(np.arange(10), data_a, color='blue')\n", + "plt.bar(15+np.arange(10), data_n, color='red')\n", + "plt.ylabel('Longest path (ns)')\n", + "plt.xticks([5, 20], [\"arachne-pnr\", \"nextpnr\"])\n", + "plt.xlim(-2, 27)\n", + "plt.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.5.2" + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} diff --git a/ice40/benchmark/simpleuart.v b/ice40/benchmark/simpleuart.v new file mode 100644 index 00000000..50808cb3 --- /dev/null +++ b/ice40/benchmark/simpleuart.v @@ -0,0 +1,137 @@ +/* + * PicoSoC - A simple example SoC using PicoRV32 + * + * Copyright (C) 2017 Clifford Wolf <clifford@clifford.at> + * + * 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. + * + */ + +module simpleuart ( + input clk, + input resetn, + + output ser_tx, + input ser_rx, + + input [3:0] reg_div_we, + input [31:0] reg_div_di, + output [31:0] reg_div_do, + + input reg_dat_we, + input reg_dat_re, + input [31:0] reg_dat_di, + output [31:0] reg_dat_do, + output reg_dat_wait +); + reg [31:0] cfg_divider; + + reg [3:0] recv_state; + reg [31:0] recv_divcnt; + reg [7:0] recv_pattern; + reg [7:0] recv_buf_data; + reg recv_buf_valid; + + reg [9:0] send_pattern; + reg [3:0] send_bitcnt; + reg [31:0] send_divcnt; + reg send_dummy; + + assign reg_div_do = cfg_divider; + + assign reg_dat_wait = reg_dat_we && (send_bitcnt || send_dummy); + assign reg_dat_do = recv_buf_valid ? recv_buf_data : ~0; + + always @(posedge clk) begin + if (!resetn) begin + cfg_divider <= 1; + end else begin + if (reg_div_we[0]) cfg_divider[ 7: 0] <= reg_div_di[ 7: 0]; + if (reg_div_we[1]) cfg_divider[15: 8] <= reg_div_di[15: 8]; + if (reg_div_we[2]) cfg_divider[23:16] <= reg_div_di[23:16]; + if (reg_div_we[3]) cfg_divider[31:24] <= reg_div_di[31:24]; + end + end + + always @(posedge clk) begin + if (!resetn) begin + recv_state <= 0; + recv_divcnt <= 0; + recv_pattern <= 0; + recv_buf_data <= 0; + recv_buf_valid <= 0; + end else begin + recv_divcnt <= recv_divcnt + 1; + if (reg_dat_re) + recv_buf_valid <= 0; + case (recv_state) + 0: begin + if (!ser_rx) + recv_state <= 1; + recv_divcnt <= 0; + end + 1: begin + if (2*recv_divcnt > cfg_divider) begin + recv_state <= 2; + recv_divcnt <= 0; + end + end + 10: begin + if (recv_divcnt > cfg_divider) begin + recv_buf_data <= recv_pattern; + recv_buf_valid <= 1; + recv_state <= 0; + end + end + default: begin + if (recv_divcnt > cfg_divider) begin + recv_pattern <= {ser_rx, recv_pattern[7:1]}; + recv_state <= recv_state + 1; + recv_divcnt <= 0; + end + end + endcase + end + end + + assign ser_tx = send_pattern[0]; + + always @(posedge clk) begin + if (reg_div_we) + send_dummy <= 1; + send_divcnt <= send_divcnt + 1; + if (!resetn) begin + send_pattern <= ~0; + send_bitcnt <= 0; + send_divcnt <= 0; + send_dummy <= 1; + end else begin + if (send_dummy && !send_bitcnt) begin + send_pattern <= ~0; + send_bitcnt <= 15; + send_divcnt <= 0; + send_dummy <= 0; + end else + if (reg_dat_we && !send_bitcnt) begin + send_pattern <= {1'b1, reg_dat_di[7:0], 1'b0}; + send_bitcnt <= 10; + send_divcnt <= 0; + end else + if (send_divcnt > cfg_divider && send_bitcnt) begin + send_pattern <= {1'b1, send_pattern[9:1]}; + send_bitcnt <= send_bitcnt - 1; + send_divcnt <= 0; + end + end + end +endmodule diff --git a/ice40/benchmark/spimemio.v b/ice40/benchmark/spimemio.v new file mode 100644 index 00000000..0bbf5326 --- /dev/null +++ b/ice40/benchmark/spimemio.v @@ -0,0 +1,579 @@ +/* + * PicoSoC - A simple example SoC using PicoRV32 + * + * Copyright (C) 2017 Clifford Wolf <clifford@clifford.at> + * + * 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. + * + */ + +module spimemio ( + input clk, resetn, + + input valid, + output ready, + input [23:0] addr, + output reg [31:0] rdata, + + output flash_csb, + output flash_clk, + + output flash_io0_oe, + output flash_io1_oe, + output flash_io2_oe, + output flash_io3_oe, + + output flash_io0_do, + output flash_io1_do, + output flash_io2_do, + output flash_io3_do, + + input flash_io0_di, + input flash_io1_di, + input flash_io2_di, + input flash_io3_di, + + input [3:0] cfgreg_we, + input [31:0] cfgreg_di, + output [31:0] cfgreg_do +); + reg xfer_resetn; + reg din_valid; + wire din_ready; + reg [7:0] din_data; + reg [3:0] din_tag; + reg din_cont; + reg din_qspi; + reg din_ddr; + reg din_rd; + + wire dout_valid; + wire [7:0] dout_data; + wire [3:0] dout_tag; + + reg [23:0] buffer; + + reg [23:0] rd_addr; + reg rd_valid; + reg rd_wait; + reg rd_inc; + + assign ready = valid && (addr == rd_addr) && rd_valid; + wire jump = valid && !ready && (addr != rd_addr+4) && rd_valid; + + reg softreset; + + reg config_en; // cfgreg[31] + reg config_ddr; // cfgreg[22] + reg config_qspi; // cfgreg[21] + reg config_cont; // cfgreg[20] + reg [3:0] config_dummy; // cfgreg[19:16] + reg [3:0] config_oe; // cfgreg[11:8] + reg config_csb; // cfgreg[5] + reg config_clk; // cfgref[4] + reg [3:0] config_do; // cfgreg[3:0] + + assign cfgreg_do[31] = config_en; + assign cfgreg_do[30:23] = 0; + assign cfgreg_do[22] = config_ddr; + assign cfgreg_do[21] = config_qspi; + assign cfgreg_do[20] = config_cont; + assign cfgreg_do[19:16] = config_dummy; + assign cfgreg_do[15:12] = 0; + assign cfgreg_do[11:8] = {flash_io3_oe, flash_io2_oe, flash_io1_oe, flash_io0_oe}; + assign cfgreg_do[7:6] = 0; + assign cfgreg_do[5] = flash_csb; + assign cfgreg_do[4] = flash_clk; + assign cfgreg_do[3:0] = {flash_io3_di, flash_io2_di, flash_io1_di, flash_io0_di}; + + always @(posedge clk) begin + softreset <= !config_en || cfgreg_we; + if (!resetn) begin + softreset <= 1; + config_en <= 1; + config_csb <= 0; + config_clk <= 0; + config_oe <= 0; + config_do <= 0; + config_ddr <= 0; + config_qspi <= 0; + config_cont <= 0; + config_dummy <= 8; + end else begin + if (cfgreg_we[0]) begin + config_csb <= cfgreg_di[5]; + config_clk <= cfgreg_di[4]; + config_do <= cfgreg_di[3:0]; + end + if (cfgreg_we[1]) begin + config_oe <= cfgreg_di[11:8]; + end + if (cfgreg_we[2]) begin + config_ddr <= cfgreg_di[22]; + config_qspi <= cfgreg_di[21]; + config_cont <= cfgreg_di[20]; + config_dummy <= cfgreg_di[19:16]; + end + if (cfgreg_we[3]) begin + config_en <= cfgreg_di[31]; + end + end + end + + wire xfer_csb; + wire xfer_clk; + + wire xfer_io0_oe; + wire xfer_io1_oe; + wire xfer_io2_oe; + wire xfer_io3_oe; + + wire xfer_io0_do; + wire xfer_io1_do; + wire xfer_io2_do; + wire xfer_io3_do; + + reg xfer_io0_90; + reg xfer_io1_90; + reg xfer_io2_90; + reg xfer_io3_90; + + always @(negedge clk) begin + xfer_io0_90 <= xfer_io0_do; + xfer_io1_90 <= xfer_io1_do; + xfer_io2_90 <= xfer_io2_do; + xfer_io3_90 <= xfer_io3_do; + end + + assign flash_csb = config_en ? xfer_csb : config_csb; + assign flash_clk = config_en ? xfer_clk : config_clk; + + assign flash_io0_oe = config_en ? xfer_io0_oe : config_oe[0]; + assign flash_io1_oe = config_en ? xfer_io1_oe : config_oe[1]; + assign flash_io2_oe = config_en ? xfer_io2_oe : config_oe[2]; + assign flash_io3_oe = config_en ? xfer_io3_oe : config_oe[3]; + + assign flash_io0_do = config_en ? (config_ddr ? xfer_io0_90 : xfer_io0_do) : config_do[0]; + assign flash_io1_do = config_en ? (config_ddr ? xfer_io1_90 : xfer_io1_do) : config_do[1]; + assign flash_io2_do = config_en ? (config_ddr ? xfer_io2_90 : xfer_io2_do) : config_do[2]; + assign flash_io3_do = config_en ? (config_ddr ? xfer_io3_90 : xfer_io3_do) : config_do[3]; + + wire xfer_dspi = din_ddr && !din_qspi; + wire xfer_ddr = din_ddr && din_qspi; + + spimemio_xfer xfer ( + .clk (clk ), + .resetn (xfer_resetn ), + .din_valid (din_valid ), + .din_ready (din_ready ), + .din_data (din_data ), + .din_tag (din_tag ), + .din_cont (din_cont ), + .din_dspi (xfer_dspi ), + .din_qspi (din_qspi ), + .din_ddr (xfer_ddr ), + .din_rd (din_rd ), + .dout_valid (dout_valid ), + .dout_data (dout_data ), + .dout_tag (dout_tag ), + .flash_csb (xfer_csb ), + .flash_clk (xfer_clk ), + .flash_io0_oe (xfer_io0_oe ), + .flash_io1_oe (xfer_io1_oe ), + .flash_io2_oe (xfer_io2_oe ), + .flash_io3_oe (xfer_io3_oe ), + .flash_io0_do (xfer_io0_do ), + .flash_io1_do (xfer_io1_do ), + .flash_io2_do (xfer_io2_do ), + .flash_io3_do (xfer_io3_do ), + .flash_io0_di (flash_io0_di), + .flash_io1_di (flash_io1_di), + .flash_io2_di (flash_io2_di), + .flash_io3_di (flash_io3_di) + ); + + reg [3:0] state; + + always @(posedge clk) begin + xfer_resetn <= 1; + din_valid <= 0; + + if (!resetn || softreset) begin + state <= 0; + xfer_resetn <= 0; + rd_valid <= 0; + din_tag <= 0; + din_cont <= 0; + din_qspi <= 0; + din_ddr <= 0; + din_rd <= 0; + end else begin + if (dout_valid && dout_tag == 1) buffer[ 7: 0] <= dout_data; + if (dout_valid && dout_tag == 2) buffer[15: 8] <= dout_data; + if (dout_valid && dout_tag == 3) buffer[23:16] <= dout_data; + if (dout_valid && dout_tag == 4) begin + rdata <= {dout_data, buffer}; + rd_addr <= rd_inc ? rd_addr + 4 : addr; + rd_valid <= 1; + rd_wait <= rd_inc; + rd_inc <= 1; + end + + if (valid) + rd_wait <= 0; + + case (state) + 0: begin + din_valid <= 1; + din_data <= 8'h ff; + din_tag <= 0; + if (din_ready) begin + din_valid <= 0; + state <= 1; + end + end + 1: begin + if (dout_valid) begin + xfer_resetn <= 0; + state <= 2; + end + end + 2: begin + din_valid <= 1; + din_data <= 8'h ab; + din_tag <= 0; + if (din_ready) begin + din_valid <= 0; + state <= 3; + end + end + 3: begin + if (dout_valid) begin + xfer_resetn <= 0; + state <= 4; + end + end + 4: begin + rd_inc <= 0; + din_valid <= 1; + din_tag <= 0; + case ({config_ddr, config_qspi}) + 2'b11: din_data <= 8'h ED; + 2'b01: din_data <= 8'h EB; + 2'b10: din_data <= 8'h BB; + 2'b00: din_data <= 8'h 03; + endcase + if (din_ready) begin + din_valid <= 0; + state <= 5; + end + end + 5: begin + if (valid && !ready) begin + din_valid <= 1; + din_tag <= 0; + din_data <= addr[23:16]; + din_qspi <= config_qspi; + din_ddr <= config_ddr; + if (din_ready) begin + din_valid <= 0; + state <= 6; + end + end + end + 6: begin + din_valid <= 1; + din_tag <= 0; + din_data <= addr[15:8]; + if (din_ready) begin + din_valid <= 0; + state <= 7; + end + end + 7: begin + din_valid <= 1; + din_tag <= 0; + din_data <= addr[7:0]; + if (din_ready) begin + din_valid <= 0; + din_data <= 0; + state <= config_qspi || config_ddr ? 8 : 9; + end + end + 8: begin + din_valid <= 1; + din_tag <= 0; + din_data <= config_cont ? 8'h A5 : 8'h FF; + if (din_ready) begin + din_rd <= 1; + din_data <= config_dummy; + din_valid <= 0; + state <= 9; + end + end + 9: begin + din_valid <= 1; + din_tag <= 1; + if (din_ready) begin + din_valid <= 0; + state <= 10; + end + end + 10: begin + din_valid <= 1; + din_data <= 8'h 00; + din_tag <= 2; + if (din_ready) begin + din_valid <= 0; + state <= 11; + end + end + 11: begin + din_valid <= 1; + din_tag <= 3; + if (din_ready) begin + din_valid <= 0; + state <= 12; + end + end + 12: begin + if (!rd_wait || valid) begin + din_valid <= 1; + din_tag <= 4; + if (din_ready) begin + din_valid <= 0; + state <= 9; + end + end + end + endcase + + if (jump) begin + rd_inc <= 0; + rd_valid <= 0; + xfer_resetn <= 0; + if (config_cont) begin + state <= 5; + end else begin + state <= 4; + din_qspi <= 0; + din_ddr <= 0; + end + din_rd <= 0; + end + end + end +endmodule + +module spimemio_xfer ( + input clk, resetn, + + input din_valid, + output din_ready, + input [7:0] din_data, + input [3:0] din_tag, + input din_cont, + input din_dspi, + input din_qspi, + input din_ddr, + input din_rd, + + output dout_valid, + output [7:0] dout_data, + output [3:0] dout_tag, + + output reg flash_csb, + output reg flash_clk, + + output reg flash_io0_oe, + output reg flash_io1_oe, + output reg flash_io2_oe, + output reg flash_io3_oe, + + output reg flash_io0_do, + output reg flash_io1_do, + output reg flash_io2_do, + output reg flash_io3_do, + + input flash_io0_di, + input flash_io1_di, + input flash_io2_di, + input flash_io3_di +); + reg [7:0] obuffer; + reg [7:0] ibuffer; + + reg [3:0] count; + reg [3:0] dummy_count; + + reg xfer_cont; + reg xfer_dspi; + reg xfer_qspi; + reg xfer_ddr; + reg xfer_ddr_q; + reg xfer_rd; + reg [3:0] xfer_tag; + reg [3:0] xfer_tag_q; + + reg [7:0] next_obuffer; + reg [7:0] next_ibuffer; + reg [3:0] next_count; + + reg fetch; + reg next_fetch; + reg last_fetch; + + always @(posedge clk) begin + xfer_ddr_q <= xfer_ddr; + xfer_tag_q <= xfer_tag; + end + + assign din_ready = din_valid && resetn && next_fetch; + + assign dout_valid = (xfer_ddr_q ? fetch && !last_fetch : next_fetch && !fetch) && resetn; + assign dout_data = ibuffer; + assign dout_tag = xfer_tag_q; + + always @* begin + flash_io0_oe = 0; + flash_io1_oe = 0; + flash_io2_oe = 0; + flash_io3_oe = 0; + + flash_io0_do = 0; + flash_io1_do = 0; + flash_io2_do = 0; + flash_io3_do = 0; + + next_obuffer = obuffer; + next_ibuffer = ibuffer; + next_count = count; + next_fetch = 0; + + if (dummy_count == 0) begin + casez ({xfer_ddr, xfer_qspi, xfer_dspi}) + 3'b 000: begin + flash_io0_oe = 1; + flash_io0_do = obuffer[7]; + + if (flash_clk) begin + next_obuffer = {obuffer[6:0], 1'b 0}; + next_count = count - |count; + end else begin + next_ibuffer = {ibuffer[6:0], flash_io1_di}; + end + + next_fetch = (next_count == 0); + end + 3'b 01?: begin + flash_io0_oe = !xfer_rd; + flash_io1_oe = !xfer_rd; + flash_io2_oe = !xfer_rd; + flash_io3_oe = !xfer_rd; + + flash_io0_do = obuffer[4]; + flash_io1_do = obuffer[5]; + flash_io2_do = obuffer[6]; + flash_io3_do = obuffer[7]; + + if (flash_clk) begin + next_obuffer = {obuffer[3:0], 4'b 0000}; + next_count = count - {|count, 2'b00}; + end else begin + next_ibuffer = {ibuffer[3:0], flash_io3_di, flash_io2_di, flash_io1_di, flash_io0_di}; + end + + next_fetch = (next_count == 0); + end + 3'b 11?: begin + flash_io0_oe = !xfer_rd; + flash_io1_oe = !xfer_rd; + flash_io2_oe = !xfer_rd; + flash_io3_oe = !xfer_rd; + + flash_io0_do = obuffer[4]; + flash_io1_do = obuffer[5]; + flash_io2_do = obuffer[6]; + flash_io3_do = obuffer[7]; + + next_obuffer = {obuffer[3:0], 4'b 0000}; + next_ibuffer = {ibuffer[3:0], flash_io3_di, flash_io2_di, flash_io1_di, flash_io0_di}; + next_count = count - {|count, 2'b00}; + + next_fetch = (next_count == 0); + end + 3'b ??1: begin + flash_io0_oe = !xfer_rd; + flash_io1_oe = !xfer_rd; + + flash_io0_do = obuffer[6]; + flash_io1_do = obuffer[7]; + + if (flash_clk) begin + next_obuffer = {obuffer[5:0], 2'b 00}; + next_count = count - {|count, 1'b0}; + end else begin + next_ibuffer = {ibuffer[5:0], flash_io1_di, flash_io0_di}; + end + + next_fetch = (next_count == 0); + end + endcase + end + end + + always @(posedge clk) begin + if (!resetn) begin + fetch <= 1; + last_fetch <= 1; + flash_csb <= 1; + flash_clk <= 0; + count <= 0; + dummy_count <= 0; + xfer_tag <= 0; + xfer_cont <= 0; + xfer_dspi <= 0; + xfer_qspi <= 0; + xfer_ddr <= 0; + xfer_rd <= 0; + end else begin + fetch <= next_fetch; + last_fetch <= xfer_ddr ? fetch : 1; + if (dummy_count) begin + flash_clk <= !flash_clk && !flash_csb; + dummy_count <= dummy_count - flash_clk; + end else + if (count) begin + flash_clk <= !flash_clk && !flash_csb; + obuffer <= next_obuffer; + ibuffer <= next_ibuffer; + count <= next_count; + end + if (din_valid && din_ready) begin + flash_csb <= 0; + flash_clk <= 0; + + count <= 8; + dummy_count <= din_rd ? din_data : 0; + obuffer <= din_data; + + xfer_tag <= din_tag; + xfer_cont <= din_cont; + xfer_dspi <= din_dspi; + xfer_qspi <= din_qspi; + xfer_ddr <= din_ddr; + xfer_rd <= din_rd; + end + end + end +endmodule diff --git a/ice40/bitstream.cc b/ice40/bitstream.cc index 9ac8e857..af5febce 100644 --- a/ice40/bitstream.cc +++ b/ice40/bitstream.cc @@ -3,6 +3,7 @@ *
* Copyright (C) 2018 Clifford Wolf <clifford@symbioticeda.com>
* Copyright (C) 2018 David Shah <david@symbioticeda.com>
+ * Copyright (C) 2018 Serge Bazanski <q3k@symbioticeda.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@@ -74,14 +75,26 @@ void set_config(const TileInfoPOD &ti, std::vector<std::vector<int8_t>> &tile_cf for (int i = 0; i < cfg.num_bits; i++) {
int8_t &cbit = tile_cfg.at(cfg.bits[i].row).at(cfg.bits[i].col);
if (cbit && !value)
- log_error("clearing already set config bit %s", name.c_str());
+ log_error("clearing already set config bit %s\n", name.c_str());
cbit = value;
}
} else {
int8_t &cbit = tile_cfg.at(cfg.bits[index].row).at(cfg.bits[index].col);
cbit = value;
if (cbit && !value)
- log_error("clearing already set config bit %s[%d]", name.c_str(), index);
+ log_error("clearing already set config bit %s[%d]\n", name.c_str(), index);
+ }
+}
+
+// Set an IE_{EN,REN} logical bit in a tile config. Logical means enabled.
+// On {HX,LP}1K devices these bits are active low, so we need to invert them.
+void set_ie_bit_logical(const Context *ctx, const TileInfoPOD &ti, std::vector<std::vector<int8_t>> &tile_cfg,
+ const std::string &name, bool value)
+{
+ if (ctx->args.type == ArchArgs::LP1K || ctx->args.type == ArchArgs::HX1K) {
+ set_config(ti, tile_cfg, name, !value);
+ } else {
+ set_config(ti, tile_cfg, name, value);
}
}
@@ -117,7 +130,7 @@ static const BelConfigPOD &get_ec_config(const ChipInfoPOD *chip, BelId bel) typedef std::vector<std::vector<std::vector<std::vector<int8_t>>>> chipconfig_t;
static void set_ec_cbit(chipconfig_t &config, const Context *ctx, const BelConfigPOD &cell_cbits, std::string name,
- bool value)
+ bool value, std::string prefix)
{
const ChipInfoPOD *chip = ctx->chip_info;
@@ -125,7 +138,7 @@ static void set_ec_cbit(chipconfig_t &config, const Context *ctx, const BelConfi const auto &cbit = cell_cbits.entries[i];
if (cbit.entry_name.get() == name) {
const auto &ti = chip->bits_info->tiles_nonrouting[tile_at(ctx, cbit.x, cbit.y)];
- set_config(ti, config.at(cbit.y).at(cbit.x), std::string("IpConfig.") + cbit.cbit_name.get(), value);
+ set_config(ti, config.at(cbit.y).at(cbit.x), prefix + cbit.cbit_name.get(), value);
return;
}
}
@@ -133,7 +146,7 @@ static void set_ec_cbit(chipconfig_t &config, const Context *ctx, const BelConfi }
void configure_extra_cell(chipconfig_t &config, const Context *ctx, CellInfo *cell,
- const std::vector<std::pair<std::string, int>> ¶ms, bool string_style)
+ const std::vector<std::pair<std::string, int>> ¶ms, bool string_style, std::string prefix)
{
const ChipInfoPOD *chip = ctx->chip_info;
const auto &bc = get_ec_config(chip, cell->bel);
@@ -163,10 +176,10 @@ void configure_extra_cell(chipconfig_t &config, const Context *ctx, CellInfo *ce value.resize(p.second);
if (p.second == 1) {
- set_ec_cbit(config, ctx, bc, p.first, value.at(0));
+ set_ec_cbit(config, ctx, bc, p.first, value.at(0), prefix);
} else {
for (int i = 0; i < p.second; i++) {
- set_ec_cbit(config, ctx, bc, p.first + "_" + std::to_string(i), value.at(i));
+ set_ec_cbit(config, ctx, bc, p.first + "_" + std::to_string(i), value.at(i), prefix);
}
}
}
@@ -258,8 +271,13 @@ void write_asc(const Context *ctx, std::ostream &out) }
}
}
+
+ std::unordered_set<Loc> sb_io_used_by_pll;
+ std::unordered_set<Loc> sb_io_used_by_io;
+
// Set logic cell config
for (auto &cell : ctx->cells) {
+
BelId bel = cell.second.get()->bel;
if (bel == BelId()) {
std::cout << "Found unplaced cell " << cell.first.str(ctx) << " while generating bitstream!" << std::endl;
@@ -304,6 +322,7 @@ void write_asc(const Context *ctx, std::ostream &out) } else if (cell.second->type == ctx->id("SB_IO")) {
const BelInfoPOD &beli = ci.bel_data[bel.index];
int x = beli.x, y = beli.y, z = beli.z;
+ sb_io_used_by_io.insert(Loc(x, y, z));
const TileInfoPOD &ti = bi.tiles_nonrouting[TILE_IO];
unsigned pin_type = get_param_or_def(cell.second.get(), ctx->id("PIN_TYPE"));
bool neg_trigger = get_param_or_def(cell.second.get(), ctx->id("NEG_TRIGGER"));
@@ -405,7 +424,70 @@ void write_asc(const Context *ctx, std::ostream &out) {"MODE_8x8", 1},
{"A_SIGNED", 1},
{"B_SIGNED", 1}};
- configure_extra_cell(config, ctx, cell.second.get(), mac16_params, false);
+ configure_extra_cell(config, ctx, cell.second.get(), mac16_params, false, std::string("IpConfig."));
+ } else if (cell.second->type == ctx->id("ICESTORM_PLL")) {
+ const std::vector<std::pair<std::string, int>> pll_params = {{"DELAY_ADJMODE_FB", 1},
+ {"DELAY_ADJMODE_REL", 1},
+ {"DIVF", 7},
+ {"DIVQ", 3},
+ {"DIVR", 4},
+ {"FDA_FEEDBACK", 4},
+ {"FDA_RELATIVE", 4},
+ {"FEEDBACK_PATH", 3},
+ {"FILTER_RANGE", 3},
+ {"PLLOUT_SELECT_A", 2},
+ {"PLLOUT_SELECT_B", 2},
+ {"PLLTYPE", 3},
+ {"SHIFTREG_DIV_MODE", 1},
+ {"TEST_MODE", 1}};
+ configure_extra_cell(config, ctx, cell.second.get(), pll_params, false, std::string("PLL."));
+
+ // Configure the SB_IOs that the clock outputs are going through.
+ for (auto &port : cell.second->ports) {
+ // If this port is not a PLLOUT port, ignore it.
+ if (port.second.name != ctx->id("PLLOUT_A") && port.second.name != ctx->id("PLLOUT_B"))
+ continue;
+
+ // If the output is not driving any net, ignore it.
+ if (port.second.net == nullptr)
+ continue;
+
+ // Get IO Bel that this PLL port goes through by finding sibling
+ // Bel driving the same wire via PIN_D_IN_0.
+ auto wire = ctx->getBelPinWire(cell.second->bel, ctx->portPinFromId(port.second.name));
+ BelId io_bel;
+ for (auto pin : ctx->getWireBelPins(wire)) {
+ if (pin.pin == PIN_D_IN_0) {
+ io_bel = pin.bel;
+ break;
+ }
+ }
+ NPNR_ASSERT(io_bel.index != -1);
+ auto io_bel_loc = ctx->getBelLocation(io_bel);
+
+ // Check that this SB_IO is either unused or just used as an output.
+ if (sb_io_used_by_io.count(io_bel_loc)) {
+ log_error("SB_IO '%s' already in use, cannot route PLL through\n", ctx->getBelName(bel).c_str(ctx));
+ }
+ sb_io_used_by_pll.insert(io_bel_loc);
+
+ // Get IE/REN config location (cf. http://www.clifford.at/icestorm/io_tile.html)
+ auto ieren = get_ieren(bi, io_bel_loc.x, io_bel_loc.y, io_bel_loc.z);
+ int iex, iey, iez;
+ std::tie(iex, iey, iez) = ieren;
+ NPNR_ASSERT(iez != -1);
+
+ // Write config.
+ const TileInfoPOD &ti = bi.tiles_nonrouting[TILE_IO];
+ // Enable input buffer and disable pull-up resistor in block
+ // (this is used by the PLL).
+ set_ie_bit_logical(ctx, ti, config.at(iey).at(iex), "IoCtrl.IE_" + std::to_string(iez), true);
+ set_ie_bit_logical(ctx, ti, config.at(iey).at(iex), "IoCtrl.REN_" + std::to_string(iez), false);
+ // PINTYPE[0] passes the PLL through to the fabric.
+ set_config(ti, config.at(io_bel_loc.y).at(io_bel_loc.x),
+ "IOB_" + std::to_string(io_bel_loc.z) + ".PINTYPE_0", true);
+ }
+
} else {
NPNR_ASSERT(false);
}
@@ -416,14 +498,16 @@ void write_asc(const Context *ctx, std::ostream &out) const TileInfoPOD &ti = bi.tiles_nonrouting[TILE_IO];
const BelInfoPOD &beli = ci.bel_data[bel.index];
int x = beli.x, y = beli.y, z = beli.z;
+ if (sb_io_used_by_pll.count(Loc(x, y, z))) {
+ continue;
+ }
+
auto ieren = get_ieren(bi, x, y, z);
int iex, iey, iez;
std::tie(iex, iey, iez) = ieren;
if (iez != -1) {
- if (ctx->args.type == ArchArgs::LP1K || ctx->args.type == ArchArgs::HX1K) {
- set_config(ti, config.at(iey).at(iex), "IoCtrl.IE_" + std::to_string(iez), true);
- set_config(ti, config.at(iey).at(iex), "IoCtrl.REN_" + std::to_string(iez), false);
- }
+ set_ie_bit_logical(ctx, ti, config.at(iey).at(iex), "IoCtrl.IE_" + std::to_string(iez), true);
+ set_ie_bit_logical(ctx, ti, config.at(iey).at(iex), "IoCtrl.REN_" + std::to_string(iez), false);
}
} else if (ctx->bel_to_cell[bel.index] == IdString() && ctx->getBelType(bel) == TYPE_ICESTORM_RAM) {
const BelInfoPOD &beli = ci.bel_data[bel.index];
@@ -482,9 +566,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/cells.cc b/ice40/cells.cc index 71a65d44..610bf85e 100644 --- a/ice40/cells.cc +++ b/ice40/cells.cc @@ -3,6 +3,7 @@ * * Copyright (C) 2018 Clifford Wolf <clifford@symbioticeda.com> * Copyright (C) 2018 David Shah <david@symbioticeda.com> + * Copyright (C) 2018 Serge Bazanski <q3k@symbioticeda.com> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -207,6 +208,40 @@ std::unique_ptr<CellInfo> create_ice_cell(Context *ctx, IdString type, std::stri add_port(ctx, new_cell.get(), "ACCUMCO", PORT_OUT); add_port(ctx, new_cell.get(), "SIGNEXTOUT", PORT_OUT); + } else if (type == ctx->id("ICESTORM_PLL")) { + new_cell->params[ctx->id("DELAY_ADJMODE_FB")] = "0"; + new_cell->params[ctx->id("DELAY_ADJMODE_REL")] = "0"; + + new_cell->params[ctx->id("DIVF")] = "0"; + new_cell->params[ctx->id("DIVQ")] = "0"; + new_cell->params[ctx->id("DIVR")] = "0"; + + new_cell->params[ctx->id("FDA_FEEDBACK")] = "0"; + new_cell->params[ctx->id("FDA_RELATIVE")] = "0"; + new_cell->params[ctx->id("FEEDBACK_PATH")] = "0"; + new_cell->params[ctx->id("FILTER_RANGE")] = "0"; + + new_cell->params[ctx->id("PLLOUT_SELECT_A")] = "0"; + new_cell->params[ctx->id("PLLOUT_SELECT_B")] = "0"; + + new_cell->params[ctx->id("PLLTYPE")] = "0"; + new_cell->params[ctx->id("SHIFTREG_DIVMODE")] = "0"; + new_cell->params[ctx->id("TEST_MODE")] = "0"; + + add_port(ctx, new_cell.get(), "BYPASS", PORT_IN); + add_port(ctx, new_cell.get(), "DYNAMICDELAY", PORT_IN); + add_port(ctx, new_cell.get(), "EXTFEEDBACK", PORT_IN); + add_port(ctx, new_cell.get(), "LATCHINPUTVALUE", PORT_IN); + add_port(ctx, new_cell.get(), "REFERENCECLK", PORT_IN); + add_port(ctx, new_cell.get(), "RESETB", PORT_IN); + + add_port(ctx, new_cell.get(), "SCLK", PORT_IN); + add_port(ctx, new_cell.get(), "SDI", PORT_IN); + add_port(ctx, new_cell.get(), "SDI", PORT_OUT); + + add_port(ctx, new_cell.get(), "LOCK", PORT_OUT); + add_port(ctx, new_cell.get(), "PLLOUT_A", PORT_OUT); + add_port(ctx, new_cell.get(), "PLLOUT_B", PORT_OUT); } else { log_error("unable to create iCE40 cell of type %s", type.c_str(ctx)); } @@ -312,6 +347,21 @@ void nxio_to_sb(Context *ctx, CellInfo *nxio, CellInfo *sbio) } } +uint8_t sb_pll40_type(const BaseCtx *ctx, const CellInfo *cell) +{ + if (cell->type == ctx->id("SB_PLL40_PAD")) + return 2; + if (cell->type == ctx->id("SB_PLL40_2_PAD")) + return 4; + if (cell->type == ctx->id("SB_PLL40_2F_PAD")) + return 5; + if (cell->type == ctx->id("SB_PLL40_CORE")) + return 3; + if (cell->type == ctx->id("SB_PLL40_2F_CORE")) + return 7; + NPNR_ASSERT(0); +} + bool is_clock_port(const BaseCtx *ctx, const PortRef &port) { if (port.cell == nullptr) diff --git a/ice40/cells.h b/ice40/cells.h index 2f9c77e8..16135448 100644 --- a/ice40/cells.h +++ b/ice40/cells.h @@ -71,6 +71,21 @@ inline bool is_sb_spram(const BaseCtx *ctx, const CellInfo *cell) { return cell- inline bool is_sb_mac16(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("SB_MAC16"); } +inline bool is_sb_pll40(const BaseCtx *ctx, const CellInfo *cell) +{ + return cell->type == ctx->id("SB_PLL40_PAD") || cell->type == ctx->id("SB_PLL40_2_PAD") || + cell->type == ctx->id("SB_PLL40_2F_PAD") || cell->type == ctx->id("SB_PLL40_CORE") || + cell->type == ctx->id("SB_PLL40_2F_CORE"); +} + +inline bool is_sb_pll40_pad(const BaseCtx *ctx, const CellInfo *cell) +{ + return cell->type == ctx->id("SB_PLL40_PAD") || cell->type == ctx->id("SB_PLL40_2_PAD") || + cell->type == ctx->id("SB_PLL40_2F_PAD"); +} + +uint8_t sb_pll40_type(const BaseCtx *ctx, const CellInfo *cell); + // Convert a SB_LUT primitive to (part of) an ICESTORM_LC, swapping ports // as needed. Set no_dff if a DFF is not being used, so that the output // can be reconnected diff --git a/ice40/chipdb.py b/ice40/chipdb.py index 0e8e3ba7..b6af8fcf 100644 --- a/ice40/chipdb.py +++ b/ice40/chipdb.py @@ -6,17 +6,11 @@ import textwrap import argparse parser = argparse.ArgumentParser(description="convert ICE40 chip database") -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("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" -nodebug = True - dev_name = None dev_width = None dev_height = None @@ -41,8 +35,6 @@ extra_cells = dict() extra_cell_config = dict() packages = list() -wire_uphill_belport = dict() -wire_downhill_belports = dict() wire_belports = dict() wire_names = dict() @@ -184,6 +176,8 @@ def wire_type(name): wt = "LOCAL" elif name in ("WCLK", "WCLKE", "WE", "RCLK", "RCLKE", "RE"): wt = "LOCAL" + elif name in ("PLLOUT_A", "PLLOUT_B"): + wt = "LOCAL" if wt is None: print("No type for wire: %s (%s)" % (longname, name), file=sys.stderr) @@ -451,17 +445,12 @@ for i in range(8): add_wire(0, 0, "padin_%d" % i) def add_bel_input(bel, wire, port): - if wire not in wire_downhill_belports: - wire_downhill_belports[wire] = set() - wire_downhill_belports[wire].add((bel, port)) if wire not in wire_belports: wire_belports[wire] = set() wire_belports[wire].add((bel, port)) bel_wires[bel].append((wire, port, 0)) def add_bel_output(bel, wire, port): - assert wire not in wire_uphill_belport - wire_uphill_belport[wire] = (bel, port) if wire not in wire_belports: wire_belports[wire] = set() wire_belports[wire].add((bel, port)) @@ -591,6 +580,9 @@ def is_ec_output(ec_entry): if "glb_netwk_" in wirename: return True return False +def is_ec_pll_clock_output(ec, ec_entry): + return ec[0] == 'PLL' and ec_entry[0] in ('PLLOUT_A', 'PLLOUT_B') + def add_bel_ec(ec): ectype, x, y, z = ec bel = len(bel_name) @@ -605,6 +597,10 @@ def add_bel_ec(ec): add_bel_output(bel, wire_names[entry[1]], entry[0]) else: add_bel_input(bel, wire_names[entry[1]], entry[0]) + elif is_ec_pll_clock_output(ec, entry): + x, y, z = entry[1] + z = 'io_{}/D_IN_0'.format(z) + add_bel_output(bel, wire_names[(x, y, z)], entry[0]) else: extra_cell_config[bel].append(entry) @@ -662,270 +658,61 @@ for tile_xy, tile_type in sorted(tiles.items()): add_bel_ec(ec) for ec in sorted(extra_cells.keys()): - if ec[1] == 0 and ec[2] == 0: + if ec[1] in (0, dev_width - 1) and ec[2] in (0, dev_height - 1): add_bel_ec(ec) 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)) + if ltype is None: + print("label %s" % (name,)) + else: + print("label %s %s" % (name, ltype)) 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)" + if comment is None: + print("ref %s" % (name,)) + else: + print("ref %s %s" % (name, comment)) 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) + assert "|" not in s + print("str |%s| %s" % (s, comment)) 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 + if comment is None: + print("u8 %d" % (v,)) + else: + print("u8 %d %s" % (v, 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) + if comment is None: + print("u16 %d" % (v,)) else: - assert 0 - if comment is not None: - self.comments[len(self.data)] = comment + print("u16 %d %s" % (v, 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) + if comment is None: + print("u32 %d" % (v,)) 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) - -bba = BinaryBlobAssembler("chipdb_blob_%s" % dev_name, endianness) + print("u32 %d %s" % (v, comment)) + + def pre(self, s): + print("pre %s" % s) + + def post(self, s): + print("post %s" % s) + + def push(self, name): + print("push %s" % name) + + def pop(self): + print("pop") + +bba = BinaryBlobAssembler() +bba.pre('#include "nextpnr.h"') +bba.pre('NEXTPNR_NAMESPACE_BEGIN') +bba.post('NEXTPNR_NAMESPACE_END') +bba.push("chipdb_blob_%s" % dev_name) bba.r("chip_info_%s" % dev_name, "chip_info") index = 0 @@ -1001,15 +788,6 @@ for wire in range(num_wires): num_downhill = 0 list_downhill = None - if wire in wire_downhill_belports: - num_bels_downhill = len(wire_downhill_belports[wire]) - bba.l("wire%d_downbels" % wire, "BelPortPOD") - for belport in sorted(wire_downhill_belports[wire]): - bba.u32(belport[0], "bel_index") - bba.u32(portpins[belport[1]], "port") - else: - num_bels_downhill = 0 - if wire in wire_belports: num_bel_pins = len(wire_belports[wire]) bba.l("wire%d_bels" % wire, "BelPortPOD") @@ -1028,19 +806,9 @@ for wire in range(num_wires): info["num_downhill"] = num_downhill info["list_downhill"] = list_downhill - info["num_bels_downhill"] = num_bels_downhill - info["list_bels_downhill"] = ("wire%d_downbels" % wire) if num_bels_downhill > 0 else None - info["num_bel_pins"] = num_bel_pins info["list_bel_pins"] = ("wire%d_bels" % wire) if num_bel_pins > 0 else None - if wire in wire_uphill_belport: - info["uphill_bel"] = wire_uphill_belport[wire][0] - info["uphill_pin"] = portpins[wire_uphill_belport[wire][1]] - else: - info["uphill_bel"] = -1 - info["uphill_pin"] = 0 - avg_x, avg_y = 0, 0 if wire in wire_xy: for x, y in wire_xy[wire]: @@ -1115,10 +883,6 @@ for wire, info in enumerate(wireinfo): bba.u32(info["num_downhill"], "num_downhill") bba.r(info["list_uphill"], "pips_uphill") bba.r(info["list_downhill"], "pips_downhill") - bba.u32(info["num_bels_downhill"], "num_bels_downhill") - 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(info["num_bel_pins"], "num_bel_pins") bba.r(info["list_bel_pins"], "bel_pins") bba.u32(len(wire_segments[wire]), "num_segments") @@ -1264,23 +1028,4 @@ bba.r("bits_info_%s" % dev_name, "bits_info") bba.r("bel_config_%s" % dev_name if len(extra_cell_config) > 0 else None, "bel_config") bba.r("package_info_%s" % dev_name, "packages_data") -bba.finalize() - -if args.c_file: - print('#include "nextpnr.h"') - print('NEXTPNR_NAMESPACE_BEGIN') - - -if args.binary: - bba.write_binary(sys.stdout) - -if args.c_file: - bba.write_string_c(sys.stdout) - -# bba.write_uint64_c(sys.stdout) -# bba.write_compact_c(sys.stdout, "uint8_t") -# bba.write_verbose_c(sys.stdout, "uint8_t") - -if args.c_file: - print('NEXTPNR_NAMESPACE_END') - +bba.pop() diff --git a/ice40/family.cmake b/ice40/family.cmake index 9af06f82..75061f44 100644 --- a/ice40/family.cmake +++ b/ice40/family.cmake @@ -19,13 +19,18 @@ if (MSVC) set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/ice40/resources/chipdb.rc PROPERTIES LANGUAGE RC) foreach (dev ${devices}) set(DEV_TXT_DB ${ICEBOX_ROOT}/chipdb-${dev}.txt) + set(DEV_CC_BBA_DB ${CMAKE_CURRENT_SOURCE_DIR}/ice40/chipdbs/chipdb-${dev}.bba) 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} -g ${DEV_GFXH} ${DEV_TXT_DB} > ${DEV_CC_DB} + add_custom_command(OUTPUT ${DEV_CC_BBA_DB} + COMMAND ${PYTHON_EXECUTABLE} ${DB_PY} -p ${DEV_PORTS_INC} -g ${DEV_GFXH} ${DEV_TXT_DB} > ${DEV_CC_BBA_DB} DEPENDS ${DEV_TXT_DB} ${DB_PY} ) + add_custom_command(OUTPUT ${DEV_CC_DB} + COMMAND bbasm ${DEV_CC_BBA_DB} ${DEV_CC_DB} + DEPENDS bbasm ${DEV_CC_BBA_DB} + ) target_sources(ice40_chipdb PRIVATE ${DEV_CC_DB}) set_source_files_properties(${DEV_CC_DB} PROPERTIES HEADER_FILE_ONLY TRUE) foreach (target ${family_targets}) @@ -36,14 +41,20 @@ else() target_compile_options(ice40_chipdb PRIVATE -g0 -O0 -w) foreach (dev ${devices}) set(DEV_TXT_DB ${ICEBOX_ROOT}/chipdb-${dev}.txt) + set(DEV_CC_BBA_DB ${CMAKE_CURRENT_SOURCE_DIR}/ice40/chipdbs/chipdb-${dev}.bba) 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_BBA_DB} + COMMAND ${PYTHON_EXECUTABLE} ${DB_PY} -p ${DEV_PORTS_INC} -g ${DEV_GFXH} ${DEV_TXT_DB} > ${DEV_CC_BBA_DB}.new + COMMAND mv ${DEV_CC_BBA_DB}.new ${DEV_CC_BBA_DB} + DEPENDS ${DEV_TXT_DB} ${DB_PY} + ) add_custom_command(OUTPUT ${DEV_CC_DB} - COMMAND ${PYTHON_EXECUTABLE} ${DB_PY} -c -p ${DEV_PORTS_INC} -g ${DEV_GFXH} ${DEV_TXT_DB} > ${DEV_CC_DB}.new + COMMAND bbasm --c ${DEV_CC_BBA_DB} ${DEV_CC_DB}.new COMMAND mv ${DEV_CC_DB}.new ${DEV_CC_DB} - DEPENDS ${DEV_TXT_DB} ${DB_PY} - ) + DEPENDS bbasm ${DEV_CC_BBA_DB} + ) target_sources(ice40_chipdb PRIVATE ${DEV_CC_DB}) foreach (target ${family_targets}) target_sources(${target} PRIVATE $<TARGET_OBJECTS:ice40_chipdb>) diff --git a/ice40/gfx.cc b/ice40/gfx.cc index f6ed789f..0798862a 100644 --- a/ice40/gfx.cc +++ b/ice40/gfx.cc @@ -24,7 +24,7 @@ NEXTPNR_NAMESPACE_BEGIN void gfxTileWire(std::vector<GraphicElement> &g, int x, int y, GfxTileWireId id, GraphicElement::style_t style) { GraphicElement el; - el.type = GraphicElement::G_LINE; + el.type = GraphicElement::TYPE_LINE; el.style = style; // Horizontal Span-4 Wires @@ -647,7 +647,7 @@ void pipGfx(std::vector<GraphicElement> &g, int x, int y, float x1, float y1, fl float ty = 0.5 * (y1 + y2); GraphicElement el; - el.type = GraphicElement::G_LINE; + el.type = GraphicElement::TYPE_ARROW; el.style = style; if (fabsf(x1 - swx1) < 0.001 && fabsf(x2 - swx1) < 0.001) { @@ -701,6 +701,17 @@ void gfxTilePip(std::vector<GraphicElement> &g, int x, int y, GfxTileWireId src, if (getWireXY_local(src, x1, y1) && getWireXY_local(dst, x2, y2)) pipGfx(g, x, y, x1, y1, x2, y2, local_swbox_x1, local_swbox_y1, local_swbox_x2, local_swbox_y2, style); + + if (src == TILE_WIRE_CARRY_IN && dst == TILE_WIRE_CARRY_IN_MUX) { + GraphicElement el; + el.type = GraphicElement::TYPE_ARROW; + el.style = style; + el.x1 = x + logic_cell_x1 + 0.005 * 3; + el.x2 = el.x1; + el.y1 = y + 0.01; + el.y2 = y + 0.02; + g.push_back(el); + } } NEXTPNR_NAMESPACE_END diff --git a/ice40/gfx.h b/ice40/gfx.h index 8a55407d..7eeaccf1 100644 --- a/ice40/gfx.h +++ b/ice40/gfx.h @@ -464,7 +464,11 @@ enum GfxTileWireId TILE_WIRE_SP12_H_R_23, TILE_WIRE_SP12_H_L_22, - TILE_WIRE_SP12_H_L_23 + TILE_WIRE_SP12_H_L_23, + + TILE_WIRE_PLLIN, + TILE_WIRE_PLLOUT_A, + TILE_WIRE_PLLOUT_B }; void gfxTileWire(std::vector<GraphicElement> &g, int x, int y, GfxTileWireId id, GraphicElement::style_t style); diff --git a/ice40/main.cc b/ice40/main.cc index 4de05d00..865eea9e 100644 --- a/ice40/main.cc +++ b/ice40/main.cc @@ -52,13 +52,13 @@ void svg_dump_decal(const Context *ctx, const DecalXY &decal) const std::string style = "stroke=\"black\" stroke-width=\"0.1\" fill=\"none\""; for (auto &el : ctx->getDecalGraphics(decal.decal)) { - if (el.type == GraphicElement::G_BOX) { + if (el.type == GraphicElement::TYPE_BOX) { std::cout << "<rect x=\"" << (offset + scale * (decal.x + el.x1)) << "\" y=\"" << (offset + scale * (decal.y + el.y1)) << "\" height=\"" << (scale * (el.y2 - el.y1)) << "\" width=\"" << (scale * (el.x2 - el.x1)) << "\" " << style << "/>\n"; } - if (el.type == GraphicElement::G_LINE) { + if (el.type == GraphicElement::TYPE_LINE) { std::cout << "<line x1=\"" << (offset + scale * (decal.x + el.x1)) << "\" y1=\"" << (offset + scale * (decal.y + el.y1)) << "\" x2=\"" << (offset + scale * (decal.x + el.x2)) << "\" y2=\"" << (offset + scale * (decal.y + el.y2)) << "\" " << style << "/>\n"; @@ -144,18 +144,16 @@ int main(int argc, char *argv[]) #endif if (vm.count("help") || argc == 1) { help: - std::cout << boost::filesystem::basename(argv[0]) - << " -- Next Generation Place and Route (git " - "sha1 " GIT_COMMIT_HASH_STR ")\n"; + 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"; + std::cout << boost::filesystem::basename(argv[0]) << " -- Next Generation Place and Route (git " + "sha1 " GIT_COMMIT_HASH_STR ")\n"; return 1; } @@ -365,8 +363,12 @@ int main(int argc, char *argv[]) } } - if (vm.count("freq")) + if (vm.count("freq")) { ctx->target_freq = vm["freq"].as<double>() * 1e6; + ctx->user_freq = true; + } else { + log_warning("Target frequency not specified. Will optimise for max frequency.\n"); + } ctx->timing_driven = true; if (vm.count("no-tmdriv")) diff --git a/ice40/pack.cc b/ice40/pack.cc index 7e2e389c..8182eb70 100644 --- a/ice40/pack.cc +++ b/ice40/pack.cc @@ -3,6 +3,7 @@ * * Copyright (C) 2018 Clifford Wolf <clifford@symbioticeda.com> * Copyright (C) 2018 David Shah <david@symbioticeda.com> + * Copyright (C) 2018 Serge Bazanski <q3k@symbioticeda.com> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -311,6 +312,10 @@ static void set_net_constant(const Context *ctx, NetInfo *orig, NetInfo *constne (user.port != ctx->id("CLK") && ((constval && user.port == ctx->id("CE")) || (!constval && user.port != ctx->id("CE"))))) { uc->ports[user.port].net = nullptr; + } else if (is_ram(ctx, uc) && !constval && user.port != ctx->id("RCLK") && user.port != ctx->id("RCLKN") && + user.port != ctx->id("WCLK") && user.port != ctx->id("WCLKN") && user.port != ctx->id("RCLKE") && + user.port != ctx->id("WCLKE")) { + uc->ports[user.port].net = nullptr; } else { uc->ports[user.port].net = constnet; constnet->users.push_back(user); @@ -536,6 +541,56 @@ static void promote_globals(Context *ctx) } } +// spliceLUT adds a pass-through LUT LC between the given cell's output port +// and either all users or only non_LUT users. +static std::unique_ptr<CellInfo> spliceLUT(Context *ctx, CellInfo *ci, IdString portId, bool onlyNonLUTs) +{ + auto port = ci->ports[portId]; + + NPNR_ASSERT(port.net != nullptr); + + // Create pass-through LUT. + std::unique_ptr<CellInfo> pt = create_ice_cell(ctx, ctx->id("ICESTORM_LC"), + ci->name.str(ctx) + "$nextpnr_" + portId.str(ctx) + "_lut_through"); + pt->params[ctx->id("LUT_INIT")] = "65280"; // output is always I3 + + // Create LUT output net. + std::unique_ptr<NetInfo> out_net = std::unique_ptr<NetInfo>(new NetInfo); + out_net->name = ctx->id(ci->name.str(ctx) + "$nextnr_" + portId.str(ctx) + "_lut_through_net"); + out_net->driver.cell = pt.get(); + out_net->driver.port = ctx->id("O"); + pt->ports.at(ctx->id("O")).net = out_net.get(); + + // New users of the original cell's port + std::vector<PortRef> new_users; + for (const auto &user : port.net->users) { + if (onlyNonLUTs && user.cell->type == ctx->id("ICESTORM_LC")) { + new_users.push_back(user); + continue; + } + // Rewrite pointer into net in user. + user.cell->ports[user.port].net = out_net.get(); + // Add user to net. + PortRef pr; + pr.cell = user.cell; + pr.port = user.port; + out_net->users.push_back(pr); + } + + // Add LUT to new users. + PortRef pr; + pr.cell = pt.get(); + pr.port = ctx->id("I3"); + new_users.push_back(pr); + pt->ports.at(ctx->id("I3")).net = port.net; + + // Replace users of the original net. + port.net->users = new_users; + + ctx->nets[out_net->name] = std::move(out_net); + return pt; +} + // Pack special functions static void pack_special(Context *ctx) { @@ -591,6 +646,184 @@ static void pack_special(Context *ctx) replace_port(ci, ctx->id(pi.name.c_str(ctx)), packed.get(), ctx->id(newname)); } new_cells.push_back(std::move(packed)); + } else if (is_sb_pll40(ctx, ci)) { + bool is_pad = is_sb_pll40_pad(ctx, ci); + bool is_core = !is_pad; + + std::unique_ptr<CellInfo> packed = + create_ice_cell(ctx, ctx->id("ICESTORM_PLL"), ci->name.str(ctx) + "_PLL"); + packed->attrs[ctx->id("TYPE")] = ci->type.str(ctx); + packed_cells.insert(ci->name); + + for (auto attr : ci->attrs) + packed->attrs[attr.first] = attr.second; + for (auto param : ci->params) + packed->params[param.first] = param.second; + + auto feedback_path = packed->params[ctx->id("FEEDBACK_PATH")]; + packed->params[ctx->id("FEEDBACK_PATH")] = + feedback_path == "DELAY" ? "0" : feedback_path == "SIMPLE" + ? "1" + : feedback_path == "PHASE_AND_DELAY" + ? "2" + : feedback_path == "EXTERNAL" ? "6" + : feedback_path; + packed->params[ctx->id("PLLTYPE")] = std::to_string(sb_pll40_type(ctx, ci)); + + NetInfo *pad_packagepin_net = nullptr; + + for (auto port : ci->ports) { + PortInfo &pi = port.second; + std::string newname = pi.name.str(ctx); + size_t bpos = newname.find('['); + if (bpos != std::string::npos) { + newname = newname.substr(0, bpos) + "_" + newname.substr(bpos + 1, (newname.size() - bpos) - 2); + } + if (pi.name == ctx->id("PLLOUTCOREA")) + newname = "PLLOUT_A"; + if (pi.name == ctx->id("PLLOUTCOREB")) + newname = "PLLOUT_B"; + if (pi.name == ctx->id("PLLOUTCORE")) + newname = "PLLOUT_A"; + + if (pi.name == ctx->id("PACKAGEPIN")) { + if (!is_pad) { + log_error(" PLL '%s' has a PACKAGEPIN but is not a PAD PLL", ci->name.c_str(ctx)); + } else { + // We drop this port and instead place the PLL adequately below. + pad_packagepin_net = port.second.net; + NPNR_ASSERT(pad_packagepin_net != nullptr); + continue; + } + } + if (pi.name == ctx->id("REFERENCECLK")) { + if (!is_core) + log_error(" PLL '%s' has a REFERENCECLK but is not a CORE PLL", ci->name.c_str(ctx)); + } + + replace_port(ci, ctx->id(pi.name.c_str(ctx)), packed.get(), ctx->id(newname)); + } + + // If PLL is not constrained already, do that - we need this + // information to then constrain the LOCK LUT. + BelId pll_bel; + bool constrained = false; + if (packed->attrs.find(ctx->id("BEL")) == packed->attrs.end()) { + for (auto bel : ctx->getBels()) { + if (ctx->getBelType(bel) != TYPE_ICESTORM_PLL) + continue; + + // A PAD PLL must have its' PACKAGEPIN on the SB_IO that's shared + // with PLLOUT_A. + if (is_pad) { + auto pll_sb_io_belpin = ctx->getIOBSharingPLLPin(bel, PIN_PLLOUT_A); + NPNR_ASSERT(pad_packagepin_net != nullptr); + auto pll_packagepin_driver = pad_packagepin_net->driver; + NPNR_ASSERT(pll_packagepin_driver.cell != nullptr); + if (pll_packagepin_driver.cell->type != ctx->id("SB_IO")) { + log_error(" PLL '%s' has a PACKAGEPIN driven by " + "an %s, should be directly connected to an input SB_IO\n", + ci->name.c_str(ctx), pll_packagepin_driver.cell->type.c_str(ctx)); + } + + auto packagepin_cell = pll_packagepin_driver.cell; + auto packagepin_bel_name = packagepin_cell->attrs.find(ctx->id("BEL")); + if (packagepin_bel_name == packagepin_cell->attrs.end()) { + log_error(" PLL '%s' PACKAGEPIN SB_IO '%s' is unconstrained\n", ci->name.c_str(ctx), + packagepin_cell->name.c_str(ctx)); + } + auto packagepin_bel = ctx->getBelByName(ctx->id(packagepin_bel_name->second)); + if (pll_sb_io_belpin.bel != packagepin_bel) { + log_error(" PLL '%s' PACKAGEPIN is connected to pin %s, can only be pin %s\n", + ci->name.c_str(ctx), ctx->getBelPackagePin(packagepin_bel).c_str(), + ctx->getBelPackagePin(pll_sb_io_belpin.bel).c_str()); + } + if (pad_packagepin_net->users.size() != 1) { + log_error(" PLL '%s' clock input '%s' can only drive PLL\n", ci->name.c_str(ctx), + pad_packagepin_net->name.c_str(ctx)); + } + // Set an attribute about this PLL's PAD SB_IO. + packed->attrs[ctx->id("BEL_PAD_INPUT")] = packagepin_bel_name->second; + // Remove the connection from the SB_IO to the PLL. + packagepin_cell->ports.erase(pll_packagepin_driver.port); + } + + log_info(" constrained '%s' to %s\n", packed->name.c_str(ctx), ctx->getBelName(bel).c_str(ctx)); + packed->attrs[ctx->id("BEL")] = ctx->getBelName(bel).str(ctx); + pll_bel = bel; + constrained = true; + } + if (!constrained) { + log_error(" could not constrain '%s' to any PLL Bel\n", packed->name.c_str(ctx)); + } + } + + // Delete the original PACKAGEPIN net if needed. + if (pad_packagepin_net != nullptr) { + for (auto user : pad_packagepin_net->users) { + user.cell->ports.erase(user.port); + } + ctx->nets.erase(pad_packagepin_net->name); + pad_packagepin_net = nullptr; + } + + // The LOCK signal on iCE40 PLLs goes through the neigh_op_bnl_1 wire. + // In practice, this means the LOCK signal can only directly reach LUT + // inputs. + // If we have a net connected to LOCK, make sure it only drives LUTs. + auto port = packed->ports[ctx->id("LOCK")]; + if (port.net != nullptr) { + bool found_lut = false; + bool all_luts = true; + unsigned int lut_count = 0; + for (const auto &user : port.net->users) { + NPNR_ASSERT(user.cell != nullptr); + if (user.cell->type == ctx->id("ICESTORM_LC")) { + found_lut = true; + lut_count++; + } else { + all_luts = false; + } + } + + if (found_lut && all_luts) { + // Every user is a LUT, carry on now. + } else if (found_lut && !all_luts && lut_count < 8) { + // Strategy: create a pass-through LUT, move all non-LUT users behind it. + log_info(" LUT strategy for %s: move non-LUT users to new LUT\n", port.name.c_str(ctx)); + auto pt = spliceLUT(ctx, packed.get(), port.name, true); + new_cells.push_back(std::move(pt)); + } else { + // Strategy: create a pass-through LUT, move every user behind it. + log_info(" LUT strategy for %s: move all users to new LUT\n", port.name.c_str(ctx)); + auto pt = spliceLUT(ctx, packed.get(), port.name, false); + new_cells.push_back(std::move(pt)); + } + + // Find wire that will be driven by this port. + const auto pll_out_wire = ctx->getBelPinWire(pll_bel, ctx->portPinFromId(port.name)); + NPNR_ASSERT(pll_out_wire.index != -1); + + // Now, constrain all LUTs on the output of the signal to be at + // the correct Bel relative to the PLL Bel. + int x = ctx->chip_info->wire_data[pll_out_wire.index].x; + int y = ctx->chip_info->wire_data[pll_out_wire.index].y; + int z = 0; + for (const auto &user : port.net->users) { + NPNR_ASSERT(user.cell != nullptr); + NPNR_ASSERT(user.cell->type == ctx->id("ICESTORM_LC")); + + // TODO(q3k): handle when the Bel might be already the + // target of another constraint. + NPNR_ASSERT(z < 8); + auto target_bel = ctx->getBelByLocation(Loc(x, y, z++)); + auto target_bel_name = ctx->getBelName(target_bel).str(ctx); + user.cell->attrs[ctx->id("BEL")] = target_bel_name; + log_info(" constrained '%s' to %s\n", user.cell->name.c_str(ctx), target_bel_name.c_str()); + } + } + + new_cells.push_back(std::move(packed)); } } diff --git a/ice40/picorv32_benchmark.py b/ice40/picorv32_benchmark.py new file mode 100755 index 00000000..a4ec581e --- /dev/null +++ b/ice40/picorv32_benchmark.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +import os, sys, threading +from os import path +import subprocess +import re + +num_runs = 8 + +if not path.exists("picorv32.json"): + subprocess.run(["wget", "https://raw.githubusercontent.com/cliffordwolf/picorv32/master/picorv32.v"], check=True) + subprocess.run(["yosys", "-q", "-p", "synth_ice40 -json picorv32.json -top top", "picorv32.v", "picorv32_top.v"], check=True) + +fmax = {} + +if not path.exists("picorv32_work"): + os.mkdir("picorv32_work") + +threads = [] + +for i in range(num_runs): + def runner(run): + ascfile = "picorv32_work/picorv32_s{}.asc".format(run) + if path.exists(ascfile): + os.remove(ascfile) + result = subprocess.run(["../nextpnr-ice40", "--hx8k", "--seed", str(run), "--json", "picorv32.json", "--asc", ascfile, "--freq", "70"], stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL) + if result.returncode != 0: + print("Run {} failed!".format(run)) + else: + icetime_res = subprocess.check_output(["icetime", "-d", "hx8k", ascfile]) + fmax_m = re.search(r'\(([0-9.]+) MHz\)', icetime_res.decode('utf-8')) + fmax[run] = float(fmax_m.group(1)) + threads.append(threading.Thread(target=runner, args=[i+1])) + +for t in threads: t.start() +for t in threads: t.join() + +fmax_min = min(fmax.values()) +fmax_max = max(fmax.values()) +fmax_avg = sum(fmax.values()) / len(fmax) + +print("{}/{} runs passed".format(len(fmax), num_runs)) +print("icetime: min = {} MHz, avg = {} MHz, max = {} MHz".format(fmax_min, fmax_avg, fmax_max)) diff --git a/ice40/place_legaliser.cc b/ice40/place_legaliser.cc index 2aefb839..9fde179d 100644 --- a/ice40/place_legaliser.cc +++ b/ice40/place_legaliser.cc @@ -75,11 +75,9 @@ static void get_chain_midpoint(const Context *ctx, const CellChain &chain, float for (auto cell : chain.cells) { if (cell->bel == BelId()) continue; - int bel_x, bel_y; - bool bel_gb; - ctx->estimatePosition(cell->bel, bel_x, bel_y, bel_gb); - total_x += bel_x; - total_y += bel_y; + Loc bel_loc = ctx->getBelLocation(cell->bel); + total_x += bel_loc.x; + total_y += bel_loc.y; N++; } NPNR_ASSERT(N > 0); @@ -92,21 +90,20 @@ static int get_cell_evilness(const Context *ctx, const CellInfo *cell) // This returns how "evil" a logic cell is, and thus how likely it is to be ripped up // during logic tile legalisation int score = 0; - if (get_net_or_empty(cell, ctx->id("I0"))) + if (get_net_or_empty(cell, ctx->id_i0)) ++score; - if (get_net_or_empty(cell, ctx->id("I1"))) + if (get_net_or_empty(cell, ctx->id_i1)) ++score; - if (get_net_or_empty(cell, ctx->id("I2"))) + if (get_net_or_empty(cell, ctx->id_i2)) ++score; - if (get_net_or_empty(cell, ctx->id("I3"))) + if (get_net_or_empty(cell, ctx->id_i3)) ++score; - if (bool_or_default(cell->params, ctx->id("DFF_ENABLE"))) { - const NetInfo *cen = get_net_or_empty(cell, ctx->id("CEN")), *sr = get_net_or_empty(cell, ctx->id("SR")); - if (cen) + if (cell->lcInfo.dffEnable) { + if (cell->lcInfo.cen) score += 10; - if (sr) + if (cell->lcInfo.sr) score += 10; - if (bool_or_default(cell->params, ctx->id("NEG_CLK"))) + if (cell->lcInfo.negClk) score += 5; } return score; diff --git a/ice40/portpins.inc b/ice40/portpins.inc index d78625d1..f9dac887 100644 --- a/ice40/portpins.inc +++ b/ice40/portpins.inc @@ -118,6 +118,8 @@ X(DYNAMICDELAY_5) X(DYNAMICDELAY_6) X(DYNAMICDELAY_7) X(LOCK) +X(PLLOUT_A) +X(PLLOUT_B) X(BYPASS) X(RESETB) X(LATCHINPUTVALUE) |