--reserve-escapes: keep a way out open for every fine-pitch pin

Before routing, each crowded pin gets the shortest way from its pad to
open board, found against pads, keepouts and earlier escapes. While the
pin still has connections to route, no other net may put a trace or a
via there, nor within clearance of it; the reservation lifts once the
pin is routed.
This commit is contained in:
2026-09-28 14:21:24 -06:00
parent 35891c02ff
commit 8b1b00cf6c
6 changed files with 175 additions and 3 deletions
+65
View File
@@ -0,0 +1,65 @@
"""--reserve-escapes: a crowded pin keeps a way out until it is routed."""
from __future__ import annotations
from pathlib import Path
from freeroute.board import build_board
from freeroute.dsn import parse_dsn
from freeroute.route.grid_router import GridRouter
from freeroute.route.pipeline import (
_rule_clearance_dsn,
_rule_width_dsn,
build_exact_routing_result,
)
SMD = (Path(__file__).resolve().parent.parent / "dsn" / "fixtures" / "smd_demo.dsn").read_text()
def _router():
dsn = parse_dsn(SMD)
s = dsn.resolution.value
router = GridRouter(
build_board(dsn),
trace_width=round(_rule_width_dsn(dsn) * s),
clearance=round(_rule_clearance_dsn(dsn) * s),
orthogonal=True,
resolution=2,
reserve_escapes=True,
)
conns = router._build_connections()
router._conns = conns
router._count_pending(conns)
router._reserve_escapes(conns)
return router, conns
def test_a_reserved_escape_is_closed_to_other_nets_and_open_to_its_own():
router, conns = _router()
assert router._reserved
node, claims = next(iter(router._reserved.items()))
pin_id, owner = claims[0]
other = next(c.net_no for c in conns if c.net_no != owner)
cell, layer = node[:2], node[2]
assert router._enter_cost(cell, layer, other, True, conns) is None # even for rip-up
assert router._enter_cost(cell, layer, owner, False, conns) is not None or node in router._keepout
def test_the_escape_is_released_once_its_pin_is_routed():
router, conns = _router()
node, claims = next(iter(router._reserved.items()))
other = next(c.net_no for c in conns if c.net_no != claims[0][1])
cell, layer = node[:2], node[2]
assert router._enter_cost(cell, layer, other, False, conns) is None
for c in conns: # every connection routed: no pin is pending any more
router._settle(c, -1)
released = router._enter_cost(cell, layer, other, False, conns)
reserved, router._reserved = router._reserved, {}
assert released == router._enter_cost(cell, layer, other, False, conns)
router._reserved = reserved
def test_reserving_escapes_loses_nothing_on_the_demo_board():
plain = build_exact_routing_result(parse_dsn(SMD), resolution=2)
reserved = build_exact_routing_result(parse_dsn(SMD), resolution=2, reserve_escapes=True)
assert len(reserved.unrouted) <= len(plain.unrouted)
+1
View File
@@ -158,6 +158,7 @@ def test_cli_supported_combinations_are_accepted(tmp_path):
"orders": ["--orders", "2"],
"promote": ["--promote-failures", "2"],
"access": ["--access-cost", "5"],
"escapes": ["--reserve-escapes"],
}
for engine, options in ENGINE_OPTIONS.items():
for option in options: