--fanout: only packages of --fanout-min-pins or more; supply-only mode; vias for signals that change layer

This commit is contained in:
2026-09-29 10:20:11 -06:00
parent 21c80d30f2
commit 74360f2f57
4 changed files with 65 additions and 30 deletions
+30 -16
View File
@@ -43,6 +43,10 @@ class FanoutRules:
edge_clearance: int
signal_layers: list[int]
via_padstack: str
#: smallest package fanned out (a big dense part is where pins get walled in)
min_pins: int = 32
#: fan out plane pins only (a stub and a via each), as a person would first
supply_only: bool = False
def plan_fanout(board: BasicBoard, rules: FanoutRules) -> list:
@@ -54,7 +58,12 @@ def plan_fanout(board: BasicBoard, rules: FanoutRules) -> list:
if z.net_nos and not board.layer_structure.arr[z.layer].is_signal:
planes.setdefault(z.net_nos[0], []).append(z)
placed = _Placed(board, rules)
by_net: dict[int, list[Pin]] = {}
for pin in board.get_pins():
for n in pin.net_nos:
by_net.setdefault(n, []).append(pin)
items: list = []
pitch = 2 * rules.via_half + rules.hole_to_hole
for pins in _packages(board, rules).values():
centre = IntPoint(
sum(p.location.x for p in pins) // len(pins), sum(p.location.y for p in pins) // len(pins)
@@ -68,22 +77,27 @@ def plan_fanout(board: BasicBoard, rules: FanoutRules) -> list:
reach = _pad_reach(pin, direction) + rules.clearance + half + rules.trace_width
end = _step(pin.location, direction, reach)
plane = planes.get(net)
if plane is not None:
# stagger the via of every other pin along the side outward
pitch = 2 * rules.via_half + rules.hole_to_hole
options = [reach + rules.via_half + rules.clearance + (k % 2) * pitch,
reach + rules.via_half + rules.clearance + ((k + 1) % 2) * pitch]
for d in options:
at = _step(pin.location, direction, d)
if not any(z.polygon is not None and z.polygon.contains(at) for z in plane):
continue
if placed.stub_fits(pin.location, at, layer, net, half) and placed.via_fits(at, net):
items.append(placed.add_stub(pin.location, at, layer, net, half))
items.append(placed.add_via(at, net))
break
partners = [q for q in by_net.get(net, []) if q is not pin]
# a signal whose other pins are all on other layers has to change
# layer somewhere: do it at the stub's end, away from the pads
elsewhere = bool(partners) and all(layer not in q.layers for q in partners)
if plane is None and (rules.supply_only or not elsewhere):
if not rules.supply_only and placed.stub_fits(pin.location, end, layer, net, half):
items.append(placed.add_stub(pin.location, end, layer, net, half))
continue
if placed.stub_fits(pin.location, end, layer, net, half):
items.append(placed.add_stub(pin.location, end, layer, net, half))
# stagger the via of every other pin along the side outward
options = [reach + rules.via_half + rules.clearance + (k % 2) * pitch,
reach + rules.via_half + rules.clearance + ((k + 1) % 2) * pitch]
for d in options:
at = _step(pin.location, direction, d)
if plane is not None and not any(
z.polygon is not None and z.polygon.contains(at) for z in plane
):
continue
if placed.stub_fits(pin.location, at, layer, net, half) and placed.via_fits(at, net):
items.append(placed.add_stub(pin.location, at, layer, net, half))
items.append(placed.add_via(at, net))
break
return items
@@ -100,7 +114,7 @@ def _packages(board: BasicBoard, rules: FanoutRules) -> dict[str, list[Pin]]:
fine = _FINE_PITCH_FACTOR * (rules.trace_width + 2 * rules.clearance)
out = {}
for ref, pins in by_ref.items():
if len(pins) < _MIN_PINS:
if len(pins) < max(rules.min_pins, _MIN_PINS):
continue
close = sum(
1 for p in pins