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 <noreply@anthropic.com>
This commit is contained in:
Joris van Zwieten 2026-07-30 15:27:30 +02:00
parent 68c1acd80a
commit 35cbbaec1f
1 changed files with 11 additions and 2 deletions

View File

@ -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;