diff options
author | David Shah <dave@ds0.me> | 2019-12-27 11:19:45 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-12-27 11:19:45 +0000 |
commit | 4e0ca50db137eb9d10098582be607c98601f8375 (patch) | |
tree | 7dd586694d7c3bca458502f3e6d677da1a6954b3 | |
parent | b6e2159cecdf79d0d94d0e6a9dda8cef6389cac1 (diff) | |
parent | 5774b13984bb151909b90ee2c668bdfb08387a2b (diff) | |
download | nextpnr-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.txt | 19 | ||||
-rw-r--r-- | 3rdparty/json11/json11.cpp | 790 | ||||
-rw-r--r-- | 3rdparty/json11/json11.hpp | 232 | ||||
-rw-r--r-- | CMakeLists.txt | 7 | ||||
-rw-r--r-- | common/arch_pybindings_shared.h | 4 | ||||
-rw-r--r-- | common/command.cc | 20 | ||||
-rw-r--r-- | common/nextpnr.cc | 84 | ||||
-rw-r--r-- | common/nextpnr.h | 40 | ||||
-rw-r--r-- | common/pybindings.cc | 28 | ||||
-rw-r--r-- | docs/netlist.md | 18 | ||||
-rw-r--r-- | ecp5/arch_pybindings.cc | 2 | ||||
-rw-r--r-- | ecp5/cells.cc | 10 | ||||
-rw-r--r-- | frontend/frontend_base.h | 731 | ||||
-rw-r--r-- | frontend/json_frontend.cc | 203 | ||||
-rw-r--r-- | frontend/json_frontend.h (renamed from json/jsonparse.h) | 14 | ||||
-rw-r--r-- | generic/arch_pybindings.cc | 2 | ||||
-rw-r--r-- | gui/basewindow.cc | 1 | ||||
-rw-r--r-- | gui/ice40/mainwindow.cc | 1 | ||||
-rw-r--r-- | ice40/arch_pybindings.cc | 2 | ||||
-rw-r--r-- | ice40/cells.cc | 4 | ||||
-rw-r--r-- | ice40/main.cc | 1 | ||||
-rw-r--r-- | ice40/pack.cc | 5 | ||||
-rw-r--r-- | json/jsonparse.cc | 1028 | ||||
-rw-r--r-- | python/report_hierarchy.py | 10 |
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 ¶ms = obj["parameters"]; + if (params.is_null()) + return; + for (const auto ¶m : 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]) + |