router2: Improved bidir routing and data structures

Signed-off-by: gatecat <gatecat@ds0.me>
This commit is contained in:
gatecat 2021-08-14 15:02:26 +01:00
parent 4d54b62e63
commit 64f6b8bc67

View File

@ -61,6 +61,7 @@ struct Router2
struct PerNetData
{
WireId src_wire;
dict<WireId, std::pair<PipId, int>> wires;
std::vector<std::vector<PerArcData>> arcs;
ArcBounds bb;
// Coordinates of the center of the net, used for the weight-to-average
@ -74,7 +75,6 @@ struct Router2
{
float cost;
float togo_cost;
delay_t delay;
float total() const { return cost + togo_cost; }
};
@ -82,9 +82,8 @@ struct Router2
{
// nextpnr
WireId w;
// net --> number of arcs; driving pip
boost::container::flat_map<int, std::pair<int, PipId>> bound_nets;
// Historical congestion cost
int curr_cong = 0;
float hist_cong_cost = 1.0;
// Wire is unavailable as locked to another arc
bool unavailable = false;
@ -93,25 +92,10 @@ struct Router2
// The notional location of the wire, to guarantee thread safety
int16_t x = 0, y = 0;
// Visit data
struct
{
bool dirty = false, visited = false;
PipId pip;
WireScore score;
} visit;
PipId pip_fwd, pip_bwd;
bool visited_fwd, visited_bwd;
};
float present_wire_cost(const PerWireData &w, int net_uid)
{
int other_sources = int(w.bound_nets.size());
if (w.bound_nets.count(net_uid))
other_sources -= 1;
if (other_sources == 0)
return 1.0f;
else
return 1 + other_sources * curr_cong_weight;
}
Context *ctx;
Router2Cfg cfg;
@ -213,7 +197,9 @@ struct Router2
if (bound != nullptr) {
auto iter = bound->wires.find(wire);
if (iter != bound->wires.end()) {
pwd.bound_nets[bound->udata] = std::make_pair(0, bound->wires.at(wire).pip);
auto &nd = nets.at(bound->udata);
nd.wires[wire] = std::make_pair(bound->wires.at(wire).pip, 0);
pwd.curr_cong = 1;
if (bound->wires.at(wire).strength == STRENGTH_PLACER) {
pwd.reserved_net = bound->udata;
} else if (bound->wires.at(wire).strength > STRENGTH_PLACER) {
@ -247,13 +233,10 @@ struct Router2
struct QueuedWire
{
explicit QueuedWire(int wire = -1, PipId pip = PipId(), Loc loc = Loc(), WireScore score = WireScore{},
int randtag = 0)
: wire(wire), pip(pip), loc(loc), score(score), randtag(randtag){};
explicit QueuedWire(int wire = -1, WireScore score = WireScore{}, int randtag = 0)
: wire(wire), score(score), randtag(randtag){};
int wire;
PipId pip;
Loc loc;
WireScore score;
int randtag = 0;
@ -281,19 +264,20 @@ struct Router2
std::vector<std::pair<size_t, size_t>> route_arcs;
std::priority_queue<QueuedWire, std::vector<QueuedWire>, QueuedWire::Greater> queue;
std::priority_queue<QueuedWire, std::vector<QueuedWire>, QueuedWire::Greater> fwd_queue, bwd_queue;
// Special case where one net has multiple logical arcs to the same physical sink
pool<WireId> processed_sinks;
// Backwards routing
std::queue<int> backwards_queue;
std::vector<int> dirty_wires;
// Thread bounding box
ArcBounds bb;
DeterministicRNG rng;
// Used to add existing routing to the heap
pool<WireId> in_wire_by_loc;
dict<std::pair<int, int>, pool<WireId>> wire_by_loc;
};
bool thread_test_wire(ThreadContext &t, PerWireData &w)
@ -322,42 +306,47 @@ struct Router2
log(__VA_ARGS__); \
} while (0)
void bind_pip_internal(NetInfo *net, size_t user, int wire, PipId pip)
void bind_pip_internal(PerNetData &net, size_t user, int wire, PipId pip)
{
auto &b = flat_wires.at(wire).bound_nets[net->udata];
++b.first;
if (b.first == 1) {
b.second = pip;
auto &wd = flat_wires.at(wire);
auto found = net.wires.find(wd.w);
if (found == net.wires.end()) {
// Not yet used for any arcs of this net, add to list
net.wires.emplace(wd.w, std::make_pair(pip, 1));
// Increase bound count of wire by 1
++wd.curr_cong;
} else {
if (b.second != pip)
log_error("internal inconsistency: attempting to bind pip %s to net %s, but wire %s is already driven "
"by pip %s\n",
ctx->nameOfPip(pip), ctx->nameOf(net), ctx->nameOfWire(flat_wires.at(wire).w),
ctx->nameOfPip(b.second));
// Already used for at least one other arc of this net
// Don't allow two uphill PIPs for the same net and wire
NPNR_ASSERT(found->second.first == pip);
// Increase the count of bound arcs
++found->second.second;
}
}
void unbind_pip_internal(NetInfo *net, size_t user, WireId wire)
void unbind_pip_internal(PerNetData &net, size_t user, WireId wire)
{
auto &b = wire_data(wire).bound_nets.at(net->udata);
--b.first;
NPNR_ASSERT(b.first >= 0);
if (b.first == 0) {
wire_data(wire).bound_nets.erase(net->udata);
auto &wd = wire_data(wire);
auto &b = net.wires.at(wd.w);
--b.second;
if (b.second == 0) {
// No remaining arcs of this net bound to this wire
--wd.curr_cong;
net.wires.erase(wd.w);
}
}
void ripup_arc(NetInfo *net, size_t user, size_t phys_pin)
{
auto &ad = nets.at(net->udata).arcs.at(user).at(phys_pin);
auto &nd = nets.at(net->udata);
auto &ad = nd.arcs.at(user).at(phys_pin);
if (!ad.routed)
return;
WireId src = nets.at(net->udata).src_wire;
WireId cursor = ad.sink_wire;
while (cursor != src) {
auto &wd = wire_data(cursor);
PipId pip = wd.bound_nets.at(net->udata).second;
unbind_pip_internal(net, user, cursor);
PipId pip = nd.wires.at(cursor).first;
unbind_pip_internal(nd, user, cursor);
cursor = ctx->getPipSrcWire(pip);
}
ad.routed = false;
@ -369,21 +358,15 @@ struct Router2
auto &nd = nets.at(net->udata);
float base_cost = ctx->getDelayNS(ctx->getPipDelay(pip).maxDelay() + ctx->getWireDelay(wire).maxDelay() +
ctx->getDelayEpsilon());
float present_cost = present_wire_cost(wd, net->udata);
int overuse = wd.curr_cong;
float hist_cost = wd.hist_cong_cost;
float bias_cost = 0;
int source_uses = 0;
if (wd.bound_nets.count(net->udata))
source_uses = wd.bound_nets.at(net->udata).first;
if (timing_driven) {
float max_bound_crit = 0;
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).at(phys_pin).arc_crit < (max_bound_crit + 0.01)) {
present_cost *= 1.5;
}
if (nd.wires.count(wire)) {
overuse -= 1;
source_uses = nd.wires.at(wire).second;
}
float present_cost = 1.0f + overuse * curr_cong_weight;
if (pip != PipId()) {
Loc pl = ctx->getPipLocation(pip);
bias_cost = cfg.bias_cost_factor * (base_cost / int(net->users.size())) *
@ -392,27 +375,30 @@ struct Router2
return base_cost * hist_cost * present_cost / (1 + source_uses) + bias_cost;
}
float get_togo_cost(NetInfo *net, size_t user, int wire, WireId sink, delay_t *delay)
float get_togo_cost(NetInfo *net, size_t user, int wire, WireId src_sink, bool bwd = false)
{
auto &nd = nets.at(net->udata);
auto &wd = flat_wires[wire];
int source_uses = 0;
if (wd.bound_nets.count(net->udata))
source_uses = wd.bound_nets.at(net->udata).first;
if (nd.wires.count(wd.w)) {
source_uses = nd.wires.at(wd.w).second;
}
// FIXME: timing/wirelength balance?
*delay = ctx->estimateDelay(wd.w, sink);
return (ctx->getDelayNS(*delay) / (1 + source_uses)) + cfg.ipin_cost_adder;
delay_t est_delay = ctx->estimateDelay(bwd ? src_sink : wd.w, bwd ? wd.w : src_sink);
return (ctx->getDelayNS(est_delay) / (1 + source_uses)) + cfg.ipin_cost_adder;
}
bool check_arc_routing(NetInfo *net, size_t usr, size_t phys_pin)
{
auto &ad = nets.at(net->udata).arcs.at(usr).at(phys_pin);
auto &nd = nets.at(net->udata);
auto &ad = nd.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)) {
while (nd.wires.count(cursor)) {
auto &wd = wire_data(cursor);
if (wd.bound_nets.size() != 1)
if (wd.curr_cong != 1)
return false;
auto &uh = wd.bound_nets.at(net->udata).second;
auto &uh = nd.wires.at(cursor).first;
if (uh == PipId())
break;
cursor = ctx->getPipSrcWire(uh);
@ -422,16 +408,16 @@ struct Router2
void record_prerouted_net(NetInfo *net, size_t usr, size_t phys_pin)
{
auto &ad = nets.at(net->udata).arcs.at(usr).at(phys_pin);
auto &nd = nets.at(net->udata);
auto &ad = nd.arcs.at(usr).at(phys_pin);
ad.routed = true;
WireId src = nets.at(net->udata).src_wire;
WireId cursor = ad.sink_wire;
while (cursor != src) {
size_t wire_idx = wire_to_idx.at(cursor);
auto &wd = flat_wires.at(wire_idx);
PipId pip = wd.bound_nets.at(net->udata).second;
bind_pip_internal(net, usr, wire_idx, pip);
PipId pip = nd.wires.at(cursor).first;
bind_pip_internal(nd, usr, wire_idx, pip);
cursor = ctx->getPipSrcWire(pip);
}
}
@ -521,28 +507,70 @@ struct Router2
void reset_wires(ThreadContext &t)
{
for (auto w : t.dirty_wires) {
flat_wires[w].visit.visited = false;
flat_wires[w].visit.dirty = false;
flat_wires[w].visit.pip = PipId();
flat_wires[w].visit.score = WireScore();
flat_wires[w].pip_fwd = PipId();
flat_wires[w].pip_bwd = PipId();
flat_wires[w].visited_fwd = false;
flat_wires[w].visited_bwd = false;
}
t.dirty_wires.clear();
}
void set_visited(ThreadContext &t, int wire, PipId pip, WireScore score)
// These nets have very-high-fanout pips and special rules must be followed (only working backwards) to avoid
// crippling perf
bool is_pseudo_const_net(const NetInfo *net)
{
auto &v = flat_wires.at(wire).visit;
if (!v.dirty)
t.dirty_wires.push_back(wire);
v.dirty = true;
v.visited = true;
v.pip = pip;
v.score = score;
#ifdef ARCH_NEXUS
if (net->driver.cell != nullptr && net->driver.cell->type == id_VCC_DRV)
return true;
#endif
return false;
}
bool was_visited(int wire) { return flat_wires.at(wire).visit.visited; }
void update_wire_by_loc(ThreadContext &t, NetInfo *net, size_t i, size_t phys_pin, bool is_mt)
{
if (is_pseudo_const_net(net))
return;
auto &nd = nets.at(net->udata);
auto &ad = nd.arcs.at(i).at(phys_pin);
WireId cursor = ad.sink_wire;
if (!nd.wires.count(cursor))
return;
while (cursor != nd.src_wire) {
if (!t.in_wire_by_loc.count(cursor)) {
t.in_wire_by_loc.insert(cursor);
for (auto dh : ctx->getPipsDownhill(cursor)) {
Loc dh_loc = ctx->getPipLocation(dh);
t.wire_by_loc[std::make_pair(dh_loc.x, dh_loc.y)].insert(cursor);
}
}
cursor = ctx->getPipSrcWire(nd.wires.at(cursor).first);
}
}
// Functions for marking wires as visited, and checking if they have already been visited
void set_visited_fwd(ThreadContext &t, int wire, PipId pip)
{
auto &wd = flat_wires.at(wire);
if (!wd.visited_fwd && !wd.visited_bwd)
t.dirty_wires.push_back(wire);
wd.pip_fwd = pip;
wd.visited_fwd = true;
}
void set_visited_bwd(ThreadContext &t, int wire, PipId pip)
{
auto &wd = flat_wires.at(wire);
if (!wd.visited_fwd && !wd.visited_bwd)
t.dirty_wires.push_back(wire);
wd.pip_bwd = pip;
wd.visited_bwd = true;
}
bool was_visited_fwd(int wire) { return flat_wires.at(wire).visited_fwd; }
bool was_visited_bwd(int wire) { return flat_wires.at(wire).visited_bwd; }
ArcRouteResult route_arc(ThreadContext &t, NetInfo *net, size_t i, size_t phys_pin, bool is_mt, bool is_bb = true)
{
// Do some initial lookups and checks
auto arc_start = std::chrono::high_resolution_clock::now();
auto &nd = nets[net->udata];
auto &ad = nd.arcs.at(i).at(phys_pin);
@ -562,259 +590,246 @@ struct Router2
if (t.processed_sinks.count(dst_wire))
return ARC_SUCCESS;
if (!t.queue.empty()) {
// We have two modes:
// 0. starting within a small range of existing routing
// 1. expanding from all routing
int mode = 0;
if (net->users.size() < 4 || nd.wires.empty())
mode = 1;
// This records the point where forwards and backwards routing met
int midpoint_wire = -1;
int explored = 1;
for (; mode < 2; mode++) {
// Clear out the queues
if (!t.fwd_queue.empty()) {
std::priority_queue<QueuedWire, std::vector<QueuedWire>, QueuedWire::Greater> new_queue;
t.queue.swap(new_queue);
t.fwd_queue.swap(new_queue);
}
if (!t.backwards_queue.empty()) {
std::queue<int> new_queue;
t.backwards_queue.swap(new_queue);
if (!t.bwd_queue.empty()) {
std::priority_queue<QueuedWire, std::vector<QueuedWire>, QueuedWire::Greater> new_queue;
t.bwd_queue.swap(new_queue);
}
// First try strongly iteration-limited routing backwards BFS
// this will deal with certain nets faster than forward A*
// and comes at a minimal performance cost for the others
// This could also be used to speed up forwards routing by a hybrid
// bidirectional approach
int backwards_iter = 0;
int backwards_limit =
ctx->getBelGlobalBuf(net->driver.cell->bel) ? cfg.global_backwards_max_iter : cfg.backwards_max_iter;
t.backwards_queue.push(wire_to_idx.at(dst_wire));
set_visited(t, wire_to_idx.at(dst_wire), PipId(), WireScore());
while (!t.backwards_queue.empty() && backwards_iter < backwards_limit) {
int cursor = t.backwards_queue.front();
t.backwards_queue.pop();
auto &cwd = flat_wires[cursor];
PipId cpip;
if (cwd.bound_nets.count(net->udata)) {
// If we can tack onto existing routing; try that
// Only do this if the existing routing is uncontented; however
int cursor2 = cursor;
bool bwd_merge_fail = false;
while (flat_wires.at(cursor2).bound_nets.count(net->udata)) {
if (flat_wires.at(cursor2).bound_nets.size() > 1) {
bwd_merge_fail = true;
break;
}
PipId p = flat_wires.at(cursor2).bound_nets.at(net->udata).second;
if (p == PipId())
break;
cursor2 = wire_to_idx.at(ctx->getPipSrcWire(p));
}
if (!bwd_merge_fail && cursor2 == src_wire_idx) {
// Found a path to merge to existing routing; backwards
cursor2 = cursor;
while (flat_wires.at(cursor2).bound_nets.count(net->udata)) {
PipId p = flat_wires.at(cursor2).bound_nets.at(net->udata).second;
if (p == PipId())
break;
cursor2 = wire_to_idx.at(ctx->getPipSrcWire(p));
set_visited(t, cursor2, p, WireScore());
}
break;
}
cpip = cwd.bound_nets.at(net->udata).second;
}
bool did_something = false;
for (auto uh : ctx->getPipsUphill(flat_wires[cursor].w)) {
did_something = true;
if (!ctx->checkPipAvailForNet(uh, net))
continue;
if (cpip != PipId() && cpip != uh)
continue; // don't allow multiple pips driving a wire with a net
int next = wire_to_idx.at(ctx->getPipSrcWire(uh));
if (was_visited(next))
continue; // skip wires that have already been visited
auto &wd = flat_wires[next];
if (wd.unavailable)
continue;
if (wd.reserved_net != -1 && wd.reserved_net != net->udata)
continue;
if (wd.bound_nets.size() > 1 || (wd.bound_nets.size() == 1 && !wd.bound_nets.count(net->udata)))
continue; // never allow congestion in backwards routing
if (!thread_test_wire(t, wd))
continue; // thread safety issue
t.backwards_queue.push(next);
set_visited(t, next, uh, WireScore());
}
if (did_something)
++backwards_iter;
}
// Check if backwards routing succeeded in reaching source
if (was_visited(src_wire_idx)) {
ROUTE_LOG_DBG(" Routed (backwards): ");
int cursor_fwd = src_wire_idx;
bind_pip_internal(net, i, src_wire_idx, PipId());
while (was_visited(cursor_fwd)) {
auto &v = flat_wires.at(cursor_fwd).visit;
if (v.pip == PipId()) {
break;
}
cursor_fwd = wire_to_idx.at(ctx->getPipDstWire(v.pip));
bind_pip_internal(net, i, cursor_fwd, v.pip);
if (ctx->debug) {
auto &wd = flat_wires.at(cursor_fwd);
ROUTE_LOG_DBG(" wire: %s (curr %d hist %f)\n", ctx->nameOfWire(wd.w),
int(wd.bound_nets.size()) - 1, wd.hist_cong_cost);
}
}
NPNR_ASSERT(cursor_fwd == dst_wire_idx);
ad.routed = true;
t.processed_sinks.insert(dst_wire);
// Unvisit any previously visited wires
reset_wires(t);
return ARC_SUCCESS;
ROUTE_LOG_DBG("src_wire = %s -> dst_wire = %s\n", ctx->nameOfWire(src_wire), ctx->nameOfWire(dst_wire));
// Add 'forward' direction startpoints to queue
auto seed_queue_fwd = [&](WireId wire, float wire_cost = 0) {
WireScore base_score;
base_score.cost = wire_cost;
int wire_idx = wire_to_idx.at(wire);
base_score.togo_cost = get_togo_cost(net, i, wire_idx, dst_wire, false);
t.fwd_queue.push(QueuedWire(wire_idx, base_score));
set_visited_fwd(t, wire_idx, PipId());
};
auto &dst_data = flat_wires.at(dst_wire_idx);
// Look for nearby existing routing
for (int dy = -cfg.bb_margin_y; dy <= cfg.bb_margin_y; dy++)
for (int dx = -cfg.bb_margin_x; dx <= cfg.bb_margin_x; dx++) {
auto fnd = t.wire_by_loc.find(std::make_pair(dst_data.x + dx, dst_data.y + dy));
if (fnd == t.wire_by_loc.end())
continue;
for (WireId wire : fnd->second) {
ROUTE_LOG_DBG(" seeding with %s\n", ctx->nameOfWire(wire));
seed_queue_fwd(wire);
}
}
// Normal forwards A* routing
reset_wires(t);
if (mode == 0 && t.fwd_queue.size() < 4)
continue;
if (mode == 1 && !is_pseudo_const_net(net)) {
// Seed forwards with the source wire, if less than 8 existing wires added
seed_queue_fwd(src_wire);
} else {
set_visited_fwd(t, src_wire_idx, PipId());
}
auto seed_queue_bwd = [&](WireId wire) {
WireScore base_score;
base_score.cost = 0;
base_score.delay = ctx->getWireDelay(src_wire).maxDelay();
delay_t forward;
base_score.togo_cost = get_togo_cost(net, i, src_wire_idx, dst_wire, &forward);
int wire_idx = wire_to_idx.at(wire);
base_score.togo_cost = get_togo_cost(net, i, wire_idx, src_wire, true);
t.bwd_queue.push(QueuedWire(wire_idx, base_score));
set_visited_bwd(t, wire_idx, PipId());
};
ROUTE_LOG_DBG("src_wire = %s -> dst_wire = %s (backward: %s, forward: %s, sum: %s)\n",
ctx->nameOfWire(src_wire), ctx->nameOfWire(dst_wire), std::to_string(base_score.delay).c_str(),
std::to_string(forward).c_str(), std::to_string(base_score.delay + forward).c_str());
// Add source wire to queue
t.queue.push(QueuedWire(src_wire_idx, PipId(), Loc(), base_score));
set_visited(t, src_wire_idx, PipId(), base_score);
// Seed backwards with the dest wire
seed_queue_bwd(dst_wire);
int toexplore = 25000 * std::max(1, (ad.bb.x1 - ad.bb.x0) + (ad.bb.y1 - ad.bb.y0));
int iter = 0;
int explored = 1;
bool debug_arc = /*usr.cell->type.str(ctx).find("RAMB") != std::string::npos && (usr.port ==
ctx->id("ADDRATIEHIGH0") || usr.port == ctx->id("ADDRARDADDRL0"))*/
false;
// When running without a bounding box, the toexplore limit should be
// suspended until a solution is reached. Once a solution is found,
// the toexplore limit should be used again to prevent requiring the
// router to drain the routing queue.
//
// Note that is it important that the must_drain_queue be set to true
// when running without a bb to ensure that a routing failure is
// because there is not route, rather than just because the toexplore
// heuristic is incorrect.
bool must_drain_queue = !is_bb;
while (!t.queue.empty() && (must_drain_queue || iter < toexplore)) {
auto curr = t.queue.top();
auto &d = flat_wires.at(curr.wire);
t.queue.pop();
// Mode 0 required both queues to be live
while (((mode == 0) ? (!t.fwd_queue.empty() && !t.bwd_queue.empty())
: (!t.fwd_queue.empty() || !t.bwd_queue.empty())) &&
(!is_bb || iter < toexplore)) {
++iter;
#if 0
ROUTE_LOG_DBG("current wire %s\n", ctx->nameOfWire(d.w));
#endif
// Explore all pips downhill of cursor
for (auto dh : ctx->getPipsDownhill(d.w)) {
if (!t.fwd_queue.empty()) {
// Explore forwards
auto curr = t.fwd_queue.top();
t.fwd_queue.pop();
if (was_visited_bwd(curr.wire)) {
// Meet in the middle; done
midpoint_wire = curr.wire;
break;
}
auto &curr_data = flat_wires.at(curr.wire);
for (PipId dh : ctx->getPipsDownhill(curr_data.w)) {
// Skip pips outside of box in bounding-box mode
#if 0
ROUTE_LOG_DBG("trying pip %s\n", ctx->nameOfPip(dh));
#endif
#if 0
int wire_intent = ctx->wireIntent(curr.wire);
if (is_bb && !hit_test_pip(ad.bb, ctx->getPipLocation(dh)) && wire_intent != ID_PSEUDO_GND && wire_intent != ID_PSEUDO_VCC)
continue;
#else
if (is_bb && !hit_test_pip(nd.bb, ctx->getPipLocation(dh)))
continue;
if (!ctx->checkPipAvailForNet(dh, net)) {
#if 0
ROUTE_LOG_DBG("Skipping pip %s because it is bound to net '%s' not net '%s'\n", ctx->nameOfPip(dh),
ctx->getBoundPipNet(dh) != nullptr ? ctx->getBoundPipNet(dh)->name.c_str(ctx)
: "<not a net>",
net->name.c_str(ctx));
#endif
if (!ctx->checkPipAvailForNet(dh, net))
continue;
}
#endif
// Evaluate score of next wire
WireId next = ctx->getPipDstWire(dh);
int next_idx = wire_to_idx.at(next);
if (was_visited(next_idx)) {
if (was_visited_fwd(next_idx)) {
// Don't expand the same node twice.
continue;
}
#if 1
if (debug_arc)
ROUTE_LOG_DBG(" src wire %s\n", ctx->nameOfWire(next));
#endif
auto &nwd = flat_wires.at(next_idx);
if (nwd.unavailable)
continue;
// Reserved for another net
if (nwd.reserved_net != -1 && nwd.reserved_net != net->udata)
continue;
if (nwd.bound_nets.count(net->udata) && nwd.bound_nets.at(net->udata).second != dh)
// Don't allow the same wire to be bound to the same net with a different driving pip
auto fnd_wire = nd.wires.find(next);
if (fnd_wire != nd.wires.end() && fnd_wire->second.first != dh)
continue;
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, 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, &forward);
#if 0
ROUTE_LOG_DBG(
"src_wire = %s -> next %s -> dst_wire = %s (backward: %s, forward: %s, sum: %s, cost = %f, "
"togo_cost = %f, total = %f), dt = %02fs\n",
ctx->nameOfWire(src_wire), ctx->nameOfWire(next), ctx->nameOfWire(dst_wire),
std::to_string(next_score.delay).c_str(), std::to_string(forward).c_str(),
std::to_string(next_score.delay + forward).c_str(), next_score.cost, next_score.togo_cost,
next_score.cost + next_score.togo_cost,
std::chrono::duration<float>(std::chrono::high_resolution_clock::now() - arc_start).count());
#endif
const auto &v = nwd.visit;
if (!v.visited || (v.score.total() > next_score.total())) {
++explored;
#if 0
ROUTE_LOG_DBG("exploring wire %s cost %f togo %f\n", ctx->nameOfWire(next), next_score.cost,
next_score.togo_cost);
#endif
// Add wire to queue if it meets criteria
t.queue.push(QueuedWire(next_idx, dh, ctx->getPipLocation(dh), next_score, t.rng.rng()));
set_visited(t, next_idx, dh, next_score);
if (next == dst_wire) {
toexplore = std::min(toexplore, iter + 5);
must_drain_queue = false;
next_score.togo_cost = cfg.estimate_weight * get_togo_cost(net, i, next_idx, dst_wire, false);
set_visited_fwd(t, next_idx, dh);
t.fwd_queue.push(QueuedWire(next_idx, next_score, t.rng.rng()));
}
}
}
}
if (was_visited(dst_wire_idx)) {
ROUTE_LOG_DBG(" Routed (explored %d wires): ", explored);
int cursor_bwd = dst_wire_idx;
while (was_visited(cursor_bwd)) {
auto &v = flat_wires.at(cursor_bwd).visit;
bind_pip_internal(net, i, cursor_bwd, v.pip);
if (ctx->debug) {
auto &wd = flat_wires.at(cursor_bwd);
ROUTE_LOG_DBG(" wire: %s (curr %d hist %f share %d)\n", ctx->nameOfWire(wd.w),
int(wd.bound_nets.size()) - 1, wd.hist_cong_cost,
wd.bound_nets.count(net->udata) ? wd.bound_nets.at(net->udata).first : 0);
}
if (v.pip == PipId()) {
NPNR_ASSERT(cursor_bwd == src_wire_idx);
if (!t.bwd_queue.empty()) {
// Explore backwards
auto curr = t.bwd_queue.top();
t.bwd_queue.pop();
if (was_visited_fwd(curr.wire)) {
// Meet in the middle; done
midpoint_wire = curr.wire;
break;
}
ROUTE_LOG_DBG(" pip: %s (%d, %d)\n", ctx->nameOfPip(v.pip), ctx->getPipLocation(v.pip).x,
ctx->getPipLocation(v.pip).y);
cursor_bwd = wire_to_idx.at(ctx->getPipSrcWire(v.pip));
auto &curr_data = flat_wires.at(curr.wire);
// Don't allow the same wire to be bound to the same net with a different driving pip
PipId bound_pip;
auto fnd_wire = nd.wires.find(curr_data.w);
if (fnd_wire != nd.wires.end())
bound_pip = fnd_wire->second.first;
for (PipId uh : ctx->getPipsUphill(curr_data.w)) {
if (bound_pip != PipId() && bound_pip != uh)
continue;
if (is_bb && !hit_test_pip(nd.bb, ctx->getPipLocation(uh)))
continue;
if (!ctx->checkPipAvailForNet(uh, net))
continue;
WireId next = ctx->getPipSrcWire(uh);
int next_idx = wire_to_idx.at(next);
if (was_visited_bwd(next_idx)) {
// Don't expand the same node twice.
continue;
}
auto &nwd = flat_wires.at(next_idx);
if (nwd.unavailable)
continue;
// Reserved for another net
if (nwd.reserved_net != -1 && nwd.reserved_net != net->udata)
continue;
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, phys_pin, next, uh);
next_score.togo_cost = cfg.estimate_weight * get_togo_cost(net, i, next_idx, src_wire, true);
set_visited_bwd(t, next_idx, uh);
t.bwd_queue.push(QueuedWire(next_idx, next_score, t.rng.rng()));
}
}
}
if (midpoint_wire != -1)
break;
}
ArcRouteResult result = ARC_SUCCESS;
if (midpoint_wire != -1) {
ROUTE_LOG_DBG(" Routed (explored %d wires): ", explored);
int cursor_bwd = midpoint_wire;
while (was_visited_fwd(cursor_bwd)) {
PipId pip = flat_wires.at(cursor_bwd).pip_fwd;
if (pip == PipId() && cursor_bwd != src_wire_idx)
break;
bind_pip_internal(nd, i, cursor_bwd, pip);
if (ctx->debug && !is_mt) {
auto &wd = flat_wires.at(cursor_bwd);
ROUTE_LOG_DBG(" fwd wire: %s (curr %d hist %f share %d)\n", ctx->nameOfWire(wd.w),
wd.curr_cong - 1, wd.hist_cong_cost, nd.wires.at(wd.w).second);
}
if (pip == PipId()) {
break;
}
ROUTE_LOG_DBG(" fwd pip: %s (%d, %d)\n", ctx->nameOfPip(pip), ctx->getPipLocation(pip).x,
ctx->getPipLocation(pip).y);
cursor_bwd = wire_to_idx.at(ctx->getPipSrcWire(pip));
}
while (cursor_bwd != src_wire_idx) {
// Tack onto existing routing
WireId bwd_w = flat_wires.at(cursor_bwd).w;
if (!nd.wires.count(bwd_w))
break;
auto &bound = nd.wires.at(bwd_w);
PipId pip = bound.first;
if (ctx->debug && !is_mt) {
auto &wd = flat_wires.at(cursor_bwd);
ROUTE_LOG_DBG(" ext wire: %s (curr %d hist %f share %d)\n", ctx->nameOfWire(wd.w),
wd.curr_cong - 1, wd.hist_cong_cost, bound.second);
}
bind_pip_internal(nd, i, cursor_bwd, pip);
if (pip == PipId())
break;
cursor_bwd = wire_to_idx.at(ctx->getPipSrcWire(pip));
}
NPNR_ASSERT(cursor_bwd == src_wire_idx);
int cursor_fwd = midpoint_wire;
while (was_visited_bwd(cursor_fwd)) {
PipId pip = flat_wires.at(cursor_fwd).pip_bwd;
if (pip == PipId()) {
break;
}
ROUTE_LOG_DBG(" bwd pip: %s (%d, %d)\n", ctx->nameOfPip(pip), ctx->getPipLocation(pip).x,
ctx->getPipLocation(pip).y);
cursor_fwd = wire_to_idx.at(ctx->getPipDstWire(pip));
bind_pip_internal(nd, i, cursor_fwd, pip);
if (ctx->debug && !is_mt) {
auto &wd = flat_wires.at(cursor_fwd);
ROUTE_LOG_DBG(" bwd wire: %s (curr %d hist %f share %d)\n", ctx->nameOfWire(wd.w),
wd.curr_cong - 1, wd.hist_cong_cost, nd.wires.at(wd.w).second);
}
}
NPNR_ASSERT(cursor_fwd == dst_wire_idx);
update_wire_by_loc(t, net, i, phys_pin, is_mt);
t.processed_sinks.insert(dst_wire);
ad.routed = true;
reset_wires(t);
auto arc_end = std::chrono::high_resolution_clock::now();
ROUTE_LOG_DBG("Routing arc %d of net '%s' (is_bb = %d) took %02fs\n", int(i), ctx->nameOf(net), is_bb,
std::chrono::duration<float>(arc_end - arc_start).count());
return ARC_SUCCESS;
} else {
auto arc_end = std::chrono::high_resolution_clock::now();
ROUTE_LOG_DBG("Failed routing arc %d of net '%s' (is_bb = %d) took %02fs\n", int(i), ctx->nameOf(net),
is_bb, std::chrono::duration<float>(arc_end - arc_start).count());
reset_wires(t);
return ARC_RETRY_WITHOUT_BB;
result = ARC_RETRY_WITHOUT_BB;
}
reset_wires(t);
return result;
}
#undef ARC_ERR
@ -837,6 +852,8 @@ struct Router2
bool have_failures = false;
t.processed_sinks.clear();
t.route_arcs.clear();
t.wire_by_loc.clear();
t.in_wire_by_loc.clear();
auto &nd = nets.at(net->udata);
for (size_t i = 0; i < net->users.size(); i++) {
auto &ad = nd.arcs.at(i);
@ -844,6 +861,7 @@ struct Router2
// Ripup failed arcs to start with
// Check if arc is already legally routed
if (check_arc_routing(net, i, j)) {
update_wire_by_loc(t, net, i, j, true);
continue;
}
@ -904,15 +922,24 @@ struct Router2
overused_wires = 0;
total_wire_use = 0;
failed_nets.clear();
for (auto &wire : flat_wires) {
total_wire_use += int(wire.bound_nets.size());
int overuse = int(wire.bound_nets.size()) - 1;
if (overuse > 0) {
wire.hist_cong_cost = std::min(1e9, wire.hist_cong_cost + overuse * hist_cong_weight);
total_overuse += overuse;
overused_wires += 1;
for (auto &bound : wire.bound_nets)
failed_nets.insert(bound.first);
pool<WireId> already_updated;
for (size_t i = 0; i < nets.size(); i++) {
auto &nd = nets.at(i);
for (const auto &w : nd.wires) {
++total_wire_use;
auto &wd = wire_data(w.first);
if (wd.curr_cong > 1) {
if (already_updated.count(w.first)) {
++total_overuse;
} else {
if (curr_cong_weight > 0)
wd.hist_cong_cost =
std::min(1e9, wd.hist_cong_cost + (wd.curr_cong - 1) * hist_cong_weight);
already_updated.insert(w.first);
++overused_wires;
}
failed_nets.insert(i);
}
}
}
for (int n : failed_nets) {
@ -981,13 +1008,12 @@ struct Router2
break;
}
}
auto &wd = wire_data(cursor);
if (!wd.bound_nets.count(net->udata)) {
if (!nd.wires.count(cursor)) {
log("Failure details:\n");
log(" Cursor: %s\n", ctx->nameOfWire(cursor));
log_error("Internal error; incomplete route tree for arc %d of net %s.\n", usr_idx, ctx->nameOf(net));
}
auto &p = wd.bound_nets.at(net->udata).second;
PipId p = nd.wires.at(cursor).first;
if (ctx->checkPipAvailForNet(p, net)) {
NetInfo *bound_net = ctx->getBoundPipNet(p);
if (bound_net == nullptr) {
@ -1062,51 +1088,13 @@ struct Router2
return success;
}
void write_xy_heatmap(std::ostream &out, bool congestion = false)
{
std::vector<std::vector<int>> hm_xy;
int max_x = 0, max_y = 0;
for (auto &wd : flat_wires) {
int val = int(wd.bound_nets.size()) - (congestion ? 1 : 0);
if (wd.bound_nets.empty())
continue;
// Estimate wire location by driving pip location
PipId drv;
for (auto &bn : wd.bound_nets)
if (bn.second.second != PipId()) {
drv = bn.second.second;
break;
}
if (drv == PipId())
continue;
Loc l = ctx->getPipLocation(drv);
max_x = std::max(max_x, l.x);
max_y = std::max(max_y, l.y);
if (l.y >= int(hm_xy.size()))
hm_xy.resize(l.y + 1);
if (l.x >= int(hm_xy.at(l.y).size()))
hm_xy.at(l.y).resize(l.x + 1);
if (val > 0)
hm_xy.at(l.y).at(l.x) += val;
}
for (int y = 0; y <= max_y; y++) {
for (int x = 0; x <= max_x; x++) {
if (y >= int(hm_xy.size()) || x >= int(hm_xy.at(y).size()))
out << "0,";
else
out << hm_xy.at(y).at(x) << ",";
}
out << std::endl;
}
}
void write_wiretype_heatmap(std::ostream &out)
{
dict<IdString, std::vector<int>> cong_by_type;
size_t max_cong = 0;
// Build histogram
for (auto &wd : flat_wires) {
size_t val = wd.bound_nets.size();
size_t val = wd.curr_cong;
IdString type = ctx->getWireType(wd.w);
max_cong = std::max(max_cong, val);
if (cong_by_type[type].size() <= max_cong)
@ -1293,37 +1281,6 @@ struct Router2
route_net(tcs.at(N), fail, false);
}
//#define ROUTER2_STATISTICS
void dump_statistics()
{
#ifdef ROUTER2_STATISTICS
int total_wires = int(flat_wires.size());
int have_hist_cong = 0;
int have_any_bound = 0, have_1_bound = 0, have_2_bound = 0, have_gte3_bound = 0;
for (auto &wire : flat_wires) {
int bound = wire.bound_nets.size();
if (bound != 0)
++have_any_bound;
if (bound == 1)
++have_1_bound;
else if (bound == 2)
++have_2_bound;
else if (bound >= 3)
++have_gte3_bound;
if (wire.hist_cong_cost > 1.0)
++have_hist_cong;
}
log_info("Out of %d wires:\n", total_wires);
log_info(" %d (%.02f%%) have any bound nets\n", have_any_bound, (100.0 * have_any_bound) / total_wires);
log_info(" %d (%.02f%%) have 1 bound net\n", have_1_bound, (100.0 * have_1_bound) / total_wires);
log_info(" %d (%.02f%%) have 2 bound nets\n", have_2_bound, (100.0 * have_2_bound) / total_wires);
log_info(" %d (%.02f%%) have >2 bound nets\n", have_gte3_bound, (100.0 * have_gte3_bound) / total_wires);
log_info(" %d (%.02f%%) have historical congestion\n", have_hist_cong,
(100.0 * have_hist_cong) / total_wires);
#endif
}
delay_t get_route_delay(int net, int usr_idx, int phys_idx)
{
auto &nd = nets.at(net);
@ -1334,14 +1291,13 @@ struct Router2
delay_t delay = 0;
while (true) {
delay += ctx->getWireDelay(cursor).maxDelay();
const auto &wd = wire_data(cursor);
if (!wd.bound_nets.count(net))
if (!nd.wires.count(cursor))
break;
auto &bound = wd.bound_nets.at(net);
if (bound.second == PipId())
auto &bound = nd.wires.at(cursor);
if (bound.first == PipId())
break;
delay += ctx->getPipDelay(bound.second).maxDelay();
cursor = ctx->getPipSrcWire(bound.second);
delay += ctx->getPipDelay(bound.first).maxDelay();
cursor = ctx->getPipSrcWire(bound.first);
}
NPNR_ASSERT(cursor == nd.src_wire);
return delay;
@ -1406,23 +1362,11 @@ struct Router2
[&](int na, int nb) { return nets.at(na).max_crit > nets.at(nb).max_crit; });
}
#if 0
for (size_t j = 0; j < route_queue.size(); j++) {
route_net(st, nets_by_udata[route_queue[j]], false);
if ((j % 1000) == 0 || j == (route_queue.size() - 1))
log(" routed %d/%d\n", int(j), int(route_queue.size()));
}
#endif
do_route();
update_route_delays();
route_queue.clear();
update_congestion();
#if 0
if (iter == 1 && ctx->debug) {
std::ofstream cong_map("cong_map_0.csv");
write_xy_heatmap(cong_map, true);
}
#endif
if (!cfg.heatmap.empty()) {
std::string filename(cfg.heatmap + "_" + std::to_string(iter) + ".csv");
std::ofstream cong_map(filename);
@ -1431,7 +1375,6 @@ struct Router2
write_wiretype_heatmap(cong_map);
log_info(" wrote wiretype heatmap to %s.\n", filename.c_str());
}
dump_statistics();
if (overused_wires == 0) {
// Try and actually bind nextpnr Arch API wires
@ -1488,7 +1431,7 @@ Router2Cfg::Router2Cfg(Context *ctx)
init_curr_cong_weight = ctx->setting<float>("router2/initCurrCongWeight", 0.5f);
hist_cong_weight = ctx->setting<float>("router2/histCongWeight", 1.0f);
curr_cong_mult = ctx->setting<float>("router2/currCongWeightMult", 2.0f);
estimate_weight = ctx->setting<float>("router2/estimateWeight", 1.75f);
estimate_weight = ctx->setting<float>("router2/estimateWeight", 1.25f);
perf_profile = ctx->setting<bool>("router2/perfProfile", false);
if (ctx->settings.count(ctx->id("router2/heatmap")))
heatmap = ctx->settings.at(ctx->id("router2/heatmap")).as_string();