From 7f81c1f4ce6c55e42c4b53051b748d029588bbea Mon Sep 17 00:00:00 2001 From: Ryan Malloy Date: Mon, 13 Jul 2026 12:27:31 -0600 Subject: [PATCH] Prove channel packing: connects all where greedy drops, feasibility-bounded Add channel_pack.dsn: three nets whose pads spread wider than a wall gap, so they must converge and pack into lanes. Plain room routing (shove on or off) drops >= 1 net under every net ordering; the packer connects all three under every ordering, DRC-clean (search tree and independent emitted-copper check). The falsifiable boundary test sweeps the gap width for N=3 and N=4 and asserts the packer connects all N iff the gap meets the geometric feasibility width (N-1)*(width+clearance) + 2*(half_width+clearance), never routes fewer than greedy below it, and never false-packs. Determinism, endpoints-on-pads, valid SES, and pack=False-equals-default (additive) are covered. --- tests/dsn/fixtures/channel_pack.dsn | 75 +++++++++ tests/route/test_room_pack.py | 233 ++++++++++++++++++++++++++++ 2 files changed, 308 insertions(+) create mode 100644 tests/dsn/fixtures/channel_pack.dsn create mode 100644 tests/route/test_room_pack.py diff --git a/tests/dsn/fixtures/channel_pack.dsn b/tests/dsn/fixtures/channel_pack.dsn new file mode 100644 index 0000000..3d351db --- /dev/null +++ b/tests/dsn/fixtures/channel_pack.dsn @@ -0,0 +1,75 @@ +(pcb "channel_pack.dsn" + (parser + (string_quote ") + (space_in_quoted_tokens on) + (host_cad "freeroute-test") + (host_version "1.0") + ) + (resolution um 10) + (unit um) + (structure + (layer F.Cu + (type signal) + (property + (index 0) + ) + ) + (layer B.Cu + (type power) + (property + (index 1) + ) + ) + (boundary + (path pcb 0 0 0 200000 0 200000 -80000 0 -80000 0 0) + ) + (keepout "wall_left" + (rect F.Cu 0 -42000 92000 -38000) + ) + (keepout "wall_right" + (rect F.Cu 108000 -42000 200000 -38000) + ) + (rule + (width 2000) + (clearance 2000) + ) + ) + (library + (padstack Rect_Pad + (shape (rect F.Cu -1000 -1000 1000 1000)) + (attach off) + ) + (image PAD + (pin Rect_Pad 1 0 0) + ) + ) + (placement + (component PAD + (place P0a 70000 -10000 front 0) + (place P0b 70000 -70000 front 0) + (place P1a 100000 -10000 front 0) + (place P1b 100000 -70000 front 0) + (place P2a 130000 -10000 front 0) + (place P2b 130000 -70000 front 0) + ) + ) + (network + (net NET0 + (pins P0a-1 P0b-1) + ) + (net NET1 + (pins P1a-1 P1b-1) + ) + (net NET2 + (pins P2a-1 P2b-1) + ) + (class default + (rule + (width 2000) + (clearance 2000) + ) + ) + ) + (wiring + ) +) diff --git a/tests/route/test_room_pack.py b/tests/route/test_room_pack.py new file mode 100644 index 0000000..18e07a1 --- /dev/null +++ b/tests/route/test_room_pack.py @@ -0,0 +1,233 @@ +"""Tests for coordinated multi-trace channel packing on the room router. + +``pack=True`` is a third opt-in room-track upgrade, independent of ``shove``. The +skeptic's finding that motivated it: shove recovers ordering drops but does not +create density; the real densifier is lane packing. When several 2-pin nets must +cross a common obstacle gap, greedy per-net routing lets each net grab a gate +independently, the paths collide, and some nets drop even though the gap is +physically wide enough for all of them (``channel_pack.dsn`` -- plain routing, +shove on or off, drops a net under *every* net ordering). The packer assigns each +net a parallel lane across the gap's usable width (pad-ordered, spaced >= width + +clearance) with staggered fan-in trunks, routes them together, and connects all +of them DRC-clean. + +The win is bounded honestly by geometry: the packer succeeds iff the gap is at +least ``(N-1)*(width+clearance) + 2*(half_width+clearance)`` wide -- the true +feasibility width for N lanes -- and never regresses below greedy below it. +""" + +from __future__ import annotations + +import itertools +from pathlib import Path + +import pytest + +from freeroute.dsn import parse_dsn +from freeroute.dsn.sexp import parse +from freeroute.geometry import Polyline, PolylineShape +from freeroute.route import route, route_dsn_board_rooms +from freeroute.route.pipeline import _rule_clearance_dsn + +FIXTURES = Path(__file__).resolve().parent.parent / "dsn" / "fixtures" +CHANNEL_PACK = FIXTURES / "channel_pack.dsn" +SIMPLE = FIXTURES / "simple_2net.dsn" +CROSSING = FIXTURES / "crossing_2net.dsn" + + +def _clearance(dsn, scale): + return round(_rule_clearance_dsn(dsn) * scale) + + +def _emitted_drc_violations(result, clearance): + """Independent DRC over the emitted route: reconstruct every wire's copper + tiles and return different-net, same-layer pairs closer than ``clearance``. + Does not consult the search tree, so it catches copper emitted but (via a bug) + absent from the tree.""" + items = [] + for net_no, segments in result.wires.items(): + for layer, corners in segments: + for box in PolylineShape(Polyline(corners), result.half_width).tiles(): + items.append((net_no, layer, box)) + bad = [] + for i in range(len(items)): + n1, l1, b1 = items[i] + expanded = b1.offset(clearance) + for j in range(i + 1, len(items)): + n2, l2, b2 = items[j] + if n1 == n2 or l1 != l2: + continue + if b2.overlaps(expanded): + bad.append((n1, n2)) + return bad + + +def _net_blocks(text): + import re + + return re.findall(r" \(net [^\n]*\n \(pins [^\n]*\n \)\n", text) + + +def _reorder(text, perm): + blocks = _net_blocks(text) + return text.replace("".join(blocks), "".join(blocks[i] for i in perm), 1) + + +def _wire_key(result): + return { + n: [(layer, [(p.x, p.y) for p in pts]) for layer, pts in segs] + for n, segs in result.result.wires.items() + } + + +def _routed_counts(text, n, *, pack, shove): + return [ + len( + route_dsn_board_rooms(parse_dsn(_reorder(text, p)), shove=shove, pack=pack)[ + 0 + ].routed_net_numbers + ) + for p in itertools.permutations(range(n)) + ] + + +# --- the genuine gap: current router drops a net under every ordering --------- + + +def test_channel_pack_plain_drops_a_net_under_every_ordering(): + """Bar (same methodology as true_density): the current router, shove on OR + off, drops >= 1 of the three nets under EVERY net ordering -- an + order-independent drop, even though the gap physically fits three lanes.""" + text = CHANNEL_PACK.read_text() + plain = _routed_counts(text, 3, pack=False, shove=False) + shoved = _routed_counts(text, 3, pack=False, shove=True) + assert all(k < 3 for k in plain) + assert all(k < 3 for k in shoved) + # shove buys nothing here: it relocates blockers, it does not pack lanes + assert shoved == plain + + +def test_channel_pack_connects_all_drc_clean(): + """The packer connects all three nets and the emitted copper is exactly + DRC-clean, verified both through the search tree and independently.""" + dsn = parse_dsn(CHANNEL_PACK.read_text()) + on, scale, _ = route_dsn_board_rooms(dsn, pack=True) + clearance = _clearance(dsn, scale) + assert on.routed_net_numbers == {1, 2, 3} + assert on.drc_clean + assert on.tree.has_violation(clearance) is None + assert _emitted_drc_violations(on.result, clearance) == [] + + +def test_channel_pack_connects_all_under_every_ordering(): + counts = _routed_counts(CHANNEL_PACK.read_text(), 3, pack=True, shove=False) + assert counts == [3, 3, 3, 3, 3, 3] + + +def test_channel_pack_is_deterministic(): + a, _, _ = route_dsn_board_rooms(parse_dsn(CHANNEL_PACK.read_text()), pack=True) + b, _, _ = route_dsn_board_rooms(parse_dsn(CHANNEL_PACK.read_text()), pack=True) + assert _wire_key(a) == _wire_key(b) + assert a.routed_net_numbers == b.routed_net_numbers + + +def test_channel_pack_endpoints_stay_on_pads(): + dsn = parse_dsn(CHANNEL_PACK.read_text()) + board_pins: dict[int, set[tuple[int, int]]] = {} + from freeroute.board import build_board + + for pin in build_board(dsn).get_pins(): + for net_no in pin.net_nos: + board_pins.setdefault(net_no, set()).add((pin.location.x, pin.location.y)) + on, _, _ = route_dsn_board_rooms(dsn, pack=True) + for net_no, segments in on.result.wires.items(): + pts = [(p.x, p.y) for _, run in segments for p in run] + assert {pts[0], pts[-1]} == board_pins[net_no] + + +def test_channel_pack_valid_ses(): + ses = route(CHANNEL_PACK.read_text(), engine="room", pack=True) + top = parse(ses) + assert top.head == "session" + nets = top.child("routes").child("network_out").children("net") + assert {n.values()[0].text for n in nets} == {"NET0", "NET1", "NET2"} + + +# --- additive: pack must not disturb the room-default / non-channel tracks ---- + + +@pytest.mark.parametrize("fixture", [SIMPLE, CROSSING, CHANNEL_PACK]) +def test_pack_off_matches_default(fixture): + """pack defaults off; passing pack=False must be byte-identical to the + foundation room route (no channel behaviour leaks into the default track).""" + dsn = parse_dsn(fixture.read_text()) + default, _, _ = route_dsn_board_rooms(dsn) + off, _, _ = route_dsn_board_rooms(dsn, pack=False) + assert _wire_key(default) == _wire_key(off) + + +def test_pack_on_non_channel_board_is_noop(): + """On a board with no obstacle gap to pack, pack=True changes nothing: the + pre-pass finds no channel group and the greedy loop runs exactly as before.""" + dsn = parse_dsn(SIMPLE.read_text()) + off, _, _ = route_dsn_board_rooms(dsn, pack=False) + on, _, _ = route_dsn_board_rooms(dsn, pack=True) + assert _wire_key(off) == _wire_key(on) + + +# --- falsifiable: pack succeeds iff the gap meets the geometric feasibility ---- + + +def _channel_dsn(n, gap, spread, *, width=200000, height=80000): + gx0 = (width - gap) // 2 + gx1 = (width + gap) // 2 + cx = width // 2 + xs = [int(round(cx + (i - (n - 1) / 2) * spread)) for i in range(n)] + places = "".join( + f" (place P{i}a {x} -10000 front 0)\n (place P{i}b {x} -70000 front 0)\n" + for i, x in enumerate(xs) + ) + nets = "".join(f" (net NET{i}\n (pins P{i}a-1 P{i}b-1)\n )\n" for i in range(n)) + return f"""(pcb "sweep.dsn" + (parser (string_quote ") (space_in_quoted_tokens on) (host_cad "t") (host_version "1")) + (resolution um 10) + (unit um) + (structure + (layer F.Cu (type signal) (property (index 0))) + (boundary (path pcb 0 0 0 {width} 0 {width} -{height} 0 -{height} 0 0)) + (keepout "wl" (rect F.Cu 0 -42000 {gx0} -38000)) + (keepout "wr" (rect F.Cu {gx1} -42000 {width} -38000)) + (rule (width 2000) (clearance 2000))) + (library + (padstack Rect_Pad (shape (rect F.Cu -1000 -1000 1000 1000)) (attach off)) + (image PAD (pin Rect_Pad 1 0 0))) + (placement (component PAD +{places} )) + (network +{nets} (class default (rule (width 2000) (clearance 2000)))) + (wiring ) +) +""" + + +@pytest.mark.parametrize("n", [3, 4]) +def test_channel_pack_boundary_is_geometric_feasibility(n): + """Sweep the gap width: the packer connects all N nets iff the gap is at least + the geometric feasibility width for N lanes, and never routes fewer than the + greedy baseline below it. width=2000, clearance=2000 -> half_width=1000, so + lanes need >= 4000 apart and >= 3000 from each gap edge.""" + width, clearance, half_width = 2000, 2000, 1000 + feasibility = (n - 1) * (width + clearance) + 2 * (half_width + clearance) + spread = 40000 + step = 1000 + for gap in range(feasibility - 4 * step, feasibility + 4 * step + 1, step): + text = _channel_dsn(n, gap, spread) + plain = len(route_dsn_board_rooms(parse_dsn(text), pack=False)[0].routed_net_numbers) + packed_res = route_dsn_board_rooms(parse_dsn(text), pack=True)[0] + packed = len(packed_res.routed_net_numbers) + assert packed_res.drc_clean + assert packed >= plain # never a regression + if gap >= feasibility: + assert packed == n # packs everything at/above feasibility + else: + assert packed < n # cannot (and does not falsely) pack below it