diff options
author | David Shah <dave@ds0.me> | 2019-08-08 08:36:37 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-08-08 08:36:37 +0100 |
commit | e0a114fcb3389108bd5821e442b8deb78b532da7 (patch) | |
tree | cbe279b2a938767cbbc15f57d6ff9d940b12dd55 | |
parent | 90364fc3fad72f8c23b7200160f1acc0343c94d9 (diff) | |
parent | bb0b6e85ce713012cd90cd18882c7873888648a1 (diff) | |
download | nextpnr-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.cc | 16 | ||||
-rw-r--r-- | common/timing.cc | 10 | ||||
-rw-r--r-- | ecp5/arch.cc | 8 | ||||
-rw-r--r-- | ecp5/arch_place.cc | 5 | ||||
-rw-r--r-- | ecp5/bitstream.cc | 10 | ||||
-rw-r--r-- | ecp5/cells.cc | 4 | ||||
-rw-r--r-- | ecp5/globals.cc | 6 | ||||
-rw-r--r-- | ecp5/main.cc | 10 | ||||
-rw-r--r-- | ecp5/pack.cc | 8 | ||||
-rw-r--r-- | json/jsonparse.cc | 4 | ||||
-rw-r--r-- | json/jsonwrite.cc | 64 |
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; } |