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.
113 lines
4.3 KiB
Python
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
|