Window boards: never search a connection ending outside the window
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.
This commit is contained in:
@@ -211,6 +211,10 @@ class GridRouter:
|
|||||||
# these nets get connections, and the grid covers only ``bounds``
|
# these nets get connections, and the grid covers only ``bounds``
|
||||||
# (cells outside it do not exist, so nothing is routed there).
|
# (cells outside it do not exist, so nothing is routed there).
|
||||||
self.only_nets = frozenset(only_nets) if only_nets is not None else None
|
self.only_nets = frozenset(only_nets) if only_nets is not None else None
|
||||||
|
self._bounds = bounds
|
||||||
|
# connections with an end outside ``bounds``: never searched, since
|
||||||
|
# the grid has no cells to reach that end by
|
||||||
|
self._outside: set[int] = set()
|
||||||
# Multi-pin nets: pair a net's pin groups by a minimum spanning tree,
|
# Multi-pin nets: pair a net's pin groups by a minimum spanning tree,
|
||||||
# and let a connection start or end on copper its net already routed
|
# and let a connection start or end on copper its net already routed
|
||||||
# (a T into the tree), not only on one chosen pin.
|
# (a T into the tree), not only on one chosen pin.
|
||||||
@@ -717,6 +721,8 @@ class GridRouter:
|
|||||||
def _search(
|
def _search(
|
||||||
self, conn: _Connection, allow_ripup: bool, conns: list[_Connection]
|
self, conn: _Connection, allow_ripup: bool, conns: list[_Connection]
|
||||||
) -> list[Node] | None:
|
) -> list[Node] | None:
|
||||||
|
if conn.index in self._outside:
|
||||||
|
return None
|
||||||
half = self._conn_half(conn)
|
half = self._conn_half(conn)
|
||||||
self._cur_half, self._spread = half, self._spread_for(half)
|
self._cur_half, self._spread = half, self._spread_for(half)
|
||||||
try:
|
try:
|
||||||
@@ -1872,6 +1878,11 @@ class GridRouter:
|
|||||||
self._net_conns: dict[int, list[_Connection]] = {}
|
self._net_conns: dict[int, list[_Connection]] = {}
|
||||||
for conn in conns:
|
for conn in conns:
|
||||||
self._net_conns.setdefault(conn.net_no, []).append(conn)
|
self._net_conns.setdefault(conn.net_no, []).append(conn)
|
||||||
|
if self._bounds is not None:
|
||||||
|
self._outside = {
|
||||||
|
c.index for c in conns
|
||||||
|
if not (_group_inside(c.a_group, self._bounds) and _group_inside(c.b_group, self._bounds))
|
||||||
|
}
|
||||||
return conns
|
return conns
|
||||||
|
|
||||||
def _fixed_pieces(self, net_no: int) -> list[_FixedPiece]:
|
def _fixed_pieces(self, net_no: int) -> list[_FixedPiece]:
|
||||||
@@ -2017,6 +2028,19 @@ def _group_label(group) -> str:
|
|||||||
return "/".join(parts) or "?"
|
return "/".join(parts) or "?"
|
||||||
|
|
||||||
|
|
||||||
|
def _group_inside(group, bounds: IntBox) -> bool:
|
||||||
|
"""A group has a pin inside ``bounds``, or a zone reaching into it."""
|
||||||
|
pins, zones = group
|
||||||
|
if any(bounds.ll.x <= p.location.x <= bounds.ur.x and bounds.ll.y <= p.location.y <= bounds.ur.y
|
||||||
|
for p in pins):
|
||||||
|
return True
|
||||||
|
for zone in zones:
|
||||||
|
box = zone.bounding_box()
|
||||||
|
if box.ll.x <= bounds.ur.x and bounds.ll.x <= box.ur.x and box.ll.y <= bounds.ur.y and bounds.ll.y <= box.ur.y:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def _ends_of(conn) -> tuple[str, str]:
|
def _ends_of(conn) -> tuple[str, str]:
|
||||||
"""A readable description of what a connection joins."""
|
"""A readable description of what a connection joins."""
|
||||||
return _group_label(conn.a_group), _group_label(conn.b_group)
|
return _group_label(conn.a_group), _group_label(conn.b_group)
|
||||||
|
|||||||
@@ -206,6 +206,11 @@ def window_reroute(
|
|||||||
after = _open(f for f in sub.failed if f[0] in affected)
|
after = _open(f for f in sub.failed if f[0] in affected)
|
||||||
if len(after) >= len(before):
|
if len(after) >= len(before):
|
||||||
continue
|
continue
|
||||||
|
# the window board left out what lies away from it, so copper
|
||||||
|
# reaching out of the window was never checked against it
|
||||||
|
if any(not _inside((sub.wires.get(n, []), sub.vias.get(n, [])), window)
|
||||||
|
for n in affected if sub.wires.get(n) or sub.vias.get(n)):
|
||||||
|
continue
|
||||||
# a net keeping some of its copper is written at one width: the
|
# a net keeping some of its copper is written at one width: the
|
||||||
# window's new copper has to have gone in at that width too
|
# window's new copper has to have gone in at that width too
|
||||||
if any(
|
if any(
|
||||||
|
|||||||
@@ -110,3 +110,19 @@ def test_window_board_keeps_far_items_of_the_rerouted_nets_only():
|
|||||||
refs = {p.component_ref for p in sub.get_pins()}
|
refs = {p.component_ref for p in sub.get_pins()}
|
||||||
assert {"P1", "P2"} <= refs # net N's pins, though outside the view
|
assert {"P1", "P2"} <= refs # net N's pins, though outside the view
|
||||||
assert not refs & {"Q1", "Q2"} # net M's, far away, left out
|
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)
|
||||||
|
|||||||
Reference in New Issue
Block a user