diff options
-rw-r--r-- | .cirrus.yml | 3 | ||||
-rw-r--r-- | common/nextpnr.cc | 92 | ||||
-rw-r--r-- | common/nextpnr.h | 13 | ||||
-rw-r--r-- | common/router1.cc | 140 | ||||
-rw-r--r-- | common/router2.cc | 180 | ||||
-rw-r--r-- | common/timing.cc | 2 | ||||
-rw-r--r-- | docs/archapi.md | 7 | ||||
-rw-r--r-- | docs/netlist.md | 1 | ||||
-rw-r--r-- | fpga_interchange/arch.cc | 18 | ||||
-rw-r--r-- | fpga_interchange/arch.h | 8 | ||||
-rw-r--r-- | fpga_interchange/family.cmake | 9 | ||||
-rw-r--r-- | fpga_interchange/main.cc | 22 | ||||
-rw-r--r-- | fpga_interchange/xdc.cc | 152 | ||||
-rw-r--r-- | fpga_interchange/xdc.h | 33 | ||||
-rw-r--r-- | frontend/frontend_base.h | 42 | ||||
-rw-r--r-- | frontend/json_frontend.cc | 2 | ||||
-rw-r--r-- | generic/arch.cc | 2 | ||||
-rw-r--r-- | generic/arch.h | 2 | ||||
-rw-r--r-- | gowin/arch.cc | 2 | ||||
-rw-r--r-- | gowin/arch.h | 2 | ||||
-rw-r--r-- | nexus/global.cc | 2 | ||||
m--------- | tests | 0 |
22 files changed, 538 insertions, 196 deletions
diff --git a/.cirrus.yml b/.cirrus.yml index 0c2d8b78..f7181fd3 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -5,12 +5,13 @@ task: memory: 20 dockerfile: .cirrus/Dockerfile.ubuntu20.04 - build_script: mkdir build && cd build && cmake .. -DARCH=all+alpha -DOXIDE_INSTALL_PREFIX=$HOME/.cargo -DBUILD_TESTS=on -DBUILD_GUI=on && make -j3 submodule_script: git submodule sync --recursive && git submodule update --init --recursive + build_script: mkdir build && cd build && cmake .. -DARCH=all+alpha -DOXIDE_INSTALL_PREFIX=$HOME/.cargo -DBUILD_TESTS=on -DBUILD_GUI=on && make -j3 test_generic_script: cd build && ./nextpnr-generic-test 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 + test_fpga_interchange_script: cd build && ./nextpnr-fpga_interchange-test 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 diff --git a/common/nextpnr.cc b/common/nextpnr.cc index 880e0344..11acf991 100644 --- a/common/nextpnr.cc +++ b/common/nextpnr.cc @@ -354,19 +354,59 @@ WireId Context::getNetinfoSourceWire(const NetInfo *net_info) const if (src_bel == BelId()) return WireId(); - IdString driver_port = net_info->driver.port; - return getBelPinWire(src_bel, driver_port); + auto bel_pins = getBelPinsForCellPin(net_info->driver.cell, net_info->driver.port); + auto iter = bel_pins.begin(); + if (iter == bel_pins.end()) + return WireId(); + WireId driver = getBelPinWire(src_bel, *iter); + ++iter; + NPNR_ASSERT(iter == bel_pins.end()); // assert there is only one driver bel pin; + return driver; } -WireId Context::getNetinfoSinkWire(const NetInfo *net_info, const PortRef &user_info) const +SSOArray<WireId, 2> Context::getNetinfoSinkWires(const NetInfo *net_info, const PortRef &user_info) const { auto dst_bel = user_info.cell->bel; - if (dst_bel == BelId()) - return WireId(); + return SSOArray<WireId, 2>(0, WireId()); + size_t bel_pin_count = 0; + // We use an SSOArray here because it avoids any heap allocation for the 99.9% case of 1 or 2 sink wires + // but as SSOArray doesn't (currently) support resizing to keep things simple it does mean we have to do + // two loops + for (auto s : getBelPinsForCellPin(user_info.cell, user_info.port)) { + (void)s; // unused + ++bel_pin_count; + } + SSOArray<WireId, 2> result(bel_pin_count, WireId()); + bel_pin_count = 0; + for (auto pin : getBelPinsForCellPin(user_info.cell, user_info.port)) { + result[bel_pin_count++] = getBelPinWire(dst_bel, pin); + } + return result; +} - IdString user_port = user_info.port; - return getBelPinWire(dst_bel, user_port); +size_t Context::getNetinfoSinkWireCount(const NetInfo *net_info, const PortRef &sink) const +{ + size_t count = 0; + for (auto s : getNetinfoSinkWires(net_info, sink)) { + (void)s; // unused + ++count; + } + return count; +} + +WireId Context::getNetinfoSinkWire(const NetInfo *net_info, const PortRef &sink, size_t phys_idx) const +{ + size_t count = 0; + for (auto s : getNetinfoSinkWires(net_info, sink)) { + if (count == phys_idx) + return s; + ++count; + } + /* TODO: This should be an assertion failure, but for the zero-wire case of unplaced sinks; legacy code currently + assumes WireId Remove once the refactoring process is complete. + */ + return WireId(); } delay_t Context::getNetinfoRouteDelay(const NetInfo *net_info, const PortRef &user_info) const @@ -383,29 +423,33 @@ delay_t Context::getNetinfoRouteDelay(const NetInfo *net_info, const PortRef &us if (src_wire == WireId()) return 0; - WireId dst_wire = getNetinfoSinkWire(net_info, user_info); - WireId cursor = dst_wire; - delay_t delay = 0; + delay_t max_delay = 0; - while (cursor != WireId() && cursor != src_wire) { - auto it = net_info->wires.find(cursor); + for (auto dst_wire : getNetinfoSinkWires(net_info, user_info)) { + WireId cursor = dst_wire; + delay_t delay = 0; - if (it == net_info->wires.end()) - break; + while (cursor != WireId() && cursor != src_wire) { + auto it = net_info->wires.find(cursor); - PipId pip = it->second.pip; - if (pip == PipId()) - break; + if (it == net_info->wires.end()) + break; - delay += getPipDelay(pip).maxDelay(); - delay += getWireDelay(cursor).maxDelay(); - cursor = getPipSrcWire(pip); - } + PipId pip = it->second.pip; + if (pip == PipId()) + break; - if (cursor == src_wire) - return delay + getWireDelay(src_wire).maxDelay(); + delay += getPipDelay(pip).maxDelay(); + delay += getWireDelay(cursor).maxDelay(); + cursor = getPipSrcWire(pip); + } - return predictDelay(net_info, user_info); + if (cursor == src_wire) + max_delay = std::max(max_delay, delay + getWireDelay(src_wire).maxDelay()); // routed + else + max_delay = std::max(max_delay, predictDelay(net_info, user_info)); // unrouted + } + return max_delay; } static uint32_t xorshift32(uint32_t x) diff --git a/common/nextpnr.h b/common/nextpnr.h index 8d6ab4cc..b6ee33fe 100644 --- a/common/nextpnr.h +++ b/common/nextpnr.h @@ -833,6 +833,7 @@ struct BaseCtx // Top-level ports std::unordered_map<IdString, PortInfo> ports; + std::unordered_map<IdString, CellInfo *> port_cells; // Floorplanning regions std::unordered_map<IdString, std::unique_ptr<Region>> region; @@ -1099,6 +1100,7 @@ template <typename R> struct ArchAPI : BaseCtx virtual WireId getBelPinWire(BelId bel, IdString pin) const = 0; virtual PortType getBelPinType(BelId bel, IdString pin) const = 0; virtual typename R::BelPinsRangeT getBelPins(BelId bel) const = 0; + virtual typename R::CellBelPinRangeT getBelPinsForCellPin(CellInfo *cell_info, IdString pin) const = 0; // Wire methods virtual typename R::AllWiresRangeT getWires() const = 0; virtual WireId getWireByName(IdStringList name) const = 0; @@ -1182,6 +1184,8 @@ template <typename R> struct ArchAPI : BaseCtx // This contains the relevant range types for the default implementations of Arch functions struct BaseArchRanges { + // Bels + using CellBelPinRangeT = std::array<IdString, 1>; // Attributes using BelAttrsRangeT = std::vector<std::pair<IdString, std::string>>; using WireAttrsRangeT = std::vector<std::pair<IdString, std::string>>; @@ -1249,6 +1253,11 @@ template <typename R> struct BaseArch : ArchAPI<R> return empty_if_possible<typename R::BelAttrsRangeT>(); } + virtual typename R::CellBelPinRangeT getBelPinsForCellPin(CellInfo *cell_info, IdString pin) const override + { + return return_if_match<std::array<IdString, 1>, typename R::CellBelPinRangeT>({pin}); + } + // Wire methods virtual IdString getWireType(WireId wire) const override { return IdString(); } virtual typename R::WireAttrsRangeT getWireAttrs(WireId) const override @@ -1494,7 +1503,9 @@ struct Context : Arch, DeterministicRNG // -------------------------------------------------------------- WireId getNetinfoSourceWire(const NetInfo *net_info) const; - WireId getNetinfoSinkWire(const NetInfo *net_info, const PortRef &sink) const; + SSOArray<WireId, 2> getNetinfoSinkWires(const NetInfo *net_info, const PortRef &sink) const; + size_t getNetinfoSinkWireCount(const NetInfo *net_info, const PortRef &sink) const; + WireId getNetinfoSinkWire(const NetInfo *net_info, const PortRef &sink, size_t phys_idx) const; delay_t getNetinfoRouteDelay(const NetInfo *net_info, const PortRef &sink) const; // provided by router1.cc diff --git a/common/router1.cc b/common/router1.cc index d2816c1e..efc06b06 100644 --- a/common/router1.cc +++ b/common/router1.cc @@ -32,12 +32,20 @@ USING_NEXTPNR_NAMESPACE struct arc_key { NetInfo *net_info; + // logical user cell port index int user_idx; + // physical index into cell->bel pin mapping (usually 0) + unsigned phys_idx; - bool operator==(const arc_key &other) const { return (net_info == other.net_info) && (user_idx == other.user_idx); } + bool operator==(const arc_key &other) const + { + return (net_info == other.net_info) && (user_idx == other.user_idx) && (phys_idx == other.phys_idx); + } bool operator<(const arc_key &other) const { - return net_info == other.net_info ? user_idx < other.user_idx : net_info->name < other.net_info->name; + return net_info == other.net_info + ? (user_idx == other.user_idx ? phys_idx < other.phys_idx : user_idx < other.user_idx) + : net_info->name < other.net_info->name; } struct Hash @@ -46,6 +54,7 @@ struct arc_key { std::size_t seed = std::hash<NetInfo *>()(arg.net_info); seed ^= std::hash<int>()(arg.user_idx) + 0x9e3779b9 + (seed << 6) + (seed >> 2); + seed ^= std::hash<int>()(arg.phys_idx) + 0x9e3779b9 + (seed << 6) + (seed >> 2); return seed; } }; @@ -142,9 +151,10 @@ struct Router1 NetInfo *net_info = arc.net_info; int user_idx = arc.user_idx; + unsigned phys_idx = arc.phys_idx; auto src_wire = ctx->getNetinfoSourceWire(net_info); - auto dst_wire = ctx->getNetinfoSinkWire(net_info, net_info->users[user_idx]); + auto dst_wire = ctx->getNetinfoSinkWire(net_info, net_info->users[user_idx], phys_idx); arc_queue_insert(arc, src_wire, dst_wire); } @@ -302,27 +312,29 @@ struct Router1 log_assert(src_wire != WireId()); for (int user_idx = 0; user_idx < int(net_info->users.size()); user_idx++) { - auto dst_wire = ctx->getNetinfoSinkWire(net_info, net_info->users[user_idx]); - log_assert(dst_wire != WireId()); - - arc_key arc; - arc.net_info = net_info; - arc.user_idx = user_idx; - - valid_arcs.insert(arc); + unsigned phys_idx = 0; + for (auto dst_wire : ctx->getNetinfoSinkWires(net_info, net_info->users[user_idx])) { + log_assert(dst_wire != WireId()); + + arc_key arc; + arc.net_info = net_info; + arc.user_idx = user_idx; + arc.phys_idx = phys_idx++; + valid_arcs.insert(arc); #if 0 - if (ctx->debug) + if (ctx->debug) log("[check] arc: %s %s\n", ctx->nameOfWire(src_wire), ctx->nameOfWire(dst_wire)); #endif - for (WireId wire : arc_to_wires[arc]) { + for (WireId wire : arc_to_wires[arc]) { #if 0 - if (ctx->debug) + if (ctx->debug) log("[check] wire: %s\n", ctx->nameOfWire(wire)); #endif - valid_wires_for_net.insert(wire); - log_assert(wire_to_arcs[wire].count(arc)); - log_assert(net_info->wires.count(wire)); + valid_wires_for_net.insert(wire); + log_assert(wire_to_arcs[wire].count(arc)); + log_assert(net_info->wires.count(wire)); + } } } @@ -375,52 +387,55 @@ struct Router1 ctx->nameOf(dst_to_arc.at(src_wire).net_info), dst_to_arc.at(src_wire).user_idx); for (int user_idx = 0; user_idx < int(net_info->users.size()); user_idx++) { - auto dst_wire = ctx->getNetinfoSinkWire(net_info, net_info->users[user_idx]); - - if (dst_wire == WireId()) - log_error("No wire found for port %s on destination cell %s.\n", - ctx->nameOf(net_info->users[user_idx].port), ctx->nameOf(net_info->users[user_idx].cell)); - - if (dst_to_arc.count(dst_wire)) { - if (dst_to_arc.at(dst_wire).net_info == net_info) - continue; - log_error("Found two arcs with same sink wire %s: %s (%d) vs %s (%d)\n", ctx->nameOfWire(dst_wire), - ctx->nameOf(net_info), user_idx, ctx->nameOf(dst_to_arc.at(dst_wire).net_info), - dst_to_arc.at(dst_wire).user_idx); - } - - if (src_to_net.count(dst_wire)) - log_error("Wire %s is used as source and sink in different nets: %s vs %s (%d)\n", - ctx->nameOfWire(dst_wire), ctx->nameOf(src_to_net.at(dst_wire)), ctx->nameOf(net_info), - user_idx); - - arc_key arc; - arc.net_info = net_info; - arc.user_idx = user_idx; - - dst_to_arc[dst_wire] = arc; + unsigned phys_idx = 0; + for (auto dst_wire : ctx->getNetinfoSinkWires(net_info, net_info->users[user_idx])) { + arc_key arc; + arc.net_info = net_info; + arc.user_idx = user_idx; + arc.phys_idx = phys_idx++; + + if (dst_to_arc.count(dst_wire)) { + if (dst_to_arc.at(dst_wire).net_info == net_info) + continue; + log_error("Found two arcs with same sink wire %s: %s (%d) vs %s (%d)\n", + ctx->nameOfWire(dst_wire), ctx->nameOf(net_info), user_idx, + ctx->nameOf(dst_to_arc.at(dst_wire).net_info), dst_to_arc.at(dst_wire).user_idx); + } - if (net_info->wires.count(dst_wire) == 0) { - arc_queue_insert(arc, src_wire, dst_wire); - continue; - } + if (src_to_net.count(dst_wire)) + log_error("Wire %s is used as source and sink in different nets: %s vs %s (%d)\n", + ctx->nameOfWire(dst_wire), ctx->nameOf(src_to_net.at(dst_wire)), + ctx->nameOf(net_info), user_idx); - WireId cursor = dst_wire; - wire_to_arcs[cursor].insert(arc); - arc_to_wires[arc].insert(cursor); + dst_to_arc[dst_wire] = arc; - while (src_wire != cursor) { - auto it = net_info->wires.find(cursor); - if (it == net_info->wires.end()) { + if (net_info->wires.count(dst_wire) == 0) { arc_queue_insert(arc, src_wire, dst_wire); - break; + continue; } - NPNR_ASSERT(it->second.pip != PipId()); - cursor = ctx->getPipSrcWire(it->second.pip); + WireId cursor = dst_wire; wire_to_arcs[cursor].insert(arc); arc_to_wires[arc].insert(cursor); + + while (src_wire != cursor) { + auto it = net_info->wires.find(cursor); + if (it == net_info->wires.end()) { + arc_queue_insert(arc, src_wire, dst_wire); + break; + } + + NPNR_ASSERT(it->second.pip != PipId()); + cursor = ctx->getPipSrcWire(it->second.pip); + wire_to_arcs[cursor].insert(arc); + arc_to_wires[arc].insert(cursor); + } } + // TODO: this matches the situation before supporting multiple cell->bel pins, but do we want to keep + // this invariant? + if (phys_idx == 0) + log_error("No wires found for port %s on destination cell %s.\n", + ctx->nameOf(net_info->users[user_idx].port), ctx->nameOf(net_info->users[user_idx].cell)); } src_to_net[src_wire] = net_info; @@ -443,7 +458,7 @@ struct Router1 int user_idx = arc.user_idx; auto src_wire = ctx->getNetinfoSourceWire(net_info); - auto dst_wire = ctx->getNetinfoSinkWire(net_info, net_info->users[user_idx]); + auto dst_wire = ctx->getNetinfoSinkWire(net_info, net_info->users[user_idx], arc.phys_idx); ripup_flag = false; if (ctx->debug) { @@ -934,14 +949,15 @@ bool Context::checkRoutedDesign() const std::unordered_map<WireId, int> dest_wires; for (int user_idx = 0; user_idx < int(net_info->users.size()); user_idx++) { - auto dst_wire = ctx->getNetinfoSinkWire(net_info, net_info->users[user_idx]); - log_assert(dst_wire != WireId()); - dest_wires[dst_wire] = user_idx; + for (auto dst_wire : ctx->getNetinfoSinkWires(net_info, net_info->users[user_idx])) { + log_assert(dst_wire != WireId()); + dest_wires[dst_wire] = user_idx; - if (net_info->wires.count(dst_wire) == 0) { - if (ctx->debug) - log(" sink %d (%s) not bound to net\n", user_idx, ctx->nameOfWire(dst_wire)); - found_unrouted = true; + if (net_info->wires.count(dst_wire) == 0) { + if (ctx->debug) + log(" sink %d (%s) not bound to net\n", user_idx, ctx->nameOfWire(dst_wire)); + found_unrouted = true; + } } } diff --git a/common/router2.cc b/common/router2.cc index 49d5fdec..a2023f47 100644 --- a/common/router2.cc +++ b/common/router2.cc @@ -58,7 +58,7 @@ struct Router2 struct PerNetData { WireId src_wire; - std::vector<PerArcData> arcs; + std::vector<std::vector<PerArcData>> arcs; ArcBounds bb; // Coordinates of the center of the net, used for the weight-to-average int cx, cy, hpwl; @@ -150,26 +150,30 @@ struct Router2 for (size_t j = 0; j < ni->users.size(); j++) { auto &usr = ni->users.at(j); - WireId src_wire = ctx->getNetinfoSourceWire(ni), dst_wire = ctx->getNetinfoSinkWire(ni, usr); - nets.at(i).src_wire = src_wire; - if (ni->driver.cell == nullptr) - src_wire = dst_wire; - if (ni->driver.cell == nullptr && dst_wire == WireId()) - continue; - if (src_wire == WireId()) - log_error("No wire found for port %s on source cell %s.\n", ctx->nameOf(ni->driver.port), - ctx->nameOf(ni->driver.cell)); - if (dst_wire == WireId()) - log_error("No wire found for port %s on destination cell %s.\n", ctx->nameOf(usr.port), - ctx->nameOf(usr.cell)); - nets.at(i).arcs.at(j).sink_wire = dst_wire; - // Set bounding box for this arc - nets.at(i).arcs.at(j).bb = ctx->getRouteBoundingBox(src_wire, dst_wire); - // Expand net bounding box to include this arc - nets.at(i).bb.x0 = std::min(nets.at(i).bb.x0, nets.at(i).arcs.at(j).bb.x0); - nets.at(i).bb.x1 = std::max(nets.at(i).bb.x1, nets.at(i).arcs.at(j).bb.x1); - nets.at(i).bb.y0 = std::min(nets.at(i).bb.y0, nets.at(i).arcs.at(j).bb.y0); - nets.at(i).bb.y1 = std::max(nets.at(i).bb.y1, nets.at(i).arcs.at(j).bb.y1); + WireId src_wire = ctx->getNetinfoSourceWire(ni); + for (auto &dst_wire : ctx->getNetinfoSinkWires(ni, usr)) { + nets.at(i).src_wire = src_wire; + if (ni->driver.cell == nullptr) + src_wire = dst_wire; + if (ni->driver.cell == nullptr && dst_wire == WireId()) + continue; + if (src_wire == WireId()) + log_error("No wire found for port %s on source cell %s.\n", ctx->nameOf(ni->driver.port), + ctx->nameOf(ni->driver.cell)); + if (dst_wire == WireId()) + log_error("No wire found for port %s on destination cell %s.\n", ctx->nameOf(usr.port), + ctx->nameOf(usr.cell)); + nets.at(i).arcs.at(j).emplace_back(); + auto &ad = nets.at(i).arcs.at(j).back(); + ad.sink_wire = dst_wire; + // Set bounding box for this arc + ad.bb = ctx->getRouteBoundingBox(src_wire, dst_wire); + // Expand net bounding box to include this arc + nets.at(i).bb.x0 = std::min(nets.at(i).bb.x0, ad.bb.x0); + nets.at(i).bb.x1 = std::max(nets.at(i).bb.x1, ad.bb.x1); + nets.at(i).bb.y0 = std::min(nets.at(i).bb.y0, ad.bb.y0); + nets.at(i).bb.y1 = std::max(nets.at(i).bb.x1, ad.bb.y1); + } // Add location to centroid sum Loc usr_loc = ctx->getBelLocation(usr.cell->bel); nets.at(i).cx += usr_loc.x; @@ -254,7 +258,7 @@ struct Router2 // Nets that failed routing std::vector<NetInfo *> failed_nets; - std::vector<int> route_arcs; + std::vector<std::pair<size_t, size_t>> route_arcs; std::priority_queue<QueuedWire, std::vector<QueuedWire>, QueuedWire::Greater> queue; // Special case where one net has multiple logical arcs to the same physical sink @@ -317,9 +321,9 @@ struct Router2 } } - void ripup_arc(NetInfo *net, size_t user) + void ripup_arc(NetInfo *net, size_t user, size_t phys_pin) { - auto &ad = nets.at(net->udata).arcs.at(user); + auto &ad = nets.at(net->udata).arcs.at(user).at(phys_pin); if (!ad.routed) return; WireId src = nets.at(net->udata).src_wire; @@ -333,7 +337,7 @@ struct Router2 ad.routed = false; } - float score_wire_for_arc(NetInfo *net, size_t user, WireId wire, PipId pip) + float score_wire_for_arc(NetInfo *net, size_t user, size_t phys_pin, WireId wire, PipId pip) { auto &wd = wire_data(wire); auto &nd = nets.at(net->udata); @@ -350,7 +354,7 @@ struct Router2 for (auto &bound : wd.bound_nets) if (bound.first != net->udata) max_bound_crit = std::max(max_bound_crit, nets.at(bound.first).max_crit); - if (max_bound_crit >= 0.8 && nd.arcs.at(user).arc_crit < (max_bound_crit + 0.01)) { + if (max_bound_crit >= 0.8 && nd.arcs.at(user).at(phys_pin).arc_crit < (max_bound_crit + 0.01)) { present_cost *= 1.5; } } @@ -372,9 +376,9 @@ struct Router2 return (ctx->getDelayNS(ctx->estimateDelay(wd.w, sink)) / (1 + source_uses)) + cfg.ipin_cost_adder; } - bool check_arc_routing(NetInfo *net, size_t usr) + bool check_arc_routing(NetInfo *net, size_t usr, size_t phys_pin) { - auto &ad = nets.at(net->udata).arcs.at(usr); + auto &ad = nets.at(net->udata).arcs.at(usr).at(phys_pin); WireId src_wire = nets.at(net->udata).src_wire; WireId cursor = ad.sink_wire; while (wire_data(cursor).bound_nets.count(net->udata)) { @@ -405,35 +409,34 @@ struct Router2 void reserve_wires_for_arc(NetInfo *net, size_t i) { WireId src = ctx->getNetinfoSourceWire(net); - WireId sink = ctx->getNetinfoSinkWire(net, net->users.at(i)); - if (sink == WireId()) - return; - std::unordered_set<WireId> rsv; - WireId cursor = sink; - bool done = false; - if (ctx->debug) - log("reserving wires for arc %d of net %s\n", int(i), ctx->nameOf(net)); - while (!done) { - auto &wd = wire_data(cursor); + for (auto sink : ctx->getNetinfoSinkWires(net, net->users.at(i))) { + std::unordered_set<WireId> rsv; + WireId cursor = sink; + bool done = false; if (ctx->debug) - log(" %s\n", ctx->nameOfWire(cursor)); - wd.reserved_net = net->udata; - if (cursor == src) - break; - WireId next_cursor; - for (auto uh : ctx->getPipsUphill(cursor)) { - WireId w = ctx->getPipSrcWire(uh); - if (is_wire_undriveable(w)) - continue; - if (next_cursor != WireId()) { - done = true; + log("reserving wires for arc %d of net %s\n", int(i), ctx->nameOf(net)); + while (!done) { + auto &wd = wire_data(cursor); + if (ctx->debug) + log(" %s\n", ctx->nameOfWire(cursor)); + wd.reserved_net = net->udata; + if (cursor == src) break; + WireId next_cursor; + for (auto uh : ctx->getPipsUphill(cursor)) { + WireId w = ctx->getPipSrcWire(uh); + if (is_wire_undriveable(w)) + continue; + if (next_cursor != WireId()) { + done = true; + break; + } + next_cursor = w; } - next_cursor = w; + if (next_cursor == WireId()) + break; + cursor = next_cursor; } - if (next_cursor == WireId()) - break; - cursor = next_cursor; } } @@ -471,15 +474,15 @@ struct Router2 } bool was_visited(int wire) { return flat_wires.at(wire).visit.visited; } - ArcRouteResult route_arc(ThreadContext &t, NetInfo *net, size_t i, bool is_mt, bool is_bb = true) + ArcRouteResult route_arc(ThreadContext &t, NetInfo *net, size_t i, size_t phys_pin, bool is_mt, bool is_bb = true) { auto &nd = nets[net->udata]; - auto &ad = nd.arcs[i]; + auto &ad = nd.arcs.at(i).at(phys_pin); auto &usr = net->users.at(i); ROUTE_LOG_DBG("Routing arc %d of net '%s' (%d, %d) -> (%d, %d)\n", int(i), ctx->nameOf(net), ad.bb.x0, ad.bb.y0, ad.bb.x1, ad.bb.y1); - WireId src_wire = ctx->getNetinfoSourceWire(net), dst_wire = ctx->getNetinfoSinkWire(net, usr); + WireId src_wire = ctx->getNetinfoSourceWire(net), dst_wire = ctx->getNetinfoSinkWire(net, usr, phys_pin); if (src_wire == WireId()) ARC_LOG_ERR("No wire found for port %s on source cell %s.\n", ctx->nameOf(net->driver.port), ctx->nameOf(net->driver.cell)); @@ -650,7 +653,7 @@ struct Router2 if (!thread_test_wire(t, nwd)) continue; // thread safety issue WireScore next_score; - next_score.cost = curr.score.cost + score_wire_for_arc(net, i, next, dh); + next_score.cost = curr.score.cost + score_wire_for_arc(net, i, phys_pin, next, dh); next_score.delay = curr.score.delay + ctx->getPipDelay(dh).maxDelay() + ctx->getWireDelay(next).maxDelay(); next_score.togo_cost = cfg.estimate_weight * get_togo_cost(net, i, next_idx, dst_wire); @@ -720,22 +723,26 @@ struct Router2 bool have_failures = false; t.processed_sinks.clear(); t.route_arcs.clear(); + auto &nd = nets.at(net->udata); for (size_t i = 0; i < net->users.size(); i++) { - // Ripup failed arcs to start with - // Check if arc is already legally routed - if (check_arc_routing(net, i)) - continue; - auto &usr = net->users.at(i); - WireId dst_wire = ctx->getNetinfoSinkWire(net, usr); - // Case of arcs that were pre-routed strongly (e.g. clocks) - if (net->wires.count(dst_wire) && net->wires.at(dst_wire).strength > STRENGTH_STRONG) - return ARC_SUCCESS; - // Ripup arc to start with - ripup_arc(net, i); - t.route_arcs.push_back(i); + auto &ad = nd.arcs.at(i); + for (size_t j = 0; j < ad.size(); j++) { + // Ripup failed arcs to start with + // Check if arc is already legally routed + if (check_arc_routing(net, i, j)) + continue; + auto &usr = net->users.at(i); + WireId dst_wire = ctx->getNetinfoSinkWire(net, usr, j); + // Case of arcs that were pre-routed strongly (e.g. clocks) + if (net->wires.count(dst_wire) && net->wires.at(dst_wire).strength > STRENGTH_STRONG) + return ARC_SUCCESS; + // Ripup arc to start with + ripup_arc(net, i, j); + t.route_arcs.emplace_back(i, j); + } } - for (auto i : t.route_arcs) { - auto res1 = route_arc(t, net, i, is_mt, true); + for (auto a : t.route_arcs) { + auto res1 = route_arc(t, net, a.first, a.second, is_mt, true); if (res1 == ARC_FATAL) return false; // Arc failed irrecoverably else if (res1 == ARC_RETRY_WITHOUT_BB) { @@ -744,14 +751,14 @@ struct Router2 have_failures = true; } else { // Attempt a re-route without the bounding box constraint - ROUTE_LOG_DBG("Rerouting arc %d of net '%s' without bounding box, possible tricky routing...\n", - int(i), ctx->nameOf(net)); - auto res2 = route_arc(t, net, i, is_mt, false); + ROUTE_LOG_DBG("Rerouting arc %d.%d of net '%s' without bounding box, possible tricky routing...\n", + int(a.first), int(a.second), ctx->nameOf(net)); + auto res2 = route_arc(t, net, a.first, a.second, is_mt, false); // If this also fails, no choice but to give up if (res2 != ARC_SUCCESS) - log_error("Failed to route arc %d of net '%s', from %s to %s.\n", int(i), ctx->nameOf(net), - ctx->nameOfWire(ctx->getNetinfoSourceWire(net)), - ctx->nameOfWire(ctx->getNetinfoSinkWire(net, net->users.at(i)))); + log_error("Failed to route arc %d.%d of net '%s', from %s to %s.\n", int(a.first), + int(a.second), ctx->nameOf(net), ctx->nameOfWire(ctx->getNetinfoSourceWire(net)), + ctx->nameOfWire(ctx->getNetinfoSinkWire(net, net->users.at(a.first), a.second))); } } } @@ -789,7 +796,7 @@ struct Router2 } } - bool bind_and_check(NetInfo *net, int usr_idx) + bool bind_and_check(NetInfo *net, int usr_idx, int phys_pin) { #ifdef ARCH_ECP5 if (net->is_global) @@ -797,13 +804,13 @@ struct Router2 #endif bool success = true; auto &nd = nets.at(net->udata); - auto &ad = nd.arcs.at(usr_idx); + auto &ad = nd.arcs.at(usr_idx).at(phys_pin); auto &usr = net->users.at(usr_idx); WireId src = ctx->getNetinfoSourceWire(net); // Skip routes with no source if (src == WireId()) return true; - WireId dst = ctx->getNetinfoSinkWire(net, usr); + WireId dst = ctx->getNetinfoSinkWire(net, usr, phys_pin); // Skip routes where the destination is already bound if (dst == WireId() || ctx->getBoundWireNet(dst) == net) return true; @@ -849,7 +856,7 @@ struct Router2 for (auto tb : to_bind) ctx->bindPip(tb, net, STRENGTH_WEAK); } else { - ripup_arc(net, usr_idx); + ripup_arc(net, usr_idx, phys_pin); failed_nets.insert(net->udata); } return success; @@ -875,9 +882,11 @@ struct Router2 ctx->unbindWire(w); // Bind the arcs using the routes we have discovered for (size_t i = 0; i < net->users.size(); i++) { - if (!bind_and_check(net, i)) { - ++arch_fail; - success = false; + for (size_t phys_pin = 0; phys_pin < nets.at(net->udata).arcs.at(i).size(); phys_pin++) { + if (!bind_and_check(net, i, phys_pin)) { + ++arch_fail; + success = false; + } } } } @@ -1123,7 +1132,8 @@ struct Router2 continue; for (int i = 0; i < int(fnd->second.criticality.size()); i++) { float c = fnd->second.criticality.at(i); - net.arcs.at(i).arc_crit = c; + for (auto &a : net.arcs.at(i)) + a.arc_crit = c; net.max_crit = std::max(net.max_crit, c); } } diff --git a/common/timing.cc b/common/timing.cc index 9fb14a33..a741c6ee 100644 --- a/common/timing.cc +++ b/common/timing.cc @@ -869,7 +869,7 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool p log_info(" Sink %s.%s\n", sink_cell->name.c_str(ctx), sink->port.c_str(ctx)); if (ctx->verbose) { auto driver_wire = ctx->getNetinfoSourceWire(net); - auto sink_wire = ctx->getNetinfoSinkWire(net, *sink); + auto sink_wire = ctx->getNetinfoSinkWire(net, *sink, 0); log_info(" prediction: %f ns estimate: %f ns\n", ctx->getDelayNS(ctx->predictDelay(net, *sink)), ctx->getDelayNS(ctx->estimateDelay(driver_wire, sink_wire))); diff --git a/docs/archapi.md b/docs/archapi.md index df7cd0a1..0f0e6181 100644 --- a/docs/archapi.md +++ b/docs/archapi.md @@ -13,6 +13,7 @@ The contents of `ArchRanges` is as follows: |`TileBelsRangeT` | `BelId` | |`BelAttrsRangeT` | std::pair<IdString, std::string> | |`BelPinsRangeT` | `IdString` | +|`CellBelPinRangeT` | `IdString` | |`AllWiresRangeT` | `WireId` | |`DownhillPipRangeT` | `PipId` | |`UphillPipRangeT` | `PipId` | @@ -250,6 +251,12 @@ Return the type (input/output/inout) of the given bel pin. Return a list of all pins on that bel. +### CellBelPinRangeT getBelPinsForCellPin(CellInfo *cell_info, IdString pin) const + +Return the list of bel pin names that a given cell pin should be routed to. In most cases there will be a single bel pin for each cell pin; and output pins must _always_ have only one bel pin associated with them. + +*BaseArch default: returns a one-element array containing `pin`* + Wire Methods ------------ diff --git a/docs/netlist.md b/docs/netlist.md index 763f7d40..2e989a33 100644 --- a/docs/netlist.md +++ b/docs/netlist.md @@ -52,6 +52,7 @@ Relevant fields from a netlist point of view are: - `nets` is a map from net name to a `unique_ptr<NetInfo>` containing net data - `net_aliases` maps every alias for a net to its canonical name (i.e. index into `nets`) - net aliases often occur when a net has a name both inside a submodule and higher level module - `ports` is a list of top level ports, primarily used during JSON export (e.g. to produce a useful post-PnR simulation model). Unlike other ports, top level ports are _not_ added to the driver or users of any connected net. In this sense, nets connected to top-level ports are _dangling_. However, top level ports _can_ still see their connected net as part of their `PortInfo`. + - `port_cells` is a map of top level port cells. This is a subset of the `cells` maps containing only ports. Context also has a method `check()` that ensures all of the contracts met above are satisfied. It is strongly suggested to run this after any pass that may modify the netlist. diff --git a/fpga_interchange/arch.cc b/fpga_interchange/arch.cc index 583813f0..1abf6f30 100644 --- a/fpga_interchange/arch.cc +++ b/fpga_interchange/arch.cc @@ -33,6 +33,7 @@ #include "router2.h" #include "timing.h" #include "util.h" +#include "xdc.h" NEXTPNR_NAMESPACE_BEGIN @@ -558,6 +559,23 @@ TimingClockingInfo Arch::getPortClockingInfo(const CellInfo *cell, IdString port return info; } +// ----------------------------------------------------------------------- + +void Arch::read_logical_netlist(const std::string &filename) {} +void Arch::write_physical_netlist(const std::string &filename) const {} + +void Arch::parse_xdc(const std::string &filename) +{ + TclInterp interp(getCtx()); + auto result = Tcl_EvalFile(interp.interp, filename.c_str()); + if (result != TCL_OK) { + log_error("Error in %s:%d => %s\n", filename.c_str(), Tcl_GetErrorLine(interp.interp), + Tcl_GetStringResult(interp.interp)); + } +} + +// ----------------------------------------------------------------------- + #ifdef WITH_HEAP const std::string Arch::defaultPlacer = "heap"; #else diff --git a/fpga_interchange/arch.h b/fpga_interchange/arch.h index 5964a38f..886978f1 100644 --- a/fpga_interchange/arch.h +++ b/fpga_interchange/arch.h @@ -650,6 +650,7 @@ struct BelBucketRange struct ArchArgs { std::string chipdb; + std::string package; }; struct ArchRanges @@ -660,6 +661,7 @@ struct ArchRanges using TileBelsRangeT = BelRange; using BelAttrsRangeT = std::vector<std::pair<IdString, std::string>>; using BelPinsRangeT = IdStringRange; + using CellBelPinRangeT = std::array<IdString, 1>; // Wires using AllWiresRangeT = WireRange; using DownhillPipRangeT = DownhillPipRange; @@ -865,6 +867,8 @@ struct Arch : ArchAPI<ArchRanges> return str_range; } + std::array<IdString, 1> getBelPinsForCellPin(CellInfo *cell_info, IdString pin) const override { return {pin}; } + // ------------------------------------------------- WireId getWireByName(IdStringList name) const override; @@ -1301,7 +1305,9 @@ struct Arch : ArchAPI<ArchRanges> static const std::vector<std::string> availableRouters; // ------------------------------------------------- - void write_physical_netlist(const std::string &filename) const {} + void read_logical_netlist(const std::string &filename); + void write_physical_netlist(const std::string &filename) const; + void parse_xdc(const std::string &filename); }; NEXTPNR_NAMESPACE_END diff --git a/fpga_interchange/family.cmake b/fpga_interchange/family.cmake index e69de29b..c3fefaba 100644 --- a/fpga_interchange/family.cmake +++ b/fpga_interchange/family.cmake @@ -0,0 +1,9 @@ +find_package(TCL) +if(NOT ${TCL_FOUND}) + message(FATAL_ERROR "Tcl is required for FPGA interchange Arch.") +endif() + +foreach (target ${family_targets}) + target_link_libraries(${target} LINK_PUBLIC ${TCL_LIBRARY}) + include_directories (${TCL_INCLUDE_PATH}) +endforeach() diff --git a/fpga_interchange/main.cc b/fpga_interchange/main.cc index 1f98b186..48b07584 100644 --- a/fpga_interchange/main.cc +++ b/fpga_interchange/main.cc @@ -49,8 +49,10 @@ po::options_description FpgaInterchangeCommandHandler::getArchOptions() { po::options_description specific("Architecture specific options"); specific.add_options()("chipdb", po::value<std::string>(), "name of chip database binary"); - specific.add_options()("xdc", po::value<std::vector<std::string>>(), "XDC-style constraints file"); + specific.add_options()("xdc", po::value<std::vector<std::string>>(), "XDC-style constraints file to read"); + specific.add_options()("netlist", po::value<std::string>(), "FPGA interchange logical netlist to read"); specific.add_options()("phys", po::value<std::string>(), "FPGA interchange Physical netlist to write"); + specific.add_options()("package", po::value<std::string>(), "Package to use"); return specific; } @@ -70,7 +72,23 @@ std::unique_ptr<Context> FpgaInterchangeCommandHandler::createContext(std::unord log_error("chip database binary must be provided\n"); } chipArgs.chipdb = vm["chipdb"].as<std::string>(); - return std::unique_ptr<Context>(new Context(chipArgs)); + if (vm.count("package")) { + chipArgs.package = vm["package"].as<std::string>(); + } + + auto ctx = std::unique_ptr<Context>(new Context(chipArgs)); + + if (vm.count("netlist")) { + ctx->read_logical_netlist(vm["netlist"].as<std::string>()); + } + + if (vm.count("xdc")) { + for (auto &x : vm["xdc"].as<std::vector<std::string>>()) { + ctx->parse_xdc(x); + } + } + + return ctx; } void FpgaInterchangeCommandHandler::customAfterLoad(Context *ctx) {} diff --git a/fpga_interchange/xdc.cc b/fpga_interchange/xdc.cc new file mode 100644 index 00000000..584a1777 --- /dev/null +++ b/fpga_interchange/xdc.cc @@ -0,0 +1,152 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2019 David Shah <david@symbioticeda.com> + * Copyright (C) 2021 Symbiflow Authors + * + * 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 "xdc.h" +#include <string> +#include "log.h" +#include "nextpnr.h" + +NEXTPNR_NAMESPACE_BEGIN + +static int port_set_from_any(Tcl_Interp *interp, Tcl_Obj *objPtr) { return TCL_ERROR; } + +static void set_tcl_obj_string(Tcl_Obj *objPtr, const std::string &s) +{ + NPNR_ASSERT(objPtr->bytes == nullptr); + // Need to have space for the end null byte. + objPtr->bytes = Tcl_Alloc(s.size() + 1); + + // Length is length of string, not including the end null byte. + objPtr->length = s.size(); + + std::copy(s.begin(), s.end(), objPtr->bytes); + objPtr->bytes[objPtr->length] = '\0'; +} + +static void port_update_string(Tcl_Obj *objPtr) +{ + const Context *ctx = static_cast<const Context *>(objPtr->internalRep.twoPtrValue.ptr1); + PortInfo *port_info = static_cast<PortInfo *>(objPtr->internalRep.twoPtrValue.ptr2); + + std::string port_name = port_info->name.str(ctx); + set_tcl_obj_string(objPtr, port_name); +} + +static void port_dup(Tcl_Obj *srcPtr, Tcl_Obj *dupPtr) +{ + dupPtr->internalRep.twoPtrValue = srcPtr->internalRep.twoPtrValue; +} + +static void port_free(Tcl_Obj *objPtr) {} + +static void Tcl_SetStringResult(Tcl_Interp *interp, const std::string &s) +{ + char *copy = Tcl_Alloc(s.size() + 1); + std::copy(s.begin(), s.end(), copy); + copy[s.size()] = '\0'; + Tcl_SetResult(interp, copy, TCL_DYNAMIC); +} + +static Tcl_ObjType port_object = { + "port", port_free, port_dup, port_update_string, port_set_from_any, +}; + +static int get_ports(ClientData data, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) +{ + const Context *ctx = static_cast<const Context *>(data); + if (objc == 1) { + // Return list of all ports. + Tcl_SetStringResult(interp, "Unimplemented"); + return TCL_ERROR; + } else if (objc == 2) { + const char *arg0 = Tcl_GetString(objv[1]); + IdString port_name = ctx->id(arg0); + + auto iter = ctx->ports.find(port_name); + if (iter == ctx->ports.end()) { + Tcl_SetStringResult(interp, "Could not find port " + port_name.str(ctx)); + return TCL_ERROR; + } + + Tcl_Obj *result = Tcl_NewObj(); + result->typePtr = &port_object; + result->internalRep.twoPtrValue.ptr1 = (void *)(ctx); + result->internalRep.twoPtrValue.ptr2 = (void *)(&iter->second); + + result->bytes = nullptr; + port_update_string(result); + + Tcl_SetObjResult(interp, result); + return TCL_OK; + } else { + return TCL_ERROR; + } +} + +static int set_property(ClientData data, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) +{ + // set_property <property> <value> <object> + if (objc != 4) { + Tcl_SetStringResult(interp, "Only simple 'set_property <property> <value> <object>' is supported"); + return TCL_ERROR; + } + + const char *property = Tcl_GetString(objv[1]); + const char *value = Tcl_GetString(objv[2]); + const Tcl_Obj *object = objv[3]; + + if (object->typePtr != &port_object) { + Tcl_SetStringResult(interp, "Only port objects are handled right now!"); + return TCL_ERROR; + } + + const Context *ctx = static_cast<const Context *>(object->internalRep.twoPtrValue.ptr1); + PortInfo *port_info = static_cast<PortInfo *>(object->internalRep.twoPtrValue.ptr2); + NPNR_ASSERT(port_info->net != nullptr); + CellInfo *cell = ctx->port_cells.at(port_info->name); + + cell->attrs[ctx->id(property)] = Property(value); + + return TCL_OK; +} + +TclInterp::TclInterp(Context *ctx) +{ + interp = Tcl_CreateInterp(); + NPNR_ASSERT(Tcl_Init(interp) == TCL_OK); + + Tcl_RegisterObjType(&port_object); + + NPNR_ASSERT(Tcl_Eval(interp, "rename unknown _original_unknown") == TCL_OK); + NPNR_ASSERT(Tcl_Eval(interp, "proc unknown args {\n" + " set result [scan [lindex $args 0] \"%d\" value]\n" + " if { $result == 1 && [llength $args] == 1 } {\n" + " return \\[$value\\]\n" + " } else {\n" + " uplevel 1 [list _original_unknown {*}$args]\n" + " }\n" + "}") == TCL_OK); + Tcl_CreateObjCommand(interp, "get_ports", get_ports, ctx, nullptr); + Tcl_CreateObjCommand(interp, "set_property", set_property, ctx, nullptr); +} + +TclInterp::~TclInterp() { Tcl_DeleteInterp(interp); } + +NEXTPNR_NAMESPACE_END diff --git a/fpga_interchange/xdc.h b/fpga_interchange/xdc.h new file mode 100644 index 00000000..c6b80870 --- /dev/null +++ b/fpga_interchange/xdc.h @@ -0,0 +1,33 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2021 Symbiflow Authors + * + * 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 <tcl.h> +#include "nextpnr.h" + +NEXTPNR_NAMESPACE_BEGIN + +struct TclInterp +{ + TclInterp(Context *ctx); + ~TclInterp(); + + Tcl_Interp *interp; +}; + +NEXTPNR_NAMESPACE_END diff --git a/frontend/frontend_base.h b/frontend/frontend_base.h index 4f7d22ec..d39a8304 100644 --- a/frontend/frontend_base.h +++ b/frontend/frontend_base.h @@ -123,7 +123,7 @@ struct ModuleInfo template <typename FrontendType> struct GenericFrontend { - GenericFrontend(Context *ctx, const FrontendType &impl) : ctx(ctx), impl(impl) {} + GenericFrontend(Context *ctx, const FrontendType &impl, bool split_io) : ctx(ctx), impl(impl), split_io(split_io) {} void operator()() { // Find which module is top @@ -141,6 +141,7 @@ template <typename FrontendType> struct GenericFrontend Context *ctx; const FrontendType &impl; + const bool split_io; using mod_dat_t = typename FrontendType::ModuleDataType; using mod_port_dat_t = typename FrontendType::ModulePortDataType; using cell_dat_t = typename FrontendType::CellDataType; @@ -148,7 +149,7 @@ template <typename FrontendType> struct GenericFrontend using bitvector_t = typename FrontendType::BitVectorDataType; std::unordered_map<IdString, ModuleInfo> mods; - std::unordered_map<IdString, const mod_dat_t &> mod_refs; + std::unordered_map<IdString, const mod_dat_t> mod_refs; IdString top; // Process the list of modules and determine @@ -585,22 +586,28 @@ template <typename FrontendType> struct GenericFrontend 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); + + if (split_io) { + 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")); + } else { + iobuf->addInout(ctx->id("IO")); + connect_port(ctx, net, iobuf, ctx->id("IO")); } - 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; @@ -608,6 +615,7 @@ template <typename FrontendType> struct GenericFrontend pinfo.net = net; pinfo.type = dir; ctx->ports[pinfo.name] = pinfo; + ctx->port_cells[pinfo.name] = iobuf; return iobuf; } diff --git a/frontend/json_frontend.cc b/frontend/json_frontend.cc index 136786fc..52f7bfdc 100644 --- a/frontend/json_frontend.cc +++ b/frontend/json_frontend.cc @@ -197,7 +197,7 @@ bool parse_json(std::istream &in, const std::string &filename, Context *ctx) 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))(); + GenericFrontend<JsonFrontendImpl>(ctx, JsonFrontendImpl(root), /*split_io=*/true)(); return true; } diff --git a/generic/arch.cc b/generic/arch.cc index b54a8b65..7cd71179 100644 --- a/generic/arch.cc +++ b/generic/arch.cc @@ -342,6 +342,8 @@ std::vector<IdString> Arch::getBelPins(BelId bel) const return ret; } +std::array<IdString, 1> Arch::getBelPinsForCellPin(CellInfo *cell_info, IdString pin) const { return {pin}; } + // --------------------------------------------------------------- WireId Arch::getWireByName(IdStringList name) const diff --git a/generic/arch.h b/generic/arch.h index accf2dce..2a0c7158 100644 --- a/generic/arch.h +++ b/generic/arch.h @@ -125,6 +125,7 @@ struct ArchRanges using TileBelsRangeT = const std::vector<BelId> &; using BelAttrsRangeT = const std::map<IdString, std::string> &; using BelPinsRangeT = std::vector<IdString>; + using CellBelPinRangeT = std::array<IdString, 1>; // Wires using AllWiresRangeT = const std::vector<WireId> &; using DownhillPipRangeT = const std::vector<PipId> &; @@ -243,6 +244,7 @@ struct Arch : ArchAPI<ArchRanges> WireId getBelPinWire(BelId bel, IdString pin) const override; PortType getBelPinType(BelId bel, IdString pin) const override; std::vector<IdString> getBelPins(BelId bel) const override; + std::array<IdString, 1> getBelPinsForCellPin(CellInfo *cell_info, IdString pin) const override; WireId getWireByName(IdStringList name) const override; IdStringList getWireName(WireId wire) const override; diff --git a/gowin/arch.cc b/gowin/arch.cc index d4e17a8a..8aeccfba 100644 --- a/gowin/arch.cc +++ b/gowin/arch.cc @@ -822,6 +822,8 @@ std::vector<IdString> Arch::getBelPins(BelId bel) const return ret; } +std::array<IdString, 1> Arch::getBelPinsForCellPin(CellInfo *cell_info, IdString pin) const { return {pin}; } + // --------------------------------------------------------------- WireId Arch::getWireByName(IdStringList name) const diff --git a/gowin/arch.h b/gowin/arch.h index c5ef0a2e..eab75899 100644 --- a/gowin/arch.h +++ b/gowin/arch.h @@ -251,6 +251,7 @@ struct ArchRanges using TileBelsRangeT = const std::vector<BelId> &; using BelAttrsRangeT = const std::map<IdString, std::string> &; using BelPinsRangeT = std::vector<IdString>; + using CellBelPinRangeT = std::array<IdString, 1>; // Wires using AllWiresRangeT = const std::vector<WireId> &; using DownhillPipRangeT = const std::vector<PipId> &; @@ -375,6 +376,7 @@ struct Arch : BaseArch<ArchRanges> WireId getBelPinWire(BelId bel, IdString pin) const override; PortType getBelPinType(BelId bel, IdString pin) const override; std::vector<IdString> getBelPins(BelId bel) const override; + std::array<IdString, 1> getBelPinsForCellPin(CellInfo *cell_info, IdString pin) const override; WireId getWireByName(IdStringList name) const override; IdStringList getWireName(WireId wire) const override; diff --git a/nexus/global.cc b/nexus/global.cc index f7abb399..62633df9 100644 --- a/nexus/global.cc +++ b/nexus/global.cc @@ -53,7 +53,7 @@ struct NexusGlobalRouter // Lookup source and destination wires WireId src = ctx->getNetinfoSourceWire(net); - WireId dst = ctx->getNetinfoSinkWire(net, net->users.at(user_idx)); + WireId dst = ctx->getNetinfoSinkWire(net, net->users.at(user_idx), 0); if (src == WireId()) log_error("Net '%s' has an invalid source port %s.%s\n", ctx->nameOf(net), ctx->nameOf(net->driver.cell), diff --git a/tests b/tests -Subproject 8f93e7e0f897b1b5da469919c9a43ba28b623b2 +Subproject 31648368460b9e216479ce7c38e6fed883c380c |