Files
rpmroute/tests/route/test_window_reroute.py
T
rsp2k 28523bcbac Window re-route: time budget, window cap, smallest first, window boards
A window's board now holds every pin and piece of copper of the nets it
reroutes and, of the rest, only items near the window, instead of the whole
board rebuilt per window. The smallest windows go first, at most
--window-cap per round (40), and no window starts once --window-budget of
the routing's own time is spent (0.25). On th-eda's flight controller the
stage had taken 12x the routing time for no gain.
2026-10-06 22:51:51 -06:00

113 lines
4.3 KiB
Python

"""Window re-route: a finer grid around a stuck connection, kept only when
it leaves fewer connections unrouted."""
from __future__ import annotations
from freeroute.board import build_board
from freeroute.dsn import parse_dsn
from freeroute.geometry import IntBox
from freeroute.route.grid_router import GridRouter
from freeroute.route.pipeline import _rule_clearance_dsn, _rule_width_dsn, route_dsn_board_exact
def _slot(gap: int = 640, far: bool = False) -> str:
"""A wall across the board with a slot in it. A trace fits the slot
only along a narrow band of heights that the board's 1x grid has no row
in and a 4x grid does. ``far`` adds a second net nowhere near the wall."""
yc = -5000
lo, hi = yc - gap // 2, yc + gap // 2
extra_place = "(place Q1 2000 -1000 front 0) (place Q2 6000 -1000 front 0)" if far else ""
extra_net = "(net M (pins Q1-1 Q2-1))" if far else ""
return f"""(pcb "slot.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 20000 0 20000 -10000 0 -10000 0 0))
(keepout "" (rect F.Cu 9000 -10000 11000 {lo}))
(keepout "" (rect F.Cu 9000 {hi} 11000 0))
(rule (width 200) (clearance 200))
)
(library
(padstack Rect_Pad (shape (rect F.Cu -100 -100 100 100)) (attach off))
(image PAD (pin Rect_Pad 1 0 0))
)
(placement
(component PAD
(place P1 3000 {yc} front 0)
(place P2 17000 {yc} front 0)
{extra_place}
)
)
(network
(net N (pins P1-1 P2-1))
{extra_net}
(class default (rule (width 200) (clearance 200)))
)
(wiring)
)
"""
def _route(text: str, rounds: int, **window):
exact, _scale, _layers = route_dsn_board_exact(
parse_dsn(text), resolution=1, window_reroute=rounds, window_resolution=4, **window,
)
return exact.result
def test_the_board_grid_misses_the_slot_and_a_finer_window_finds_it():
plain = _route(_slot(), 0)
assert len(plain.failed) == 1
windowed = _route(_slot(), 1)
assert windowed.failed == []
assert windowed.wires # and the copper it found is in the result
def test_a_window_that_cannot_help_leaves_the_board_as_it_was():
plain = _route(_slot(gap=500, far=True), 0) # too narrow for any grid
windowed = _route(_slot(gap=500, far=True), 2)
assert len(windowed.failed) == len(plain.failed) == 1
assert windowed.wires == plain.wires
assert windowed.vias == plain.vias
def test_copper_outside_the_window_is_kept_as_it_was():
plain = _route(_slot(far=True), 0)
windowed = _route(_slot(far=True), 1)
assert windowed.failed == []
far_net = 2 # M, the second net in the DSN
assert windowed.wires[far_net] == plain.wires[far_net]
def test_a_router_limited_to_some_nets_and_a_box_builds_only_those():
dsn = parse_dsn(_slot(far=True))
s = dsn.resolution.value
board = build_board(dsn)
whole = GridRouter(board, trace_width=round(_rule_width_dsn(dsn) * s),
clearance=round(_rule_clearance_dsn(dsn) * s), orthogonal=True)
part = GridRouter(board, trace_width=round(_rule_width_dsn(dsn) * s),
clearance=round(_rule_clearance_dsn(dsn) * s), orthogonal=True,
only_nets=frozenset({2}), bounds=IntBox(0, -20000, 80000, 0))
assert {c.net_no for c in whole._build_connections()} == {1, 2}
assert {c.net_no for c in part._build_connections()} == {2}
assert part.cols * part.rows < whole.cols * whole.rows
def test_a_spent_budget_starts_no_window():
assert len(_route(_slot(), 1, window_budget=0.0).failed) == 1
assert _route(_slot(), 1, window_budget=1000.0).failed == []
def test_window_board_keeps_far_items_of_the_rerouted_nets_only():
from freeroute.route.window import _item_box, _window_board
board = build_board(parse_dsn(_slot(far=True)))
items = [(item, _item_box(item)) for item in board.get_items()]
view = IntBox(80000, -60000, 120000, -40000) # around the slot, far from Q1/Q2
sub = _window_board(board, items, view, {1}, {}, set(), {}, 100, 300, 300, [0], "via")
refs = {p.component_ref for p in sub.get_pins()}
assert {"P1", "P2"} <= refs # net N's pins, though outside the view
assert not refs & {"Q1", "Q2"} # net M's, far away, left out