Files
rpmroute/tests/route/test_window_reroute.py
rsp2k a6aa1991a1 Rename to rpmroute
The router began as a port of FreeRouting and has grown its own engine
(negotiated congestion, tree nets, exact checks, axis escapes), and
'freeroute' was one letter from the project it no longer follows. The
package, module and command are now rpmroute; the freeroute command stays
as an alias, and the RPMROUTE / RPMROUTE_ORACLE_JAVA variables fall back
to the old FREEROUTE names. Version 2026.10.07. Routing is unchanged: a
PCBench board routes to a byte-identical session either way.
2026-10-07 14:10:36 -06:00

129 lines
5.0 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 rpmroute.board import build_board
from rpmroute.dsn import parse_dsn
from rpmroute.geometry import IntBox
from rpmroute.route.grid_router import GridRouter
from rpmroute.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 rpmroute.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
def test_a_connection_ending_outside_the_window_gets_no_copper():
"""Its far end has no grid cells; a stub out to it would cross items the
window board never saw."""
from rpmroute.route.exact_router import route_board_exact
dsn = parse_dsn(_slot(far=True))
s = dsn.resolution.value
window = IntBox(70000, -70000, 130000, -30000) # the slot only; net M's pins are far off
result = route_board_exact(
build_board(dsn), trace_width=round(_rule_width_dsn(dsn) * s),
clearance=round(_rule_clearance_dsn(dsn) * s), only_nets=frozenset({2}), bounds=window,
).result
assert [f[0] for f in result.failed] == [2]
assert not result.wires.get(2) and not result.vias.get(2)