Merge pull request #353 from YosysHQ/generic-frontend
New hierarchy-capable generic frontend framework and json11 based JSON frontend
This commit is contained in:
commit
4e0ca50db1
19
3rdparty/json11/LICENSE.txt
vendored
Normal file
19
3rdparty/json11/LICENSE.txt
vendored
Normal file
@ -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.
|
790
3rdparty/json11/json11.cpp
vendored
Normal file
790
3rdparty/json11/json11.cpp
vendored
Normal file
@ -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
|
232
3rdparty/json11/json11.hpp
vendored
Normal file
232
3rdparty/json11/json11.hpp
vendored
Normal file
@ -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
|
@ -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)
|
||||
|
@ -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");
|
||||
|
@ -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());
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
@ -806,6 +838,10 @@ struct Context : Arch, DeterministicRNG
|
||||
bool getActualRouteDelay(WireId src_wire, WireId dst_wire, delay_t *delay = nullptr,
|
||||
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
|
||||
|
@ -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");
|
||||
|
||||
|
@ -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
|
||||
- `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.
|
||||
|
@ -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
|
||||
|
@ -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");
|
||||
|
731
frontend/frontend_base.h
Normal file
731
frontend/frontend_base.h
Normal file
@ -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
|
203
frontend/json_frontend.cc
Normal file
203
frontend/json_frontend.cc
Normal file
@ -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
|
@ -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
|
@ -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>);
|
||||
}
|
||||
|
||||
|
@ -28,7 +28,6 @@
|
||||
#include <fstream>
|
||||
#include "designwidget.h"
|
||||
#include "fpgaviewwidget.h"
|
||||
#include "jsonparse.h"
|
||||
#include "jsonwrite.h"
|
||||
#include "log.h"
|
||||
#include "mainwindow.h"
|
||||
|
@ -27,7 +27,6 @@
|
||||
#include <fstream>
|
||||
#include "bitstream.h"
|
||||
#include "design_utils.h"
|
||||
#include "jsonparse.h"
|
||||
#include "log.h"
|
||||
#include "pcf.h"
|
||||
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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"
|
||||
|
@ -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();
|
||||
|
1028
json/jsonparse.cc
1028
json/jsonparse.cc
File diff suppressed because it is too large
Load Diff
10
python/report_hierarchy.py
Normal file
10
python/report_hierarchy.py
Normal file
@ -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])
|
||||
|
Loading…
Reference in New Issue
Block a user