With items away from the window left off its board, a stub from the window edge out to a rerouted net's far pin crossed copper the exact check never saw: 38 shorts on th-eda's flight controller. A connection with an end outside the bounds is now left unrouted, and a window whose new copper leaves the window is refused.
129 lines
5.0 KiB
Python
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 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
|
|
|
|
|
|
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 freeroute.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)
|