aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMiodrag Milanovic <mmicko@gmail.com>2019-12-28 13:54:06 +0100
committerMiodrag Milanovic <mmicko@gmail.com>2019-12-28 13:54:06 +0100
commit796d6489953927105d3b0ed22308f29676b168fa (patch)
treebc0f470642c0943713c441aa7c3e9e310cb23ccc
parent50f87a6024859d197eefa8de0b0b616b1e03e239 (diff)
parent0d43aff2682d91817ea4a1fb5dff6e169ae9a659 (diff)
downloadnextpnr-796d6489953927105d3b0ed22308f29676b168fa.tar.gz
nextpnr-796d6489953927105d3b0ed22308f29676b168fa.tar.bz2
nextpnr-796d6489953927105d3b0ed22308f29676b168fa.zip
Merge remote-tracking branch 'origin/master' into mmicko/ecp5_gui
-rw-r--r--.cirrus.yml2
-rw-r--r--.gitignore3
-rw-r--r--3rdparty/json11/LICENSE.txt19
-rw-r--r--3rdparty/json11/json11.cpp790
-rw-r--r--3rdparty/json11/json11.hpp232
-rw-r--r--CMakeLists.txt9
-rw-r--r--README.md22
-rw-r--r--bba/bba.cmake2
-rw-r--r--bba/main.cc28
-rw-r--r--common/arch_pybindings_shared.h4
-rw-r--r--common/command.cc31
-rw-r--r--common/design_utils.cc12
-rw-r--r--common/design_utils.h3
-rw-r--r--common/nextpnr.cc84
-rw-r--r--common/nextpnr.h45
-rw-r--r--common/placer_heap.cc100
-rw-r--r--common/pybindings.cc28
-rw-r--r--common/sdf.cc334
-rw-r--r--common/timing.cc3
-rw-r--r--docs/faq.md33
-rw-r--r--docs/generic.md8
-rw-r--r--docs/netlist.md18
-rw-r--r--ecp5/arch.cc12
-rw-r--r--ecp5/arch.h8
-rw-r--r--ecp5/arch_place.cc24
-rw-r--r--ecp5/arch_pybindings.cc2
-rw-r--r--ecp5/archdefs.h6
-rw-r--r--ecp5/bitstream.cc22
-rw-r--r--ecp5/cells.cc48
-rw-r--r--ecp5/constids.inc8
-rw-r--r--ecp5/family.cmake4
-rw-r--r--ecp5/globals.cc4
-rw-r--r--ecp5/pack.cc304
-rw-r--r--ecp5/synth/.gitignore3
-rw-r--r--ecp5/synth/blinky.v77
-rw-r--r--ecp5/synth/blinky.ys2
-rw-r--r--ecp5/synth/ulx3s_empty.config439
-rwxr-xr-xecp5/trellis_import.py8
-rw-r--r--frontend/frontend_base.h731
-rw-r--r--frontend/json_frontend.cc203
-rw-r--r--frontend/json_frontend.h (renamed from json/jsonparse.h)14
-rw-r--r--generic/arch.cc102
-rw-r--r--generic/arch.h5
-rw-r--r--generic/arch_pybindings.cc2
-rw-r--r--generic/cells.cc14
-rw-r--r--generic/examples/.gitignore3
-rw-r--r--generic/examples/bitstream.py2
-rw-r--r--generic/examples/blinky.v13
-rw-r--r--generic/examples/blinky_tb.v38
-rw-r--r--generic/examples/simple.py7
-rwxr-xr-xgeneric/examples/simple.sh3
-rw-r--r--generic/examples/simple_timing.py26
-rwxr-xr-xgeneric/examples/simtest.sh7
-rw-r--r--generic/main.cc6
-rw-r--r--generic/pack.cc17
-rw-r--r--generic/synth/cells_map.v4
-rw-r--r--generic/synth/prims.v28
-rw-r--r--gui/basewindow.cc1
-rw-r--r--gui/ice40/mainwindow.cc1
-rw-r--r--ice40/arch.cc4
-rw-r--r--ice40/arch_pybindings.cc2
-rw-r--r--ice40/archdefs.h4
-rw-r--r--ice40/bitstream.cc3
-rw-r--r--ice40/cells.cc26
-rw-r--r--ice40/family.cmake27
-rw-r--r--ice40/main.cc1
-rw-r--r--ice40/pack.cc21
-rw-r--r--json/jsonparse.cc1028
-rw-r--r--json/jsonwrite.cc56
-rw-r--r--python/interactive.py6
-rw-r--r--python/report_hierarchy.py10
71 files changed, 3391 insertions, 1805 deletions
diff --git a/.cirrus.yml b/.cirrus.yml
index 5cac994e..bdb3c48c 100644
--- a/.cirrus.yml
+++ b/.cirrus.yml
@@ -11,6 +11,6 @@ task:
test_ice40_script: cd build && ./nextpnr-ice40-test
smoketest_ice40_script: export NEXTPNR=$(pwd)/build/nextpnr-ice40 && cd ice40/smoketest/attosoc && ./smoketest.sh
test_ecp5_script: cd build && ./nextpnr-ecp5-test
- smoketest_generic_script: export NEXTPNR=$(pwd)/build/nextpnr-generic && cd generic/examples && ./simple.sh
+ smoketest_generic_script: export NEXTPNR=$(pwd)/build/nextpnr-generic && cd generic/examples && ./simple.sh && ./simtest.sh
regressiontest_ice40_script: make -j $(nproc) -C tests/ice40/regressions NPNR=$(pwd)/build/nextpnr-ice40
regressiontest_ecp5_script: make -j $(nproc) -C tests/ecp5/regressions NPNR=$(pwd)/build/nextpnr-ecp5
diff --git a/.gitignore b/.gitignore
index 9b3ffd02..9796d242 100644
--- a/.gitignore
+++ b/.gitignore
@@ -19,6 +19,9 @@ CMakeCache.txt
.*.swp
a.out
*.json
+*.dot
+*.il
+/generic/examples/blinky.png
build/
*.asc
*.bin
diff --git a/3rdparty/json11/LICENSE.txt b/3rdparty/json11/LICENSE.txt
new file mode 100644
index 00000000..691742e9
--- /dev/null
+++ b/3rdparty/json11/LICENSE.txt
@@ -0,0 +1,19 @@
+Copyright (c) 2013 Dropbox, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/3rdparty/json11/json11.cpp b/3rdparty/json11/json11.cpp
new file mode 100644
index 00000000..88024e92
--- /dev/null
+++ b/3rdparty/json11/json11.cpp
@@ -0,0 +1,790 @@
+/* Copyright (c) 2013 Dropbox, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "json11.hpp"
+#include <cassert>
+#include <cmath>
+#include <cstdlib>
+#include <cstdio>
+#include <limits>
+
+namespace json11 {
+
+static const int max_depth = 200;
+
+using std::string;
+using std::vector;
+using std::map;
+using std::make_shared;
+using std::initializer_list;
+using std::move;
+
+/* Helper for representing null - just a do-nothing struct, plus comparison
+ * operators so the helpers in JsonValue work. We can't use nullptr_t because
+ * it may not be orderable.
+ */
+struct NullStruct {
+ bool operator==(NullStruct) const { return true; }
+ bool operator<(NullStruct) const { return false; }
+};
+
+/* * * * * * * * * * * * * * * * * * * *
+ * Serialization
+ */
+
+static void dump(NullStruct, string &out) {
+ out += "null";
+}
+
+static void dump(double value, string &out) {
+ if (std::isfinite(value)) {
+ char buf[32];
+ snprintf(buf, sizeof buf, "%.17g", value);
+ out += buf;
+ } else {
+ out += "null";
+ }
+}
+
+static void dump(int value, string &out) {
+ char buf[32];
+ snprintf(buf, sizeof buf, "%d", value);
+ out += buf;
+}
+
+static void dump(bool value, string &out) {
+ out += value ? "true" : "false";
+}
+
+static void dump(const string &value, string &out) {
+ out += '"';
+ for (size_t i = 0; i < value.length(); i++) {
+ const char ch = value[i];
+ if (ch == '\\') {
+ out += "\\\\";
+ } else if (ch == '"') {
+ out += "\\\"";
+ } else if (ch == '\b') {
+ out += "\\b";
+ } else if (ch == '\f') {
+ out += "\\f";
+ } else if (ch == '\n') {
+ out += "\\n";
+ } else if (ch == '\r') {
+ out += "\\r";
+ } else if (ch == '\t') {
+ out += "\\t";
+ } else if (static_cast<uint8_t>(ch) <= 0x1f) {
+ char buf[8];
+ snprintf(buf, sizeof buf, "\\u%04x", ch);
+ out += buf;
+ } else if (static_cast<uint8_t>(ch) == 0xe2 && static_cast<uint8_t>(value[i+1]) == 0x80
+ && static_cast<uint8_t>(value[i+2]) == 0xa8) {
+ out += "\\u2028";
+ i += 2;
+ } else if (static_cast<uint8_t>(ch) == 0xe2 && static_cast<uint8_t>(value[i+1]) == 0x80
+ && static_cast<uint8_t>(value[i+2]) == 0xa9) {
+ out += "\\u2029";
+ i += 2;
+ } else {
+ out += ch;
+ }
+ }
+ out += '"';
+}
+
+static void dump(const Json::array &values, string &out) {
+ bool first = true;
+ out += "[";
+ for (const auto &value : values) {
+ if (!first)
+ out += ", ";
+ value.dump(out);
+ first = false;
+ }
+ out += "]";
+}
+
+static void dump(const Json::object &values, string &out) {
+ bool first = true;
+ out += "{";
+ for (const auto &kv : values) {
+ if (!first)
+ out += ", ";
+ dump(kv.first, out);
+ out += ": ";
+ kv.second.dump(out);
+ first = false;
+ }
+ out += "}";
+}
+
+void Json::dump(string &out) const {
+ m_ptr->dump(out);
+}
+
+/* * * * * * * * * * * * * * * * * * * *
+ * Value wrappers
+ */
+
+template <Json::Type tag, typename T>
+class Value : public JsonValue {
+protected:
+
+ // Constructors
+ explicit Value(const T &value) : m_value(value) {}
+ explicit Value(T &&value) : m_value(move(value)) {}
+
+ // Get type tag
+ Json::Type type() const override {
+ return tag;
+ }
+
+ // Comparisons
+ bool equals(const JsonValue * other) const override {
+ return m_value == static_cast<const Value<tag, T> *>(other)->m_value;
+ }
+ bool less(const JsonValue * other) const override {
+ return m_value < static_cast<const Value<tag, T> *>(other)->m_value;
+ }
+
+ const T m_value;
+ void dump(string &out) const override { json11::dump(m_value, out); }
+};
+
+class JsonDouble final : public Value<Json::NUMBER, double> {
+ double number_value() const override { return m_value; }
+ int int_value() const override { return static_cast<int>(m_value); }
+ bool equals(const JsonValue * other) const override { return m_value == other->number_value(); }
+ bool less(const JsonValue * other) const override { return m_value < other->number_value(); }
+public:
+ explicit JsonDouble(double value) : Value(value) {}
+};
+
+class JsonInt final : public Value<Json::NUMBER, int> {
+ double number_value() const override { return m_value; }
+ int int_value() const override { return m_value; }
+ bool equals(const JsonValue * other) const override { return m_value == other->number_value(); }
+ bool less(const JsonValue * other) const override { return m_value < other->number_value(); }
+public:
+ explicit JsonInt(int value) : Value(value) {}
+};
+
+class JsonBoolean final : public Value<Json::BOOL, bool> {
+ bool bool_value() const override { return m_value; }
+public:
+ explicit JsonBoolean(bool value) : Value(value) {}
+};
+
+class JsonString final : public Value<Json::STRING, string> {
+ const string &string_value() const override { return m_value; }
+public:
+ explicit JsonString(const string &value) : Value(value) {}
+ explicit JsonString(string &&value) : Value(move(value)) {}
+};
+
+class JsonArray final : public Value<Json::ARRAY, Json::array> {
+ const Json::array &array_items() const override { return m_value; }
+ const Json & operator[](size_t i) const override;
+public:
+ explicit JsonArray(const Json::array &value) : Value(value) {}
+ explicit JsonArray(Json::array &&value) : Value(move(value)) {}
+};
+
+class JsonObject final : public Value<Json::OBJECT, Json::object> {
+ const Json::object &object_items() const override { return m_value; }
+ const Json & operator[](const string &key) const override;
+public:
+ explicit JsonObject(const Json::object &value) : Value(value) {}
+ explicit JsonObject(Json::object &&value) : Value(move(value)) {}
+};
+
+class JsonNull final : public Value<Json::NUL, NullStruct> {
+public:
+ JsonNull() : Value({}) {}
+};
+
+/* * * * * * * * * * * * * * * * * * * *
+ * Static globals - static-init-safe
+ */
+struct Statics {
+ const std::shared_ptr<JsonValue> null = make_shared<JsonNull>();
+ const std::shared_ptr<JsonValue> t = make_shared<JsonBoolean>(true);
+ const std::shared_ptr<JsonValue> f = make_shared<JsonBoolean>(false);
+ const string empty_string;
+ const vector<Json> empty_vector;
+ const map<string, Json> empty_map;
+ Statics() {}
+};
+
+static const Statics & statics() {
+ static const Statics s {};
+ return s;
+}
+
+static const Json & static_null() {
+ // This has to be separate, not in Statics, because Json() accesses statics().null.
+ static const Json json_null;
+ return json_null;
+}
+
+/* * * * * * * * * * * * * * * * * * * *
+ * Constructors
+ */
+
+Json::Json() noexcept : m_ptr(statics().null) {}
+Json::Json(std::nullptr_t) noexcept : m_ptr(statics().null) {}
+Json::Json(double value) : m_ptr(make_shared<JsonDouble>(value)) {}
+Json::Json(int value) : m_ptr(make_shared<JsonInt>(value)) {}
+Json::Json(bool value) : m_ptr(value ? statics().t : statics().f) {}
+Json::Json(const string &value) : m_ptr(make_shared<JsonString>(value)) {}
+Json::Json(string &&value) : m_ptr(make_shared<JsonString>(move(value))) {}
+Json::Json(const char * value) : m_ptr(make_shared<JsonString>(value)) {}
+Json::Json(const Json::array &values) : m_ptr(make_shared<JsonArray>(values)) {}
+Json::Json(Json::array &&values) : m_ptr(make_shared<JsonArray>(move(values))) {}
+Json::Json(const Json::object &values) : m_ptr(make_shared<JsonObject>(values)) {}
+Json::Json(Json::object &&values) : m_ptr(make_shared<JsonObject>(move(values))) {}
+
+/* * * * * * * * * * * * * * * * * * * *
+ * Accessors
+ */
+
+Json::Type Json::type() const { return m_ptr->type(); }
+double Json::number_value() const { return m_ptr->number_value(); }
+int Json::int_value() const { return m_ptr->int_value(); }
+bool Json::bool_value() const { return m_ptr->bool_value(); }
+const string & Json::string_value() const { return m_ptr->string_value(); }
+const vector<Json> & Json::array_items() const { return m_ptr->array_items(); }
+const map<string, Json> & Json::object_items() const { return m_ptr->object_items(); }
+const Json & Json::operator[] (size_t i) const { return (*m_ptr)[i]; }
+const Json & Json::operator[] (const string &key) const { return (*m_ptr)[key]; }
+
+double JsonValue::number_value() const { return 0; }
+int JsonValue::int_value() const { return 0; }
+bool JsonValue::bool_value() const { return false; }
+const string & JsonValue::string_value() const { return statics().empty_string; }
+const vector<Json> & JsonValue::array_items() const { return statics().empty_vector; }
+const map<string, Json> & JsonValue::object_items() const { return statics().empty_map; }
+const Json & JsonValue::operator[] (size_t) const { return static_null(); }
+const Json & JsonValue::operator[] (const string &) const { return static_null(); }
+
+const Json & JsonObject::operator[] (const string &key) const {
+ auto iter = m_value.find(key);
+ return (iter == m_value.end()) ? static_null() : iter->second;
+}
+const Json & JsonArray::operator[] (size_t i) const {
+ if (i >= m_value.size()) return static_null();
+ else return m_value[i];
+}
+
+/* * * * * * * * * * * * * * * * * * * *
+ * Comparison
+ */
+
+bool Json::operator== (const Json &other) const {
+ if (m_ptr == other.m_ptr)
+ return true;
+ if (m_ptr->type() != other.m_ptr->type())
+ return false;
+
+ return m_ptr->equals(other.m_ptr.get());
+}
+
+bool Json::operator< (const Json &other) const {
+ if (m_ptr == other.m_ptr)
+ return false;
+ if (m_ptr->type() != other.m_ptr->type())
+ return m_ptr->type() < other.m_ptr->type();
+
+ return m_ptr->less(other.m_ptr.get());
+}
+
+/* * * * * * * * * * * * * * * * * * * *
+ * Parsing
+ */
+
+/* esc(c)
+ *
+ * Format char c suitable for printing in an error message.
+ */
+static inline string esc(char c) {
+ char buf[12];
+ if (static_cast<uint8_t>(c) >= 0x20 && static_cast<uint8_t>(c) <= 0x7f) {
+ snprintf(buf, sizeof buf, "'%c' (%d)", c, c);
+ } else {
+ snprintf(buf, sizeof buf, "(%d)", c);
+ }
+ return string(buf);
+}
+
+static inline bool in_range(long x, long lower, long upper) {
+ return (x >= lower && x <= upper);
+}
+
+namespace {
+/* JsonParser
+ *
+ * Object that tracks all state of an in-progress parse.
+ */
+struct JsonParser final {
+
+ /* State
+ */
+ const string &str;
+ size_t i;
+ string &err;
+ bool failed;
+ const JsonParse strategy;
+
+ /* fail(msg, err_ret = Json())
+ *
+ * Mark this parse as failed.
+ */
+ Json fail(string &&msg) {
+ return fail(move(msg), Json());
+ }
+
+ template <typename T>
+ T fail(string &&msg, const T err_ret) {
+ if (!failed)
+ err = std::move(msg);
+ failed = true;
+ return err_ret;
+ }
+
+ /* consume_whitespace()
+ *
+ * Advance until the current character is non-whitespace.
+ */
+ void consume_whitespace() {
+ while (str[i] == ' ' || str[i] == '\r' || str[i] == '\n' || str[i] == '\t')
+ i++;
+ }
+
+ /* consume_comment()
+ *
+ * Advance comments (c-style inline and multiline).
+ */
+ bool consume_comment() {
+ bool comment_found = false;
+ if (str[i] == '/') {
+ i++;
+ if (i == str.size())
+ return fail("unexpected end of input after start of comment", false);
+ if (str[i] == '/') { // inline comment
+ i++;
+ // advance until next line, or end of input
+ while (i < str.size() && str[i] != '\n') {
+ i++;
+ }
+ comment_found = true;
+ }
+ else if (str[i] == '*') { // multiline comment
+ i++;
+ if (i > str.size()-2)
+ return fail("unexpected end of input inside multi-line comment", false);
+ // advance until closing tokens
+ while (!(str[i] == '*' && str[i+1] == '/')) {
+ i++;
+ if (i > str.size()-2)
+ return fail(
+ "unexpected end of input inside multi-line comment", false);
+ }
+ i += 2;
+ comment_found = true;
+ }
+ else
+ return fail("malformed comment", false);
+ }
+ return comment_found;
+ }
+
+ /* consume_garbage()
+ *
+ * Advance until the current character is non-whitespace and non-comment.
+ */
+ void consume_garbage() {
+ consume_whitespace();
+ if(strategy == JsonParse::COMMENTS) {
+ bool comment_found = false;
+ do {
+ comment_found = consume_comment();
+ if (failed) return;
+ consume_whitespace();
+ }
+ while(comment_found);
+ }
+ }
+
+ /* get_next_token()
+ *
+ * Return the next non-whitespace character. If the end of the input is reached,
+ * flag an error and return 0.
+ */
+ char get_next_token() {
+ consume_garbage();
+ if (failed) return static_cast<char>(0);
+ if (i == str.size())
+ return fail("unexpected end of input", static_cast<char>(0));
+
+ return str[i++];
+ }
+
+ /* encode_utf8(pt, out)
+ *
+ * Encode pt as UTF-8 and add it to out.
+ */
+ void encode_utf8(long pt, string & out) {
+ if (pt < 0)
+ return;
+
+ if (pt < 0x80) {
+ out += static_cast<char>(pt);
+ } else if (pt < 0x800) {
+ out += static_cast<char>((pt >> 6) | 0xC0);
+ out += static_cast<char>((pt & 0x3F) | 0x80);
+ } else if (pt < 0x10000) {
+ out += static_cast<char>((pt >> 12) | 0xE0);
+ out += static_cast<char>(((pt >> 6) & 0x3F) | 0x80);
+ out += static_cast<char>((pt & 0x3F) | 0x80);
+ } else {
+ out += static_cast<char>((pt >> 18) | 0xF0);
+ out += static_cast<char>(((pt >> 12) & 0x3F) | 0x80);
+ out += static_cast<char>(((pt >> 6) & 0x3F) | 0x80);
+ out += static_cast<char>((pt & 0x3F) | 0x80);
+ }
+ }
+
+ /* parse_string()
+ *
+ * Parse a string, starting at the current position.
+ */
+ string parse_string() {
+ string out;
+ long last_escaped_codepoint = -1;
+ while (true) {
+ if (i == str.size())
+ return fail("unexpected end of input in string", "");
+
+ char ch = str[i++];
+
+ if (ch == '"') {
+ encode_utf8(last_escaped_codepoint, out);
+ return out;
+ }
+
+ if (in_range(ch, 0, 0x1f))
+ return fail("unescaped " + esc(ch) + " in string", "");
+
+ // The usual case: non-escaped characters
+ if (ch != '\\') {
+ encode_utf8(last_escaped_codepoint, out);
+ last_escaped_codepoint = -1;
+ out += ch;
+ continue;
+ }
+
+ // Handle escapes
+ if (i == str.size())
+ return fail("unexpected end of input in string", "");
+
+ ch = str[i++];
+
+ if (ch == 'u') {
+ // Extract 4-byte escape sequence
+ string esc = str.substr(i, 4);
+ // Explicitly check length of the substring. The following loop
+ // relies on std::string returning the terminating NUL when
+ // accessing str[length]. Checking here reduces brittleness.
+ if (esc.length() < 4) {
+ return fail("bad \\u escape: " + esc, "");
+ }
+ for (size_t j = 0; j < 4; j++) {
+ if (!in_range(esc[j], 'a', 'f') && !in_range(esc[j], 'A', 'F')
+ && !in_range(esc[j], '0', '9'))
+ return fail("bad \\u escape: " + esc, "");
+ }
+
+ long codepoint = strtol(esc.data(), nullptr, 16);
+
+ // JSON specifies that characters outside the BMP shall be encoded as a pair
+ // of 4-hex-digit \u escapes encoding their surrogate pair components. Check
+ // whether we're in the middle of such a beast: the previous codepoint was an
+ // escaped lead (high) surrogate, and this is a trail (low) surrogate.
+ if (in_range(last_escaped_codepoint, 0xD800, 0xDBFF)
+ && in_range(codepoint, 0xDC00, 0xDFFF)) {
+ // Reassemble the two surrogate pairs into one astral-plane character, per
+ // the UTF-16 algorithm.
+ encode_utf8((((last_escaped_codepoint - 0xD800) << 10)
+ | (codepoint - 0xDC00)) + 0x10000, out);
+ last_escaped_codepoint = -1;
+ } else {
+ encode_utf8(last_escaped_codepoint, out);
+ last_escaped_codepoint = codepoint;
+ }
+
+ i += 4;
+ continue;
+ }
+
+ encode_utf8(last_escaped_codepoint, out);
+ last_escaped_codepoint = -1;
+
+ if (ch == 'b') {
+ out += '\b';
+ } else if (ch == 'f') {
+ out += '\f';
+ } else if (ch == 'n') {
+ out += '\n';
+ } else if (ch == 'r') {
+ out += '\r';
+ } else if (ch == 't') {
+ out += '\t';
+ } else if (ch == '"' || ch == '\\' || ch == '/') {
+ out += ch;
+ } else {
+ return fail("invalid escape character " + esc(ch), "");
+ }
+ }
+ }
+
+ /* parse_number()
+ *
+ * Parse a double.
+ */
+ Json parse_number() {
+ size_t start_pos = i;
+
+ if (str[i] == '-')
+ i++;
+
+ // Integer part
+ if (str[i] == '0') {
+ i++;
+ if (in_range(str[i], '0', '9'))
+ return fail("leading 0s not permitted in numbers");
+ } else if (in_range(str[i], '1', '9')) {
+ i++;
+ while (in_range(str[i], '0', '9'))
+ i++;
+ } else {
+ return fail("invalid " + esc(str[i]) + " in number");
+ }
+
+ if (str[i] != '.' && str[i] != 'e' && str[i] != 'E'
+ && (i - start_pos) <= static_cast<size_t>(std::numeric_limits<int>::digits10)) {
+ return std::atoi(str.c_str() + start_pos);
+ }
+
+ // Decimal part
+ if (str[i] == '.') {
+ i++;
+ if (!in_range(str[i], '0', '9'))
+ return fail("at least one digit required in fractional part");
+
+ while (in_range(str[i], '0', '9'))
+ i++;
+ }
+
+ // Exponent part
+ if (str[i] == 'e' || str[i] == 'E') {
+ i++;
+
+ if (str[i] == '+' || str[i] == '-')
+ i++;
+
+ if (!in_range(str[i], '0', '9'))
+ return fail("at least one digit required in exponent");
+
+ while (in_range(str[i], '0', '9'))
+ i++;
+ }
+
+ return std::strtod(str.c_str() + start_pos, nullptr);
+ }
+
+ /* expect(str, res)
+ *
+ * Expect that 'str' starts at the character that was just read. If it does, advance
+ * the input and return res. If not, flag an error.
+ */
+ Json expect(const string &expected, Json res) {
+ assert(i != 0);
+ i--;
+ if (str.compare(i, expected.length(), expected) == 0) {
+ i += expected.length();
+ return res;
+ } else {
+ return fail("parse error: expected " + expected + ", got " + str.substr(i, expected.length()));
+ }
+ }
+
+ /* parse_json()
+ *
+ * Parse a JSON object.
+ */
+ Json parse_json(int depth) {
+ if (depth > max_depth) {
+ return fail("exceeded maximum nesting depth");
+ }
+
+ char ch = get_next_token();
+ if (failed)
+ return Json();
+
+ if (ch == '-' || (ch >= '0' && ch <= '9')) {
+ i--;
+ return parse_number();
+ }
+
+ if (ch == 't')
+ return expect("true", true);
+
+ if (ch == 'f')
+ return expect("false", false);
+
+ if (ch == 'n')
+ return expect("null", Json());
+
+ if (ch == '"')
+ return parse_string();
+
+ if (ch == '{') {
+ map<string, Json> data;
+ ch = get_next_token();
+ if (ch == '}')
+ return data;
+
+ while (1) {
+ if (ch != '"')
+ return fail("expected '\"' in object, got " + esc(ch));
+
+ string key = parse_string();
+ if (failed)
+ return Json();
+
+ ch = get_next_token();
+ if (ch != ':')
+ return fail("expected ':' in object, got " + esc(ch));
+
+ data[std::move(key)] = parse_json(depth + 1);
+ if (failed)
+ return Json();
+
+ ch = get_next_token();
+ if (ch == '}')
+ break;
+ if (ch != ',')
+ return fail("expected ',' in object, got " + esc(ch));
+
+ ch = get_next_token();
+ }
+ return data;
+ }
+
+ if (ch == '[') {
+ vector<Json> data;
+ ch = get_next_token();
+ if (ch == ']')
+ return data;
+
+ while (1) {
+ i--;
+ data.push_back(parse_json(depth + 1));
+ if (failed)
+ return Json();
+
+ ch = get_next_token();
+ if (ch == ']')
+ break;
+ if (ch != ',')
+ return fail("expected ',' in list, got " + esc(ch));
+
+ ch = get_next_token();
+ (void)ch;
+ }
+ return data;
+ }
+
+ return fail("expected value, got " + esc(ch));
+ }
+};
+}//namespace {
+
+Json Json::parse(const string &in, string &err, JsonParse strategy) {
+ JsonParser parser { in, 0, err, false, strategy };
+ Json result = parser.parse_json(0);
+
+ // Check for any trailing garbage
+ parser.consume_garbage();
+ if (parser.failed)
+ return Json();
+ if (parser.i != in.size())
+ return parser.fail("unexpected trailing " + esc(in[parser.i]));
+
+ return result;
+}
+
+// Documented in json11.hpp
+vector<Json> Json::parse_multi(const string &in,
+ std::string::size_type &parser_stop_pos,
+ string &err,
+ JsonParse strategy) {
+ JsonParser parser { in, 0, err, false, strategy };
+ parser_stop_pos = 0;
+ vector<Json> json_vec;
+ while (parser.i != in.size() && !parser.failed) {
+ json_vec.push_back(parser.parse_json(0));
+ if (parser.failed)
+ break;
+
+ // Check for another object
+ parser.consume_garbage();
+ if (parser.failed)
+ break;
+ parser_stop_pos = parser.i;
+ }
+ return json_vec;
+}
+
+/* * * * * * * * * * * * * * * * * * * *
+ * Shape-checking
+ */
+
+bool Json::has_shape(const shape & types, string & err) const {
+ if (!is_object()) {
+ err = "expected JSON object, got " + dump();
+ return false;
+ }
+
+ const auto& obj_items = object_items();
+ for (auto & item : types) {
+ const auto it = obj_items.find(item.first);
+ if (it == obj_items.cend() || it->second.type() != item.second) {
+ err = "bad type for " + item.first + " in " + dump();
+ return false;
+ }
+ }
+
+ return true;
+}
+
+} // namespace json11
diff --git a/3rdparty/json11/json11.hpp b/3rdparty/json11/json11.hpp
new file mode 100644
index 00000000..0c47d050
--- /dev/null
+++ b/3rdparty/json11/json11.hpp
@@ -0,0 +1,232 @@
+/* json11
+ *
+ * json11 is a tiny JSON library for C++11, providing JSON parsing and serialization.
+ *
+ * The core object provided by the library is json11::Json. A Json object represents any JSON
+ * value: null, bool, number (int or double), string (std::string), array (std::vector), or
+ * object (std::map).
+ *
+ * Json objects act like values: they can be assigned, copied, moved, compared for equality or
+ * order, etc. There are also helper methods Json::dump, to serialize a Json to a string, and
+ * Json::parse (static) to parse a std::string as a Json object.
+ *
+ * Internally, the various types of Json object are represented by the JsonValue class
+ * hierarchy.
+ *
+ * A note on numbers - JSON specifies the syntax of number formatting but not its semantics,
+ * so some JSON implementations distinguish between integers and floating-point numbers, while
+ * some don't. In json11, we choose the latter. Because some JSON implementations (namely
+ * Javascript itself) treat all numbers as the same type, distinguishing the two leads
+ * to JSON that will be *silently* changed by a round-trip through those implementations.
+ * Dangerous! To avoid that risk, json11 stores all numbers as double internally, but also
+ * provides integer helpers.
+ *
+ * Fortunately, double-precision IEEE754 ('double') can precisely store any integer in the
+ * range +/-2^53, which includes every 'int' on most systems. (Timestamps often use int64
+ * or long long to avoid the Y2038K problem; a double storing microseconds since some epoch
+ * will be exact for +/- 275 years.)
+ */
+
+/* Copyright (c) 2013 Dropbox, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#pragma once
+
+#include <string>
+#include <vector>
+#include <map>
+#include <memory>
+#include <initializer_list>
+
+#ifdef _MSC_VER
+ #if _MSC_VER <= 1800 // VS 2013
+ #ifndef noexcept
+ #define noexcept throw()
+ #endif
+
+ #ifndef snprintf
+ #define snprintf _snprintf_s
+ #endif
+ #endif
+#endif
+
+namespace json11 {
+
+enum JsonParse {
+ STANDARD, COMMENTS
+};
+
+class JsonValue;
+
+class Json final {
+public:
+ // Types
+ enum Type {
+ NUL, NUMBER, BOOL, STRING, ARRAY, OBJECT
+ };
+
+ // Array and object typedefs
+ typedef std::vector<Json> array;
+ typedef std::map<std::string, Json> object;
+
+ // Constructors for the various types of JSON value.
+ Json() noexcept; // NUL
+ Json(std::nullptr_t) noexcept; // NUL
+ Json(double value); // NUMBER
+ Json(int value); // NUMBER
+ Json(bool value); // BOOL
+ Json(const std::string &value); // STRING
+ Json(std::string &&value); // STRING
+ Json(const char * value); // STRING
+ Json(const array &values); // ARRAY
+ Json(array &&values); // ARRAY
+ Json(const object &values); // OBJECT
+ Json(object &&values); // OBJECT
+
+ // Implicit constructor: anything with a to_json() function.
+ template <class T, class = decltype(&T::to_json)>
+ Json(const T & t) : Json(t.to_json()) {}
+
+ // Implicit constructor: map-like objects (std::map, std::unordered_map, etc)
+ template <class M, typename std::enable_if<
+ std::is_constructible<std::string, decltype(std::declval<M>().begin()->first)>::value
+ && std::is_constructible<Json, decltype(std::declval<M>().begin()->second)>::value,
+ int>::type = 0>
+ Json(const M & m) : Json(object(m.begin(), m.end())) {}
+
+ // Implicit constructor: vector-like objects (std::list, std::vector, std::set, etc)
+ template <class V, typename std::enable_if<
+ std::is_constructible<Json, decltype(*std::declval<V>().begin())>::value,
+ int>::type = 0>
+ Json(const V & v) : Json(array(v.begin(), v.end())) {}
+
+ // This prevents Json(some_pointer) from accidentally producing a bool. Use
+ // Json(bool(some_pointer)) if that behavior is desired.
+ Json(void *) = delete;
+
+ // Accessors
+ Type type() const;
+
+ bool is_null() const { return type() == NUL; }
+ bool is_number() const { return type() == NUMBER; }
+ bool is_bool() const { return type() == BOOL; }
+ bool is_string() const { return type() == STRING; }
+ bool is_array() const { return type() == ARRAY; }
+ bool is_object() const { return type() == OBJECT; }
+
+ // Return the enclosed value if this is a number, 0 otherwise. Note that json11 does not
+ // distinguish between integer and non-integer numbers - number_value() and int_value()
+ // can both be applied to a NUMBER-typed object.
+ double number_value() const;
+ int int_value() const;
+
+ // Return the enclosed value if this is a boolean, false otherwise.
+ bool bool_value() const;
+ // Return the enclosed string if this is a string, "" otherwise.
+ const std::string &string_value() const;
+ // Return the enclosed std::vector if this is an array, or an empty vector otherwise.
+ const array &array_items() const;
+ // Return the enclosed std::map if this is an object, or an empty map otherwise.
+ const object &object_items() const;
+
+ // Return a reference to arr[i] if this is an array, Json() otherwise.
+ const Json & operator[](size_t i) const;
+ // Return a reference to obj[key] if this is an object, Json() otherwise.
+ const Json & operator[](const std::string &key) const;
+
+ // Serialize.
+ void dump(std::string &out) const;
+ std::string dump() const {
+ std::string out;
+ dump(out);
+ return out;
+ }
+
+ // Parse. If parse fails, return Json() and assign an error message to err.
+ static Json parse(const std::string & in,
+ std::string & err,
+ JsonParse strategy = JsonParse::STANDARD);
+ static Json parse(const char * in,
+ std::string & err,
+ JsonParse strategy = JsonParse::STANDARD) {
+ if (in) {
+ return parse(std::string(in), err, strategy);
+ } else {
+ err = "null input";
+ return nullptr;
+ }
+ }
+ // Parse multiple objects, concatenated or separated by whitespace
+ static std::vector<Json> parse_multi(
+ const std::string & in,
+ std::string::size_type & parser_stop_pos,
+ std::string & err,
+ JsonParse strategy = JsonParse::STANDARD);
+
+ static inline std::vector<Json> parse_multi(
+ const std::string & in,
+ std::string & err,
+ JsonParse strategy = JsonParse::STANDARD) {
+ std::string::size_type parser_stop_pos;
+ return parse_multi(in, parser_stop_pos, err, strategy);
+ }
+
+ bool operator== (const Json &rhs) const;
+ bool operator< (const Json &rhs) const;
+ bool operator!= (const Json &rhs) const { return !(*this == rhs); }
+ bool operator<= (const Json &rhs) const { return !(rhs < *this); }
+ bool operator> (const Json &rhs) const { return (rhs < *this); }
+ bool operator>= (const Json &rhs) const { return !(*this < rhs); }
+
+ /* has_shape(types, err)
+ *
+ * Return true if this is a JSON object and, for each item in types, has a field of
+ * the given type. If not, return false and set err to a descriptive message.
+ */
+ typedef std::initializer_list<std::pair<std::string, Type>> shape;
+ bool has_shape(const shape & types, std::string & err) const;
+
+private:
+ std::shared_ptr<JsonValue> m_ptr;
+};
+
+// Internal class hierarchy - JsonValue objects are not exposed to users of this API.
+class JsonValue {
+protected:
+ friend class Json;
+ friend class JsonInt;
+ friend class JsonDouble;
+ virtual Json::Type type() const = 0;
+ virtual bool equals(const JsonValue * other) const = 0;
+ virtual bool less(const JsonValue * other) const = 0;
+ virtual void dump(std::string &out) const = 0;
+ virtual double number_value() const;
+ virtual int int_value() const;
+ virtual bool bool_value() const;
+ virtual const std::string &string_value() const;
+ virtual const Json::array &array_items() const;
+ virtual const Json &operator[](size_t i) const;
+ virtual const Json::object &object_items() const;
+ virtual const Json &operator[](const std::string &key) const;
+ virtual ~JsonValue() {}
+};
+
+} // namespace json11
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 7bdff611..54da5bdd 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -97,7 +97,7 @@ endif()
find_package(Sanitizers)
# List of Boost libraries to include
-set(boost_libs filesystem thread program_options iostreams)
+set(boost_libs filesystem thread program_options iostreams system)
if (BUILD_GUI AND NOT BUILD_PYTHON)
message(FATAL_ERROR "GUI requires Python to build")
@@ -191,7 +191,7 @@ if (BUILD_PYTHON)
endif ()
endif()
-include_directories(common/ json/ ${Boost_INCLUDE_DIRS} ${PYTHON_INCLUDE_DIRS})
+include_directories(common/ json/ frontend/ 3rdparty/json11/ ${Boost_INCLUDE_DIRS} ${PYTHON_INCLUDE_DIRS})
if(BUILD_HEAP)
find_package (Eigen3 REQUIRED NO_MODULE)
@@ -202,7 +202,10 @@ endif()
aux_source_directory(common/ COMMON_SRC_FILES)
aux_source_directory(json/ JSON_PARSER_FILES)
-set(COMMON_FILES ${COMMON_SRC_FILES} ${JSON_PARSER_FILES})
+aux_source_directory(3rdparty/json11 EXT_JSON11_FILES)
+aux_source_directory(frontend/ FRONTEND_FILES)
+
+set(COMMON_FILES ${COMMON_SRC_FILES} ${EXT_JSON11_FILES} ${JSON_PARSER_FILES} ${FRONTEND_FILES})
set(CMAKE_BUILD_TYPE Release)
if(MINGW)
diff --git a/README.md b/README.md
index efbc6bb0..cf743732 100644
--- a/README.md
+++ b/README.md
@@ -6,13 +6,10 @@ tool.
Currently nextpnr supports:
* Lattice iCE40 devices supported by [Project IceStorm](http://www.clifford.at/icestorm/)
- * *(experimental)* Lattice ECP5 devices supported by [Project Trellis](https://github.com/SymbiFlow/prjtrellis)
+ * Lattice ECP5 devices supported by [Project Trellis](https://github.com/SymbiFlow/prjtrellis)
* *(experimental)* a "generic" back-end for user-defined architectures
-We hope to see Xilinx 7 Series thanks to
-[Project X-Ray](https://github.com/SymbiFlow/prjxray) and even more FPGA families
-supported in the future. We would love your help in developing this
-awesome new project!
+There is some work in progress towards [support for Xilinx devices](https://github.com/daveshah1/nextpnr-xilinx/) but it is not upstream and not intended for end users at the present time. We hope to see more FPGA families supported in the future. We would love your help in developing this awesome new project!
A brief (academic) paper describing the Yosys+nextpnr flow can be found
on [arXiv](https://arxiv.org/abs/1903.10407).
@@ -38,7 +35,7 @@ of the selected architecture:
- Qt5 or later (`qt5-default` for Ubuntu 16.04)
- Python 3.5 or later, including development libraries (`python3-dev` for Ubuntu)
- on Windows make sure to install same version as supported by [vcpkg](https://github.com/Microsoft/vcpkg/blob/master/ports/python3/CONTROL)
-- Boost libraries (`libboost-dev libboost-filesystem-dev libboost-thread-dev libboost-program-options-dev libboost-python-dev libboost-dev` or `libboost-all-dev` for Ubuntu)
+- Boost libraries (`libboost-dev libboost-filesystem-dev libboost-thread-dev libboost-program-options-dev libboost-python-dev libboost-iostreams-dev libboost-dev` or `libboost-all-dev` for Ubuntu)
- Eigen3 (`libeigen3-dev` for Ubuntu) is required to build the analytic placer
- Latest git Yosys is required to synthesise the demo design
- For building on Windows with MSVC, usage of vcpkg is advised for dependency installation.
@@ -49,6 +46,8 @@ of the selected architecture:
- For building on macOS, brew utility is needed.
- Install all needed packages `brew install cmake python boost boost-python3 qt5 eigen`
- Do not forget to add qt5 in path as well `echo 'export PATH="/usr/local/opt/qt/bin:$PATH"' >> ~/.bash_profile`
+
+ NOTE: this change is effective in next terminal session, so please re-open terminal window before next step
Getting started
---------------
@@ -106,12 +105,7 @@ make -j$(nproc)
sudo make install
```
- - For an ECP5 blinky on the 45k ULX3S board, first synthesise using `yosys blinky.ys` in `ecp5/synth`.
- - Then run ECP5 place-and route using `./nextpnr-ecp5 --json ecp5/synth/blinky.json --basecfg ecp5/synth/ulx3s_empty.config --textcfg ecp5/synth/ulx3s_out.config`
- - Create a bitstream using `ecppack ulx3s_out.config ulx3s.bit`
- - Note that `ulx3s_empty.config` contains fixed/unknown bits to be copied to the output bitstream
-
- - More examples of the ECP5 flow for a range of boards can be found in the [Project Trellis Examples](https://github.com/SymbiFlow/prjtrellis/tree/master/examples).
+ - Examples of the ECP5 flow for a range of boards can be found in the [Project Trellis Examples](https://github.com/SymbiFlow/prjtrellis/tree/master/examples).
### nextpnr-generic
@@ -124,7 +118,7 @@ make -j$(nproc)
sudo make install
```
-TBD: Getting started example for generic target.
+An example of how to use the generic flow is in [generic/examples](generic/examples). See also the [Generic Architecture docs](docs/generic.md).
Additional notes for building nextpnr
-------------------------------------
@@ -170,7 +164,7 @@ Notes for developers
Testing
-------
-- To build test binaries as well, use `-DBUILD_TESTS=ON` and after `make` run `make tests` to run them, or you can run separate binaries.
+- To build test binaries as well, use `-DBUILD_TESTS=ON` and after `make` run `make test` to run them, or you can run separate binaries.
- To use code sanitizers use the `cmake` options:
- `-DSANITIZE_ADDRESS=ON`
- `-DSANITIZE_MEMORY=ON -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++`
diff --git a/bba/bba.cmake b/bba/bba.cmake
index a6995ca3..f1f5a754 100644
--- a/bba/bba.cmake
+++ b/bba/bba.cmake
@@ -5,7 +5,7 @@ ENDIF(CMAKE_CROSSCOMPILING)
IF(NOT CMAKE_CROSSCOMPILING)
ADD_EXECUTABLE(bbasm bba/main.cc)
- target_link_libraries(bbasm LINK_PUBLIC ${Boost_PROGRAM_OPTIONS_LIBRARY})
+ target_link_libraries(bbasm LINK_PUBLIC ${Boost_PROGRAM_OPTIONS_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_SYSTEM_LIBRARY})
ENDIF(NOT CMAKE_CROSSCOMPILING)
IF(NOT CMAKE_CROSSCOMPILING)
diff --git a/bba/main.cc b/bba/main.cc
index d4d16e12..bff42028 100644
--- a/bba/main.cc
+++ b/bba/main.cc
@@ -19,6 +19,7 @@
*/
#include <assert.h>
+#include <boost/filesystem/convenience.hpp>
#include <boost/program_options.hpp>
#include <iostream>
#include <map>
@@ -72,6 +73,7 @@ int main(int argc, char **argv)
bool verbose = false;
bool bigEndian;
bool writeC = false;
+ bool writeE = false;
char buffer[512];
namespace po = boost::program_options;
@@ -82,7 +84,8 @@ int main(int argc, char **argv)
options.add_options()("debug,d", "debug output");
options.add_options()("be,b", "big endian");
options.add_options()("le,l", "little endian");
- options.add_options()("c,c", "write c strings");
+ options.add_options()("c,c", "write C strings");
+ options.add_options()("e,e", "write #embed C");
options.add_options()("files", po::value<std::vector<std::string>>(), "file parameters");
pos.add("files", -1);
@@ -115,13 +118,19 @@ int main(int argc, char **argv)
}
if (vm.count("c"))
writeC = true;
+ if (vm.count("e"))
+ writeE = true;
+ if (writeC && writeE) {
+ printf("Incompatible modes\n");
+ exit(-1);
+ }
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) {
+ if (files.size() != (writeE ? 3 : 2)) {
printf("Input and output parameters must be set\n");
exit(-1);
}
@@ -433,6 +442,21 @@ int main(int argc, char **argv)
for (auto &s : postText)
fprintf(fileOut, "%s\n", s.c_str());
+ } else if (writeE) {
+ 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);
+ fprintf(fileOut, "#embed_str \"%s\"\n", boost::filesystem::basename(files.at(2)).c_str());
+ fprintf(fileOut, ";\n");
+
+ for (auto &s : postText)
+ fprintf(fileOut, "%s\n", s.c_str());
+
+ FILE *fileBin = fopen(files.at(2).c_str(), "wb");
+ assert(fileBin != nullptr);
+ fwrite(data.data(), int(data.size()), 1, fileBin);
+ fclose(fileBin);
} else {
fwrite(data.data(), int(data.size()), 1, fileOut);
}
diff --git a/common/arch_pybindings_shared.h b/common/arch_pybindings_shared.h
index f681af92..89a61dad 100644
--- a/common/arch_pybindings_shared.h
+++ b/common/arch_pybindings_shared.h
@@ -5,6 +5,10 @@ readonly_wrapper<Context, decltype(&Context::cells), &Context::cells, wrap_conte
readonly_wrapper<Context, decltype(&Context::nets), &Context::nets, wrap_context<NetMap &>>::def_wrap(ctx_cls, "nets");
readonly_wrapper<Context, decltype(&Context::net_aliases), &Context::net_aliases, wrap_context<AliasMap &>>::def_wrap(
ctx_cls, "net_aliases");
+readonly_wrapper<Context, decltype(&Context::hierarchy), &Context::hierarchy, wrap_context<HierarchyMap &>>::def_wrap(
+ ctx_cls, "hierarchy");
+readwrite_wrapper<Context, decltype(&Context::top_module), &Context::top_module, conv_to_str<IdString>,
+ conv_from_str<IdString>>::def_wrap(ctx_cls, "top_module");
fn_wrapper_1a<Context, decltype(&Context::getNetByAlias), &Context::getNetByAlias, deref_and_wrap<NetInfo>,
conv_from_str<IdString>>::def_wrap(ctx_cls, "getNetByAlias");
diff --git a/common/command.cc b/common/command.cc
index ad5b6c54..c2f02b27 100644
--- a/common/command.cc
+++ b/common/command.cc
@@ -35,7 +35,7 @@
#include <iostream>
#include "command.h"
#include "design_utils.h"
-#include "jsonparse.h"
+#include "json_frontend.h"
#include "jsonwrite.h"
#include "log.h"
#include "timing.h"
@@ -149,6 +149,9 @@ po::options_description CommandHandler::getGeneralOptions()
general.add_options()("freq", po::value<double>(), "set target frequency for design in MHz");
general.add_options()("timing-allow-fail", "allow timing to fail in design");
general.add_options()("no-tmdriv", "disable timing-driven placement");
+ general.add_options()("sdf", po::value<std::string>(), "SDF delay back-annotation file to write");
+ general.add_options()("sdf-cvc", "enable tweaks for SDF file compatibility with the CVC simulator");
+
return general;
}
@@ -262,9 +265,8 @@ int CommandHandler::executeMain(std::unique_ptr<Context> ctx)
if (vm.count("json")) {
std::string filename = vm["json"].as<std::string>();
std::ifstream f(filename);
- if (!parse_json_file(f, filename, w.getContext()))
+ if (!parse_json(f, filename, w.getContext()))
log_error("Loading design failed.\n");
-
customAfterLoad(w.getContext());
w.notifyChangeContext();
w.updateActions();
@@ -281,7 +283,7 @@ int CommandHandler::executeMain(std::unique_ptr<Context> ctx)
if (vm.count("json")) {
std::string filename = vm["json"].as<std::string>();
std::ifstream f(filename);
- if (!parse_json_file(f, filename, ctx.get()))
+ if (!parse_json(f, filename, ctx.get()))
log_error("Loading design failed.\n");
customAfterLoad(ctx.get());
@@ -336,6 +338,14 @@ int CommandHandler::executeMain(std::unique_ptr<Context> ctx)
log_error("Saving design failed.\n");
}
+ if (vm.count("sdf")) {
+ std::string filename = vm["sdf"].as<std::string>();
+ std::ofstream f(filename);
+ if (!f)
+ log_error("Failed to open SDF file '%s' for writing.\n", filename.c_str());
+ ctx->writeSDF(f, vm.count("sdf-cvc"));
+ }
+
#ifndef NO_PYTHON
deinit_python();
#endif
@@ -371,12 +381,6 @@ int CommandHandler::exec()
return 0;
std::unordered_map<std::string, Property> values;
- if (vm.count("json")) {
- std::string filename = vm["json"].as<std::string>();
- std::ifstream f(filename);
- if (!load_json_settings(f, filename, values))
- log_error("Loading design failed.\n");
- }
std::unique_ptr<Context> ctx = createContext(values);
setupContext(ctx.get());
setupArchContext(ctx.get());
@@ -393,17 +397,12 @@ std::unique_ptr<Context> CommandHandler::load_json(std::string filename)
{
vm.clear();
std::unordered_map<std::string, Property> values;
- {
- std::ifstream f(filename);
- if (!load_json_settings(f, filename, values))
- log_error("Loading design failed.\n");
- }
std::unique_ptr<Context> ctx = createContext(values);
setupContext(ctx.get());
setupArchContext(ctx.get());
{
std::ifstream f(filename);
- if (!parse_json_file(f, filename, ctx.get()))
+ if (!parse_json(f, filename, ctx.get()))
log_error("Loading design failed.\n");
}
customAfterLoad(ctx.get());
diff --git a/common/design_utils.cc b/common/design_utils.cc
index bdf5ca5c..10212a03 100644
--- a/common/design_utils.cc
+++ b/common/design_utils.cc
@@ -88,7 +88,7 @@ void connect_port(const Context *ctx, NetInfo *net, CellInfo *cell, IdString por
NPNR_ASSERT(net->driver.cell == nullptr);
net->driver.cell = cell;
net->driver.port = port_name;
- } else if (port.type == PORT_IN) {
+ } else if (port.type == PORT_IN || port.type == PORT_INOUT) {
PortRef user;
user.cell = cell;
user.port = port_name;
@@ -146,4 +146,14 @@ void rename_port(Context *ctx, CellInfo *cell, IdString old_name, IdString new_n
cell->ports[new_name] = pi;
}
+void rename_net(Context *ctx, NetInfo *net, IdString new_name)
+{
+ if (net == nullptr)
+ return;
+ NPNR_ASSERT(!ctx->nets.count(new_name));
+ std::swap(ctx->nets[net->name], ctx->nets[new_name]);
+ ctx->nets.erase(net->name);
+ net->name = new_name;
+}
+
NEXTPNR_NAMESPACE_END
diff --git a/common/design_utils.h b/common/design_utils.h
index 3eb9024f..1ae1d648 100644
--- a/common/design_utils.h
+++ b/common/design_utils.h
@@ -94,6 +94,9 @@ void connect_ports(Context *ctx, CellInfo *cell1, IdString port1_name, CellInfo
// Rename a port if it exists on a cell
void rename_port(Context *ctx, CellInfo *cell, IdString old_name, IdString new_name);
+// Rename a net without invalidating pointers to it
+void rename_net(Context *ctx, NetInfo *net, IdString new_name);
+
void print_utilisation(const Context *ctx);
NEXTPNR_NAMESPACE_END
diff --git a/common/nextpnr.cc b/common/nextpnr.cc
index 933f124c..1156490c 100644
--- a/common/nextpnr.cc
+++ b/common/nextpnr.cc
@@ -21,6 +21,7 @@
#include <boost/algorithm/string.hpp>
#include "design_utils.h"
#include "log.h"
+#include "util.h"
NEXTPNR_NAMESPACE_BEGIN
@@ -522,7 +523,16 @@ void BaseCtx::createRectangularRegion(IdString name, int x0, int y0, int x1, int
void BaseCtx::addBelToRegion(IdString name, BelId bel) { region[name]->bels.insert(bel); }
void BaseCtx::constrainCellToRegion(IdString cell, IdString region_name)
{
- cells[cell]->region = region[region_name].get();
+ // Support hierarchical cells as well as leaf ones
+ if (hierarchy.count(cell)) {
+ auto &hc = hierarchy.at(cell);
+ for (auto &lc : hc.leaf_cells)
+ constrainCellToRegion(lc.second, region_name);
+ for (auto &hsc : hc.hier_cells)
+ constrainCellToRegion(hsc.second, region_name);
+ }
+ if (cells.count(cell))
+ cells.at(cell)->region = region[region_name].get();
}
DecalXY BaseCtx::constructDecalXY(DecalId decal, float x, float y)
{
@@ -723,4 +733,76 @@ void BaseCtx::copyBelPorts(IdString cell, BelId bel)
}
}
+namespace {
+struct FixupHierarchyWorker
+{
+ FixupHierarchyWorker(Context *ctx) : ctx(ctx){};
+ Context *ctx;
+ void run()
+ {
+ trim_hierarchy(ctx->top_module);
+ rebuild_hierarchy();
+ };
+ // Remove cells and nets that no longer exist in the netlist
+ std::vector<IdString> todelete_cells, todelete_nets;
+ void trim_hierarchy(IdString path)
+ {
+ auto &h = ctx->hierarchy.at(path);
+ todelete_cells.clear();
+ todelete_nets.clear();
+ for (auto &lc : h.leaf_cells) {
+ if (!ctx->cells.count(lc.second))
+ todelete_cells.push_back(lc.first);
+ }
+ for (auto &n : h.nets)
+ if (!ctx->nets.count(n.second))
+ todelete_nets.push_back(n.first);
+ for (auto tdc : todelete_cells) {
+ h.leaf_cells_by_gname.erase(h.leaf_cells.at(tdc));
+ h.leaf_cells.erase(tdc);
+ }
+ for (auto tdn : todelete_nets) {
+ h.nets_by_gname.erase(h.nets.at(tdn));
+ h.nets.erase(tdn);
+ }
+ for (auto &sc : h.hier_cells)
+ trim_hierarchy(sc.second);
+ }
+
+ IdString construct_local_name(HierarchicalCell &hc, IdString global_name, bool is_cell)
+ {
+ std::string gn = global_name.str(ctx);
+ auto dp = gn.find_last_of('.');
+ if (dp != std::string::npos)
+ gn = gn.substr(dp + 1);
+ IdString name = ctx->id(gn);
+ // Make sure name is unique
+ int adder = 0;
+ while (is_cell ? hc.leaf_cells.count(name) : hc.nets.count(name)) {
+ ++adder;
+ name = ctx->id(gn + "$" + std::to_string(adder));
+ }
+ return name;
+ }
+
+ // Update hierarchy structure for nets and cells that have hiercell set
+ void rebuild_hierarchy()
+ {
+ for (auto cell : sorted(ctx->cells)) {
+ CellInfo *ci = cell.second;
+ if (ci->hierpath == IdString())
+ ci->hierpath = ctx->top_module;
+ auto &hc = ctx->hierarchy.at(ci->hierpath);
+ if (hc.leaf_cells_by_gname.count(ci->name))
+ continue; // already known
+ IdString local_name = construct_local_name(hc, ci->name, true);
+ hc.leaf_cells_by_gname[ci->name] = local_name;
+ hc.leaf_cells[local_name] = ci->name;
+ }
+ }
+};
+} // namespace
+
+void Context::fixupHierarchy() { FixupHierarchyWorker(this).run(); }
+
NEXTPNR_NAMESPACE_END
diff --git a/common/nextpnr.h b/common/nextpnr.h
index bae828f6..61e04415 100644
--- a/common/nextpnr.h
+++ b/common/nextpnr.h
@@ -387,7 +387,7 @@ struct ClockConstraint;
struct NetInfo : ArchNetInfo
{
- IdString name;
+ IdString name, hierpath;
int32_t udata = 0;
PortRef driver;
@@ -397,6 +397,8 @@ struct NetInfo : ArchNetInfo
// wire -> uphill_pip
std::unordered_map<WireId, PipMap> wires;
+ std::vector<IdString> aliases; // entries in net_aliases that point to this net
+
std::unique_ptr<ClockConstraint> clkconstr;
TimingConstrObjectId tmg_id;
@@ -421,7 +423,7 @@ struct PortInfo
struct CellInfo : ArchCellInfo
{
- IdString name, type;
+ IdString name, type, hierpath;
int32_t udata;
std::unordered_map<IdString, PortInfo> ports;
@@ -525,6 +527,31 @@ struct TimingConstraint
std::unordered_set<TimingConstrObjectId> to;
};
+// Represents the contents of a non-leaf cell in a design
+// with hierarchy
+
+struct HierarchicalPort
+{
+ IdString name;
+ PortType dir;
+ std::vector<IdString> nets;
+ int offset;
+ bool upto;
+};
+
+struct HierarchicalCell
+{
+ IdString name, type, parent, fullpath;
+ // Name inside cell instance -> global name
+ std::unordered_map<IdString, IdString> leaf_cells, nets;
+ // Global name -> name inside cell instance
+ std::unordered_map<IdString, IdString> leaf_cells_by_gname, nets_by_gname;
+ // Cell port to net
+ std::unordered_map<IdString, HierarchicalPort> ports;
+ // Name inside cell instance -> global name
+ std::unordered_map<IdString, IdString> hier_cells;
+};
+
inline bool operator==(const std::pair<const TimingConstrObjectId, TimingConstraint *> &a,
const std::pair<TimingConstrObjectId, TimingConstraint *> &b)
{
@@ -618,6 +645,11 @@ struct BaseCtx
std::unordered_map<IdString, std::unique_ptr<NetInfo>> nets;
std::unordered_map<IdString, std::unique_ptr<CellInfo>> cells;
+ // Hierarchical (non-leaf) cells by full path
+ std::unordered_map<IdString, HierarchicalCell> hierarchy;
+ // This is the root of the above structure
+ IdString top_module;
+
// Aliases for nets, which may have more than one name due to assignments and hierarchy
std::unordered_map<IdString, IdString> net_aliases;
@@ -807,6 +839,15 @@ struct Context : Arch, DeterministicRNG
std::unordered_map<WireId, PipId> *route = nullptr, bool useEstimate = true);
// --------------------------------------------------------------
+ // call after changing hierpath or adding/removing nets and cells
+ void fixupHierarchy();
+
+ // --------------------------------------------------------------
+
+ // provided by sdf.cc
+ void writeSDF(std::ostream &out, bool cvc_mode = false) const;
+
+ // --------------------------------------------------------------
uint32_t checksum() const;
diff --git a/common/placer_heap.cc b/common/placer_heap.cc
index e9fc2fb2..01e50123 100644
--- a/common/placer_heap.cc
+++ b/common/placer_heap.cc
@@ -308,6 +308,14 @@ class HeAPPlacer
std::vector<std::vector<int>> nearest_row_with_bel;
std::vector<std::vector<int>> nearest_col_with_bel;
+ struct BoundingBox
+ {
+ // Actual bounding box
+ int x0 = 0, x1 = 0, y0 = 0, y1 = 0;
+ };
+
+ std::unordered_map<IdString, BoundingBox> constraint_region_bounds;
+
// In some cases, we can't use bindBel because we allow overlap in the earlier stages. So we use this custom
// structure instead
struct CellLocation
@@ -443,6 +451,31 @@ class HeAPPlacer
nr.at(y) = loc.y;
}
}
+
+ // Determine bounding boxes of region constraints
+ for (auto &region : sorted(ctx->region)) {
+ Region *r = region.second;
+ BoundingBox bb;
+ if (r->constr_bels) {
+ bb.x0 = std::numeric_limits<int>::max();
+ bb.x1 = std::numeric_limits<int>::min();
+ bb.y0 = std::numeric_limits<int>::max();
+ bb.y1 = std::numeric_limits<int>::min();
+ for (auto bel : r->bels) {
+ Loc loc = ctx->getBelLocation(bel);
+ bb.x0 = std::min(bb.x0, loc.x);
+ bb.x1 = std::max(bb.x1, loc.x);
+ bb.y0 = std::min(bb.y0, loc.y);
+ bb.y1 = std::max(bb.y1, loc.y);
+ }
+ } else {
+ bb.x0 = 0;
+ bb.y0 = 0;
+ bb.x1 = max_x;
+ bb.y1 = max_y;
+ }
+ constraint_region_bounds[r->name] = bb;
+ }
}
// Build and solve in one direction
@@ -684,9 +717,15 @@ class HeAPPlacer
if (yaxis) {
cell_locs.at(solve_cells.at(i)->name).rawy = vals.at(i);
cell_locs.at(solve_cells.at(i)->name).y = std::min(max_y, std::max(0, int(vals.at(i))));
+ if (solve_cells.at(i)->region != nullptr)
+ cell_locs.at(solve_cells.at(i)->name).y =
+ limit_to_reg(solve_cells.at(i)->region, cell_locs.at(solve_cells.at(i)->name).y, true);
} else {
cell_locs.at(solve_cells.at(i)->name).rawx = vals.at(i);
cell_locs.at(solve_cells.at(i)->name).x = std::min(max_x, std::max(0, int(vals.at(i))));
+ if (solve_cells.at(i)->region != nullptr)
+ cell_locs.at(solve_cells.at(i)->name).x =
+ limit_to_reg(solve_cells.at(i)->region, cell_locs.at(solve_cells.at(i)->name).x, false);
}
}
@@ -735,6 +774,7 @@ class HeAPPlacer
}
int ripup_radius = 2;
int total_iters = 0;
+ int total_iters_noreset = 0;
while (!remaining.empty()) {
auto top = remaining.top();
remaining.pop();
@@ -754,15 +794,38 @@ class HeAPPlacer
int best_inp_len = std::numeric_limits<int>::max();
total_iters++;
+ total_iters_noreset++;
if (total_iters > int(solve_cells.size())) {
total_iters = 0;
ripup_radius = std::max(std::max(max_x, max_y), ripup_radius * 2);
}
+ if (total_iters_noreset > std::max(5000, 8 * int(ctx->cells.size()))) {
+ log_error("Unable to find legal placement for all cells, design is probably at utilisation limit.\n");
+ }
+
while (!placed) {
- int nx = ctx->rng(2 * radius + 1) + std::max(cell_locs.at(ci->name).x - radius, 0);
- int ny = ctx->rng(2 * radius + 1) + std::max(cell_locs.at(ci->name).y - radius, 0);
+ // Set a conservative timeout
+ if (iter > std::max(1000, 3 * int(ctx->cells.size())))
+ log_error("Unable to find legal placement for cell '%s', check constraints and utilisation.\n",
+ ctx->nameOf(ci));
+
+ int rx = radius, ry = radius;
+
+ if (ci->region != nullptr) {
+ rx = std::min(radius, (constraint_region_bounds[ci->region->name].x1 -
+ constraint_region_bounds[ci->region->name].x0) /
+ 2 +
+ 1);
+ ry = std::min(radius, (constraint_region_bounds[ci->region->name].y1 -
+ constraint_region_bounds[ci->region->name].y0) /
+ 2 +
+ 1);
+ }
+
+ int nx = ctx->rng(2 * rx + 1) + std::max(cell_locs.at(ci->name).x - rx, 0);
+ int ny = ctx->rng(2 * ry + 1) + std::max(cell_locs.at(ci->name).y - ry, 0);
iter++;
iter_at_radius++;
@@ -820,6 +883,8 @@ class HeAPPlacer
if (ci->constr_children.empty() && !ci->constr_abs_z) {
for (auto sz : fb.at(nx).at(ny)) {
+ if (ci->region != nullptr && ci->region->constr_bels && !ci->region->bels.count(sz))
+ continue;
if (ctx->checkBelAvail(sz) || (radius > ripup_radius || ctx->rng(20000) < 10)) {
CellInfo *bound = ctx->getBoundBelCell(sz);
if (bound != nullptr) {
@@ -881,6 +946,8 @@ class HeAPPlacer
Loc ploc = visit.front().second;
visit.pop();
BelId target = ctx->getBelByLocation(ploc);
+ if (vc->region != nullptr && vc->region->constr_bels && !vc->region->bels.count(target))
+ goto fail;
CellInfo *bound;
if (target == BelId() || ctx->getBelType(target) != vc->type)
goto fail;
@@ -948,6 +1015,15 @@ class HeAPPlacer
// Implementation of the cut-based spreading as described in the HeAP/SimPL papers
static constexpr float beta = 0.9;
+ template <typename T> T limit_to_reg(Region *reg, T val, bool dir)
+ {
+ if (reg == nullptr)
+ return val;
+ int limit_low = dir ? constraint_region_bounds[reg->name].y0 : constraint_region_bounds[reg->name].x0;
+ int limit_high = dir ? constraint_region_bounds[reg->name].y1 : constraint_region_bounds[reg->name].x1;
+ return std::max<T>(std::min<T>(val, limit_high), limit_low);
+ }
+
struct ChainExtent
{
int x0, y0, x1, y1;
@@ -1460,10 +1536,22 @@ class HeAPPlacer
: p->cell_locs.at(cut_cells.at(br.first - 1)->name).rawx;
double m = (br.second - bl.second) / std::max(0.00001, orig_right - orig_left);
for (int j = bl.first; j < br.first; j++) {
- auto &pos = dir ? p->cell_locs.at(cut_cells.at(j)->name).rawy
- : p->cell_locs.at(cut_cells.at(j)->name).rawx;
- NPNR_ASSERT(pos >= orig_left && pos <= orig_right);
- pos = bl.second + m * (pos - orig_left);
+ Region *cr = cut_cells.at(j)->region;
+ if (cr != nullptr) {
+ // Limit spreading bounds to constraint region; if applicable
+ double brsc = p->limit_to_reg(cr, br.second, dir);
+ double blsc = p->limit_to_reg(cr, bl.second, dir);
+ double mr = (brsc - blsc) / std::max(0.00001, orig_right - orig_left);
+ auto &pos = dir ? p->cell_locs.at(cut_cells.at(j)->name).rawy
+ : p->cell_locs.at(cut_cells.at(j)->name).rawx;
+ NPNR_ASSERT(pos >= orig_left && pos <= orig_right);
+ pos = blsc + mr * (pos - orig_left);
+ } else {
+ auto &pos = dir ? p->cell_locs.at(cut_cells.at(j)->name).rawy
+ : p->cell_locs.at(cut_cells.at(j)->name).rawx;
+ NPNR_ASSERT(pos >= orig_left && pos <= orig_right);
+ pos = bl.second + m * (pos - orig_left);
+ }
// log("[%f, %f] -> [%f, %f]: %f -> %f\n", orig_left, orig_right, bl.second, br.second,
// orig_pos, pos);
}
diff --git a/common/pybindings.cc b/common/pybindings.cc
index 03979233..3b2a3744 100644
--- a/common/pybindings.cc
+++ b/common/pybindings.cc
@@ -22,7 +22,7 @@
#include "pybindings.h"
#include "arch_pybindings.h"
-#include "jsonparse.h"
+#include "json_frontend.h"
#include "log.h"
#include "nextpnr.h"
@@ -53,7 +53,7 @@ void parse_json_shim(std::string filename, Context &d)
std::ifstream inf(filename);
if (!inf)
throw std::runtime_error("failed to open file " + filename);
- parse_json_file(inf, filename, &d);
+ parse_json(inf, filename, &d);
}
// Create a new Chip and load design from json file
@@ -131,7 +131,7 @@ BOOST_PYTHON_MODULE(MODULE_NAME)
typedef std::unordered_map<IdString, Property> AttrMap;
typedef std::unordered_map<IdString, PortInfo> PortMap;
- typedef std::unordered_map<IdString, IdString> PinMap;
+ typedef std::unordered_map<IdString, IdString> IdIdMap;
typedef std::unordered_map<IdString, std::unique_ptr<Region>> RegionMap;
class_<BaseCtx, BaseCtx *, boost::noncopyable>("BaseCtx", no_init);
@@ -157,8 +157,8 @@ BOOST_PYTHON_MODULE(MODULE_NAME)
conv_from_str<BelId>>::def_wrap(ci_cls, "bel");
readwrite_wrapper<CellInfo &, decltype(&CellInfo::belStrength), &CellInfo::belStrength, pass_through<PlaceStrength>,
pass_through<PlaceStrength>>::def_wrap(ci_cls, "belStrength");
- readonly_wrapper<CellInfo &, decltype(&CellInfo::pins), &CellInfo::pins, wrap_context<PinMap &>>::def_wrap(ci_cls,
- "pins");
+ readonly_wrapper<CellInfo &, decltype(&CellInfo::pins), &CellInfo::pins, wrap_context<IdIdMap &>>::def_wrap(ci_cls,
+ "pins");
fn_wrapper_1a_v<CellInfo &, decltype(&CellInfo::addInput), &CellInfo::addInput, conv_from_str<IdString>>::def_wrap(
ci_cls, "addInput");
@@ -230,9 +230,25 @@ BOOST_PYTHON_MODULE(MODULE_NAME)
readonly_wrapper<Region &, decltype(&Region::wires), &Region::wires, wrap_context<WireSet &>>::def_wrap(region_cls,
"wires");
+ auto hierarchy_cls = class_<ContextualWrapper<HierarchicalCell &>>("HierarchicalCell", no_init);
+ readwrite_wrapper<HierarchicalCell &, decltype(&HierarchicalCell::name), &HierarchicalCell::name,
+ conv_to_str<IdString>, conv_from_str<IdString>>::def_wrap(hierarchy_cls, "name");
+ readwrite_wrapper<HierarchicalCell &, decltype(&HierarchicalCell::type), &HierarchicalCell::type,
+ conv_to_str<IdString>, conv_from_str<IdString>>::def_wrap(hierarchy_cls, "type");
+ readwrite_wrapper<HierarchicalCell &, decltype(&HierarchicalCell::parent), &HierarchicalCell::parent,
+ conv_to_str<IdString>, conv_from_str<IdString>>::def_wrap(hierarchy_cls, "parent");
+ readwrite_wrapper<HierarchicalCell &, decltype(&HierarchicalCell::fullpath), &HierarchicalCell::fullpath,
+ conv_to_str<IdString>, conv_from_str<IdString>>::def_wrap(hierarchy_cls, "fullpath");
+
+ readonly_wrapper<HierarchicalCell &, decltype(&HierarchicalCell::leaf_cells), &HierarchicalCell::leaf_cells,
+ wrap_context<IdIdMap &>>::def_wrap(hierarchy_cls, "leaf_cells");
+ readonly_wrapper<HierarchicalCell &, decltype(&HierarchicalCell::nets), &HierarchicalCell::nets,
+ wrap_context<IdIdMap &>>::def_wrap(hierarchy_cls, "nets");
+ readonly_wrapper<HierarchicalCell &, decltype(&HierarchicalCell::hier_cells), &HierarchicalCell::hier_cells,
+ wrap_context<IdIdMap &>>::def_wrap(hierarchy_cls, "hier_cells");
WRAP_MAP(AttrMap, conv_to_str<Property>, "AttrMap");
WRAP_MAP(PortMap, wrap_context<PortInfo &>, "PortMap");
- WRAP_MAP(PinMap, conv_to_str<IdString>, "PinMap");
+ WRAP_MAP(IdIdMap, conv_to_str<IdString>, "IdIdMap");
WRAP_MAP(WireMap, wrap_context<PipMap &>, "WireMap");
WRAP_MAP_UPTR(RegionMap, "RegionMap");
diff --git a/common/sdf.cc b/common/sdf.cc
new file mode 100644
index 00000000..b9606907
--- /dev/null
+++ b/common/sdf.cc
@@ -0,0 +1,334 @@
+/*
+ * nextpnr -- Next Generation Place and Route
+ *
+ * Copyright (C) 2019 David Shah <dave@ds0.me>
+ *
+ * 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 "nextpnr.h"
+#include "util.h"
+
+NEXTPNR_NAMESPACE_BEGIN
+
+namespace SDF {
+
+struct MinMaxTyp
+{
+ double min, typ, max;
+};
+
+struct RiseFallDelay
+{
+ MinMaxTyp rise, fall;
+};
+
+struct PortAndEdge
+{
+ std::string port;
+ ClockEdge edge;
+};
+
+struct IOPath
+{
+ std::string from, to;
+ RiseFallDelay delay;
+};
+
+struct TimingCheck
+{
+ enum CheckType
+ {
+ SETUPHOLD,
+ PERIOD,
+ WIDTH
+ } type;
+ PortAndEdge from, to;
+ RiseFallDelay delay;
+};
+
+struct Cell
+{
+ std::string celltype, instance;
+ std::vector<IOPath> iopaths;
+ std::vector<TimingCheck> checks;
+};
+
+struct CellPort
+{
+ std::string cell, port;
+};
+
+struct Interconnect
+{
+ CellPort from, to;
+ RiseFallDelay delay;
+};
+
+struct SDFWriter
+{
+ bool cvc_mode = false;
+ std::vector<Cell> cells;
+ std::vector<Interconnect> conn;
+ std::string sdfversion, design, vendor, program;
+
+ std::string format_name(const std::string &name)
+ {
+ std::string fmt = "\"";
+ for (char c : name) {
+ if (c == '\\' || c == '\"')
+ fmt += "\"";
+ fmt += c;
+ }
+ fmt += "\"";
+ return fmt;
+ }
+
+ std::string escape_name(const std::string &name)
+ {
+ std::string esc;
+ for (char c : name) {
+ if (c == '$' || c == '\\' || c == '[' || c == ']' || c == ':' || (cvc_mode && c == '.'))
+ esc += '\\';
+ esc += c;
+ }
+ return esc;
+ }
+
+ std::string timing_check_name(TimingCheck::CheckType type)
+ {
+ switch (type) {
+ case TimingCheck::SETUPHOLD:
+ return "SETUPHOLD";
+ case TimingCheck::PERIOD:
+ return "PERIOD";
+ case TimingCheck::WIDTH:
+ return "WIDTH";
+ default:
+ NPNR_ASSERT_FALSE("unknown timing check type");
+ }
+ }
+
+ void write_delay(std::ostream &out, const RiseFallDelay &delay)
+ {
+ write_delay(out, delay.rise);
+ out << " ";
+ write_delay(out, delay.fall);
+ }
+
+ void write_delay(std::ostream &out, const MinMaxTyp &delay)
+ {
+ if (cvc_mode)
+ out << "(" << int(delay.min) << ":" << int(delay.typ) << ":" << int(delay.max) << ")";
+ else
+ out << "(" << delay.min << ":" << delay.typ << ":" << delay.max << ")";
+ }
+
+ void write_port(std::ostream &out, const CellPort &port)
+ {
+ if (cvc_mode)
+ out << escape_name(port.cell) + "." + escape_name(port.port);
+ else
+ out << escape_name(port.cell + "/" + port.port);
+ }
+
+ void write_portedge(std::ostream &out, const PortAndEdge &pe)
+ {
+ out << "(" << (pe.edge == RISING_EDGE ? "posedge" : "negedge") << " " << escape_name(pe.port) << ")";
+ }
+
+ void write(std::ostream &out)
+ {
+ out << "(DELAYFILE" << std::endl;
+ // Headers and metadata
+ out << " (SDFVERSION " << format_name(sdfversion) << ")" << std::endl;
+ out << " (DESIGN " << format_name(design) << ")" << std::endl;
+ out << " (VENDOR " << format_name(vendor) << ")" << std::endl;
+ out << " (PROGRAM " << format_name(program) << ")" << std::endl;
+ out << " (DIVIDER " << (cvc_mode ? "." : "/") << ")" << std::endl;
+ out << " (TIMESCALE 1ps)" << std::endl;
+ // Write interconnect delays, with the main design begin a "cell"
+ out << " (CELL" << std::endl;
+ out << " (CELLTYPE " << format_name(design) << ")" << std::endl;
+ out << " (INSTANCE )" << std::endl;
+ out << " (DELAY" << std::endl;
+ out << " (ABSOLUTE" << std::endl;
+ for (auto &ic : conn) {
+ out << " (INTERCONNECT ";
+ write_port(out, ic.from);
+ out << " ";
+ write_port(out, ic.to);
+ out << " ";
+ write_delay(out, ic.delay);
+ out << ")" << std::endl;
+ }
+ out << " )" << std::endl;
+ out << " )" << std::endl;
+ out << " )" << std::endl;
+ // Write cells
+ for (auto &cell : cells) {
+ out << " (CELL" << std::endl;
+ out << " (CELLTYPE " << format_name(cell.celltype) << ")" << std::endl;
+ out << " (INSTANCE " << escape_name(cell.instance) << ")" << std::endl;
+ // IOPATHs (combinational delay and clock-to-q)
+ if (!cell.iopaths.empty()) {
+ out << " (DELAY" << std::endl;
+ out << " (ABSOLUTE" << std::endl;
+ for (auto &path : cell.iopaths) {
+ out << " (IOPATH " << escape_name(path.from) << " " << escape_name(path.to) << " ";
+ write_delay(out, path.delay);
+ out << ")" << std::endl;
+ }
+ out << " )" << std::endl;
+ out << " )" << std::endl;
+ }
+ // Timing Checks (setup/hold, period, width)
+ if (!cell.checks.empty()) {
+ out << " (TIMINGCHECK" << std::endl;
+ for (auto &check : cell.checks) {
+ out << " (" << timing_check_name(check.type) << " ";
+ write_portedge(out, check.from);
+ out << " ";
+ if (check.type == TimingCheck::SETUPHOLD) {
+ write_portedge(out, check.to);
+ out << " ";
+ }
+ if (check.type == TimingCheck::SETUPHOLD)
+ write_delay(out, check.delay);
+ else
+ write_delay(out, check.delay.rise);
+ out << ")" << std::endl;
+ }
+ out << " )" << std::endl;
+ }
+ out << " )" << std::endl;
+ }
+ out << ")" << std::endl;
+ }
+};
+
+} // namespace SDF
+
+void Context::writeSDF(std::ostream &out, bool cvc_mode) const
+{
+ using namespace SDF;
+ SDFWriter wr;
+ wr.cvc_mode = cvc_mode;
+ wr.design = str_or_default(attrs, id("module"), "top");
+ wr.sdfversion = "3.0";
+ wr.vendor = "nextpnr";
+ wr.program = "nextpnr";
+
+ const double delay_scale = 1000;
+ // Convert from DelayInfo to SDF-friendly RiseFallDelay
+ auto convert_delay = [&](const DelayInfo &dly) {
+ RiseFallDelay rf;
+ rf.rise.min = getDelayNS(dly.minRaiseDelay()) * delay_scale;
+ rf.rise.typ = getDelayNS((dly.minRaiseDelay() + dly.maxRaiseDelay()) / 2) * delay_scale; // fixme: typ delays?
+ rf.rise.max = getDelayNS(dly.maxRaiseDelay()) * delay_scale;
+ rf.fall.min = getDelayNS(dly.minFallDelay()) * delay_scale;
+ rf.fall.typ = getDelayNS((dly.minFallDelay() + dly.maxFallDelay()) / 2) * delay_scale; // fixme: typ delays?
+ rf.fall.max = getDelayNS(dly.maxFallDelay()) * delay_scale;
+ return rf;
+ };
+
+ auto convert_setuphold = [&](const DelayInfo &setup, const DelayInfo &hold) {
+ RiseFallDelay rf;
+ rf.rise.min = getDelayNS(setup.minDelay()) * delay_scale;
+ rf.rise.typ = getDelayNS((setup.minDelay() + setup.maxDelay()) / 2) * delay_scale; // fixme: typ delays?
+ rf.rise.max = getDelayNS(setup.maxDelay()) * delay_scale;
+ rf.fall.min = getDelayNS(hold.minDelay()) * delay_scale;
+ rf.fall.typ = getDelayNS((hold.minDelay() + hold.maxDelay()) / 2) * delay_scale; // fixme: typ delays?
+ rf.fall.max = getDelayNS(hold.maxDelay()) * delay_scale;
+ return rf;
+ };
+
+ for (auto cell : sorted(cells)) {
+ Cell sc;
+ const CellInfo *ci = cell.second;
+ sc.instance = ci->name.str(this);
+ sc.celltype = ci->type.str(this);
+ for (auto port : ci->ports) {
+ int clockCount = 0;
+ TimingPortClass cls = getPortTimingClass(ci, port.first, clockCount);
+ if (cls == TMG_IGNORE)
+ continue;
+ if (port.second.net == nullptr)
+ continue; // Ignore disconnected ports
+ if (port.second.type != PORT_IN) {
+ // Add combinational paths to this output (or inout)
+ for (auto other : ci->ports) {
+ if (other.second.net == nullptr)
+ continue;
+ if (other.second.type == PORT_OUT)
+ continue;
+ DelayInfo dly;
+ if (!getCellDelay(ci, other.first, port.first, dly))
+ continue;
+ IOPath iop;
+ iop.from = other.first.str(this);
+ iop.to = port.first.str(this);
+ iop.delay = convert_delay(dly);
+ sc.iopaths.push_back(iop);
+ }
+ // Add clock-to-output delays, also as IOPaths
+ if (cls == TMG_REGISTER_OUTPUT)
+ for (int i = 0; i < clockCount; i++) {
+ auto clkInfo = getPortClockingInfo(ci, port.first, i);
+ IOPath cqp;
+ cqp.from = clkInfo.clock_port.str(this);
+ cqp.to = port.first.str(this);
+ cqp.delay = convert_delay(clkInfo.clockToQ);
+ sc.iopaths.push_back(cqp);
+ }
+ }
+ if (port.second.type != PORT_OUT && cls == TMG_REGISTER_INPUT) {
+ // Add setup/hold checks
+ for (int i = 0; i < clockCount; i++) {
+ auto clkInfo = getPortClockingInfo(ci, port.first, i);
+ TimingCheck chk;
+ chk.from.edge = RISING_EDGE; // Add setup/hold checks equally for rising and falling edges
+ chk.from.port = port.first.str(this);
+ chk.to.edge = clkInfo.edge;
+ chk.to.port = clkInfo.clock_port.str(this);
+ chk.type = TimingCheck::SETUPHOLD;
+ chk.delay = convert_setuphold(clkInfo.setup, clkInfo.hold);
+ sc.checks.push_back(chk);
+ chk.from.edge = FALLING_EDGE;
+ sc.checks.push_back(chk);
+ }
+ }
+ }
+ wr.cells.push_back(sc);
+ }
+
+ for (auto net : sorted(nets)) {
+ NetInfo *ni = net.second;
+ if (ni->driver.cell == nullptr)
+ continue;
+ for (auto &usr : ni->users) {
+ Interconnect ic;
+ ic.from.cell = ni->driver.cell->name.str(this);
+ ic.from.port = ni->driver.port.str(this);
+ ic.to.cell = usr.cell->name.str(this);
+ ic.to.port = usr.port.str(this);
+ // FIXME: min/max routing delay - or at least constructing DelayInfo here
+ ic.delay = convert_delay(getDelayFromNS(getDelayNS(getNetinfoRouteDelay(ni, usr))));
+ wr.conn.push_back(ic);
+ }
+ }
+ wr.write(out);
+}
+
+NEXTPNR_NAMESPACE_END \ No newline at end of file
diff --git a/common/timing.cc b/common/timing.cc
index 37600c8c..4e84fffe 100644
--- a/common/timing.cc
+++ b/common/timing.cc
@@ -434,8 +434,7 @@ struct Timing
int port_clocks;
TimingPortClass portClass =
ctx->getPortTimingClass(crit_net->driver.cell, port.first, port_clocks);
- if (portClass == TMG_CLOCK_INPUT || portClass == TMG_ENDPOINT || portClass == TMG_IGNORE ||
- portClass == TMG_REGISTER_INPUT)
+ if (portClass == TMG_CLOCK_INPUT || portClass == TMG_ENDPOINT || portClass == TMG_IGNORE)
continue;
// And find the fanin net with the latest arrival time
if (net_data.count(port.second.net) &&
diff --git a/docs/faq.md b/docs/faq.md
index 7b358187..fe0c7231 100644
--- a/docs/faq.md
+++ b/docs/faq.md
@@ -132,9 +132,8 @@ Nextpnr and other tools
### Which toolchain should I use and why?
- * If you wish to do new **research** into FPGA architectures, place and route
- algorithms or other similar topics, we suggest you look at using
- [Verilog to Routing](https://verilogtorouting.org).
+ * If you wish to do new **research** into FPGA architectures, or other similar topics, we suggest you look at using
+ [Verilog to Routing](https://verilogtorouting.org). If you want to use nextpnr, you might also be able to use the [Generic Arch](generic.md).
* If you are developing FPGA code in **Verilog** for a **Lattice iCE40** and
need an open source toolchain, we suggest you use [Yosys](http://www.clifford.at/yosys/) and nextpnr.
@@ -144,14 +143,9 @@ Nextpnr and other tools
migrating to nextpnr.
* If you are developing Verilog FPGA code targeted at the Lattice ECP5 and
- need an open source toolchain, you may consider the **extremely
- experimental** ECP5 support in Yosys and nextpnr.
+ need an open source toolchain, there is also stable ECP5 support in Yosys and nextpnr.
- * If you are developing FPGA code in **VHDL** you will need to use either a
- version of [Yosys with Verific support](https://github.com/YosysHQ/yosys/tree/master/frontends/verific) or the vendor provided tools due
- to the lack of useful open source VHDL support in Yosys. You could also look at developing
- one of the experimental open source VHDL frontends, such as [yavhdl](https://github.com/rqou/yavhdl)
- or [ghdlsynth-beta](https://github.com/tgingold/ghdlsynth-beta), further.
+ * If you are developing FPGA code in **VHDL** you may wish to look at the [ghdlsynth-beta](https://github.com/tgingold/ghdlsynth-beta) experimental VHDL frontend for Yosys.
### Why didn't you just improve [arachne-pnr](https://github.com/cseed/arachne-pnr)?
@@ -162,11 +156,9 @@ that actually produced valid bitstreams.
For its original purpose, it has served the community extremely well. However,
it was never designed to support multiple different FPGA families, nor more
-complicated timing driven placement and routing used by most commercial place and route
-tools.
+complicated timing driven placement and routing used by most commercial place and route tools.
-It felt like extending arachne-pnr was not going to be the best path forward, so
-it was decided to build nextpnr as replacement.
+It felt like extending arachne-pnr was not going to be the best path forward, so it was decided to build nextpnr as replacement.
### arachne-pnr does X better!
@@ -174,7 +166,8 @@ If you have a use case which prevents you from switching to nextpnr from
arachne, we want to hear about it! Please create an issue and we will do our best to solve the problem!
We want nextpnr to be a suitable replacement for anyone who is currently a user
-of arachne-pnr.
+of arachne-pnr, and it is important to bear in mind that arachne-pnr is no
+longer in active development.
### Why are you not just contributing to [Verilog to Routing](https://verilogtorouting.org)?
@@ -191,8 +184,7 @@ for current FPGAs.
We also believe that support for real architectures will enable interesting new
research. nextpnr (like all place and route tools) depends heavily on
-research groups like the VtR developers to investigate and push forward FPGA placement and routing
-algorithms in new and exciting ways.
+research groups like the VtR developers to investigate and push forward FPGA placement and routing algorithms in new and exciting ways.
#### What is VPR?
@@ -220,15 +212,14 @@ enable support for creation of bitstreams for these parts.
the bitstream format for the Xilinx Series 7 series of FPGAs. It also includes
tooling around bitstream generation for these parts.
-While nextpnr currently does **not** support these Xilinx parts, we expect it
-will soon be using Project X-Ray in a similar manner to Project Trellis.
+While upstream nextpnr currently does **not** support these Xilinx parts, we expect it might soon be using Project X-Ray in a similar manner to Project Trellis.
### What is [Project IceStorm](http://www.clifford.at/icestorm/)?
[Project IceStorm](http://www.clifford.at/icestorm/) is both a project to
document the bitstream for the Lattice iCE40 series of parts **and** a full
-flow including Yosys and arachne-pnr for converting Verilog into a bitstream for
-these parts.
+flow including Yosys and arachne-pnr for converting Verilog into a bitstream
+for these parts.
As the open source community now has support for multiple different FPGA parts,
in the nextpnr documentation we generally use Project IceStorm to mean the database and
diff --git a/docs/generic.md b/docs/generic.md
index d6ddbfb6..a635f98c 100644
--- a/docs/generic.md
+++ b/docs/generic.md
@@ -72,7 +72,7 @@ Sets the number of input pins a LUT in the architecture has. Only affects the ge
### void setDelayScaling(double scale, double offset);
-Set the linear scaling vs distance and fixed offset (both values in nanoseconds) for routing delay estimates.
+Set the linear scaling vs distance and fixed offset (both values in nanoseconds) for routing delay estimates. Delay estimates that correlate to pip delays, even if they have no bearing to reality, are important for reasonable routing runtime.
### void addCellTimingClock(IdString cell, IdString port);
@@ -96,17 +96,19 @@ Specify clock-to-out time for a port of a cell, and set the timing class of that
## Generic Packer
-The generic packer combines K-input LUTs (`LUT` cells) and simple D-type flip flops (`DFF` cells) (posedge clock only, no set/reset or enable) into a `GENERIC_SLICE` cell. It also inserts `GENERIC_IOB`s onto any top level IO pins without an IO buffer.
+The generic packer combines K-input LUTs (`LUT` cells) and simple D-type flip flops (`DFF` cells) (posedge clock only, no set/reset or enable) into a `GENERIC_SLICE` cell. It also inserts `GENERIC_IOB`s onto any top level IO pins without an IO buffer. Constrained IOBs can be implemented by instantiating `GENERIC_IOB` and setting the `BEL` attribute to an IO location.
Thus, the architecture should provide bels with the following ports in order to use the generic packer:
- - `GENERIC_SLICE` bels with `CLK` input, `I[0]` .. `I[K-1]` LUT inputs and `Q` LUT/FF output (N.B. both LUT and FF outputs are not available at the same time)
+ - `GENERIC_SLICE` bels with `CLK` input, `I[0]` .. `I[K-1]` LUT inputs, `F` LUT output and `Q` FF output (N.B. both LUT and FF outputs are not available at the same time, to represent the constraints of some FPGAs).
- `GENERIC_IOB` bels with `I` output buffer input, `EN` output enable input, and `O` input buffer output.
See [prims.v](../generic/synth/prims.v) for Verilog simulation models for all these cells.
[synth_generic.tcl](../generic/synth/synth_generic.tcl) can be used with Yosys to perform synthesis to the generic `LUT` and `DFF` cells which the generic packer supports. Invoke it using `tcl synth_generic.tcl K out.json` where _K_ is the number of LUT inputs and _out.json_ the name of the JSON file to write.
+The generic packer in its current state is intended for experimentation and proof-of-concept tests. It is _not_ intended to make use of all FPGA features or support complex designs. In these cases a proper [Arch API](archapi.md) implementation is strongly recommended.
+
## Validity Checks
The following constraints are enforced by the generic architecture during placement.
diff --git a/docs/netlist.md b/docs/netlist.md
index 0f9a8969..3953241e 100644
--- a/docs/netlist.md
+++ b/docs/netlist.md
@@ -19,6 +19,7 @@ Other structures used by these basic structures include:
`CellInfo` instances have the following fields:
- `name` and `type` are `IdString`s containing the instance name, and type
+ - `hierpath` is name of the hierarchical cell containing the instance, for designs with hierarchy
- `ports` is a map from port name `IdString` to `PortInfo` structures for each cell port
- `bel` and `belStrength` contain the ID of the Bel the cell is placed onto; and placement strength of the cell; if placed. Placement/ripup should always be done by `Arch::bindBel` and `Arch::unbindBel` rather than by manipulating these fields.
- `params` and `attrs` store parameters and attributes - from the input JSON or assigned in flows to add metadata - by mapping from parameter name `IdString` to `Property`.
@@ -34,6 +35,7 @@ Other structures used by these basic structures include:
`NetInfo` instances have the following fields:
- `name` is the IdString name of the net - for nets with multiple names, one name is chosen according to a set of rules by the JSON frontend
+ - `hierpath` is name of the hierarchical cell containing the instance, for designs with hierarchy
- `driver` refers to the source of the net using `PortRef`; `driver.cell == nullptr` means that the net is undriven. Nets must have zero or one driver only. The corresponding cell port must be an output and its `PortInfo::net` must refer back to this net.
- `users` contains a list of `PortRef` references to sink ports on the net. Nets can have zero or more sinks. Each corresponding cell port must be an input or inout; and its `PortInfo::net` must refer back to this net.
- `wires` is a map that stores the routing tree of a net, if the net is routed.
@@ -70,4 +72,18 @@ The second is `ArchCellInfo` and `ArchNetInfo`. These are provided by architectu
- `getNetinfoSourceWire` gets the physical wire `WireId` associated with the source of a net
- `getNetinfoSinkWire` gets the physical wire `WireId` associated with a given sink (specified by `PortRef`)
- `getNetinfoRouteDelay` gets the routing delay - actual if the net is fully routed, estimated otherwise - between the source and a given sink of a net
- - `getNetByAlias` returns the pointer to a net given any of its aliases - this should be used in preference to a direct lookup in `nets` whenever a net name is provided by the user \ No newline at end of file
+ - `getNetByAlias` returns the pointer to a net given any of its aliases - this should be used in preference to a direct lookup in `nets` whenever a net name is provided by the user
+
+## Hierarchy
+
+As most place and route algorithms require a flattened netlist to work with (consider - each leaf cell instance must have its own bel), the primary netlist structures are flattened. However, some tasks such as floorplanning require an understanding of hierarchy.
+
+`HierarchicalCell` is the main data structure for storing hierarchy. This represents an instance of a hierarchical, rather than leaf cell (leaf cells are represented by a `CellInfo`).
+
+ - `name` and `type` are the instance name and cell type
+ - `parent` is the hierarchical path of the parent cell, and `fullpath` is the hierarchical path of this cell
+ - `leaf_cells`, `nets` map from a name inside the hierarchical cell to a 'global' name in the flattened netlist (i.e. one that indexes into `ctx->{cells,nets}`)
+ - `leaf_cells_by_gname`, `nets_by_gname` are the inverse of the above maps; going from `{CellInfo,NetInfo}::name` to an instance name inside the cell
+ - `hier_cells` maps instance names of sub-hierarchical (non-leaf) cells to global names (indexing into `ctx->hierarchy`)
+
+To preserve hierarchy during passes such as packing, ensure that `hierpath` is set on new cells derived from existing ones, and call `fixupHierarchy()` at the end to rebuild `HierarchicalCell` structures.
diff --git a/ecp5/arch.cc b/ecp5/arch.cc
index e678d3b3..994e8660 100644
--- a/ecp5/arch.cc
+++ b/ecp5/arch.cc
@@ -117,6 +117,9 @@ Arch::Arch(ArchArgs args) : args(args)
log_error("Unsupported ECP5 chip type.\n");
}
#endif
+ if (chip_info->const_id_count != DB_CONST_ID_COUNT)
+ log_error("Chip database 'bba' and nextpnr code are out of sync; please rebuild (or contact distribution "
+ "maintainer)!\n");
package_info = nullptr;
for (int i = 0; i < chip_info->num_packages; i++) {
if (args.package == chip_info->package_info[i].name.get()) {
@@ -475,7 +478,13 @@ delay_t Arch::estimateDelay(WireId src, WireId dst) const
}
};
- auto src_loc = est_location(src), dst_loc = est_location(dst);
+ auto src_loc = est_location(src);
+ std::pair<int, int> dst_loc;
+ if (wire_loc_overrides.count(dst)) {
+ dst_loc = wire_loc_overrides.at(dst);
+ } else {
+ dst_loc = est_location(dst);
+ }
int dx = abs(src_loc.first - dst_loc.first), dy = abs(src_loc.second - dst_loc.second);
@@ -560,6 +569,7 @@ bool Arch::place()
bool Arch::route()
{
+ setupWireLocations();
route_ecp5_globals(getCtx());
assignArchInfo();
assign_budget(getCtx(), true);
diff --git a/ecp5/arch.h b/ecp5/arch.h
index a0254965..b3e36e52 100644
--- a/ecp5/arch.h
+++ b/ecp5/arch.h
@@ -196,6 +196,7 @@ NPNR_PACKED_STRUCT(struct ChipInfoPOD {
int32_t num_tiles;
int32_t num_location_types;
int32_t num_packages, num_pios;
+ int32_t const_id_count;
RelPtr<LocationTypePOD> locations;
RelPtr<int32_t> location_type;
RelPtr<GlobalInfoPOD> location_glbinfo;
@@ -1034,7 +1035,7 @@ struct Arch : BaseCtx
if (chip_info->tiletype_names[tileloc.tile_names[j].type_idx].get() == type)
return tileloc.tile_names[j].name.get();
}
- NPNR_ASSERT_FALSE_STR("no with type " + type);
+ NPNR_ASSERT_FALSE_STR("no tile with type " + type);
}
GlobalInfoPOD globalInfoAtLoc(Location loc);
@@ -1054,6 +1055,11 @@ struct Arch : BaseCtx
// Special case for delay estimates due to its physical location
// being far from the logical location of its primitive
WireId gsrclk_wire;
+ // Improves directivity of routing to DSP inputs, avoids issues
+ // with different routes to the same physical reset wire causing
+ // conflicts and slow routing
+ std::unordered_map<WireId, std::pair<int, int>> wire_loc_overrides;
+ void setupWireLocations();
mutable std::unordered_map<DelayKey, std::pair<bool, DelayInfo>> celldelay_cache;
diff --git a/ecp5/arch_place.cc b/ecp5/arch_place.cc
index d5c345af..6057605b 100644
--- a/ecp5/arch_place.cc
+++ b/ecp5/arch_place.cc
@@ -196,4 +196,28 @@ void Arch::permute_luts()
}
}
+void Arch::setupWireLocations()
+{
+ wire_loc_overrides.clear();
+ for (auto cell : sorted(cells)) {
+ CellInfo *ci = cell.second;
+ if (ci->bel == BelId())
+ continue;
+ if (ci->type == id_MULT18X18D || ci->type == id_DCUA) {
+ for (auto &port : ci->ports) {
+ if (port.second.type != PORT_IN || port.second.net == nullptr)
+ continue;
+ WireId pw = getBelPinWire(ci->bel, port.first);
+ if (pw == WireId())
+ continue;
+ for (auto uh : getPipsUphill(pw)) {
+ WireId pip_src = getPipSrcWire(uh);
+ wire_loc_overrides[pw] = std::make_pair(pip_src.location.x, pip_src.location.y);
+ break;
+ }
+ }
+ }
+ }
+}
+
NEXTPNR_NAMESPACE_END
diff --git a/ecp5/arch_pybindings.cc b/ecp5/arch_pybindings.cc
index da6d3e50..cd5e31c3 100644
--- a/ecp5/arch_pybindings.cc
+++ b/ecp5/arch_pybindings.cc
@@ -49,6 +49,7 @@ void arch_wrap_python()
typedef std::unordered_map<IdString, std::unique_ptr<CellInfo>> CellMap;
typedef std::unordered_map<IdString, std::unique_ptr<NetInfo>> NetMap;
typedef std::unordered_map<IdString, IdString> AliasMap;
+ typedef std::unordered_map<IdString, HierarchicalCell> HierarchyMap;
auto belpin_cls = class_<ContextualWrapper<BelPin>>("BelPin", no_init);
readonly_wrapper<BelPin, decltype(&BelPin::bel), &BelPin::bel, conv_to_str<BelId>>::def_wrap(belpin_cls, "bel");
@@ -64,6 +65,7 @@ void arch_wrap_python()
WRAP_MAP_UPTR(CellMap, "IdCellMap");
WRAP_MAP_UPTR(NetMap, "IdNetMap");
+ WRAP_MAP(HierarchyMap, wrap_context<HierarchicalCell &>, "HierarchyMap");
}
NEXTPNR_NAMESPACE_END
diff --git a/ecp5/archdefs.h b/ecp5/archdefs.h
index d155d672..b176eec0 100644
--- a/ecp5/archdefs.h
+++ b/ecp5/archdefs.h
@@ -52,17 +52,23 @@ struct DelayInfo
// -----------------------------------------------------------------------
+// https://bugreports.qt.io/browse/QTBUG-80789
+
+#ifndef Q_MOC_RUN
enum ConstIds
{
ID_NONE
#define X(t) , ID_##t
#include "constids.inc"
#undef X
+ ,
+ DB_CONST_ID_COUNT
};
#define X(t) static constexpr auto id_##t = IdString(ID_##t);
#include "constids.inc"
#undef X
+#endif
NPNR_PACKED_STRUCT(struct LocationPOD { int16_t x, y; });
diff --git a/ecp5/bitstream.cc b/ecp5/bitstream.cc
index acab95dd..66bd639a 100644
--- a/ecp5/bitstream.cc
+++ b/ecp5/bitstream.cc
@@ -759,6 +759,10 @@ void write_bitstream(Context *ctx, std::string base_config_file, std::string tex
str_or_default(ci->params, ctx->id("REG0_REGSET"), "RESET"));
cc.tiles[tname].add_enum(slice + ".REG1.REGSET",
str_or_default(ci->params, ctx->id("REG1_REGSET"), "RESET"));
+ cc.tiles[tname].add_enum(slice + ".REG0.LSRMODE",
+ str_or_default(ci->params, ctx->id("REG0_LSRMODE"), "LSR"));
+ cc.tiles[tname].add_enum(slice + ".REG1.LSRMODE",
+ str_or_default(ci->params, ctx->id("REG1_LSRMODE"), "LSR"));
cc.tiles[tname].add_enum(slice + ".CEMUX", str_or_default(ci->params, ctx->id("CEMUX"), "1"));
if (ci->sliceInfo.using_dff) {
@@ -865,6 +869,16 @@ void write_bitstream(Context *ctx, std::string base_config_file, std::string tex
if (ci->attrs.count(ctx->id("DIFFRESISTOR")))
cc.tiles[pio_tile].add_enum(pio + ".DIFFRESISTOR",
str_or_default(ci->attrs, ctx->id("DIFFRESISTOR"), "OFF"));
+ if (ci->attrs.count(ctx->id("DRIVE"))) {
+ static bool drive_3v3_warning_done = false;
+ if (iotype == "LVCMOS33") {
+ cc.tiles[pio_tile].add_enum(pio + ".DRIVE", str_or_default(ci->attrs, ctx->id("DRIVE"), "8"));
+ } else {
+ if (!drive_3v3_warning_done)
+ log_warning("Trellis limitation: DRIVE can only be set on 3V3 IO pins.\n");
+ drive_3v3_warning_done = true;
+ }
+ }
if (ci->attrs.count(ctx->id("TERMINATION"))) {
auto vccio = get_vccio(ioType_from_str(iotype));
switch (vccio) {
@@ -1283,6 +1297,9 @@ void write_bitstream(Context *ctx, std::string base_config_file, std::string tex
else
cc.tiles[pic_tile].add_enum(prim + "." + param.first.str(ctx), param.second.as_string());
}
+ if (get_net_or_empty(ci, id_LOADN) != nullptr) {
+ cc.tiles[pic_tile].add_enum(prim + ".LOADNMUX", "LOADN");
+ }
} else if (ci->type == id_DCUA) {
TileGroup tg;
tg.tiles = get_dcu_tiles(ctx, ci->bel);
@@ -1384,8 +1401,9 @@ void write_bitstream(Context *ctx, std::string base_config_file, std::string tex
Loc loc = ctx->getBelLocation(ci->bel);
bool u = loc.y<15, r = loc.x> 15;
std::string tiletype = fmt_str("DDRDLL_" << (u ? 'U' : 'L') << (r ? 'R' : 'L'));
- if (ctx->args.type == ArchArgs::LFE5U_25F || ctx->args.type == ArchArgs::LFE5UM_25F ||
- ctx->args.type == ArchArgs::LFE5UM5G_25F)
+ if ((ctx->args.type == ArchArgs::LFE5U_25F || ctx->args.type == ArchArgs::LFE5UM_25F ||
+ ctx->args.type == ArchArgs::LFE5UM5G_25F) &&
+ u)
tiletype += "A";
std::string tile = ctx->getTileByType(tiletype);
cc.tiles[tile].add_enum("DDRDLL.MODE", "DDRDLLA");
diff --git a/ecp5/cells.cc b/ecp5/cells.cc
index 37b6ac8b..c630c2c3 100644
--- a/ecp5/cells.cc
+++ b/ecp5/cells.cc
@@ -233,6 +233,8 @@ static void replace_port_safe(bool has_ff, CellInfo *ff, IdString ff_port, CellI
void ff_to_slice(Context *ctx, CellInfo *ff, CellInfo *lc, int index, bool driven_by_lut)
{
+ if (lc->hierpath == IdString())
+ lc->hierpath = ff->hierpath;
bool has_ff = lc->ports.at(ctx->id("Q0")).net != nullptr || lc->ports.at(ctx->id("Q1")).net != nullptr;
std::string reg = "REG" + std::to_string(index);
set_param_safe(has_ff, lc, ctx->id("SRMODE"), str_or_default(ff->params, ctx->id("SRMODE"), "LSR_OVER_CE"));
@@ -243,6 +245,7 @@ void ff_to_slice(Context *ctx, CellInfo *ff, CellInfo *lc, int index, bool drive
lc->params[ctx->id(reg + "_SD")] = std::string(driven_by_lut ? "1" : "0");
lc->params[ctx->id(reg + "_REGSET")] = str_or_default(ff->params, ctx->id("REGSET"), "RESET");
+ lc->params[ctx->id(reg + "_LSRMODE")] = str_or_default(ff->params, ctx->id("LSRMODE"), "LSR");
replace_port_safe(has_ff, ff, ctx->id("CLK"), lc, ctx->id("CLK"));
if (ff->ports.find(ctx->id("LSR")) != ff->ports.end())
replace_port_safe(has_ff, ff, ctx->id("LSR"), lc, ctx->id("LSR"));
@@ -250,15 +253,28 @@ void ff_to_slice(Context *ctx, CellInfo *ff, CellInfo *lc, int index, bool drive
replace_port_safe(has_ff, ff, ctx->id("CE"), lc, ctx->id("CE"));
replace_port(ff, ctx->id("Q"), lc, ctx->id("Q" + std::to_string(index)));
- if (driven_by_lut) {
- replace_port(ff, ctx->id("DI"), lc, ctx->id("DI" + std::to_string(index)));
+ if (get_net_or_empty(ff, ctx->id("M")) != nullptr) {
+ // PRLD FFs that use both M and DI
+ NPNR_ASSERT(!driven_by_lut);
+ // As M is used; must route DI through a new LUT
+ lc->params[ctx->id(reg + "_SD")] = std::string("1");
+ lc->params[ctx->id("LUT" + std::to_string(index) + "_INITVAL")] = Property(0xFF00, 16);
+ replace_port(ff, ctx->id("DI"), lc, ctx->id("D" + std::to_string(index)));
+ replace_port(ff, ctx->id("M"), lc, ctx->id("M" + std::to_string(index)));
+ connect_ports(ctx, lc, ctx->id("F" + std::to_string(index)), lc, ctx->id("DI" + std::to_string(index)));
} else {
- replace_port(ff, ctx->id("DI"), lc, ctx->id("M" + std::to_string(index)));
+ if (driven_by_lut) {
+ replace_port(ff, ctx->id("DI"), lc, ctx->id("DI" + std::to_string(index)));
+ } else {
+ replace_port(ff, ctx->id("DI"), lc, ctx->id("M" + std::to_string(index)));
+ }
}
}
void lut_to_slice(Context *ctx, CellInfo *lut, CellInfo *lc, int index)
{
+ if (lc->hierpath == IdString())
+ lc->hierpath = lut->hierpath;
lc->params[ctx->id("LUT" + std::to_string(index) + "_INITVAL")] =
get_or_default(lut->params, ctx->id("INIT"), Property(0, 16));
replace_port(lut, ctx->id("A"), lc, ctx->id("A" + std::to_string(index)));
@@ -270,6 +286,8 @@ void lut_to_slice(Context *ctx, CellInfo *lut, CellInfo *lc, int index)
void ccu2c_to_slice(Context *ctx, CellInfo *ccu, CellInfo *lc)
{
+ if (lc->hierpath == IdString())
+ lc->hierpath = ccu->hierpath;
lc->params[ctx->id("MODE")] = std::string("CCU2");
lc->params[ctx->id("LUT0_INITVAL")] = get_or_default(ccu->params, ctx->id("INIT0"), Property(0, 16));
lc->params[ctx->id("LUT1_INITVAL")] = get_or_default(ccu->params, ctx->id("INIT1"), Property(0, 16));
@@ -297,6 +315,8 @@ void ccu2c_to_slice(Context *ctx, CellInfo *ccu, CellInfo *lc)
void dram_to_ramw(Context *ctx, CellInfo *ram, CellInfo *lc)
{
+ if (lc->hierpath == IdString())
+ lc->hierpath = ram->hierpath;
lc->params[ctx->id("MODE")] = std::string("RAMW");
replace_port(ram, ctx->id("WAD[0]"), lc, ctx->id("D0"));
replace_port(ram, ctx->id("WAD[1]"), lc, ctx->id("B0"));
@@ -328,6 +348,8 @@ static unsigned get_dram_init(const Context *ctx, const CellInfo *ram, int bit)
void dram_to_ram_slice(Context *ctx, CellInfo *ram, CellInfo *lc, CellInfo *ramw, int index)
{
+ if (lc->hierpath == IdString())
+ lc->hierpath = ram->hierpath;
lc->params[ctx->id("MODE")] = std::string("DPRAM");
lc->params[ctx->id("WREMUX")] = str_or_default(ram->params, ctx->id("WREMUX"), "WRE");
lc->params[ctx->id("WCKMUX")] = str_or_default(ram->params, ctx->id("WCKMUX"), "WCK");
@@ -416,7 +438,25 @@ void nxio_to_tr(Context *ctx, CellInfo *nxio, CellInfo *trio, std::vector<std::u
} else {
NPNR_ASSERT(false);
}
- NetInfo *donet = trio->ports.at(ctx->id("I")).net;
+ NetInfo *donet = trio->ports.at(ctx->id("I")).net, *dinet = trio->ports.at(ctx->id("O")).net;
+
+ // Rename I/O nets to avoid conflicts
+ if (donet != nullptr && donet->name == nxio->name)
+ rename_net(ctx, donet, ctx->id(donet->name.str(ctx) + "$TRELLIS_IO_OUT"));
+ if (dinet != nullptr && dinet->name == nxio->name)
+ rename_net(ctx, dinet, ctx->id(dinet->name.str(ctx) + "$TRELLIS_IO_IN"));
+
+ // Create a new top port net for accurate IO timing analysis and simulation netlists
+ if (ctx->ports.count(nxio->name)) {
+ IdString tn_netname = nxio->name;
+ NPNR_ASSERT(!ctx->nets.count(tn_netname));
+ std::unique_ptr<NetInfo> toplevel_net{new NetInfo};
+ toplevel_net->name = tn_netname;
+ connect_port(ctx, toplevel_net.get(), trio, ctx->id("B"));
+ ctx->ports[nxio->name].net = toplevel_net.get();
+ ctx->nets[tn_netname] = std::move(toplevel_net);
+ }
+
CellInfo *tbuf = net_driven_by(
ctx, donet, [](const Context *ctx, const CellInfo *cell) { return cell->type == ctx->id("$_TBUF_"); },
ctx->id("Y"));
diff --git a/ecp5/constids.inc b/ecp5/constids.inc
index b38e23ef..e5ec1c3e 100644
--- a/ecp5/constids.inc
+++ b/ecp5/constids.inc
@@ -1329,3 +1329,11 @@ X(WIRE_TYPE_G_HPBX)
X(WIRE_TYPE_G_VPTX)
X(WIRE_TYPE_L_HPBX)
X(WIRE_TYPE_R_HPBX)
+
+X(IOLOGIC_MODE_IDDRX1F)
+X(IOLOGIC_MODE_IDDRX2F)
+X(IOLOGIC_MODE_IREG)
+X(IOLOGIC_MODE_ODDRX1F)
+X(IOLOGIC_MODE_ODDRX2F)
+X(IOLOGIC_MODE_OREG)
+X(IOLOGIC_MODE_TSREG)
diff --git a/ecp5/family.cmake b/ecp5/family.cmake
index b0b520d7..860e648d 100644
--- a/ecp5/family.cmake
+++ b/ecp5/family.cmake
@@ -49,7 +49,7 @@ if (NOT EXTERNAL_CHIPDB)
else()
add_custom_command(OUTPUT ${DEV_CC_BBA_DB}
COMMAND ${ENV_CMD} ${PYTHON_EXECUTABLE} ${DB_PY} -p ${DEV_CONSTIDS_INC} -g ${DEV_GFXH} ${dev} > ${DEV_CC_BBA_DB}
- DEPENDS ${DEV_CONSTIDS_INC} ${DEV_GFXH} ${DB_PY} ${PREV_DEV_CC_BBA_DB}
+ DEPENDS ${DB_PY} ${DEV_CONSTIDS_INC} ${DEV_GFXH} ${PREV_DEV_CC_BBA_DB}
)
add_custom_command(OUTPUT ${DEV_CC_DB}
COMMAND bbasm ${BBASM_ENDIAN_FLAG} ${DEV_CC_BBA_DB} ${DEV_CC_DB}
@@ -82,7 +82,7 @@ if (NOT EXTERNAL_CHIPDB)
add_custom_command(OUTPUT ${DEV_CC_BBA_DB}
COMMAND ${ENV_CMD} ${PYTHON_EXECUTABLE} ${DB_PY} -p ${DEV_CONSTIDS_INC} -g ${DEV_GFXH} ${dev} > ${DEV_CC_BBA_DB}.new
COMMAND mv ${DEV_CC_BBA_DB}.new ${DEV_CC_BBA_DB}
- DEPENDS ${DEV_CONSTIDS_INC} ${DEV_GFXH} ${DB_PY} ${PREV_DEV_CC_BBA_DB}
+ DEPENDS ${DB_PY} ${DEV_CONSTIDS_INC} ${DEV_GFXH} ${PREV_DEV_CC_BBA_DB}
)
add_custom_command(OUTPUT ${DEV_CC_DB}
COMMAND bbasm --c ${BBASM_ENDIAN_FLAG} ${DEV_CC_BBA_DB} ${DEV_CC_DB}.new
diff --git a/ecp5/globals.cc b/ecp5/globals.cc
index da2ba8f0..c0f4b504 100644
--- a/ecp5/globals.cc
+++ b/ecp5/globals.cc
@@ -58,7 +58,7 @@ class Ecp5GlobalRouter
if (user.cell->type == id_DCUA && (user.port == id_CH0_FF_RXI_CLK || user.port == id_CH1_FF_RXI_CLK ||
user.port == id_CH0_FF_TXI_CLK || user.port == id_CH1_FF_TXI_CLK))
return true;
- if ((user.cell->type == id_IOLOGIC || user.cell->type == id_SIOLOGIC) && user.port == id_CLK)
+ if ((user.cell->type == id_IOLOGIC || user.cell->type == id_SIOLOGIC) && (user.port == id_CLK))
return true;
return false;
}
@@ -74,6 +74,8 @@ class Ecp5GlobalRouter
clockCount[ni->name]++;
if (user.cell->type == id_DCUA)
clockCount[ni->name] += 100;
+ if (user.cell->type == id_IOLOGIC || user.cell->type == id_SIOLOGIC)
+ clockCount[ni->name] += 10;
}
}
// log_info("clkcount %s: %d\n", ni->name.c_str(ctx),clockCount[ni->name]);
diff --git a/ecp5/pack.cc b/ecp5/pack.cc
index 244a79d5..3867ab3d 100644
--- a/ecp5/pack.cc
+++ b/ecp5/pack.cc
@@ -56,6 +56,52 @@ class Ecp5Packer
new_cells.clear();
}
+ // Print logic usgage
+ int available_slices = 0;
+ void print_logic_usage()
+ {
+ int total_luts = 0, total_ffs = 0;
+ int total_ramluts = 0, total_ramwluts = 0;
+ for (auto bel : ctx->getBels()) {
+ if (ctx->getBelType(bel) == id_TRELLIS_SLICE) {
+ available_slices += 1;
+ total_luts += 2;
+ total_ffs += 2;
+ Loc l = ctx->getBelLocation(bel);
+ if (l.z == 0 || l.z == 1)
+ total_ramluts += 2;
+ if (l.z == 2)
+ total_ramwluts += 2;
+ }
+ }
+ int used_lgluts = 0, used_cyluts = 0, used_ramluts = 0, used_ramwluts = 0, used_ffs = 0;
+ for (auto &cell : ctx->cells) {
+ CellInfo *ci = cell.second.get();
+ if (is_lut(ctx, ci))
+ ++used_lgluts;
+ if (is_carry(ctx, ci))
+ used_cyluts += 2;
+ if (is_dpram(ctx, ci)) {
+ used_ramluts += 4;
+ used_ramwluts += 2;
+ }
+ if (is_ff(ctx, ci))
+ used_ffs += 2;
+ }
+ log_info("Logic utilisation before packing:\n");
+ auto pc = [](int used, int total) { return 100 * used / total; };
+ int used_luts = used_lgluts + used_cyluts + used_ramluts + used_ramwluts;
+ log_info(" Total LUT4s: %5d/%5d %5d%%\n", used_luts, total_luts, pc(used_luts, total_luts));
+ log_info(" logic LUTs: %5d/%5d %5d%%\n", used_lgluts, total_luts, pc(used_lgluts, total_luts));
+ log_info(" carry LUTs: %5d/%5d %5d%%\n", used_cyluts, total_luts, pc(used_cyluts, total_luts));
+ log_info(" RAM LUTs: %5d/%5d %5d%%\n", used_ramluts, total_ramluts, pc(used_ramluts, total_ramluts));
+ log_info(" RAMW LUTs: %5d/%5d %5d%%\n", used_ramwluts, total_ramwluts,
+ pc(used_ramwluts, total_ramwluts));
+ log_break();
+ log_info(" Total DFFs: %5d/%5d %5d%%\n", used_ffs, total_ffs, pc(used_ffs, total_ffs));
+ log_break();
+ }
+
// Find FFs associated with LUTs, or LUT expansion muxes
void find_lutff_pairs()
{
@@ -66,7 +112,8 @@ class Ecp5Packer
NetInfo *znet = ci->ports.at(ctx->id("Z")).net;
if (znet != nullptr) {
CellInfo *ff = net_only_drives(ctx, znet, is_ff, ctx->id("DI"), false);
- if (ff != nullptr) {
+ // Can't combine preload FF with LUT due to conflict on M
+ if (ff != nullptr && get_net_or_empty(ff, ctx->id("M")) == nullptr) {
lutffPairs[ci->name] = ff->name;
fflutPairs[ff->name] = ci->name;
}
@@ -75,6 +122,58 @@ class Ecp5Packer
}
}
+ // Check if a flipflop is available in a slice
+ bool is_ff_available(CellInfo *slice, int ff)
+ {
+ if (get_net_or_empty(slice, (ff == 1) ? id_Q1 : id_Q0) != nullptr)
+ return false;
+ if (get_net_or_empty(slice, (ff == 1) ? id_M1 : id_M0) != nullptr)
+ return false;
+ return true;
+ }
+
+ // Check if a flipflop can be added to a slice
+ bool can_add_ff_to_slice(CellInfo *slice, CellInfo *ff)
+ {
+ std::string clkmux = str_or_default(ff->params, ctx->id("CLKMUX"), "CLK");
+ std::string lsrmux = str_or_default(ff->params, ctx->id("LSRMUX"), "LSR");
+
+ bool has_dpram = str_or_default(slice->params, ctx->id("MODE"), "LOGIC") == "DPRAM";
+ if (has_dpram) {
+ std::string wckmux = str_or_default(slice->params, ctx->id("WCKMUX"), "WCK");
+ std::string wremux = str_or_default(slice->params, ctx->id("WREMUX"), "WRE");
+ if (wckmux != clkmux && !(wckmux == "WCK" && clkmux == "CLK"))
+ return false;
+ if (wremux != lsrmux && !(wremux == "WRE" && lsrmux == "LSR"))
+ return false;
+ }
+ bool has_ff0 = get_net_or_empty(slice, id_Q0) != nullptr;
+ bool has_ff1 = get_net_or_empty(slice, id_Q1) != nullptr;
+ if (!has_ff0 && !has_ff1)
+ return true;
+ if (str_or_default(ff->params, ctx->id("GSR"), "DISABLED") !=
+ str_or_default(slice->params, ctx->id("GSR"), "DISABLED"))
+ return false;
+ if (str_or_default(ff->params, ctx->id("SRMODE"), "LSR_OVER_CE") !=
+ str_or_default(slice->params, ctx->id("SRMODE"), "LSR_OVER_CE"))
+ return false;
+ if (str_or_default(ff->params, ctx->id("CEMUX"), "1") != str_or_default(slice->params, ctx->id("CEMUX"), "1"))
+ return false;
+ if (str_or_default(ff->params, ctx->id("LSRMUX"), "LSR") !=
+ str_or_default(slice->params, ctx->id("LSRMUX"), "LSR"))
+ return false;
+ if (str_or_default(ff->params, ctx->id("CLKMUX"), "CLK") !=
+ str_or_default(slice->params, ctx->id("CLKMUX"), "CLK"))
+ return false;
+ if (net_or_nullptr(ff, ctx->id("CLK")) != net_or_nullptr(slice, ctx->id("CLK")))
+ return false;
+ if (net_or_nullptr(ff, ctx->id("CE")) != net_or_nullptr(slice, ctx->id("CE")))
+ return false;
+ if (net_or_nullptr(ff, ctx->id("LSR")) != net_or_nullptr(slice, ctx->id("LSR")))
+ return false;
+ return true;
+ }
+
const NetInfo *net_or_nullptr(CellInfo *cell, IdString port)
{
auto fnd = cell->ports.find(port);
@@ -134,6 +233,8 @@ class Ecp5Packer
// Return true if a FF can be added to a DPRAM slice
bool can_pack_ff_dram(CellInfo *dpram, CellInfo *ff)
{
+ if (get_net_or_empty(ff, ctx->id("M")) != nullptr)
+ return false; // skip PRLD FFs due to M/DI conflict
std::string wckmux = str_or_default(dpram->params, ctx->id("WCKMUX"), "WCK");
std::string clkmux = str_or_default(ff->params, ctx->id("CLKMUX"), "CLK");
if (wckmux != clkmux && !(wckmux == "WCK" && clkmux == "CLK"))
@@ -362,8 +463,7 @@ class Ecp5Packer
for (auto &port : ci->ports)
disconnect_port(ctx, ci, port.first);
} else if (trio != nullptr) {
- // Trivial case, TRELLIS_IO used. Just destroy the net and the
- // iobuf
+ // Trivial case, TRELLIS_IO used. Just remove the IOBUF
log_info("%s feeds TRELLIS_IO %s, removing %s %s.\n", ci->name.c_str(ctx), trio->name.c_str(ctx),
ci->type.c_str(ctx), ci->name.c_str(ctx));
@@ -384,14 +484,6 @@ class Ecp5Packer
std::swap(net->clkconstr, onet->clkconstr);
}
}
- ctx->nets.erase(net->name);
- trio->ports.at(ctx->id("B")).net = nullptr;
- }
- if (ci->type == ctx->id("$nextpnr_iobuf")) {
- NetInfo *net2 = ci->ports.at(ctx->id("I")).net;
- if (net2 != nullptr) {
- ctx->nets.erase(net2->name);
- }
}
} else if (drives_top_port(ionet, tp)) {
log_info("%s feeds %s %s.%s, removing %s %s.\n", ci->name.c_str(ctx), tp.cell->type.c_str(ctx),
@@ -414,7 +506,8 @@ class Ecp5Packer
new_cells.push_back(std::move(tr_cell));
trio = new_cells.back().get();
}
-
+ for (auto port : ci->ports)
+ disconnect_port(ctx, ci, port.first);
packed_cells.insert(ci->name);
if (trio != nullptr) {
for (const auto &attr : ci->attrs)
@@ -1033,17 +1126,117 @@ class Ecp5Packer
flush_cells();
}
+ // Find a cell that meets some criterea near an origin cell
+ // Used for packing an FF into a nearby SLICE
+ template <typename TFunc> CellInfo *find_nearby_cell(CellInfo *origin, TFunc Func)
+ {
+ std::unordered_set<CellInfo *> visited_cells;
+ std::queue<CellInfo *> to_visit;
+ visited_cells.insert(origin);
+ to_visit.push(origin);
+ int iter = 0;
+ while (!to_visit.empty() && iter < 10000) {
+ CellInfo *cursor = to_visit.front();
+ to_visit.pop();
+ if (Func(cursor))
+ return cursor;
+ for (const auto &port : cursor->ports) {
+ NetInfo *pn = port.second.net;
+ if (pn == nullptr)
+ continue;
+ // Skip high-fanout nets that are unlikely to be relevant
+ if (pn->users.size() > 25)
+ continue;
+ // Add other ports on this net if not already visited
+ auto visit_port = [&](const PortRef &port) {
+ if (port.cell == nullptr)
+ return;
+ if (visited_cells.count(port.cell))
+ return;
+ // If not already visited; add the cell of this port to the queue
+ to_visit.push(port.cell);
+ visited_cells.insert(port.cell);
+ };
+ visit_port(pn->driver);
+ for (const auto &usr : pn->users)
+ visit_port(usr);
+ }
+ ++iter;
+ }
+ return nullptr;
+ }
+
// Pack flipflops that weren't paired with a LUT
+ float dense_pack_mode_thresh = 0.95f;
void pack_remaining_ffs()
{
+ // Enter dense flipflop packing mode once utilisation exceeds a threshold (default: 95%)
+ int used_slices = 0;
+ for (auto &cell : ctx->cells)
+ if (cell.second->type == id_TRELLIS_SLICE)
+ ++used_slices;
+
log_info("Packing unpaired FFs into a SLICE...\n");
for (auto cell : sorted(ctx->cells)) {
CellInfo *ci = cell.second;
if (is_ff(ctx, ci)) {
+ bool pack_dense = used_slices > (dense_pack_mode_thresh * available_slices);
+ bool requires_m = get_net_or_empty(ci, ctx->id("M")) != nullptr;
+ if (pack_dense && !requires_m) {
+ // If dense packing threshold exceeded; always try and pack the FF into an existing slice
+ // Find a SLICE with space "near" the flipflop in the netlist
+ std::vector<CellInfo *> ltile;
+ CellInfo *target = find_nearby_cell(ci, [&](CellInfo *cursor) {
+ if (cursor->type != id_TRELLIS_SLICE)
+ return false;
+ if (!cursor->constr_children.empty() || cursor->constr_parent != nullptr) {
+ auto &constr_children = (cursor->constr_parent != nullptr)
+ ? cursor->constr_parent->constr_children
+ : cursor->constr_children;
+ // Skip big chains for performance
+ if (constr_children.size() > 8)
+ return false;
+ // Have to check the whole of the tile for legality when dealing with chains, not just slice
+ ltile.clear();
+ if (cursor->constr_parent != nullptr)
+ ltile.push_back(cursor->constr_parent);
+ else
+ ltile.push_back(cursor);
+ for (auto c : constr_children)
+ ltile.push_back(c);
+ if (!can_add_ff_to_tile(ltile, cursor))
+ return false;
+ }
+ if (!can_add_ff_to_slice(cursor, ci))
+ return false;
+ for (int i = 0; i < 2; i++)
+ if (is_ff_available(cursor, i))
+ return true;
+ return false;
+ });
+
+ // If found, add the FF to this slice instead of creating a new one
+ if (target != nullptr) {
+ for (int i = 0; i < 2; i++) {
+ if (is_ff_available(target, i)) {
+ ff_to_slice(ctx, ci, target, i, false);
+ goto ff_packed;
+ }
+ }
+ }
+
+ if (false) {
+ ff_packed:
+ packed_cells.insert(ci->name);
+ continue;
+ }
+ }
+
std::unique_ptr<CellInfo> slice =
create_ecp5_cell(ctx, ctx->id("TRELLIS_SLICE"), ci->name.str(ctx) + "_SLICE");
ff_to_slice(ctx, ci, slice.get(), 0, false);
new_cells.push_back(std::move(slice));
+ ++used_slices;
packed_cells.insert(ci->name);
}
}
@@ -1986,7 +2179,8 @@ class Ecp5Packer
iol->params[ctx->id("DELAY.DEL_VALUE")] =
lookup_delay(str_or_default(ci->params, ctx->id("DEL_MODE"), "USER_DEFINED"));
if (ci->params.count(ctx->id("DEL_VALUE")) &&
- std::string(ci->params.at(ctx->id("DEL_VALUE")).as_string()).substr(0, 5) != "DELAY")
+ (!ci->params.at(ctx->id("DEL_VALUE")).is_string ||
+ std::string(ci->params.at(ctx->id("DEL_VALUE")).as_string()).substr(0, 5) != "DELAY"))
iol->params[ctx->id("DELAY.DEL_VALUE")] = ci->params.at(ctx->id("DEL_VALUE"));
if (ci->ports.count(id_LOADN))
replace_port(ci, id_LOADN, iol, id_LOADN);
@@ -2353,6 +2547,7 @@ class Ecp5Packer
for (auto cell : sorted(ctx->cells)) {
CellInfo *ci = cell.second;
if (ci->type == id_ECLKBRIDGECS) {
+ Loc loc;
NetInfo *i0 = get_net_or_empty(ci, id_CLK0), *i1 = get_net_or_empty(ci, id_CLK1),
*o = get_net_or_empty(ci, id_ECSOUT);
for (NetInfo *input : {i0, i1}) {
@@ -2366,24 +2561,53 @@ class Ecp5Packer
for (auto bel : ctx->getBels()) {
if (ctx->getBelType(bel) != id_ECLKBRIDGECS)
continue;
- Loc loc = ctx->getBelLocation(bel);
+ loc = ctx->getBelLocation(bel);
if (loc.x == user_loc.x) {
ci->attrs[ctx->id("BEL")] = ctx->getBelName(bel).str(ctx);
- if (o != nullptr)
- for (auto user2 : o->users) {
- // Set side hint to ensure edge clock choice is routeable
- if (user2.cell->type == id_ECLKSYNCB && user2.port == id_ECLKI) {
- NetInfo *synco = get_net_or_empty(user2.cell, id_ECLKO);
- if (synco != nullptr)
- bridge_side_hint[synco] = (loc.x > 1) ? 0 : 1;
- }
- }
goto eclkbridge_done;
}
}
}
+ if (input->driver.cell != nullptr) {
+ CellInfo *drv = input->driver.cell;
+ if (!drv->attrs.count(ctx->id("BEL")))
+ continue;
+ Loc drv_loc = ctx->getBelLocation(
+ ctx->getBelByName(ctx->id(drv->attrs.at(ctx->id("BEL")).as_string())));
+ BelId closest;
+ int closest_x = -1; // aim for same side of chip
+ for (auto bel : ctx->getBels()) {
+ if (ctx->getBelType(bel) != id_ECLKBRIDGECS)
+ continue;
+ loc = ctx->getBelLocation(bel);
+ if (closest_x == -1 || std::abs(loc.x - drv_loc.x) < std::abs(closest_x - drv_loc.x)) {
+ closest_x = loc.x;
+ closest = bel;
+ }
+ }
+ NPNR_ASSERT(closest != BelId());
+ loc = ctx->getBelLocation(closest);
+ ci->attrs[ctx->id("BEL")] = ctx->getBelName(closest).str(ctx);
+ goto eclkbridge_done;
+ }
+ }
+ // If all else fails, place randomly
+ for (auto bel : ctx->getBels()) {
+ if (ctx->getBelType(bel) != id_ECLKBRIDGECS)
+ continue;
+ loc = ctx->getBelLocation(bel);
+ ci->attrs[ctx->id("BEL")] = ctx->getBelName(bel).str(ctx);
}
eclkbridge_done:
+ if (o != nullptr)
+ for (auto user2 : o->users) {
+ // Set side hint to ensure edge clock choice is routeable
+ if (user2.cell->type == id_ECLKSYNCB && user2.port == id_ECLKI) {
+ NetInfo *synco = get_net_or_empty(user2.cell, id_ECLKO);
+ if (synco != nullptr)
+ bridge_side_hint[synco] = (loc.x > 1) ? 0 : 1;
+ }
+ }
continue;
}
}
@@ -2468,12 +2692,37 @@ class Ecp5Packer
const NetInfo *clk = net_or_nullptr(ci, id_CLK);
if (clk == nullptr)
log_error("DDRDLLA '%s' has disconnected port CLK\n", ci->name.c_str(ctx));
+
+ bool left_bank_users = false, right_bank_users = false;
+ // Check which side the delay codes (DDRDEL) are used on
+ const NetInfo *ddrdel = net_or_nullptr(ci, id_DDRDEL);
+ if (ddrdel != nullptr) {
+ for (auto &usr : ddrdel->users) {
+ const CellInfo *uc = usr.cell;
+ if (uc->type != id_DQSBUFM || !uc->attrs.count(ctx->id("BEL")))
+ continue;
+ BelId dqsb_bel = ctx->getBelByName(ctx->id(uc->attrs.at(ctx->id("BEL")).as_string()));
+ Loc dqsb_loc = ctx->getBelLocation(dqsb_bel);
+ if (dqsb_loc.x > 15)
+ right_bank_users = true;
+ if (dqsb_loc.x < 15)
+ left_bank_users = true;
+ }
+ }
+
+ if (left_bank_users && right_bank_users)
+ log_error("DDRDLLA '%s' has DDRDEL uses on both sides of the chip.\n", ctx->nameOf(ci));
+
for (auto &eclk : eclks) {
if (eclk.second.unbuf == clk) {
for (auto bel : ctx->getBels()) {
if (ctx->getBelType(bel) != id_DDRDLL)
continue;
Loc loc = ctx->getBelLocation(bel);
+ if (loc.x > 15 && left_bank_users)
+ continue;
+ if (loc.x < 15 && right_bank_users)
+ continue;
int ddrdll_bank = -1;
if (loc.x < 15 && loc.y < 15)
ddrdll_bank = 7;
@@ -2485,6 +2734,7 @@ class Ecp5Packer
ddrdll_bank = 3;
if (eclk.first.first != ddrdll_bank)
continue;
+ log_info("Constraining DDRDLLA '%s' to bel '%s'\n", ctx->nameOf(ci), ctx->nameOfBel(bel));
ci->attrs[ctx->id("BEL")] = ctx->getBelName(bel).str(ctx);
make_eclk(ci->ports.at(id_CLK), ci, bel, eclk.first.first);
goto ddrdll_done;
@@ -2583,7 +2833,7 @@ class Ecp5Packer
std::unordered_set<IdString> changed_cells;
for (auto net : changed_nets)
for (auto &user : ctx->nets.at(net)->users)
- if (user.port == id_CLKI || user.port == id_ECLKI)
+ if (user.port == id_CLKI || user.port == id_ECLKI || user.port == id_CLK0 || user.port == id_CLK1)
changed_cells.insert(user.cell->name);
changed_nets.clear();
for (auto cell : sorted(changed_cells)) {
@@ -2600,6 +2850,9 @@ class Ecp5Packer
copy_constraint(ci, id_CLKI, id_CDIVX, ratio);
} else if (ci->type == id_ECLKSYNCB || ci->type == id_TRELLIS_ECLKBUF) {
copy_constraint(ci, id_ECLKI, id_ECLKO, 1);
+ } else if (ci->type == id_ECLKBRIDGECS) {
+ copy_constraint(ci, id_CLK0, id_ECSOUT, 1);
+ copy_constraint(ci, id_CLK1, id_ECSOUT, 1);
} else if (ci->type == id_DCCA) {
copy_constraint(ci, id_CLKI, id_CLKO, 1);
} else if (ci->type == id_EHXPLLL) {
@@ -2657,14 +2910,15 @@ class Ecp5Packer
void pack()
{
prepack_checks();
+ print_logic_usage();
pack_io();
pack_dqsbuf();
+ preplace_plls();
pack_iologic();
pack_ebr();
pack_dsps();
pack_dcus();
pack_misc();
- preplace_plls();
pack_constants();
pack_dram();
pack_carries();
diff --git a/ecp5/synth/.gitignore b/ecp5/synth/.gitignore
deleted file mode 100644
index f4dfa215..00000000
--- a/ecp5/synth/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-*.bit
-*_out.config
-
diff --git a/ecp5/synth/blinky.v b/ecp5/synth/blinky.v
deleted file mode 100644
index 9c6b187b..00000000
--- a/ecp5/synth/blinky.v
+++ /dev/null
@@ -1,77 +0,0 @@
-module top(input clk_pin, input btn_pin, output [7:0] led_pin, output gpio0_pin);
-
- wire clk;
- wire [7:0] led;
- wire btn;
- wire gpio0;
-
- (* LOC="G2" *) (* IO_TYPE="LVCMOS33" *)
- TRELLIS_IO #(.DIR("INPUT")) clk_buf (.B(clk_pin), .O(clk));
-
- (* LOC="R1" *) (* IO_TYPE="LVCMOS33" *)
- TRELLIS_IO #(.DIR("INPUT")) btn_buf (.B(btn_pin), .O(btn));
-
- (* LOC="B2" *) (* IO_TYPE="LVCMOS33" *)
- TRELLIS_IO #(.DIR("OUTPUT")) led_buf_0 (.B(led_pin[0]), .I(led[0]));
- (* LOC="C2" *) (* IO_TYPE="LVCMOS33" *)
- TRELLIS_IO #(.DIR("OUTPUT")) led_buf_1 (.B(led_pin[1]), .I(led[1]));
- (* LOC="C1" *) (* IO_TYPE="LVCMOS33" *)
- TRELLIS_IO #(.DIR("OUTPUT")) led_buf_2 (.B(led_pin[2]), .I(led[2]));
- (* LOC="D2" *) (* IO_TYPE="LVCMOS33" *)
- TRELLIS_IO #(.DIR("OUTPUT")) led_buf_3 (.B(led_pin[3]), .I(led[3]));
-
- (* LOC="D1" *) (* IO_TYPE="LVCMOS33" *)
- TRELLIS_IO #(.DIR("OUTPUT")) led_buf_4 (.B(led_pin[4]), .I(led[4]));
- (* LOC="E2" *) (* IO_TYPE="LVCMOS33" *)
- TRELLIS_IO #(.DIR("OUTPUT")) led_buf_5 (.B(led_pin[5]), .I(led[5]));
- (* LOC="E1" *) (* IO_TYPE="LVCMOS33" *)
- TRELLIS_IO #(.DIR("OUTPUT")) led_buf_6 (.B(led_pin[6]), .I(led[6]));
- (* LOC="H3" *) (* IO_TYPE="LVCMOS33" *)
- TRELLIS_IO #(.DIR("OUTPUT")) led_buf_7 (.B(led_pin[7]), .I(led[7]));
-
-
- (* LOC="L2" *) (* IO_TYPE="LVCMOS33" *)
- TRELLIS_IO #(.DIR("OUTPUT")) gpio0_buf (.B(gpio0_pin), .I(gpio0));
-
- localparam ctr_width = 24;
- localparam ctr_max = 2**ctr_width - 1;
- reg [ctr_width-1:0] ctr = 0;
- reg [9:0] pwm_ctr = 0;
- reg dir = 0;
-
- always@(posedge clk) begin
- ctr <= btn ? ctr : (dir ? ctr - 1'b1 : ctr + 1'b1);
- if (ctr[ctr_width-1 : ctr_width-3] == 0 && dir == 1)
- dir <= 1'b0;
- else if (ctr[ctr_width-1 : ctr_width-3] == 7 && dir == 0)
- dir <= 1'b1;
- pwm_ctr <= pwm_ctr + 1'b1;
- end
-
- reg [9:0] brightness [0:7];
- localparam bright_max = 2**10 - 1;
- reg [7:0] led_reg;
-
- genvar i;
- generate
- for (i = 0; i < 8; i=i+1) begin
- always @ (posedge clk) begin
- if (ctr[ctr_width-1 : ctr_width-3] == i)
- brightness[i] <= bright_max;
- else if (ctr[ctr_width-1 : ctr_width-3] == (i - 1))
- brightness[i] <= ctr[ctr_width-4:ctr_width-13];
- else if (ctr[ctr_width-1 : ctr_width-3] == (i + 1))
- brightness[i] <= bright_max - ctr[ctr_width-4:ctr_width-13];
- else
- brightness[i] <= 0;
- led_reg[i] <= pwm_ctr < brightness[i];
- end
- end
- endgenerate
-
- assign led = led_reg;
-
- // Tie GPIO0, keep board from rebooting
- assign gpio0 = 1'b1;
-
-endmodule
diff --git a/ecp5/synth/blinky.ys b/ecp5/synth/blinky.ys
deleted file mode 100644
index fb359380..00000000
--- a/ecp5/synth/blinky.ys
+++ /dev/null
@@ -1,2 +0,0 @@
-read_verilog blinky.v
-synth_ecp5 -noccu2 -nomux -nodram -json blinky.json
diff --git a/ecp5/synth/ulx3s_empty.config b/ecp5/synth/ulx3s_empty.config
deleted file mode 100644
index 815e7f0d..00000000
--- a/ecp5/synth/ulx3s_empty.config
+++ /dev/null
@@ -1,439 +0,0 @@
-.device LFE5U-45F
-
-.tile CIB_R10C3:PVT_COUNT2
-unknown: F2B0
-unknown: F3B0
-unknown: F5B0
-unknown: F11B0
-unknown: F13B0
-
-.tile CIB_R5C1:CIB_PLL1
-enum: CIB.JA3MUX 0
-enum: CIB.JB3MUX 0
-
-
-.tile CIB_R5C89:CIB_PLL1
-enum: CIB.JA3MUX 0
-enum: CIB.JB3MUX 0
-
-
-.tile CIB_R70C3:CIB_PLL3
-enum: CIB.JA3MUX 0
-enum: CIB.JB3MUX 0
-
-
-.tile CIB_R70C42:VCIB_DCU0
-enum: CIB.JA1MUX 0
-enum: CIB.JA3MUX 0
-enum: CIB.JA5MUX 0
-enum: CIB.JA7MUX 0
-enum: CIB.JB1MUX 0
-enum: CIB.JB3MUX 0
-enum: CIB.JB5MUX 0
-enum: CIB.JB7MUX 0
-enum: CIB.JC0MUX 0
-enum: CIB.JC2MUX 0
-enum: CIB.JC4MUX 0
-enum: CIB.JC6MUX 0
-enum: CIB.JD0MUX 0
-enum: CIB.JD2MUX 0
-enum: CIB.JD4MUX 0
-enum: CIB.JD6MUX 0
-
-
-.tile CIB_R70C43:VCIB_DCUA
-enum: CIB.JA1MUX 0
-enum: CIB.JA3MUX 0
-enum: CIB.JA5MUX 0
-enum: CIB.JA7MUX 0
-enum: CIB.JB1MUX 0
-enum: CIB.JB3MUX 0
-enum: CIB.JB5MUX 0
-enum: CIB.JB7MUX 0
-enum: CIB.JC0MUX 0
-enum: CIB.JC2MUX 0
-enum: CIB.JC4MUX 0
-enum: CIB.JC6MUX 0
-enum: CIB.JD0MUX 0
-enum: CIB.JD2MUX 0
-enum: CIB.JD4MUX 0
-enum: CIB.JD6MUX 0
-
-
-.tile CIB_R70C44:VCIB_DCUB
-enum: CIB.JA1MUX 0
-enum: CIB.JA3MUX 0
-enum: CIB.JA5MUX 0
-enum: CIB.JA7MUX 0
-enum: CIB.JB1MUX 0
-enum: CIB.JB3MUX 0
-enum: CIB.JB5MUX 0
-enum: CIB.JB7MUX 0
-enum: CIB.JC0MUX 0
-enum: CIB.JC2MUX 0
-enum: CIB.JC4MUX 0
-enum: CIB.JC6MUX 0
-enum: CIB.JD0MUX 0
-enum: CIB.JD2MUX 0
-enum: CIB.JD4MUX 0
-enum: CIB.JD6MUX 0
-
-
-.tile CIB_R70C45:VCIB_DCUC
-enum: CIB.JA1MUX 0
-enum: CIB.JA3MUX 0
-enum: CIB.JA5MUX 0
-enum: CIB.JA7MUX 0
-enum: CIB.JB1MUX 0
-enum: CIB.JB3MUX 0
-enum: CIB.JB5MUX 0
-enum: CIB.JB7MUX 0
-enum: CIB.JC0MUX 0
-enum: CIB.JC2MUX 0
-enum: CIB.JC4MUX 0
-enum: CIB.JC6MUX 0
-enum: CIB.JD0MUX 0
-enum: CIB.JD2MUX 0
-enum: CIB.JD4MUX 0
-enum: CIB.JD6MUX 0
-
-
-.tile CIB_R70C46:VCIB_DCUD
-enum: CIB.JA1MUX 0
-enum: CIB.JA5MUX 0
-enum: CIB.JA7MUX 0
-enum: CIB.JB1MUX 0
-enum: CIB.JB3MUX 0
-enum: CIB.JB5MUX 0
-enum: CIB.JB7MUX 0
-enum: CIB.JC0MUX 0
-enum: CIB.JC2MUX 0
-enum: CIB.JC4MUX 0
-enum: CIB.JC6MUX 0
-enum: CIB.JD0MUX 0
-enum: CIB.JD2MUX 0
-enum: CIB.JD4MUX 0
-enum: CIB.JD6MUX 0
-
-
-.tile CIB_R70C47:VCIB_DCUF
-enum: CIB.JA1MUX 0
-enum: CIB.JA3MUX 0
-enum: CIB.JA5MUX 0
-enum: CIB.JA7MUX 0
-enum: CIB.JB1MUX 0
-enum: CIB.JB3MUX 0
-enum: CIB.JB5MUX 0
-enum: CIB.JB7MUX 0
-enum: CIB.JC0MUX 0
-enum: CIB.JC2MUX 0
-enum: CIB.JC4MUX 0
-enum: CIB.JC6MUX 0
-enum: CIB.JD0MUX 0
-enum: CIB.JD2MUX 0
-enum: CIB.JD4MUX 0
-enum: CIB.JD6MUX 0
-
-
-.tile CIB_R70C48:VCIB_DCU3
-enum: CIB.JA5MUX 0
-enum: CIB.JA7MUX 0
-enum: CIB.JB1MUX 0
-enum: CIB.JB3MUX 0
-enum: CIB.JB5MUX 0
-enum: CIB.JB7MUX 0
-enum: CIB.JC0MUX 0
-enum: CIB.JC4MUX 0
-enum: CIB.JC6MUX 0
-enum: CIB.JD0MUX 0
-enum: CIB.JD2MUX 0
-enum: CIB.JD4MUX 0
-enum: CIB.JD6MUX 0
-
-
-.tile CIB_R70C49:VCIB_DCU2
-enum: CIB.JB1MUX 0
-enum: CIB.JB3MUX 0
-enum: CIB.JB5MUX 0
-enum: CIB.JB7MUX 0
-enum: CIB.JD0MUX 0
-enum: CIB.JD2MUX 0
-enum: CIB.JD4MUX 0
-enum: CIB.JD6MUX 0
-
-
-.tile CIB_R70C50:VCIB_DCUG
-enum: CIB.JB1MUX 0
-enum: CIB.JB3MUX 0
-enum: CIB.JB5MUX 0
-enum: CIB.JB7MUX 0
-enum: CIB.JD0MUX 0
-enum: CIB.JD2MUX 0
-enum: CIB.JD4MUX 0
-enum: CIB.JD6MUX 0
-
-
-.tile CIB_R70C51:VCIB_DCUH
-enum: CIB.JB1MUX 0
-enum: CIB.JB3MUX 0
-enum: CIB.JB5MUX 0
-enum: CIB.JB7MUX 0
-enum: CIB.JD0MUX 0
-enum: CIB.JD2MUX 0
-enum: CIB.JD4MUX 0
-enum: CIB.JD6MUX 0
-
-
-.tile CIB_R70C52:VCIB_DCUI
-enum: CIB.JB1MUX 0
-enum: CIB.JB3MUX 0
-enum: CIB.JB5MUX 0
-enum: CIB.JB7MUX 0
-enum: CIB.JD0MUX 0
-enum: CIB.JD2MUX 0
-enum: CIB.JD4MUX 0
-enum: CIB.JD6MUX 0
-
-
-.tile CIB_R70C53:VCIB_DCU1
-enum: CIB.JB1MUX 0
-enum: CIB.JB3MUX 0
-enum: CIB.JB5MUX 0
-enum: CIB.JD0MUX 0
-enum: CIB.JD2MUX 0
-
-
-.tile CIB_R70C69:VCIB_DCU0
-enum: CIB.JA1MUX 0
-enum: CIB.JA3MUX 0
-enum: CIB.JA5MUX 0
-enum: CIB.JA7MUX 0
-enum: CIB.JB1MUX 0
-enum: CIB.JB3MUX 0
-enum: CIB.JB5MUX 0
-enum: CIB.JB7MUX 0
-enum: CIB.JC0MUX 0
-enum: CIB.JC2MUX 0
-enum: CIB.JC4MUX 0
-enum: CIB.JC6MUX 0
-enum: CIB.JD0MUX 0
-enum: CIB.JD2MUX 0
-enum: CIB.JD4MUX 0
-enum: CIB.JD6MUX 0
-
-
-.tile CIB_R70C6:CIB_EFB0
-enum: CIB.JB3MUX 0
-enum: CIB.JC6MUX 0
-enum: CIB.JD6MUX 0
-
-
-.tile CIB_R70C70:VCIB_DCUA
-enum: CIB.JA1MUX 0
-enum: CIB.JA3MUX 0
-enum: CIB.JA5MUX 0
-enum: CIB.JA7MUX 0
-enum: CIB.JB1MUX 0
-enum: CIB.JB3MUX 0
-enum: CIB.JB5MUX 0
-enum: CIB.JB7MUX 0
-enum: CIB.JC0MUX 0
-enum: CIB.JC2MUX 0
-enum: CIB.JC4MUX 0
-enum: CIB.JC6MUX 0
-enum: CIB.JD0MUX 0
-enum: CIB.JD2MUX 0
-enum: CIB.JD4MUX 0
-enum: CIB.JD6MUX 0
-
-
-.tile CIB_R70C71:VCIB_DCUB
-enum: CIB.JA1MUX 0
-enum: CIB.JA3MUX 0
-enum: CIB.JA5MUX 0
-enum: CIB.JA7MUX 0
-enum: CIB.JB1MUX 0
-enum: CIB.JB3MUX 0
-enum: CIB.JB5MUX 0
-enum: CIB.JB7MUX 0
-enum: CIB.JC0MUX 0
-enum: CIB.JC2MUX 0
-enum: CIB.JC4MUX 0
-enum: CIB.JC6MUX 0
-enum: CIB.JD0MUX 0
-enum: CIB.JD2MUX 0
-enum: CIB.JD4MUX 0
-enum: CIB.JD6MUX 0
-
-
-.tile CIB_R70C72:VCIB_DCUC
-enum: CIB.JA1MUX 0
-enum: CIB.JA3MUX 0
-enum: CIB.JA5MUX 0
-enum: CIB.JA7MUX 0
-enum: CIB.JB1MUX 0
-enum: CIB.JB3MUX 0
-enum: CIB.JB5MUX 0
-enum: CIB.JB7MUX 0
-enum: CIB.JC0MUX 0
-enum: CIB.JC2MUX 0
-enum: CIB.JC4MUX 0
-enum: CIB.JC6MUX 0
-enum: CIB.JD0MUX 0
-enum: CIB.JD2MUX 0
-enum: CIB.JD4MUX 0
-enum: CIB.JD6MUX 0
-
-
-.tile CIB_R70C73:VCIB_DCUD
-enum: CIB.JA1MUX 0
-enum: CIB.JA5MUX 0
-enum: CIB.JA7MUX 0
-enum: CIB.JB1MUX 0
-enum: CIB.JB3MUX 0
-enum: CIB.JB5MUX 0
-enum: CIB.JB7MUX 0
-enum: CIB.JC0MUX 0
-enum: CIB.JC2MUX 0
-enum: CIB.JC4MUX 0
-enum: CIB.JC6MUX 0
-enum: CIB.JD0MUX 0
-enum: CIB.JD2MUX 0
-enum: CIB.JD4MUX 0
-enum: CIB.JD6MUX 0
-
-
-.tile CIB_R70C74:VCIB_DCUF
-enum: CIB.JA1MUX 0
-enum: CIB.JA3MUX 0
-enum: CIB.JA5MUX 0
-enum: CIB.JA7MUX 0
-enum: CIB.JB1MUX 0
-enum: CIB.JB3MUX 0
-enum: CIB.JB5MUX 0
-enum: CIB.JB7MUX 0
-enum: CIB.JC0MUX 0
-enum: CIB.JC2MUX 0
-enum: CIB.JC4MUX 0
-enum: CIB.JC6MUX 0
-enum: CIB.JD0MUX 0
-enum: CIB.JD2MUX 0
-enum: CIB.JD4MUX 0
-enum: CIB.JD6MUX 0
-
-
-.tile CIB_R70C75:VCIB_DCU3
-enum: CIB.JA5MUX 0
-enum: CIB.JA7MUX 0
-enum: CIB.JB1MUX 0
-enum: CIB.JB3MUX 0
-enum: CIB.JB5MUX 0
-enum: CIB.JB7MUX 0
-enum: CIB.JC0MUX 0
-enum: CIB.JC4MUX 0
-enum: CIB.JC6MUX 0
-enum: CIB.JD0MUX 0
-enum: CIB.JD2MUX 0
-enum: CIB.JD4MUX 0
-enum: CIB.JD6MUX 0
-
-
-.tile CIB_R70C76:VCIB_DCU2
-enum: CIB.JB1MUX 0
-enum: CIB.JB3MUX 0
-enum: CIB.JB5MUX 0
-enum: CIB.JB7MUX 0
-enum: CIB.JD0MUX 0
-enum: CIB.JD2MUX 0
-enum: CIB.JD4MUX 0
-enum: CIB.JD6MUX 0
-
-
-.tile CIB_R70C77:VCIB_DCUG
-enum: CIB.JB1MUX 0
-enum: CIB.JB3MUX 0
-enum: CIB.JB5MUX 0
-enum: CIB.JB7MUX 0
-enum: CIB.JD0MUX 0
-enum: CIB.JD2MUX 0
-enum: CIB.JD4MUX 0
-enum: CIB.JD6MUX 0
-
-
-.tile CIB_R70C78:VCIB_DCUH
-enum: CIB.JB1MUX 0
-enum: CIB.JB3MUX 0
-enum: CIB.JB5MUX 0
-enum: CIB.JB7MUX 0
-enum: CIB.JD0MUX 0
-enum: CIB.JD2MUX 0
-enum: CIB.JD4MUX 0
-enum: CIB.JD6MUX 0
-
-
-.tile CIB_R70C79:VCIB_DCUI
-enum: CIB.JB1MUX 0
-enum: CIB.JB3MUX 0
-enum: CIB.JB5MUX 0
-enum: CIB.JB7MUX 0
-enum: CIB.JD0MUX 0
-enum: CIB.JD2MUX 0
-enum: CIB.JD4MUX 0
-enum: CIB.JD6MUX 0
-
-
-.tile CIB_R70C7:CIB_EFB1
-enum: CIB.JA3MUX 0
-enum: CIB.JA4MUX 0
-enum: CIB.JA5MUX 0
-enum: CIB.JA6MUX 0
-enum: CIB.JB3MUX 0
-enum: CIB.JB4MUX 0
-enum: CIB.JB5MUX 0
-enum: CIB.JB6MUX 0
-enum: CIB.JC3MUX 0
-enum: CIB.JC4MUX 0
-enum: CIB.JC5MUX 0
-enum: CIB.JD3MUX 0
-enum: CIB.JD4MUX 0
-enum: CIB.JD5MUX 0
-
-
-.tile CIB_R70C80:VCIB_DCU1
-enum: CIB.JB1MUX 0
-enum: CIB.JB3MUX 0
-enum: CIB.JB5MUX 0
-enum: CIB.JD0MUX 0
-enum: CIB.JD2MUX 0
-
-
-.tile CIB_R70C87:CIB_PLL3
-enum: CIB.JA3MUX 0
-enum: CIB.JB3MUX 0
-
-
-.tile MIB_R10C40:CMUX_UL_0
-arc: G_DCS0CLK0 G_VPFN0000
-
-
-.tile MIB_R10C41:CMUX_UR_0
-arc: G_DCS0CLK1 G_VPFN0000
-
-
-.tile MIB_R58C40:CMUX_LL_0
-arc: G_DCS1CLK0 G_VPFN0000
-
-
-.tile MIB_R58C41:CMUX_LR_0
-arc: G_DCS1CLK1 G_VPFN0000
-
-
-.tile MIB_R71C4:EFB0_PICB0
-unknown: F54B1
-unknown: F56B1
-unknown: F82B1
-unknown: F94B1
-
-.tile MIB_R71C3:BANKREF8
-unknown: F18B0
-
diff --git a/ecp5/trellis_import.py b/ecp5/trellis_import.py
index 9f8a9482..c8589b6c 100755
--- a/ecp5/trellis_import.py
+++ b/ecp5/trellis_import.py
@@ -320,6 +320,8 @@ def process_timing_data():
max_delay = min(entry["rising"][2], entry["falling"][2])
delays.append((constids[from_pin], constids[to_pin], min_delay, max_delay))
elif entry["type"] == "SetupHold":
+ if type(entry["pin"]) is list:
+ continue
pin = constids[entry["pin"]]
clock = constids[entry["clock"][1]]
min_setup = entry["setup"][0]
@@ -580,6 +582,7 @@ def write_database(dev_name, chip, ddrg, endianness):
bba.u32(len(location_types), "num_location_types")
bba.u32(len(packages), "num_packages")
bba.u32(len(pindata), "num_pios")
+ bba.u32(const_id_count, "const_id_count")
bba.r("locations", "locations")
bba.r("location_types", "location_type")
@@ -596,11 +599,12 @@ def write_database(dev_name, chip, ddrg, endianness):
dev_names = {"25k": "LFE5UM5G-25F", "45k": "LFE5UM5G-45F", "85k": "LFE5UM5G-85F"}
def main():
- global max_row, max_col
+ global max_row, max_col, const_id_count
pytrellis.load_database(database.get_db_root())
args = parser.parse_args()
# Read port pin file
+ const_id_count = 1 # count ID_NONE
with open(args.constids) as f:
for line in f:
line = line.replace("(", " ")
@@ -612,7 +616,7 @@ def main():
assert line[0] == "X"
idx = len(constids) + 1
constids[line[1]] = idx
-
+ const_id_count += 1
constids["SLICE"] = constids["TRELLIS_SLICE"]
constids["PIO"] = constids["TRELLIS_IO"]
diff --git a/frontend/frontend_base.h b/frontend/frontend_base.h
new file mode 100644
index 00000000..45847e21
--- /dev/null
+++ b/frontend/frontend_base.h
@@ -0,0 +1,731 @@
+/*
+ * nextpnr -- Next Generation Place and Route
+ *
+ * Copyright (C) 2019 David Shah <dave@ds0.me>
+ *
+ * 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.
+ *
+ */
+
+/*
+ * Generic Frontend Framework
+ *
+ * This is designed to make it possible to build frontends for parsing any format isomorphic to Yosys JSON [1]
+ * with maximal inlining and minimal need for overhead such as runtime polymorphism or extra wrapper types.
+ *
+ * [1] http://www.clifford.at/yosys/cmd_write_json.html
+ *
+ * The frontend should implement a class referred to as FrontendType that defines the following type(def)s and
+ * functions:
+ *
+ * Types:
+ * ModuleDataType: corresponds to a single entry in "modules"
+ * ModulePortDataType: corresponds to a single entry in "ports" of a module
+ * CellDataType: corresponds to a single entry in "cells"
+ * NetnameDataType: corresponds to a single entry in "netnames"
+ * BitVectorDataType: corresponds to a signal/constant bit vector (e.g. a "connections" field)
+ *
+ * Functions:
+ *
+ * void foreach_module(Func) const;
+ * calls Func(const std::string &name, const ModuleDataType &mod);
+ * for each module in the netlist
+ *
+ * void foreach_port(const ModuleDataType &mod, Func) const;
+ * calls Func(const std::string &name, const ModulePortDataType &port);
+ * for each port of mod
+ *
+ * void foreach_cell(const ModuleDataType &mod, Func) const;
+ * calls Func(const std::string &name, const CellDataType &cell)
+ * for each cell of mod
+ *
+ * void foreach_netname(const ModuleDataType &mod, Func) const;
+ * calls Func(const std::string &name, const NetnameDataType &cell);
+ * for each netname entry of mod
+ *
+ * PortType get_port_dir(const ModulePortDataType &port) const;
+ * gets the PortType direction of a module port
+ *
+ * int get_array_offset(const ModulePortDataType &port) const;
+ * gets the start bit number of a port or netname entry
+ *
+ * bool is_array_upto(const ModulePortDataType &port) const;
+ * returns true if a port/net is an "upto" type port or netname entry
+ *
+ * const BitVectorDataType &get_port_bits(const ModulePortDataType &port) const;
+ * gets the bit vector of a module port
+ *
+ * const std::string& get_cell_type(const CellDataType &cell) const;
+ * gets the type of a cell
+ *
+ * void foreach_attr(const {ModuleDataType|CellDataType|ModulePortDataType|NetnameDataType} &obj, Func) const;
+ * calls Func(const std::string &name, const Property &value);
+ * for each attribute on a module, cell, module port or net
+ *
+ * void foreach_param(const CellDataType &obj, Func) const;
+ * calls Func(const std::string &name, const Property &value);
+ * for each parameter of a cell
+ *
+ * void foreach_setting(const ModuleDataType &obj, Func) const;
+ * calls Func(const std::string &name, const Property &value);
+ * for each module-level setting
+ *
+ * void foreach_port_dir(const CellDataType &cell, Func) const;
+ * calls Func(const std::string &name, PortType dir);
+ * for each port direction of a cell
+ *
+ * void foreach_port_conn(const CellDataType &cell, Func) const;
+ * calls Func(const std::string &name, const BitVectorDataType &conn);
+ * for each port connection of a cell
+ *
+ * const BitVectorDataType &get_net_bits(const NetnameDataType &net) const;
+ * gets the BitVector corresponding to the bits entry of a netname field
+ *
+ * int get_vector_length(const BitVectorDataType &bits) const;
+ * gets the length of a BitVector
+ *
+ * bool is_vector_bit_constant(const BitVectorDataType &bits, int i) const;
+ * returns true if bit <i> of bits is constant
+ *
+ * char get_vector_bit_constval(const BitVectorDataType &bits, int i) const;
+ * returns a char [01xz] corresponding to the constant value of bit <i>
+ *
+ * int get_vector_bit_signal(const BitVectorDataType &bits, int i) const;
+ * returns the signal number of vector bit <i>
+ *
+ */
+
+#include "design_utils.h"
+#include "log.h"
+#include "nextpnr.h"
+#include "util.h"
+NEXTPNR_NAMESPACE_BEGIN
+
+namespace {
+
+// Used for hierarchy resolution
+struct ModuleInfo
+{
+ bool is_top = false, is_blackbox = false, is_whitebox = false;
+ inline bool is_box() const { return is_blackbox || is_whitebox; }
+ std::unordered_set<IdString> instantiated_celltypes;
+};
+
+template <typename FrontendType> struct GenericFrontend
+{
+ GenericFrontend(Context *ctx, const FrontendType &impl) : ctx(ctx), impl(impl) {}
+ void operator()()
+ {
+ // Find which module is top
+ find_top_module();
+ HierModuleState m;
+ m.is_toplevel = true;
+ m.prefix = "";
+ m.path = top;
+ ctx->top_module = top;
+ // Do the actual import, starting from the top level module
+ import_module(m, top.str(ctx), top.str(ctx), mod_refs.at(top));
+ }
+
+ Context *ctx;
+ const FrontendType &impl;
+ using mod_dat_t = typename FrontendType::ModuleDataType;
+ using mod_port_dat_t = typename FrontendType::ModulePortDataType;
+ using cell_dat_t = typename FrontendType::CellDataType;
+ using netname_dat_t = typename FrontendType::NetnameDataType;
+ using bitvector_t = typename FrontendType::BitVectorDataType;
+
+ std::unordered_map<IdString, ModuleInfo> mods;
+ std::unordered_map<IdString, const mod_dat_t &> mod_refs;
+ IdString top;
+
+ // Process the list of modules and determine
+ // the top module
+ void find_top_module()
+ {
+ impl.foreach_module([&](const std::string &name, const mod_dat_t &mod) {
+ IdString mod_id = ctx->id(name);
+ auto &mi = mods[mod_id];
+ mod_refs.emplace(mod_id, mod);
+ impl.foreach_attr(mod, [&](const std::string &name, const Property &value) {
+ if (name == "top")
+ mi.is_top = (value.intval != 0);
+ else if (name == "blackbox")
+ mi.is_blackbox = (value.intval != 0);
+ else if (name == "whitebox")
+ mi.is_whitebox = (value.intval != 0);
+ });
+ impl.foreach_cell(mod, [&](const std::string &name, const cell_dat_t &cell) {
+ mi.instantiated_celltypes.insert(ctx->id(impl.get_cell_type(cell)));
+ });
+ });
+ // First of all, see if a top module has been manually specified
+ if (ctx->settings.count(ctx->id("frontend/top"))) {
+ IdString user_top = ctx->id(ctx->settings.at(ctx->id("frontend/top")).as_string());
+ if (!mods.count(user_top))
+ log_error("Top module '%s' not found!\n", ctx->nameOf(user_top));
+ top = user_top;
+ return;
+ }
+ // If not, look for a module with the top attribute set
+ IdString top_by_attr;
+ for (auto &mod : mods) {
+ if (mod.second.is_top && !mod.second.is_box()) {
+ if (top_by_attr != IdString())
+ log_error("Found multiple modules with (* top *) set (including %s and %s).\n",
+ ctx->nameOf(top_by_attr), ctx->nameOf(mod.first));
+ top_by_attr = mod.first;
+ }
+ }
+ if (top_by_attr != IdString()) {
+ top = top_by_attr;
+ return;
+ }
+ // Finally, attempt to autodetect the top module using hierarchy
+ // (a module that is not a box and is not used as a cell by any other module)
+ std::unordered_set<IdString> candidate_top;
+ for (auto &mod : mods)
+ if (!mod.second.is_box())
+ candidate_top.insert(mod.first);
+ for (auto &mod : mods)
+ for (auto &c : mod.second.instantiated_celltypes)
+ candidate_top.erase(c);
+ if (candidate_top.size() != 1) {
+ if (candidate_top.size() == 0)
+ log_info("No candidate top level modules.\n");
+ else
+ for (auto ctp : sorted(candidate_top))
+ log_info("Candidate top module: '%s'\n", ctx->nameOf(ctp));
+ log_error("Failed to autodetect top module, please specify using --top.\n");
+ }
+ top = *(candidate_top.begin());
+ }
+
+ // Create a unique name (guaranteed collision free) for a net or a cell; based on
+ // a base name and suffix. __unique__i will be be appended with increasing i
+ // if a collision is found until no collision
+ IdString unique_name(const std::string &base, const std::string &suffix, bool is_net)
+ {
+ IdString name;
+ int incr = 0;
+ do {
+ std::string comb = base + suffix;
+ if (incr > 0) {
+ comb += "__unique__";
+ comb += std::to_string(incr);
+ }
+ name = ctx->id(comb);
+ incr++;
+ } while (is_net ? ctx->nets.count(name) : ctx->cells.count(name));
+ return name;
+ }
+
+ // A flat index of map; designed to cope with merging nets where pointers to nets would go stale
+ // A net's udata points into this index
+ std::vector<NetInfo *> net_flatindex;
+ std::vector<std::vector<int>> net_old_indices; // the other indices of a net in net_flatindex for merging
+
+ // This structure contains some structures specific to the import of a module at
+ // a certain point in the hierarchy
+ struct HierModuleState
+ {
+ bool is_toplevel;
+ std::string prefix;
+ IdString parent_path, path;
+ // Map from index in module to "flat" index of nets
+ std::vector<int> index_to_net_flatindex;
+ // Get a reference to index_to_net; resizing if
+ // appropriate
+ int &net_by_idx(int idx)
+ {
+ NPNR_ASSERT(idx >= 0);
+ if (idx >= int(index_to_net_flatindex.size()))
+ index_to_net_flatindex.resize(idx + 1, -1);
+ return index_to_net_flatindex.at(idx);
+ }
+ std::unordered_map<IdString, std::vector<int>> port_to_bus;
+ // All of the names given to a net
+ std::vector<std::vector<std::string>> net_names;
+ };
+
+ void import_module(HierModuleState &m, const std::string &name, const std::string &type, const mod_dat_t &data)
+ {
+ NPNR_ASSERT(!ctx->hierarchy.count(m.path));
+ ctx->hierarchy[m.path].name = ctx->id(name);
+ ctx->hierarchy[m.path].type = ctx->id(type);
+ ctx->hierarchy[m.path].parent = m.parent_path;
+ ctx->hierarchy[m.path].fullpath = m.path;
+
+ std::vector<NetInfo *> index_to_net;
+ if (!m.is_toplevel) {
+ // Import port connections; for submodules only
+ import_port_connections(m, data);
+ } else {
+ // Just create a list of ports for netname resolution
+ impl.foreach_port(data,
+ [&](const std::string &name, const mod_port_dat_t &) { m.port_to_bus[ctx->id(name)]; });
+ // Import module-level attributes
+ impl.foreach_attr(
+ data, [&](const std::string &name, const Property &value) { ctx->attrs[ctx->id(name)] = value; });
+ // Import settings
+ impl.foreach_setting(data, [&](const std::string &name, const Property &value) {
+ ctx->settings[ctx->id(name)] = value;
+ });
+ }
+ import_module_netnames(m, data);
+ import_module_cells(m, data);
+ if (m.is_toplevel) {
+ import_toplevel_ports(m, data);
+ // Mark design as loaded through nextpnr
+ ctx->settings[ctx->id("synth")] = 1;
+ // Process nextpnr-specific attributes
+ ctx->attributesToArchInfo();
+ }
+ }
+
+ // Multiple labels might refer to the same net. Resolve conflicts for the primary name thus:
+ // - (toplevel) ports are always preferred
+ // - names with fewer $ are always prefered
+ // - between equal $ counts, fewer .s are prefered
+ // - ties are resolved alphabetically
+ bool prefer_netlabel(HierModuleState &m, const std::string &a, const std::string &b)
+ {
+ if (m.port_to_bus.count(ctx->id(a)))
+ return true;
+ if (m.port_to_bus.count(ctx->id(b)))
+ return false;
+
+ if (b.empty())
+ return true;
+ long a_dollars = std::count(a.begin(), a.end(), '$'), b_dollars = std::count(b.begin(), b.end(), '$');
+ if (a_dollars < b_dollars)
+ return true;
+ else if (a_dollars > b_dollars)
+ return false;
+ long a_dots = std::count(a.begin(), a.end(), '.'), b_dots = std::count(b.begin(), b.end(), '.');
+ if (a_dots < b_dots)
+ return true;
+ else if (a_dots > b_dots)
+ return false;
+ return a < b;
+ };
+
+ // Get a net by index in modulestate (not flatindex); creating it if it doesn't already exist
+ NetInfo *create_or_get_net(HierModuleState &m, int idx)
+ {
+ auto &midx = m.net_by_idx(idx);
+ if (midx != -1) {
+ return net_flatindex.at(midx);
+ } else {
+ std::string name;
+ if (idx < int(m.net_names.size()) && !m.net_names.at(idx).empty()) {
+ // Use the rule above to find the preferred name for a net
+ name = m.net_names.at(idx).at(0);
+ for (size_t j = 1; j < m.net_names.at(idx).size(); j++)
+ if (prefer_netlabel(m, m.net_names.at(idx).at(j), name))
+ name = m.net_names.at(idx).at(j);
+ } else {
+ name = "$frontend$" + std::to_string(idx);
+ }
+ NetInfo *net = ctx->createNet(unique_name(m.prefix, name, true));
+ // Add to the flat index of nets
+ net->udata = int(net_flatindex.size());
+ net_flatindex.push_back(net);
+ // Add to the module-level index of netsd
+ midx = net->udata;
+ // Create aliases for all possible names
+ if (idx < int(m.net_names.size()) && !m.net_names.at(idx).empty()) {
+ for (const auto &name : m.net_names.at(idx)) {
+ IdString name_id = ctx->id(name);
+ net->aliases.push_back(name_id);
+ ctx->net_aliases[name_id] = net->name;
+ }
+ } else {
+ net->aliases.push_back(net->name);
+ ctx->net_aliases[net->name] = net->name;
+ }
+ return net;
+ }
+ }
+
+ // Get the name of a vector bit given basename; settings and index
+ std::string get_bit_name(const std::string &base, int index, int length, int offset = 0, bool upto = false)
+ {
+ std::string port = base;
+ if (length == 1 && offset == 0)
+ return port;
+ int real_index;
+ if (upto)
+ real_index = offset + length - index - 1; // reversed ports like [0:7]
+ else
+ real_index = offset + index; // normal 'downto' ports like [7:0]
+ port += '[';
+ port += std::to_string(real_index);
+ port += ']';
+ return port;
+ }
+
+ // Import the netnames section of a module
+ void import_module_netnames(HierModuleState &m, const mod_dat_t &data)
+ {
+ impl.foreach_netname(data, [&](const std::string &basename, const netname_dat_t &nn) {
+ bool upto = impl.is_array_upto(nn);
+ int offset = impl.get_array_offset(nn);
+ const auto &bits = impl.get_net_bits(nn);
+ int width = impl.get_vector_length(bits);
+ for (int i = 0; i < width; i++) {
+ if (impl.is_vector_bit_constant(bits, i))
+ continue;
+
+ std::string bit_name = get_bit_name(basename, i, width, offset, upto);
+
+ int net_bit = impl.get_vector_bit_signal(bits, i);
+ int mapped_bit = m.net_by_idx(net_bit);
+ if (mapped_bit == -1) {
+ // Net doesn't exist yet. Add the name here to the list of candidate names so we have that for when
+ // we create it later
+ if (net_bit >= int(m.net_names.size()))
+ m.net_names.resize(net_bit + 1);
+ m.net_names.at(net_bit).push_back(bit_name);
+ } else {
+ // Net already exists; add this name as an alias
+ NetInfo *ni = net_flatindex.at(mapped_bit);
+ IdString alias_name = ctx->id(m.prefix + bit_name);
+ if (ctx->net_aliases.count(alias_name))
+ continue; // don't add duplicate aliases
+ ctx->net_aliases[alias_name] = ni->name;
+ ni->aliases.push_back(alias_name);
+ }
+ }
+ });
+ }
+
+ // Create a new constant net; given a hint for what the name should be and its value
+ NetInfo *create_constant_net(HierModuleState &m, const std::string &name_hint, char constval)
+ {
+ IdString name = unique_name(m.prefix, name_hint, true);
+ NetInfo *ni = ctx->createNet(name);
+ add_constant_driver(m, ni, constval);
+ return ni;
+ }
+
+ // Import a leaf cell - (white|black)box
+ void import_leaf_cell(HierModuleState &m, const std::string &name, const cell_dat_t &cd)
+ {
+ IdString inst_name = unique_name(m.prefix, name, false);
+ ctx->hierarchy[m.path].leaf_cells_by_gname[inst_name] = ctx->id(name);
+ ctx->hierarchy[m.path].leaf_cells[ctx->id(name)] = inst_name;
+ CellInfo *ci = ctx->createCell(inst_name, ctx->id(impl.get_cell_type(cd)));
+ ci->hierpath = m.path;
+ // Import port directions
+ std::unordered_map<IdString, PortType> port_dirs;
+ impl.foreach_port_dir(cd, [&](const std::string &port, PortType dir) { port_dirs[ctx->id(port)] = dir; });
+ // Import port connectivity
+ impl.foreach_port_conn(cd, [&](const std::string &name, const bitvector_t &bits) {
+ if (!port_dirs.count(ctx->id(name)))
+ log_error("Failed to get direction for port '%s' of cell '%s'\n", name.c_str(), inst_name.c_str(ctx));
+ PortType dir = port_dirs.at(ctx->id(name));
+ int width = impl.get_vector_length(bits);
+ for (int i = 0; i < width; i++) {
+ std::string port_bit_name = get_bit_name(name, i, width);
+ IdString port_bit_ids = ctx->id(port_bit_name);
+ // Create cell port
+ ci->ports[port_bit_ids].name = port_bit_ids;
+ ci->ports[port_bit_ids].type = dir;
+ // Resolve connectivity
+ NetInfo *net;
+ if (impl.is_vector_bit_constant(bits, i)) {
+ // Create a constant driver if one is needed
+ net = create_constant_net(m, name + "." + port_bit_name + "$const",
+ impl.get_vector_bit_constval(bits, i));
+ } else {
+ // Otherwise, lookup (creating if needed) the net with this index
+ net = create_or_get_net(m, impl.get_vector_bit_signal(bits, i));
+ }
+ NPNR_ASSERT(net != nullptr);
+
+ // Check for multiple drivers
+ if (dir == PORT_OUT && net->driver.cell != nullptr)
+ log_error("Net '%s' is multiply driven by cell ports %s.%s and %s.%s\n", ctx->nameOf(net),
+ ctx->nameOf(net->driver.cell), ctx->nameOf(net->driver.port), ctx->nameOf(inst_name),
+ port_bit_name.c_str());
+ connect_port(ctx, net, ci, port_bit_ids);
+ }
+ });
+ // Import attributes and parameters
+ impl.foreach_attr(cd,
+ [&](const std::string &name, const Property &value) { ci->attrs[ctx->id(name)] = value; });
+ impl.foreach_param(cd,
+ [&](const std::string &name, const Property &value) { ci->params[ctx->id(name)] = value; });
+ }
+
+ // Import a submodule cell
+ void import_submodule_cell(HierModuleState &m, const std::string &name, const cell_dat_t &cd)
+ {
+ HierModuleState submod;
+ submod.is_toplevel = false;
+ // Create mapping from submodule port to nets (referenced by index in flatindex)
+ impl.foreach_port_conn(cd, [&](const std::string &name, const bitvector_t &bits) {
+ int width = impl.get_vector_length(bits);
+ for (int i = 0; i < width; i++) {
+ // Index of port net in flatindex
+ int net_ref = -1;
+ if (impl.is_vector_bit_constant(bits, i)) {
+ // Create a constant driver if one is needed
+ std::string port_bit_name = get_bit_name(name, i, width);
+ NetInfo *cnet = create_constant_net(m, name + "." + port_bit_name + "$const",
+ impl.get_vector_bit_constval(bits, i));
+ cnet->udata = int(net_flatindex.size());
+ net_flatindex.push_back(cnet);
+ net_ref = cnet->udata;
+ } else {
+ // Otherwise, lookup (creating if needed) the net with given in-module index
+ net_ref = create_or_get_net(m, impl.get_vector_bit_signal(bits, i))->udata;
+ }
+ NPNR_ASSERT(net_ref != -1);
+ submod.port_to_bus[ctx->id(name)].push_back(net_ref);
+ }
+ });
+ // Create prefix for submodule
+ submod.prefix = m.prefix;
+ submod.prefix += name;
+ submod.prefix += '.';
+ submod.parent_path = m.path;
+ submod.path = ctx->id(m.path.str(ctx) + "/" + name);
+ ctx->hierarchy[m.path].hier_cells[ctx->id(name)] = submod.path;
+ // Do the submodule import
+ auto type = impl.get_cell_type(cd);
+ import_module(submod, name, type, mod_refs.at(ctx->id(type)));
+ }
+
+ // Import the cells section of a module
+ void import_module_cells(HierModuleState &m, const mod_dat_t &data)
+ {
+ impl.foreach_cell(data, [&](const std::string &cellname, const cell_dat_t &cd) {
+ IdString type = ctx->id(impl.get_cell_type(cd));
+ if (mods.count(type) && !mods.at(type).is_box()) {
+ // Module type is known; and not boxed. Import as a submodule by flattening hierarchy
+ import_submodule_cell(m, cellname, cd);
+ } else {
+ // Module type is unknown or boxes. Import as a leaf cell (nextpnr CellInfo)
+ import_leaf_cell(m, cellname, cd);
+ }
+ });
+ }
+
+ // Create a top level input/output buffer
+ CellInfo *create_iobuf(NetInfo *net, PortType dir, const std::string &name)
+ {
+ // Skip IOBUF insertion if this is a design checkpoint (where they will already exist)
+ if (ctx->settings.count(ctx->id("synth")))
+ return nullptr;
+ IdString name_id = ctx->id(name);
+ if (ctx->cells.count(name_id))
+ log_error("Cell '%s' of type '%s' with the same name as a top-level IO is not allowed.\n", name.c_str(),
+ ctx->cells.at(name_id)->type.c_str(ctx));
+ CellInfo *iobuf = ctx->createCell(name_id, ctx->id("unknown_iob"));
+ // Copy attributes from net to IOB
+ for (auto &attr : net->attrs)
+ iobuf->attrs[attr.first] = attr.second;
+ // What we do now depends on port type
+ if (dir == PORT_IN) {
+ iobuf->type = ctx->id("$nextpnr_ibuf");
+ iobuf->addOutput(ctx->id("O"));
+ if (net->driver.cell != nullptr) {
+ CellInfo *drv = net->driver.cell;
+ if (drv->type != ctx->id("$nextpnr_iobuf"))
+ log_error("Net '%s' is multiply driven by cell port %s.%s and top level input '%s'.\n",
+ ctx->nameOf(net), ctx->nameOf(drv), ctx->nameOf(net->driver.port), name.c_str());
+ // Special case: input, etc, directly drives inout
+ // Use the input net of the inout instead
+ net = drv->ports.at(ctx->id("I")).net;
+ }
+ NPNR_ASSERT(net->driver.cell == nullptr);
+ // Connect IBUF output and net
+ connect_port(ctx, net, iobuf, ctx->id("O"));
+ } else if (dir == PORT_OUT) {
+ iobuf->type = ctx->id("$nextpnr_obuf");
+ iobuf->addInput(ctx->id("I"));
+ // Connect IBUF input and net
+ connect_port(ctx, net, iobuf, ctx->id("I"));
+ } else if (dir == PORT_INOUT) {
+ iobuf->type = ctx->id("$nextpnr_iobuf");
+ iobuf->addInput(ctx->id("I"));
+ iobuf->addOutput(ctx->id("O"));
+ // Need to bifurcate the net to avoid multiple drivers and split
+ // the input/output parts of an inout
+ // Create a new net connecting only the current net's driver and the IOBUF input
+ // Then use the IOBUF output to drive all of the current net's users
+ NetInfo *split_iobuf_i = ctx->createNet(unique_name("", "$" + name + "$iobuf_i", true));
+ auto drv = net->driver;
+ if (drv.cell != nullptr) {
+ disconnect_port(ctx, drv.cell, drv.port);
+ drv.cell->ports[drv.port].net = nullptr;
+ connect_port(ctx, split_iobuf_i, drv.cell, drv.port);
+ }
+ connect_port(ctx, split_iobuf_i, iobuf, ctx->id("I"));
+ NPNR_ASSERT(net->driver.cell == nullptr);
+ connect_port(ctx, net, iobuf, ctx->id("O"));
+ }
+
+ PortInfo pinfo;
+ pinfo.name = name_id;
+ pinfo.net = net;
+ pinfo.type = dir;
+ ctx->ports[pinfo.name] = pinfo;
+
+ return iobuf;
+ }
+
+ // Import ports of the top level module
+ void import_toplevel_ports(HierModuleState &m, const mod_dat_t &data)
+ {
+ // For correct handling of inout ports driving other ports
+ // first import non-inouts then import inouts so that they bifurcate correctly
+ for (bool inout : {false, true}) {
+ impl.foreach_port(data, [&](const std::string &portname, const mod_port_dat_t &pd) {
+ const auto &port_bv = impl.get_port_bits(pd);
+ int offset = impl.get_array_offset(pd);
+ bool is_upto = impl.is_array_upto(pd);
+ int width = impl.get_vector_length(port_bv);
+ PortType dir = impl.get_port_dir(pd);
+ if ((dir == PORT_INOUT) != inout)
+ return;
+ for (int i = 0; i < width; i++) {
+ std::string pbit_name = get_bit_name(portname, i, width, offset, is_upto);
+ NetInfo *port_net = nullptr;
+ if (impl.is_vector_bit_constant(port_bv, i)) {
+ // Port bit is constant. Need to create a new constant net.
+ port_net =
+ create_constant_net(m, pbit_name + "$const", impl.get_vector_bit_constval(port_bv, i));
+ } else {
+ // Port bit is a signal. Need to create/get the associated net
+ port_net = create_or_get_net(m, impl.get_vector_bit_signal(port_bv, i));
+ }
+ create_iobuf(port_net, dir, pbit_name);
+ }
+ });
+ }
+ }
+
+ // Add a constant-driving VCC or GND cell to make a net constant
+ // (constval can be [01xz], x and z or no-ops)
+ int const_autoidx = 0;
+ void add_constant_driver(HierModuleState &m, NetInfo *net, char constval)
+ {
+
+ if (constval == 'x' || constval == 'z')
+ return; // 'x' or 'z' is the same as undriven
+ NPNR_ASSERT(constval == '0' || constval == '1');
+ IdString cell_name = unique_name(
+ m.prefix, net->name.str(ctx) + (constval == '1' ? "$VCC$" : "$GND$") + std::to_string(const_autoidx++),
+ false);
+ CellInfo *cc = ctx->createCell(cell_name, ctx->id(constval == '1' ? "VCC" : "GND"));
+ cc->ports[ctx->id("Y")].name = ctx->id("Y");
+ cc->ports[ctx->id("Y")].type = PORT_OUT;
+ if (net->driver.cell != nullptr)
+ log_error("Net '%s' is multiply driven by port %s.%s and constant '%c'\n", ctx->nameOf(net),
+ ctx->nameOf(net->driver.cell), ctx->nameOf(net->driver.port), constval);
+ connect_port(ctx, net, cc, ctx->id("Y"));
+ }
+
+ // Merge two nets - e.g. if one net in a submodule bifurcates to two output bits and therefore two different
+ // parent nets
+ void merge_nets(NetInfo *base, NetInfo *mergee)
+ {
+ // Resolve drivers
+ if (mergee->driver.cell != nullptr) {
+ if (base->driver.cell != nullptr)
+ log_error("Attempting to merge nets '%s' and '%s' due to port connectivity; but this would result in a "
+ "multiply driven net\n",
+ ctx->nameOf(base), ctx->nameOf(mergee));
+ else {
+ mergee->driver.cell->ports[mergee->driver.port].net = base;
+ base->driver = mergee->driver;
+ }
+ }
+ // Combine users
+ for (auto &usr : mergee->users) {
+ usr.cell->ports[usr.port].net = base;
+ base->users.push_back(usr);
+ }
+ // Point aliases to the new net
+ for (IdString alias : mergee->aliases) {
+ ctx->net_aliases[alias] = base->name;
+ base->aliases.push_back(alias);
+ }
+ // Create a new alias from mergee's name to new base name
+ ctx->net_aliases[mergee->name] = base->name;
+ // Update flat index of nets
+ for (auto old_idx : net_old_indices.at(mergee->udata)) {
+ net_old_indices.at(base->udata).push_back(old_idx);
+ net_flatindex.at(old_idx) = base;
+ }
+ net_old_indices.at(base->udata).push_back(mergee->udata);
+ net_flatindex.at(mergee->udata) = base;
+ net_old_indices.at(mergee->udata).clear();
+ // Remove merged net from context
+ ctx->nets.erase(mergee->name);
+ }
+
+ // Import connections between a submodule and its parent
+ void import_port_connections(HierModuleState &m, const mod_dat_t &data)
+ {
+ impl.foreach_port(data, [&](const std::string &name, const mod_port_dat_t &port) {
+ // CHECK: should disconnected module inputs really just be skipped; or is it better
+ // to insert a ground driver?
+ if (!m.port_to_bus.count(ctx->id(name)))
+ return;
+ auto &p2b = m.port_to_bus.at(ctx->id(name));
+ // Get direction and vector of port bits
+ PortType dir = impl.get_port_dir(port);
+ const auto &bv = impl.get_port_bits(port);
+ int bv_size = impl.get_vector_length(bv);
+ // Iterate over bits of port; making connections
+ for (int i = 0; i < std::min<int>(bv_size, p2b.size()); i++) {
+ int conn_net = p2b.at(i);
+ if (conn_net == -1)
+ continue;
+ NetInfo *conn_ni = net_flatindex.at(conn_net);
+ NPNR_ASSERT(conn_ni != nullptr);
+ if (impl.is_vector_bit_constant(bv, i)) {
+ // It is a constant, we might need to insert a constant driver here to drive the corresponding
+ // net in the parent
+ char constval = impl.get_vector_bit_constval(bv, i);
+ // Inputs cannot be driving a constant back to the parent
+ if (dir == PORT_IN)
+ log_error("Input port %s%s[%d] cannot be driving a constant '%c'.\n", m.prefix.c_str(),
+ name.c_str(), i, constval);
+ // Insert the constant driver
+ add_constant_driver(m, conn_ni, constval);
+ } else {
+ // If not driving a constant; simply make the port bit net index in the submodule correspond
+ // to connected net in the parent module
+ int &submod_net = m.net_by_idx(impl.get_vector_bit_signal(bv, i));
+ if (submod_net == -1) {
+ // A net at this index doesn't yet exist
+ // We can simply set this index to point to the net in the parent
+ submod_net = conn_net;
+ } else {
+ // A net at this index already exists (this would usually be a submodule net
+ // connected to more than one I/O port)
+ merge_nets(net_flatindex.at(submod_net), net_flatindex.at(conn_net));
+ }
+ }
+ }
+ });
+ }
+};
+} // namespace
+
+NEXTPNR_NAMESPACE_END
diff --git a/frontend/json_frontend.cc b/frontend/json_frontend.cc
new file mode 100644
index 00000000..d2e6248e
--- /dev/null
+++ b/frontend/json_frontend.cc
@@ -0,0 +1,203 @@
+/*
+ * nextpnr -- Next Generation Place and Route
+ *
+ * Copyright (C) 2019 David Shah <dave@ds0.me>
+ *
+ * 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 "json_frontend.h"
+#include "frontend_base.h"
+#include "json11.hpp"
+#include "log.h"
+#include "nextpnr.h"
+
+#include <streambuf>
+
+NEXTPNR_NAMESPACE_BEGIN
+
+using namespace json11;
+
+struct JsonFrontendImpl
+{
+ // See specification in frontend_base.h
+ JsonFrontendImpl(Json &root) : root(root){};
+ Json &root;
+ typedef const Json &ModuleDataType;
+ typedef const Json &ModulePortDataType;
+ typedef const Json &CellDataType;
+ typedef const Json &NetnameDataType;
+ typedef const Json::array &BitVectorDataType;
+
+ template <typename TFunc> void foreach_module(TFunc Func) const
+ {
+ for (const auto &mod : root.object_items())
+ Func(mod.first, mod.second);
+ }
+
+ template <typename TFunc> void foreach_port(const ModuleDataType &mod, TFunc Func) const
+ {
+ const auto &ports = mod["ports"];
+ if (ports.is_null())
+ return;
+ for (const auto &port : ports.object_items())
+ Func(port.first, port.second);
+ }
+
+ template <typename TFunc> void foreach_cell(const ModuleDataType &mod, TFunc Func) const
+ {
+ const auto &cells = mod["cells"];
+ if (cells.is_null())
+ return;
+ for (const auto &cell : cells.object_items())
+ Func(cell.first, cell.second);
+ }
+
+ template <typename TFunc> void foreach_netname(const ModuleDataType &mod, TFunc Func) const
+ {
+ const auto &netnames = mod["netnames"];
+ if (netnames.is_null())
+ return;
+ for (const auto &netname : netnames.object_items())
+ Func(netname.first, netname.second);
+ }
+
+ PortType lookup_portdir(const std::string &dir) const
+ {
+ if (dir == "input")
+ return PORT_IN;
+ else if (dir == "inout")
+ return PORT_INOUT;
+ else if (dir == "output")
+ return PORT_OUT;
+ else
+ NPNR_ASSERT_FALSE("invalid json port direction");
+ }
+
+ PortType get_port_dir(const ModulePortDataType &port) const
+ {
+ return lookup_portdir(port["direction"].string_value());
+ }
+
+ int get_array_offset(const Json &obj) const
+ {
+ auto offset = obj["offset"];
+ return offset.is_null() ? 0 : offset.int_value();
+ }
+
+ bool is_array_upto(const Json &obj) const
+ {
+ auto upto = obj["upto"];
+ return upto.is_null() ? false : bool(upto.int_value());
+ }
+
+ const BitVectorDataType &get_port_bits(const ModulePortDataType &port) const { return port["bits"].array_items(); }
+
+ const std::string &get_cell_type(const CellDataType &cell) const { return cell["type"].string_value(); }
+
+ Property parse_property(const Json &val) const
+ {
+ if (val.is_number())
+ return Property(val.int_value(), 32);
+ else
+ return Property::from_string(val.string_value());
+ }
+
+ template <typename TFunc> void foreach_attr(const Json &obj, TFunc Func) const
+ {
+ const auto &attrs = obj["attributes"];
+ if (attrs.is_null())
+ return;
+ for (const auto &attr : attrs.object_items()) {
+ Func(attr.first, parse_property(attr.second));
+ }
+ }
+
+ template <typename TFunc> void foreach_param(const Json &obj, TFunc Func) const
+ {
+ const auto &params = obj["parameters"];
+ if (params.is_null())
+ return;
+ for (const auto &param : params.object_items()) {
+ Func(param.first, parse_property(param.second));
+ }
+ }
+
+ template <typename TFunc> void foreach_setting(const Json &obj, TFunc Func) const
+ {
+ const auto &settings = obj["settings"];
+ if (settings.is_null())
+ return;
+ for (const auto &setting : settings.object_items()) {
+ Func(setting.first, parse_property(setting.second));
+ }
+ }
+
+ template <typename TFunc> void foreach_port_dir(const CellDataType &cell, TFunc Func) const
+ {
+ for (const auto &pdir : cell["port_directions"].object_items())
+ Func(pdir.first, lookup_portdir(pdir.second.string_value()));
+ }
+
+ template <typename TFunc> void foreach_port_conn(const CellDataType &cell, TFunc Func) const
+ {
+ for (const auto &pconn : cell["connections"].object_items())
+ Func(pconn.first, pconn.second.array_items());
+ }
+
+ const BitVectorDataType &get_net_bits(const NetnameDataType &net) const { return net["bits"].array_items(); }
+
+ int get_vector_length(const BitVectorDataType &bits) const { return int(bits.size()); }
+
+ bool is_vector_bit_constant(const BitVectorDataType &bits, int i) const
+ {
+ NPNR_ASSERT(i < int(bits.size()));
+ return bits[i].is_string();
+ }
+
+ char get_vector_bit_constval(const BitVectorDataType &bits, int i) const
+ {
+ auto s = bits.at(i).string_value();
+ NPNR_ASSERT(s.size() == 1);
+ return s.at(0);
+ }
+
+ int get_vector_bit_signal(const BitVectorDataType &bits, int i) const
+ {
+ NPNR_ASSERT(bits.at(i).is_number());
+ return bits.at(i).int_value();
+ }
+};
+
+bool parse_json(std::istream &in, const std::string &filename, Context *ctx)
+{
+ Json root;
+ {
+ if (!in)
+ log_error("Failed to open JSON file '%s'.\n", filename.c_str());
+ std::string json_str((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>());
+ std::string error;
+ root = Json::parse(json_str, error, JsonParse::COMMENTS);
+ if (root.is_null())
+ log_error("Failed to parse JSON file '%s': %s.\n", filename.c_str(), error.c_str());
+ root = root["modules"];
+ if (root.is_null())
+ log_error("JSON file '%s' doesn't look like a netlist (doesn't contain \"modules\" key)\n",
+ filename.c_str());
+ }
+ GenericFrontend<JsonFrontendImpl>(ctx, JsonFrontendImpl(root))();
+ return true;
+}
+
+NEXTPNR_NAMESPACE_END
diff --git a/json/jsonparse.h b/frontend/json_frontend.h
index 65e3f02e..4d6c28f7 100644
--- a/json/jsonparse.h
+++ b/frontend/json_frontend.h
@@ -1,7 +1,7 @@
/*
* nextpnr -- Next Generation Place and Route
*
- * Copyright (C) 2018 SymbioticEDA
+ * Copyright (C) 2019 David Shah <dave@ds0.me>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@@ -17,18 +17,10 @@
*
*/
-#ifndef JSON_PARSER
-#define JSON_PARSER
-
-#include <istream>
-#include <string>
#include "nextpnr.h"
NEXTPNR_NAMESPACE_BEGIN
-extern bool parse_json_file(std::istream &, std::string &, Context *);
-extern bool load_json_settings(std::istream &f, std::string &filename,
- std::unordered_map<std::string, Property> &values);
-NEXTPNR_NAMESPACE_END
+bool parse_json(std::istream &in, const std::string &filename, Context *ctx);
-#endif
+NEXTPNR_NAMESPACE_END
diff --git a/generic/arch.cc b/generic/arch.cc
index 9e59540e..c1c01d26 100644
--- a/generic/arch.cc
+++ b/generic/arch.cc
@@ -21,11 +21,36 @@
#include <math.h>
#include "nextpnr.h"
#include "placer1.h"
+#include "placer_heap.h"
#include "router1.h"
#include "util.h"
NEXTPNR_NAMESPACE_BEGIN
+WireInfo &Arch::wire_info(IdString wire)
+{
+ auto w = wires.find(wire);
+ if (w == wires.end())
+ NPNR_ASSERT_FALSE_STR("no wire named " + wire.str(this));
+ return w->second;
+}
+
+PipInfo &Arch::pip_info(IdString pip)
+{
+ auto p = pips.find(pip);
+ if (p == pips.end())
+ NPNR_ASSERT_FALSE_STR("no pip named " + pip.str(this));
+ return p->second;
+}
+
+BelInfo &Arch::bel_info(IdString bel)
+{
+ auto b = bels.find(bel);
+ if (b == bels.end())
+ NPNR_ASSERT_FALSE_STR("no bel named " + bel.str(this));
+ return b->second;
+}
+
void Arch::addWire(IdString name, IdString type, int x, int y)
{
NPNR_ASSERT(wires.count(name) == 0);
@@ -49,8 +74,8 @@ void Arch::addPip(IdString name, IdString type, IdString srcWire, IdString dstWi
pi.delay = delay;
pi.loc = loc;
- wires.at(srcWire).downhill.push_back(name);
- wires.at(dstWire).uphill.push_back(name);
+ wire_info(srcWire).downhill.push_back(name);
+ wire_info(dstWire).uphill.push_back(name);
pip_ids.push_back(name);
if (int(tilePipDimZ.size()) <= loc.x)
@@ -74,7 +99,7 @@ void Arch::addAlias(IdString name, IdString type, IdString srcWire, IdString dst
pi.dstWire = dstWire;
pi.delay = delay;
- wires.at(srcWire).aliases.push_back(name);
+ wire_info(srcWire).aliases.push_back(name);
pip_ids.push_back(name);
}
@@ -114,38 +139,38 @@ void Arch::addBel(IdString name, IdString type, Loc loc, bool gb)
void Arch::addBelInput(IdString bel, IdString name, IdString wire)
{
- NPNR_ASSERT(bels.at(bel).pins.count(name) == 0);
- PinInfo &pi = bels.at(bel).pins[name];
+ NPNR_ASSERT(bel_info(bel).pins.count(name) == 0);
+ PinInfo &pi = bel_info(bel).pins[name];
pi.name = name;
pi.wire = wire;
pi.type = PORT_IN;
- wires.at(wire).downhill_bel_pins.push_back(BelPin{bel, name});
- wires.at(wire).bel_pins.push_back(BelPin{bel, name});
+ wire_info(wire).downhill_bel_pins.push_back(BelPin{bel, name});
+ wire_info(wire).bel_pins.push_back(BelPin{bel, name});
}
void Arch::addBelOutput(IdString bel, IdString name, IdString wire)
{
- NPNR_ASSERT(bels.at(bel).pins.count(name) == 0);
- PinInfo &pi = bels.at(bel).pins[name];
+ NPNR_ASSERT(bel_info(bel).pins.count(name) == 0);
+ PinInfo &pi = bel_info(bel).pins[name];
pi.name = name;
pi.wire = wire;
pi.type = PORT_OUT;
- wires.at(wire).uphill_bel_pin = BelPin{bel, name};
- wires.at(wire).bel_pins.push_back(BelPin{bel, name});
+ wire_info(wire).uphill_bel_pin = BelPin{bel, name};
+ wire_info(wire).bel_pins.push_back(BelPin{bel, name});
}
void Arch::addBelInout(IdString bel, IdString name, IdString wire)
{
- NPNR_ASSERT(bels.at(bel).pins.count(name) == 0);
- PinInfo &pi = bels.at(bel).pins[name];
+ NPNR_ASSERT(bel_info(bel).pins.count(name) == 0);
+ PinInfo &pi = bel_info(bel).pins[name];
pi.name = name;
pi.wire = wire;
pi.type = PORT_INOUT;
- wires.at(wire).downhill_bel_pins.push_back(BelPin{bel, name});
- wires.at(wire).bel_pins.push_back(BelPin{bel, name});
+ wire_info(wire).downhill_bel_pins.push_back(BelPin{bel, name});
+ wire_info(wire).bel_pins.push_back(BelPin{bel, name});
}
void Arch::addGroupBel(IdString group, IdString bel) { groups[group].bels.push_back(bel); }
@@ -164,19 +189,19 @@ void Arch::addDecalGraphic(DecalId decal, const GraphicElement &graphic)
void Arch::setWireDecal(WireId wire, DecalXY decalxy)
{
- wires.at(wire).decalxy = decalxy;
+ wire_info(wire).decalxy = decalxy;
refreshUiWire(wire);
}
void Arch::setPipDecal(PipId pip, DecalXY decalxy)
{
- pips.at(pip).decalxy = decalxy;
+ pip_info(pip).decalxy = decalxy;
refreshUiPip(pip);
}
void Arch::setBelDecal(BelId bel, DecalXY decalxy)
{
- bels.at(bel).decalxy = decalxy;
+ bel_info(bel).decalxy = decalxy;
refreshUiBel(bel);
}
@@ -186,11 +211,11 @@ void Arch::setGroupDecal(GroupId group, DecalXY decalxy)
refreshUiGroup(group);
}
-void Arch::setWireAttr(IdString wire, IdString key, const std::string &value) { wires.at(wire).attrs[key] = value; }
+void Arch::setWireAttr(IdString wire, IdString key, const std::string &value) { wire_info(wire).attrs[key] = value; }
-void Arch::setPipAttr(IdString pip, IdString key, const std::string &value) { pips.at(pip).attrs[key] = value; }
+void Arch::setPipAttr(IdString pip, IdString key, const std::string &value) { pip_info(pip).attrs[key] = value; }
-void Arch::setBelAttr(IdString bel, IdString key, const std::string &value) { bels.at(bel).attrs[key] = value; }
+void Arch::setBelAttr(IdString bel, IdString key, const std::string &value) { bel_info(bel).attrs[key] = value; }
void Arch::setLutK(int K) { args.K = K; }
@@ -494,8 +519,29 @@ bool Arch::getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay
bool Arch::place()
{
std::string placer = str_or_default(settings, id("placer"), defaultPlacer);
- // FIXME: No HeAP because it needs a list of IO buffers
- if (placer == "sa") {
+ if (placer == "heap") {
+ bool have_iobuf_or_constr = false;
+ for (auto cell : sorted(cells)) {
+ CellInfo *ci = cell.second;
+ if (ci->type == id("GENERIC_IOB") || ci->bel != BelId() || ci->attrs.count(id("BEL"))) {
+ have_iobuf_or_constr = true;
+ break;
+ }
+ }
+ bool retVal;
+ if (!have_iobuf_or_constr) {
+ log_warning("Unable to use HeAP due to a lack of IO buffers or constrained cells as anchors; reverting to "
+ "SA.\n");
+ retVal = placer1(getCtx(), Placer1Cfg(getCtx()));
+ } else {
+ PlacerHeapCfg cfg(getCtx());
+ cfg.ioBufTypes.insert(id("GENERIC_IOB"));
+ retVal = placer_heap(getCtx(), cfg);
+ }
+ getCtx()->settings[getCtx()->id("place")] = 1;
+ archInfoToAttributes();
+ return retVal;
+ } else if (placer == "sa") {
bool retVal = placer1(getCtx(), Placer1Cfg(getCtx()));
getCtx()->settings[getCtx()->id("place")] = 1;
archInfoToAttributes();
@@ -596,9 +642,17 @@ bool Arch::isBelLocationValid(BelId bel) const
return cellsCompatible(cells.data(), int(cells.size()));
}
+#ifdef WITH_HEAP
+const std::string Arch::defaultPlacer = "heap";
+#else
const std::string Arch::defaultPlacer = "sa";
-const std::vector<std::string> Arch::availablePlacers = {"sa"};
+#endif
+const std::vector<std::string> Arch::availablePlacers = {"sa",
+#ifdef WITH_HEAP
+ "heap"
+#endif
+};
void Arch::assignArchInfo()
{
for (auto &cell : getCtx()->cells) {
diff --git a/generic/arch.h b/generic/arch.h
index e9d3593c..444d2636 100644
--- a/generic/arch.h
+++ b/generic/arch.h
@@ -122,6 +122,11 @@ struct Arch : BaseCtx
std::unordered_map<IdString, BelInfo> bels;
std::unordered_map<GroupId, GroupInfo> groups;
+ // These functions include useful errors if not found
+ WireInfo &wire_info(IdString wire);
+ PipInfo &pip_info(IdString wire);
+ BelInfo &bel_info(IdString wire);
+
std::vector<IdString> bel_ids, wire_ids, pip_ids;
std::unordered_map<Loc, BelId> bel_by_loc;
diff --git a/generic/arch_pybindings.cc b/generic/arch_pybindings.cc
index 8526e409..2600cac0 100644
--- a/generic/arch_pybindings.cc
+++ b/generic/arch_pybindings.cc
@@ -141,6 +141,7 @@ void arch_wrap_python()
typedef std::unordered_map<IdString, std::unique_ptr<CellInfo>> CellMap;
typedef std::unordered_map<IdString, std::unique_ptr<NetInfo>> NetMap;
+ typedef std::unordered_map<IdString, HierarchicalCell> HierarchyMap;
readonly_wrapper<Context, decltype(&Context::cells), &Context::cells, wrap_context<CellMap &>>::def_wrap(ctx_cls,
"cells");
@@ -231,6 +232,7 @@ void arch_wrap_python()
WRAP_MAP_UPTR(CellMap, "IdCellMap");
WRAP_MAP_UPTR(NetMap, "IdNetMap");
+ WRAP_MAP(HierarchyMap, wrap_context<HierarchicalCell &>, "HierarchyMap");
WRAP_VECTOR(const std::vector<IdString>, conv_to_str<IdString>);
}
diff --git a/generic/cells.cc b/generic/cells.cc
index 53886e33..c4421f90 100644
--- a/generic/cells.cc
+++ b/generic/cells.cc
@@ -42,7 +42,7 @@ std::unique_ptr<CellInfo> create_generic_cell(Context *ctx, IdString type, std::
}
new_cell->type = type;
if (type == ctx->id("GENERIC_SLICE")) {
- new_cell->params[ctx->id("K")] = std::to_string(ctx->args.K);
+ new_cell->params[ctx->id("K")] = ctx->args.K;
new_cell->params[ctx->id("INIT")] = 0;
new_cell->params[ctx->id("FF_USED")] = 0;
@@ -51,6 +51,7 @@ std::unique_ptr<CellInfo> create_generic_cell(Context *ctx, IdString type, std::
add_port(ctx, new_cell.get(), "CLK", PORT_IN);
+ add_port(ctx, new_cell.get(), "F", PORT_OUT);
add_port(ctx, new_cell.get(), "Q", PORT_OUT);
} else if (type == ctx->id("GENERIC_IOB")) {
new_cell->params[ctx->id("INPUT_USED")] = 0;
@@ -80,8 +81,8 @@ void lut_to_lc(const Context *ctx, CellInfo *lut, CellInfo *lc, bool no_dff)
}
if (no_dff) {
- replace_port(lut, ctx->id("Q"), lc, ctx->id("Q"));
lc->params[ctx->id("FF_USED")] = 0;
+ replace_port(lut, ctx->id("Q"), lc, ctx->id("F"));
}
}
@@ -91,7 +92,14 @@ void dff_to_lc(const Context *ctx, CellInfo *dff, CellInfo *lc, bool pass_thru_l
replace_port(dff, ctx->id("CLK"), lc, ctx->id("CLK"));
if (pass_thru_lut) {
- lc->params[ctx->id("INIT")] = 2;
+ // Fill LUT with alternating 10
+ const int init_size = 1 << lc->params[ctx->id("K")].as_int64();
+ std::string init;
+ init.reserve(init_size);
+ for (int i = 0; i < init_size; i += 2)
+ init.append("10");
+ lc->params[ctx->id("INIT")] = Property::from_string(init);
+
replace_port(dff, ctx->id("D"), lc, ctx->id("I[0]"));
}
diff --git a/generic/examples/.gitignore b/generic/examples/.gitignore
index 38e95de5..ad2fba28 100644
--- a/generic/examples/.gitignore
+++ b/generic/examples/.gitignore
@@ -1,3 +1,6 @@
blinky.fasm
__pycache__
*.pyc
+pnrblinky.v
+/blinky_simtest
+*.vcd
diff --git a/generic/examples/bitstream.py b/generic/examples/bitstream.py
index 1ab94f0c..7f0b5c07 100644
--- a/generic/examples/bitstream.py
+++ b/generic/examples/bitstream.py
@@ -14,4 +14,4 @@ param_map = {
}
with open("blinky.fasm", "w") as f:
- write_fasm(ctx, param_map, f) \ No newline at end of file
+ write_fasm(ctx, param_map, f)
diff --git a/generic/examples/blinky.v b/generic/examples/blinky.v
index b7cb1b86..42becb72 100644
--- a/generic/examples/blinky.v
+++ b/generic/examples/blinky.v
@@ -1,9 +1,12 @@
-module top(input clk, output reg [7:0] leds);
+module top(input clk, rst, output reg [7:0] leds);
-reg [25:0] ctr;
+reg [7:0] ctr;
always @(posedge clk)
- ctr <= ctr + 1'b1;
+ if (rst)
+ ctr <= 8'h00;
+ else
+ ctr <= ctr + 1'b1;
-assign leds = ctr[25:18];
+assign leds = ctr;
-endmodule \ No newline at end of file
+endmodule
diff --git a/generic/examples/blinky_tb.v b/generic/examples/blinky_tb.v
new file mode 100644
index 00000000..f9925e6f
--- /dev/null
+++ b/generic/examples/blinky_tb.v
@@ -0,0 +1,38 @@
+`timescale 1ns / 1ps
+module blinky_tb;
+
+reg clk = 1'b0, rst = 1'b0;
+reg [7:0] ctr_gold = 8'h00;
+wire [7:0] ctr_gate;
+top dut_i(.clk(clk), .rst(rst), .leds(ctr_gate));
+
+task oneclk;
+ begin
+ clk = 1'b1;
+ #10;
+ clk = 1'b0;
+ #10;
+ end
+endtask
+
+initial begin
+ $dumpfile("blinky_simtest.vcd");
+ $dumpvars(0, blinky_tb);
+ #100;
+ rst = 1'b1;
+ repeat (5) oneclk;
+ #5
+ rst = 1'b0;
+ #5
+ repeat (500) begin
+ if (ctr_gold !== ctr_gate) begin
+ $display("mismatch gold=%b gate=%b", ctr_gold, ctr_gate);
+ $stop;
+ end
+ oneclk;
+ ctr_gold = ctr_gold + 1'b1;
+ end
+ $finish;
+end
+
+endmodule
diff --git a/generic/examples/simple.py b/generic/examples/simple.py
index 9339b68a..9379b505 100644
--- a/generic/examples/simple.py
+++ b/generic/examples/simple.py
@@ -9,6 +9,7 @@ for x in range(X):
for z in range(N):
ctx.addWire(name="X%dY%dZ%d_CLK" % (x, y, z), type="BEL_CLK", x=x, y=y)
ctx.addWire(name="X%dY%dZ%d_Q" % (x, y, z), type="BEL_Q", x=x, y=y)
+ ctx.addWire(name="X%dY%dZ%d_F" % (x, y, z), type="BEL_F", x=x, y=y)
for i in range(K):
ctx.addWire(name="X%dY%dZ%d_I%d" % (x, y, z, i), type="BEL_I", x=x, y=y)
# Local wires
@@ -29,6 +30,7 @@ for x in range(X):
ctx.addBelInput(bel="X%dY%d_SLICE%d" % (x, y, z), name="CLK", wire="X%dY%dZ%d_CLK" % (x, y, z))
for k in range(K):
ctx.addBelInput(bel="X%dY%d_SLICE%d" % (x, y, z), name="I[%d]" % k, wire="X%dY%dZ%d_I%d" % (x, y, z, k))
+ ctx.addBelOutput(bel="X%dY%d_SLICE%d" % (x, y, z), name="F", wire="X%dY%dZ%d_F" % (x, y, z))
ctx.addBelOutput(bel="X%dY%d_SLICE%d" % (x, y, z), name="Q", wire="X%dY%dZ%d_Q" % (x, y, z))
for x in range(X):
@@ -48,6 +50,9 @@ for x in range(X):
# Pips from bel outputs to locals
def create_output_pips(dst, offset, skip):
for i in range(offset % skip, N, skip):
+ src = "X%dY%dZ%d_F" % (x, y, i)
+ ctx.addPip(name="X%dY%d.%s.%s" % (x, y, src, dst), type="BEL_OUTPUT",
+ srcWire=src, dstWire=dst, delay=ctx.getDelayFromNS(0.05), loc=Loc(x, y, 0))
src = "X%dY%dZ%d_Q" % (x, y, i)
ctx.addPip(name="X%dY%d.%s.%s" % (x, y, src, dst), type="BEL_OUTPUT",
srcWire=src, dstWire=dst, delay=ctx.getDelayFromNS(0.05), loc=Loc(x, y, 0))
@@ -69,4 +74,4 @@ for x in range(X):
create_neighbour_pips(dst, x, y+1, (l + 4) % Sl, Sl)
create_neighbour_pips(dst, x+1, y-1, (l + 5) % Sl, Sl)
create_neighbour_pips(dst, x+1, y, (l + 6) % Sl, Sl)
- create_neighbour_pips(dst, x+1, y+1, (l + 7) % Sl, Sl) \ No newline at end of file
+ create_neighbour_pips(dst, x+1, y+1, (l + 7) % Sl, Sl)
diff --git a/generic/examples/simple.sh b/generic/examples/simple.sh
index 8ae903f9..76bc6168 100755
--- a/generic/examples/simple.sh
+++ b/generic/examples/simple.sh
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
set -ex
yosys -p "tcl ../synth/synth_generic.tcl 4 blinky.json" blinky.v
-${NEXTPNR:-../../nextpnr-generic} --pre-pack simple.py --pre-place simple_timing.py --json blinky.json --post-route bitstream.py
+${NEXTPNR:-../../nextpnr-generic} --pre-pack simple.py --pre-place simple_timing.py --json blinky.json --post-route bitstream.py --write pnrblinky.json
+yosys -p "read_verilog -lib ../synth/prims.v; read_json pnrblinky.json; dump -o blinky.il; show -format png -prefix blinky"
diff --git a/generic/examples/simple_timing.py b/generic/examples/simple_timing.py
index 2ccb197e..1067b556 100644
--- a/generic/examples/simple_timing.py
+++ b/generic/examples/simple_timing.py
@@ -1,15 +1,13 @@
for cname, cell in ctx.cells:
- if cell.type != "GENERIC_SLICE":
- continue
- if cname in ("$PACKER_GND", "$PACKER_VCC"):
- continue
- K = int(cell.params["K"])
- if int(cell.params["FF_USED"], 2) == 1:
- ctx.addCellTimingClock(cell=cname, port="CLK")
- for i in range(K):
- ctx.addCellTimingSetupHold(cell=cname, port="I[%d]" % i, clock="CLK",
- setup=ctx.getDelayFromNS(0.2), hold=ctx.getDelayFromNS(0))
- ctx.addCellTimingClockToOut(cell=cname, port="Q", clock="CLK", clktoq=ctx.getDelayFromNS(0.2))
- else:
- for i in range(K):
- ctx.addCellTimingDelay(cell=cname, fromPort="I[%d]" % i, toPort="Q", delay=ctx.getDelayFromNS(0.2)) \ No newline at end of file
+ if cell.type != "GENERIC_SLICE":
+ continue
+ if cname in ("$PACKER_GND", "$PACKER_VCC"):
+ continue
+ K = int(cell.params["K"])
+ ctx.addCellTimingClock(cell=cname, port="CLK")
+ for i in range(K):
+ ctx.addCellTimingSetupHold(cell=cname, port="I[%d]" % i, clock="CLK",
+ setup=ctx.getDelayFromNS(0.2), hold=ctx.getDelayFromNS(0))
+ ctx.addCellTimingClockToOut(cell=cname, port="Q", clock="CLK", clktoq=ctx.getDelayFromNS(0.2))
+ for i in range(K):
+ ctx.addCellTimingDelay(cell=cname, fromPort="I[%d]" % i, toPort="F", delay=ctx.getDelayFromNS(0.2))
diff --git a/generic/examples/simtest.sh b/generic/examples/simtest.sh
new file mode 100755
index 00000000..ef328914
--- /dev/null
+++ b/generic/examples/simtest.sh
@@ -0,0 +1,7 @@
+#!/usr/bin/env bash
+set -ex
+yosys -p "tcl ../synth/synth_generic.tcl 4 blinky.json" blinky.v
+${NEXTPNR:-../../nextpnr-generic} --no-iobs --pre-pack simple.py --pre-place simple_timing.py --json blinky.json --post-route bitstream.py --write pnrblinky.json
+yosys -p "read_json pnrblinky.json; write_verilog -noattr -norename pnrblinky.v"
+iverilog -o blinky_simtest ../synth/prims.v blinky_tb.v pnrblinky.v
+vvp -N ./blinky_simtest
diff --git a/generic/main.cc b/generic/main.cc
index 7dfc6aa7..bb780996 100644
--- a/generic/main.cc
+++ b/generic/main.cc
@@ -46,6 +46,7 @@ po::options_description GenericCommandHandler::getArchOptions()
{
po::options_description specific("Architecture specific options");
specific.add_options()("generic", "set device type to generic");
+ specific.add_options()("no-iobs", "disable automatic IO buffer insertion");
return specific;
}
@@ -59,7 +60,10 @@ std::unique_ptr<Context> GenericCommandHandler::createContext(std::unordered_map
if (arch_name != "generic")
log_error("Unsuported architecture '%s'.\n", arch_name.c_str());
}
- return std::unique_ptr<Context>(new Context(chipArgs));
+ auto ctx = std::unique_ptr<Context>(new Context(chipArgs));
+ if (vm.count("no-iobs"))
+ ctx->settings[ctx->id("disable_iobs")] = Property::State::S1;
+ return ctx;
}
int main(int argc, char *argv[])
diff --git a/generic/pack.cc b/generic/pack.cc
index 3dc12bc1..e92e04c5 100644
--- a/generic/pack.cc
+++ b/generic/pack.cc
@@ -150,20 +150,21 @@ static void pack_constants(Context *ctx)
log_info("Packing constants..\n");
std::unique_ptr<CellInfo> gnd_cell = create_generic_cell(ctx, ctx->id("GENERIC_SLICE"), "$PACKER_GND");
- gnd_cell->params[ctx->id("INIT")] = 0;
+ gnd_cell->params[ctx->id("INIT")] = Property(0, 1 << ctx->args.K);
std::unique_ptr<NetInfo> gnd_net = std::unique_ptr<NetInfo>(new NetInfo);
gnd_net->name = ctx->id("$PACKER_GND_NET");
gnd_net->driver.cell = gnd_cell.get();
- gnd_net->driver.port = ctx->id("Q");
- gnd_cell->ports.at(ctx->id("Q")).net = gnd_net.get();
+ gnd_net->driver.port = ctx->id("F");
+ gnd_cell->ports.at(ctx->id("F")).net = gnd_net.get();
std::unique_ptr<CellInfo> vcc_cell = create_generic_cell(ctx, ctx->id("GENERIC_SLICE"), "$PACKER_VCC");
- vcc_cell->params[ctx->id("INIT")] = 1;
+ // Fill with 1s
+ vcc_cell->params[ctx->id("INIT")] = Property(Property::S1).extract(0, (1 << ctx->args.K), Property::S1);
std::unique_ptr<NetInfo> vcc_net = std::unique_ptr<NetInfo>(new NetInfo);
vcc_net->name = ctx->id("$PACKER_VCC_NET");
vcc_net->driver.cell = vcc_cell.get();
- vcc_net->driver.port = ctx->id("Q");
- vcc_cell->ports.at(ctx->id("Q")).net = vcc_net.get();
+ vcc_net->driver.port = ctx->id("F");
+ vcc_cell->ports.at(ctx->id("F")).net = vcc_net.get();
std::vector<IdString> dead_nets;
@@ -249,6 +250,10 @@ static void pack_io(Context *ctx)
delete_nets.insert(net2->name);
}
}
+ } else if (bool_or_default(ctx->settings, ctx->id("disable_iobs"))) {
+ // No IO buffer insertion; just remove nextpnr_[io]buf
+ for (auto &p : ci->ports)
+ disconnect_port(ctx, ci, p.first);
} else {
// Create a GENERIC_IOB buffer
std::unique_ptr<CellInfo> ice_cell =
diff --git a/generic/synth/cells_map.v b/generic/synth/cells_map.v
index a6027534..1d0939e0 100644
--- a/generic/synth/cells_map.v
+++ b/generic/synth/cells_map.v
@@ -4,7 +4,9 @@ module \$lut (A, Y);
input [WIDTH-1:0] A;
output Y;
- LUT #(.K(`LUT_K), .INIT(LUT)) _TECHMAP_REPLACE_ (.I(A), .Q(Y));
+ localparam rep = 1<<(`LUT_K-WIDTH);
+
+ LUT #(.K(`LUT_K), .INIT({rep{LUT}})) _TECHMAP_REPLACE_ (.I(A), .Q(Y));
endmodule
module \$_DFF_P_ (input D, C, output Q); DFF _TECHMAP_REPLACE_ (.D(D), .Q(Q), .CLK(C)); endmodule
diff --git a/generic/synth/prims.v b/generic/synth/prims.v
index 95fcfac7..ca445e6e 100644
--- a/generic/synth/prims.v
+++ b/generic/synth/prims.v
@@ -2,18 +2,27 @@
module LUT #(
parameter K = 4,
- parameter [2**K-1:0] INIT = 0,
+ parameter [2**K-1:0] INIT = 0
) (
input [K-1:0] I,
output Q
);
- assign Q = INIT[I];
+ wire [K-1:0] I_pd;
+
+ genvar ii;
+ generate
+ for (ii = 0; ii < K; ii = ii + 1'b1)
+ assign I_pd[ii] = (I[ii] === 1'bz) ? 1'b0 : I[ii];
+ endgenerate
+
+ assign Q = INIT[I_pd];
endmodule
module DFF (
input CLK, D,
output reg Q
);
+ initial Q = 1'b0;
always @(posedge CLK)
Q <= D;
endmodule
@@ -25,17 +34,16 @@ module GENERIC_SLICE #(
) (
input CLK,
input [K-1:0] I,
+ output F,
output Q
);
+ wire f_wire;
- wire lut_q;
- LUT #(.K(K), .INIT(INIT)) lut_i(.I(I), .Q(lut_q));
+ LUT #(.K(K), .INIT(INIT)) lut_i(.I(I), .Q(f_wire));
- generate if (FF_USED)
- DFF dff_i(.CLK(CLK), .D(lut_q), .Q(Q));
- else
- assign Q = lut_q;
- endgenerate
+ DFF dff_i(.CLK(CLK), .D(f_wire), .Q(Q));
+
+ assign F = f_wire;
endmodule
module GENERIC_IOB #(
@@ -56,4 +64,4 @@ module GENERIC_IOB #(
generate if (INPUT_USED)
assign O = PAD;
endgenerate
-endmodule \ No newline at end of file
+endmodule
diff --git a/gui/basewindow.cc b/gui/basewindow.cc
index 7290f73c..c3dbc131 100644
--- a/gui/basewindow.cc
+++ b/gui/basewindow.cc
@@ -28,7 +28,6 @@
#include <fstream>
#include "designwidget.h"
#include "fpgaviewwidget.h"
-#include "jsonparse.h"
#include "jsonwrite.h"
#include "log.h"
#include "mainwindow.h"
diff --git a/gui/ice40/mainwindow.cc b/gui/ice40/mainwindow.cc
index ccff2117..dc8a3a23 100644
--- a/gui/ice40/mainwindow.cc
+++ b/gui/ice40/mainwindow.cc
@@ -27,7 +27,6 @@
#include <fstream>
#include "bitstream.h"
#include "design_utils.h"
-#include "jsonparse.h"
#include "log.h"
#include "pcf.h"
diff --git a/ice40/arch.cc b/ice40/arch.cc
index cc50cb68..0f8e5969 100644
--- a/ice40/arch.cc
+++ b/ice40/arch.cc
@@ -1245,7 +1245,11 @@ void Arch::assignCellInfo(CellInfo *cell)
}
}
+#ifdef WITH_HEAP
+const std::string Arch::defaultPlacer = "heap";
+#else
const std::string Arch::defaultPlacer = "sa";
+#endif
const std::vector<std::string> Arch::availablePlacers = {"sa",
#ifdef WITH_HEAP
diff --git a/ice40/arch_pybindings.cc b/ice40/arch_pybindings.cc
index cef7c58f..e2022091 100644
--- a/ice40/arch_pybindings.cc
+++ b/ice40/arch_pybindings.cc
@@ -59,6 +59,7 @@ void arch_wrap_python()
typedef std::unordered_map<IdString, std::unique_ptr<CellInfo>> CellMap;
typedef std::unordered_map<IdString, std::unique_ptr<NetInfo>> NetMap;
+ typedef std::unordered_map<IdString, HierarchicalCell> HierarchyMap;
typedef std::unordered_map<IdString, IdString> AliasMap;
auto belpin_cls = class_<ContextualWrapper<BelPin>>("BelPin", no_init);
@@ -75,6 +76,7 @@ void arch_wrap_python()
WRAP_MAP_UPTR(CellMap, "IdCellMap");
WRAP_MAP_UPTR(NetMap, "IdNetMap");
+ WRAP_MAP(HierarchyMap, wrap_context<HierarchicalCell &>, "HierarchyMap");
}
NEXTPNR_NAMESPACE_END
diff --git a/ice40/archdefs.h b/ice40/archdefs.h
index 89591af5..e95953f1 100644
--- a/ice40/archdefs.h
+++ b/ice40/archdefs.h
@@ -48,6 +48,9 @@ struct DelayInfo
// -----------------------------------------------------------------------
+// https://bugreports.qt.io/browse/QTBUG-80789
+
+#ifndef Q_MOC_RUN
enum ConstIds
{
ID_NONE
@@ -59,6 +62,7 @@ enum ConstIds
#define X(t) static constexpr auto id_##t = IdString(ID_##t);
#include "constids.inc"
#undef X
+#endif
struct BelId
{
diff --git a/ice40/bitstream.cc b/ice40/bitstream.cc
index 7149f127..9586b8ff 100644
--- a/ice40/bitstream.cc
+++ b/ice40/bitstream.cc
@@ -159,6 +159,9 @@ void configure_extra_cell(chipconfig_t &config, const Context *ctx, CellInfo *ce
if (string_style) {
// Lattice's weird string style params, not sure if
// prefixes other than 0b should be supported, only 0b features in docs
+ if (cell->params.count(ctx->id(p.first)) && !cell->params.at(ctx->id(p.first)).is_string)
+ log_error("expected configuration string starting with '0b' for parameter '%s' on cell '%s'\n",
+ p.first.c_str(), cell->name.c_str(ctx));
std::string raw = get_param_str_or_def(cell, ctx->id(p.first), "0b0");
if (raw.substr(0, 2) != "0b")
log_error("expected configuration string starting with '0b' for parameter '%s' on cell '%s'\n",
diff --git a/ice40/cells.cc b/ice40/cells.cc
index c4e93d5b..f1901c43 100644
--- a/ice40/cells.cc
+++ b/ice40/cells.cc
@@ -69,7 +69,7 @@ std::unique_ptr<CellInfo> create_ice_cell(Context *ctx, IdString type, std::stri
new_cell->params[ctx->id("PIN_TYPE")] = Property(0, 6);
new_cell->params[ctx->id("PULLUP")] = Property::State::S0;
new_cell->params[ctx->id("NEG_TRIGGER")] = Property::State::S0;
- new_cell->params[ctx->id("IOSTANDARD")] = Property("SB_LVCMOS");
+ new_cell->params[ctx->id("IO_STANDARD")] = Property("SB_LVCMOS");
add_port(ctx, new_cell.get(), "PACKAGE_PIN", PORT_INOUT);
@@ -346,6 +346,8 @@ std::unique_ptr<CellInfo> create_ice_cell(Context *ctx, IdString type, std::stri
void lut_to_lc(const Context *ctx, CellInfo *lut, CellInfo *lc, bool no_dff)
{
+ if (lc->hierpath == IdString())
+ lc->hierpath = lut->hierpath;
lc->params[ctx->id("LUT_INIT")] = lut->params[ctx->id("LUT_INIT")].extract(0, 16, Property::State::S0);
replace_port(lut, ctx->id("I0"), lc, ctx->id("I0"));
replace_port(lut, ctx->id("I1"), lc, ctx->id("I1"));
@@ -359,6 +361,8 @@ void lut_to_lc(const Context *ctx, CellInfo *lut, CellInfo *lc, bool no_dff)
void dff_to_lc(const Context *ctx, CellInfo *dff, CellInfo *lc, bool pass_thru_lut)
{
+ if (lc->hierpath == IdString())
+ lc->hierpath = dff->hierpath;
lc->params[ctx->id("DFF_ENABLE")] = Property::State::S1;
std::string config = dff->type.str(ctx).substr(6);
auto citer = config.begin();
@@ -426,7 +430,25 @@ void nxio_to_sb(Context *ctx, CellInfo *nxio, CellInfo *sbio, std::unordered_set
} else {
NPNR_ASSERT(false);
}
- NetInfo *donet = sbio->ports.at(ctx->id("D_OUT_0")).net;
+ NetInfo *donet = sbio->ports.at(ctx->id("D_OUT_0")).net, *dinet = sbio->ports.at(ctx->id("D_IN_0")).net;
+
+ // Rename I/O nets to avoid conflicts
+ if (donet != nullptr && donet->name == nxio->name)
+ rename_net(ctx, donet, ctx->id(donet->name.str(ctx) + "$SB_IO_OUT"));
+ if (dinet != nullptr && dinet->name == nxio->name)
+ rename_net(ctx, dinet, ctx->id(dinet->name.str(ctx) + "$SB_IO_IN"));
+
+ // Create a new top port net for accurate IO timing analysis and simulation netlists
+ if (ctx->ports.count(nxio->name)) {
+ IdString tn_netname = nxio->name;
+ NPNR_ASSERT(!ctx->nets.count(tn_netname));
+ std::unique_ptr<NetInfo> toplevel_net{new NetInfo};
+ toplevel_net->name = tn_netname;
+ connect_port(ctx, toplevel_net.get(), sbio, ctx->id("PACKAGE_PIN"));
+ ctx->ports[nxio->name].net = toplevel_net.get();
+ ctx->nets[tn_netname] = std::move(toplevel_net);
+ }
+
CellInfo *tbuf = net_driven_by(
ctx, donet, [](const Context *ctx, const CellInfo *cell) { return cell->type == ctx->id("$_TBUF_"); },
ctx->id("Y"));
diff --git a/ice40/family.cmake b/ice40/family.cmake
index f22883fb..2859f456 100644
--- a/ice40/family.cmake
+++ b/ice40/family.cmake
@@ -40,14 +40,6 @@ if (NOT EXTERNAL_CHIPDB)
set(DEV_CC_DB ${CMAKE_CURRENT_BINARY_DIR}/ice40/chipdbs/chipdb-${dev}.bin)
set(DEV_CONSTIDS_INC ${CMAKE_CURRENT_SOURCE_DIR}/ice40/constids.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_CONSTIDS_INC} -g ${DEV_GFXH} ${OPT_FAST} ${OPT_SLOW} ${DEV_TXT_DB} > ${DEV_CC_BBA_DB}
- DEPENDS ${DEV_CONSTIDS_INC} ${DEV_GFXH} ${DEV_TXT_DB} ${DB_PY} ${PREV_DEV_CC_BBA_DB}
- )
- add_custom_command(OUTPUT ${DEV_CC_DB}
- COMMAND bbasm ${BBASM_ENDIAN_FLAG} ${DEV_CC_BBA_DB} ${DEV_CC_DB}
- DEPENDS bbasm ${DEV_CC_BBA_DB}
- )
if(PREGENERATED_BBA_PATH)
add_custom_command(OUTPUT ${DEV_CC_DB}
@@ -92,6 +84,7 @@ if (NOT EXTERNAL_CHIPDB)
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_BINARY_DIR}/ice40/chipdbs/chipdb-${dev}.cc)
+ set(DEV_BIN_DB ${CMAKE_CURRENT_BINARY_DIR}/ice40/chipdbs/chipdb-${dev}.bin)
set(DEV_CONSTIDS_INC ${CMAKE_CURRENT_SOURCE_DIR}/ice40/constids.inc)
set(DEV_GFXH ${CMAKE_CURRENT_SOURCE_DIR}/ice40/gfx.h)
if(PREGENERATED_BBA_PATH)
@@ -105,11 +98,19 @@ if (NOT EXTERNAL_CHIPDB)
COMMAND mv ${DEV_CC_BBA_DB}.new ${DEV_CC_BBA_DB}
DEPENDS ${DEV_CONSTIDS_INC} ${DEV_GFXH} ${DEV_TXT_DB} ${DB_PY} ${PREV_DEV_CC_BBA_DB}
)
- add_custom_command(OUTPUT ${DEV_CC_DB}
- COMMAND bbasm --c ${BBASM_ENDIAN_FLAG} ${DEV_CC_BBA_DB} ${DEV_CC_DB}.new
- COMMAND mv ${DEV_CC_DB}.new ${DEV_CC_DB}
- DEPENDS bbasm ${DEV_CC_BBA_DB}
- )
+ if(USE_C_EMBED)
+ add_custom_command(OUTPUT ${DEV_CC_DB}
+ COMMAND bbasm --e ${BBASM_ENDIAN_FLAG} ${DEV_CC_BBA_DB} ${DEV_CC_DB}.new ${DEV_BIN_DB}
+ COMMAND mv ${DEV_CC_DB}.new ${DEV_CC_DB}
+ DEPENDS bbasm ${DEV_CC_BBA_DB}
+ )
+ else()
+ add_custom_command(OUTPUT ${DEV_CC_DB}
+ COMMAND bbasm --c ${BBASM_ENDIAN_FLAG} ${DEV_CC_BBA_DB} ${DEV_CC_DB}.new
+ COMMAND mv ${DEV_CC_DB}.new ${DEV_CC_DB}
+ DEPENDS bbasm ${DEV_CC_BBA_DB}
+ )
+ endif()
endif()
if (SERIALIZE_CHIPDB)
set(PREV_DEV_CC_BBA_DB ${DEV_CC_BBA_DB})
diff --git a/ice40/main.cc b/ice40/main.cc
index 5a5fa0c7..3b512a5a 100644
--- a/ice40/main.cc
+++ b/ice40/main.cc
@@ -24,7 +24,6 @@
#include "bitstream.h"
#include "command.h"
#include "design_utils.h"
-#include "jsonparse.h"
#include "log.h"
#include "pcf.h"
#include "timing.h"
diff --git a/ice40/pack.cc b/ice40/pack.cc
index a1eba411..5b13e9ee 100644
--- a/ice40/pack.cc
+++ b/ice40/pack.cc
@@ -459,7 +459,6 @@ static void pack_io(Context *ctx)
{
std::unordered_set<IdString> packed_cells;
std::unordered_set<IdString> delete_nets;
-
std::vector<std::unique_ptr<CellInfo>> new_cells;
log_info("Packing IOs..\n");
@@ -478,8 +477,7 @@ static void pack_io(Context *ctx)
rgb = net->driver.cell;
}
if (sb != nullptr) {
- // Trivial case, SB_IO used. Just destroy the net and the
- // iobuf
+ // Trivial case, SB_IO used. Just destroy the iobuf
log_info("%s feeds SB_IO %s, removing %s %s.\n", ci->name.c_str(ctx), sb->name.c_str(ctx),
ci->type.c_str(ctx), ci->name.c_str(ctx));
NetInfo *net = sb->ports.at(ctx->id("PACKAGE_PIN")).net;
@@ -490,7 +488,6 @@ static void pack_io(Context *ctx)
sb->type.c_str(ctx), sb->name.c_str(ctx));
if (net != nullptr) {
-
if (net->clkconstr != nullptr) {
if (sb->ports.count(id_D_IN_0)) {
NetInfo *din0_net = sb->ports.at(id_D_IN_0).net;
@@ -509,15 +506,6 @@ static void pack_io(Context *ctx)
}
}
}
-
- delete_nets.insert(net->name);
- sb->ports.at(ctx->id("PACKAGE_PIN")).net = nullptr;
- }
- if (ci->type == ctx->id("$nextpnr_iobuf")) {
- NetInfo *net2 = ci->ports.at(ctx->id("I")).net;
- if (net2 != nullptr) {
- delete_nets.insert(net2->name);
- }
}
} else if (rgb != nullptr) {
log_info("%s use by SB_RGBA_DRV/SB_RGB_DRV %s, not creating SB_IO\n", ci->name.c_str(ctx),
@@ -533,11 +521,15 @@ static void pack_io(Context *ctx)
new_cells.push_back(std::move(ice_cell));
sb = new_cells.back().get();
}
+ for (auto port : ci->ports)
+ disconnect_port(ctx, ci, port.first);
packed_cells.insert(ci->name);
std::copy(ci->attrs.begin(), ci->attrs.end(), std::inserter(sb->attrs, sb->attrs.begin()));
} else if (is_sb_io(ctx, ci) || is_sb_gb_io(ctx, ci)) {
NetInfo *net = ci->ports.at(ctx->id("PACKAGE_PIN")).net;
- if ((net != nullptr) && (net->users.size() > 1))
+ if ((net != nullptr) && ((net->users.size() > 2) ||
+ (net->driver.cell != nullptr &&
+ net->driver.cell->type == ctx->id("$nextpnr_obuf") && net->users.size() > 1)))
log_error("PACKAGE_PIN of %s '%s' connected to more than a single top level IO.\n", ci->type.c_str(ctx),
ci->name.c_str(ctx));
}
@@ -1495,6 +1487,7 @@ bool Arch::pack()
promote_globals(ctx);
ctx->assignArchInfo();
constrain_chains(ctx);
+ ctx->fixupHierarchy();
ctx->assignArchInfo();
ctx->settings[ctx->id("pack")] = 1;
archInfoToAttributes();
diff --git a/json/jsonparse.cc b/json/jsonparse.cc
deleted file mode 100644
index fb712b28..00000000
--- a/json/jsonparse.cc
+++ /dev/null
@@ -1,1028 +0,0 @@
-/*
- * nextpnr -- Next Generation Place and Route
- *
- * Copyright (C) 2018 SymbioticEDA
- *
- * jsonparse.cc -- liberally copied from the yosys file of the same name by
- *
- * Copyright (C) 2018 Clifford Wolf <clifford@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 "jsonparse.h"
-#include <assert.h>
-#include <fstream>
-#include <iostream>
-#include <iterator>
-#include <log.h>
-#include <map>
-#include <string>
-#include "nextpnr.h"
-
-NEXTPNR_NAMESPACE_BEGIN
-
-extern bool check_all_nets_driven(Context *ctx);
-
-namespace JsonParser {
-
-const bool json_debug = false;
-
-typedef std::string string;
-
-template <typename T> int GetSize(const T &obj) { return obj.size(); }
-
-struct JsonNode
-{
- char type; // S=String, N=Number, A=Array, D=Dict
- string data_string;
- int data_number;
- std::vector<JsonNode *> data_array;
- std::map<string, JsonNode *> data_dict;
- std::vector<string> data_dict_keys;
-
- JsonNode(std::istream &f, int &lineno)
- {
- type = 0;
- data_number = 0;
-
- while (1) {
- int ch = f.get();
-
- if (ch == EOF)
- log_error("Unexpected EOF in JSON file.\n");
-
- if (ch == '\n')
- lineno++;
- if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n')
- continue;
-
- if (ch == '\"') {
- type = 'S';
-
- while (1) {
- ch = f.get();
-
- if (ch == EOF)
- log_error("Unexpected EOF in JSON string.\n");
-
- if (ch == '\"')
- break;
-
- if (ch == '\\') {
- int ch = f.get();
-
- if (ch == EOF)
- log_error("Unexpected EOF in JSON string.\n");
- }
-
- data_string += ch;
- }
-
- break;
- }
-
- if (('0' <= ch && ch <= '9') || ('-' == ch)) {
- type = 'N';
- if (ch == '-')
- data_number = 0;
- else
- data_number = ch - '0';
- data_string += ch;
-
- while (1) {
- ch = f.get();
-
- if (ch == EOF)
- break;
-
- if (ch == '.')
- goto parse_real;
-
- if (ch < '0' || '9' < ch) {
- f.unget();
- break;
- }
-
- data_number = data_number * 10 + (ch - '0');
- data_string += ch;
- }
-
- if (data_string[0] == '-')
- data_number = -data_number;
- data_string = "";
- break;
-
- parse_real:
- type = 'S';
- data_number = 0;
- data_string += ch;
-
- while (1) {
- ch = f.get();
-
- if (ch == EOF)
- break;
-
- if (ch < '0' || '9' < ch) {
- f.unget();
- break;
- }
-
- data_string += ch;
- }
-
- break;
- }
-
- if (ch == '[') {
- type = 'A';
-
- while (1) {
- ch = f.get();
-
- if (ch == EOF)
- log_error("Unexpected EOF in JSON file.\n");
-
- if (ch == '\n')
- lineno++;
- if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == ',')
- continue;
-
- if (ch == ']')
- break;
-
- f.unget();
- data_array.push_back(new JsonNode(f, lineno));
- }
-
- break;
- }
-
- if (ch == '{') {
- type = 'D';
-
- while (1) {
- ch = f.get();
-
- if (ch == EOF)
- log_error("Unexpected EOF in JSON file.\n");
-
- if (ch == '\n')
- lineno++;
- if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == ',')
- continue;
-
- if (ch == '}')
- break;
-
- f.unget();
- JsonNode key(f, lineno);
-
- while (1) {
- ch = f.get();
-
- if (ch == EOF)
- log_error("Unexpected EOF in JSON file.\n");
-
- if (ch == '\n')
- lineno++;
- if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == ':')
- continue;
-
- f.unget();
- break;
- }
-
- JsonNode *value = new JsonNode(f, lineno);
-
- if (key.type != 'S')
- log_error("Unexpected non-string key in JSON dict, line %d.\n", lineno);
-
- data_dict[key.data_string] = value;
- data_dict_keys.push_back(key.data_string);
- }
-
- break;
- }
-
- log_error("Unexpected character in JSON file, line %d: '%c'\n", lineno, ch);
- }
- }
-
- ~JsonNode()
- {
- for (auto it : data_array)
- delete it;
- for (auto &it : data_dict)
- delete it.second;
- }
-};
-
-inline Property json_parse_attr_param_value(JsonNode *node)
-{
- Property value;
-
- if (node->type == 'S') {
- value = Property::from_string(node->data_string);
- } else if (node->type == 'N') {
- value = Property(node->data_number, 32);
- } else if (node->type == 'A') {
- log_error("JSON attribute or parameter value is an array.\n");
- } else if (node->type == 'D') {
- log_error("JSON attribute or parameter value is a dict.\n");
- } else {
- log_abort();
- }
-
- return value;
-}
-
-void ground_net(Context *ctx, NetInfo *net)
-{
- std::unique_ptr<CellInfo> cell = std::unique_ptr<CellInfo>(new CellInfo);
- PortInfo port_info;
- PortRef port_ref;
-
- cell->name = ctx->id(net->name.str(ctx) + ".GND");
- cell->type = ctx->id("GND");
-
- port_info.name = ctx->id(cell->name.str(ctx) + "[]");
- port_info.net = net;
- port_info.type = PORT_OUT;
-
- port_ref.cell = cell.get();
- port_ref.port = port_info.name;
-
- net->driver = port_ref;
-
- cell->ports[port_info.name] = port_info;
-
- ctx->cells[cell->name] = std::move(cell);
-}
-
-void vcc_net(Context *ctx, NetInfo *net)
-{
- std::unique_ptr<CellInfo> cell = std::unique_ptr<CellInfo>(new CellInfo);
- PortInfo port_info;
- PortRef port_ref;
-
- cell->name = ctx->id(net->name.str(ctx) + ".VCC");
- cell->type = ctx->id("VCC");
-
- port_info.name = ctx->id(cell->name.str(ctx) + "[]");
- port_info.net = net;
- port_info.type = PORT_OUT;
-
- port_ref.cell = cell.get();
- port_ref.port = port_info.name;
-
- net->driver = port_ref;
-
- cell->ports[port_info.name] = port_info;
-
- ctx->cells[cell->name] = std::move(cell);
-}
-
-//
-// is_blackbox
-//
-// Checks the JsonNode for an attributes dictionary, with a "blackbox" entry.
-// An item is deemed to be a blackbox if this entry exists and if its
-// value is not zero. If the item is a black box, this routine will return
-// true, false otherwise
-bool is_blackbox(JsonNode *node)
-{
- JsonNode *attr_node, *bbox_node = nullptr, *wbox_node = nullptr;
-
- if (node->data_dict.count("attributes") == 0)
- return false;
- attr_node = node->data_dict.at("attributes");
- if (attr_node == NULL)
- return false;
- if (attr_node->type != 'D')
- return false;
- if (GetSize(attr_node->data_dict) == 0)
- return false;
- if (attr_node->data_dict.count("blackbox"))
- bbox_node = attr_node->data_dict.at("blackbox");
- if (attr_node->data_dict.count("whitebox"))
- wbox_node = attr_node->data_dict.at("whitebox");
- if (bbox_node == NULL && wbox_node == NULL)
- return false;
- if (bbox_node && bbox_node->type != 'N')
- log_error("JSON module blackbox attribute value is not a number\n");
- if (bbox_node && bbox_node->data_number == 0)
- return false;
- if (wbox_node && wbox_node->type != 'N')
- log_error("JSON module whitebox attribute value is not a number\n");
- if (wbox_node && wbox_node->data_number == 0)
- return false;
- return true;
-}
-
-void json_import_cell_params(Context *ctx, string &modname, CellInfo *cell, JsonNode *param_node,
- std::unordered_map<IdString, Property> *dest, int param_id)
-{
- //
- JsonNode *param;
- IdString pId;
- //
- param = param_node->data_dict.at(param_node->data_dict_keys[param_id]);
-
- pId = ctx->id(param_node->data_dict_keys[param_id]);
- (*dest)[pId] = json_parse_attr_param_value(param);
-
- if (json_debug)
- log_info(" Added parameter \'%s\'=%s to cell \'%s\' "
- "of module \'%s\'\n",
- pId.c_str(ctx), cell->params[pId].as_string().c_str(), cell->name.c_str(ctx), modname.c_str());
-}
-
-void json_import_net_attrib(Context *ctx, string &modname, NetInfo *net, JsonNode *param_node,
- std::unordered_map<IdString, Property> *dest, int param_id)
-{
- //
- JsonNode *param;
- IdString pId;
- //
- param = param_node->data_dict.at(param_node->data_dict_keys[param_id]);
-
- pId = ctx->id(param_node->data_dict_keys[param_id]);
- (*dest)[pId] = json_parse_attr_param_value(param);
-
- if (json_debug)
- log_info(" Added parameter \'%s\'=%s to net \'%s\' "
- "of module \'%s\'\n",
- pId.c_str(ctx), net->attrs[pId].as_string().c_str(), net->name.c_str(ctx), modname.c_str());
-}
-
-void json_import_top_attrib(Context *ctx, string &modname, JsonNode *param_node,
- std::unordered_map<IdString, Property> *dest, int param_id)
-{
- //
- JsonNode *param;
- IdString pId;
- //
- param = param_node->data_dict.at(param_node->data_dict_keys[param_id]);
-
- pId = ctx->id(param_node->data_dict_keys[param_id]);
- (*dest)[pId] = json_parse_attr_param_value(param);
-
- if (json_debug)
- log_info(" Added parameter \'%s\'=%s module \'%s\'\n", pId.c_str(ctx), (*dest)[pId].as_string().c_str(),
- modname.c_str());
-}
-
-static int const_net_idx = 0;
-
-template <typename F>
-void json_import_ports(Context *ctx, const string &modname, const std::vector<IdString> &netnames,
- const string &obj_name, const string &port_name, JsonNode *dir_node, JsonNode *wire_group_node,
- bool upto, int start_offset, F visitor)
-{
- // Examine a port of a cell or the design. For every bit of the port,
- // the connected net will be processed and `visitor` will be called
- // with (PortType dir, std::string name, NetInfo *net)
- assert(dir_node);
-
- if (json_debug)
- log_info(" Examining port %s, node %s\n", port_name.c_str(), obj_name.c_str());
-
- if (!wire_group_node)
- log_error("JSON no connection match "
- "for port_direction \'%s\' of node \'%s\' "
- "in module \'%s\'\n",
- port_name.c_str(), obj_name.c_str(), modname.c_str());
-
- assert(wire_group_node);
-
- assert(dir_node->type == 'S');
- assert(wire_group_node->type == 'A');
-
- PortInfo port_info;
-
- port_info.name = ctx->id(port_name);
- if (dir_node->data_string.compare("input") == 0)
- port_info.type = PORT_IN;
- else if (dir_node->data_string.compare("output") == 0)
- port_info.type = PORT_OUT;
- else if (dir_node->data_string.compare("inout") == 0)
- port_info.type = PORT_INOUT;
- else
- log_error("JSON unknown port direction \'%s\' in node \'%s\' "
- "of module \'%s\'\n",
- dir_node->data_string.c_str(), obj_name.c_str(), modname.c_str());
- //
- // Find an update, or create a net to connect
- // to this port.
- //
- NetInfo *this_net = nullptr;
- bool is_bus;
-
- //
- // If this port references a bus, then there will be multiple nets
- // connected to it, all specified as part of an array.
- //
- is_bus = (wire_group_node->data_array.size() > 1);
-
- // Now loop through all of the connections to this port.
- if (wire_group_node->data_array.size() == 0) {
- //
- // There is/are no connections to this port.
- //
- // Create the port, but leave the net NULL
-
- visitor(port_info.type, port_info.name.str(ctx), nullptr);
-
- if (json_debug)
- log_info(" Port \'%s\' has no connection in \'%s\'\n", port_info.name.c_str(ctx), obj_name.c_str());
-
- } else
- for (int index = 0; index < int(wire_group_node->data_array.size()); index++) {
- //
- JsonNode *wire_node;
- PortInfo this_port;
- IdString net_id;
- //
- wire_node = wire_group_node->data_array[index];
- //
- // Pick a name for this port
- int ndx = index + start_offset;
- if (upto)
- ndx = start_offset + wire_group_node->data_array.size() - index - 1;
- if (is_bus)
- this_port.name = ctx->id(port_info.name.str(ctx) + "[" + std::to_string(ndx) + "]");
- else
- this_port.name = port_info.name;
- this_port.type = port_info.type;
-
- if (wire_node->type == 'N') {
- int net_num;
-
- // A simple net, specified by a number
- net_num = wire_node->data_number;
- if (net_num < int(netnames.size()))
- net_id = netnames.at(net_num);
- else
- net_id = ctx->id(std::to_string(net_num));
- if (ctx->nets.count(net_id) == 0) {
- // The net doesn't exist in the design (yet)
- // Create in now
-
- if (json_debug)
- log_info(" Generating a new net, \'%d\'\n", net_num);
-
- std::unique_ptr<NetInfo> net = std::unique_ptr<NetInfo>(new NetInfo());
- net->name = net_id;
- net->driver.cell = NULL;
- net->driver.port = IdString();
- ctx->nets[net_id] = std::move(net);
-
- this_net = ctx->nets[net_id].get();
- } else {
- //
- // The net already exists within the design.
- // We'll connect to it
- //
- this_net = ctx->nets[net_id].get();
- if (json_debug)
- log_info(" Reusing net \'%s\', id \'%s\', "
- "with driver \'%s\'\n",
- this_net->name.c_str(ctx), net_id.c_str(ctx),
- (this_net->driver.cell != NULL) ? this_net->driver.port.c_str(ctx) : "NULL");
- }
-
- } else if (wire_node->type == 'S') {
- // Strings are only used to drive wires for the fixed
- // values "0", "1", and "x". Handle those constant
- // values here.
- //
- // Constants always get their own new net
- std::unique_ptr<NetInfo> net = std::unique_ptr<NetInfo>(new NetInfo());
- net->name = ctx->id("$const_" + std::to_string(const_net_idx++));
-
- if (wire_node->data_string.compare(string("0")) == 0) {
-
- if (json_debug)
- log_info(" Generating a constant "
- "zero net\n");
- ground_net(ctx, net.get());
-
- } else if (wire_node->data_string.compare(string("1")) == 0) {
-
- if (json_debug)
- log_info(" Generating a constant "
- "one net\n");
- vcc_net(ctx, net.get());
-
- } else if (wire_node->data_string.compare(string("x")) == 0) {
- ground_net(ctx, net.get());
- } else
- log_error(" Unknown fixed type wire node "
- "value, \'%s\'\n",
- wire_node->data_string.c_str());
- IdString n = net->name;
- ctx->nets[net->name] = std::move(net);
- this_net = ctx->nets[n].get();
- }
-
- if (json_debug)
- log_info(" Inserting port \'%s\' into cell \'%s\'\n", this_port.name.c_str(ctx), obj_name.c_str());
- visitor(this_port.type, this_port.name.str(ctx), this_net);
- }
-}
-
-void json_import_cell(Context *ctx, string modname, const std::vector<IdString> &netnames, JsonNode *cell_node,
- string cell_name)
-{
- JsonNode *cell_type, *param_node, *attr_node;
-
- cell_type = cell_node->data_dict.at("type");
- if (cell_type == NULL)
- return;
-
- std::unique_ptr<CellInfo> cell = std::unique_ptr<CellInfo>(new CellInfo);
-
- cell->name = ctx->id(cell_name);
- assert(cell_type->type == 'S');
- cell->type = ctx->id(cell_type->data_string);
- // No BEL assignment here/yet
-
- if (json_debug)
- log_info(" Processing %s $ %s\n", modname.c_str(), cell->name.c_str(ctx));
-
- param_node = cell_node->data_dict.at("parameters");
- if (param_node->type != 'D')
- log_error("JSON parameter list of \'%s\' is not a data dictionary\n", cell->name.c_str(ctx));
-
- //
- // Loop through all parameters, adding them into the
- // design to annotate the cell
- //
- for (int paramid = 0; paramid < GetSize(param_node->data_dict_keys); paramid++) {
-
- json_import_cell_params(ctx, modname, cell.get(), param_node, &cell->params, paramid);
- }
-
- attr_node = cell_node->data_dict.at("attributes");
- if (attr_node->type != 'D')
- log_error("JSON attribute list of \'%s\' is not a data dictionary\n", cell->name.c_str(ctx));
-
- //
- // Loop through all attributes, adding them into the
- // design to annotate the cell
- //
- for (int attrid = 0; attrid < GetSize(attr_node->data_dict_keys); attrid++) {
-
- json_import_cell_params(ctx, modname, cell.get(), attr_node, &cell->attrs, attrid);
- }
-
- //
- // Now connect the ports of this module. The ports are defined by
- // both the port directions node as well as the connections node.
- // Both should contain dictionaries having the same keys.
- //
-
- JsonNode *pdir_node = NULL;
- if (cell_node->data_dict.count("port_directions") > 0) {
-
- pdir_node = cell_node->data_dict.at("port_directions");
- if (pdir_node->type != 'D')
- log_error("JSON port_directions node of \'%s\' "
- "in module \'%s\' is not a "
- "dictionary\n",
- cell->name.c_str(ctx), modname.c_str());
-
- } else if (cell_node->data_dict.count("ports") > 0) {
- pdir_node = cell_node->data_dict.at("ports");
- if (pdir_node->type != 'D')
- log_error("JSON ports node of \'%s\' "
- "in module \'%s\' is not a "
- "dictionary\n",
- cell->name.c_str(ctx), modname.c_str());
- }
-
- JsonNode *connections = cell_node->data_dict.at("connections");
- if (connections->type != 'D')
- log_error("JSON connections node of \'%s\' "
- "in module \'%s\' is not a "
- "dictionary\n",
- cell->name.c_str(ctx), modname.c_str());
-
- if (GetSize(pdir_node->data_dict_keys) != GetSize(connections->data_dict_keys))
- log_error("JSON number of connections doesnt "
- "match number of ports in node \'%s\' "
- "of module \'%s\'\n",
- cell->name.c_str(ctx), modname.c_str());
-
- //
- // Loop through all of the ports of this logic element
- //
- for (int portid = 0; portid < GetSize(pdir_node->data_dict_keys); portid++) {
- //
- string port_name;
- JsonNode *dir_node, *wire_group_node;
- //
-
- port_name = pdir_node->data_dict_keys[portid];
- dir_node = pdir_node->data_dict.at(port_name);
- wire_group_node = connections->data_dict.at(port_name);
-
- json_import_ports(ctx, modname, netnames, cell->name.str(ctx), port_name, dir_node, wire_group_node, false, 0,
- [&cell, ctx](PortType type, const std::string &name, NetInfo *net) {
- cell->ports[ctx->id(name)] = PortInfo{ctx->id(name), net, type};
- PortRef pr;
- pr.cell = cell.get();
- pr.port = ctx->id(name);
- if (net != nullptr) {
- if (type == PORT_IN || type == PORT_INOUT) {
- net->users.push_back(pr);
- } else if (type == PORT_OUT) {
- if (net->driver.cell != nullptr)
- log_error("multiple drivers on net '%s' (%s.%s and %s.%s)\n",
- net->name.c_str(ctx), net->driver.cell->name.c_str(ctx),
- net->driver.port.c_str(ctx), pr.cell->name.c_str(ctx),
- pr.port.c_str(ctx));
- net->driver = pr;
- }
- }
- });
- }
-
- ctx->cells[cell->name] = std::move(cell);
- // check_all_nets_driven(ctx);
-}
-
-static void insert_iobuf(Context *ctx, NetInfo *net, PortType type, const string &name)
-{
- // Instantiate a architecture-independent IO buffer connected to a given
- // net, of a given type, and named after the IO port.
- //
- // During packing, this generic IO buffer will be converted to an
- // architecure primitive.
- //
- if (ctx->settings.find(ctx->id("synth")) == ctx->settings.end()) {
- std::unique_ptr<CellInfo> iobuf = std::unique_ptr<CellInfo>(new CellInfo());
- iobuf->name = ctx->id(name);
- std::copy(net->attrs.begin(), net->attrs.end(), std::inserter(iobuf->attrs, iobuf->attrs.begin()));
- if (type == PORT_IN) {
- if (ctx->verbose)
- log_info("processing input port %s\n", name.c_str());
- iobuf->type = ctx->id("$nextpnr_ibuf");
- iobuf->ports[ctx->id("O")] = PortInfo{ctx->id("O"), net, PORT_OUT};
- // Special case: input, etc, directly drives inout
- if (net->driver.cell != nullptr) {
- if (net->driver.cell->type != ctx->id("$nextpnr_iobuf"))
- log_error("Top-level input '%s' also driven by %s.%s.\n", name.c_str(),
- net->driver.cell->name.c_str(ctx), net->driver.port.c_str(ctx));
- net = net->driver.cell->ports.at(ctx->id("I")).net;
- }
- assert(net->driver.cell == nullptr);
- net->driver.port = ctx->id("O");
- net->driver.cell = iobuf.get();
- } else if (type == PORT_OUT) {
- if (ctx->verbose)
- log_info("processing output port %s\n", name.c_str());
- iobuf->type = ctx->id("$nextpnr_obuf");
- iobuf->ports[ctx->id("I")] = PortInfo{ctx->id("I"), net, PORT_IN};
- PortRef ref;
- ref.cell = iobuf.get();
- ref.port = ctx->id("I");
- net->users.push_back(ref);
- } else if (type == PORT_INOUT) {
- if (ctx->verbose)
- log_info("processing inout port %s\n", name.c_str());
- iobuf->type = ctx->id("$nextpnr_iobuf");
- iobuf->ports[ctx->id("I")] = PortInfo{ctx->id("I"), nullptr, PORT_IN};
-
- // Split the input and output nets for bidir ports
- std::unique_ptr<NetInfo> net2 = std::unique_ptr<NetInfo>(new NetInfo());
- net2->name = ctx->id("$" + net->name.str(ctx) + "$iobuf_i");
- net2->driver = net->driver;
- if (net->driver.cell != nullptr) {
- net2->driver.cell->ports[net2->driver.port].net = net2.get();
- net->driver.cell = nullptr;
- }
- iobuf->ports[ctx->id("I")].net = net2.get();
- PortRef ref;
- ref.cell = iobuf.get();
- ref.port = ctx->id("I");
- net2->users.push_back(ref);
- ctx->nets[net2->name] = std::move(net2);
-
- iobuf->ports[ctx->id("O")] = PortInfo{ctx->id("O"), net, PORT_OUT};
- assert(net->driver.cell == nullptr);
- net->driver.port = ctx->id("O");
- net->driver.cell = iobuf.get();
- } else {
- assert(false);
- }
- ctx->cells[iobuf->name] = std::move(iobuf);
- }
-
- PortInfo pinfo;
- pinfo.name = ctx->id(name);
- pinfo.net = net;
- pinfo.type = type;
- ctx->ports[pinfo.name] = pinfo;
-}
-
-void json_import_toplevel_port(Context *ctx, const string &modname, const std::vector<IdString> &netnames,
- const string &portname, JsonNode *node)
-{
- JsonNode *dir_node = node->data_dict.at("direction");
- JsonNode *nets_node = node->data_dict.at("bits");
- bool upto = false;
- int start_offset = 0;
- if (node->data_dict.count("upto") != 0) {
- JsonNode *val = node->data_dict.at("upto");
- if (val->type == 'N')
- upto = val->data_number != 0;
- }
- if (node->data_dict.count("offset") != 0) {
- JsonNode *val = node->data_dict.at("offset");
- if (val->type == 'N')
- start_offset = val->data_number;
- }
- json_import_ports(
- ctx, modname, netnames, "Top Level IO", portname, dir_node, nets_node, upto, start_offset,
- [ctx](PortType type, const std::string &name, NetInfo *net) { insert_iobuf(ctx, net, type, name); });
-}
-
-void json_import(Context *ctx, string modname, JsonNode *node)
-{
- if (is_blackbox(node))
- return;
-
- log_info("Importing module %s\n", modname.c_str());
- ctx->attrs[ctx->id("module")] = modname;
- JsonNode *attr_node = node->data_dict.at("attributes");
- for (int attrid = 0; attrid < GetSize(attr_node->data_dict_keys); attrid++) {
- json_import_top_attrib(ctx, modname, attr_node, &ctx->attrs, attrid);
- }
-
- JsonNode *ports_parent = nullptr;
- if (node->data_dict.count("ports") > 0)
- ports_parent = node->data_dict.at("ports");
-
- // Multiple labels might refer to the same net. For now we resolve conflicts thus:
- // - (toplevel) ports are always preferred
- // - names with fewer $ are always prefered
- // - between equal $ counts, fewer .s are prefered
- // - ties are resolved alphabetically
- auto prefer_netlabel = [ports_parent](const std::string &a, const std::string &b) {
- if (ports_parent != nullptr) {
- if (ports_parent->data_dict.count(a))
- return true;
- if (ports_parent->data_dict.count(b))
- return false;
- }
- if (b.empty())
- return true;
- long a_dollars = std::count(a.begin(), a.end(), '$'), b_dollars = std::count(b.begin(), b.end(), '$');
- if (a_dollars < b_dollars)
- return true;
- else if (a_dollars > b_dollars)
- return false;
- long a_dots = std::count(a.begin(), a.end(), '.'), b_dots = std::count(b.begin(), b.end(), '.');
- if (a_dots < b_dots)
- return true;
- else if (a_dots > b_dots)
- return false;
- return a < b;
- };
-
- // Import netnames
- std::vector<std::vector<std::string>> netlabels;
- if (node->data_dict.count("netnames")) {
- JsonNode *cell_parent = node->data_dict.at("netnames");
- for (int nnid = 0; nnid < GetSize(cell_parent->data_dict_keys); nnid++) {
- JsonNode *here;
-
- here = cell_parent->data_dict.at(cell_parent->data_dict_keys[nnid]);
- std::string basename = cell_parent->data_dict_keys[nnid];
- bool upto = false;
- int start_offset = 0;
- if (here->data_dict.count("upto") != 0) {
- JsonNode *val = here->data_dict.at("upto");
- if (val->type == 'N')
- upto = val->data_number != 0;
- }
- if (here->data_dict.count("offset") != 0) {
- JsonNode *val = here->data_dict.at("offset");
- if (val->type == 'N')
- start_offset = val->data_number;
- }
- if (here->data_dict.count("bits")) {
- JsonNode *bits = here->data_dict.at("bits");
- assert(bits->type == 'A');
- size_t num_bits = bits->data_array.size();
- for (size_t i = 0; i < num_bits; i++) {
- int netid = bits->data_array.at(i)->data_number;
- if (netid >= int(netlabels.size()))
- netlabels.resize(netid + 1);
- int ndx = i + start_offset;
- if (upto)
- ndx = start_offset + num_bits - i - 1;
- std::string name =
- basename + (num_bits == 1 ? "" : std::string("[") + std::to_string(ndx) + std::string("]"));
- netlabels.at(netid).push_back(name);
- }
- }
- }
- }
- std::vector<IdString> netids;
- for (size_t i = 0; i < netlabels.size(); i++) {
- auto &labels = netlabels.at(i);
- if (labels.empty()) {
- // Backup for unnamed nets (not sure if these should actually happen)
- netids.push_back(ctx->id("$nextpnr$unknown_netname$" + std::to_string(i)));
- } else {
- // Pick a primary name for the net according to a simple heuristic
- std::string pref = labels.at(0);
- for (size_t j = 1; j < labels.size(); j++)
- if (prefer_netlabel(labels.at(j), pref))
- pref = labels.at(j);
- netids.push_back(ctx->id(pref));
- }
- }
- if (node->data_dict.count("cells")) {
- JsonNode *cell_parent = node->data_dict.at("cells");
- //
- //
- // Loop through all of the logic elements in a flattened design
- //
- //
- for (int cellid = 0; cellid < GetSize(cell_parent->data_dict_keys); cellid++) {
- JsonNode *here = cell_parent->data_dict.at(cell_parent->data_dict_keys[cellid]);
- json_import_cell(ctx, modname, netids, here, cell_parent->data_dict_keys[cellid]);
- }
- }
-
- if (ports_parent != nullptr) {
- // N.B. ports must be imported after cells for tristate behaviour
- // to be correct
- // Loop through all ports, first non-tristate then tristate to handle
- // interconnected ports correctly
- for (int portid = 0; portid < GetSize(ports_parent->data_dict_keys); portid++) {
- JsonNode *here;
-
- here = ports_parent->data_dict.at(ports_parent->data_dict_keys[portid]);
- JsonNode *dir_node = here->data_dict.at("direction");
- NPNR_ASSERT(dir_node->type == 'S');
- if (dir_node->data_string == "inout")
- continue;
- json_import_toplevel_port(ctx, modname, netids, ports_parent->data_dict_keys[portid], here);
- }
- for (int portid = 0; portid < GetSize(ports_parent->data_dict_keys); portid++) {
- JsonNode *here;
-
- here = ports_parent->data_dict.at(ports_parent->data_dict_keys[portid]);
- JsonNode *dir_node = here->data_dict.at("direction");
- NPNR_ASSERT(dir_node->type == 'S');
- if (dir_node->data_string != "inout")
- continue;
- json_import_toplevel_port(ctx, modname, netids, ports_parent->data_dict_keys[portid], here);
- }
- }
- if (node->data_dict.count("netnames")) {
- JsonNode *net_parent = node->data_dict.at("netnames");
- for (int nnid = 0; nnid < GetSize(net_parent->data_dict_keys); nnid++) {
- JsonNode *here;
-
- here = net_parent->data_dict.at(net_parent->data_dict_keys[nnid]);
- std::string basename = net_parent->data_dict_keys[nnid];
- if (here->data_dict.count("bits")) {
- JsonNode *bits = here->data_dict.at("bits");
- assert(bits->type == 'A');
- size_t num_bits = bits->data_array.size();
- for (size_t i = 0; i < num_bits; i++) {
- std::string name =
- basename + (num_bits == 1 ? "" : std::string("[") + std::to_string(i) + std::string("]"));
- IdString net_id = ctx->id(name);
- if (here->data_dict.count("attributes") && ctx->nets.find(net_id) != ctx->nets.end()) {
- NetInfo *this_net = ctx->nets[net_id].get();
-
- JsonNode *attr_node = here->data_dict.at("attributes");
- if (attr_node->type != 'D')
- log_error("JSON attribute list of \'%s\' is not a data dictionary\n",
- this_net->name.c_str(ctx));
-
- //
- // Loop through all attributes, adding them into the
- // design to annotate the cell
- //
- for (int attrid = 0; attrid < GetSize(attr_node->data_dict_keys); attrid++) {
- json_import_net_attrib(ctx, modname, this_net, attr_node, &this_net->attrs, attrid);
- }
- }
- }
- }
- }
- }
- // Import net aliases
- for (size_t i = 0; i < netids.size(); i++) {
- IdString netname = netids.at(i);
- if (!ctx->nets.count(netname))
- continue;
- for (auto &label : netlabels.at(i)) {
- IdString labelid = ctx->id(label);
- NPNR_ASSERT(!ctx->net_aliases.count(labelid));
- ctx->net_aliases[labelid] = netname;
- }
- }
- check_all_nets_driven(ctx);
- ctx->settings[ctx->id("synth")] = 1;
-}
-}; // End Namespace JsonParser
-
-bool parse_json_file(std::istream &f, std::string &filename, Context *ctx)
-{
- try {
- using namespace JsonParser;
-
- if (!f)
- log_error("failed to open JSON file.\n");
-
- int lineno = 1;
-
- JsonNode root(f, lineno);
-
- if (root.type != 'D')
- log_error("JSON root node is not a dictionary.\n");
-
- if (root.data_dict.count("modules") != 0) {
- JsonNode *modules = root.data_dict.at("modules");
-
- if (modules->type != 'D')
- log_error("JSON modules node is not a dictionary.\n");
-
- for (auto &it : modules->data_dict)
- json_import(ctx, it.first, it.second);
- }
-
- log_info("Checksum: 0x%08x\n", ctx->checksum());
- log_break();
- ctx->attributesToArchInfo();
- return true;
- } catch (log_execution_error_exception) {
- return false;
- }
-}
-
-bool load_json_settings(std::istream &f, std::string &filename, std::unordered_map<std::string, Property> &values)
-{
- try {
- using namespace JsonParser;
-
- if (!f)
- log_error("failed to open JSON file.\n");
-
- int lineno = 1;
-
- JsonNode root(f, lineno);
-
- if (root.type != 'D')
- log_error("JSON root node is not a dictionary.\n");
-
- if (root.data_dict.count("modules") != 0) {
- JsonNode *modules = root.data_dict.at("modules");
-
- if (modules->type != 'D')
- log_error("JSON modules node is not a dictionary.\n");
-
- for (auto &it : modules->data_dict) {
- JsonNode *node = it.second;
- if (is_blackbox(node))
- continue;
-
- if (node->data_dict.count("settings")) {
- JsonNode *attr_node = node->data_dict.at("settings");
- for (int attrid = 0; attrid < GetSize(attr_node->data_dict_keys); attrid++) {
- JsonNode *param = attr_node->data_dict.at(attr_node->data_dict_keys[attrid]);
- std::string pId = attr_node->data_dict_keys[attrid];
- values[pId] = json_parse_attr_param_value(param);
- }
- }
- }
- }
-
- return true;
- } catch (log_execution_error_exception) {
- return false;
- }
-}
-
-NEXTPNR_NAMESPACE_END
diff --git a/json/jsonwrite.cc b/json/jsonwrite.cc
index 3d3b70e4..9723d3b2 100644
--- a/json/jsonwrite.cc
+++ b/json/jsonwrite.cc
@@ -73,14 +73,17 @@ struct PortGroup
PortType dir;
};
-std::vector<PortGroup> group_ports(Context *ctx)
+std::vector<PortGroup> group_ports(Context *ctx, const std::unordered_map<IdString, PortInfo> &ports,
+ bool is_cell = false)
{
std::vector<PortGroup> groups;
std::unordered_map<std::string, size_t> base_to_group;
- for (auto &pair : ctx->ports) {
+ for (auto &pair : ports) {
std::string name = pair.second.name.str(ctx);
if ((name.back() != ']') || (name.find('[') == std::string::npos)) {
- groups.push_back({name, {pair.first.index}, pair.second.type});
+ groups.push_back({name,
+ {is_cell ? (pair.second.net ? pair.second.net->name.index : -1) : pair.first.index},
+ pair.second.type});
} else {
int off1 = int(name.find_last_of('['));
std::string basename = name.substr(0, off1);
@@ -95,26 +98,27 @@ std::vector<PortGroup> group_ports(Context *ctx)
if (int(grp.bits.size()) <= index)
grp.bits.resize(index + 1, -1);
NPNR_ASSERT(grp.bits.at(index) == -1);
- grp.bits.at(index) = pair.second.net ? pair.second.net->name.index : pair.first.index;
+ grp.bits.at(index) = pair.second.net ? pair.second.net->name.index : (is_cell ? -1 : pair.first.index);
}
}
return groups;
-};
+}
-std::string format_port_bits(const PortGroup &port)
+std::string format_port_bits(const PortGroup &port, int &dummy_idx)
{
std::stringstream s;
s << "[ ";
bool first = true;
- for (auto bit : port.bits) {
- if (!first)
- s << ", ";
- if (bit == -1)
- s << "\"x\"";
- else
- s << bit;
- first = false;
- }
+ if (port.bits.size() != 1 || port.bits.at(0) != -1) // skip single disconnected ports
+ for (auto bit : port.bits) {
+ if (!first)
+ s << ", ";
+ if (bit == -1)
+ s << (++dummy_idx);
+ else
+ s << bit;
+ first = false;
+ }
s << " ]";
return s.str();
}
@@ -122,6 +126,7 @@ std::string format_port_bits(const PortGroup &port)
void write_module(std::ostream &f, Context *ctx)
{
auto val = ctx->attrs.find(ctx->id("module"));
+ int dummy_idx = int(ctx->idstring_idx_to_str->size()) + 1000;
if (val != ctx->attrs.end())
f << stringf(" %s: {\n", get_string(val->second.as_string()).c_str());
else
@@ -134,14 +139,14 @@ void write_module(std::ostream &f, Context *ctx)
f << stringf("\n },\n");
f << stringf(" \"ports\": {");
- auto ports = group_ports(ctx);
+ auto ports = group_ports(ctx, ctx->ports);
bool first = true;
for (auto &port : ports) {
f << stringf("%s\n", first ? "" : ",");
f << stringf(" %s: {\n", get_string(port.name).c_str());
f << stringf(" \"direction\": \"%s\",\n",
port.dir == PORT_IN ? "input" : port.dir == PORT_INOUT ? "inout" : "output");
- f << stringf(" \"bits\": %s\n", format_port_bits(port).c_str());
+ f << stringf(" \"bits\": %s\n", format_port_bits(port, dummy_idx).c_str());
f << stringf(" }");
first = false;
}
@@ -151,6 +156,7 @@ void write_module(std::ostream &f, Context *ctx)
first = true;
for (auto &pair : ctx->cells) {
auto &c = pair.second;
+ auto cell_ports = group_ports(ctx, c->ports, true);
f << stringf("%s\n", first ? "" : ",");
f << stringf(" %s: {\n", get_name(c->name, ctx).c_str());
f << stringf(" \"hide_name\": %s,\n", c->name.c_str(ctx)[0] == '$' ? "1" : "0");
@@ -163,24 +169,18 @@ void write_module(std::ostream &f, Context *ctx)
f << stringf("\n },\n");
f << stringf(" \"port_directions\": {");
bool first2 = true;
- for (auto &conn : c->ports) {
- auto &p = conn.second;
- std::string direction = (p.type == PORT_IN) ? "input" : (p.type == PORT_OUT) ? "output" : "inout";
+ for (auto &pg : cell_ports) {
+ std::string direction = (pg.dir == PORT_IN) ? "input" : (pg.dir == PORT_OUT) ? "output" : "inout";
f << stringf("%s\n", first2 ? "" : ",");
- f << stringf(" %s: \"%s\"", get_name(conn.first, ctx).c_str(), direction.c_str());
+ f << stringf(" %s: \"%s\"", get_string(pg.name).c_str(), direction.c_str());
first2 = false;
}
f << stringf("\n },\n");
f << stringf(" \"connections\": {");
first2 = true;
- for (auto &conn : c->ports) {
- auto &p = conn.second;
+ for (auto &pg : cell_ports) {
f << stringf("%s\n", first2 ? "" : ",");
- if (p.net)
- f << stringf(" %s: [ %d ]", get_name(conn.first, ctx).c_str(), p.net->name.index);
- else
- f << stringf(" %s: [ ]", get_name(conn.first, ctx).c_str());
-
+ f << stringf(" %s: %s", get_string(pg.name).c_str(), format_port_bits(pg, dummy_idx).c_str());
first2 = false;
}
f << stringf("\n }\n");
diff --git a/python/interactive.py b/python/interactive.py
new file mode 100644
index 00000000..a2f09edb
--- /dev/null
+++ b/python/interactive.py
@@ -0,0 +1,6 @@
+# Pass this file to one of the Python script arguments (e.g. --pre-place interactive.py)
+# to drop to a command-line interactive Python session in the middle of place and route
+
+import code
+print("Press Ctrl+D to finish interactive session")
+code.interact(local=locals())
diff --git a/python/report_hierarchy.py b/python/report_hierarchy.py
new file mode 100644
index 00000000..6d409a9b
--- /dev/null
+++ b/python/report_hierarchy.py
@@ -0,0 +1,10 @@
+def visit(indent, data):
+ istr = " " * indent
+ print("{}{}: {}".format(istr, data.name, data.type))
+ for lname, gname in data.leaf_cells:
+ print("{} {} -> {}".format(istr, lname, gname))
+ for lname, gname in data.hier_cells:
+ visit(indent + 4, ctx.hierarchy[gname])
+
+visit(0, ctx.hierarchy[ctx.top_module])
+