Pin access: --access-cost, vias in pads inside wire keepouts, exact stub checks
--access-cost C makes a route pay C to pass where it would close another pin's way out (its end cells, the via it may need there, and the clearance around both) while that pin still has connections to route. A pin inside a wire keepout can only leave by a via; its stub to the cell centre was track inside the keepout, which the exact check refused. The via now goes on the pin itself. Pin stubs are also checked exactly against wire keepouts, as the exact engine will check them. bench/diagnose.py reports what each refused connection collided with.
This commit is contained in:
@@ -157,8 +157,16 @@ class GridRouter:
|
||||
heuristic_weight: float = 1.0,
|
||||
seed: int | None = None,
|
||||
route_first: frozenset | None = None,
|
||||
access_cost: float = 0.0,
|
||||
) -> None:
|
||||
self.board = board
|
||||
# Pin access: while a pin still has connections to route, the cells
|
||||
# its route would leave by (its end cells, the via it may need there,
|
||||
# and the clearance around both) cost other nets this much extra, so
|
||||
# they route round a fine-pitch pin's way out instead of sealing it.
|
||||
self.access_cost = max(float(access_cost), 0.0)
|
||||
self._doorways: dict[Node, list[tuple[int, int]]] = {}
|
||||
self._pending: dict[int, int] = {}
|
||||
# None keeps the deterministic order; a seed shuffles which connection
|
||||
# routes first, to measure how much a result owes to the order
|
||||
self.seed = seed
|
||||
@@ -289,6 +297,8 @@ class GridRouter:
|
||||
margin = self.clearance + self.half_width
|
||||
outline = self.board.outline
|
||||
self._via_edge_block: set[Cell] = set()
|
||||
# wire keepouts as boxes, to check a pin stub the way the exact engine will
|
||||
self._keepout_boxes: dict[int, list[IntBox]] = {}
|
||||
if outline is not None:
|
||||
# cells too close to the real outline (rounded corners included):
|
||||
# for a trace on every layer, and separately for a via's wider copper
|
||||
@@ -325,6 +335,8 @@ class GridRouter:
|
||||
if box.is_empty():
|
||||
continue
|
||||
# the same distance the exact engine verifies against
|
||||
if item.blocks_wires:
|
||||
self._keepout_boxes.setdefault(item.layer, []).append(box)
|
||||
for cell in self._cells_in_box(box, margin):
|
||||
if item.blocks_wires:
|
||||
self._keepout.add((*cell, item.layer))
|
||||
@@ -474,12 +486,16 @@ class GridRouter:
|
||||
enter = self._enter_cost(cell, cl, net_no, allow_ripup, conns)
|
||||
if enter is None:
|
||||
continue
|
||||
if self._doorways:
|
||||
enter += self._access(nxt, net_no)
|
||||
step = 1.0 if dx == 0 or dy == 0 else math.sqrt(2)
|
||||
self._relax(
|
||||
current, nxt, base + step + enter, goal_cell, g_score, came_from, open_heap
|
||||
)
|
||||
if len(self.layers) > 1:
|
||||
g_via = base + self.via_cost
|
||||
if self._doorways:
|
||||
g_via += self._access(current, net_no)
|
||||
for layer in self.layers:
|
||||
if layer == cl:
|
||||
continue
|
||||
@@ -513,6 +529,18 @@ class GridRouter:
|
||||
]
|
||||
|
||||
def _stub_clear(self, pin: Pin, cell: Cell, layer: int, net_no: int, conns) -> bool:
|
||||
keepouts = self._keepout_boxes.get(layer)
|
||||
if keepouts:
|
||||
# the stub is off the grid, so check it exactly, as the exact
|
||||
# engine will: its copper grown by clearance must miss every keepout
|
||||
from freeroute.geometry.polyline import Polyline, PolylineShape
|
||||
|
||||
end = self._cell_center(cell)
|
||||
if end != pin.location:
|
||||
for tile in PolylineShape(Polyline([pin.location, end]), self.half_width).tiles():
|
||||
grown = tile.offset(self.clearance)
|
||||
if any(k.overlaps(grown) for k in keepouts):
|
||||
return False
|
||||
for c in self._stub_cells(pin.location, cell):
|
||||
owner = self._cell_owner.get((*c, layer))
|
||||
if owner is not None and conns[owner].net_no != net_no:
|
||||
@@ -765,8 +793,19 @@ class GridRouter:
|
||||
if node not in self._cell_owner:
|
||||
self._cell_owner[node] = conn.index
|
||||
conn.trace_cells.append((c, layer))
|
||||
# A pin inside a wire keepout can only leave by a via. Its stub to the
|
||||
# cell centre would be track inside the keepout, so the via goes on
|
||||
# the pin itself (a via in its own pad) and the stub is left out.
|
||||
in_pad: dict[int, IntPoint] = {}
|
||||
if len(wire_segs) > 1:
|
||||
for idx, pin in ((0, conn.a_end), (len(wire_segs) - 1, conn.b_end)):
|
||||
layer, cells = wire_segs[idx]
|
||||
if isinstance(pin, Pin) and len(cells) == 1 and (*cells[0], layer) in self._keepout:
|
||||
in_pad[idx] = pin.location
|
||||
conn.wires = []
|
||||
for idx, (layer, cells) in enumerate(wire_segs):
|
||||
if idx in in_pad:
|
||||
continue
|
||||
points = [self._cell_center(c) for c in cells]
|
||||
if idx == 0 and isinstance(conn.a_end, Pin):
|
||||
points.insert(0, conn.a_end.location)
|
||||
@@ -776,7 +815,11 @@ class GridRouter:
|
||||
if len(points) >= 2:
|
||||
conn.wires.append((layer, points))
|
||||
conn.vias = [self._cell_center(c) for c in conn.via_cells]
|
||||
for idx, location in in_pad.items():
|
||||
# via k joins wire segments k and k + 1
|
||||
conn.vias[0 if idx == 0 else idx - 1] = location
|
||||
conn.routed = True
|
||||
self._settle(conn, -1)
|
||||
|
||||
def _rip(self, conn: _Connection) -> None:
|
||||
for cell, layer in conn.trace_cells:
|
||||
@@ -799,6 +842,7 @@ class GridRouter:
|
||||
conn.vias = []
|
||||
conn.routed = False
|
||||
conn.rip_count += 1
|
||||
self._settle(conn, +1)
|
||||
|
||||
def _crossed_owners(self, path: list[Node], net_no: int, conns: list[_Connection]) -> set[int]:
|
||||
owners: set[int] = set()
|
||||
@@ -814,6 +858,50 @@ class GridRouter:
|
||||
owners.add(i)
|
||||
return owners
|
||||
|
||||
# --- pin access ---------------------------------------------------------
|
||||
|
||||
@staticmethod
|
||||
def _group_pins(conn: _Connection):
|
||||
return [*conn.a_group[0], *conn.b_group[0]]
|
||||
|
||||
def _build_doorways(self, conns: list[_Connection]) -> None:
|
||||
"""Map every cell that would close a pin's way out to that pin."""
|
||||
spread = [(0, 0), *self._halo_offsets]
|
||||
done: set[int] = set()
|
||||
for conn in conns:
|
||||
for pin in self._group_pins(conn):
|
||||
self._pending[id(pin)] = self._pending.get(id(pin), 0) + 1
|
||||
if id(pin) in done:
|
||||
continue
|
||||
done.add(id(pin))
|
||||
claim = (id(pin), conn.net_no)
|
||||
exits: set[Node] = set()
|
||||
for x, y, layer in self._end_nodes(pin, conn.net_no, conns):
|
||||
exits.add((x, y, layer))
|
||||
for c in self._via_cells((x, y)):
|
||||
exits.update((*c, other) for other in self.layers)
|
||||
cells: set[Node] = set()
|
||||
for x, y, layer in exits:
|
||||
for dx, dy in spread:
|
||||
if 0 <= x + dx < self.cols and 0 <= y + dy < self.rows:
|
||||
cells.add((x + dx, y + dy, layer))
|
||||
for node in cells:
|
||||
self._doorways.setdefault(node, []).append(claim)
|
||||
|
||||
def _settle(self, conn: _Connection, delta: int) -> None:
|
||||
if not self._pending:
|
||||
return
|
||||
for pin in self._group_pins(conn):
|
||||
if id(pin) in self._pending:
|
||||
self._pending[id(pin)] += delta
|
||||
|
||||
def _access(self, node: Node, net_no: int) -> float:
|
||||
"""Extra cost for ``net_no`` to use a cell another pin still needs."""
|
||||
for pin_id, owner_net in self._doorways.get(node, ()):
|
||||
if owner_net != net_no and self._pending.get(pin_id, 0) > 0:
|
||||
return self.access_cost
|
||||
return 0.0
|
||||
|
||||
# --- passes -------------------------------------------------------------
|
||||
|
||||
def route(self) -> RouteResult:
|
||||
@@ -822,6 +910,8 @@ class GridRouter:
|
||||
self._conns = conns
|
||||
if not conns:
|
||||
return RouteResult(self.half_width)
|
||||
if self.access_cost:
|
||||
self._build_doorways(conns)
|
||||
|
||||
passes = self.max_passes if self.rip_up else 1
|
||||
best_count = -1
|
||||
@@ -1067,6 +1157,7 @@ def route_board(
|
||||
heuristic_weight: float = 1.0,
|
||||
seed: int | None = None,
|
||||
route_first: frozenset | None = None,
|
||||
access_cost: float = 0.0,
|
||||
) -> RouteResult:
|
||||
"""Convenience wrapper: build a :class:`GridRouter` and route the board."""
|
||||
router = GridRouter(
|
||||
@@ -1084,5 +1175,6 @@ def route_board(
|
||||
heuristic_weight=heuristic_weight,
|
||||
seed=seed,
|
||||
route_first=route_first,
|
||||
access_cost=access_cost,
|
||||
)
|
||||
return router.route()
|
||||
|
||||
Reference in New Issue
Block a user