Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6cc2406583 | ||
|
|
30297e94dc |
@@ -215,7 +215,8 @@ def route_board_exact(
|
|||||||
# connections of one net that end on the same plane cell share its
|
# connections of one net that end on the same plane cell share its
|
||||||
# via: write it once, and never on top of a via already on the board
|
# via: write it once, and never on top of a via already on the board
|
||||||
acc.vias.extend(
|
acc.vias.extend(
|
||||||
v for v in dict.fromkeys(vias) if v not in acc.vias and (net_no, v) not in fixed_vias
|
v for v in dict.fromkeys(vias)
|
||||||
|
if v not in acc.vias and not _on_fixed_via(net_no, v, fixed_vias, via_half)
|
||||||
)
|
)
|
||||||
for box, box_layers in boxes:
|
for box, box_layers in boxes:
|
||||||
oid = next(owner)
|
oid = next(owner)
|
||||||
@@ -645,6 +646,15 @@ def _end_in_rule_area(tree, wires, half_width) -> bool:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _on_fixed_via(net_no, point, fixed_vias, via_half) -> bool:
|
||||||
|
"""A routed via that reuses a via already on the board: the grid put it
|
||||||
|
on the fixed via's cell, a little off its centre, and it is not written."""
|
||||||
|
return any(
|
||||||
|
net == net_no and abs(loc.x - point.x) <= via_half and abs(loc.y - point.y) <= via_half
|
||||||
|
for net, loc in fixed_vias
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _copper_boxes(wires, vias, half_width, via_layers, via_half=None):
|
def _copper_boxes(wires, vias, half_width, via_layers, via_half=None):
|
||||||
via_half = via_half or half_width
|
via_half = via_half or half_width
|
||||||
boxes: list[tuple[IntBox, frozenset[int]]] = []
|
boxes: list[tuple[IntBox, frozenset[int]]] = []
|
||||||
|
|||||||
@@ -257,6 +257,7 @@ class GridRouter:
|
|||||||
# an own-net via at the very same cell, which it simply reuses.
|
# an own-net via at the very same cell, which it simply reuses.
|
||||||
drill = via_drill if via_drill else 2 * self.via_half
|
drill = via_drill if via_drill else 2 * self.via_half
|
||||||
need = drill + max(hole_to_hole, 0)
|
need = drill + max(hole_to_hole, 0)
|
||||||
|
self._via_drill, self._hole_gap, self._hole_need = drill, max(hole_to_hole, 0), need
|
||||||
reach = math.ceil(need / self.step)
|
reach = math.ceil(need / self.step)
|
||||||
self._hole_offsets = [
|
self._hole_offsets = [
|
||||||
(dx, dy)
|
(dx, dy)
|
||||||
@@ -265,6 +266,7 @@ class GridRouter:
|
|||||||
if (dx or dy) and math.hypot(dx, dy) * self.step < need
|
if (dx or dy) and math.hypot(dx, dy) * self.step < need
|
||||||
]
|
]
|
||||||
self._via_nets: dict[Cell, dict[int, int]] = {} # routed via centres
|
self._via_nets: dict[Cell, dict[int, int]] = {} # routed via centres
|
||||||
|
self._via_centres: list[IntPoint] | None = None # cache for _hole_clear_at
|
||||||
self._fixed_vias: dict[Cell, int] = {}
|
self._fixed_vias: dict[Cell, int] = {}
|
||||||
self._fixed_via_points: set[tuple[int, IntPoint]] = set()
|
self._fixed_via_points: set[tuple[int, IntPoint]] = set()
|
||||||
|
|
||||||
@@ -299,6 +301,21 @@ class GridRouter:
|
|||||||
if isinstance(item, Via) and item.fixed and item.net_nos:
|
if isinstance(item, Via) and item.fixed and item.net_nos:
|
||||||
self._fixed_vias[self._cell_of(item.location)] = item.net_nos[0]
|
self._fixed_vias[self._cell_of(item.location)] = item.net_nos[0]
|
||||||
self._fixed_via_points.add((item.net_nos[0], item.location))
|
self._fixed_via_points.add((item.net_nos[0], item.location))
|
||||||
|
# Through-hole pads have holes too, and a route changes layer at one
|
||||||
|
# for free, so no via (of any net) goes within hole spacing of one.
|
||||||
|
# The DSN has no drill for a pad; its smaller half size stands in.
|
||||||
|
self._tht_holes: list[tuple[IntPoint, int]] = []
|
||||||
|
self._tht_block: set[Cell] = set()
|
||||||
|
for pin in board.get_pins():
|
||||||
|
if pin.shape is None or sum(1 for layer in pin.layers if layer in self.layers) < 2:
|
||||||
|
continue
|
||||||
|
box = pin.shape.bounding_box()
|
||||||
|
radius = min(box.ur.x - box.ll.x, box.ur.y - box.ll.y) // 2
|
||||||
|
reach = radius + self._via_drill // 2 + self._hole_gap
|
||||||
|
self._tht_holes.append((pin.location, reach))
|
||||||
|
ring = IntBox(pin.location.x, pin.location.y, pin.location.x, pin.location.y)
|
||||||
|
for cell in self._cells_in_box(ring, reach):
|
||||||
|
self._tht_block.add(cell)
|
||||||
|
|
||||||
# --- grid helpers -------------------------------------------------------
|
# --- grid helpers -------------------------------------------------------
|
||||||
|
|
||||||
@@ -524,7 +541,10 @@ class GridRouter:
|
|||||||
return nets
|
return nets
|
||||||
|
|
||||||
def _holes_clear(self, cell: Cell, net_no: int) -> bool:
|
def _holes_clear(self, cell: Cell, net_no: int) -> bool:
|
||||||
"""No via centre within hole-to-hole reach, but an own-net one here."""
|
"""No via centre within hole-to-hole reach, but an own-net one here,
|
||||||
|
and no through-hole pad's hole within reach at all."""
|
||||||
|
if cell in self._tht_block:
|
||||||
|
return False
|
||||||
if self._via_nets_at(cell) - {net_no}:
|
if self._via_nets_at(cell) - {net_no}:
|
||||||
return False
|
return False
|
||||||
cx, cy = cell
|
cx, cy = cell
|
||||||
@@ -717,7 +737,23 @@ class GridRouter:
|
|||||||
]
|
]
|
||||||
copper += [(box, net) for net, _, box in self.board.get_fixed_copper()]
|
copper += [(box, net) for net, _, box in self.board.get_fixed_copper()]
|
||||||
self._static_copper = copper
|
self._static_copper = copper
|
||||||
return not any(net != net_no and box.overlaps(grown) for box, net in self._static_copper)
|
if any(net != net_no and box.overlaps(grown) for box, net in self._static_copper):
|
||||||
|
return False
|
||||||
|
return self._hole_clear_at(point)
|
||||||
|
|
||||||
|
def _hole_clear_at(self, point: IntPoint) -> bool:
|
||||||
|
"""Hole spacing measured exactly from ``point`` to every via and
|
||||||
|
through-hole pad (a via moved into a pad is not on its cell's centre)."""
|
||||||
|
need = self._hole_need
|
||||||
|
if self._via_centres is None: # rebuilt only after vias change
|
||||||
|
self._via_centres = [loc for _net, loc in self._fixed_via_points] + [
|
||||||
|
self._cell_center(c) for c, count in self._via_nets.items() if any(count.values())
|
||||||
|
]
|
||||||
|
if any(math.hypot(c.x - point.x, c.y - point.y) < need for c in self._via_centres):
|
||||||
|
return False
|
||||||
|
return not any(
|
||||||
|
math.hypot(loc.x - point.x, loc.y - point.y) < reach for loc, reach in self._tht_holes
|
||||||
|
)
|
||||||
|
|
||||||
def _stub_cells(self, pin_location: IntPoint, cell: Cell, half: int | None = None) -> list[Cell]:
|
def _stub_cells(self, pin_location: IntPoint, cell: Cell, half: int | None = None) -> list[Cell]:
|
||||||
"""Cells where another net's trace would come too close to the stub."""
|
"""Cells where another net's trace would come too close to the stub."""
|
||||||
@@ -993,6 +1029,8 @@ class GridRouter:
|
|||||||
for cell in conn.via_cells:
|
for cell in conn.via_cells:
|
||||||
count = self._via_nets.setdefault(cell, {})
|
count = self._via_nets.setdefault(cell, {})
|
||||||
count[conn.net_no] = count.get(conn.net_no, 0) + 1
|
count[conn.net_no] = count.get(conn.net_no, 0) + 1
|
||||||
|
if conn.via_cells:
|
||||||
|
self._via_centres = None
|
||||||
for cell, layer in conn.trace_cells:
|
for cell, layer in conn.trace_cells:
|
||||||
self._cell_owner[(*cell, layer)] = conn.index
|
self._cell_owner[(*cell, layer)] = conn.index
|
||||||
conn.half = half = self._conn_half(conn)
|
conn.half = half = self._conn_half(conn)
|
||||||
@@ -1071,6 +1109,8 @@ class GridRouter:
|
|||||||
count = self._via_nets.get(cell)
|
count = self._via_nets.get(cell)
|
||||||
if count and count.get(conn.net_no):
|
if count and count.get(conn.net_no):
|
||||||
count[conn.net_no] -= 1
|
count[conn.net_no] -= 1
|
||||||
|
if conn.via_cells:
|
||||||
|
self._via_centres = None
|
||||||
conn.via_cells = []
|
conn.via_cells = []
|
||||||
conn.wires = []
|
conn.wires = []
|
||||||
conn.vias = []
|
conn.vias = []
|
||||||
|
|||||||
@@ -54,3 +54,47 @@ def test_connections_sharing_a_plane_via_write_it_once():
|
|||||||
vias = exact.result.vias[1]
|
vias = exact.result.vias[1]
|
||||||
assert len(vias) < 8, "no via is shared, so this board no longer tests sharing"
|
assert len(vias) < 8, "no via is shared, so this board no longer tests sharing"
|
||||||
assert len(set(vias)) == len(vias), "a shared via written more than once"
|
assert len(set(vias)) == len(vias), "a shared via written more than once"
|
||||||
|
|
||||||
|
|
||||||
|
def _router(dsn_text):
|
||||||
|
from freeroute.board import build_board
|
||||||
|
from freeroute.route.grid_router import GridRouter
|
||||||
|
from freeroute.route.pipeline import _rule_clearance_dsn, _rule_width_dsn
|
||||||
|
|
||||||
|
dsn = parse_dsn(dsn_text)
|
||||||
|
s = dsn.resolution.value
|
||||||
|
return GridRouter(
|
||||||
|
build_board(dsn), trace_width=round(_rule_width_dsn(dsn) * s),
|
||||||
|
clearance=round(_rule_clearance_dsn(dsn) * s), resolution=2, orthogonal=True,
|
||||||
|
via_drill=300 * s, hole_to_hole=250 * s,
|
||||||
|
), s
|
||||||
|
|
||||||
|
|
||||||
|
_THT = """(pcb tht (parser) (resolution um 10) (unit um)
|
||||||
|
(structure (layer F.Cu (type signal)) (layer B.Cu (type signal))
|
||||||
|
(boundary (path pcb 0 0 0 20000 0 20000 -20000 0 -20000 0 0))
|
||||||
|
(via "Via[0-1]_500:300_um") (rule (width 150) (clearance 150)))
|
||||||
|
(placement (component T (place J1 10000 -10000 front 0)))
|
||||||
|
(library (image T (pin tht 1 0 0))
|
||||||
|
(padstack tht (shape (circle F.Cu 1700)) (shape (circle B.Cu 1700)) (attach off))
|
||||||
|
(padstack "Via[0-1]_500:300_um" (shape (circle F.Cu 500)) (shape (circle B.Cu 500)) (attach off)))
|
||||||
|
(network (net SIG (pins J1-1))))"""
|
||||||
|
|
||||||
|
|
||||||
|
def test_no_via_goes_near_a_through_hole_pads_hole_even_of_its_own_net():
|
||||||
|
router, s = _router(_THT)
|
||||||
|
pin = router._cell_of(router.board.get_pins()[0].location)
|
||||||
|
assert not router._holes_clear(pin, 1) # own net, right on the pad
|
||||||
|
far = (pin[0] + 40, pin[1])
|
||||||
|
assert router._holes_clear(far, 1)
|
||||||
|
|
||||||
|
|
||||||
|
def test_a_via_in_a_pad_keeps_hole_spacing_from_a_fixed_via():
|
||||||
|
from freeroute.geometry import IntPoint
|
||||||
|
|
||||||
|
text = _THT.replace("(network (net SIG (pins J1-1))))",
|
||||||
|
"(network (net SIG (pins J1-1)))\n"
|
||||||
|
" (wiring (via \"Via[0-1]_500:300_um\" 3000 -3000 (net SIG) (type route))))")
|
||||||
|
router, s = _router(text)
|
||||||
|
assert not router._hole_clear_at(IntPoint(3400 * s, -3000 * s)) # 0.4 mm < 0.55 mm
|
||||||
|
assert router._hole_clear_at(IntPoint(3600 * s, -3000 * s))
|
||||||
|
|||||||
Reference in New Issue
Block a user