Finer routing grid: --grid-resolution K with trace halos
A cell was exactly one trace plus its clearance, so a trace owning its cell was the whole clearance model, and the 0.4 mm grid could not reach most pins of a 0.5 mm pitch package: between two 0.3 mm pads a trace's centre has a legal band only 0.15 mm wide. Resolution K makes cells K times finer and has every trace claim a halo, the cells whose centre is nearer than trace + clearance to one of its own, released again on rip-up. K = 1 has an empty halo and is the old grid exactly (tested). On a 0.5 mm row, K = 4 gets every pin out where K = 1 gets two. On the rc4 boards, K = 2 cuts the sense board's opens from 66 to 41 (JAR: 32) and the power board's to 7 (JAR: 12), still with no DRC errors from freeroute's own copper.
This commit is contained in:
@@ -101,6 +101,7 @@ class _Connection:
|
||||
"trace_cells",
|
||||
"via_cells",
|
||||
"via_halo",
|
||||
"halo_nodes",
|
||||
)
|
||||
|
||||
def __init__(self, index: int, net_no: int, a: Pin, b: Pin) -> None:
|
||||
@@ -115,6 +116,7 @@ class _Connection:
|
||||
self.trace_cells: list[tuple[Cell, int]] = []
|
||||
self.via_cells: list[Cell] = []
|
||||
self.via_halo: list[Cell] = []
|
||||
self.halo_nodes: list[Node] = []
|
||||
|
||||
|
||||
class GridRouter:
|
||||
@@ -135,6 +137,7 @@ class GridRouter:
|
||||
orthogonal: bool = False,
|
||||
via_half: int | None = None,
|
||||
edge_clearance: int | None = None,
|
||||
resolution: int = 1,
|
||||
) -> None:
|
||||
self.board = board
|
||||
self.trace_width = max(trace_width, 1)
|
||||
@@ -149,8 +152,21 @@ class GridRouter:
|
||||
# trace copper; 8-connected also produces 45-degree segments.
|
||||
self._neighbours = _NEIGHBOURS[:4] if orthogonal else _NEIGHBOURS
|
||||
self._orthogonal = orthogonal
|
||||
# cell size must fit a trace plus its clearance to a neighbouring trace
|
||||
self.step = max(self.trace_width + self.clearance, 1)
|
||||
# At resolution 1 a cell is exactly one trace plus its clearance, so a
|
||||
# trace owning its cell is the whole clearance model. At resolution k
|
||||
# cells are k times finer, fine enough to give every pin of a 0.5 mm
|
||||
# pitch row its own column, and a trace also claims a halo: the cells
|
||||
# whose centre is nearer than trace + clearance to one of its own.
|
||||
self.resolution = max(int(resolution), 1)
|
||||
self.step = max((self.trace_width + self.clearance) // self.resolution, 1)
|
||||
need = self.trace_width + self.clearance
|
||||
reach = math.ceil(need / self.step)
|
||||
self._halo_offsets = [
|
||||
(dx, dy)
|
||||
for dx in range(-reach, reach + 1)
|
||||
for dy in range(-reach, reach + 1)
|
||||
if (dx or dy) and math.hypot(dx, dy) * self.step < need
|
||||
]
|
||||
# A via's copper is wider than a trace. Another net's trace needs its
|
||||
# centre via_half + clearance + half_width from the via's centre, so a
|
||||
# via also claims every cell within via_reach of its own.
|
||||
@@ -177,6 +193,8 @@ class GridRouter:
|
||||
# per-connection occupancy (owner = connection index)
|
||||
self._cell_owner: dict[Node, int] = {}
|
||||
self._via_owner: dict[Cell, int] = {}
|
||||
# trace halos: node -> indices of the connections claiming it
|
||||
self._halo: dict[Node, list[int]] = {}
|
||||
self._rasterize_obstacles()
|
||||
|
||||
# --- grid helpers -------------------------------------------------------
|
||||
@@ -299,6 +317,11 @@ class GridRouter:
|
||||
via_owner = self._via_owner.get(cell)
|
||||
if via_owner is not None and conns[via_owner].net_no != net_no:
|
||||
blocking = via_owner
|
||||
if blocking is None:
|
||||
for i in self._halo.get(node, ()):
|
||||
if conns[i].net_no != net_no:
|
||||
blocking = i
|
||||
break
|
||||
if blocking is None:
|
||||
return 0.0
|
||||
if not allow_ripup:
|
||||
@@ -458,6 +481,14 @@ class GridRouter:
|
||||
conn.via_cells = list(via_cells)
|
||||
for cell, layer in conn.trace_cells:
|
||||
self._cell_owner[(*cell, layer)] = conn.index
|
||||
conn.halo_nodes = []
|
||||
for (cx, cy), layer in conn.trace_cells:
|
||||
for dx, dy in self._halo_offsets:
|
||||
x, y = cx + dx, cy + dy
|
||||
if 0 <= x < self.cols and 0 <= y < self.rows:
|
||||
node = (x, y, layer)
|
||||
self._halo.setdefault(node, []).append(conn.index)
|
||||
conn.halo_nodes.append(node)
|
||||
conn.via_halo = []
|
||||
for cell in conn.via_cells:
|
||||
for c in self._via_cells(cell):
|
||||
@@ -492,6 +523,13 @@ class GridRouter:
|
||||
if self._via_owner.get(cell) == conn.index:
|
||||
del self._via_owner[cell]
|
||||
conn.via_halo = []
|
||||
for node in conn.halo_nodes:
|
||||
claims = self._halo.get(node)
|
||||
if claims is not None:
|
||||
claims.remove(conn.index)
|
||||
if not claims:
|
||||
del self._halo[node]
|
||||
conn.halo_nodes = []
|
||||
conn.trace_cells = []
|
||||
conn.via_cells = []
|
||||
conn.wires = []
|
||||
@@ -508,6 +546,9 @@ class GridRouter:
|
||||
via_owner = self._via_owner.get((cx, cy))
|
||||
if via_owner is not None and conns[via_owner].net_no != net_no:
|
||||
owners.add(via_owner)
|
||||
for i in self._halo.get((cx, cy, cl), ()):
|
||||
if conns[i].net_no != net_no:
|
||||
owners.add(i)
|
||||
return owners
|
||||
|
||||
# --- passes -------------------------------------------------------------
|
||||
@@ -645,6 +686,7 @@ def route_board(
|
||||
orthogonal: bool = False,
|
||||
via_half: int | None = None,
|
||||
edge_clearance: int | None = None,
|
||||
resolution: int = 1,
|
||||
) -> RouteResult:
|
||||
"""Convenience wrapper: build a :class:`GridRouter` and route the board."""
|
||||
router = GridRouter(
|
||||
@@ -658,5 +700,6 @@ def route_board(
|
||||
orthogonal=orthogonal,
|
||||
via_half=via_half,
|
||||
edge_clearance=edge_clearance,
|
||||
resolution=resolution,
|
||||
)
|
||||
return router.route()
|
||||
|
||||
Reference in New Issue
Block a user