aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Shah <dave@ds0.me>2019-08-08 08:36:37 +0100
committerGitHub <noreply@github.com>2019-08-08 08:36:37 +0100
commite0a114fcb3389108bd5821e442b8deb78b532da7 (patch)
treecbe279b2a938767cbbc15f57d6ff9d940b12dd55
parent90364fc3fad72f8c23b7200160f1acc0343c94d9 (diff)
parentbb0b6e85ce713012cd90cd18882c7873888648a1 (diff)
downloadnextpnr-e0a114fcb3389108bd5821e442b8deb78b532da7.tar.gz
nextpnr-e0a114fcb3389108bd5821e442b8deb78b532da7.tar.bz2
nextpnr-e0a114fcb3389108bd5821e442b8deb78b532da7.zip
Merge pull request #308 from YosysHQ/ecp5_ooc
Add out-of-context mode to ECP5 architecture
-rw-r--r--common/nextpnr.cc16
-rw-r--r--common/timing.cc10
-rw-r--r--ecp5/arch.cc8
-rw-r--r--ecp5/arch_place.cc5
-rw-r--r--ecp5/bitstream.cc10
-rw-r--r--ecp5/cells.cc4
-rw-r--r--ecp5/globals.cc6
-rw-r--r--ecp5/main.cc10
-rw-r--r--ecp5/pack.cc8
-rw-r--r--json/jsonparse.cc4
-rw-r--r--json/jsonwrite.cc64
11 files changed, 124 insertions, 21 deletions
diff --git a/common/nextpnr.cc b/common/nextpnr.cc
index 0d89b55a..ab4601a6 100644
--- a/common/nextpnr.cc
+++ b/common/nextpnr.cc
@@ -294,6 +294,9 @@ delay_t Context::getNetinfoRouteDelay(const NetInfo *net_info, const PortRef &us
break;
PipId pip = it->second.pip;
+ if (pip == PipId())
+ break;
+
delay += getPipDelay(pip).maxDelay();
delay += getWireDelay(cursor).maxDelay();
cursor = getPipSrcWire(pip);
@@ -571,6 +574,16 @@ void BaseCtx::attributesToArchInfo()
BelId b = getCtx()->getBelByName(id(val->second.as_string()));
getCtx()->bindBel(b, ci, strength);
}
+
+ val = ci->attrs.find(id("CONSTR_PARENT"));
+ if (val != ci->attrs.end()) {
+ auto parent = cells.find(id(val->second.str));
+ if (parent != cells.end())
+ ci->constr_parent = parent->second.get();
+ else
+ continue;
+ }
+
val = ci->attrs.find(id("CONSTR_X"));
if (val != ci->attrs.end())
ci->constr_x = val->second.as_int64();
@@ -599,7 +612,8 @@ void BaseCtx::attributesToArchInfo()
auto children = val->second.as_string();
boost::split(strs, children, boost::is_any_of(";"));
for (auto val : strs) {
- ci->constr_children.push_back(cells.find(id(val.c_str()))->second.get());
+ if (cells.count(id(val.c_str())))
+ ci->constr_children.push_back(cells.find(id(val.c_str()))->second.get());
}
}
}
diff --git a/common/timing.cc b/common/timing.cc
index 599d6dbd..37600c8c 100644
--- a/common/timing.cc
+++ b/common/timing.cc
@@ -185,8 +185,16 @@ struct Timing
}
}
- std::deque<NetInfo *> queue(topographical_order.begin(), topographical_order.end());
+ // In out-of-context mode, handle top-level ports correctly
+ if (bool_or_default(ctx->settings, ctx->id("arch.ooc"))) {
+ for (auto &p : ctx->ports) {
+ if (p.second.type != PORT_IN || p.second.net == nullptr)
+ continue;
+ topographical_order.emplace_back(p.second.net);
+ }
+ }
+ std::deque<NetInfo *> queue(topographical_order.begin(), topographical_order.end());
// Now walk the design, from the start points identified previously, building up a topographical order
while (!queue.empty()) {
const auto net = queue.front();
diff --git a/ecp5/arch.cc b/ecp5/arch.cc
index 4be9833e..9f137b9b 100644
--- a/ecp5/arch.cc
+++ b/ecp5/arch.cc
@@ -524,9 +524,15 @@ bool Arch::place()
} else {
log_error("ECP5 architecture does not support placer '%s'\n", placer.c_str());
}
-
permute_luts();
+
+ // In out-of-context mode, create a locked macro
+ if (bool_or_default(settings, id("arch.ooc")))
+ for (auto &cell : cells)
+ cell.second->belStrength = STRENGTH_LOCKED;
+
getCtx()->settings[getCtx()->id("place")] = 1;
+
archInfoToAttributes();
return true;
}
diff --git a/ecp5/arch_place.cc b/ecp5/arch_place.cc
index b5c11851..18374c07 100644
--- a/ecp5/arch_place.cc
+++ b/ecp5/arch_place.cc
@@ -152,7 +152,10 @@ void Arch::permute_luts()
inputs.emplace_back(crit, i);
}
// Least critical first (A input is slowest)
- std::sort(inputs.begin(), inputs.end());
+
+ // Avoid permuting locked LUTs (e.g. from an OOC submodule)
+ if (ci->belStrength <= STRENGTH_STRONG)
+ std::sort(inputs.begin(), inputs.end());
for (int i = 0; i < 4; i++) {
IdString p = port_names.at(i);
// log_info("%s %s %f\n", p.c_str(ctx), port_names.at(inputs.at(i).second).c_str(ctx), inputs.at(i).first);
diff --git a/ecp5/bitstream.cc b/ecp5/bitstream.cc
index a4b345e6..1e0dcadc 100644
--- a/ecp5/bitstream.cc
+++ b/ecp5/bitstream.cc
@@ -789,15 +789,13 @@ void write_bitstream(Context *ctx, std::string base_config_file, std::string tex
if (str_or_default(ci->params, ctx->id("MODE"), "LOGIC") == "CCU2") {
cc.tiles[tname].add_enum(slice + ".CCU2.INJECT1_0",
- str_or_default(ci->params, ctx->id("INJECT1_0"), "YES"));
+ str_or_default(ci->params, ctx->id("CCU2_INJECT1_0"), "YES"));
cc.tiles[tname].add_enum(slice + ".CCU2.INJECT1_1",
- str_or_default(ci->params, ctx->id("INJECT1_1"), "YES"));
+ str_or_default(ci->params, ctx->id("CCU2_INJECT1_1"), "YES"));
} else {
// Don't interfere with cascade mux wiring
- cc.tiles[tname].add_enum(slice + ".CCU2.INJECT1_0",
- str_or_default(ci->params, ctx->id("INJECT1_0"), "_NONE_"));
- cc.tiles[tname].add_enum(slice + ".CCU2.INJECT1_1",
- str_or_default(ci->params, ctx->id("INJECT1_1"), "_NONE_"));
+ cc.tiles[tname].add_enum(slice + ".CCU2.INJECT1_0", "_NONE_");
+ cc.tiles[tname].add_enum(slice + ".CCU2.INJECT1_1", "_NONE_");
}
if (str_or_default(ci->params, ctx->id("MODE"), "LOGIC") == "DPRAM" && slice == "SLICEA") {
diff --git a/ecp5/cells.cc b/ecp5/cells.cc
index 46f84d97..37b6ac8b 100644
--- a/ecp5/cells.cc
+++ b/ecp5/cells.cc
@@ -274,8 +274,8 @@ void ccu2c_to_slice(Context *ctx, CellInfo *ccu, CellInfo *lc)
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));
- lc->params[ctx->id("INJECT1_0")] = str_or_default(ccu->params, ctx->id("INJECT1_0"), "YES");
- lc->params[ctx->id("INJECT1_1")] = str_or_default(ccu->params, ctx->id("INJECT1_1"), "YES");
+ lc->params[ctx->id("CCU2_INJECT1_0")] = str_or_default(ccu->params, ctx->id("INJECT1_0"), "YES");
+ lc->params[ctx->id("CCU2_INJECT1_1")] = str_or_default(ccu->params, ctx->id("INJECT1_1"), "YES");
replace_port(ccu, ctx->id("CIN"), lc, ctx->id("FCI"));
diff --git a/ecp5/globals.cc b/ecp5/globals.cc
index 5fb348e7..9dd4449b 100644
--- a/ecp5/globals.cc
+++ b/ecp5/globals.cc
@@ -431,11 +431,15 @@ class Ecp5GlobalRouter
public:
void promote_globals()
{
+ bool is_ooc = bool_or_default(ctx->settings, ctx->id("arch.ooc"));
log_info("Promoting globals...\n");
auto clocks = get_clocks();
for (auto clock : clocks) {
log_info(" promoting clock net %s to global network\n", clock->name.c_str(ctx));
- insert_dcc(clock);
+ if (is_ooc) // Don't actually do anything in OOC mode, global routing will be done in the full design
+ clock->is_global = true;
+ else
+ insert_dcc(clock);
}
}
diff --git a/ecp5/main.cc b/ecp5/main.cc
index 5544c55f..4ae0e27d 100644
--- a/ecp5/main.cc
+++ b/ecp5/main.cc
@@ -71,6 +71,10 @@ po::options_description ECP5CommandHandler::getArchOptions()
specific.add_options()("lpf", po::value<std::vector<std::string>>(), "LPF pin constraint file(s)");
specific.add_options()("lpf-allow-unconstrained", "don't require LPF file(s) to constrain all IO");
+ specific.add_options()(
+ "out-of-context",
+ "disable IO buffer insertion and global promotion/routing, for building pre-routed blocks (experimental)");
+
return specific;
}
void ECP5CommandHandler::validate()
@@ -91,6 +95,10 @@ void ECP5CommandHandler::customBitstream(Context *ctx)
basecfg = vm["basecfg"].as<std::string>();
}
+ if (bool_or_default(ctx->settings, ctx->id("arch.ooc")) && vm.count("textcfg"))
+ log_error("bitstream generation is not available in out-of-context mode (use --write to create a post-PnR JSON "
+ "design)\n");
+
std::string textcfg;
if (vm.count("textcfg"))
textcfg = vm["textcfg"].as<std::string>();
@@ -228,6 +236,8 @@ std::unique_ptr<Context> ECP5CommandHandler::createContext(std::unordered_map<st
ctx->settings[ctx->id(val.first)] = val.second;
ctx->settings[ctx->id("arch.package")] = ctx->archArgs().package;
ctx->settings[ctx->id("arch.speed")] = speedString(ctx->archArgs().speed);
+ if (vm.count("out-of-context"))
+ ctx->settings[ctx->id("arch.ooc")] = 1;
return ctx;
}
diff --git a/ecp5/pack.cc b/ecp5/pack.cc
index a9416f32..7cf9df78 100644
--- a/ecp5/pack.cc
+++ b/ecp5/pack.cc
@@ -356,7 +356,12 @@ class Ecp5Packer
ionet = ci->ports.at(ctx->id("I")).net;
trio = net_only_drives(ctx, ci->ports.at(ctx->id("I")).net, is_trellis_io, ctx->id("B"), true, ci);
}
- if (trio != nullptr) {
+ if (bool_or_default(ctx->settings, ctx->id("arch.ooc"))) {
+ // No IO buffer insertion in out-of-context mode, just remove the nextpnr buffer
+ // and leave the top level port
+ 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
log_info("%s feeds TRELLIS_IO %s, removing %s %s.\n", ci->name.c_str(ctx), trio->name.c_str(ctx),
@@ -673,6 +678,7 @@ class Ecp5Packer
CellInfo *make_carry_feed_out(NetInfo *carry, boost::optional<PortRef> chain_next = boost::optional<PortRef>())
{
std::unique_ptr<CellInfo> feedout = create_ecp5_cell(ctx, ctx->id("CCU2C"));
+
feedout->params[ctx->id("INIT0")] = Property(0, 16);
feedout->params[ctx->id("INIT1")] = Property(10, 16); // LUT4 = 0; LUT2 = A
feedout->params[ctx->id("INJECT1_0")] = std::string("NO");
diff --git a/json/jsonparse.cc b/json/jsonparse.cc
index b3bb491b..ad21daf2 100644
--- a/json/jsonparse.cc
+++ b/json/jsonparse.cc
@@ -733,10 +733,10 @@ static void insert_iobuf(Context *ctx, NetInfo *net, PortType type, const string
}
PortInfo pinfo;
- pinfo.name = net->name;
+ pinfo.name = ctx->id(name);
pinfo.net = net;
pinfo.type = type;
- ctx->ports[net->name] = pinfo;
+ ctx->ports[pinfo.name] = pinfo;
}
void json_import_toplevel_port(Context *ctx, const string &modname, const std::vector<IdString> &netnames,
diff --git a/json/jsonwrite.cc b/json/jsonwrite.cc
index 46cb0b72..3d3b70e4 100644
--- a/json/jsonwrite.cc
+++ b/json/jsonwrite.cc
@@ -66,6 +66,59 @@ void write_parameters(std::ostream &f, Context *ctx, const std::unordered_map<Id
}
}
+struct PortGroup
+{
+ std::string name;
+ std::vector<int> bits;
+ PortType dir;
+};
+
+std::vector<PortGroup> group_ports(Context *ctx)
+{
+ std::vector<PortGroup> groups;
+ std::unordered_map<std::string, size_t> base_to_group;
+ for (auto &pair : ctx->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});
+ } else {
+ int off1 = int(name.find_last_of('['));
+ std::string basename = name.substr(0, off1);
+ int index = std::stoi(name.substr(off1 + 1, name.size() - (off1 + 2)));
+
+ if (!base_to_group.count(basename)) {
+ base_to_group[basename] = groups.size();
+ groups.push_back({basename, std::vector<int>(index + 1, -1), pair.second.type});
+ }
+
+ auto &grp = groups.at(base_to_group[basename]);
+ 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;
+ }
+ }
+ return groups;
+};
+
+std::string format_port_bits(const PortGroup &port)
+{
+ 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;
+ }
+ s << " ]";
+ return s.str();
+}
+
void write_module(std::ostream &f, Context *ctx)
{
auto val = ctx->attrs.find(ctx->id("module"));
@@ -80,14 +133,15 @@ void write_module(std::ostream &f, Context *ctx)
write_parameters(f, ctx, ctx->attrs, true);
f << stringf("\n },\n");
f << stringf(" \"ports\": {");
+
+ auto ports = group_ports(ctx);
bool first = true;
- for (auto &pair : ctx->ports) {
- auto &c = pair.second;
+ for (auto &port : ports) {
f << stringf("%s\n", first ? "" : ",");
- f << stringf(" %s: {\n", get_name(c.name, ctx).c_str());
+ f << stringf(" %s: {\n", get_string(port.name).c_str());
f << stringf(" \"direction\": \"%s\",\n",
- c.type == PORT_IN ? "input" : c.type == PORT_INOUT ? "inout" : "output");
- f << stringf(" \"bits\": [ %d ]\n", pair.first.index);
+ port.dir == PORT_IN ? "input" : port.dir == PORT_INOUT ? "inout" : "output");
+ f << stringf(" \"bits\": %s\n", format_port_bits(port).c_str());
f << stringf(" }");
first = false;
}