aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.cirrus.yml3
-rw-r--r--common/nextpnr.cc92
-rw-r--r--common/nextpnr.h13
-rw-r--r--common/router1.cc140
-rw-r--r--common/router2.cc180
-rw-r--r--common/timing.cc2
-rw-r--r--docs/archapi.md7
-rw-r--r--docs/netlist.md1
-rw-r--r--fpga_interchange/arch.cc18
-rw-r--r--fpga_interchange/arch.h8
-rw-r--r--fpga_interchange/family.cmake9
-rw-r--r--fpga_interchange/main.cc22
-rw-r--r--fpga_interchange/xdc.cc152
-rw-r--r--fpga_interchange/xdc.h33
-rw-r--r--frontend/frontend_base.h42
-rw-r--r--frontend/json_frontend.cc2
-rw-r--r--generic/arch.cc2
-rw-r--r--generic/arch.h2
-rw-r--r--gowin/arch.cc2
-rw-r--r--gowin/arch.h2
-rw-r--r--nexus/global.cc2
m---------tests0
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