aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Shah <dave@ds0.me>2019-12-27 11:19:45 +0000
committerGitHub <noreply@github.com>2019-12-27 11:19:45 +0000
commit4e0ca50db137eb9d10098582be607c98601f8375 (patch)
tree7dd586694d7c3bca458502f3e6d677da1a6954b3
parentb6e2159cecdf79d0d94d0e6a9dda8cef6389cac1 (diff)
parent5774b13984bb151909b90ee2c668bdfb08387a2b (diff)
downloadnextpnr-4e0ca50db137eb9d10098582be607c98601f8375.tar.gz
nextpnr-4e0ca50db137eb9d10098582be607c98601f8375.tar.bz2
nextpnr-4e0ca50db137eb9d10098582be607c98601f8375.zip
Merge pull request #353 from YosysHQ/generic-frontend
New hierarchy-capable generic frontend framework and json11 based JSON frontend
-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.txt7
-rw-r--r--common/arch_pybindings_shared.h4
-rw-r--r--common/command.cc20
-rw-r--r--common/nextpnr.cc84
-rw-r--r--common/nextpnr.h40
-rw-r--r--common/pybindings.cc28
-rw-r--r--docs/netlist.md18
-rw-r--r--ecp5/arch_pybindings.cc2
-rw-r--r--ecp5/cells.cc10
-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_pybindings.cc2
-rw-r--r--gui/basewindow.cc1
-rw-r--r--gui/ice40/mainwindow.cc1
-rw-r--r--ice40/arch_pybindings.cc2
-rw-r--r--ice40/cells.cc4
-rw-r--r--ice40/main.cc1
-rw-r--r--ice40/pack.cc5
-rw-r--r--json/jsonparse.cc1028
-rw-r--r--python/report_hierarchy.py10
24 files changed, 2185 insertions, 1071 deletions
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 9af4bb6c..54da5bdd 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -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/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 fd310789..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"
@@ -265,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();
@@ -284,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());
@@ -382,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());
@@ -404,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/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 24f6948b..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,10 @@ 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;
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/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_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/cells.cc b/ecp5/cells.cc
index b06350c7..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"));
@@ -271,6 +273,8 @@ void ff_to_slice(Context *ctx, CellInfo *ff, CellInfo *lc, int index, bool drive
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)));
@@ -282,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));
@@ -309,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"));
@@ -340,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");
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_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/gui/basewindow.cc b/gui/basewindow.cc
index 550a4b93..a470335d 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_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/cells.cc b/ice40/cells.cc
index 3def82bf..f1901c43 100644
--- a/ice40/cells.cc
+++ b/ice40/cells.cc
@@ -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();
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 90c6de31..5b13e9ee 100644
--- a/ice40/pack.cc
+++ b/ice40/pack.cc
@@ -527,7 +527,9 @@ static void pack_io(Context *ctx)
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));
}
@@ -1485,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/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])
+