Merge remote-tracking branch 'origin/master' into mmicko/ecp5_gui
This commit is contained in:
commit
796d648995
@ -11,6 +11,6 @@ task:
|
||||
test_ice40_script: cd build && ./nextpnr-ice40-test
|
||||
smoketest_ice40_script: export NEXTPNR=$(pwd)/build/nextpnr-ice40 && cd ice40/smoketest/attosoc && ./smoketest.sh
|
||||
test_ecp5_script: cd build && ./nextpnr-ecp5-test
|
||||
smoketest_generic_script: export NEXTPNR=$(pwd)/build/nextpnr-generic && cd generic/examples && ./simple.sh
|
||||
smoketest_generic_script: export NEXTPNR=$(pwd)/build/nextpnr-generic && cd generic/examples && ./simple.sh && ./simtest.sh
|
||||
regressiontest_ice40_script: make -j $(nproc) -C tests/ice40/regressions NPNR=$(pwd)/build/nextpnr-ice40
|
||||
regressiontest_ecp5_script: make -j $(nproc) -C tests/ecp5/regressions NPNR=$(pwd)/build/nextpnr-ecp5
|
||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -19,6 +19,9 @@ CMakeCache.txt
|
||||
.*.swp
|
||||
a.out
|
||||
*.json
|
||||
*.dot
|
||||
*.il
|
||||
/generic/examples/blinky.png
|
||||
build/
|
||||
*.asc
|
||||
*.bin
|
||||
|
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
|
@ -97,7 +97,7 @@ endif()
|
||||
find_package(Sanitizers)
|
||||
|
||||
# List of Boost libraries to include
|
||||
set(boost_libs filesystem thread program_options iostreams)
|
||||
set(boost_libs filesystem thread program_options iostreams system)
|
||||
|
||||
if (BUILD_GUI AND NOT BUILD_PYTHON)
|
||||
message(FATAL_ERROR "GUI requires Python to build")
|
||||
@ -191,7 +191,7 @@ if (BUILD_PYTHON)
|
||||
endif ()
|
||||
endif()
|
||||
|
||||
include_directories(common/ json/ ${Boost_INCLUDE_DIRS} ${PYTHON_INCLUDE_DIRS})
|
||||
include_directories(common/ json/ frontend/ 3rdparty/json11/ ${Boost_INCLUDE_DIRS} ${PYTHON_INCLUDE_DIRS})
|
||||
|
||||
if(BUILD_HEAP)
|
||||
find_package (Eigen3 REQUIRED NO_MODULE)
|
||||
@ -202,7 +202,10 @@ endif()
|
||||
|
||||
aux_source_directory(common/ COMMON_SRC_FILES)
|
||||
aux_source_directory(json/ JSON_PARSER_FILES)
|
||||
set(COMMON_FILES ${COMMON_SRC_FILES} ${JSON_PARSER_FILES})
|
||||
aux_source_directory(3rdparty/json11 EXT_JSON11_FILES)
|
||||
aux_source_directory(frontend/ FRONTEND_FILES)
|
||||
|
||||
set(COMMON_FILES ${COMMON_SRC_FILES} ${EXT_JSON11_FILES} ${JSON_PARSER_FILES} ${FRONTEND_FILES})
|
||||
set(CMAKE_BUILD_TYPE Release)
|
||||
|
||||
if(MINGW)
|
||||
|
22
README.md
22
README.md
@ -6,13 +6,10 @@ tool.
|
||||
|
||||
Currently nextpnr supports:
|
||||
* Lattice iCE40 devices supported by [Project IceStorm](http://www.clifford.at/icestorm/)
|
||||
* *(experimental)* Lattice ECP5 devices supported by [Project Trellis](https://github.com/SymbiFlow/prjtrellis)
|
||||
* Lattice ECP5 devices supported by [Project Trellis](https://github.com/SymbiFlow/prjtrellis)
|
||||
* *(experimental)* a "generic" back-end for user-defined architectures
|
||||
|
||||
We hope to see Xilinx 7 Series thanks to
|
||||
[Project X-Ray](https://github.com/SymbiFlow/prjxray) and even more FPGA families
|
||||
supported in the future. We would love your help in developing this
|
||||
awesome new project!
|
||||
There is some work in progress towards [support for Xilinx devices](https://github.com/daveshah1/nextpnr-xilinx/) but it is not upstream and not intended for end users at the present time. We hope to see more FPGA families supported in the future. We would love your help in developing this awesome new project!
|
||||
|
||||
A brief (academic) paper describing the Yosys+nextpnr flow can be found
|
||||
on [arXiv](https://arxiv.org/abs/1903.10407).
|
||||
@ -38,7 +35,7 @@ of the selected architecture:
|
||||
- Qt5 or later (`qt5-default` for Ubuntu 16.04)
|
||||
- Python 3.5 or later, including development libraries (`python3-dev` for Ubuntu)
|
||||
- on Windows make sure to install same version as supported by [vcpkg](https://github.com/Microsoft/vcpkg/blob/master/ports/python3/CONTROL)
|
||||
- Boost libraries (`libboost-dev libboost-filesystem-dev libboost-thread-dev libboost-program-options-dev libboost-python-dev libboost-dev` or `libboost-all-dev` for Ubuntu)
|
||||
- Boost libraries (`libboost-dev libboost-filesystem-dev libboost-thread-dev libboost-program-options-dev libboost-python-dev libboost-iostreams-dev libboost-dev` or `libboost-all-dev` for Ubuntu)
|
||||
- Eigen3 (`libeigen3-dev` for Ubuntu) is required to build the analytic placer
|
||||
- Latest git Yosys is required to synthesise the demo design
|
||||
- For building on Windows with MSVC, usage of vcpkg is advised for dependency installation.
|
||||
@ -49,6 +46,8 @@ of the selected architecture:
|
||||
- For building on macOS, brew utility is needed.
|
||||
- Install all needed packages `brew install cmake python boost boost-python3 qt5 eigen`
|
||||
- Do not forget to add qt5 in path as well `echo 'export PATH="/usr/local/opt/qt/bin:$PATH"' >> ~/.bash_profile`
|
||||
|
||||
NOTE: this change is effective in next terminal session, so please re-open terminal window before next step
|
||||
|
||||
Getting started
|
||||
---------------
|
||||
@ -106,12 +105,7 @@ make -j$(nproc)
|
||||
sudo make install
|
||||
```
|
||||
|
||||
- For an ECP5 blinky on the 45k ULX3S board, first synthesise using `yosys blinky.ys` in `ecp5/synth`.
|
||||
- Then run ECP5 place-and route using `./nextpnr-ecp5 --json ecp5/synth/blinky.json --basecfg ecp5/synth/ulx3s_empty.config --textcfg ecp5/synth/ulx3s_out.config`
|
||||
- Create a bitstream using `ecppack ulx3s_out.config ulx3s.bit`
|
||||
- Note that `ulx3s_empty.config` contains fixed/unknown bits to be copied to the output bitstream
|
||||
|
||||
- More examples of the ECP5 flow for a range of boards can be found in the [Project Trellis Examples](https://github.com/SymbiFlow/prjtrellis/tree/master/examples).
|
||||
- Examples of the ECP5 flow for a range of boards can be found in the [Project Trellis Examples](https://github.com/SymbiFlow/prjtrellis/tree/master/examples).
|
||||
|
||||
|
||||
### nextpnr-generic
|
||||
@ -124,7 +118,7 @@ make -j$(nproc)
|
||||
sudo make install
|
||||
```
|
||||
|
||||
TBD: Getting started example for generic target.
|
||||
An example of how to use the generic flow is in [generic/examples](generic/examples). See also the [Generic Architecture docs](docs/generic.md).
|
||||
|
||||
Additional notes for building nextpnr
|
||||
-------------------------------------
|
||||
@ -170,7 +164,7 @@ Notes for developers
|
||||
Testing
|
||||
-------
|
||||
|
||||
- To build test binaries as well, use `-DBUILD_TESTS=ON` and after `make` run `make tests` to run them, or you can run separate binaries.
|
||||
- To build test binaries as well, use `-DBUILD_TESTS=ON` and after `make` run `make test` to run them, or you can run separate binaries.
|
||||
- To use code sanitizers use the `cmake` options:
|
||||
- `-DSANITIZE_ADDRESS=ON`
|
||||
- `-DSANITIZE_MEMORY=ON -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++`
|
||||
|
@ -5,7 +5,7 @@ ENDIF(CMAKE_CROSSCOMPILING)
|
||||
|
||||
IF(NOT CMAKE_CROSSCOMPILING)
|
||||
ADD_EXECUTABLE(bbasm bba/main.cc)
|
||||
target_link_libraries(bbasm LINK_PUBLIC ${Boost_PROGRAM_OPTIONS_LIBRARY})
|
||||
target_link_libraries(bbasm LINK_PUBLIC ${Boost_PROGRAM_OPTIONS_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_SYSTEM_LIBRARY})
|
||||
ENDIF(NOT CMAKE_CROSSCOMPILING)
|
||||
|
||||
IF(NOT CMAKE_CROSSCOMPILING)
|
||||
|
28
bba/main.cc
28
bba/main.cc
@ -19,6 +19,7 @@
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <boost/filesystem/convenience.hpp>
|
||||
#include <boost/program_options.hpp>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
@ -72,6 +73,7 @@ int main(int argc, char **argv)
|
||||
bool verbose = false;
|
||||
bool bigEndian;
|
||||
bool writeC = false;
|
||||
bool writeE = false;
|
||||
char buffer[512];
|
||||
|
||||
namespace po = boost::program_options;
|
||||
@ -82,7 +84,8 @@ int main(int argc, char **argv)
|
||||
options.add_options()("debug,d", "debug output");
|
||||
options.add_options()("be,b", "big endian");
|
||||
options.add_options()("le,l", "little endian");
|
||||
options.add_options()("c,c", "write c strings");
|
||||
options.add_options()("c,c", "write C strings");
|
||||
options.add_options()("e,e", "write #embed C");
|
||||
options.add_options()("files", po::value<std::vector<std::string>>(), "file parameters");
|
||||
pos.add("files", -1);
|
||||
|
||||
@ -115,13 +118,19 @@ int main(int argc, char **argv)
|
||||
}
|
||||
if (vm.count("c"))
|
||||
writeC = true;
|
||||
if (vm.count("e"))
|
||||
writeE = true;
|
||||
|
||||
if (writeC && writeE) {
|
||||
printf("Incompatible modes\n");
|
||||
exit(-1);
|
||||
}
|
||||
if (vm.count("files") == 0) {
|
||||
printf("File parameters are mandatory\n");
|
||||
exit(-1);
|
||||
}
|
||||
std::vector<std::string> files = vm["files"].as<std::vector<std::string>>();
|
||||
if (files.size() != 2) {
|
||||
if (files.size() != (writeE ? 3 : 2)) {
|
||||
printf("Input and output parameters must be set\n");
|
||||
exit(-1);
|
||||
}
|
||||
@ -433,6 +442,21 @@ int main(int argc, char **argv)
|
||||
|
||||
for (auto &s : postText)
|
||||
fprintf(fileOut, "%s\n", s.c_str());
|
||||
} else if (writeE) {
|
||||
for (auto &s : preText)
|
||||
fprintf(fileOut, "%s\n", s.c_str());
|
||||
|
||||
fprintf(fileOut, "const char %s[%d] =\n", streams[0].name.c_str(), int(data.size()) + 1);
|
||||
fprintf(fileOut, "#embed_str \"%s\"\n", boost::filesystem::basename(files.at(2)).c_str());
|
||||
fprintf(fileOut, ";\n");
|
||||
|
||||
for (auto &s : postText)
|
||||
fprintf(fileOut, "%s\n", s.c_str());
|
||||
|
||||
FILE *fileBin = fopen(files.at(2).c_str(), "wb");
|
||||
assert(fileBin != nullptr);
|
||||
fwrite(data.data(), int(data.size()), 1, fileBin);
|
||||
fclose(fileBin);
|
||||
} else {
|
||||
fwrite(data.data(), int(data.size()), 1, fileOut);
|
||||
}
|
||||
|
@ -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"
|
||||
@ -149,6 +149,9 @@ po::options_description CommandHandler::getGeneralOptions()
|
||||
general.add_options()("freq", po::value<double>(), "set target frequency for design in MHz");
|
||||
general.add_options()("timing-allow-fail", "allow timing to fail in design");
|
||||
general.add_options()("no-tmdriv", "disable timing-driven placement");
|
||||
general.add_options()("sdf", po::value<std::string>(), "SDF delay back-annotation file to write");
|
||||
general.add_options()("sdf-cvc", "enable tweaks for SDF file compatibility with the CVC simulator");
|
||||
|
||||
return general;
|
||||
}
|
||||
|
||||
@ -262,9 +265,8 @@ int CommandHandler::executeMain(std::unique_ptr<Context> ctx)
|
||||
if (vm.count("json")) {
|
||||
std::string filename = vm["json"].as<std::string>();
|
||||
std::ifstream f(filename);
|
||||
if (!parse_json_file(f, filename, w.getContext()))
|
||||
if (!parse_json(f, filename, w.getContext()))
|
||||
log_error("Loading design failed.\n");
|
||||
|
||||
customAfterLoad(w.getContext());
|
||||
w.notifyChangeContext();
|
||||
w.updateActions();
|
||||
@ -281,7 +283,7 @@ int CommandHandler::executeMain(std::unique_ptr<Context> ctx)
|
||||
if (vm.count("json")) {
|
||||
std::string filename = vm["json"].as<std::string>();
|
||||
std::ifstream f(filename);
|
||||
if (!parse_json_file(f, filename, ctx.get()))
|
||||
if (!parse_json(f, filename, ctx.get()))
|
||||
log_error("Loading design failed.\n");
|
||||
|
||||
customAfterLoad(ctx.get());
|
||||
@ -336,6 +338,14 @@ int CommandHandler::executeMain(std::unique_ptr<Context> ctx)
|
||||
log_error("Saving design failed.\n");
|
||||
}
|
||||
|
||||
if (vm.count("sdf")) {
|
||||
std::string filename = vm["sdf"].as<std::string>();
|
||||
std::ofstream f(filename);
|
||||
if (!f)
|
||||
log_error("Failed to open SDF file '%s' for writing.\n", filename.c_str());
|
||||
ctx->writeSDF(f, vm.count("sdf-cvc"));
|
||||
}
|
||||
|
||||
#ifndef NO_PYTHON
|
||||
deinit_python();
|
||||
#endif
|
||||
@ -371,12 +381,6 @@ int CommandHandler::exec()
|
||||
return 0;
|
||||
|
||||
std::unordered_map<std::string, Property> values;
|
||||
if (vm.count("json")) {
|
||||
std::string filename = vm["json"].as<std::string>();
|
||||
std::ifstream f(filename);
|
||||
if (!load_json_settings(f, filename, values))
|
||||
log_error("Loading design failed.\n");
|
||||
}
|
||||
std::unique_ptr<Context> ctx = createContext(values);
|
||||
setupContext(ctx.get());
|
||||
setupArchContext(ctx.get());
|
||||
@ -393,17 +397,12 @@ std::unique_ptr<Context> CommandHandler::load_json(std::string filename)
|
||||
{
|
||||
vm.clear();
|
||||
std::unordered_map<std::string, Property> values;
|
||||
{
|
||||
std::ifstream f(filename);
|
||||
if (!load_json_settings(f, filename, values))
|
||||
log_error("Loading design failed.\n");
|
||||
}
|
||||
std::unique_ptr<Context> ctx = createContext(values);
|
||||
setupContext(ctx.get());
|
||||
setupArchContext(ctx.get());
|
||||
{
|
||||
std::ifstream f(filename);
|
||||
if (!parse_json_file(f, filename, ctx.get()))
|
||||
if (!parse_json(f, filename, ctx.get()))
|
||||
log_error("Loading design failed.\n");
|
||||
}
|
||||
customAfterLoad(ctx.get());
|
||||
|
@ -88,7 +88,7 @@ void connect_port(const Context *ctx, NetInfo *net, CellInfo *cell, IdString por
|
||||
NPNR_ASSERT(net->driver.cell == nullptr);
|
||||
net->driver.cell = cell;
|
||||
net->driver.port = port_name;
|
||||
} else if (port.type == PORT_IN) {
|
||||
} else if (port.type == PORT_IN || port.type == PORT_INOUT) {
|
||||
PortRef user;
|
||||
user.cell = cell;
|
||||
user.port = port_name;
|
||||
@ -146,4 +146,14 @@ void rename_port(Context *ctx, CellInfo *cell, IdString old_name, IdString new_n
|
||||
cell->ports[new_name] = pi;
|
||||
}
|
||||
|
||||
void rename_net(Context *ctx, NetInfo *net, IdString new_name)
|
||||
{
|
||||
if (net == nullptr)
|
||||
return;
|
||||
NPNR_ASSERT(!ctx->nets.count(new_name));
|
||||
std::swap(ctx->nets[net->name], ctx->nets[new_name]);
|
||||
ctx->nets.erase(net->name);
|
||||
net->name = new_name;
|
||||
}
|
||||
|
||||
NEXTPNR_NAMESPACE_END
|
||||
|
@ -94,6 +94,9 @@ void connect_ports(Context *ctx, CellInfo *cell1, IdString port1_name, CellInfo
|
||||
// Rename a port if it exists on a cell
|
||||
void rename_port(Context *ctx, CellInfo *cell, IdString old_name, IdString new_name);
|
||||
|
||||
// Rename a net without invalidating pointers to it
|
||||
void rename_net(Context *ctx, NetInfo *net, IdString new_name);
|
||||
|
||||
void print_utilisation(const Context *ctx);
|
||||
|
||||
NEXTPNR_NAMESPACE_END
|
||||
|
@ -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,15 @@ 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
|
||||
void writeSDF(std::ostream &out, bool cvc_mode = false) const;
|
||||
|
||||
// --------------------------------------------------------------
|
||||
|
||||
uint32_t checksum() const;
|
||||
|
@ -308,6 +308,14 @@ class HeAPPlacer
|
||||
std::vector<std::vector<int>> nearest_row_with_bel;
|
||||
std::vector<std::vector<int>> nearest_col_with_bel;
|
||||
|
||||
struct BoundingBox
|
||||
{
|
||||
// Actual bounding box
|
||||
int x0 = 0, x1 = 0, y0 = 0, y1 = 0;
|
||||
};
|
||||
|
||||
std::unordered_map<IdString, BoundingBox> constraint_region_bounds;
|
||||
|
||||
// In some cases, we can't use bindBel because we allow overlap in the earlier stages. So we use this custom
|
||||
// structure instead
|
||||
struct CellLocation
|
||||
@ -443,6 +451,31 @@ class HeAPPlacer
|
||||
nr.at(y) = loc.y;
|
||||
}
|
||||
}
|
||||
|
||||
// Determine bounding boxes of region constraints
|
||||
for (auto ®ion : sorted(ctx->region)) {
|
||||
Region *r = region.second;
|
||||
BoundingBox bb;
|
||||
if (r->constr_bels) {
|
||||
bb.x0 = std::numeric_limits<int>::max();
|
||||
bb.x1 = std::numeric_limits<int>::min();
|
||||
bb.y0 = std::numeric_limits<int>::max();
|
||||
bb.y1 = std::numeric_limits<int>::min();
|
||||
for (auto bel : r->bels) {
|
||||
Loc loc = ctx->getBelLocation(bel);
|
||||
bb.x0 = std::min(bb.x0, loc.x);
|
||||
bb.x1 = std::max(bb.x1, loc.x);
|
||||
bb.y0 = std::min(bb.y0, loc.y);
|
||||
bb.y1 = std::max(bb.y1, loc.y);
|
||||
}
|
||||
} else {
|
||||
bb.x0 = 0;
|
||||
bb.y0 = 0;
|
||||
bb.x1 = max_x;
|
||||
bb.y1 = max_y;
|
||||
}
|
||||
constraint_region_bounds[r->name] = bb;
|
||||
}
|
||||
}
|
||||
|
||||
// Build and solve in one direction
|
||||
@ -684,9 +717,15 @@ class HeAPPlacer
|
||||
if (yaxis) {
|
||||
cell_locs.at(solve_cells.at(i)->name).rawy = vals.at(i);
|
||||
cell_locs.at(solve_cells.at(i)->name).y = std::min(max_y, std::max(0, int(vals.at(i))));
|
||||
if (solve_cells.at(i)->region != nullptr)
|
||||
cell_locs.at(solve_cells.at(i)->name).y =
|
||||
limit_to_reg(solve_cells.at(i)->region, cell_locs.at(solve_cells.at(i)->name).y, true);
|
||||
} else {
|
||||
cell_locs.at(solve_cells.at(i)->name).rawx = vals.at(i);
|
||||
cell_locs.at(solve_cells.at(i)->name).x = std::min(max_x, std::max(0, int(vals.at(i))));
|
||||
if (solve_cells.at(i)->region != nullptr)
|
||||
cell_locs.at(solve_cells.at(i)->name).x =
|
||||
limit_to_reg(solve_cells.at(i)->region, cell_locs.at(solve_cells.at(i)->name).x, false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -735,6 +774,7 @@ class HeAPPlacer
|
||||
}
|
||||
int ripup_radius = 2;
|
||||
int total_iters = 0;
|
||||
int total_iters_noreset = 0;
|
||||
while (!remaining.empty()) {
|
||||
auto top = remaining.top();
|
||||
remaining.pop();
|
||||
@ -754,15 +794,38 @@ class HeAPPlacer
|
||||
int best_inp_len = std::numeric_limits<int>::max();
|
||||
|
||||
total_iters++;
|
||||
total_iters_noreset++;
|
||||
if (total_iters > int(solve_cells.size())) {
|
||||
total_iters = 0;
|
||||
ripup_radius = std::max(std::max(max_x, max_y), ripup_radius * 2);
|
||||
}
|
||||
|
||||
if (total_iters_noreset > std::max(5000, 8 * int(ctx->cells.size()))) {
|
||||
log_error("Unable to find legal placement for all cells, design is probably at utilisation limit.\n");
|
||||
}
|
||||
|
||||
while (!placed) {
|
||||
|
||||
int nx = ctx->rng(2 * radius + 1) + std::max(cell_locs.at(ci->name).x - radius, 0);
|
||||
int ny = ctx->rng(2 * radius + 1) + std::max(cell_locs.at(ci->name).y - radius, 0);
|
||||
// Set a conservative timeout
|
||||
if (iter > std::max(1000, 3 * int(ctx->cells.size())))
|
||||
log_error("Unable to find legal placement for cell '%s', check constraints and utilisation.\n",
|
||||
ctx->nameOf(ci));
|
||||
|
||||
int rx = radius, ry = radius;
|
||||
|
||||
if (ci->region != nullptr) {
|
||||
rx = std::min(radius, (constraint_region_bounds[ci->region->name].x1 -
|
||||
constraint_region_bounds[ci->region->name].x0) /
|
||||
2 +
|
||||
1);
|
||||
ry = std::min(radius, (constraint_region_bounds[ci->region->name].y1 -
|
||||
constraint_region_bounds[ci->region->name].y0) /
|
||||
2 +
|
||||
1);
|
||||
}
|
||||
|
||||
int nx = ctx->rng(2 * rx + 1) + std::max(cell_locs.at(ci->name).x - rx, 0);
|
||||
int ny = ctx->rng(2 * ry + 1) + std::max(cell_locs.at(ci->name).y - ry, 0);
|
||||
|
||||
iter++;
|
||||
iter_at_radius++;
|
||||
@ -820,6 +883,8 @@ class HeAPPlacer
|
||||
|
||||
if (ci->constr_children.empty() && !ci->constr_abs_z) {
|
||||
for (auto sz : fb.at(nx).at(ny)) {
|
||||
if (ci->region != nullptr && ci->region->constr_bels && !ci->region->bels.count(sz))
|
||||
continue;
|
||||
if (ctx->checkBelAvail(sz) || (radius > ripup_radius || ctx->rng(20000) < 10)) {
|
||||
CellInfo *bound = ctx->getBoundBelCell(sz);
|
||||
if (bound != nullptr) {
|
||||
@ -881,6 +946,8 @@ class HeAPPlacer
|
||||
Loc ploc = visit.front().second;
|
||||
visit.pop();
|
||||
BelId target = ctx->getBelByLocation(ploc);
|
||||
if (vc->region != nullptr && vc->region->constr_bels && !vc->region->bels.count(target))
|
||||
goto fail;
|
||||
CellInfo *bound;
|
||||
if (target == BelId() || ctx->getBelType(target) != vc->type)
|
||||
goto fail;
|
||||
@ -948,6 +1015,15 @@ class HeAPPlacer
|
||||
// Implementation of the cut-based spreading as described in the HeAP/SimPL papers
|
||||
static constexpr float beta = 0.9;
|
||||
|
||||
template <typename T> T limit_to_reg(Region *reg, T val, bool dir)
|
||||
{
|
||||
if (reg == nullptr)
|
||||
return val;
|
||||
int limit_low = dir ? constraint_region_bounds[reg->name].y0 : constraint_region_bounds[reg->name].x0;
|
||||
int limit_high = dir ? constraint_region_bounds[reg->name].y1 : constraint_region_bounds[reg->name].x1;
|
||||
return std::max<T>(std::min<T>(val, limit_high), limit_low);
|
||||
}
|
||||
|
||||
struct ChainExtent
|
||||
{
|
||||
int x0, y0, x1, y1;
|
||||
@ -1460,10 +1536,22 @@ class HeAPPlacer
|
||||
: p->cell_locs.at(cut_cells.at(br.first - 1)->name).rawx;
|
||||
double m = (br.second - bl.second) / std::max(0.00001, orig_right - orig_left);
|
||||
for (int j = bl.first; j < br.first; j++) {
|
||||
auto &pos = dir ? p->cell_locs.at(cut_cells.at(j)->name).rawy
|
||||
: p->cell_locs.at(cut_cells.at(j)->name).rawx;
|
||||
NPNR_ASSERT(pos >= orig_left && pos <= orig_right);
|
||||
pos = bl.second + m * (pos - orig_left);
|
||||
Region *cr = cut_cells.at(j)->region;
|
||||
if (cr != nullptr) {
|
||||
// Limit spreading bounds to constraint region; if applicable
|
||||
double brsc = p->limit_to_reg(cr, br.second, dir);
|
||||
double blsc = p->limit_to_reg(cr, bl.second, dir);
|
||||
double mr = (brsc - blsc) / std::max(0.00001, orig_right - orig_left);
|
||||
auto &pos = dir ? p->cell_locs.at(cut_cells.at(j)->name).rawy
|
||||
: p->cell_locs.at(cut_cells.at(j)->name).rawx;
|
||||
NPNR_ASSERT(pos >= orig_left && pos <= orig_right);
|
||||
pos = blsc + mr * (pos - orig_left);
|
||||
} else {
|
||||
auto &pos = dir ? p->cell_locs.at(cut_cells.at(j)->name).rawy
|
||||
: p->cell_locs.at(cut_cells.at(j)->name).rawx;
|
||||
NPNR_ASSERT(pos >= orig_left && pos <= orig_right);
|
||||
pos = bl.second + m * (pos - orig_left);
|
||||
}
|
||||
// log("[%f, %f] -> [%f, %f]: %f -> %f\n", orig_left, orig_right, bl.second, br.second,
|
||||
// orig_pos, pos);
|
||||
}
|
||||
|
@ -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");
|
||||
|
||||
|
334
common/sdf.cc
Normal file
334
common/sdf.cc
Normal file
@ -0,0 +1,334 @@
|
||||
/*
|
||||
* nextpnr -- Next Generation Place and Route
|
||||
*
|
||||
* Copyright (C) 2019 David Shah <dave@ds0.me>
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "nextpnr.h"
|
||||
#include "util.h"
|
||||
|
||||
NEXTPNR_NAMESPACE_BEGIN
|
||||
|
||||
namespace SDF {
|
||||
|
||||
struct MinMaxTyp
|
||||
{
|
||||
double min, typ, max;
|
||||
};
|
||||
|
||||
struct RiseFallDelay
|
||||
{
|
||||
MinMaxTyp rise, fall;
|
||||
};
|
||||
|
||||
struct PortAndEdge
|
||||
{
|
||||
std::string port;
|
||||
ClockEdge edge;
|
||||
};
|
||||
|
||||
struct IOPath
|
||||
{
|
||||
std::string from, to;
|
||||
RiseFallDelay delay;
|
||||
};
|
||||
|
||||
struct TimingCheck
|
||||
{
|
||||
enum CheckType
|
||||
{
|
||||
SETUPHOLD,
|
||||
PERIOD,
|
||||
WIDTH
|
||||
} type;
|
||||
PortAndEdge from, to;
|
||||
RiseFallDelay delay;
|
||||
};
|
||||
|
||||
struct Cell
|
||||
{
|
||||
std::string celltype, instance;
|
||||
std::vector<IOPath> iopaths;
|
||||
std::vector<TimingCheck> checks;
|
||||
};
|
||||
|
||||
struct CellPort
|
||||
{
|
||||
std::string cell, port;
|
||||
};
|
||||
|
||||
struct Interconnect
|
||||
{
|
||||
CellPort from, to;
|
||||
RiseFallDelay delay;
|
||||
};
|
||||
|
||||
struct SDFWriter
|
||||
{
|
||||
bool cvc_mode = false;
|
||||
std::vector<Cell> cells;
|
||||
std::vector<Interconnect> conn;
|
||||
std::string sdfversion, design, vendor, program;
|
||||
|
||||
std::string format_name(const std::string &name)
|
||||
{
|
||||
std::string fmt = "\"";
|
||||
for (char c : name) {
|
||||
if (c == '\\' || c == '\"')
|
||||
fmt += "\"";
|
||||
fmt += c;
|
||||
}
|
||||
fmt += "\"";
|
||||
return fmt;
|
||||
}
|
||||
|
||||
std::string escape_name(const std::string &name)
|
||||
{
|
||||
std::string esc;
|
||||
for (char c : name) {
|
||||
if (c == '$' || c == '\\' || c == '[' || c == ']' || c == ':' || (cvc_mode && c == '.'))
|
||||
esc += '\\';
|
||||
esc += c;
|
||||
}
|
||||
return esc;
|
||||
}
|
||||
|
||||
std::string timing_check_name(TimingCheck::CheckType type)
|
||||
{
|
||||
switch (type) {
|
||||
case TimingCheck::SETUPHOLD:
|
||||
return "SETUPHOLD";
|
||||
case TimingCheck::PERIOD:
|
||||
return "PERIOD";
|
||||
case TimingCheck::WIDTH:
|
||||
return "WIDTH";
|
||||
default:
|
||||
NPNR_ASSERT_FALSE("unknown timing check type");
|
||||
}
|
||||
}
|
||||
|
||||
void write_delay(std::ostream &out, const RiseFallDelay &delay)
|
||||
{
|
||||
write_delay(out, delay.rise);
|
||||
out << " ";
|
||||
write_delay(out, delay.fall);
|
||||
}
|
||||
|
||||
void write_delay(std::ostream &out, const MinMaxTyp &delay)
|
||||
{
|
||||
if (cvc_mode)
|
||||
out << "(" << int(delay.min) << ":" << int(delay.typ) << ":" << int(delay.max) << ")";
|
||||
else
|
||||
out << "(" << delay.min << ":" << delay.typ << ":" << delay.max << ")";
|
||||
}
|
||||
|
||||
void write_port(std::ostream &out, const CellPort &port)
|
||||
{
|
||||
if (cvc_mode)
|
||||
out << escape_name(port.cell) + "." + escape_name(port.port);
|
||||
else
|
||||
out << escape_name(port.cell + "/" + port.port);
|
||||
}
|
||||
|
||||
void write_portedge(std::ostream &out, const PortAndEdge &pe)
|
||||
{
|
||||
out << "(" << (pe.edge == RISING_EDGE ? "posedge" : "negedge") << " " << escape_name(pe.port) << ")";
|
||||
}
|
||||
|
||||
void write(std::ostream &out)
|
||||
{
|
||||
out << "(DELAYFILE" << std::endl;
|
||||
// Headers and metadata
|
||||
out << " (SDFVERSION " << format_name(sdfversion) << ")" << std::endl;
|
||||
out << " (DESIGN " << format_name(design) << ")" << std::endl;
|
||||
out << " (VENDOR " << format_name(vendor) << ")" << std::endl;
|
||||
out << " (PROGRAM " << format_name(program) << ")" << std::endl;
|
||||
out << " (DIVIDER " << (cvc_mode ? "." : "/") << ")" << std::endl;
|
||||
out << " (TIMESCALE 1ps)" << std::endl;
|
||||
// Write interconnect delays, with the main design begin a "cell"
|
||||
out << " (CELL" << std::endl;
|
||||
out << " (CELLTYPE " << format_name(design) << ")" << std::endl;
|
||||
out << " (INSTANCE )" << std::endl;
|
||||
out << " (DELAY" << std::endl;
|
||||
out << " (ABSOLUTE" << std::endl;
|
||||
for (auto &ic : conn) {
|
||||
out << " (INTERCONNECT ";
|
||||
write_port(out, ic.from);
|
||||
out << " ";
|
||||
write_port(out, ic.to);
|
||||
out << " ";
|
||||
write_delay(out, ic.delay);
|
||||
out << ")" << std::endl;
|
||||
}
|
||||
out << " )" << std::endl;
|
||||
out << " )" << std::endl;
|
||||
out << " )" << std::endl;
|
||||
// Write cells
|
||||
for (auto &cell : cells) {
|
||||
out << " (CELL" << std::endl;
|
||||
out << " (CELLTYPE " << format_name(cell.celltype) << ")" << std::endl;
|
||||
out << " (INSTANCE " << escape_name(cell.instance) << ")" << std::endl;
|
||||
// IOPATHs (combinational delay and clock-to-q)
|
||||
if (!cell.iopaths.empty()) {
|
||||
out << " (DELAY" << std::endl;
|
||||
out << " (ABSOLUTE" << std::endl;
|
||||
for (auto &path : cell.iopaths) {
|
||||
out << " (IOPATH " << escape_name(path.from) << " " << escape_name(path.to) << " ";
|
||||
write_delay(out, path.delay);
|
||||
out << ")" << std::endl;
|
||||
}
|
||||
out << " )" << std::endl;
|
||||
out << " )" << std::endl;
|
||||
}
|
||||
// Timing Checks (setup/hold, period, width)
|
||||
if (!cell.checks.empty()) {
|
||||
out << " (TIMINGCHECK" << std::endl;
|
||||
for (auto &check : cell.checks) {
|
||||
out << " (" << timing_check_name(check.type) << " ";
|
||||
write_portedge(out, check.from);
|
||||
out << " ";
|
||||
if (check.type == TimingCheck::SETUPHOLD) {
|
||||
write_portedge(out, check.to);
|
||||
out << " ";
|
||||
}
|
||||
if (check.type == TimingCheck::SETUPHOLD)
|
||||
write_delay(out, check.delay);
|
||||
else
|
||||
write_delay(out, check.delay.rise);
|
||||
out << ")" << std::endl;
|
||||
}
|
||||
out << " )" << std::endl;
|
||||
}
|
||||
out << " )" << std::endl;
|
||||
}
|
||||
out << ")" << std::endl;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace SDF
|
||||
|
||||
void Context::writeSDF(std::ostream &out, bool cvc_mode) const
|
||||
{
|
||||
using namespace SDF;
|
||||
SDFWriter wr;
|
||||
wr.cvc_mode = cvc_mode;
|
||||
wr.design = str_or_default(attrs, id("module"), "top");
|
||||
wr.sdfversion = "3.0";
|
||||
wr.vendor = "nextpnr";
|
||||
wr.program = "nextpnr";
|
||||
|
||||
const double delay_scale = 1000;
|
||||
// Convert from DelayInfo to SDF-friendly RiseFallDelay
|
||||
auto convert_delay = [&](const DelayInfo &dly) {
|
||||
RiseFallDelay rf;
|
||||
rf.rise.min = getDelayNS(dly.minRaiseDelay()) * delay_scale;
|
||||
rf.rise.typ = getDelayNS((dly.minRaiseDelay() + dly.maxRaiseDelay()) / 2) * delay_scale; // fixme: typ delays?
|
||||
rf.rise.max = getDelayNS(dly.maxRaiseDelay()) * delay_scale;
|
||||
rf.fall.min = getDelayNS(dly.minFallDelay()) * delay_scale;
|
||||
rf.fall.typ = getDelayNS((dly.minFallDelay() + dly.maxFallDelay()) / 2) * delay_scale; // fixme: typ delays?
|
||||
rf.fall.max = getDelayNS(dly.maxFallDelay()) * delay_scale;
|
||||
return rf;
|
||||
};
|
||||
|
||||
auto convert_setuphold = [&](const DelayInfo &setup, const DelayInfo &hold) {
|
||||
RiseFallDelay rf;
|
||||
rf.rise.min = getDelayNS(setup.minDelay()) * delay_scale;
|
||||
rf.rise.typ = getDelayNS((setup.minDelay() + setup.maxDelay()) / 2) * delay_scale; // fixme: typ delays?
|
||||
rf.rise.max = getDelayNS(setup.maxDelay()) * delay_scale;
|
||||
rf.fall.min = getDelayNS(hold.minDelay()) * delay_scale;
|
||||
rf.fall.typ = getDelayNS((hold.minDelay() + hold.maxDelay()) / 2) * delay_scale; // fixme: typ delays?
|
||||
rf.fall.max = getDelayNS(hold.maxDelay()) * delay_scale;
|
||||
return rf;
|
||||
};
|
||||
|
||||
for (auto cell : sorted(cells)) {
|
||||
Cell sc;
|
||||
const CellInfo *ci = cell.second;
|
||||
sc.instance = ci->name.str(this);
|
||||
sc.celltype = ci->type.str(this);
|
||||
for (auto port : ci->ports) {
|
||||
int clockCount = 0;
|
||||
TimingPortClass cls = getPortTimingClass(ci, port.first, clockCount);
|
||||
if (cls == TMG_IGNORE)
|
||||
continue;
|
||||
if (port.second.net == nullptr)
|
||||
continue; // Ignore disconnected ports
|
||||
if (port.second.type != PORT_IN) {
|
||||
// Add combinational paths to this output (or inout)
|
||||
for (auto other : ci->ports) {
|
||||
if (other.second.net == nullptr)
|
||||
continue;
|
||||
if (other.second.type == PORT_OUT)
|
||||
continue;
|
||||
DelayInfo dly;
|
||||
if (!getCellDelay(ci, other.first, port.first, dly))
|
||||
continue;
|
||||
IOPath iop;
|
||||
iop.from = other.first.str(this);
|
||||
iop.to = port.first.str(this);
|
||||
iop.delay = convert_delay(dly);
|
||||
sc.iopaths.push_back(iop);
|
||||
}
|
||||
// Add clock-to-output delays, also as IOPaths
|
||||
if (cls == TMG_REGISTER_OUTPUT)
|
||||
for (int i = 0; i < clockCount; i++) {
|
||||
auto clkInfo = getPortClockingInfo(ci, port.first, i);
|
||||
IOPath cqp;
|
||||
cqp.from = clkInfo.clock_port.str(this);
|
||||
cqp.to = port.first.str(this);
|
||||
cqp.delay = convert_delay(clkInfo.clockToQ);
|
||||
sc.iopaths.push_back(cqp);
|
||||
}
|
||||
}
|
||||
if (port.second.type != PORT_OUT && cls == TMG_REGISTER_INPUT) {
|
||||
// Add setup/hold checks
|
||||
for (int i = 0; i < clockCount; i++) {
|
||||
auto clkInfo = getPortClockingInfo(ci, port.first, i);
|
||||
TimingCheck chk;
|
||||
chk.from.edge = RISING_EDGE; // Add setup/hold checks equally for rising and falling edges
|
||||
chk.from.port = port.first.str(this);
|
||||
chk.to.edge = clkInfo.edge;
|
||||
chk.to.port = clkInfo.clock_port.str(this);
|
||||
chk.type = TimingCheck::SETUPHOLD;
|
||||
chk.delay = convert_setuphold(clkInfo.setup, clkInfo.hold);
|
||||
sc.checks.push_back(chk);
|
||||
chk.from.edge = FALLING_EDGE;
|
||||
sc.checks.push_back(chk);
|
||||
}
|
||||
}
|
||||
}
|
||||
wr.cells.push_back(sc);
|
||||
}
|
||||
|
||||
for (auto net : sorted(nets)) {
|
||||
NetInfo *ni = net.second;
|
||||
if (ni->driver.cell == nullptr)
|
||||
continue;
|
||||
for (auto &usr : ni->users) {
|
||||
Interconnect ic;
|
||||
ic.from.cell = ni->driver.cell->name.str(this);
|
||||
ic.from.port = ni->driver.port.str(this);
|
||||
ic.to.cell = usr.cell->name.str(this);
|
||||
ic.to.port = usr.port.str(this);
|
||||
// FIXME: min/max routing delay - or at least constructing DelayInfo here
|
||||
ic.delay = convert_delay(getDelayFromNS(getDelayNS(getNetinfoRouteDelay(ni, usr))));
|
||||
wr.conn.push_back(ic);
|
||||
}
|
||||
}
|
||||
wr.write(out);
|
||||
}
|
||||
|
||||
NEXTPNR_NAMESPACE_END
|
@ -434,8 +434,7 @@ struct Timing
|
||||
int port_clocks;
|
||||
TimingPortClass portClass =
|
||||
ctx->getPortTimingClass(crit_net->driver.cell, port.first, port_clocks);
|
||||
if (portClass == TMG_CLOCK_INPUT || portClass == TMG_ENDPOINT || portClass == TMG_IGNORE ||
|
||||
portClass == TMG_REGISTER_INPUT)
|
||||
if (portClass == TMG_CLOCK_INPUT || portClass == TMG_ENDPOINT || portClass == TMG_IGNORE)
|
||||
continue;
|
||||
// And find the fanin net with the latest arrival time
|
||||
if (net_data.count(port.second.net) &&
|
||||
|
33
docs/faq.md
33
docs/faq.md
@ -132,9 +132,8 @@ Nextpnr and other tools
|
||||
|
||||
### Which toolchain should I use and why?
|
||||
|
||||
* If you wish to do new **research** into FPGA architectures, place and route
|
||||
algorithms or other similar topics, we suggest you look at using
|
||||
[Verilog to Routing](https://verilogtorouting.org).
|
||||
* If you wish to do new **research** into FPGA architectures, or other similar topics, we suggest you look at using
|
||||
[Verilog to Routing](https://verilogtorouting.org). If you want to use nextpnr, you might also be able to use the [Generic Arch](generic.md).
|
||||
|
||||
* If you are developing FPGA code in **Verilog** for a **Lattice iCE40** and
|
||||
need an open source toolchain, we suggest you use [Yosys](http://www.clifford.at/yosys/) and nextpnr.
|
||||
@ -144,14 +143,9 @@ Nextpnr and other tools
|
||||
migrating to nextpnr.
|
||||
|
||||
* If you are developing Verilog FPGA code targeted at the Lattice ECP5 and
|
||||
need an open source toolchain, you may consider the **extremely
|
||||
experimental** ECP5 support in Yosys and nextpnr.
|
||||
need an open source toolchain, there is also stable ECP5 support in Yosys and nextpnr.
|
||||
|
||||
* If you are developing FPGA code in **VHDL** you will need to use either a
|
||||
version of [Yosys with Verific support](https://github.com/YosysHQ/yosys/tree/master/frontends/verific) or the vendor provided tools due
|
||||
to the lack of useful open source VHDL support in Yosys. You could also look at developing
|
||||
one of the experimental open source VHDL frontends, such as [yavhdl](https://github.com/rqou/yavhdl)
|
||||
or [ghdlsynth-beta](https://github.com/tgingold/ghdlsynth-beta), further.
|
||||
* If you are developing FPGA code in **VHDL** you may wish to look at the [ghdlsynth-beta](https://github.com/tgingold/ghdlsynth-beta) experimental VHDL frontend for Yosys.
|
||||
|
||||
### Why didn't you just improve [arachne-pnr](https://github.com/cseed/arachne-pnr)?
|
||||
|
||||
@ -162,11 +156,9 @@ that actually produced valid bitstreams.
|
||||
|
||||
For its original purpose, it has served the community extremely well. However,
|
||||
it was never designed to support multiple different FPGA families, nor more
|
||||
complicated timing driven placement and routing used by most commercial place and route
|
||||
tools.
|
||||
complicated timing driven placement and routing used by most commercial place and route tools.
|
||||
|
||||
It felt like extending arachne-pnr was not going to be the best path forward, so
|
||||
it was decided to build nextpnr as replacement.
|
||||
It felt like extending arachne-pnr was not going to be the best path forward, so it was decided to build nextpnr as replacement.
|
||||
|
||||
### arachne-pnr does X better!
|
||||
|
||||
@ -174,7 +166,8 @@ If you have a use case which prevents you from switching to nextpnr from
|
||||
arachne, we want to hear about it! Please create an issue and we will do our best to solve the problem!
|
||||
|
||||
We want nextpnr to be a suitable replacement for anyone who is currently a user
|
||||
of arachne-pnr.
|
||||
of arachne-pnr, and it is important to bear in mind that arachne-pnr is no
|
||||
longer in active development.
|
||||
|
||||
### Why are you not just contributing to [Verilog to Routing](https://verilogtorouting.org)?
|
||||
|
||||
@ -191,8 +184,7 @@ for current FPGAs.
|
||||
|
||||
We also believe that support for real architectures will enable interesting new
|
||||
research. nextpnr (like all place and route tools) depends heavily on
|
||||
research groups like the VtR developers to investigate and push forward FPGA placement and routing
|
||||
algorithms in new and exciting ways.
|
||||
research groups like the VtR developers to investigate and push forward FPGA placement and routing algorithms in new and exciting ways.
|
||||
|
||||
#### What is VPR?
|
||||
|
||||
@ -220,15 +212,14 @@ enable support for creation of bitstreams for these parts.
|
||||
the bitstream format for the Xilinx Series 7 series of FPGAs. It also includes
|
||||
tooling around bitstream generation for these parts.
|
||||
|
||||
While nextpnr currently does **not** support these Xilinx parts, we expect it
|
||||
will soon be using Project X-Ray in a similar manner to Project Trellis.
|
||||
While upstream nextpnr currently does **not** support these Xilinx parts, we expect it might soon be using Project X-Ray in a similar manner to Project Trellis.
|
||||
|
||||
### What is [Project IceStorm](http://www.clifford.at/icestorm/)?
|
||||
|
||||
[Project IceStorm](http://www.clifford.at/icestorm/) is both a project to
|
||||
document the bitstream for the Lattice iCE40 series of parts **and** a full
|
||||
flow including Yosys and arachne-pnr for converting Verilog into a bitstream for
|
||||
these parts.
|
||||
flow including Yosys and arachne-pnr for converting Verilog into a bitstream
|
||||
for these parts.
|
||||
|
||||
As the open source community now has support for multiple different FPGA parts,
|
||||
in the nextpnr documentation we generally use Project IceStorm to mean the database and
|
||||
|
@ -72,7 +72,7 @@ Sets the number of input pins a LUT in the architecture has. Only affects the ge
|
||||
|
||||
### void setDelayScaling(double scale, double offset);
|
||||
|
||||
Set the linear scaling vs distance and fixed offset (both values in nanoseconds) for routing delay estimates.
|
||||
Set the linear scaling vs distance and fixed offset (both values in nanoseconds) for routing delay estimates. Delay estimates that correlate to pip delays, even if they have no bearing to reality, are important for reasonable routing runtime.
|
||||
|
||||
### void addCellTimingClock(IdString cell, IdString port);
|
||||
|
||||
@ -96,17 +96,19 @@ Specify clock-to-out time for a port of a cell, and set the timing class of that
|
||||
|
||||
## Generic Packer
|
||||
|
||||
The generic packer combines K-input LUTs (`LUT` cells) and simple D-type flip flops (`DFF` cells) (posedge clock only, no set/reset or enable) into a `GENERIC_SLICE` cell. It also inserts `GENERIC_IOB`s onto any top level IO pins without an IO buffer.
|
||||
The generic packer combines K-input LUTs (`LUT` cells) and simple D-type flip flops (`DFF` cells) (posedge clock only, no set/reset or enable) into a `GENERIC_SLICE` cell. It also inserts `GENERIC_IOB`s onto any top level IO pins without an IO buffer. Constrained IOBs can be implemented by instantiating `GENERIC_IOB` and setting the `BEL` attribute to an IO location.
|
||||
|
||||
Thus, the architecture should provide bels with the following ports in order to use the generic packer:
|
||||
|
||||
- `GENERIC_SLICE` bels with `CLK` input, `I[0]` .. `I[K-1]` LUT inputs and `Q` LUT/FF output (N.B. both LUT and FF outputs are not available at the same time)
|
||||
- `GENERIC_SLICE` bels with `CLK` input, `I[0]` .. `I[K-1]` LUT inputs, `F` LUT output and `Q` FF output (N.B. both LUT and FF outputs are not available at the same time, to represent the constraints of some FPGAs).
|
||||
- `GENERIC_IOB` bels with `I` output buffer input, `EN` output enable input, and `O` input buffer output.
|
||||
|
||||
See [prims.v](../generic/synth/prims.v) for Verilog simulation models for all these cells.
|
||||
|
||||
[synth_generic.tcl](../generic/synth/synth_generic.tcl) can be used with Yosys to perform synthesis to the generic `LUT` and `DFF` cells which the generic packer supports. Invoke it using `tcl synth_generic.tcl K out.json` where _K_ is the number of LUT inputs and _out.json_ the name of the JSON file to write.
|
||||
|
||||
The generic packer in its current state is intended for experimentation and proof-of-concept tests. It is _not_ intended to make use of all FPGA features or support complex designs. In these cases a proper [Arch API](archapi.md) implementation is strongly recommended.
|
||||
|
||||
## Validity Checks
|
||||
|
||||
The following constraints are enforced by the generic architecture during placement.
|
||||
|
@ -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.
|
||||
|
12
ecp5/arch.cc
12
ecp5/arch.cc
@ -117,6 +117,9 @@ Arch::Arch(ArchArgs args) : args(args)
|
||||
log_error("Unsupported ECP5 chip type.\n");
|
||||
}
|
||||
#endif
|
||||
if (chip_info->const_id_count != DB_CONST_ID_COUNT)
|
||||
log_error("Chip database 'bba' and nextpnr code are out of sync; please rebuild (or contact distribution "
|
||||
"maintainer)!\n");
|
||||
package_info = nullptr;
|
||||
for (int i = 0; i < chip_info->num_packages; i++) {
|
||||
if (args.package == chip_info->package_info[i].name.get()) {
|
||||
@ -475,7 +478,13 @@ delay_t Arch::estimateDelay(WireId src, WireId dst) const
|
||||
}
|
||||
};
|
||||
|
||||
auto src_loc = est_location(src), dst_loc = est_location(dst);
|
||||
auto src_loc = est_location(src);
|
||||
std::pair<int, int> dst_loc;
|
||||
if (wire_loc_overrides.count(dst)) {
|
||||
dst_loc = wire_loc_overrides.at(dst);
|
||||
} else {
|
||||
dst_loc = est_location(dst);
|
||||
}
|
||||
|
||||
int dx = abs(src_loc.first - dst_loc.first), dy = abs(src_loc.second - dst_loc.second);
|
||||
|
||||
@ -560,6 +569,7 @@ bool Arch::place()
|
||||
|
||||
bool Arch::route()
|
||||
{
|
||||
setupWireLocations();
|
||||
route_ecp5_globals(getCtx());
|
||||
assignArchInfo();
|
||||
assign_budget(getCtx(), true);
|
||||
|
@ -196,6 +196,7 @@ NPNR_PACKED_STRUCT(struct ChipInfoPOD {
|
||||
int32_t num_tiles;
|
||||
int32_t num_location_types;
|
||||
int32_t num_packages, num_pios;
|
||||
int32_t const_id_count;
|
||||
RelPtr<LocationTypePOD> locations;
|
||||
RelPtr<int32_t> location_type;
|
||||
RelPtr<GlobalInfoPOD> location_glbinfo;
|
||||
@ -1034,7 +1035,7 @@ struct Arch : BaseCtx
|
||||
if (chip_info->tiletype_names[tileloc.tile_names[j].type_idx].get() == type)
|
||||
return tileloc.tile_names[j].name.get();
|
||||
}
|
||||
NPNR_ASSERT_FALSE_STR("no with type " + type);
|
||||
NPNR_ASSERT_FALSE_STR("no tile with type " + type);
|
||||
}
|
||||
|
||||
GlobalInfoPOD globalInfoAtLoc(Location loc);
|
||||
@ -1054,6 +1055,11 @@ struct Arch : BaseCtx
|
||||
// Special case for delay estimates due to its physical location
|
||||
// being far from the logical location of its primitive
|
||||
WireId gsrclk_wire;
|
||||
// Improves directivity of routing to DSP inputs, avoids issues
|
||||
// with different routes to the same physical reset wire causing
|
||||
// conflicts and slow routing
|
||||
std::unordered_map<WireId, std::pair<int, int>> wire_loc_overrides;
|
||||
void setupWireLocations();
|
||||
|
||||
mutable std::unordered_map<DelayKey, std::pair<bool, DelayInfo>> celldelay_cache;
|
||||
|
||||
|
@ -196,4 +196,28 @@ void Arch::permute_luts()
|
||||
}
|
||||
}
|
||||
|
||||
void Arch::setupWireLocations()
|
||||
{
|
||||
wire_loc_overrides.clear();
|
||||
for (auto cell : sorted(cells)) {
|
||||
CellInfo *ci = cell.second;
|
||||
if (ci->bel == BelId())
|
||||
continue;
|
||||
if (ci->type == id_MULT18X18D || ci->type == id_DCUA) {
|
||||
for (auto &port : ci->ports) {
|
||||
if (port.second.type != PORT_IN || port.second.net == nullptr)
|
||||
continue;
|
||||
WireId pw = getBelPinWire(ci->bel, port.first);
|
||||
if (pw == WireId())
|
||||
continue;
|
||||
for (auto uh : getPipsUphill(pw)) {
|
||||
WireId pip_src = getPipSrcWire(uh);
|
||||
wire_loc_overrides[pw] = std::make_pair(pip_src.location.x, pip_src.location.y);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NEXTPNR_NAMESPACE_END
|
||||
|
@ -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
|
||||
|
@ -52,17 +52,23 @@ struct DelayInfo
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
// https://bugreports.qt.io/browse/QTBUG-80789
|
||||
|
||||
#ifndef Q_MOC_RUN
|
||||
enum ConstIds
|
||||
{
|
||||
ID_NONE
|
||||
#define X(t) , ID_##t
|
||||
#include "constids.inc"
|
||||
#undef X
|
||||
,
|
||||
DB_CONST_ID_COUNT
|
||||
};
|
||||
|
||||
#define X(t) static constexpr auto id_##t = IdString(ID_##t);
|
||||
#include "constids.inc"
|
||||
#undef X
|
||||
#endif
|
||||
|
||||
NPNR_PACKED_STRUCT(struct LocationPOD { int16_t x, y; });
|
||||
|
||||
|
@ -759,6 +759,10 @@ void write_bitstream(Context *ctx, std::string base_config_file, std::string tex
|
||||
str_or_default(ci->params, ctx->id("REG0_REGSET"), "RESET"));
|
||||
cc.tiles[tname].add_enum(slice + ".REG1.REGSET",
|
||||
str_or_default(ci->params, ctx->id("REG1_REGSET"), "RESET"));
|
||||
cc.tiles[tname].add_enum(slice + ".REG0.LSRMODE",
|
||||
str_or_default(ci->params, ctx->id("REG0_LSRMODE"), "LSR"));
|
||||
cc.tiles[tname].add_enum(slice + ".REG1.LSRMODE",
|
||||
str_or_default(ci->params, ctx->id("REG1_LSRMODE"), "LSR"));
|
||||
cc.tiles[tname].add_enum(slice + ".CEMUX", str_or_default(ci->params, ctx->id("CEMUX"), "1"));
|
||||
|
||||
if (ci->sliceInfo.using_dff) {
|
||||
@ -865,6 +869,16 @@ void write_bitstream(Context *ctx, std::string base_config_file, std::string tex
|
||||
if (ci->attrs.count(ctx->id("DIFFRESISTOR")))
|
||||
cc.tiles[pio_tile].add_enum(pio + ".DIFFRESISTOR",
|
||||
str_or_default(ci->attrs, ctx->id("DIFFRESISTOR"), "OFF"));
|
||||
if (ci->attrs.count(ctx->id("DRIVE"))) {
|
||||
static bool drive_3v3_warning_done = false;
|
||||
if (iotype == "LVCMOS33") {
|
||||
cc.tiles[pio_tile].add_enum(pio + ".DRIVE", str_or_default(ci->attrs, ctx->id("DRIVE"), "8"));
|
||||
} else {
|
||||
if (!drive_3v3_warning_done)
|
||||
log_warning("Trellis limitation: DRIVE can only be set on 3V3 IO pins.\n");
|
||||
drive_3v3_warning_done = true;
|
||||
}
|
||||
}
|
||||
if (ci->attrs.count(ctx->id("TERMINATION"))) {
|
||||
auto vccio = get_vccio(ioType_from_str(iotype));
|
||||
switch (vccio) {
|
||||
@ -1283,6 +1297,9 @@ void write_bitstream(Context *ctx, std::string base_config_file, std::string tex
|
||||
else
|
||||
cc.tiles[pic_tile].add_enum(prim + "." + param.first.str(ctx), param.second.as_string());
|
||||
}
|
||||
if (get_net_or_empty(ci, id_LOADN) != nullptr) {
|
||||
cc.tiles[pic_tile].add_enum(prim + ".LOADNMUX", "LOADN");
|
||||
}
|
||||
} else if (ci->type == id_DCUA) {
|
||||
TileGroup tg;
|
||||
tg.tiles = get_dcu_tiles(ctx, ci->bel);
|
||||
@ -1384,8 +1401,9 @@ void write_bitstream(Context *ctx, std::string base_config_file, std::string tex
|
||||
Loc loc = ctx->getBelLocation(ci->bel);
|
||||
bool u = loc.y<15, r = loc.x> 15;
|
||||
std::string tiletype = fmt_str("DDRDLL_" << (u ? 'U' : 'L') << (r ? 'R' : 'L'));
|
||||
if (ctx->args.type == ArchArgs::LFE5U_25F || ctx->args.type == ArchArgs::LFE5UM_25F ||
|
||||
ctx->args.type == ArchArgs::LFE5UM5G_25F)
|
||||
if ((ctx->args.type == ArchArgs::LFE5U_25F || ctx->args.type == ArchArgs::LFE5UM_25F ||
|
||||
ctx->args.type == ArchArgs::LFE5UM5G_25F) &&
|
||||
u)
|
||||
tiletype += "A";
|
||||
std::string tile = ctx->getTileByType(tiletype);
|
||||
cc.tiles[tile].add_enum("DDRDLL.MODE", "DDRDLLA");
|
||||
|
@ -233,6 +233,8 @@ static void replace_port_safe(bool has_ff, CellInfo *ff, IdString ff_port, CellI
|
||||
|
||||
void ff_to_slice(Context *ctx, CellInfo *ff, CellInfo *lc, int index, bool driven_by_lut)
|
||||
{
|
||||
if (lc->hierpath == IdString())
|
||||
lc->hierpath = ff->hierpath;
|
||||
bool has_ff = lc->ports.at(ctx->id("Q0")).net != nullptr || lc->ports.at(ctx->id("Q1")).net != nullptr;
|
||||
std::string reg = "REG" + std::to_string(index);
|
||||
set_param_safe(has_ff, lc, ctx->id("SRMODE"), str_or_default(ff->params, ctx->id("SRMODE"), "LSR_OVER_CE"));
|
||||
@ -243,6 +245,7 @@ void ff_to_slice(Context *ctx, CellInfo *ff, CellInfo *lc, int index, bool drive
|
||||
|
||||
lc->params[ctx->id(reg + "_SD")] = std::string(driven_by_lut ? "1" : "0");
|
||||
lc->params[ctx->id(reg + "_REGSET")] = str_or_default(ff->params, ctx->id("REGSET"), "RESET");
|
||||
lc->params[ctx->id(reg + "_LSRMODE")] = str_or_default(ff->params, ctx->id("LSRMODE"), "LSR");
|
||||
replace_port_safe(has_ff, ff, ctx->id("CLK"), lc, ctx->id("CLK"));
|
||||
if (ff->ports.find(ctx->id("LSR")) != ff->ports.end())
|
||||
replace_port_safe(has_ff, ff, ctx->id("LSR"), lc, ctx->id("LSR"));
|
||||
@ -250,15 +253,28 @@ void ff_to_slice(Context *ctx, CellInfo *ff, CellInfo *lc, int index, bool drive
|
||||
replace_port_safe(has_ff, ff, ctx->id("CE"), lc, ctx->id("CE"));
|
||||
|
||||
replace_port(ff, ctx->id("Q"), lc, ctx->id("Q" + std::to_string(index)));
|
||||
if (driven_by_lut) {
|
||||
replace_port(ff, ctx->id("DI"), lc, ctx->id("DI" + std::to_string(index)));
|
||||
if (get_net_or_empty(ff, ctx->id("M")) != nullptr) {
|
||||
// PRLD FFs that use both M and DI
|
||||
NPNR_ASSERT(!driven_by_lut);
|
||||
// As M is used; must route DI through a new LUT
|
||||
lc->params[ctx->id(reg + "_SD")] = std::string("1");
|
||||
lc->params[ctx->id("LUT" + std::to_string(index) + "_INITVAL")] = Property(0xFF00, 16);
|
||||
replace_port(ff, ctx->id("DI"), lc, ctx->id("D" + std::to_string(index)));
|
||||
replace_port(ff, ctx->id("M"), lc, ctx->id("M" + std::to_string(index)));
|
||||
connect_ports(ctx, lc, ctx->id("F" + std::to_string(index)), lc, ctx->id("DI" + std::to_string(index)));
|
||||
} else {
|
||||
replace_port(ff, ctx->id("DI"), lc, ctx->id("M" + std::to_string(index)));
|
||||
if (driven_by_lut) {
|
||||
replace_port(ff, ctx->id("DI"), lc, ctx->id("DI" + std::to_string(index)));
|
||||
} else {
|
||||
replace_port(ff, ctx->id("DI"), lc, ctx->id("M" + std::to_string(index)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void lut_to_slice(Context *ctx, CellInfo *lut, CellInfo *lc, int index)
|
||||
{
|
||||
if (lc->hierpath == IdString())
|
||||
lc->hierpath = lut->hierpath;
|
||||
lc->params[ctx->id("LUT" + std::to_string(index) + "_INITVAL")] =
|
||||
get_or_default(lut->params, ctx->id("INIT"), Property(0, 16));
|
||||
replace_port(lut, ctx->id("A"), lc, ctx->id("A" + std::to_string(index)));
|
||||
@ -270,6 +286,8 @@ void lut_to_slice(Context *ctx, CellInfo *lut, CellInfo *lc, int index)
|
||||
|
||||
void ccu2c_to_slice(Context *ctx, CellInfo *ccu, CellInfo *lc)
|
||||
{
|
||||
if (lc->hierpath == IdString())
|
||||
lc->hierpath = ccu->hierpath;
|
||||
lc->params[ctx->id("MODE")] = std::string("CCU2");
|
||||
lc->params[ctx->id("LUT0_INITVAL")] = get_or_default(ccu->params, ctx->id("INIT0"), Property(0, 16));
|
||||
lc->params[ctx->id("LUT1_INITVAL")] = get_or_default(ccu->params, ctx->id("INIT1"), Property(0, 16));
|
||||
@ -297,6 +315,8 @@ void ccu2c_to_slice(Context *ctx, CellInfo *ccu, CellInfo *lc)
|
||||
|
||||
void dram_to_ramw(Context *ctx, CellInfo *ram, CellInfo *lc)
|
||||
{
|
||||
if (lc->hierpath == IdString())
|
||||
lc->hierpath = ram->hierpath;
|
||||
lc->params[ctx->id("MODE")] = std::string("RAMW");
|
||||
replace_port(ram, ctx->id("WAD[0]"), lc, ctx->id("D0"));
|
||||
replace_port(ram, ctx->id("WAD[1]"), lc, ctx->id("B0"));
|
||||
@ -328,6 +348,8 @@ static unsigned get_dram_init(const Context *ctx, const CellInfo *ram, int bit)
|
||||
|
||||
void dram_to_ram_slice(Context *ctx, CellInfo *ram, CellInfo *lc, CellInfo *ramw, int index)
|
||||
{
|
||||
if (lc->hierpath == IdString())
|
||||
lc->hierpath = ram->hierpath;
|
||||
lc->params[ctx->id("MODE")] = std::string("DPRAM");
|
||||
lc->params[ctx->id("WREMUX")] = str_or_default(ram->params, ctx->id("WREMUX"), "WRE");
|
||||
lc->params[ctx->id("WCKMUX")] = str_or_default(ram->params, ctx->id("WCKMUX"), "WCK");
|
||||
@ -416,7 +438,25 @@ void nxio_to_tr(Context *ctx, CellInfo *nxio, CellInfo *trio, std::vector<std::u
|
||||
} else {
|
||||
NPNR_ASSERT(false);
|
||||
}
|
||||
NetInfo *donet = trio->ports.at(ctx->id("I")).net;
|
||||
NetInfo *donet = trio->ports.at(ctx->id("I")).net, *dinet = trio->ports.at(ctx->id("O")).net;
|
||||
|
||||
// Rename I/O nets to avoid conflicts
|
||||
if (donet != nullptr && donet->name == nxio->name)
|
||||
rename_net(ctx, donet, ctx->id(donet->name.str(ctx) + "$TRELLIS_IO_OUT"));
|
||||
if (dinet != nullptr && dinet->name == nxio->name)
|
||||
rename_net(ctx, dinet, ctx->id(dinet->name.str(ctx) + "$TRELLIS_IO_IN"));
|
||||
|
||||
// Create a new top port net for accurate IO timing analysis and simulation netlists
|
||||
if (ctx->ports.count(nxio->name)) {
|
||||
IdString tn_netname = nxio->name;
|
||||
NPNR_ASSERT(!ctx->nets.count(tn_netname));
|
||||
std::unique_ptr<NetInfo> toplevel_net{new NetInfo};
|
||||
toplevel_net->name = tn_netname;
|
||||
connect_port(ctx, toplevel_net.get(), trio, ctx->id("B"));
|
||||
ctx->ports[nxio->name].net = toplevel_net.get();
|
||||
ctx->nets[tn_netname] = std::move(toplevel_net);
|
||||
}
|
||||
|
||||
CellInfo *tbuf = net_driven_by(
|
||||
ctx, donet, [](const Context *ctx, const CellInfo *cell) { return cell->type == ctx->id("$_TBUF_"); },
|
||||
ctx->id("Y"));
|
||||
|
@ -1329,3 +1329,11 @@ X(WIRE_TYPE_G_HPBX)
|
||||
X(WIRE_TYPE_G_VPTX)
|
||||
X(WIRE_TYPE_L_HPBX)
|
||||
X(WIRE_TYPE_R_HPBX)
|
||||
|
||||
X(IOLOGIC_MODE_IDDRX1F)
|
||||
X(IOLOGIC_MODE_IDDRX2F)
|
||||
X(IOLOGIC_MODE_IREG)
|
||||
X(IOLOGIC_MODE_ODDRX1F)
|
||||
X(IOLOGIC_MODE_ODDRX2F)
|
||||
X(IOLOGIC_MODE_OREG)
|
||||
X(IOLOGIC_MODE_TSREG)
|
||||
|
@ -49,7 +49,7 @@ if (NOT EXTERNAL_CHIPDB)
|
||||
else()
|
||||
add_custom_command(OUTPUT ${DEV_CC_BBA_DB}
|
||||
COMMAND ${ENV_CMD} ${PYTHON_EXECUTABLE} ${DB_PY} -p ${DEV_CONSTIDS_INC} -g ${DEV_GFXH} ${dev} > ${DEV_CC_BBA_DB}
|
||||
DEPENDS ${DEV_CONSTIDS_INC} ${DEV_GFXH} ${DB_PY} ${PREV_DEV_CC_BBA_DB}
|
||||
DEPENDS ${DB_PY} ${DEV_CONSTIDS_INC} ${DEV_GFXH} ${PREV_DEV_CC_BBA_DB}
|
||||
)
|
||||
add_custom_command(OUTPUT ${DEV_CC_DB}
|
||||
COMMAND bbasm ${BBASM_ENDIAN_FLAG} ${DEV_CC_BBA_DB} ${DEV_CC_DB}
|
||||
@ -82,7 +82,7 @@ if (NOT EXTERNAL_CHIPDB)
|
||||
add_custom_command(OUTPUT ${DEV_CC_BBA_DB}
|
||||
COMMAND ${ENV_CMD} ${PYTHON_EXECUTABLE} ${DB_PY} -p ${DEV_CONSTIDS_INC} -g ${DEV_GFXH} ${dev} > ${DEV_CC_BBA_DB}.new
|
||||
COMMAND mv ${DEV_CC_BBA_DB}.new ${DEV_CC_BBA_DB}
|
||||
DEPENDS ${DEV_CONSTIDS_INC} ${DEV_GFXH} ${DB_PY} ${PREV_DEV_CC_BBA_DB}
|
||||
DEPENDS ${DB_PY} ${DEV_CONSTIDS_INC} ${DEV_GFXH} ${PREV_DEV_CC_BBA_DB}
|
||||
)
|
||||
add_custom_command(OUTPUT ${DEV_CC_DB}
|
||||
COMMAND bbasm --c ${BBASM_ENDIAN_FLAG} ${DEV_CC_BBA_DB} ${DEV_CC_DB}.new
|
||||
|
@ -58,7 +58,7 @@ class Ecp5GlobalRouter
|
||||
if (user.cell->type == id_DCUA && (user.port == id_CH0_FF_RXI_CLK || user.port == id_CH1_FF_RXI_CLK ||
|
||||
user.port == id_CH0_FF_TXI_CLK || user.port == id_CH1_FF_TXI_CLK))
|
||||
return true;
|
||||
if ((user.cell->type == id_IOLOGIC || user.cell->type == id_SIOLOGIC) && user.port == id_CLK)
|
||||
if ((user.cell->type == id_IOLOGIC || user.cell->type == id_SIOLOGIC) && (user.port == id_CLK))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
@ -74,6 +74,8 @@ class Ecp5GlobalRouter
|
||||
clockCount[ni->name]++;
|
||||
if (user.cell->type == id_DCUA)
|
||||
clockCount[ni->name] += 100;
|
||||
if (user.cell->type == id_IOLOGIC || user.cell->type == id_SIOLOGIC)
|
||||
clockCount[ni->name] += 10;
|
||||
}
|
||||
}
|
||||
// log_info("clkcount %s: %d\n", ni->name.c_str(ctx),clockCount[ni->name]);
|
||||
|
304
ecp5/pack.cc
304
ecp5/pack.cc
@ -56,6 +56,52 @@ class Ecp5Packer
|
||||
new_cells.clear();
|
||||
}
|
||||
|
||||
// Print logic usgage
|
||||
int available_slices = 0;
|
||||
void print_logic_usage()
|
||||
{
|
||||
int total_luts = 0, total_ffs = 0;
|
||||
int total_ramluts = 0, total_ramwluts = 0;
|
||||
for (auto bel : ctx->getBels()) {
|
||||
if (ctx->getBelType(bel) == id_TRELLIS_SLICE) {
|
||||
available_slices += 1;
|
||||
total_luts += 2;
|
||||
total_ffs += 2;
|
||||
Loc l = ctx->getBelLocation(bel);
|
||||
if (l.z == 0 || l.z == 1)
|
||||
total_ramluts += 2;
|
||||
if (l.z == 2)
|
||||
total_ramwluts += 2;
|
||||
}
|
||||
}
|
||||
int used_lgluts = 0, used_cyluts = 0, used_ramluts = 0, used_ramwluts = 0, used_ffs = 0;
|
||||
for (auto &cell : ctx->cells) {
|
||||
CellInfo *ci = cell.second.get();
|
||||
if (is_lut(ctx, ci))
|
||||
++used_lgluts;
|
||||
if (is_carry(ctx, ci))
|
||||
used_cyluts += 2;
|
||||
if (is_dpram(ctx, ci)) {
|
||||
used_ramluts += 4;
|
||||
used_ramwluts += 2;
|
||||
}
|
||||
if (is_ff(ctx, ci))
|
||||
used_ffs += 2;
|
||||
}
|
||||
log_info("Logic utilisation before packing:\n");
|
||||
auto pc = [](int used, int total) { return 100 * used / total; };
|
||||
int used_luts = used_lgluts + used_cyluts + used_ramluts + used_ramwluts;
|
||||
log_info(" Total LUT4s: %5d/%5d %5d%%\n", used_luts, total_luts, pc(used_luts, total_luts));
|
||||
log_info(" logic LUTs: %5d/%5d %5d%%\n", used_lgluts, total_luts, pc(used_lgluts, total_luts));
|
||||
log_info(" carry LUTs: %5d/%5d %5d%%\n", used_cyluts, total_luts, pc(used_cyluts, total_luts));
|
||||
log_info(" RAM LUTs: %5d/%5d %5d%%\n", used_ramluts, total_ramluts, pc(used_ramluts, total_ramluts));
|
||||
log_info(" RAMW LUTs: %5d/%5d %5d%%\n", used_ramwluts, total_ramwluts,
|
||||
pc(used_ramwluts, total_ramwluts));
|
||||
log_break();
|
||||
log_info(" Total DFFs: %5d/%5d %5d%%\n", used_ffs, total_ffs, pc(used_ffs, total_ffs));
|
||||
log_break();
|
||||
}
|
||||
|
||||
// Find FFs associated with LUTs, or LUT expansion muxes
|
||||
void find_lutff_pairs()
|
||||
{
|
||||
@ -66,7 +112,8 @@ class Ecp5Packer
|
||||
NetInfo *znet = ci->ports.at(ctx->id("Z")).net;
|
||||
if (znet != nullptr) {
|
||||
CellInfo *ff = net_only_drives(ctx, znet, is_ff, ctx->id("DI"), false);
|
||||
if (ff != nullptr) {
|
||||
// Can't combine preload FF with LUT due to conflict on M
|
||||
if (ff != nullptr && get_net_or_empty(ff, ctx->id("M")) == nullptr) {
|
||||
lutffPairs[ci->name] = ff->name;
|
||||
fflutPairs[ff->name] = ci->name;
|
||||
}
|
||||
@ -75,6 +122,58 @@ class Ecp5Packer
|
||||
}
|
||||
}
|
||||
|
||||
// Check if a flipflop is available in a slice
|
||||
bool is_ff_available(CellInfo *slice, int ff)
|
||||
{
|
||||
if (get_net_or_empty(slice, (ff == 1) ? id_Q1 : id_Q0) != nullptr)
|
||||
return false;
|
||||
if (get_net_or_empty(slice, (ff == 1) ? id_M1 : id_M0) != nullptr)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if a flipflop can be added to a slice
|
||||
bool can_add_ff_to_slice(CellInfo *slice, CellInfo *ff)
|
||||
{
|
||||
std::string clkmux = str_or_default(ff->params, ctx->id("CLKMUX"), "CLK");
|
||||
std::string lsrmux = str_or_default(ff->params, ctx->id("LSRMUX"), "LSR");
|
||||
|
||||
bool has_dpram = str_or_default(slice->params, ctx->id("MODE"), "LOGIC") == "DPRAM";
|
||||
if (has_dpram) {
|
||||
std::string wckmux = str_or_default(slice->params, ctx->id("WCKMUX"), "WCK");
|
||||
std::string wremux = str_or_default(slice->params, ctx->id("WREMUX"), "WRE");
|
||||
if (wckmux != clkmux && !(wckmux == "WCK" && clkmux == "CLK"))
|
||||
return false;
|
||||
if (wremux != lsrmux && !(wremux == "WRE" && lsrmux == "LSR"))
|
||||
return false;
|
||||
}
|
||||
bool has_ff0 = get_net_or_empty(slice, id_Q0) != nullptr;
|
||||
bool has_ff1 = get_net_or_empty(slice, id_Q1) != nullptr;
|
||||
if (!has_ff0 && !has_ff1)
|
||||
return true;
|
||||
if (str_or_default(ff->params, ctx->id("GSR"), "DISABLED") !=
|
||||
str_or_default(slice->params, ctx->id("GSR"), "DISABLED"))
|
||||
return false;
|
||||
if (str_or_default(ff->params, ctx->id("SRMODE"), "LSR_OVER_CE") !=
|
||||
str_or_default(slice->params, ctx->id("SRMODE"), "LSR_OVER_CE"))
|
||||
return false;
|
||||
if (str_or_default(ff->params, ctx->id("CEMUX"), "1") != str_or_default(slice->params, ctx->id("CEMUX"), "1"))
|
||||
return false;
|
||||
if (str_or_default(ff->params, ctx->id("LSRMUX"), "LSR") !=
|
||||
str_or_default(slice->params, ctx->id("LSRMUX"), "LSR"))
|
||||
return false;
|
||||
if (str_or_default(ff->params, ctx->id("CLKMUX"), "CLK") !=
|
||||
str_or_default(slice->params, ctx->id("CLKMUX"), "CLK"))
|
||||
return false;
|
||||
if (net_or_nullptr(ff, ctx->id("CLK")) != net_or_nullptr(slice, ctx->id("CLK")))
|
||||
return false;
|
||||
if (net_or_nullptr(ff, ctx->id("CE")) != net_or_nullptr(slice, ctx->id("CE")))
|
||||
return false;
|
||||
if (net_or_nullptr(ff, ctx->id("LSR")) != net_or_nullptr(slice, ctx->id("LSR")))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
const NetInfo *net_or_nullptr(CellInfo *cell, IdString port)
|
||||
{
|
||||
auto fnd = cell->ports.find(port);
|
||||
@ -134,6 +233,8 @@ class Ecp5Packer
|
||||
// Return true if a FF can be added to a DPRAM slice
|
||||
bool can_pack_ff_dram(CellInfo *dpram, CellInfo *ff)
|
||||
{
|
||||
if (get_net_or_empty(ff, ctx->id("M")) != nullptr)
|
||||
return false; // skip PRLD FFs due to M/DI conflict
|
||||
std::string wckmux = str_or_default(dpram->params, ctx->id("WCKMUX"), "WCK");
|
||||
std::string clkmux = str_or_default(ff->params, ctx->id("CLKMUX"), "CLK");
|
||||
if (wckmux != clkmux && !(wckmux == "WCK" && clkmux == "CLK"))
|
||||
@ -362,8 +463,7 @@ class Ecp5Packer
|
||||
for (auto &port : ci->ports)
|
||||
disconnect_port(ctx, ci, port.first);
|
||||
} else if (trio != nullptr) {
|
||||
// Trivial case, TRELLIS_IO used. Just destroy the net and the
|
||||
// iobuf
|
||||
// Trivial case, TRELLIS_IO used. Just remove the IOBUF
|
||||
log_info("%s feeds TRELLIS_IO %s, removing %s %s.\n", ci->name.c_str(ctx), trio->name.c_str(ctx),
|
||||
ci->type.c_str(ctx), ci->name.c_str(ctx));
|
||||
|
||||
@ -384,14 +484,6 @@ class Ecp5Packer
|
||||
std::swap(net->clkconstr, onet->clkconstr);
|
||||
}
|
||||
}
|
||||
ctx->nets.erase(net->name);
|
||||
trio->ports.at(ctx->id("B")).net = nullptr;
|
||||
}
|
||||
if (ci->type == ctx->id("$nextpnr_iobuf")) {
|
||||
NetInfo *net2 = ci->ports.at(ctx->id("I")).net;
|
||||
if (net2 != nullptr) {
|
||||
ctx->nets.erase(net2->name);
|
||||
}
|
||||
}
|
||||
} else if (drives_top_port(ionet, tp)) {
|
||||
log_info("%s feeds %s %s.%s, removing %s %s.\n", ci->name.c_str(ctx), tp.cell->type.c_str(ctx),
|
||||
@ -414,7 +506,8 @@ class Ecp5Packer
|
||||
new_cells.push_back(std::move(tr_cell));
|
||||
trio = new_cells.back().get();
|
||||
}
|
||||
|
||||
for (auto port : ci->ports)
|
||||
disconnect_port(ctx, ci, port.first);
|
||||
packed_cells.insert(ci->name);
|
||||
if (trio != nullptr) {
|
||||
for (const auto &attr : ci->attrs)
|
||||
@ -1033,17 +1126,117 @@ class Ecp5Packer
|
||||
flush_cells();
|
||||
}
|
||||
|
||||
// Find a cell that meets some criterea near an origin cell
|
||||
// Used for packing an FF into a nearby SLICE
|
||||
template <typename TFunc> CellInfo *find_nearby_cell(CellInfo *origin, TFunc Func)
|
||||
{
|
||||
std::unordered_set<CellInfo *> visited_cells;
|
||||
std::queue<CellInfo *> to_visit;
|
||||
visited_cells.insert(origin);
|
||||
to_visit.push(origin);
|
||||
int iter = 0;
|
||||
while (!to_visit.empty() && iter < 10000) {
|
||||
CellInfo *cursor = to_visit.front();
|
||||
to_visit.pop();
|
||||
if (Func(cursor))
|
||||
return cursor;
|
||||
for (const auto &port : cursor->ports) {
|
||||
NetInfo *pn = port.second.net;
|
||||
if (pn == nullptr)
|
||||
continue;
|
||||
// Skip high-fanout nets that are unlikely to be relevant
|
||||
if (pn->users.size() > 25)
|
||||
continue;
|
||||
// Add other ports on this net if not already visited
|
||||
auto visit_port = [&](const PortRef &port) {
|
||||
if (port.cell == nullptr)
|
||||
return;
|
||||
if (visited_cells.count(port.cell))
|
||||
return;
|
||||
// If not already visited; add the cell of this port to the queue
|
||||
to_visit.push(port.cell);
|
||||
visited_cells.insert(port.cell);
|
||||
};
|
||||
visit_port(pn->driver);
|
||||
for (const auto &usr : pn->users)
|
||||
visit_port(usr);
|
||||
}
|
||||
++iter;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Pack flipflops that weren't paired with a LUT
|
||||
float dense_pack_mode_thresh = 0.95f;
|
||||
void pack_remaining_ffs()
|
||||
{
|
||||
// Enter dense flipflop packing mode once utilisation exceeds a threshold (default: 95%)
|
||||
int used_slices = 0;
|
||||
for (auto &cell : ctx->cells)
|
||||
if (cell.second->type == id_TRELLIS_SLICE)
|
||||
++used_slices;
|
||||
|
||||
log_info("Packing unpaired FFs into a SLICE...\n");
|
||||
for (auto cell : sorted(ctx->cells)) {
|
||||
CellInfo *ci = cell.second;
|
||||
if (is_ff(ctx, ci)) {
|
||||
bool pack_dense = used_slices > (dense_pack_mode_thresh * available_slices);
|
||||
bool requires_m = get_net_or_empty(ci, ctx->id("M")) != nullptr;
|
||||
if (pack_dense && !requires_m) {
|
||||
// If dense packing threshold exceeded; always try and pack the FF into an existing slice
|
||||
// Find a SLICE with space "near" the flipflop in the netlist
|
||||
std::vector<CellInfo *> ltile;
|
||||
CellInfo *target = find_nearby_cell(ci, [&](CellInfo *cursor) {
|
||||
if (cursor->type != id_TRELLIS_SLICE)
|
||||
return false;
|
||||
if (!cursor->constr_children.empty() || cursor->constr_parent != nullptr) {
|
||||
auto &constr_children = (cursor->constr_parent != nullptr)
|
||||
? cursor->constr_parent->constr_children
|
||||
: cursor->constr_children;
|
||||
// Skip big chains for performance
|
||||
if (constr_children.size() > 8)
|
||||
return false;
|
||||
// Have to check the whole of the tile for legality when dealing with chains, not just slice
|
||||
ltile.clear();
|
||||
if (cursor->constr_parent != nullptr)
|
||||
ltile.push_back(cursor->constr_parent);
|
||||
else
|
||||
ltile.push_back(cursor);
|
||||
for (auto c : constr_children)
|
||||
ltile.push_back(c);
|
||||
if (!can_add_ff_to_tile(ltile, cursor))
|
||||
return false;
|
||||
}
|
||||
if (!can_add_ff_to_slice(cursor, ci))
|
||||
return false;
|
||||
for (int i = 0; i < 2; i++)
|
||||
if (is_ff_available(cursor, i))
|
||||
return true;
|
||||
return false;
|
||||
});
|
||||
|
||||
// If found, add the FF to this slice instead of creating a new one
|
||||
if (target != nullptr) {
|
||||
for (int i = 0; i < 2; i++) {
|
||||
if (is_ff_available(target, i)) {
|
||||
ff_to_slice(ctx, ci, target, i, false);
|
||||
goto ff_packed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (false) {
|
||||
ff_packed:
|
||||
packed_cells.insert(ci->name);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<CellInfo> slice =
|
||||
create_ecp5_cell(ctx, ctx->id("TRELLIS_SLICE"), ci->name.str(ctx) + "_SLICE");
|
||||
ff_to_slice(ctx, ci, slice.get(), 0, false);
|
||||
new_cells.push_back(std::move(slice));
|
||||
++used_slices;
|
||||
packed_cells.insert(ci->name);
|
||||
}
|
||||
}
|
||||
@ -1986,7 +2179,8 @@ class Ecp5Packer
|
||||
iol->params[ctx->id("DELAY.DEL_VALUE")] =
|
||||
lookup_delay(str_or_default(ci->params, ctx->id("DEL_MODE"), "USER_DEFINED"));
|
||||
if (ci->params.count(ctx->id("DEL_VALUE")) &&
|
||||
std::string(ci->params.at(ctx->id("DEL_VALUE")).as_string()).substr(0, 5) != "DELAY")
|
||||
(!ci->params.at(ctx->id("DEL_VALUE")).is_string ||
|
||||
std::string(ci->params.at(ctx->id("DEL_VALUE")).as_string()).substr(0, 5) != "DELAY"))
|
||||
iol->params[ctx->id("DELAY.DEL_VALUE")] = ci->params.at(ctx->id("DEL_VALUE"));
|
||||
if (ci->ports.count(id_LOADN))
|
||||
replace_port(ci, id_LOADN, iol, id_LOADN);
|
||||
@ -2353,6 +2547,7 @@ class Ecp5Packer
|
||||
for (auto cell : sorted(ctx->cells)) {
|
||||
CellInfo *ci = cell.second;
|
||||
if (ci->type == id_ECLKBRIDGECS) {
|
||||
Loc loc;
|
||||
NetInfo *i0 = get_net_or_empty(ci, id_CLK0), *i1 = get_net_or_empty(ci, id_CLK1),
|
||||
*o = get_net_or_empty(ci, id_ECSOUT);
|
||||
for (NetInfo *input : {i0, i1}) {
|
||||
@ -2366,24 +2561,53 @@ class Ecp5Packer
|
||||
for (auto bel : ctx->getBels()) {
|
||||
if (ctx->getBelType(bel) != id_ECLKBRIDGECS)
|
||||
continue;
|
||||
Loc loc = ctx->getBelLocation(bel);
|
||||
loc = ctx->getBelLocation(bel);
|
||||
if (loc.x == user_loc.x) {
|
||||
ci->attrs[ctx->id("BEL")] = ctx->getBelName(bel).str(ctx);
|
||||
if (o != nullptr)
|
||||
for (auto user2 : o->users) {
|
||||
// Set side hint to ensure edge clock choice is routeable
|
||||
if (user2.cell->type == id_ECLKSYNCB && user2.port == id_ECLKI) {
|
||||
NetInfo *synco = get_net_or_empty(user2.cell, id_ECLKO);
|
||||
if (synco != nullptr)
|
||||
bridge_side_hint[synco] = (loc.x > 1) ? 0 : 1;
|
||||
}
|
||||
}
|
||||
goto eclkbridge_done;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (input->driver.cell != nullptr) {
|
||||
CellInfo *drv = input->driver.cell;
|
||||
if (!drv->attrs.count(ctx->id("BEL")))
|
||||
continue;
|
||||
Loc drv_loc = ctx->getBelLocation(
|
||||
ctx->getBelByName(ctx->id(drv->attrs.at(ctx->id("BEL")).as_string())));
|
||||
BelId closest;
|
||||
int closest_x = -1; // aim for same side of chip
|
||||
for (auto bel : ctx->getBels()) {
|
||||
if (ctx->getBelType(bel) != id_ECLKBRIDGECS)
|
||||
continue;
|
||||
loc = ctx->getBelLocation(bel);
|
||||
if (closest_x == -1 || std::abs(loc.x - drv_loc.x) < std::abs(closest_x - drv_loc.x)) {
|
||||
closest_x = loc.x;
|
||||
closest = bel;
|
||||
}
|
||||
}
|
||||
NPNR_ASSERT(closest != BelId());
|
||||
loc = ctx->getBelLocation(closest);
|
||||
ci->attrs[ctx->id("BEL")] = ctx->getBelName(closest).str(ctx);
|
||||
goto eclkbridge_done;
|
||||
}
|
||||
}
|
||||
// If all else fails, place randomly
|
||||
for (auto bel : ctx->getBels()) {
|
||||
if (ctx->getBelType(bel) != id_ECLKBRIDGECS)
|
||||
continue;
|
||||
loc = ctx->getBelLocation(bel);
|
||||
ci->attrs[ctx->id("BEL")] = ctx->getBelName(bel).str(ctx);
|
||||
}
|
||||
eclkbridge_done:
|
||||
if (o != nullptr)
|
||||
for (auto user2 : o->users) {
|
||||
// Set side hint to ensure edge clock choice is routeable
|
||||
if (user2.cell->type == id_ECLKSYNCB && user2.port == id_ECLKI) {
|
||||
NetInfo *synco = get_net_or_empty(user2.cell, id_ECLKO);
|
||||
if (synco != nullptr)
|
||||
bridge_side_hint[synco] = (loc.x > 1) ? 0 : 1;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@ -2468,12 +2692,37 @@ class Ecp5Packer
|
||||
const NetInfo *clk = net_or_nullptr(ci, id_CLK);
|
||||
if (clk == nullptr)
|
||||
log_error("DDRDLLA '%s' has disconnected port CLK\n", ci->name.c_str(ctx));
|
||||
|
||||
bool left_bank_users = false, right_bank_users = false;
|
||||
// Check which side the delay codes (DDRDEL) are used on
|
||||
const NetInfo *ddrdel = net_or_nullptr(ci, id_DDRDEL);
|
||||
if (ddrdel != nullptr) {
|
||||
for (auto &usr : ddrdel->users) {
|
||||
const CellInfo *uc = usr.cell;
|
||||
if (uc->type != id_DQSBUFM || !uc->attrs.count(ctx->id("BEL")))
|
||||
continue;
|
||||
BelId dqsb_bel = ctx->getBelByName(ctx->id(uc->attrs.at(ctx->id("BEL")).as_string()));
|
||||
Loc dqsb_loc = ctx->getBelLocation(dqsb_bel);
|
||||
if (dqsb_loc.x > 15)
|
||||
right_bank_users = true;
|
||||
if (dqsb_loc.x < 15)
|
||||
left_bank_users = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (left_bank_users && right_bank_users)
|
||||
log_error("DDRDLLA '%s' has DDRDEL uses on both sides of the chip.\n", ctx->nameOf(ci));
|
||||
|
||||
for (auto &eclk : eclks) {
|
||||
if (eclk.second.unbuf == clk) {
|
||||
for (auto bel : ctx->getBels()) {
|
||||
if (ctx->getBelType(bel) != id_DDRDLL)
|
||||
continue;
|
||||
Loc loc = ctx->getBelLocation(bel);
|
||||
if (loc.x > 15 && left_bank_users)
|
||||
continue;
|
||||
if (loc.x < 15 && right_bank_users)
|
||||
continue;
|
||||
int ddrdll_bank = -1;
|
||||
if (loc.x < 15 && loc.y < 15)
|
||||
ddrdll_bank = 7;
|
||||
@ -2485,6 +2734,7 @@ class Ecp5Packer
|
||||
ddrdll_bank = 3;
|
||||
if (eclk.first.first != ddrdll_bank)
|
||||
continue;
|
||||
log_info("Constraining DDRDLLA '%s' to bel '%s'\n", ctx->nameOf(ci), ctx->nameOfBel(bel));
|
||||
ci->attrs[ctx->id("BEL")] = ctx->getBelName(bel).str(ctx);
|
||||
make_eclk(ci->ports.at(id_CLK), ci, bel, eclk.first.first);
|
||||
goto ddrdll_done;
|
||||
@ -2583,7 +2833,7 @@ class Ecp5Packer
|
||||
std::unordered_set<IdString> changed_cells;
|
||||
for (auto net : changed_nets)
|
||||
for (auto &user : ctx->nets.at(net)->users)
|
||||
if (user.port == id_CLKI || user.port == id_ECLKI)
|
||||
if (user.port == id_CLKI || user.port == id_ECLKI || user.port == id_CLK0 || user.port == id_CLK1)
|
||||
changed_cells.insert(user.cell->name);
|
||||
changed_nets.clear();
|
||||
for (auto cell : sorted(changed_cells)) {
|
||||
@ -2600,6 +2850,9 @@ class Ecp5Packer
|
||||
copy_constraint(ci, id_CLKI, id_CDIVX, ratio);
|
||||
} else if (ci->type == id_ECLKSYNCB || ci->type == id_TRELLIS_ECLKBUF) {
|
||||
copy_constraint(ci, id_ECLKI, id_ECLKO, 1);
|
||||
} else if (ci->type == id_ECLKBRIDGECS) {
|
||||
copy_constraint(ci, id_CLK0, id_ECSOUT, 1);
|
||||
copy_constraint(ci, id_CLK1, id_ECSOUT, 1);
|
||||
} else if (ci->type == id_DCCA) {
|
||||
copy_constraint(ci, id_CLKI, id_CLKO, 1);
|
||||
} else if (ci->type == id_EHXPLLL) {
|
||||
@ -2657,14 +2910,15 @@ class Ecp5Packer
|
||||
void pack()
|
||||
{
|
||||
prepack_checks();
|
||||
print_logic_usage();
|
||||
pack_io();
|
||||
pack_dqsbuf();
|
||||
preplace_plls();
|
||||
pack_iologic();
|
||||
pack_ebr();
|
||||
pack_dsps();
|
||||
pack_dcus();
|
||||
pack_misc();
|
||||
preplace_plls();
|
||||
pack_constants();
|
||||
pack_dram();
|
||||
pack_carries();
|
||||
|
3
ecp5/synth/.gitignore
vendored
3
ecp5/synth/.gitignore
vendored
@ -1,3 +0,0 @@
|
||||
*.bit
|
||||
*_out.config
|
||||
|
@ -1,77 +0,0 @@
|
||||
module top(input clk_pin, input btn_pin, output [7:0] led_pin, output gpio0_pin);
|
||||
|
||||
wire clk;
|
||||
wire [7:0] led;
|
||||
wire btn;
|
||||
wire gpio0;
|
||||
|
||||
(* LOC="G2" *) (* IO_TYPE="LVCMOS33" *)
|
||||
TRELLIS_IO #(.DIR("INPUT")) clk_buf (.B(clk_pin), .O(clk));
|
||||
|
||||
(* LOC="R1" *) (* IO_TYPE="LVCMOS33" *)
|
||||
TRELLIS_IO #(.DIR("INPUT")) btn_buf (.B(btn_pin), .O(btn));
|
||||
|
||||
(* LOC="B2" *) (* IO_TYPE="LVCMOS33" *)
|
||||
TRELLIS_IO #(.DIR("OUTPUT")) led_buf_0 (.B(led_pin[0]), .I(led[0]));
|
||||
(* LOC="C2" *) (* IO_TYPE="LVCMOS33" *)
|
||||
TRELLIS_IO #(.DIR("OUTPUT")) led_buf_1 (.B(led_pin[1]), .I(led[1]));
|
||||
(* LOC="C1" *) (* IO_TYPE="LVCMOS33" *)
|
||||
TRELLIS_IO #(.DIR("OUTPUT")) led_buf_2 (.B(led_pin[2]), .I(led[2]));
|
||||
(* LOC="D2" *) (* IO_TYPE="LVCMOS33" *)
|
||||
TRELLIS_IO #(.DIR("OUTPUT")) led_buf_3 (.B(led_pin[3]), .I(led[3]));
|
||||
|
||||
(* LOC="D1" *) (* IO_TYPE="LVCMOS33" *)
|
||||
TRELLIS_IO #(.DIR("OUTPUT")) led_buf_4 (.B(led_pin[4]), .I(led[4]));
|
||||
(* LOC="E2" *) (* IO_TYPE="LVCMOS33" *)
|
||||
TRELLIS_IO #(.DIR("OUTPUT")) led_buf_5 (.B(led_pin[5]), .I(led[5]));
|
||||
(* LOC="E1" *) (* IO_TYPE="LVCMOS33" *)
|
||||
TRELLIS_IO #(.DIR("OUTPUT")) led_buf_6 (.B(led_pin[6]), .I(led[6]));
|
||||
(* LOC="H3" *) (* IO_TYPE="LVCMOS33" *)
|
||||
TRELLIS_IO #(.DIR("OUTPUT")) led_buf_7 (.B(led_pin[7]), .I(led[7]));
|
||||
|
||||
|
||||
(* LOC="L2" *) (* IO_TYPE="LVCMOS33" *)
|
||||
TRELLIS_IO #(.DIR("OUTPUT")) gpio0_buf (.B(gpio0_pin), .I(gpio0));
|
||||
|
||||
localparam ctr_width = 24;
|
||||
localparam ctr_max = 2**ctr_width - 1;
|
||||
reg [ctr_width-1:0] ctr = 0;
|
||||
reg [9:0] pwm_ctr = 0;
|
||||
reg dir = 0;
|
||||
|
||||
always@(posedge clk) begin
|
||||
ctr <= btn ? ctr : (dir ? ctr - 1'b1 : ctr + 1'b1);
|
||||
if (ctr[ctr_width-1 : ctr_width-3] == 0 && dir == 1)
|
||||
dir <= 1'b0;
|
||||
else if (ctr[ctr_width-1 : ctr_width-3] == 7 && dir == 0)
|
||||
dir <= 1'b1;
|
||||
pwm_ctr <= pwm_ctr + 1'b1;
|
||||
end
|
||||
|
||||
reg [9:0] brightness [0:7];
|
||||
localparam bright_max = 2**10 - 1;
|
||||
reg [7:0] led_reg;
|
||||
|
||||
genvar i;
|
||||
generate
|
||||
for (i = 0; i < 8; i=i+1) begin
|
||||
always @ (posedge clk) begin
|
||||
if (ctr[ctr_width-1 : ctr_width-3] == i)
|
||||
brightness[i] <= bright_max;
|
||||
else if (ctr[ctr_width-1 : ctr_width-3] == (i - 1))
|
||||
brightness[i] <= ctr[ctr_width-4:ctr_width-13];
|
||||
else if (ctr[ctr_width-1 : ctr_width-3] == (i + 1))
|
||||
brightness[i] <= bright_max - ctr[ctr_width-4:ctr_width-13];
|
||||
else
|
||||
brightness[i] <= 0;
|
||||
led_reg[i] <= pwm_ctr < brightness[i];
|
||||
end
|
||||
end
|
||||
endgenerate
|
||||
|
||||
assign led = led_reg;
|
||||
|
||||
// Tie GPIO0, keep board from rebooting
|
||||
assign gpio0 = 1'b1;
|
||||
|
||||
endmodule
|
@ -1,2 +0,0 @@
|
||||
read_verilog blinky.v
|
||||
synth_ecp5 -noccu2 -nomux -nodram -json blinky.json
|
@ -1,439 +0,0 @@
|
||||
.device LFE5U-45F
|
||||
|
||||
.tile CIB_R10C3:PVT_COUNT2
|
||||
unknown: F2B0
|
||||
unknown: F3B0
|
||||
unknown: F5B0
|
||||
unknown: F11B0
|
||||
unknown: F13B0
|
||||
|
||||
.tile CIB_R5C1:CIB_PLL1
|
||||
enum: CIB.JA3MUX 0
|
||||
enum: CIB.JB3MUX 0
|
||||
|
||||
|
||||
.tile CIB_R5C89:CIB_PLL1
|
||||
enum: CIB.JA3MUX 0
|
||||
enum: CIB.JB3MUX 0
|
||||
|
||||
|
||||
.tile CIB_R70C3:CIB_PLL3
|
||||
enum: CIB.JA3MUX 0
|
||||
enum: CIB.JB3MUX 0
|
||||
|
||||
|
||||
.tile CIB_R70C42:VCIB_DCU0
|
||||
enum: CIB.JA1MUX 0
|
||||
enum: CIB.JA3MUX 0
|
||||
enum: CIB.JA5MUX 0
|
||||
enum: CIB.JA7MUX 0
|
||||
enum: CIB.JB1MUX 0
|
||||
enum: CIB.JB3MUX 0
|
||||
enum: CIB.JB5MUX 0
|
||||
enum: CIB.JB7MUX 0
|
||||
enum: CIB.JC0MUX 0
|
||||
enum: CIB.JC2MUX 0
|
||||
enum: CIB.JC4MUX 0
|
||||
enum: CIB.JC6MUX 0
|
||||
enum: CIB.JD0MUX 0
|
||||
enum: CIB.JD2MUX 0
|
||||
enum: CIB.JD4MUX 0
|
||||
enum: CIB.JD6MUX 0
|
||||
|
||||
|
||||
.tile CIB_R70C43:VCIB_DCUA
|
||||
enum: CIB.JA1MUX 0
|
||||
enum: CIB.JA3MUX 0
|
||||
enum: CIB.JA5MUX 0
|
||||
enum: CIB.JA7MUX 0
|
||||
enum: CIB.JB1MUX 0
|
||||
enum: CIB.JB3MUX 0
|
||||
enum: CIB.JB5MUX 0
|
||||
enum: CIB.JB7MUX 0
|
||||
enum: CIB.JC0MUX 0
|
||||
enum: CIB.JC2MUX 0
|
||||
enum: CIB.JC4MUX 0
|
||||
enum: CIB.JC6MUX 0
|
||||
enum: CIB.JD0MUX 0
|
||||
enum: CIB.JD2MUX 0
|
||||
enum: CIB.JD4MUX 0
|
||||
enum: CIB.JD6MUX 0
|
||||
|
||||
|
||||
.tile CIB_R70C44:VCIB_DCUB
|
||||
enum: CIB.JA1MUX 0
|
||||
enum: CIB.JA3MUX 0
|
||||
enum: CIB.JA5MUX 0
|
||||
enum: CIB.JA7MUX 0
|
||||
enum: CIB.JB1MUX 0
|
||||
enum: CIB.JB3MUX 0
|
||||
enum: CIB.JB5MUX 0
|
||||
enum: CIB.JB7MUX 0
|
||||
enum: CIB.JC0MUX 0
|
||||
enum: CIB.JC2MUX 0
|
||||
enum: CIB.JC4MUX 0
|
||||
enum: CIB.JC6MUX 0
|
||||
enum: CIB.JD0MUX 0
|
||||
enum: CIB.JD2MUX 0
|
||||
enum: CIB.JD4MUX 0
|
||||
enum: CIB.JD6MUX 0
|
||||
|
||||
|
||||
.tile CIB_R70C45:VCIB_DCUC
|
||||
enum: CIB.JA1MUX 0
|
||||
enum: CIB.JA3MUX 0
|
||||
enum: CIB.JA5MUX 0
|
||||
enum: CIB.JA7MUX 0
|
||||
enum: CIB.JB1MUX 0
|
||||
enum: CIB.JB3MUX 0
|
||||
enum: CIB.JB5MUX 0
|
||||
enum: CIB.JB7MUX 0
|
||||
enum: CIB.JC0MUX 0
|
||||
enum: CIB.JC2MUX 0
|
||||
enum: CIB.JC4MUX 0
|
||||
enum: CIB.JC6MUX 0
|
||||
enum: CIB.JD0MUX 0
|
||||
enum: CIB.JD2MUX 0
|
||||
enum: CIB.JD4MUX 0
|
||||
enum: CIB.JD6MUX 0
|
||||
|
||||
|
||||
.tile CIB_R70C46:VCIB_DCUD
|
||||
enum: CIB.JA1MUX 0
|
||||
enum: CIB.JA5MUX 0
|
||||
enum: CIB.JA7MUX 0
|
||||
enum: CIB.JB1MUX 0
|
||||
enum: CIB.JB3MUX 0
|
||||
enum: CIB.JB5MUX 0
|
||||
enum: CIB.JB7MUX 0
|
||||
enum: CIB.JC0MUX 0
|
||||
enum: CIB.JC2MUX 0
|
||||
enum: CIB.JC4MUX 0
|
||||
enum: CIB.JC6MUX 0
|
||||
enum: CIB.JD0MUX 0
|
||||
enum: CIB.JD2MUX 0
|
||||
enum: CIB.JD4MUX 0
|
||||
enum: CIB.JD6MUX 0
|
||||
|
||||
|
||||
.tile CIB_R70C47:VCIB_DCUF
|
||||
enum: CIB.JA1MUX 0
|
||||
enum: CIB.JA3MUX 0
|
||||
enum: CIB.JA5MUX 0
|
||||
enum: CIB.JA7MUX 0
|
||||
enum: CIB.JB1MUX 0
|
||||
enum: CIB.JB3MUX 0
|
||||
enum: CIB.JB5MUX 0
|
||||
enum: CIB.JB7MUX 0
|
||||
enum: CIB.JC0MUX 0
|
||||
enum: CIB.JC2MUX 0
|
||||
enum: CIB.JC4MUX 0
|
||||
enum: CIB.JC6MUX 0
|
||||
enum: CIB.JD0MUX 0
|
||||
enum: CIB.JD2MUX 0
|
||||
enum: CIB.JD4MUX 0
|
||||
enum: CIB.JD6MUX 0
|
||||
|
||||
|
||||
.tile CIB_R70C48:VCIB_DCU3
|
||||
enum: CIB.JA5MUX 0
|
||||
enum: CIB.JA7MUX 0
|
||||
enum: CIB.JB1MUX 0
|
||||
enum: CIB.JB3MUX 0
|
||||
enum: CIB.JB5MUX 0
|
||||
enum: CIB.JB7MUX 0
|
||||
enum: CIB.JC0MUX 0
|
||||
enum: CIB.JC4MUX 0
|
||||
enum: CIB.JC6MUX 0
|
||||
enum: CIB.JD0MUX 0
|
||||
enum: CIB.JD2MUX 0
|
||||
enum: CIB.JD4MUX 0
|
||||
enum: CIB.JD6MUX 0
|
||||
|
||||
|
||||
.tile CIB_R70C49:VCIB_DCU2
|
||||
enum: CIB.JB1MUX 0
|
||||
enum: CIB.JB3MUX 0
|
||||
enum: CIB.JB5MUX 0
|
||||
enum: CIB.JB7MUX 0
|
||||
enum: CIB.JD0MUX 0
|
||||
enum: CIB.JD2MUX 0
|
||||
enum: CIB.JD4MUX 0
|
||||
enum: CIB.JD6MUX 0
|
||||
|
||||
|
||||
.tile CIB_R70C50:VCIB_DCUG
|
||||
enum: CIB.JB1MUX 0
|
||||
enum: CIB.JB3MUX 0
|
||||
enum: CIB.JB5MUX 0
|
||||
enum: CIB.JB7MUX 0
|
||||
enum: CIB.JD0MUX 0
|
||||
enum: CIB.JD2MUX 0
|
||||
enum: CIB.JD4MUX 0
|
||||
enum: CIB.JD6MUX 0
|
||||
|
||||
|
||||
.tile CIB_R70C51:VCIB_DCUH
|
||||
enum: CIB.JB1MUX 0
|
||||
enum: CIB.JB3MUX 0
|
||||
enum: CIB.JB5MUX 0
|
||||
enum: CIB.JB7MUX 0
|
||||
enum: CIB.JD0MUX 0
|
||||
enum: CIB.JD2MUX 0
|
||||
enum: CIB.JD4MUX 0
|
||||
enum: CIB.JD6MUX 0
|
||||
|
||||
|
||||
.tile CIB_R70C52:VCIB_DCUI
|
||||
enum: CIB.JB1MUX 0
|
||||
enum: CIB.JB3MUX 0
|
||||
enum: CIB.JB5MUX 0
|
||||
enum: CIB.JB7MUX 0
|
||||
enum: CIB.JD0MUX 0
|
||||
enum: CIB.JD2MUX 0
|
||||
enum: CIB.JD4MUX 0
|
||||
enum: CIB.JD6MUX 0
|
||||
|
||||
|
||||
.tile CIB_R70C53:VCIB_DCU1
|
||||
enum: CIB.JB1MUX 0
|
||||
enum: CIB.JB3MUX 0
|
||||
enum: CIB.JB5MUX 0
|
||||
enum: CIB.JD0MUX 0
|
||||
enum: CIB.JD2MUX 0
|
||||
|
||||
|
||||
.tile CIB_R70C69:VCIB_DCU0
|
||||
enum: CIB.JA1MUX 0
|
||||
enum: CIB.JA3MUX 0
|
||||
enum: CIB.JA5MUX 0
|
||||
enum: CIB.JA7MUX 0
|
||||
enum: CIB.JB1MUX 0
|
||||
enum: CIB.JB3MUX 0
|
||||
enum: CIB.JB5MUX 0
|
||||
enum: CIB.JB7MUX 0
|
||||
enum: CIB.JC0MUX 0
|
||||
enum: CIB.JC2MUX 0
|
||||
enum: CIB.JC4MUX 0
|
||||
enum: CIB.JC6MUX 0
|
||||
enum: CIB.JD0MUX 0
|
||||
enum: CIB.JD2MUX 0
|
||||
enum: CIB.JD4MUX 0
|
||||
enum: CIB.JD6MUX 0
|
||||
|
||||
|
||||
.tile CIB_R70C6:CIB_EFB0
|
||||
enum: CIB.JB3MUX 0
|
||||
enum: CIB.JC6MUX 0
|
||||
enum: CIB.JD6MUX 0
|
||||
|
||||
|
||||
.tile CIB_R70C70:VCIB_DCUA
|
||||
enum: CIB.JA1MUX 0
|
||||
enum: CIB.JA3MUX 0
|
||||
enum: CIB.JA5MUX 0
|
||||
enum: CIB.JA7MUX 0
|
||||
enum: CIB.JB1MUX 0
|
||||
enum: CIB.JB3MUX 0
|
||||
enum: CIB.JB5MUX 0
|
||||
enum: CIB.JB7MUX 0
|
||||
enum: CIB.JC0MUX 0
|
||||
enum: CIB.JC2MUX 0
|
||||
enum: CIB.JC4MUX 0
|
||||
enum: CIB.JC6MUX 0
|
||||
enum: CIB.JD0MUX 0
|
||||
enum: CIB.JD2MUX 0
|
||||
enum: CIB.JD4MUX 0
|
||||
enum: CIB.JD6MUX 0
|
||||
|
||||
|
||||
.tile CIB_R70C71:VCIB_DCUB
|
||||
enum: CIB.JA1MUX 0
|
||||
enum: CIB.JA3MUX 0
|
||||
enum: CIB.JA5MUX 0
|
||||
enum: CIB.JA7MUX 0
|
||||
enum: CIB.JB1MUX 0
|
||||
enum: CIB.JB3MUX 0
|
||||
enum: CIB.JB5MUX 0
|
||||
enum: CIB.JB7MUX 0
|
||||
enum: CIB.JC0MUX 0
|
||||
enum: CIB.JC2MUX 0
|
||||
enum: CIB.JC4MUX 0
|
||||
enum: CIB.JC6MUX 0
|
||||
enum: CIB.JD0MUX 0
|
||||
enum: CIB.JD2MUX 0
|
||||
enum: CIB.JD4MUX 0
|
||||
enum: CIB.JD6MUX 0
|
||||
|
||||
|
||||
.tile CIB_R70C72:VCIB_DCUC
|
||||
enum: CIB.JA1MUX 0
|
||||
enum: CIB.JA3MUX 0
|
||||
enum: CIB.JA5MUX 0
|
||||
enum: CIB.JA7MUX 0
|
||||
enum: CIB.JB1MUX 0
|
||||
enum: CIB.JB3MUX 0
|
||||
enum: CIB.JB5MUX 0
|
||||
enum: CIB.JB7MUX 0
|
||||
enum: CIB.JC0MUX 0
|
||||
enum: CIB.JC2MUX 0
|
||||
enum: CIB.JC4MUX 0
|
||||
enum: CIB.JC6MUX 0
|
||||
enum: CIB.JD0MUX 0
|
||||
enum: CIB.JD2MUX 0
|
||||
enum: CIB.JD4MUX 0
|
||||
enum: CIB.JD6MUX 0
|
||||
|
||||
|
||||
.tile CIB_R70C73:VCIB_DCUD
|
||||
enum: CIB.JA1MUX 0
|
||||
enum: CIB.JA5MUX 0
|
||||
enum: CIB.JA7MUX 0
|
||||
enum: CIB.JB1MUX 0
|
||||
enum: CIB.JB3MUX 0
|
||||
enum: CIB.JB5MUX 0
|
||||
enum: CIB.JB7MUX 0
|
||||
enum: CIB.JC0MUX 0
|
||||
enum: CIB.JC2MUX 0
|
||||
enum: CIB.JC4MUX 0
|
||||
enum: CIB.JC6MUX 0
|
||||
enum: CIB.JD0MUX 0
|
||||
enum: CIB.JD2MUX 0
|
||||
enum: CIB.JD4MUX 0
|
||||
enum: CIB.JD6MUX 0
|
||||
|
||||
|
||||
.tile CIB_R70C74:VCIB_DCUF
|
||||
enum: CIB.JA1MUX 0
|
||||
enum: CIB.JA3MUX 0
|
||||
enum: CIB.JA5MUX 0
|
||||
enum: CIB.JA7MUX 0
|
||||
enum: CIB.JB1MUX 0
|
||||
enum: CIB.JB3MUX 0
|
||||
enum: CIB.JB5MUX 0
|
||||
enum: CIB.JB7MUX 0
|
||||
enum: CIB.JC0MUX 0
|
||||
enum: CIB.JC2MUX 0
|
||||
enum: CIB.JC4MUX 0
|
||||
enum: CIB.JC6MUX 0
|
||||
enum: CIB.JD0MUX 0
|
||||
enum: CIB.JD2MUX 0
|
||||
enum: CIB.JD4MUX 0
|
||||
enum: CIB.JD6MUX 0
|
||||
|
||||
|
||||
.tile CIB_R70C75:VCIB_DCU3
|
||||
enum: CIB.JA5MUX 0
|
||||
enum: CIB.JA7MUX 0
|
||||
enum: CIB.JB1MUX 0
|
||||
enum: CIB.JB3MUX 0
|
||||
enum: CIB.JB5MUX 0
|
||||
enum: CIB.JB7MUX 0
|
||||
enum: CIB.JC0MUX 0
|
||||
enum: CIB.JC4MUX 0
|
||||
enum: CIB.JC6MUX 0
|
||||
enum: CIB.JD0MUX 0
|
||||
enum: CIB.JD2MUX 0
|
||||
enum: CIB.JD4MUX 0
|
||||
enum: CIB.JD6MUX 0
|
||||
|
||||
|
||||
.tile CIB_R70C76:VCIB_DCU2
|
||||
enum: CIB.JB1MUX 0
|
||||
enum: CIB.JB3MUX 0
|
||||
enum: CIB.JB5MUX 0
|
||||
enum: CIB.JB7MUX 0
|
||||
enum: CIB.JD0MUX 0
|
||||
enum: CIB.JD2MUX 0
|
||||
enum: CIB.JD4MUX 0
|
||||
enum: CIB.JD6MUX 0
|
||||
|
||||
|
||||
.tile CIB_R70C77:VCIB_DCUG
|
||||
enum: CIB.JB1MUX 0
|
||||
enum: CIB.JB3MUX 0
|
||||
enum: CIB.JB5MUX 0
|
||||
enum: CIB.JB7MUX 0
|
||||
enum: CIB.JD0MUX 0
|
||||
enum: CIB.JD2MUX 0
|
||||
enum: CIB.JD4MUX 0
|
||||
enum: CIB.JD6MUX 0
|
||||
|
||||
|
||||
.tile CIB_R70C78:VCIB_DCUH
|
||||
enum: CIB.JB1MUX 0
|
||||
enum: CIB.JB3MUX 0
|
||||
enum: CIB.JB5MUX 0
|
||||
enum: CIB.JB7MUX 0
|
||||
enum: CIB.JD0MUX 0
|
||||
enum: CIB.JD2MUX 0
|
||||
enum: CIB.JD4MUX 0
|
||||
enum: CIB.JD6MUX 0
|
||||
|
||||
|
||||
.tile CIB_R70C79:VCIB_DCUI
|
||||
enum: CIB.JB1MUX 0
|
||||
enum: CIB.JB3MUX 0
|
||||
enum: CIB.JB5MUX 0
|
||||
enum: CIB.JB7MUX 0
|
||||
enum: CIB.JD0MUX 0
|
||||
enum: CIB.JD2MUX 0
|
||||
enum: CIB.JD4MUX 0
|
||||
enum: CIB.JD6MUX 0
|
||||
|
||||
|
||||
.tile CIB_R70C7:CIB_EFB1
|
||||
enum: CIB.JA3MUX 0
|
||||
enum: CIB.JA4MUX 0
|
||||
enum: CIB.JA5MUX 0
|
||||
enum: CIB.JA6MUX 0
|
||||
enum: CIB.JB3MUX 0
|
||||
enum: CIB.JB4MUX 0
|
||||
enum: CIB.JB5MUX 0
|
||||
enum: CIB.JB6MUX 0
|
||||
enum: CIB.JC3MUX 0
|
||||
enum: CIB.JC4MUX 0
|
||||
enum: CIB.JC5MUX 0
|
||||
enum: CIB.JD3MUX 0
|
||||
enum: CIB.JD4MUX 0
|
||||
enum: CIB.JD5MUX 0
|
||||
|
||||
|
||||
.tile CIB_R70C80:VCIB_DCU1
|
||||
enum: CIB.JB1MUX 0
|
||||
enum: CIB.JB3MUX 0
|
||||
enum: CIB.JB5MUX 0
|
||||
enum: CIB.JD0MUX 0
|
||||
enum: CIB.JD2MUX 0
|
||||
|
||||
|
||||
.tile CIB_R70C87:CIB_PLL3
|
||||
enum: CIB.JA3MUX 0
|
||||
enum: CIB.JB3MUX 0
|
||||
|
||||
|
||||
.tile MIB_R10C40:CMUX_UL_0
|
||||
arc: G_DCS0CLK0 G_VPFN0000
|
||||
|
||||
|
||||
.tile MIB_R10C41:CMUX_UR_0
|
||||
arc: G_DCS0CLK1 G_VPFN0000
|
||||
|
||||
|
||||
.tile MIB_R58C40:CMUX_LL_0
|
||||
arc: G_DCS1CLK0 G_VPFN0000
|
||||
|
||||
|
||||
.tile MIB_R58C41:CMUX_LR_0
|
||||
arc: G_DCS1CLK1 G_VPFN0000
|
||||
|
||||
|
||||
.tile MIB_R71C4:EFB0_PICB0
|
||||
unknown: F54B1
|
||||
unknown: F56B1
|
||||
unknown: F82B1
|
||||
unknown: F94B1
|
||||
|
||||
.tile MIB_R71C3:BANKREF8
|
||||
unknown: F18B0
|
||||
|
@ -320,6 +320,8 @@ def process_timing_data():
|
||||
max_delay = min(entry["rising"][2], entry["falling"][2])
|
||||
delays.append((constids[from_pin], constids[to_pin], min_delay, max_delay))
|
||||
elif entry["type"] == "SetupHold":
|
||||
if type(entry["pin"]) is list:
|
||||
continue
|
||||
pin = constids[entry["pin"]]
|
||||
clock = constids[entry["clock"][1]]
|
||||
min_setup = entry["setup"][0]
|
||||
@ -580,6 +582,7 @@ def write_database(dev_name, chip, ddrg, endianness):
|
||||
bba.u32(len(location_types), "num_location_types")
|
||||
bba.u32(len(packages), "num_packages")
|
||||
bba.u32(len(pindata), "num_pios")
|
||||
bba.u32(const_id_count, "const_id_count")
|
||||
|
||||
bba.r("locations", "locations")
|
||||
bba.r("location_types", "location_type")
|
||||
@ -596,11 +599,12 @@ def write_database(dev_name, chip, ddrg, endianness):
|
||||
dev_names = {"25k": "LFE5UM5G-25F", "45k": "LFE5UM5G-45F", "85k": "LFE5UM5G-85F"}
|
||||
|
||||
def main():
|
||||
global max_row, max_col
|
||||
global max_row, max_col, const_id_count
|
||||
pytrellis.load_database(database.get_db_root())
|
||||
args = parser.parse_args()
|
||||
|
||||
# Read port pin file
|
||||
const_id_count = 1 # count ID_NONE
|
||||
with open(args.constids) as f:
|
||||
for line in f:
|
||||
line = line.replace("(", " ")
|
||||
@ -612,7 +616,7 @@ def main():
|
||||
assert line[0] == "X"
|
||||
idx = len(constids) + 1
|
||||
constids[line[1]] = idx
|
||||
|
||||
const_id_count += 1
|
||||
|
||||
constids["SLICE"] = constids["TRELLIS_SLICE"]
|
||||
constids["PIO"] = constids["TRELLIS_IO"]
|
||||
|
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
|
102
generic/arch.cc
102
generic/arch.cc
@ -21,11 +21,36 @@
|
||||
#include <math.h>
|
||||
#include "nextpnr.h"
|
||||
#include "placer1.h"
|
||||
#include "placer_heap.h"
|
||||
#include "router1.h"
|
||||
#include "util.h"
|
||||
|
||||
NEXTPNR_NAMESPACE_BEGIN
|
||||
|
||||
WireInfo &Arch::wire_info(IdString wire)
|
||||
{
|
||||
auto w = wires.find(wire);
|
||||
if (w == wires.end())
|
||||
NPNR_ASSERT_FALSE_STR("no wire named " + wire.str(this));
|
||||
return w->second;
|
||||
}
|
||||
|
||||
PipInfo &Arch::pip_info(IdString pip)
|
||||
{
|
||||
auto p = pips.find(pip);
|
||||
if (p == pips.end())
|
||||
NPNR_ASSERT_FALSE_STR("no pip named " + pip.str(this));
|
||||
return p->second;
|
||||
}
|
||||
|
||||
BelInfo &Arch::bel_info(IdString bel)
|
||||
{
|
||||
auto b = bels.find(bel);
|
||||
if (b == bels.end())
|
||||
NPNR_ASSERT_FALSE_STR("no bel named " + bel.str(this));
|
||||
return b->second;
|
||||
}
|
||||
|
||||
void Arch::addWire(IdString name, IdString type, int x, int y)
|
||||
{
|
||||
NPNR_ASSERT(wires.count(name) == 0);
|
||||
@ -49,8 +74,8 @@ void Arch::addPip(IdString name, IdString type, IdString srcWire, IdString dstWi
|
||||
pi.delay = delay;
|
||||
pi.loc = loc;
|
||||
|
||||
wires.at(srcWire).downhill.push_back(name);
|
||||
wires.at(dstWire).uphill.push_back(name);
|
||||
wire_info(srcWire).downhill.push_back(name);
|
||||
wire_info(dstWire).uphill.push_back(name);
|
||||
pip_ids.push_back(name);
|
||||
|
||||
if (int(tilePipDimZ.size()) <= loc.x)
|
||||
@ -74,7 +99,7 @@ void Arch::addAlias(IdString name, IdString type, IdString srcWire, IdString dst
|
||||
pi.dstWire = dstWire;
|
||||
pi.delay = delay;
|
||||
|
||||
wires.at(srcWire).aliases.push_back(name);
|
||||
wire_info(srcWire).aliases.push_back(name);
|
||||
pip_ids.push_back(name);
|
||||
}
|
||||
|
||||
@ -114,38 +139,38 @@ void Arch::addBel(IdString name, IdString type, Loc loc, bool gb)
|
||||
|
||||
void Arch::addBelInput(IdString bel, IdString name, IdString wire)
|
||||
{
|
||||
NPNR_ASSERT(bels.at(bel).pins.count(name) == 0);
|
||||
PinInfo &pi = bels.at(bel).pins[name];
|
||||
NPNR_ASSERT(bel_info(bel).pins.count(name) == 0);
|
||||
PinInfo &pi = bel_info(bel).pins[name];
|
||||
pi.name = name;
|
||||
pi.wire = wire;
|
||||
pi.type = PORT_IN;
|
||||
|
||||
wires.at(wire).downhill_bel_pins.push_back(BelPin{bel, name});
|
||||
wires.at(wire).bel_pins.push_back(BelPin{bel, name});
|
||||
wire_info(wire).downhill_bel_pins.push_back(BelPin{bel, name});
|
||||
wire_info(wire).bel_pins.push_back(BelPin{bel, name});
|
||||
}
|
||||
|
||||
void Arch::addBelOutput(IdString bel, IdString name, IdString wire)
|
||||
{
|
||||
NPNR_ASSERT(bels.at(bel).pins.count(name) == 0);
|
||||
PinInfo &pi = bels.at(bel).pins[name];
|
||||
NPNR_ASSERT(bel_info(bel).pins.count(name) == 0);
|
||||
PinInfo &pi = bel_info(bel).pins[name];
|
||||
pi.name = name;
|
||||
pi.wire = wire;
|
||||
pi.type = PORT_OUT;
|
||||
|
||||
wires.at(wire).uphill_bel_pin = BelPin{bel, name};
|
||||
wires.at(wire).bel_pins.push_back(BelPin{bel, name});
|
||||
wire_info(wire).uphill_bel_pin = BelPin{bel, name};
|
||||
wire_info(wire).bel_pins.push_back(BelPin{bel, name});
|
||||
}
|
||||
|
||||
void Arch::addBelInout(IdString bel, IdString name, IdString wire)
|
||||
{
|
||||
NPNR_ASSERT(bels.at(bel).pins.count(name) == 0);
|
||||
PinInfo &pi = bels.at(bel).pins[name];
|
||||
NPNR_ASSERT(bel_info(bel).pins.count(name) == 0);
|
||||
PinInfo &pi = bel_info(bel).pins[name];
|
||||
pi.name = name;
|
||||
pi.wire = wire;
|
||||
pi.type = PORT_INOUT;
|
||||
|
||||
wires.at(wire).downhill_bel_pins.push_back(BelPin{bel, name});
|
||||
wires.at(wire).bel_pins.push_back(BelPin{bel, name});
|
||||
wire_info(wire).downhill_bel_pins.push_back(BelPin{bel, name});
|
||||
wire_info(wire).bel_pins.push_back(BelPin{bel, name});
|
||||
}
|
||||
|
||||
void Arch::addGroupBel(IdString group, IdString bel) { groups[group].bels.push_back(bel); }
|
||||
@ -164,19 +189,19 @@ void Arch::addDecalGraphic(DecalId decal, const GraphicElement &graphic)
|
||||
|
||||
void Arch::setWireDecal(WireId wire, DecalXY decalxy)
|
||||
{
|
||||
wires.at(wire).decalxy = decalxy;
|
||||
wire_info(wire).decalxy = decalxy;
|
||||
refreshUiWire(wire);
|
||||
}
|
||||
|
||||
void Arch::setPipDecal(PipId pip, DecalXY decalxy)
|
||||
{
|
||||
pips.at(pip).decalxy = decalxy;
|
||||
pip_info(pip).decalxy = decalxy;
|
||||
refreshUiPip(pip);
|
||||
}
|
||||
|
||||
void Arch::setBelDecal(BelId bel, DecalXY decalxy)
|
||||
{
|
||||
bels.at(bel).decalxy = decalxy;
|
||||
bel_info(bel).decalxy = decalxy;
|
||||
refreshUiBel(bel);
|
||||
}
|
||||
|
||||
@ -186,11 +211,11 @@ void Arch::setGroupDecal(GroupId group, DecalXY decalxy)
|
||||
refreshUiGroup(group);
|
||||
}
|
||||
|
||||
void Arch::setWireAttr(IdString wire, IdString key, const std::string &value) { wires.at(wire).attrs[key] = value; }
|
||||
void Arch::setWireAttr(IdString wire, IdString key, const std::string &value) { wire_info(wire).attrs[key] = value; }
|
||||
|
||||
void Arch::setPipAttr(IdString pip, IdString key, const std::string &value) { pips.at(pip).attrs[key] = value; }
|
||||
void Arch::setPipAttr(IdString pip, IdString key, const std::string &value) { pip_info(pip).attrs[key] = value; }
|
||||
|
||||
void Arch::setBelAttr(IdString bel, IdString key, const std::string &value) { bels.at(bel).attrs[key] = value; }
|
||||
void Arch::setBelAttr(IdString bel, IdString key, const std::string &value) { bel_info(bel).attrs[key] = value; }
|
||||
|
||||
void Arch::setLutK(int K) { args.K = K; }
|
||||
|
||||
@ -494,8 +519,29 @@ bool Arch::getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay
|
||||
bool Arch::place()
|
||||
{
|
||||
std::string placer = str_or_default(settings, id("placer"), defaultPlacer);
|
||||
// FIXME: No HeAP because it needs a list of IO buffers
|
||||
if (placer == "sa") {
|
||||
if (placer == "heap") {
|
||||
bool have_iobuf_or_constr = false;
|
||||
for (auto cell : sorted(cells)) {
|
||||
CellInfo *ci = cell.second;
|
||||
if (ci->type == id("GENERIC_IOB") || ci->bel != BelId() || ci->attrs.count(id("BEL"))) {
|
||||
have_iobuf_or_constr = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
bool retVal;
|
||||
if (!have_iobuf_or_constr) {
|
||||
log_warning("Unable to use HeAP due to a lack of IO buffers or constrained cells as anchors; reverting to "
|
||||
"SA.\n");
|
||||
retVal = placer1(getCtx(), Placer1Cfg(getCtx()));
|
||||
} else {
|
||||
PlacerHeapCfg cfg(getCtx());
|
||||
cfg.ioBufTypes.insert(id("GENERIC_IOB"));
|
||||
retVal = placer_heap(getCtx(), cfg);
|
||||
}
|
||||
getCtx()->settings[getCtx()->id("place")] = 1;
|
||||
archInfoToAttributes();
|
||||
return retVal;
|
||||
} else if (placer == "sa") {
|
||||
bool retVal = placer1(getCtx(), Placer1Cfg(getCtx()));
|
||||
getCtx()->settings[getCtx()->id("place")] = 1;
|
||||
archInfoToAttributes();
|
||||
@ -596,9 +642,17 @@ bool Arch::isBelLocationValid(BelId bel) const
|
||||
return cellsCompatible(cells.data(), int(cells.size()));
|
||||
}
|
||||
|
||||
#ifdef WITH_HEAP
|
||||
const std::string Arch::defaultPlacer = "heap";
|
||||
#else
|
||||
const std::string Arch::defaultPlacer = "sa";
|
||||
const std::vector<std::string> Arch::availablePlacers = {"sa"};
|
||||
#endif
|
||||
|
||||
const std::vector<std::string> Arch::availablePlacers = {"sa",
|
||||
#ifdef WITH_HEAP
|
||||
"heap"
|
||||
#endif
|
||||
};
|
||||
void Arch::assignArchInfo()
|
||||
{
|
||||
for (auto &cell : getCtx()->cells) {
|
||||
|
@ -122,6 +122,11 @@ struct Arch : BaseCtx
|
||||
std::unordered_map<IdString, BelInfo> bels;
|
||||
std::unordered_map<GroupId, GroupInfo> groups;
|
||||
|
||||
// These functions include useful errors if not found
|
||||
WireInfo &wire_info(IdString wire);
|
||||
PipInfo &pip_info(IdString wire);
|
||||
BelInfo &bel_info(IdString wire);
|
||||
|
||||
std::vector<IdString> bel_ids, wire_ids, pip_ids;
|
||||
|
||||
std::unordered_map<Loc, BelId> bel_by_loc;
|
||||
|
@ -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>);
|
||||
}
|
||||
|
||||
|
@ -42,7 +42,7 @@ std::unique_ptr<CellInfo> create_generic_cell(Context *ctx, IdString type, std::
|
||||
}
|
||||
new_cell->type = type;
|
||||
if (type == ctx->id("GENERIC_SLICE")) {
|
||||
new_cell->params[ctx->id("K")] = std::to_string(ctx->args.K);
|
||||
new_cell->params[ctx->id("K")] = ctx->args.K;
|
||||
new_cell->params[ctx->id("INIT")] = 0;
|
||||
new_cell->params[ctx->id("FF_USED")] = 0;
|
||||
|
||||
@ -51,6 +51,7 @@ std::unique_ptr<CellInfo> create_generic_cell(Context *ctx, IdString type, std::
|
||||
|
||||
add_port(ctx, new_cell.get(), "CLK", PORT_IN);
|
||||
|
||||
add_port(ctx, new_cell.get(), "F", PORT_OUT);
|
||||
add_port(ctx, new_cell.get(), "Q", PORT_OUT);
|
||||
} else if (type == ctx->id("GENERIC_IOB")) {
|
||||
new_cell->params[ctx->id("INPUT_USED")] = 0;
|
||||
@ -80,8 +81,8 @@ void lut_to_lc(const Context *ctx, CellInfo *lut, CellInfo *lc, bool no_dff)
|
||||
}
|
||||
|
||||
if (no_dff) {
|
||||
replace_port(lut, ctx->id("Q"), lc, ctx->id("Q"));
|
||||
lc->params[ctx->id("FF_USED")] = 0;
|
||||
replace_port(lut, ctx->id("Q"), lc, ctx->id("F"));
|
||||
}
|
||||
}
|
||||
|
||||
@ -91,7 +92,14 @@ void dff_to_lc(const Context *ctx, CellInfo *dff, CellInfo *lc, bool pass_thru_l
|
||||
replace_port(dff, ctx->id("CLK"), lc, ctx->id("CLK"));
|
||||
|
||||
if (pass_thru_lut) {
|
||||
lc->params[ctx->id("INIT")] = 2;
|
||||
// Fill LUT with alternating 10
|
||||
const int init_size = 1 << lc->params[ctx->id("K")].as_int64();
|
||||
std::string init;
|
||||
init.reserve(init_size);
|
||||
for (int i = 0; i < init_size; i += 2)
|
||||
init.append("10");
|
||||
lc->params[ctx->id("INIT")] = Property::from_string(init);
|
||||
|
||||
replace_port(dff, ctx->id("D"), lc, ctx->id("I[0]"));
|
||||
}
|
||||
|
||||
|
3
generic/examples/.gitignore
vendored
3
generic/examples/.gitignore
vendored
@ -1,3 +1,6 @@
|
||||
blinky.fasm
|
||||
__pycache__
|
||||
*.pyc
|
||||
pnrblinky.v
|
||||
/blinky_simtest
|
||||
*.vcd
|
||||
|
@ -14,4 +14,4 @@ param_map = {
|
||||
}
|
||||
|
||||
with open("blinky.fasm", "w") as f:
|
||||
write_fasm(ctx, param_map, f)
|
||||
write_fasm(ctx, param_map, f)
|
||||
|
@ -1,9 +1,12 @@
|
||||
module top(input clk, output reg [7:0] leds);
|
||||
module top(input clk, rst, output reg [7:0] leds);
|
||||
|
||||
reg [25:0] ctr;
|
||||
reg [7:0] ctr;
|
||||
always @(posedge clk)
|
||||
ctr <= ctr + 1'b1;
|
||||
if (rst)
|
||||
ctr <= 8'h00;
|
||||
else
|
||||
ctr <= ctr + 1'b1;
|
||||
|
||||
assign leds = ctr[25:18];
|
||||
assign leds = ctr;
|
||||
|
||||
endmodule
|
||||
endmodule
|
||||
|
38
generic/examples/blinky_tb.v
Normal file
38
generic/examples/blinky_tb.v
Normal file
@ -0,0 +1,38 @@
|
||||
`timescale 1ns / 1ps
|
||||
module blinky_tb;
|
||||
|
||||
reg clk = 1'b0, rst = 1'b0;
|
||||
reg [7:0] ctr_gold = 8'h00;
|
||||
wire [7:0] ctr_gate;
|
||||
top dut_i(.clk(clk), .rst(rst), .leds(ctr_gate));
|
||||
|
||||
task oneclk;
|
||||
begin
|
||||
clk = 1'b1;
|
||||
#10;
|
||||
clk = 1'b0;
|
||||
#10;
|
||||
end
|
||||
endtask
|
||||
|
||||
initial begin
|
||||
$dumpfile("blinky_simtest.vcd");
|
||||
$dumpvars(0, blinky_tb);
|
||||
#100;
|
||||
rst = 1'b1;
|
||||
repeat (5) oneclk;
|
||||
#5
|
||||
rst = 1'b0;
|
||||
#5
|
||||
repeat (500) begin
|
||||
if (ctr_gold !== ctr_gate) begin
|
||||
$display("mismatch gold=%b gate=%b", ctr_gold, ctr_gate);
|
||||
$stop;
|
||||
end
|
||||
oneclk;
|
||||
ctr_gold = ctr_gold + 1'b1;
|
||||
end
|
||||
$finish;
|
||||
end
|
||||
|
||||
endmodule
|
@ -9,6 +9,7 @@ for x in range(X):
|
||||
for z in range(N):
|
||||
ctx.addWire(name="X%dY%dZ%d_CLK" % (x, y, z), type="BEL_CLK", x=x, y=y)
|
||||
ctx.addWire(name="X%dY%dZ%d_Q" % (x, y, z), type="BEL_Q", x=x, y=y)
|
||||
ctx.addWire(name="X%dY%dZ%d_F" % (x, y, z), type="BEL_F", x=x, y=y)
|
||||
for i in range(K):
|
||||
ctx.addWire(name="X%dY%dZ%d_I%d" % (x, y, z, i), type="BEL_I", x=x, y=y)
|
||||
# Local wires
|
||||
@ -29,6 +30,7 @@ for x in range(X):
|
||||
ctx.addBelInput(bel="X%dY%d_SLICE%d" % (x, y, z), name="CLK", wire="X%dY%dZ%d_CLK" % (x, y, z))
|
||||
for k in range(K):
|
||||
ctx.addBelInput(bel="X%dY%d_SLICE%d" % (x, y, z), name="I[%d]" % k, wire="X%dY%dZ%d_I%d" % (x, y, z, k))
|
||||
ctx.addBelOutput(bel="X%dY%d_SLICE%d" % (x, y, z), name="F", wire="X%dY%dZ%d_F" % (x, y, z))
|
||||
ctx.addBelOutput(bel="X%dY%d_SLICE%d" % (x, y, z), name="Q", wire="X%dY%dZ%d_Q" % (x, y, z))
|
||||
|
||||
for x in range(X):
|
||||
@ -48,6 +50,9 @@ for x in range(X):
|
||||
# Pips from bel outputs to locals
|
||||
def create_output_pips(dst, offset, skip):
|
||||
for i in range(offset % skip, N, skip):
|
||||
src = "X%dY%dZ%d_F" % (x, y, i)
|
||||
ctx.addPip(name="X%dY%d.%s.%s" % (x, y, src, dst), type="BEL_OUTPUT",
|
||||
srcWire=src, dstWire=dst, delay=ctx.getDelayFromNS(0.05), loc=Loc(x, y, 0))
|
||||
src = "X%dY%dZ%d_Q" % (x, y, i)
|
||||
ctx.addPip(name="X%dY%d.%s.%s" % (x, y, src, dst), type="BEL_OUTPUT",
|
||||
srcWire=src, dstWire=dst, delay=ctx.getDelayFromNS(0.05), loc=Loc(x, y, 0))
|
||||
@ -69,4 +74,4 @@ for x in range(X):
|
||||
create_neighbour_pips(dst, x, y+1, (l + 4) % Sl, Sl)
|
||||
create_neighbour_pips(dst, x+1, y-1, (l + 5) % Sl, Sl)
|
||||
create_neighbour_pips(dst, x+1, y, (l + 6) % Sl, Sl)
|
||||
create_neighbour_pips(dst, x+1, y+1, (l + 7) % Sl, Sl)
|
||||
create_neighbour_pips(dst, x+1, y+1, (l + 7) % Sl, Sl)
|
||||
|
@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
set -ex
|
||||
yosys -p "tcl ../synth/synth_generic.tcl 4 blinky.json" blinky.v
|
||||
${NEXTPNR:-../../nextpnr-generic} --pre-pack simple.py --pre-place simple_timing.py --json blinky.json --post-route bitstream.py
|
||||
${NEXTPNR:-../../nextpnr-generic} --pre-pack simple.py --pre-place simple_timing.py --json blinky.json --post-route bitstream.py --write pnrblinky.json
|
||||
yosys -p "read_verilog -lib ../synth/prims.v; read_json pnrblinky.json; dump -o blinky.il; show -format png -prefix blinky"
|
||||
|
@ -1,15 +1,13 @@
|
||||
for cname, cell in ctx.cells:
|
||||
if cell.type != "GENERIC_SLICE":
|
||||
continue
|
||||
if cname in ("$PACKER_GND", "$PACKER_VCC"):
|
||||
continue
|
||||
K = int(cell.params["K"])
|
||||
if int(cell.params["FF_USED"], 2) == 1:
|
||||
ctx.addCellTimingClock(cell=cname, port="CLK")
|
||||
for i in range(K):
|
||||
ctx.addCellTimingSetupHold(cell=cname, port="I[%d]" % i, clock="CLK",
|
||||
setup=ctx.getDelayFromNS(0.2), hold=ctx.getDelayFromNS(0))
|
||||
ctx.addCellTimingClockToOut(cell=cname, port="Q", clock="CLK", clktoq=ctx.getDelayFromNS(0.2))
|
||||
else:
|
||||
for i in range(K):
|
||||
ctx.addCellTimingDelay(cell=cname, fromPort="I[%d]" % i, toPort="Q", delay=ctx.getDelayFromNS(0.2))
|
||||
if cell.type != "GENERIC_SLICE":
|
||||
continue
|
||||
if cname in ("$PACKER_GND", "$PACKER_VCC"):
|
||||
continue
|
||||
K = int(cell.params["K"])
|
||||
ctx.addCellTimingClock(cell=cname, port="CLK")
|
||||
for i in range(K):
|
||||
ctx.addCellTimingSetupHold(cell=cname, port="I[%d]" % i, clock="CLK",
|
||||
setup=ctx.getDelayFromNS(0.2), hold=ctx.getDelayFromNS(0))
|
||||
ctx.addCellTimingClockToOut(cell=cname, port="Q", clock="CLK", clktoq=ctx.getDelayFromNS(0.2))
|
||||
for i in range(K):
|
||||
ctx.addCellTimingDelay(cell=cname, fromPort="I[%d]" % i, toPort="F", delay=ctx.getDelayFromNS(0.2))
|
||||
|
7
generic/examples/simtest.sh
Executable file
7
generic/examples/simtest.sh
Executable file
@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
set -ex
|
||||
yosys -p "tcl ../synth/synth_generic.tcl 4 blinky.json" blinky.v
|
||||
${NEXTPNR:-../../nextpnr-generic} --no-iobs --pre-pack simple.py --pre-place simple_timing.py --json blinky.json --post-route bitstream.py --write pnrblinky.json
|
||||
yosys -p "read_json pnrblinky.json; write_verilog -noattr -norename pnrblinky.v"
|
||||
iverilog -o blinky_simtest ../synth/prims.v blinky_tb.v pnrblinky.v
|
||||
vvp -N ./blinky_simtest
|
@ -46,6 +46,7 @@ po::options_description GenericCommandHandler::getArchOptions()
|
||||
{
|
||||
po::options_description specific("Architecture specific options");
|
||||
specific.add_options()("generic", "set device type to generic");
|
||||
specific.add_options()("no-iobs", "disable automatic IO buffer insertion");
|
||||
return specific;
|
||||
}
|
||||
|
||||
@ -59,7 +60,10 @@ std::unique_ptr<Context> GenericCommandHandler::createContext(std::unordered_map
|
||||
if (arch_name != "generic")
|
||||
log_error("Unsuported architecture '%s'.\n", arch_name.c_str());
|
||||
}
|
||||
return std::unique_ptr<Context>(new Context(chipArgs));
|
||||
auto ctx = std::unique_ptr<Context>(new Context(chipArgs));
|
||||
if (vm.count("no-iobs"))
|
||||
ctx->settings[ctx->id("disable_iobs")] = Property::State::S1;
|
||||
return ctx;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
|
@ -150,20 +150,21 @@ static void pack_constants(Context *ctx)
|
||||
log_info("Packing constants..\n");
|
||||
|
||||
std::unique_ptr<CellInfo> gnd_cell = create_generic_cell(ctx, ctx->id("GENERIC_SLICE"), "$PACKER_GND");
|
||||
gnd_cell->params[ctx->id("INIT")] = 0;
|
||||
gnd_cell->params[ctx->id("INIT")] = Property(0, 1 << ctx->args.K);
|
||||
std::unique_ptr<NetInfo> gnd_net = std::unique_ptr<NetInfo>(new NetInfo);
|
||||
gnd_net->name = ctx->id("$PACKER_GND_NET");
|
||||
gnd_net->driver.cell = gnd_cell.get();
|
||||
gnd_net->driver.port = ctx->id("Q");
|
||||
gnd_cell->ports.at(ctx->id("Q")).net = gnd_net.get();
|
||||
gnd_net->driver.port = ctx->id("F");
|
||||
gnd_cell->ports.at(ctx->id("F")).net = gnd_net.get();
|
||||
|
||||
std::unique_ptr<CellInfo> vcc_cell = create_generic_cell(ctx, ctx->id("GENERIC_SLICE"), "$PACKER_VCC");
|
||||
vcc_cell->params[ctx->id("INIT")] = 1;
|
||||
// Fill with 1s
|
||||
vcc_cell->params[ctx->id("INIT")] = Property(Property::S1).extract(0, (1 << ctx->args.K), Property::S1);
|
||||
std::unique_ptr<NetInfo> vcc_net = std::unique_ptr<NetInfo>(new NetInfo);
|
||||
vcc_net->name = ctx->id("$PACKER_VCC_NET");
|
||||
vcc_net->driver.cell = vcc_cell.get();
|
||||
vcc_net->driver.port = ctx->id("Q");
|
||||
vcc_cell->ports.at(ctx->id("Q")).net = vcc_net.get();
|
||||
vcc_net->driver.port = ctx->id("F");
|
||||
vcc_cell->ports.at(ctx->id("F")).net = vcc_net.get();
|
||||
|
||||
std::vector<IdString> dead_nets;
|
||||
|
||||
@ -249,6 +250,10 @@ static void pack_io(Context *ctx)
|
||||
delete_nets.insert(net2->name);
|
||||
}
|
||||
}
|
||||
} else if (bool_or_default(ctx->settings, ctx->id("disable_iobs"))) {
|
||||
// No IO buffer insertion; just remove nextpnr_[io]buf
|
||||
for (auto &p : ci->ports)
|
||||
disconnect_port(ctx, ci, p.first);
|
||||
} else {
|
||||
// Create a GENERIC_IOB buffer
|
||||
std::unique_ptr<CellInfo> ice_cell =
|
||||
|
@ -4,7 +4,9 @@ module \$lut (A, Y);
|
||||
input [WIDTH-1:0] A;
|
||||
output Y;
|
||||
|
||||
LUT #(.K(`LUT_K), .INIT(LUT)) _TECHMAP_REPLACE_ (.I(A), .Q(Y));
|
||||
localparam rep = 1<<(`LUT_K-WIDTH);
|
||||
|
||||
LUT #(.K(`LUT_K), .INIT({rep{LUT}})) _TECHMAP_REPLACE_ (.I(A), .Q(Y));
|
||||
endmodule
|
||||
|
||||
module \$_DFF_P_ (input D, C, output Q); DFF _TECHMAP_REPLACE_ (.D(D), .Q(Q), .CLK(C)); endmodule
|
||||
|
@ -2,18 +2,27 @@
|
||||
|
||||
module LUT #(
|
||||
parameter K = 4,
|
||||
parameter [2**K-1:0] INIT = 0,
|
||||
parameter [2**K-1:0] INIT = 0
|
||||
) (
|
||||
input [K-1:0] I,
|
||||
output Q
|
||||
);
|
||||
assign Q = INIT[I];
|
||||
wire [K-1:0] I_pd;
|
||||
|
||||
genvar ii;
|
||||
generate
|
||||
for (ii = 0; ii < K; ii = ii + 1'b1)
|
||||
assign I_pd[ii] = (I[ii] === 1'bz) ? 1'b0 : I[ii];
|
||||
endgenerate
|
||||
|
||||
assign Q = INIT[I_pd];
|
||||
endmodule
|
||||
|
||||
module DFF (
|
||||
input CLK, D,
|
||||
output reg Q
|
||||
);
|
||||
initial Q = 1'b0;
|
||||
always @(posedge CLK)
|
||||
Q <= D;
|
||||
endmodule
|
||||
@ -25,17 +34,16 @@ module GENERIC_SLICE #(
|
||||
) (
|
||||
input CLK,
|
||||
input [K-1:0] I,
|
||||
output F,
|
||||
output Q
|
||||
);
|
||||
wire f_wire;
|
||||
|
||||
wire lut_q;
|
||||
LUT #(.K(K), .INIT(INIT)) lut_i(.I(I), .Q(lut_q));
|
||||
LUT #(.K(K), .INIT(INIT)) lut_i(.I(I), .Q(f_wire));
|
||||
|
||||
generate if (FF_USED)
|
||||
DFF dff_i(.CLK(CLK), .D(lut_q), .Q(Q));
|
||||
else
|
||||
assign Q = lut_q;
|
||||
endgenerate
|
||||
DFF dff_i(.CLK(CLK), .D(f_wire), .Q(Q));
|
||||
|
||||
assign F = f_wire;
|
||||
endmodule
|
||||
|
||||
module GENERIC_IOB #(
|
||||
@ -56,4 +64,4 @@ module GENERIC_IOB #(
|
||||
generate if (INPUT_USED)
|
||||
assign O = PAD;
|
||||
endgenerate
|
||||
endmodule
|
||||
endmodule
|
||||
|
@ -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"
|
||||
|
||||
|
@ -1245,7 +1245,11 @@ void Arch::assignCellInfo(CellInfo *cell)
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef WITH_HEAP
|
||||
const std::string Arch::defaultPlacer = "heap";
|
||||
#else
|
||||
const std::string Arch::defaultPlacer = "sa";
|
||||
#endif
|
||||
|
||||
const std::vector<std::string> Arch::availablePlacers = {"sa",
|
||||
#ifdef WITH_HEAP
|
||||
|
@ -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
|
||||
|
@ -48,6 +48,9 @@ struct DelayInfo
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
// https://bugreports.qt.io/browse/QTBUG-80789
|
||||
|
||||
#ifndef Q_MOC_RUN
|
||||
enum ConstIds
|
||||
{
|
||||
ID_NONE
|
||||
@ -59,6 +62,7 @@ enum ConstIds
|
||||
#define X(t) static constexpr auto id_##t = IdString(ID_##t);
|
||||
#include "constids.inc"
|
||||
#undef X
|
||||
#endif
|
||||
|
||||
struct BelId
|
||||
{
|
||||
|
@ -159,6 +159,9 @@ void configure_extra_cell(chipconfig_t &config, const Context *ctx, CellInfo *ce
|
||||
if (string_style) {
|
||||
// Lattice's weird string style params, not sure if
|
||||
// prefixes other than 0b should be supported, only 0b features in docs
|
||||
if (cell->params.count(ctx->id(p.first)) && !cell->params.at(ctx->id(p.first)).is_string)
|
||||
log_error("expected configuration string starting with '0b' for parameter '%s' on cell '%s'\n",
|
||||
p.first.c_str(), cell->name.c_str(ctx));
|
||||
std::string raw = get_param_str_or_def(cell, ctx->id(p.first), "0b0");
|
||||
if (raw.substr(0, 2) != "0b")
|
||||
log_error("expected configuration string starting with '0b' for parameter '%s' on cell '%s'\n",
|
||||
|
@ -69,7 +69,7 @@ std::unique_ptr<CellInfo> create_ice_cell(Context *ctx, IdString type, std::stri
|
||||
new_cell->params[ctx->id("PIN_TYPE")] = Property(0, 6);
|
||||
new_cell->params[ctx->id("PULLUP")] = Property::State::S0;
|
||||
new_cell->params[ctx->id("NEG_TRIGGER")] = Property::State::S0;
|
||||
new_cell->params[ctx->id("IOSTANDARD")] = Property("SB_LVCMOS");
|
||||
new_cell->params[ctx->id("IO_STANDARD")] = Property("SB_LVCMOS");
|
||||
|
||||
add_port(ctx, new_cell.get(), "PACKAGE_PIN", PORT_INOUT);
|
||||
|
||||
@ -346,6 +346,8 @@ std::unique_ptr<CellInfo> create_ice_cell(Context *ctx, IdString type, std::stri
|
||||
|
||||
void lut_to_lc(const Context *ctx, CellInfo *lut, CellInfo *lc, bool no_dff)
|
||||
{
|
||||
if (lc->hierpath == IdString())
|
||||
lc->hierpath = lut->hierpath;
|
||||
lc->params[ctx->id("LUT_INIT")] = lut->params[ctx->id("LUT_INIT")].extract(0, 16, Property::State::S0);
|
||||
replace_port(lut, ctx->id("I0"), lc, ctx->id("I0"));
|
||||
replace_port(lut, ctx->id("I1"), lc, ctx->id("I1"));
|
||||
@ -359,6 +361,8 @@ void lut_to_lc(const Context *ctx, CellInfo *lut, CellInfo *lc, bool no_dff)
|
||||
|
||||
void dff_to_lc(const Context *ctx, CellInfo *dff, CellInfo *lc, bool pass_thru_lut)
|
||||
{
|
||||
if (lc->hierpath == IdString())
|
||||
lc->hierpath = dff->hierpath;
|
||||
lc->params[ctx->id("DFF_ENABLE")] = Property::State::S1;
|
||||
std::string config = dff->type.str(ctx).substr(6);
|
||||
auto citer = config.begin();
|
||||
@ -426,7 +430,25 @@ void nxio_to_sb(Context *ctx, CellInfo *nxio, CellInfo *sbio, std::unordered_set
|
||||
} else {
|
||||
NPNR_ASSERT(false);
|
||||
}
|
||||
NetInfo *donet = sbio->ports.at(ctx->id("D_OUT_0")).net;
|
||||
NetInfo *donet = sbio->ports.at(ctx->id("D_OUT_0")).net, *dinet = sbio->ports.at(ctx->id("D_IN_0")).net;
|
||||
|
||||
// Rename I/O nets to avoid conflicts
|
||||
if (donet != nullptr && donet->name == nxio->name)
|
||||
rename_net(ctx, donet, ctx->id(donet->name.str(ctx) + "$SB_IO_OUT"));
|
||||
if (dinet != nullptr && dinet->name == nxio->name)
|
||||
rename_net(ctx, dinet, ctx->id(dinet->name.str(ctx) + "$SB_IO_IN"));
|
||||
|
||||
// Create a new top port net for accurate IO timing analysis and simulation netlists
|
||||
if (ctx->ports.count(nxio->name)) {
|
||||
IdString tn_netname = nxio->name;
|
||||
NPNR_ASSERT(!ctx->nets.count(tn_netname));
|
||||
std::unique_ptr<NetInfo> toplevel_net{new NetInfo};
|
||||
toplevel_net->name = tn_netname;
|
||||
connect_port(ctx, toplevel_net.get(), sbio, ctx->id("PACKAGE_PIN"));
|
||||
ctx->ports[nxio->name].net = toplevel_net.get();
|
||||
ctx->nets[tn_netname] = std::move(toplevel_net);
|
||||
}
|
||||
|
||||
CellInfo *tbuf = net_driven_by(
|
||||
ctx, donet, [](const Context *ctx, const CellInfo *cell) { return cell->type == ctx->id("$_TBUF_"); },
|
||||
ctx->id("Y"));
|
||||
|
@ -40,14 +40,6 @@ if (NOT EXTERNAL_CHIPDB)
|
||||
set(DEV_CC_DB ${CMAKE_CURRENT_BINARY_DIR}/ice40/chipdbs/chipdb-${dev}.bin)
|
||||
set(DEV_CONSTIDS_INC ${CMAKE_CURRENT_SOURCE_DIR}/ice40/constids.inc)
|
||||
set(DEV_GFXH ${CMAKE_CURRENT_SOURCE_DIR}/ice40/gfx.h)
|
||||
add_custom_command(OUTPUT ${DEV_CC_BBA_DB}
|
||||
COMMAND ${PYTHON_EXECUTABLE} ${DB_PY} -p ${DEV_CONSTIDS_INC} -g ${DEV_GFXH} ${OPT_FAST} ${OPT_SLOW} ${DEV_TXT_DB} > ${DEV_CC_BBA_DB}
|
||||
DEPENDS ${DEV_CONSTIDS_INC} ${DEV_GFXH} ${DEV_TXT_DB} ${DB_PY} ${PREV_DEV_CC_BBA_DB}
|
||||
)
|
||||
add_custom_command(OUTPUT ${DEV_CC_DB}
|
||||
COMMAND bbasm ${BBASM_ENDIAN_FLAG} ${DEV_CC_BBA_DB} ${DEV_CC_DB}
|
||||
DEPENDS bbasm ${DEV_CC_BBA_DB}
|
||||
)
|
||||
|
||||
if(PREGENERATED_BBA_PATH)
|
||||
add_custom_command(OUTPUT ${DEV_CC_DB}
|
||||
@ -92,6 +84,7 @@ if (NOT EXTERNAL_CHIPDB)
|
||||
set(DEV_TXT_DB ${ICEBOX_ROOT}/chipdb-${dev}.txt)
|
||||
set(DEV_CC_BBA_DB ${CMAKE_CURRENT_SOURCE_DIR}/ice40/chipdbs/chipdb-${dev}.bba)
|
||||
set(DEV_CC_DB ${CMAKE_CURRENT_BINARY_DIR}/ice40/chipdbs/chipdb-${dev}.cc)
|
||||
set(DEV_BIN_DB ${CMAKE_CURRENT_BINARY_DIR}/ice40/chipdbs/chipdb-${dev}.bin)
|
||||
set(DEV_CONSTIDS_INC ${CMAKE_CURRENT_SOURCE_DIR}/ice40/constids.inc)
|
||||
set(DEV_GFXH ${CMAKE_CURRENT_SOURCE_DIR}/ice40/gfx.h)
|
||||
if(PREGENERATED_BBA_PATH)
|
||||
@ -105,11 +98,19 @@ if (NOT EXTERNAL_CHIPDB)
|
||||
COMMAND mv ${DEV_CC_BBA_DB}.new ${DEV_CC_BBA_DB}
|
||||
DEPENDS ${DEV_CONSTIDS_INC} ${DEV_GFXH} ${DEV_TXT_DB} ${DB_PY} ${PREV_DEV_CC_BBA_DB}
|
||||
)
|
||||
add_custom_command(OUTPUT ${DEV_CC_DB}
|
||||
COMMAND bbasm --c ${BBASM_ENDIAN_FLAG} ${DEV_CC_BBA_DB} ${DEV_CC_DB}.new
|
||||
COMMAND mv ${DEV_CC_DB}.new ${DEV_CC_DB}
|
||||
DEPENDS bbasm ${DEV_CC_BBA_DB}
|
||||
)
|
||||
if(USE_C_EMBED)
|
||||
add_custom_command(OUTPUT ${DEV_CC_DB}
|
||||
COMMAND bbasm --e ${BBASM_ENDIAN_FLAG} ${DEV_CC_BBA_DB} ${DEV_CC_DB}.new ${DEV_BIN_DB}
|
||||
COMMAND mv ${DEV_CC_DB}.new ${DEV_CC_DB}
|
||||
DEPENDS bbasm ${DEV_CC_BBA_DB}
|
||||
)
|
||||
else()
|
||||
add_custom_command(OUTPUT ${DEV_CC_DB}
|
||||
COMMAND bbasm --c ${BBASM_ENDIAN_FLAG} ${DEV_CC_BBA_DB} ${DEV_CC_DB}.new
|
||||
COMMAND mv ${DEV_CC_DB}.new ${DEV_CC_DB}
|
||||
DEPENDS bbasm ${DEV_CC_BBA_DB}
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
if (SERIALIZE_CHIPDB)
|
||||
set(PREV_DEV_CC_BBA_DB ${DEV_CC_BBA_DB})
|
||||
|
@ -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"
|
||||
|
@ -459,7 +459,6 @@ static void pack_io(Context *ctx)
|
||||
{
|
||||
std::unordered_set<IdString> packed_cells;
|
||||
std::unordered_set<IdString> delete_nets;
|
||||
|
||||
std::vector<std::unique_ptr<CellInfo>> new_cells;
|
||||
log_info("Packing IOs..\n");
|
||||
|
||||
@ -478,8 +477,7 @@ static void pack_io(Context *ctx)
|
||||
rgb = net->driver.cell;
|
||||
}
|
||||
if (sb != nullptr) {
|
||||
// Trivial case, SB_IO used. Just destroy the net and the
|
||||
// iobuf
|
||||
// Trivial case, SB_IO used. Just destroy the iobuf
|
||||
log_info("%s feeds SB_IO %s, removing %s %s.\n", ci->name.c_str(ctx), sb->name.c_str(ctx),
|
||||
ci->type.c_str(ctx), ci->name.c_str(ctx));
|
||||
NetInfo *net = sb->ports.at(ctx->id("PACKAGE_PIN")).net;
|
||||
@ -490,7 +488,6 @@ static void pack_io(Context *ctx)
|
||||
sb->type.c_str(ctx), sb->name.c_str(ctx));
|
||||
|
||||
if (net != nullptr) {
|
||||
|
||||
if (net->clkconstr != nullptr) {
|
||||
if (sb->ports.count(id_D_IN_0)) {
|
||||
NetInfo *din0_net = sb->ports.at(id_D_IN_0).net;
|
||||
@ -509,15 +506,6 @@ static void pack_io(Context *ctx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delete_nets.insert(net->name);
|
||||
sb->ports.at(ctx->id("PACKAGE_PIN")).net = nullptr;
|
||||
}
|
||||
if (ci->type == ctx->id("$nextpnr_iobuf")) {
|
||||
NetInfo *net2 = ci->ports.at(ctx->id("I")).net;
|
||||
if (net2 != nullptr) {
|
||||
delete_nets.insert(net2->name);
|
||||
}
|
||||
}
|
||||
} else if (rgb != nullptr) {
|
||||
log_info("%s use by SB_RGBA_DRV/SB_RGB_DRV %s, not creating SB_IO\n", ci->name.c_str(ctx),
|
||||
@ -533,11 +521,15 @@ static void pack_io(Context *ctx)
|
||||
new_cells.push_back(std::move(ice_cell));
|
||||
sb = new_cells.back().get();
|
||||
}
|
||||
for (auto port : ci->ports)
|
||||
disconnect_port(ctx, ci, port.first);
|
||||
packed_cells.insert(ci->name);
|
||||
std::copy(ci->attrs.begin(), ci->attrs.end(), std::inserter(sb->attrs, sb->attrs.begin()));
|
||||
} else if (is_sb_io(ctx, ci) || is_sb_gb_io(ctx, ci)) {
|
||||
NetInfo *net = ci->ports.at(ctx->id("PACKAGE_PIN")).net;
|
||||
if ((net != nullptr) && (net->users.size() > 1))
|
||||
if ((net != nullptr) && ((net->users.size() > 2) ||
|
||||
(net->driver.cell != nullptr &&
|
||||
net->driver.cell->type == ctx->id("$nextpnr_obuf") && net->users.size() > 1)))
|
||||
log_error("PACKAGE_PIN of %s '%s' connected to more than a single top level IO.\n", ci->type.c_str(ctx),
|
||||
ci->name.c_str(ctx));
|
||||
}
|
||||
@ -1495,6 +1487,7 @@ bool Arch::pack()
|
||||
promote_globals(ctx);
|
||||
ctx->assignArchInfo();
|
||||
constrain_chains(ctx);
|
||||
ctx->fixupHierarchy();
|
||||
ctx->assignArchInfo();
|
||||
ctx->settings[ctx->id("pack")] = 1;
|
||||
archInfoToAttributes();
|
||||
|
1028
json/jsonparse.cc
1028
json/jsonparse.cc
File diff suppressed because it is too large
Load Diff
@ -73,14 +73,17 @@ struct PortGroup
|
||||
PortType dir;
|
||||
};
|
||||
|
||||
std::vector<PortGroup> group_ports(Context *ctx)
|
||||
std::vector<PortGroup> group_ports(Context *ctx, const std::unordered_map<IdString, PortInfo> &ports,
|
||||
bool is_cell = false)
|
||||
{
|
||||
std::vector<PortGroup> groups;
|
||||
std::unordered_map<std::string, size_t> base_to_group;
|
||||
for (auto &pair : ctx->ports) {
|
||||
for (auto &pair : ports) {
|
||||
std::string name = pair.second.name.str(ctx);
|
||||
if ((name.back() != ']') || (name.find('[') == std::string::npos)) {
|
||||
groups.push_back({name, {pair.first.index}, pair.second.type});
|
||||
groups.push_back({name,
|
||||
{is_cell ? (pair.second.net ? pair.second.net->name.index : -1) : pair.first.index},
|
||||
pair.second.type});
|
||||
} else {
|
||||
int off1 = int(name.find_last_of('['));
|
||||
std::string basename = name.substr(0, off1);
|
||||
@ -95,26 +98,27 @@ std::vector<PortGroup> group_ports(Context *ctx)
|
||||
if (int(grp.bits.size()) <= index)
|
||||
grp.bits.resize(index + 1, -1);
|
||||
NPNR_ASSERT(grp.bits.at(index) == -1);
|
||||
grp.bits.at(index) = pair.second.net ? pair.second.net->name.index : pair.first.index;
|
||||
grp.bits.at(index) = pair.second.net ? pair.second.net->name.index : (is_cell ? -1 : pair.first.index);
|
||||
}
|
||||
}
|
||||
return groups;
|
||||
};
|
||||
}
|
||||
|
||||
std::string format_port_bits(const PortGroup &port)
|
||||
std::string format_port_bits(const PortGroup &port, int &dummy_idx)
|
||||
{
|
||||
std::stringstream s;
|
||||
s << "[ ";
|
||||
bool first = true;
|
||||
for (auto bit : port.bits) {
|
||||
if (!first)
|
||||
s << ", ";
|
||||
if (bit == -1)
|
||||
s << "\"x\"";
|
||||
else
|
||||
s << bit;
|
||||
first = false;
|
||||
}
|
||||
if (port.bits.size() != 1 || port.bits.at(0) != -1) // skip single disconnected ports
|
||||
for (auto bit : port.bits) {
|
||||
if (!first)
|
||||
s << ", ";
|
||||
if (bit == -1)
|
||||
s << (++dummy_idx);
|
||||
else
|
||||
s << bit;
|
||||
first = false;
|
||||
}
|
||||
s << " ]";
|
||||
return s.str();
|
||||
}
|
||||
@ -122,6 +126,7 @@ std::string format_port_bits(const PortGroup &port)
|
||||
void write_module(std::ostream &f, Context *ctx)
|
||||
{
|
||||
auto val = ctx->attrs.find(ctx->id("module"));
|
||||
int dummy_idx = int(ctx->idstring_idx_to_str->size()) + 1000;
|
||||
if (val != ctx->attrs.end())
|
||||
f << stringf(" %s: {\n", get_string(val->second.as_string()).c_str());
|
||||
else
|
||||
@ -134,14 +139,14 @@ void write_module(std::ostream &f, Context *ctx)
|
||||
f << stringf("\n },\n");
|
||||
f << stringf(" \"ports\": {");
|
||||
|
||||
auto ports = group_ports(ctx);
|
||||
auto ports = group_ports(ctx, ctx->ports);
|
||||
bool first = true;
|
||||
for (auto &port : ports) {
|
||||
f << stringf("%s\n", first ? "" : ",");
|
||||
f << stringf(" %s: {\n", get_string(port.name).c_str());
|
||||
f << stringf(" \"direction\": \"%s\",\n",
|
||||
port.dir == PORT_IN ? "input" : port.dir == PORT_INOUT ? "inout" : "output");
|
||||
f << stringf(" \"bits\": %s\n", format_port_bits(port).c_str());
|
||||
f << stringf(" \"bits\": %s\n", format_port_bits(port, dummy_idx).c_str());
|
||||
f << stringf(" }");
|
||||
first = false;
|
||||
}
|
||||
@ -151,6 +156,7 @@ void write_module(std::ostream &f, Context *ctx)
|
||||
first = true;
|
||||
for (auto &pair : ctx->cells) {
|
||||
auto &c = pair.second;
|
||||
auto cell_ports = group_ports(ctx, c->ports, true);
|
||||
f << stringf("%s\n", first ? "" : ",");
|
||||
f << stringf(" %s: {\n", get_name(c->name, ctx).c_str());
|
||||
f << stringf(" \"hide_name\": %s,\n", c->name.c_str(ctx)[0] == '$' ? "1" : "0");
|
||||
@ -163,24 +169,18 @@ void write_module(std::ostream &f, Context *ctx)
|
||||
f << stringf("\n },\n");
|
||||
f << stringf(" \"port_directions\": {");
|
||||
bool first2 = true;
|
||||
for (auto &conn : c->ports) {
|
||||
auto &p = conn.second;
|
||||
std::string direction = (p.type == PORT_IN) ? "input" : (p.type == PORT_OUT) ? "output" : "inout";
|
||||
for (auto &pg : cell_ports) {
|
||||
std::string direction = (pg.dir == PORT_IN) ? "input" : (pg.dir == PORT_OUT) ? "output" : "inout";
|
||||
f << stringf("%s\n", first2 ? "" : ",");
|
||||
f << stringf(" %s: \"%s\"", get_name(conn.first, ctx).c_str(), direction.c_str());
|
||||
f << stringf(" %s: \"%s\"", get_string(pg.name).c_str(), direction.c_str());
|
||||
first2 = false;
|
||||
}
|
||||
f << stringf("\n },\n");
|
||||
f << stringf(" \"connections\": {");
|
||||
first2 = true;
|
||||
for (auto &conn : c->ports) {
|
||||
auto &p = conn.second;
|
||||
for (auto &pg : cell_ports) {
|
||||
f << stringf("%s\n", first2 ? "" : ",");
|
||||
if (p.net)
|
||||
f << stringf(" %s: [ %d ]", get_name(conn.first, ctx).c_str(), p.net->name.index);
|
||||
else
|
||||
f << stringf(" %s: [ ]", get_name(conn.first, ctx).c_str());
|
||||
|
||||
f << stringf(" %s: %s", get_string(pg.name).c_str(), format_port_bits(pg, dummy_idx).c_str());
|
||||
first2 = false;
|
||||
}
|
||||
f << stringf("\n }\n");
|
||||
|
6
python/interactive.py
Normal file
6
python/interactive.py
Normal file
@ -0,0 +1,6 @@
|
||||
# Pass this file to one of the Python script arguments (e.g. --pre-place interactive.py)
|
||||
# to drop to a command-line interactive Python session in the middle of place and route
|
||||
|
||||
import code
|
||||
print("Press Ctrl+D to finish interactive session")
|
||||
code.interact(local=locals())
|
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