From 64f6b8bc67a815ed89871a87528846763baf8096 Mon Sep 17 00:00:00 2001 From: gatecat Date: Sat, 14 Aug 2021 15:02:26 +0100 Subject: [PATCH] router2: Improved bidir routing and data structures Signed-off-by: gatecat --- common/router2.cc | 783 +++++++++++++++++++++------------------------- 1 file changed, 363 insertions(+), 420 deletions(-) diff --git a/common/router2.cc b/common/router2.cc index 9264903d..3605a7fd 100644 --- a/common/router2.cc +++ b/common/router2.cc @@ -61,6 +61,7 @@ struct Router2 struct PerNetData { WireId src_wire; + dict> wires; std::vector> 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> 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> route_arcs; - std::priority_queue, QueuedWire::Greater> queue; + std::priority_queue, QueuedWire::Greater> fwd_queue, bwd_queue; // Special case where one net has multiple logical arcs to the same physical sink pool processed_sinks; - // Backwards routing - std::queue backwards_queue; - std::vector dirty_wires; // Thread bounding box ArcBounds bb; DeterministicRNG rng; + + // Used to add existing routing to the heap + pool in_wire_by_loc; + dict, pool> 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()) { - std::priority_queue, QueuedWire::Greater> new_queue; - t.queue.swap(new_queue); - } - if (!t.backwards_queue.empty()) { - std::queue new_queue; - t.backwards_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; + // 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::Greater> new_queue; + t.fwd_queue.swap(new_queue); + } + if (!t.bwd_queue.empty()) { + std::priority_queue, QueuedWire::Greater> new_queue; + t.bwd_queue.swap(new_queue); + } + // Unvisit any previously visited wires + reset_wires(t); + + 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); + } + } + + 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; + 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()); + }; + + // 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; + + // 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 (!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; } - 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()); + 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 (is_bb && !hit_test_pip(nd.bb, ctx->getPipLocation(dh))) + continue; + if (!ctx->checkPipAvailForNet(dh, net)) + continue; + WireId next = ctx->getPipDstWire(dh); + int next_idx = wire_to_idx.at(next); + if (was_visited_fwd(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; + // 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.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 (!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; + } + 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())); } - 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; + if (midpoint_wire != -1) + break; } - // 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()) { + 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; } - cursor_fwd = wire_to_idx.at(ctx->getPipDstWire(v.pip)); - bind_pip_internal(net, i, cursor_fwd, v.pip); - if (ctx->debug) { + 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(" wire: %s (curr %d hist %f)\n", ctx->nameOfWire(wd.w), - int(wd.bound_nets.size()) - 1, wd.hist_cong_cost); + 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); - ad.routed = true; - t.processed_sinks.insert(dst_wire); - reset_wires(t); - return ARC_SUCCESS; - } - // Normal forwards A* routing - reset_wires(t); - 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); - - 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); - - 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(); - ++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)) { - // 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) - : "", - net->name.c_str(ctx)); -#endif - 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)) { - // 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; - 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) - 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(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; - } - } - } - } - 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); - 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)); - } + 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(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(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 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> 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> 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("router2/initCurrCongWeight", 0.5f); hist_cong_weight = ctx->setting("router2/histCongWeight", 1.0f); curr_cong_mult = ctx->setting("router2/currCongWeightMult", 2.0f); - estimate_weight = ctx->setting("router2/estimateWeight", 1.75f); + estimate_weight = ctx->setting("router2/estimateWeight", 1.25f); perf_profile = ctx->setting("router2/perfProfile", false); if (ctx->settings.count(ctx->id("router2/heatmap"))) heatmap = ctx->settings.at(ctx->id("router2/heatmap")).as_string();