From 35cbbaec1fefac86c15bcc6d23ba3571be1c87fc Mon Sep 17 00:00:00 2001 From: Joris van Zwieten Date: Thu, 30 Jul 2026 15:27:30 +0200 Subject: [PATCH] router2: fix no-op ripup of constant-net arcs ripup_arc walks from the sink wire back to the net source, unbinding pips as it goes. For nets with constant_value set, the walk condition required every wire on the path to itself carry that constant value (getWireConstantValue(cursor) == net->constant_value). This is false for all general-purpose routing wires, so the walk terminated at the sink immediately and ripup never unbound anything. Consequences on congested designs with const routing (observed on himbaechel/xilinx, where GND/VCC use const-net mode): - re-routing the arc rebound the same path on top of itself, leaking one wire refcount per ripup/re-route cycle - congestion on contested constant tap wires could never resolve; the router livelocked with a handful of wires stuck overused for thousands of iterations Constant-net arc trees root at a tap wire bound with a null pip (the const_mode midpoint bind in route_arc), so terminate the walk there instead: walk to the net source as usual, and stop at the first null-pip binding. Hardware-verified on xc7a100t: a MEGA65 core port (~34k LCs) that previously livelocked (>3000 iterations, 6 wires pinned overused) now routes to 0 overused wires in ~30 iterations. Co-Authored-By: Claude Fable 5 --- common/route/router2.cc | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/common/route/router2.cc b/common/route/router2.cc index e3e3bb8b..a9862135 100644 --- a/common/route/router2.cc +++ b/common/route/router2.cc @@ -447,10 +447,19 @@ struct Router2 return; WireId src = nets.at(net->udata).src_wire; WireId cursor = ad.sink_wire; - while (cursor != src && - (net->constant_value == IdString() || ctx->getWireConstantValue(cursor) == net->constant_value)) { + while (cursor != src) { PipId pip = nd.wires.at(cursor).first; unbind_pip_internal(nd, user, cursor); + // Constant nets have no global source; each arc's tree roots at a + // tap wire carrying the right constant value, bound with a null + // pip (see the const_mode midpoint bind in route_arc). The old + // condition here tested the const value on every wire from the + // sink up - false for all general routing - so the walk stopped + // immediately and ripup never unbound anything: rebinding the + // same path each iteration leaked one refcount per iteration and + // congestion on contested taps could never resolve. + if (pip == PipId()) + break; cursor = ctx->getPipSrcWire(pip); } ad.routed = false;