diff --git a/src/freeroute/route/grid_router.py b/src/freeroute/route/grid_router.py index 28529ff..a227a4f 100644 --- a/src/freeroute/route/grid_router.py +++ b/src/freeroute/route/grid_router.py @@ -854,11 +854,14 @@ class GridRouter: ) return None - def _tree_ends(self, conn: _Connection, group) -> dict[Node, int]: - """Copper its net has already routed on ``group``'s side of ``conn``, - each node mapped to the connection that laid it: a route may start or - end anywhere on it. Empty for plane nets, which keep their drops to - the plane, and when tree routing is off.""" + def _tree_ends(self, conn: _Connection, group) -> dict[Node, frozenset[int]]: + """Copper its net has already routed on ``group``'s side of ``conn``: a + route may start or end anywhere on it. Each node maps to what a route + ending there relies on: the connection that laid the copper and every + one between it and ``group`` (routed connections of a net form a + forest, so that path is unique). Ripping any of them would cut the + route off. Empty for plane nets, which keep their drops to the plane, + and when tree routing is off.""" if not self.tree or conn.net_no in self._plane_nets: return {} adjacent: dict[int, list[tuple[int, _Connection]]] = {} @@ -868,18 +871,21 @@ class GridRouter: a, b = id(c.a_group), id(c.b_group) adjacent.setdefault(a, []).append((b, c)) adjacent.setdefault(b, []).append((a, c)) - owners: dict[Node, int] = {} - seen = {id(group)} + owners: dict[Node, frozenset[int]] = {} + path_to: dict[int, frozenset[int]] = {id(group): frozenset()} stack = [id(group)] while stack: - for other, c in adjacent.get(stack.pop(), ()): + here = stack.pop() + for other, c in adjacent.get(here, ()): + if other in path_to: + continue + path = path_to[here] | {c.index} + path_to[other] = path + stack.append(other) for node in c.copper: # a walled cell's via moved into the pad: no copper there if node not in self._keepout and node not in self._via_only: - owners.setdefault(node, c.index) - if other not in seen: - seen.add(other) - stack.append(other) + owners.setdefault(node, path) return owners def _with_dependents(self, victims: list[_Connection], conns: list[_Connection]) -> list[_Connection]: @@ -1943,12 +1949,13 @@ def _first(items): def _anchors(path, starts, goals, a_tree, b_tree) -> frozenset[int]: - """The connections whose copper a found path starts or ends on.""" - found = set() + """What a found path relies on: the connections between the copper it + starts or ends on and its own groups.""" + found: set[int] = set() if starts.get(path[0]) is None and path[0] in a_tree: - found.add(a_tree[path[0]]) + found |= a_tree[path[0]] if goals.get(path[-1]) is None and path[-1] in b_tree: - found.add(b_tree[path[-1]]) + found |= b_tree[path[-1]] return frozenset(found) diff --git a/tests/route/test_tree_nets.py b/tests/route/test_tree_nets.py index 0fdf6f2..74c197a 100644 --- a/tests/route/test_tree_nets.py +++ b/tests/route/test_tree_nets.py @@ -139,3 +139,32 @@ def test_occupancy_stays_honest(tree_nets): for anchor, hanging in router._dependents.items(): for i in hanging: assert conns[i].routed and conns[anchor].routed + + +# P1-P2 along the top, P3 far off to the lower right, P4 just under the +# middle of the P1-P2 leg. +CHAIN_TEE = TEE.replace( + """ (place P3 100000 -70000 front 0) +""", + """ (place P3 190000 -90000 front 0) + (place P4 100000 -40000 front 0) +""", +).replace("(pins P1-1 P2-1 P3-1)", "(pins P1-1 P2-1 P3-1 P4-1)") + + +def test_a_tee_depends_on_every_connection_back_to_its_group(): + """Joined as P1-P2, P2-P3, P3-P4, P4's connection tees onto P1-P2 copper, + which reaches P3's group only through P2-P3. Ripping P2-P3 must take the T + too, or it would be counted routed while cut off from P3.""" + router = _router(text=CHAIN_TEE) + conns = router._conns = router._build_connections() + groups = {c.a.component_ref: c.a_group for c in conns} + groups.update({c.b.component_ref: c.b_group for c in conns}) + for conn, (a, b) in zip(conns, (("P1", "P2"), ("P2", "P3"), ("P3", "P4")), strict=True): + conn.a_group, conn.b_group = groups[a], groups[b] + for conn in conns: + assert router._try_route(conn, conns) + first, second, third = conns + assert third.path[0] in first.copper # the T lands on the far leg + assert third.anchors == {first.index, second.index} + assert router._with_dependents([second], conns) == [second, third]