Merge remote-tracking branch 'origin/master' into mmicko/ecp5_gui

This commit is contained in:
Miodrag Milanovic 2019-12-28 13:54:06 +01:00
commit 796d648995
71 changed files with 3391 additions and 1805 deletions

View File

@ -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
View File

@ -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
View 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
View 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
View 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

View File

@ -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)

View File

@ -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++`

View File

@ -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)

View File

@ -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);
}

View File

@ -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");

View File

@ -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());

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -308,6 +308,14 @@ class HeAPPlacer
std::vector<std::vector<int>> nearest_row_with_bel;
std::vector<std::vector<int>> nearest_col_with_bel;
struct BoundingBox
{
// Actual bounding box
int x0 = 0, x1 = 0, y0 = 0, y1 = 0;
};
std::unordered_map<IdString, BoundingBox> constraint_region_bounds;
// In some cases, we can't use bindBel because we allow overlap in the earlier stages. So we use this custom
// structure instead
struct CellLocation
@ -443,6 +451,31 @@ class HeAPPlacer
nr.at(y) = loc.y;
}
}
// Determine bounding boxes of region constraints
for (auto &region : sorted(ctx->region)) {
Region *r = region.second;
BoundingBox bb;
if (r->constr_bels) {
bb.x0 = std::numeric_limits<int>::max();
bb.x1 = std::numeric_limits<int>::min();
bb.y0 = std::numeric_limits<int>::max();
bb.y1 = std::numeric_limits<int>::min();
for (auto bel : r->bels) {
Loc loc = ctx->getBelLocation(bel);
bb.x0 = std::min(bb.x0, loc.x);
bb.x1 = std::max(bb.x1, loc.x);
bb.y0 = std::min(bb.y0, loc.y);
bb.y1 = std::max(bb.y1, loc.y);
}
} else {
bb.x0 = 0;
bb.y0 = 0;
bb.x1 = max_x;
bb.y1 = max_y;
}
constraint_region_bounds[r->name] = bb;
}
}
// Build and solve in one direction
@ -684,9 +717,15 @@ class HeAPPlacer
if (yaxis) {
cell_locs.at(solve_cells.at(i)->name).rawy = vals.at(i);
cell_locs.at(solve_cells.at(i)->name).y = std::min(max_y, std::max(0, int(vals.at(i))));
if (solve_cells.at(i)->region != nullptr)
cell_locs.at(solve_cells.at(i)->name).y =
limit_to_reg(solve_cells.at(i)->region, cell_locs.at(solve_cells.at(i)->name).y, true);
} else {
cell_locs.at(solve_cells.at(i)->name).rawx = vals.at(i);
cell_locs.at(solve_cells.at(i)->name).x = std::min(max_x, std::max(0, int(vals.at(i))));
if (solve_cells.at(i)->region != nullptr)
cell_locs.at(solve_cells.at(i)->name).x =
limit_to_reg(solve_cells.at(i)->region, cell_locs.at(solve_cells.at(i)->name).x, false);
}
}
@ -735,6 +774,7 @@ class HeAPPlacer
}
int ripup_radius = 2;
int total_iters = 0;
int total_iters_noreset = 0;
while (!remaining.empty()) {
auto top = remaining.top();
remaining.pop();
@ -754,15 +794,38 @@ class HeAPPlacer
int best_inp_len = std::numeric_limits<int>::max();
total_iters++;
total_iters_noreset++;
if (total_iters > int(solve_cells.size())) {
total_iters = 0;
ripup_radius = std::max(std::max(max_x, max_y), ripup_radius * 2);
}
if (total_iters_noreset > std::max(5000, 8 * int(ctx->cells.size()))) {
log_error("Unable to find legal placement for all cells, design is probably at utilisation limit.\n");
}
while (!placed) {
int nx = ctx->rng(2 * radius + 1) + std::max(cell_locs.at(ci->name).x - radius, 0);
int ny = ctx->rng(2 * radius + 1) + std::max(cell_locs.at(ci->name).y - radius, 0);
// Set a conservative timeout
if (iter > std::max(1000, 3 * int(ctx->cells.size())))
log_error("Unable to find legal placement for cell '%s', check constraints and utilisation.\n",
ctx->nameOf(ci));
int rx = radius, ry = radius;
if (ci->region != nullptr) {
rx = std::min(radius, (constraint_region_bounds[ci->region->name].x1 -
constraint_region_bounds[ci->region->name].x0) /
2 +
1);
ry = std::min(radius, (constraint_region_bounds[ci->region->name].y1 -
constraint_region_bounds[ci->region->name].y0) /
2 +
1);
}
int nx = ctx->rng(2 * rx + 1) + std::max(cell_locs.at(ci->name).x - rx, 0);
int ny = ctx->rng(2 * ry + 1) + std::max(cell_locs.at(ci->name).y - ry, 0);
iter++;
iter_at_radius++;
@ -820,6 +883,8 @@ class HeAPPlacer
if (ci->constr_children.empty() && !ci->constr_abs_z) {
for (auto sz : fb.at(nx).at(ny)) {
if (ci->region != nullptr && ci->region->constr_bels && !ci->region->bels.count(sz))
continue;
if (ctx->checkBelAvail(sz) || (radius > ripup_radius || ctx->rng(20000) < 10)) {
CellInfo *bound = ctx->getBoundBelCell(sz);
if (bound != nullptr) {
@ -881,6 +946,8 @@ class HeAPPlacer
Loc ploc = visit.front().second;
visit.pop();
BelId target = ctx->getBelByLocation(ploc);
if (vc->region != nullptr && vc->region->constr_bels && !vc->region->bels.count(target))
goto fail;
CellInfo *bound;
if (target == BelId() || ctx->getBelType(target) != vc->type)
goto fail;
@ -948,6 +1015,15 @@ class HeAPPlacer
// Implementation of the cut-based spreading as described in the HeAP/SimPL papers
static constexpr float beta = 0.9;
template <typename T> T limit_to_reg(Region *reg, T val, bool dir)
{
if (reg == nullptr)
return val;
int limit_low = dir ? constraint_region_bounds[reg->name].y0 : constraint_region_bounds[reg->name].x0;
int limit_high = dir ? constraint_region_bounds[reg->name].y1 : constraint_region_bounds[reg->name].x1;
return std::max<T>(std::min<T>(val, limit_high), limit_low);
}
struct ChainExtent
{
int x0, y0, x1, y1;
@ -1460,10 +1536,22 @@ class HeAPPlacer
: p->cell_locs.at(cut_cells.at(br.first - 1)->name).rawx;
double m = (br.second - bl.second) / std::max(0.00001, orig_right - orig_left);
for (int j = bl.first; j < br.first; j++) {
auto &pos = dir ? p->cell_locs.at(cut_cells.at(j)->name).rawy
: p->cell_locs.at(cut_cells.at(j)->name).rawx;
NPNR_ASSERT(pos >= orig_left && pos <= orig_right);
pos = bl.second + m * (pos - orig_left);
Region *cr = cut_cells.at(j)->region;
if (cr != nullptr) {
// Limit spreading bounds to constraint region; if applicable
double brsc = p->limit_to_reg(cr, br.second, dir);
double blsc = p->limit_to_reg(cr, bl.second, dir);
double mr = (brsc - blsc) / std::max(0.00001, orig_right - orig_left);
auto &pos = dir ? p->cell_locs.at(cut_cells.at(j)->name).rawy
: p->cell_locs.at(cut_cells.at(j)->name).rawx;
NPNR_ASSERT(pos >= orig_left && pos <= orig_right);
pos = blsc + mr * (pos - orig_left);
} else {
auto &pos = dir ? p->cell_locs.at(cut_cells.at(j)->name).rawy
: p->cell_locs.at(cut_cells.at(j)->name).rawx;
NPNR_ASSERT(pos >= orig_left && pos <= orig_right);
pos = bl.second + m * (pos - orig_left);
}
// log("[%f, %f] -> [%f, %f]: %f -> %f\n", orig_left, orig_right, bl.second, br.second,
// orig_pos, pos);
}

View File

@ -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
View 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

View File

@ -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) &&

View File

@ -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

View File

@ -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.

View File

@ -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.

View File

@ -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);

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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; });

View File

@ -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");

View File

@ -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"));

View File

@ -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)

View File

@ -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

View File

@ -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]);

View File

@ -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();

View File

@ -1,3 +0,0 @@
*.bit
*_out.config

View File

@ -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

View File

@ -1,2 +0,0 @@
read_verilog blinky.v
synth_ecp5 -noccu2 -nomux -nodram -json blinky.json

View File

@ -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

View File

@ -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
View 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
View 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 &params = obj["parameters"];
if (params.is_null())
return;
for (const auto &param : params.object_items()) {
Func(param.first, parse_property(param.second));
}
}
template <typename TFunc> void foreach_setting(const Json &obj, TFunc Func) const
{
const auto &settings = obj["settings"];
if (settings.is_null())
return;
for (const auto &setting : settings.object_items()) {
Func(setting.first, parse_property(setting.second));
}
}
template <typename TFunc> void foreach_port_dir(const CellDataType &cell, TFunc Func) const
{
for (const auto &pdir : cell["port_directions"].object_items())
Func(pdir.first, lookup_portdir(pdir.second.string_value()));
}
template <typename TFunc> void foreach_port_conn(const CellDataType &cell, TFunc Func) const
{
for (const auto &pconn : cell["connections"].object_items())
Func(pconn.first, pconn.second.array_items());
}
const BitVectorDataType &get_net_bits(const NetnameDataType &net) const { return net["bits"].array_items(); }
int get_vector_length(const BitVectorDataType &bits) const { return int(bits.size()); }
bool is_vector_bit_constant(const BitVectorDataType &bits, int i) const
{
NPNR_ASSERT(i < int(bits.size()));
return bits[i].is_string();
}
char get_vector_bit_constval(const BitVectorDataType &bits, int i) const
{
auto s = bits.at(i).string_value();
NPNR_ASSERT(s.size() == 1);
return s.at(0);
}
int get_vector_bit_signal(const BitVectorDataType &bits, int i) const
{
NPNR_ASSERT(bits.at(i).is_number());
return bits.at(i).int_value();
}
};
bool parse_json(std::istream &in, const std::string &filename, Context *ctx)
{
Json root;
{
if (!in)
log_error("Failed to open JSON file '%s'.\n", filename.c_str());
std::string json_str((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>());
std::string error;
root = Json::parse(json_str, error, JsonParse::COMMENTS);
if (root.is_null())
log_error("Failed to parse JSON file '%s': %s.\n", filename.c_str(), error.c_str());
root = root["modules"];
if (root.is_null())
log_error("JSON file '%s' doesn't look like a netlist (doesn't contain \"modules\" key)\n",
filename.c_str());
}
GenericFrontend<JsonFrontendImpl>(ctx, JsonFrontendImpl(root))();
return true;
}
NEXTPNR_NAMESPACE_END

View File

@ -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

View File

@ -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) {

View File

@ -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;

View File

@ -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>);
}

View File

@ -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]"));
}

View File

@ -1,3 +1,6 @@
blinky.fasm
__pycache__
*.pyc
pnrblinky.v
/blinky_simtest
*.vcd

View File

@ -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)

View File

@ -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

View 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

View File

@ -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)

View File

@ -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"

View File

@ -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
View 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

View File

@ -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[])

View File

@ -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 =

View File

@ -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

View File

@ -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

View File

@ -28,7 +28,6 @@
#include <fstream>
#include "designwidget.h"
#include "fpgaviewwidget.h"
#include "jsonparse.h"
#include "jsonwrite.h"
#include "log.h"
#include "mainwindow.h"

View File

@ -27,7 +27,6 @@
#include <fstream>
#include "bitstream.h"
#include "design_utils.h"
#include "jsonparse.h"
#include "log.h"
#include "pcf.h"

View File

@ -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

View File

@ -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

View File

@ -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
{

View File

@ -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",

View File

@ -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"));

View File

@ -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})

View File

@ -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"

View File

@ -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();

File diff suppressed because it is too large Load Diff

View File

@ -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
View 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())

View 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])