Count copper already on the board as connections already made
Fixed tracks and vias only kept other nets away, so a pin fanned out to its plane was routed to the plane again, beside its own fan-out. Pins, zones and planes that fixed copper of their net touches now form one connected group: a fanned-out supply pin needs nothing more, a fanned-out signal routes from its via, and copper touching no pin or zone adds nothing to route.
This commit is contained in:
@@ -1253,7 +1253,9 @@ class GridRouter:
|
||||
# deterministic: fewer-pin nets first, then by net number
|
||||
for net_no in sorted(nets, key=lambda n: (len(pins_by_net.get(n, [])), n)):
|
||||
zones = zones_by_net.get(net_no, []) + planes_by_net.get(net_no, [])
|
||||
groups = _connected_groups(pins_by_net.get(net_no, []), zones, plane_ids)
|
||||
groups = _connected_groups(
|
||||
pins_by_net.get(net_no, []), zones, plane_ids, self._fixed_pieces(net_no)
|
||||
)
|
||||
hubs = [g for g in groups if any(id(z) in plane_ids for z in g[1])]
|
||||
if hubs:
|
||||
# a plane net: every other group goes straight to the plane
|
||||
@@ -1267,6 +1269,28 @@ class GridRouter:
|
||||
conns.append(conn)
|
||||
return conns
|
||||
|
||||
def _fixed_pieces(self, net_no: int) -> list[_FixedPiece]:
|
||||
"""The net's tracks and vias already on the board, as connectivity."""
|
||||
cache = getattr(self, "_fixed_cache", None)
|
||||
if cache is None:
|
||||
from freeroute.board.items import Trace
|
||||
from freeroute.geometry.polyline import Polyline, PolylineShape, corner_box
|
||||
|
||||
cache = self._fixed_cache = {}
|
||||
for item in self.board.get_items():
|
||||
if not item.fixed or not item.net_nos:
|
||||
continue
|
||||
if isinstance(item, Via) and item.shape is not None:
|
||||
piece = _FixedPiece(item.layers, [item.shape.bounding_box()], via=item)
|
||||
elif isinstance(item, Trace) and not item.is_empty():
|
||||
boxes = list(PolylineShape(Polyline(item.corners), item.half_width).tiles())
|
||||
boxes += [corner_box(e, item.half_width) for e in (item.corners[0], item.corners[-1])]
|
||||
piece = _FixedPiece([item.layer], boxes)
|
||||
else:
|
||||
continue
|
||||
cache.setdefault(item.net_nos[0], []).append(piece)
|
||||
return cache.get(net_no, [])
|
||||
|
||||
def _pins_by_net(self) -> dict[int, list[Pin]]:
|
||||
by_net: dict[int, list[Pin]] = {}
|
||||
for pin in self.board.get_pins():
|
||||
@@ -1361,13 +1385,49 @@ def _pin_touches_zone(pin: Pin, zone, plane: bool = False) -> bool:
|
||||
return any(zone.polygon.contains(c) for c in corners)
|
||||
|
||||
|
||||
def _touches(a: IntBox, b: IntBox) -> bool:
|
||||
return not (a.ur.x < b.ll.x or b.ur.x < a.ll.x or a.ur.y < b.ll.y or b.ur.y < a.ll.y)
|
||||
|
||||
|
||||
def _fixed_joins_zone(piece, zone, plane: bool) -> bool:
|
||||
"""A through via inside a plane joins it; copper inside a zone on its
|
||||
own layer joins that zone."""
|
||||
if zone.polygon is None:
|
||||
return False
|
||||
if piece.via is not None:
|
||||
if plane or zone.layer in piece.layers:
|
||||
return zone.polygon.contains(piece.via.location)
|
||||
return False
|
||||
return zone.layer in piece.layers and any(
|
||||
zone.polygon.contains(IntPoint((b.ll.x + b.ur.x) // 2, (b.ll.y + b.ur.y) // 2))
|
||||
for b in piece.boxes
|
||||
)
|
||||
|
||||
|
||||
class _FixedPiece:
|
||||
"""One track or via already on the board, as connectivity."""
|
||||
|
||||
__slots__ = ("layers", "boxes", "via")
|
||||
|
||||
def __init__(self, layers, boxes, via=None) -> None:
|
||||
self.layers = frozenset(layers)
|
||||
self.boxes = boxes
|
||||
self.via = via # the Via item, for a via
|
||||
|
||||
|
||||
def _connected_groups(
|
||||
pins: list[Pin], zones: list, planes: frozenset = frozenset()
|
||||
pins: list[Pin], zones: list, planes: frozenset = frozenset(), fixed: list | None = None
|
||||
) -> list[tuple[list[Pin], list]]:
|
||||
"""Pins and zones of one net that copper already joins: a pad in a zone
|
||||
is connected to it, and two zones sharing a pad are one. Only the groups
|
||||
need routing between them. Pin groups come first, in pin order."""
|
||||
parent = list(range(len(pins) + len(zones)))
|
||||
is connected to it, two zones sharing a pad are one, and copper already
|
||||
on the board (tracks and vias, ``fixed``) joins whatever it touches. Only
|
||||
the groups need routing between them. Pin groups come first, in pin
|
||||
order. A group's fixed vias join its pins as extra route ends, so a
|
||||
fanned-out pin is routed from its via; copper that touches no pin and no
|
||||
zone adds nothing to route."""
|
||||
fixed = fixed or []
|
||||
n_pins, n_zones = len(pins), len(zones)
|
||||
parent = list(range(n_pins + n_zones + len(fixed)))
|
||||
|
||||
def find(i: int) -> int:
|
||||
while parent[i] != i:
|
||||
@@ -1379,6 +1439,24 @@ def _connected_groups(
|
||||
for i, pin in enumerate(pins):
|
||||
if _pin_touches_zone(pin, zone, id(zone) in planes):
|
||||
parent[find(i)] = find(len(pins) + j)
|
||||
base = n_pins + n_zones
|
||||
for k, piece in enumerate(fixed):
|
||||
me = base + k
|
||||
for i, pin in enumerate(pins):
|
||||
if pin.shape is None or not piece.layers & set(pin.layers):
|
||||
continue
|
||||
pad = pin.shape.bounding_box()
|
||||
if any(_touches(pad, b) for b in piece.boxes):
|
||||
parent[find(i)] = find(me)
|
||||
for j, zone in enumerate(zones):
|
||||
if _fixed_joins_zone(piece, zone, id(zone) in planes):
|
||||
parent[find(n_pins + j)] = find(me)
|
||||
for k2 in range(k):
|
||||
other = fixed[k2]
|
||||
if piece.layers & other.layers and any(
|
||||
_touches(a, b) for a in piece.boxes for b in other.boxes
|
||||
):
|
||||
parent[find(base + k2)] = find(me)
|
||||
groups: dict[int, tuple[list[Pin], list]] = {}
|
||||
order: list[int] = []
|
||||
for i, pin in enumerate(pins):
|
||||
@@ -1393,9 +1471,22 @@ def _connected_groups(
|
||||
groups[root] = ([], [])
|
||||
order.append(root)
|
||||
groups[root][1].append(zone)
|
||||
for k, piece in enumerate(fixed):
|
||||
root = find(base + k)
|
||||
if piece.via is not None and root in groups:
|
||||
groups[root][0].append(_via_end(piece.via))
|
||||
return [groups[r] for r in order]
|
||||
|
||||
|
||||
def _via_end(via) -> Pin:
|
||||
"""A fixed via as a place a route may start or end (not a pad to route)."""
|
||||
return Pin(
|
||||
net_nos=list(via.net_nos), fixed=True, name="via", component_ref="",
|
||||
padstack_name=via.padstack_name, location=via.location,
|
||||
layers=list(via.layers), shape=via.shape,
|
||||
)
|
||||
|
||||
|
||||
def _split_path(path: list[Node]) -> tuple[list[tuple[int, list[Cell]]], list[Cell]]:
|
||||
"""Split a node path into per-layer wire segments and via cells."""
|
||||
wires: list[tuple[int, list[Cell]]] = []
|
||||
|
||||
Reference in New Issue
Block a user