aboutsummaryrefslogtreecommitdiffstats
path: root/common/route/router1.cc
diff options
context:
space:
mode:
Diffstat (limited to 'common/route/router1.cc')
-rw-r--r--common/route/router1.cc1175
1 files changed, 1175 insertions, 0 deletions
diff --git a/common/route/router1.cc b/common/route/router1.cc
new file mode 100644
index 00000000..98132116
--- /dev/null
+++ b/common/route/router1.cc
@@ -0,0 +1,1175 @@
+/*
+ * nextpnr -- Next Generation Place and Route
+ *
+ * Copyright (C) 2018 Claire Xenia Wolf <claire@yosyshq.com>
+ *
+ * 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 <chrono>
+#include <cmath>
+#include <queue>
+
+#include "log.h"
+#include "router1.h"
+#include "scope_lock.h"
+#include "timing.h"
+
+namespace {
+
+USING_NEXTPNR_NAMESPACE
+
+struct arc_key
+{
+ NetInfo *net_info;
+ // logical user cell port index
+ store_index<PortRef> 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) && (phys_idx == other.phys_idx);
+ }
+ bool operator<(const arc_key &other) const
+ {
+ 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;
+ }
+
+ unsigned int hash() const
+ {
+ std::size_t seed = std::hash<NetInfo *>()(net_info);
+ seed ^= user_idx.hash() + 0x9e3779b9 + (seed << 6) + (seed >> 2);
+ seed ^= std::hash<int>()(phys_idx) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
+ return seed;
+ }
+};
+
+struct arc_entry
+{
+ arc_key arc;
+ delay_t pri;
+ int randtag = 0;
+
+ struct Less
+ {
+ bool operator()(const arc_entry &lhs, const arc_entry &rhs) const noexcept
+ {
+ if (lhs.pri != rhs.pri)
+ return lhs.pri < rhs.pri;
+ return lhs.randtag < rhs.randtag;
+ }
+ };
+};
+
+struct QueuedWire
+{
+ WireId wire;
+ PipId pip;
+
+ delay_t delay = 0, penalty = 0, bonus = 0, togo = 0;
+ int randtag = 0;
+
+ struct Greater
+ {
+ bool operator()(const QueuedWire &lhs, const QueuedWire &rhs) const noexcept
+ {
+ delay_t l = lhs.delay + lhs.penalty + lhs.togo;
+ delay_t r = rhs.delay + rhs.penalty + rhs.togo;
+ NPNR_ASSERT(l >= 0);
+ NPNR_ASSERT(r >= 0);
+ l -= lhs.bonus;
+ r -= rhs.bonus;
+ return l == r ? lhs.randtag > rhs.randtag : l > r;
+ }
+ };
+};
+
+struct Router1
+{
+ Context *ctx;
+ const Router1Cfg &cfg;
+
+ std::priority_queue<arc_entry, std::vector<arc_entry>, arc_entry::Less> arc_queue;
+ dict<WireId, pool<arc_key>> wire_to_arcs;
+ dict<arc_key, pool<WireId>> arc_to_wires;
+ pool<arc_key> queued_arcs;
+
+ std::priority_queue<QueuedWire, std::vector<QueuedWire>, QueuedWire::Greater> queue;
+
+ dict<WireId, int> wireScores;
+ dict<NetInfo *, int, hash_ptr_ops> netScores;
+
+ int arcs_with_ripup = 0;
+ int arcs_without_ripup = 0;
+ bool ripup_flag;
+
+ TimingAnalyser tmg;
+
+ bool timing_driven = true;
+
+ Router1(Context *ctx, const Router1Cfg &cfg) : ctx(ctx), cfg(cfg), tmg(ctx)
+ {
+ timing_driven = ctx->setting<bool>("timing_driven");
+ tmg.setup();
+ tmg.run();
+ }
+
+ void arc_queue_insert(const arc_key &arc, WireId src_wire, WireId dst_wire)
+ {
+ if (queued_arcs.count(arc))
+ return;
+
+ delay_t pri = ctx->estimateDelay(src_wire, dst_wire) *
+ (100 * tmg.get_criticality(CellPortKey(arc.net_info->users.at(arc.user_idx))));
+
+ arc_entry entry;
+ entry.arc = arc;
+ entry.pri = pri;
+ entry.randtag = ctx->rng();
+
+#if 0
+ if (ctx->debug)
+ log("[arc_queue_insert] %s (%d) %s %s [%d %d]\n", ctx->nameOf(entry.arc.net_info), entry.arc.user_idx,
+ ctx->nameOfWire(src_wire), ctx->nameOfWire(dst_wire), (int)entry.pri, entry.randtag);
+#endif
+
+ arc_queue.push(entry);
+ queued_arcs.insert(arc);
+ }
+
+ void arc_queue_insert(const arc_key &arc)
+ {
+ if (queued_arcs.count(arc))
+ return;
+
+ NetInfo *net_info = arc.net_info;
+ auto 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], phys_idx);
+
+ arc_queue_insert(arc, src_wire, dst_wire);
+ }
+
+ arc_key arc_queue_pop()
+ {
+ arc_entry entry = arc_queue.top();
+
+#if 0
+ if (ctx->debug)
+ log("[arc_queue_pop] %s (%d) [%d %d]\n", ctx->nameOf(entry.arc.net_info), entry.arc.user_idx,
+ (int)entry.pri, entry.randtag);
+#endif
+
+ arc_queue.pop();
+ queued_arcs.erase(entry.arc);
+ return entry.arc;
+ }
+
+ void ripup_net(NetInfo *net)
+ {
+ if (ctx->debug)
+ log(" ripup net %s\n", ctx->nameOf(net));
+
+ netScores[net]++;
+
+ std::vector<WireId> wires;
+ for (auto &it : net->wires)
+ wires.push_back(it.first);
+
+ ctx->sorted_shuffle(wires);
+
+ for (WireId w : wires) {
+ std::vector<arc_key> arcs;
+ for (auto &it : wire_to_arcs[w]) {
+ arc_to_wires[it].erase(w);
+ arcs.push_back(it);
+ }
+ wire_to_arcs[w].clear();
+
+ ctx->sorted_shuffle(arcs);
+
+ for (auto &it : arcs)
+ arc_queue_insert(it);
+
+ if (ctx->debug)
+ log(" unbind wire %s\n", ctx->nameOfWire(w));
+
+ ctx->unbindWire(w);
+ wireScores[w]++;
+ }
+
+ ripup_flag = true;
+ }
+
+ void ripup_wire(WireId wire, int extra_indent = 0)
+ {
+ if (ctx->debug)
+ log(" ripup wire %s\n", ctx->nameOfWire(wire));
+
+ WireId w = ctx->getConflictingWireWire(wire);
+
+ if (w == WireId()) {
+ NetInfo *n = ctx->getConflictingWireNet(wire);
+ if (n != nullptr)
+ ripup_net(n);
+ } else {
+ std::vector<arc_key> arcs;
+ for (auto &it : wire_to_arcs[w]) {
+ arc_to_wires[it].erase(w);
+ arcs.push_back(it);
+ }
+ wire_to_arcs[w].clear();
+
+ ctx->sorted_shuffle(arcs);
+
+ for (auto &it : arcs)
+ arc_queue_insert(it);
+
+ if (ctx->debug)
+ log(" unbind wire %s\n", ctx->nameOfWire(w));
+
+ ctx->unbindWire(w);
+ wireScores[w]++;
+ }
+
+ ripup_flag = true;
+ }
+
+ void ripup_pip(PipId pip)
+ {
+ if (ctx->debug)
+ log(" ripup pip %s\n", ctx->nameOfPip(pip));
+
+ WireId w = ctx->getConflictingPipWire(pip);
+
+ if (w == WireId()) {
+ NetInfo *n = ctx->getConflictingPipNet(pip);
+ if (n != nullptr)
+ ripup_net(n);
+ } else {
+ std::vector<arc_key> arcs;
+ for (auto &it : wire_to_arcs[w]) {
+ arc_to_wires[it].erase(w);
+ arcs.push_back(it);
+ }
+ wire_to_arcs[w].clear();
+
+ ctx->sorted_shuffle(arcs);
+
+ for (auto &it : arcs)
+ arc_queue_insert(it);
+
+ if (ctx->debug)
+ log(" unbind wire %s\n", ctx->nameOfWire(w));
+
+ ctx->unbindWire(w);
+ wireScores[w]++;
+ }
+
+ ripup_flag = true;
+ }
+
+ bool skip_net(NetInfo *net_info)
+ {
+#ifdef ARCH_ECP5
+ // ECP5 global nets currently appear part-unrouted due to arch database limitations
+ // Don't touch them in the router
+ if (net_info->is_global)
+ return true;
+#endif
+ if (net_info->driver.cell == nullptr)
+ return true;
+
+ return false;
+ }
+
+ void check()
+ {
+ pool<arc_key> valid_arcs;
+
+ for (auto &net_it : ctx->nets) {
+ NetInfo *net_info = net_it.second.get();
+ pool<WireId> valid_wires_for_net;
+
+ if (skip_net(net_info))
+ continue;
+
+#if 0
+ if (ctx->debug)
+ log("[check] net: %s\n", ctx->nameOf(net_info));
+#endif
+
+ auto src_wire = ctx->getNetinfoSourceWire(net_info);
+ log_assert(src_wire != WireId());
+
+ for (auto user : net_info->users.enumerate()) {
+ unsigned phys_idx = 0;
+ for (auto dst_wire : ctx->getNetinfoSinkWires(net_info, user.value)) {
+ log_assert(dst_wire != WireId());
+
+ arc_key arc;
+ arc.net_info = net_info;
+ arc.user_idx = user.index;
+ arc.phys_idx = phys_idx++;
+ valid_arcs.insert(arc);
+#if 0
+ 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]) {
+#if 0
+ 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));
+ }
+ }
+ }
+
+ for (auto &it : net_info->wires) {
+ WireId w = it.first;
+ log_assert(valid_wires_for_net.count(w));
+ }
+ }
+
+ for (auto &it : wire_to_arcs) {
+ for (auto &arc : it.second)
+ log_assert(valid_arcs.count(arc));
+ }
+
+ for (auto &it : arc_to_wires) {
+ log_assert(valid_arcs.count(it.first));
+ }
+ }
+
+ void setup()
+ {
+ dict<WireId, NetInfo *> src_to_net;
+ dict<WireId, arc_key> dst_to_arc;
+
+ std::vector<IdString> net_names;
+ for (auto &net_it : ctx->nets)
+ net_names.push_back(net_it.first);
+
+ ctx->sorted_shuffle(net_names);
+
+ for (IdString net_name : net_names) {
+ NetInfo *net_info = ctx->nets.at(net_name).get();
+
+ if (skip_net(net_info))
+ continue;
+
+ auto src_wire = ctx->getNetinfoSourceWire(net_info);
+
+ if (src_wire == WireId())
+ log_error("No wire found for port %s on source cell %s.\n", ctx->nameOf(net_info->driver.port),
+ ctx->nameOf(net_info->driver.cell));
+
+ if (src_to_net.count(src_wire))
+ log_error("Found two nets with same source wire %s: %s vs %s\n", ctx->nameOfWire(src_wire),
+ ctx->nameOf(net_info), ctx->nameOf(src_to_net.at(src_wire)));
+
+ if (dst_to_arc.count(src_wire))
+ log_error("Wire %s is used as source and sink in different nets: %s vs %s (%d)\n",
+ ctx->nameOfWire(src_wire), ctx->nameOf(net_info),
+ ctx->nameOf(dst_to_arc.at(src_wire).net_info), dst_to_arc.at(src_wire).user_idx.idx());
+
+ for (auto user : net_info->users.enumerate()) {
+ unsigned phys_idx = 0;
+ for (auto dst_wire : ctx->getNetinfoSinkWires(net_info, user.value)) {
+ arc_key arc;
+ arc.net_info = net_info;
+ arc.user_idx = user.index;
+ 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.index.idx(),
+ ctx->nameOf(dst_to_arc.at(dst_wire).net_info),
+ dst_to_arc.at(dst_wire).user_idx.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.index.idx());
+
+ dst_to_arc[dst_wire] = arc;
+
+ if (net_info->wires.count(dst_wire) == 0) {
+ arc_queue_insert(arc, src_wire, dst_wire);
+ continue;
+ }
+
+ 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_warning("No wires found for port %s on destination cell %s.\n", ctx->nameOf(user.value.port),
+ ctx->nameOf(user.value.cell));
+ }
+
+ src_to_net[src_wire] = net_info;
+
+ std::vector<WireId> unbind_wires;
+
+ for (auto &it : net_info->wires)
+ if (it.second.strength < STRENGTH_LOCKED && wire_to_arcs.count(it.first) == 0)
+ unbind_wires.push_back(it.first);
+
+ for (auto it : unbind_wires)
+ ctx->unbindWire(it);
+ }
+ }
+
+ bool route_arc(const arc_key &arc, bool ripup)
+ {
+
+ NetInfo *net_info = arc.net_info;
+ auto user_idx = arc.user_idx;
+
+ auto src_wire = ctx->getNetinfoSourceWire(net_info);
+ auto dst_wire = ctx->getNetinfoSinkWire(net_info, net_info->users[user_idx], arc.phys_idx);
+ ripup_flag = false;
+
+ float crit = tmg.get_criticality(CellPortKey(net_info->users.at(user_idx)));
+
+ if (ctx->debug) {
+ log("Routing arc %d on net %s (%d arcs total):\n", user_idx.idx(), ctx->nameOf(net_info),
+ int(net_info->users.capacity()));
+ log(" source ... %s\n", ctx->nameOfWire(src_wire));
+ log(" sink ..... %s\n", ctx->nameOfWire(dst_wire));
+ }
+
+ // unbind wires that are currently used exclusively by this arc
+
+ pool<WireId> old_arc_wires;
+ old_arc_wires.swap(arc_to_wires[arc]);
+
+ for (WireId wire : old_arc_wires) {
+ auto &arc_wires = wire_to_arcs.at(wire);
+ NPNR_ASSERT(arc_wires.count(arc));
+ arc_wires.erase(arc);
+ if (arc_wires.empty()) {
+ if (ctx->debug)
+ log(" unbind %s\n", ctx->nameOfWire(wire));
+ ctx->unbindWire(wire);
+ }
+ }
+
+ // special case
+
+ if (src_wire == dst_wire) {
+ NetInfo *bound = ctx->getBoundWireNet(src_wire);
+ if (bound != nullptr)
+ NPNR_ASSERT(bound == net_info);
+ else {
+ ctx->bindWire(src_wire, net_info, STRENGTH_WEAK);
+ }
+ arc_to_wires[arc].insert(src_wire);
+ wire_to_arcs[src_wire].insert(arc);
+ return true;
+ }
+
+ // reset wire queue
+
+ if (!queue.empty()) {
+ std::priority_queue<QueuedWire, std::vector<QueuedWire>, QueuedWire::Greater> new_queue;
+ queue.swap(new_queue);
+ }
+ dict<WireId, QueuedWire> visited;
+
+ // A* main loop
+
+ int visitCnt = 0;
+ int maxVisitCnt = INT_MAX;
+ delay_t best_est = 0;
+ delay_t best_score = -1;
+
+ {
+ QueuedWire qw;
+ qw.wire = src_wire;
+ qw.pip = PipId();
+ qw.delay = ctx->getWireDelay(qw.wire).maxDelay();
+ qw.penalty = 0;
+ qw.bonus = 0;
+ if (cfg.useEstimate) {
+ qw.togo = ctx->estimateDelay(qw.wire, dst_wire);
+ best_est = qw.delay + qw.togo;
+ }
+ qw.randtag = ctx->rng();
+
+ queue.push(qw);
+ visited[qw.wire] = qw;
+ }
+
+ while (visitCnt++ < maxVisitCnt && !queue.empty()) {
+ QueuedWire qw = queue.top();
+ queue.pop();
+
+ for (auto pip : ctx->getPipsDownhill(qw.wire)) {
+ delay_t next_delay = qw.delay + ctx->getPipDelay(pip).maxDelay();
+ delay_t next_penalty = qw.penalty;
+ delay_t next_bonus = qw.bonus;
+ delay_t penalty_delta = 0;
+
+ WireId next_wire = ctx->getPipDstWire(pip);
+ next_delay += ctx->getWireDelay(next_wire).maxDelay();
+
+ WireId conflictWireWire = WireId(), conflictPipWire = WireId();
+ NetInfo *conflictWireNet = nullptr, *conflictPipNet = nullptr;
+
+ if (net_info->wires.count(next_wire) && net_info->wires.at(next_wire).pip == pip) {
+ next_bonus += cfg.reuseBonus * (1.0 - crit);
+ } else {
+ if (!ctx->checkWireAvail(next_wire)) {
+ if (!ripup)
+ continue;
+ conflictWireWire = ctx->getConflictingWireWire(next_wire);
+ if (conflictWireWire == WireId()) {
+ conflictWireNet = ctx->getConflictingWireNet(next_wire);
+ if (conflictWireNet == nullptr)
+ continue;
+ else {
+ if (conflictWireNet->wires.count(next_wire) &&
+ conflictWireNet->wires.at(next_wire).strength > STRENGTH_STRONG)
+ continue;
+ }
+ } else {
+ NetInfo *conflicting = ctx->getBoundWireNet(conflictWireWire);
+ if (conflicting != nullptr) {
+ if (conflicting->wires.count(conflictWireWire) &&
+ conflicting->wires.at(conflictWireWire).strength > STRENGTH_STRONG)
+ continue;
+ }
+ }
+ }
+
+ if (!ctx->checkPipAvail(pip)) {
+ if (!ripup)
+ continue;
+ conflictPipWire = ctx->getConflictingPipWire(pip);
+ if (conflictPipWire == WireId()) {
+ conflictPipNet = ctx->getConflictingPipNet(pip);
+ if (conflictPipNet == nullptr)
+ continue;
+ else {
+ if (conflictPipNet->wires.count(next_wire) &&
+ conflictPipNet->wires.at(next_wire).strength > STRENGTH_STRONG)
+ continue;
+ }
+ } else {
+ NetInfo *conflicting = ctx->getBoundWireNet(conflictPipWire);
+ if (conflicting != nullptr) {
+ if (conflicting->wires.count(conflictPipWire) &&
+ conflicting->wires.at(conflictPipWire).strength > STRENGTH_STRONG)
+ continue;
+ }
+ }
+ }
+
+ if (conflictWireNet != nullptr && conflictPipWire != WireId() &&
+ conflictWireNet->wires.count(conflictPipWire))
+ conflictPipWire = WireId();
+
+ if (conflictPipNet != nullptr && conflictWireWire != WireId() &&
+ conflictPipNet->wires.count(conflictWireWire))
+ conflictWireWire = WireId();
+
+ if (conflictWireWire == conflictPipWire)
+ conflictWireWire = WireId();
+
+ if (conflictWireNet == conflictPipNet)
+ conflictWireNet = nullptr;
+
+ if (conflictWireWire != WireId()) {
+ auto scores_it = wireScores.find(conflictWireWire);
+ if (scores_it != wireScores.end())
+ penalty_delta += scores_it->second * cfg.wireRipupPenalty;
+ penalty_delta += cfg.wireRipupPenalty;
+ }
+
+ if (conflictPipWire != WireId()) {
+ auto scores_it = wireScores.find(conflictPipWire);
+ if (scores_it != wireScores.end())
+ penalty_delta += scores_it->second * cfg.wireRipupPenalty;
+ penalty_delta += cfg.wireRipupPenalty;
+ }
+
+ if (conflictWireNet != nullptr) {
+ auto scores_it = netScores.find(conflictWireNet);
+ if (scores_it != netScores.end())
+ penalty_delta += scores_it->second * cfg.netRipupPenalty;
+ penalty_delta += cfg.netRipupPenalty;
+ penalty_delta += conflictWireNet->wires.size() * cfg.wireRipupPenalty;
+ }
+
+ if (conflictPipNet != nullptr) {
+ auto scores_it = netScores.find(conflictPipNet);
+ if (scores_it != netScores.end())
+ penalty_delta += scores_it->second * cfg.netRipupPenalty;
+ penalty_delta += cfg.netRipupPenalty;
+ penalty_delta += conflictPipNet->wires.size() * cfg.wireRipupPenalty;
+ }
+ }
+
+ next_penalty += penalty_delta * (timing_driven ? std::max(0.05, (1.0 - crit)) : 1);
+
+ delay_t next_score = next_delay + next_penalty;
+ NPNR_ASSERT(next_score >= 0);
+
+ if ((best_score >= 0) && (next_score - next_bonus - cfg.estimatePrecision > best_score))
+ continue;
+
+ auto old_visited_it = visited.find(next_wire);
+ if (old_visited_it != visited.end()) {
+ delay_t old_delay = old_visited_it->second.delay;
+ delay_t old_score = old_delay + old_visited_it->second.penalty;
+ NPNR_ASSERT(old_score >= 0);
+
+ if (next_score + ctx->getDelayEpsilon() >= old_score)
+ continue;
+
+#if 0
+ if (ctx->debug)
+ log("Found better route to %s. Old vs new delay estimate: %.3f (%.3f) %.3f (%.3f)\n",
+ ctx->nameOfWire(next_wire),
+ ctx->getDelayNS(old_score),
+ ctx->getDelayNS(old_visited_it->second.delay),
+ ctx->getDelayNS(next_score),
+ ctx->getDelayNS(next_delay));
+#endif
+ }
+
+ QueuedWire next_qw;
+ next_qw.wire = next_wire;
+ next_qw.pip = pip;
+ next_qw.delay = next_delay;
+ next_qw.penalty = next_penalty;
+ next_qw.bonus = next_bonus;
+ if (cfg.useEstimate) {
+ next_qw.togo = ctx->estimateDelay(next_wire, dst_wire);
+ delay_t this_est = next_qw.delay + next_qw.togo;
+ if (this_est / 2 - cfg.estimatePrecision > best_est)
+ continue;
+ if (best_est > this_est)
+ best_est = this_est;
+ }
+ next_qw.randtag = ctx->rng();
+
+#if 0
+ if (ctx->debug)
+ log("%s -> %s: %.3f (%.3f)\n",
+ ctx->nameOfWire(qw.wire),
+ ctx->nameOfWire(next_wire),
+ ctx->getDelayNS(next_score),
+ ctx->getDelayNS(next_delay));
+#endif
+
+ visited[next_qw.wire] = next_qw;
+ queue.push(next_qw);
+
+ if (next_wire == dst_wire) {
+ maxVisitCnt = std::min(maxVisitCnt, 2 * visitCnt + (next_qw.penalty > 0 ? 100 : 0));
+ best_score = next_score - next_bonus;
+ }
+ }
+ }
+
+ if (ctx->debug)
+ log(" total number of visited nodes: %d\n", visitCnt);
+
+ if (visited.count(dst_wire) == 0) {
+ if (ctx->debug)
+ log(" no route found for this arc\n");
+ return false;
+ }
+
+ if (ctx->debug) {
+ log(" final route delay: %8.2f\n", ctx->getDelayNS(visited[dst_wire].delay));
+ log(" final route penalty: %8.2f\n", ctx->getDelayNS(visited[dst_wire].penalty));
+ log(" final route bonus: %8.2f\n", ctx->getDelayNS(visited[dst_wire].bonus));
+ log(" arc budget: %12.2f\n", ctx->getDelayNS(net_info->users[user_idx].budget));
+ }
+
+ // bind resulting route (and maybe unroute other nets)
+
+ pool<WireId> unassign_wires = arc_to_wires[arc];
+
+ WireId cursor = dst_wire;
+ delay_t accumulated_path_delay = 0;
+ delay_t last_path_delay_delta = 0;
+ while (1) {
+ auto pip = visited[cursor].pip;
+
+ if (ctx->debug) {
+ delay_t path_delay_delta = ctx->estimateDelay(cursor, dst_wire) - accumulated_path_delay;
+
+ log(" node %s (%+.2f %+.2f)\n", ctx->nameOfWire(cursor), ctx->getDelayNS(path_delay_delta),
+ ctx->getDelayNS(path_delay_delta - last_path_delay_delta));
+
+ last_path_delay_delta = path_delay_delta;
+
+ if (pip != PipId())
+ accumulated_path_delay += ctx->getPipDelay(pip).maxDelay();
+ accumulated_path_delay += ctx->getWireDelay(cursor).maxDelay();
+ }
+
+ if (pip == PipId())
+ NPNR_ASSERT(cursor == src_wire);
+
+ if (!net_info->wires.count(cursor) || net_info->wires.at(cursor).pip != pip) {
+ if (!ctx->checkWireAvail(cursor)) {
+ ripup_wire(cursor);
+ NPNR_ASSERT(ctx->checkWireAvail(cursor));
+ }
+
+ if (pip != PipId() && !ctx->checkPipAvail(pip)) {
+ ripup_pip(pip);
+ NPNR_ASSERT(ctx->checkPipAvail(pip));
+ }
+
+ if (pip == PipId()) {
+ if (ctx->debug)
+ log(" bind wire %s\n", ctx->nameOfWire(cursor));
+ ctx->bindWire(cursor, net_info, STRENGTH_WEAK);
+ } else {
+ if (ctx->debug)
+ log(" bind pip %s\n", ctx->nameOfPip(pip));
+ ctx->bindPip(pip, net_info, STRENGTH_WEAK);
+ }
+ }
+
+ wire_to_arcs[cursor].insert(arc);
+ arc_to_wires[arc].insert(cursor);
+
+ if (pip == PipId())
+ break;
+
+ cursor = ctx->getPipSrcWire(pip);
+ }
+
+ if (ripup_flag)
+ arcs_with_ripup++;
+ else
+ arcs_without_ripup++;
+
+ return true;
+ }
+
+ delay_t find_slack_thresh()
+ {
+ // If more than 5% of arcs have negative slack; use the 5% threshold as a ripup criteria
+ int arc_count = 0;
+ int failed_count = 0;
+ delay_t default_thresh = ctx->getDelayEpsilon();
+
+ for (auto &net : ctx->nets) {
+ NetInfo *ni = net.second.get();
+ if (skip_net(ni))
+ continue;
+ for (auto &usr : ni->users) {
+ ++arc_count;
+ delay_t slack = tmg.get_setup_slack(CellPortKey(usr));
+ if (slack == std::numeric_limits<delay_t>::min())
+ continue;
+ if (slack < default_thresh)
+ ++failed_count;
+ }
+ }
+
+ if (arc_count < 50 || (failed_count < (0.05 * arc_count))) {
+ return default_thresh;
+ }
+
+ std::vector<delay_t> slacks;
+ for (auto &net : ctx->nets) {
+ NetInfo *ni = net.second.get();
+ if (skip_net(ni))
+ continue;
+ for (auto &usr : ni->users) {
+ delay_t slack = tmg.get_setup_slack(CellPortKey(usr));
+ if (slack == std::numeric_limits<delay_t>::min())
+ continue;
+ slacks.push_back(slack);
+ }
+ }
+ std::sort(slacks.begin(), slacks.end());
+ delay_t thresh = slacks.at(int(slacks.size() * 0.05));
+ log_warning("%.f%% of arcs have failing slack; using %.2fns as ripup threshold. Consider a reduced Fmax "
+ "constraint.\n",
+ (100.0 * failed_count) / arc_count, ctx->getDelayNS(thresh));
+ return thresh;
+ }
+};
+
+} // namespace
+
+NEXTPNR_NAMESPACE_BEGIN
+
+Router1Cfg::Router1Cfg(Context *ctx)
+{
+ maxIterCnt = ctx->setting<int>("router1/maxIterCnt", 200);
+ cleanupReroute = ctx->setting<bool>("router1/cleanupReroute", true);
+ fullCleanupReroute = ctx->setting<bool>("router1/fullCleanupReroute", true);
+ useEstimate = ctx->setting<bool>("router1/useEstimate", true);
+
+ wireRipupPenalty = ctx->getRipupDelayPenalty();
+ netRipupPenalty = 10 * ctx->getRipupDelayPenalty();
+ reuseBonus = wireRipupPenalty / 2;
+
+ estimatePrecision = 100 * ctx->getRipupDelayPenalty();
+}
+
+bool router1(Context *ctx, const Router1Cfg &cfg)
+{
+ try {
+ log_break();
+ log_info("Routing..\n");
+ ScopeLock<Context> lock(ctx);
+ auto rstart = std::chrono::high_resolution_clock::now();
+
+ log_info("Setting up routing queue.\n");
+
+ Router1 router(ctx, cfg);
+ router.setup();
+#ifndef NDEBUG
+ router.check();
+#endif
+
+ log_info("Routing %d arcs.\n", int(router.arc_queue.size()));
+
+ int iter_cnt = 0;
+ int last_arcs_with_ripup = 0;
+ int last_arcs_without_ripup = 0;
+ int timing_fail_count = 0;
+ bool timing_ripup = ctx->setting<bool>("router/tmg_ripup", false);
+ delay_t ripup_slack = 0;
+
+ log_info(" | (re-)routed arcs | delta | remaining| time spent |\n");
+ log_info(" IterCnt | w/ripup wo/ripup | w/r wo/r | arcs| batch(sec) total(sec)|\n");
+
+ auto prev_time = rstart;
+ while (!router.arc_queue.empty()) {
+ if (++iter_cnt % 1000 == 0) {
+ auto curr_time = std::chrono::high_resolution_clock::now();
+ log_info("%10d | %8d %10d | %4d %5d | %9d| %10.02f %10.02f|\n", iter_cnt, router.arcs_with_ripup,
+ router.arcs_without_ripup, router.arcs_with_ripup - last_arcs_with_ripup,
+ router.arcs_without_ripup - last_arcs_without_ripup, int(router.arc_queue.size()),
+ std::chrono::duration<float>(curr_time - prev_time).count(),
+ std::chrono::duration<float>(curr_time - rstart).count());
+ prev_time = curr_time;
+ last_arcs_with_ripup = router.arcs_with_ripup;
+ last_arcs_without_ripup = router.arcs_without_ripup;
+ ctx->yield();
+#ifndef NDEBUG
+ router.check();
+#endif
+ }
+
+ if (ctx->debug)
+ log("-- %d --\n", iter_cnt);
+
+ arc_key arc = router.arc_queue_pop();
+
+ if (!router.route_arc(arc, true)) {
+ log_warning("Failed to find a route for arc %d of net %s.\n", arc.user_idx.idx(),
+ ctx->nameOf(arc.net_info));
+#ifndef NDEBUG
+ router.check();
+ ctx->check();
+#endif
+ return false;
+ }
+ // Timing driven ripup
+ if (timing_ripup && router.arc_queue.empty() && timing_fail_count < 50) {
+ ++timing_fail_count;
+ router.tmg.run();
+ delay_t wns = 0, tns = 0;
+ if (timing_fail_count == 1)
+ ripup_slack = router.find_slack_thresh();
+ for (auto &net : ctx->nets) {
+ NetInfo *ni = net.second.get();
+ if (router.skip_net(ni))
+ continue;
+ bool is_locked = false;
+ for (auto &wire : ni->wires) {
+ if (wire.second.strength > STRENGTH_STRONG)
+ is_locked = true;
+ }
+ if (is_locked)
+ continue;
+ for (auto &usr : ni->users) {
+ delay_t slack = router.tmg.get_setup_slack(CellPortKey(usr));
+ if (slack == std::numeric_limits<delay_t>::min())
+ continue;
+ if (slack < 0) {
+ wns = std::min(wns, slack);
+ tns += slack;
+ }
+ if (slack <= ripup_slack) {
+ for (WireId w : ctx->getNetinfoSinkWires(ni, usr)) {
+ if (ctx->checkWireAvail(w))
+ continue;
+ router.ripup_wire(w);
+ }
+ }
+ }
+ }
+ log_info(" %d arcs ripped up due to negative slack WNS=%.02fns TNS=%.02fns.\n",
+ int(router.arc_queue.size()), ctx->getDelayNS(wns), ctx->getDelayNS(tns));
+ iter_cnt = 0;
+ router.wireScores.clear();
+ router.netScores.clear();
+ }
+ }
+ auto rend = std::chrono::high_resolution_clock::now();
+ log_info("%10d | %8d %10d | %4d %5d | %9d| %10.02f %10.02f|\n", iter_cnt, router.arcs_with_ripup,
+ router.arcs_without_ripup, router.arcs_with_ripup - last_arcs_with_ripup,
+ router.arcs_without_ripup - last_arcs_without_ripup, int(router.arc_queue.size()),
+ std::chrono::duration<float>(rend - prev_time).count(),
+ std::chrono::duration<float>(rend - rstart).count());
+ log_info("Routing complete.\n");
+ ctx->yield();
+ log_info("Router1 time %.02fs\n", std::chrono::duration<float>(rend - rstart).count());
+
+#ifndef NDEBUG
+ router.check();
+ ctx->check();
+ log_assert(ctx->checkRoutedDesign());
+#endif
+
+ log_info("Checksum: 0x%08x\n", ctx->checksum());
+ timing_analysis(ctx, true /* slack_histogram */, true /* print_fmax */, true /* print_path */,
+ true /* warn_on_failure */, true /* update_results */);
+
+ return true;
+ } catch (log_execution_error_exception) {
+#ifndef NDEBUG
+ ctx->lock();
+ ctx->check();
+ ctx->unlock();
+#endif
+ return false;
+ }
+}
+
+bool Context::checkRoutedDesign() const
+{
+ const Context *ctx = getCtx();
+
+ for (auto &net_it : ctx->nets) {
+ NetInfo *net_info = net_it.second.get();
+
+#ifdef ARCH_ECP5
+ if (net_info->is_global)
+ continue;
+#endif
+
+ if (ctx->debug)
+ log("checking net %s\n", ctx->nameOf(net_info));
+
+ if (net_info->users.empty()) {
+ if (ctx->debug)
+ log(" net without sinks\n");
+ log_assert(net_info->wires.empty());
+ continue;
+ }
+
+ bool found_unrouted = false;
+ bool found_loop = false;
+ bool found_stub = false;
+
+ struct ExtraWireInfo
+ {
+ int order_num = 0;
+ pool<WireId> children;
+ };
+
+ dict<WireId, std::unique_ptr<ExtraWireInfo>> db;
+
+ for (auto &it : net_info->wires) {
+ WireId w = it.first;
+ PipId p = it.second.pip;
+
+ if (p != PipId()) {
+ log_assert(ctx->getPipDstWire(p) == w);
+ db.emplace(ctx->getPipSrcWire(p), std::make_unique<ExtraWireInfo>()).first->second->children.insert(w);
+ }
+ }
+
+ auto src_wire = ctx->getNetinfoSourceWire(net_info);
+ if (src_wire == WireId()) {
+ log_assert(net_info->driver.cell == nullptr);
+ if (ctx->debug)
+ log(" undriven and unrouted\n");
+ continue;
+ }
+
+ if (net_info->wires.count(src_wire) == 0) {
+ if (ctx->debug)
+ log(" source (%s) not bound to net\n", ctx->nameOfWire(src_wire));
+ found_unrouted = true;
+ }
+
+ dict<WireId, store_index<PortRef>> dest_wires;
+ for (auto user : net_info->users.enumerate()) {
+ for (auto dst_wire : ctx->getNetinfoSinkWires(net_info, user.value)) {
+ log_assert(dst_wire != WireId());
+ dest_wires[dst_wire] = user.index;
+
+ if (net_info->wires.count(dst_wire) == 0) {
+ if (ctx->debug)
+ log(" sink %d (%s) not bound to net\n", user.index.idx(), ctx->nameOfWire(dst_wire));
+ found_unrouted = true;
+ }
+ }
+ }
+
+ std::function<void(WireId, int)> setOrderNum;
+ pool<WireId> logged_wires;
+
+ setOrderNum = [&](WireId w, int num) {
+ auto &db_entry = *db.emplace(w, std::make_unique<ExtraWireInfo>()).first->second;
+ if (db_entry.order_num != 0) {
+ found_loop = true;
+ log(" %*s=> loop\n", 2 * num, "");
+ return;
+ }
+ db_entry.order_num = num;
+ for (WireId child : db_entry.children) {
+ if (ctx->debug) {
+ log(" %*s-> %s\n", 2 * num, "", ctx->nameOfWire(child));
+ logged_wires.insert(child);
+ }
+ setOrderNum(child, num + 1);
+ }
+ if (db_entry.children.empty()) {
+ if (dest_wires.count(w) != 0) {
+ if (ctx->debug)
+ log(" %*s=> sink %d\n", 2 * num, "", dest_wires.at(w).idx());
+ } else {
+ if (ctx->debug)
+ log(" %*s=> stub\n", 2 * num, "");
+ found_stub = true;
+ }
+ }
+ };
+
+ if (ctx->debug) {
+ log(" driver: %s\n", ctx->nameOfWire(src_wire));
+ logged_wires.insert(src_wire);
+ }
+ setOrderNum(src_wire, 1);
+
+ pool<WireId> dangling_wires;
+
+ for (auto &it : db) {
+ auto &db_entry = *it.second;
+ if (db_entry.order_num == 0)
+ dangling_wires.insert(it.first);
+ }
+
+ if (ctx->debug) {
+ if (dangling_wires.empty()) {
+ log(" no dangling wires.\n");
+ } else {
+ pool<WireId> root_wires = dangling_wires;
+
+ for (WireId w : dangling_wires) {
+ for (WireId c : db[w]->children)
+ root_wires.erase(c);
+ }
+
+ for (WireId w : root_wires) {
+ log(" dangling wire: %s\n", ctx->nameOfWire(w));
+ logged_wires.insert(w);
+ setOrderNum(w, 1);
+ }
+
+ for (WireId w : dangling_wires) {
+ if (logged_wires.count(w) == 0)
+ log(" loop: %s -> %s\n", ctx->nameOfWire(ctx->getPipSrcWire(net_info->wires.at(w).pip)),
+ ctx->nameOfWire(w));
+ }
+ }
+ }
+
+ bool fail = false;
+
+ if (found_unrouted) {
+ if (ctx->debug)
+ log("check failed: found unrouted arcs\n");
+ fail = true;
+ }
+
+ if (found_loop) {
+ if (ctx->debug)
+ log("check failed: found loops\n");
+ fail = true;
+ }
+
+ if (found_stub) {
+ if (ctx->debug)
+ log("check failed: found stubs\n");
+ fail = true;
+ }
+
+ if (!dangling_wires.empty()) {
+ if (ctx->debug)
+ log("check failed: found dangling wires\n");
+ fail = true;
+ }
+
+ if (fail)
+ return false;
+ }
+
+ return true;
+}
+
+bool Context::getActualRouteDelay(WireId src_wire, WireId dst_wire, delay_t *delay, dict<WireId, PipId> *route,
+ bool useEstimate)
+{
+ // FIXME
+ return false;
+}
+
+NEXTPNR_NAMESPACE_END