--fanout: only packages of --fanout-min-pins or more; supply-only mode; vias for signals that change layer
This commit is contained in:
+16
-5
@@ -189,10 +189,19 @@ def build_parser() -> argparse.ArgumentParser:
|
||||
)
|
||||
parser.add_argument(
|
||||
"--fanout",
|
||||
action="store_true",
|
||||
nargs="?",
|
||||
const="all",
|
||||
choices=["all", "supply"],
|
||||
help="grid and exact engines: before routing, give every fine-pitch pin a stub "
|
||||
"outward past its pad row (and a via to its plane, for a plane net), committed "
|
||||
"as copper so later routes cannot wall it in",
|
||||
"as copper so later routes cannot wall it in; --fanout supply: plane pins only",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--fanout-min-pins",
|
||||
type=int,
|
||||
default=32,
|
||||
metavar="N",
|
||||
help="with --fanout: fan out only packages with at least N fine-pitch pins (default 32)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--jobs",
|
||||
@@ -224,7 +233,7 @@ def main(argv: list[str] | None = None) -> int:
|
||||
("promote", args.promote_failures is not None),
|
||||
("access", args.access_cost is not None),
|
||||
("rollback", not args.rollback),
|
||||
("fanout", args.fanout),
|
||||
("fanout", bool(args.fanout)),
|
||||
)
|
||||
if asked
|
||||
]
|
||||
@@ -260,7 +269,8 @@ def main(argv: list[str] | None = None) -> int:
|
||||
"heuristic_weight": args.heuristic_weight,
|
||||
"access_cost": args.access_cost or 0.0,
|
||||
"hole_to_hole_mm": args.hole_to_hole,
|
||||
"fanout": args.fanout,
|
||||
"fanout": args.fanout or False,
|
||||
"fanout_min_pins": args.fanout_min_pins,
|
||||
"rollback": args.rollback,
|
||||
}
|
||||
elif args.engine == "grid":
|
||||
@@ -272,7 +282,8 @@ def main(argv: list[str] | None = None) -> int:
|
||||
"resolution": 1 if args.grid_resolution is None else args.grid_resolution,
|
||||
"access_cost": args.access_cost or 0.0,
|
||||
"hole_to_hole_mm": args.hole_to_hole,
|
||||
"fanout": args.fanout,
|
||||
"fanout": args.fanout or False,
|
||||
"fanout_min_pins": args.fanout_min_pins,
|
||||
"rollback": args.rollback,
|
||||
}
|
||||
if args.engine == "room":
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -88,7 +88,9 @@ def _dsn_units_per_um(dsn: DsnBoard) -> float:
|
||||
return {"um": 1.0, "mm": 0.001, "mil": 1 / 25.4, "inch": 1 / 25400}.get(dsn.unit, 1.0)
|
||||
|
||||
|
||||
def _place_fanout(board, dsn, scale, layers, width, clearance, edge, hole_to_hole_mm) -> None:
|
||||
def _place_fanout(
|
||||
board, dsn, scale, layers, width, clearance, edge, hole_to_hole_mm, mode=True, min_pins=32
|
||||
) -> None:
|
||||
"""Put the fan-out's stubs and vias on the board, before routing."""
|
||||
from .fanout import FanoutRules, plan_fanout
|
||||
|
||||
@@ -102,6 +104,8 @@ def _place_fanout(board, dsn, scale, layers, width, clearance, edge, hole_to_hol
|
||||
edge_clearance=edge or 0,
|
||||
signal_layers=layers or [i for i, layer in enumerate(dsn.layers) if layer.is_signal],
|
||||
via_padstack=_via_padstack(dsn),
|
||||
min_pins=min_pins,
|
||||
supply_only=mode == "supply",
|
||||
)
|
||||
for item in plan_fanout(board, rules):
|
||||
board.insert(item)
|
||||
@@ -151,7 +155,8 @@ def route_dsn_board(
|
||||
route_first: frozenset | None = None,
|
||||
access_cost: float = 0.0,
|
||||
hole_to_hole_mm: float = 0.25,
|
||||
fanout: bool = False,
|
||||
fanout: bool | str = False,
|
||||
fanout_min_pins: int = 32,
|
||||
rollback: bool = True,
|
||||
) -> tuple[RouteResult, int, list[str]]:
|
||||
"""Route a parsed :class:`DsnBoard`; return the board-unit result, the scale,
|
||||
@@ -165,7 +170,7 @@ def route_dsn_board(
|
||||
clearance_board = round(_rule_clearance_dsn(dsn) * scale)
|
||||
if fanout:
|
||||
_place_fanout(board, dsn, scale, layers, width_board, clearance_board,
|
||||
edge_clearance_board, hole_to_hole_mm)
|
||||
edge_clearance_board, hole_to_hole_mm, fanout, fanout_min_pins)
|
||||
result = route_board(
|
||||
board,
|
||||
resolution=resolution,
|
||||
@@ -234,7 +239,8 @@ def build_routing_result(
|
||||
route_first: frozenset | None = None,
|
||||
access_cost: float = 0.0,
|
||||
hole_to_hole_mm: float = 0.25,
|
||||
fanout: bool = False,
|
||||
fanout: bool | str = False,
|
||||
fanout_min_pins: int = 32,
|
||||
rollback: bool = True,
|
||||
) -> RoutingResult:
|
||||
"""Route ``dsn`` (grid track) and convert to a DSN-unit RoutingResult."""
|
||||
@@ -245,6 +251,7 @@ def build_routing_result(
|
||||
access_cost=access_cost,
|
||||
hole_to_hole_mm=hole_to_hole_mm,
|
||||
fanout=fanout,
|
||||
fanout_min_pins=fanout_min_pins,
|
||||
rollback=rollback,
|
||||
)
|
||||
return _to_routing_result(route, dsn, scale, layer_names)
|
||||
@@ -265,7 +272,8 @@ def route_dsn_board_exact(
|
||||
route_first: frozenset | None = None,
|
||||
access_cost: float = 0.0,
|
||||
hole_to_hole_mm: float = 0.25,
|
||||
fanout: bool = False,
|
||||
fanout: bool | str = False,
|
||||
fanout_min_pins: int = 32,
|
||||
rollback: bool = True,
|
||||
) -> tuple[ExactRouteResult, int, list[str]]:
|
||||
"""Route a parsed :class:`DsnBoard` with the exact-geometry (orthogonal,
|
||||
@@ -280,7 +288,7 @@ def route_dsn_board_exact(
|
||||
clearance_board = round(_rule_clearance_dsn(dsn) * scale)
|
||||
if fanout:
|
||||
_place_fanout(board, dsn, scale, layers, width_board, clearance_board,
|
||||
edge_clearance_board, hole_to_hole_mm)
|
||||
edge_clearance_board, hole_to_hole_mm, fanout, fanout_min_pins)
|
||||
exact = route_board_exact(
|
||||
board,
|
||||
resolution=resolution,
|
||||
@@ -321,7 +329,8 @@ def build_exact_routing_result(
|
||||
route_first: frozenset | None = None,
|
||||
access_cost: float = 0.0,
|
||||
hole_to_hole_mm: float = 0.25,
|
||||
fanout: bool = False,
|
||||
fanout: bool | str = False,
|
||||
fanout_min_pins: int = 32,
|
||||
rollback: bool = True,
|
||||
) -> RoutingResult:
|
||||
"""Route ``dsn`` (exact track) and convert to a DSN-unit RoutingResult."""
|
||||
@@ -340,6 +349,7 @@ def build_exact_routing_result(
|
||||
access_cost=access_cost,
|
||||
hole_to_hole_mm=hole_to_hole_mm,
|
||||
fanout=fanout,
|
||||
fanout_min_pins=fanout_min_pins,
|
||||
rollback=rollback,
|
||||
)
|
||||
return _to_routing_result(exact.result, dsn, scale, layer_names)
|
||||
|
||||
@@ -48,7 +48,7 @@ def _rules(dsn):
|
||||
s = dsn.resolution.value
|
||||
return FanoutRules(trace_width=150 * s, clearance=150 * s, via_half=225 * s, via_drill=200 * s,
|
||||
hole_to_hole=250 * s, edge_clearance=0, signal_layers=[0, 3],
|
||||
via_padstack="Via[0-3]_450:200_um")
|
||||
via_padstack="Via[0-3]_450:200_um", min_pins=8)
|
||||
|
||||
|
||||
def test_every_pin_gets_a_stub_outward_past_its_pad():
|
||||
@@ -71,7 +71,7 @@ def test_a_plane_pin_gets_a_via_and_needs_no_route():
|
||||
board = build_board(dsn)
|
||||
items = plan_fanout(board, _rules(dsn))
|
||||
assert len([i for i in items if isinstance(i, Via)]) == 1
|
||||
exact, _scale, _ = route_dsn_board_exact(dsn, resolution=2, fanout=True)
|
||||
exact, _scale, _ = route_dsn_board_exact(dsn, resolution=2, fanout=True, fanout_min_pins=8)
|
||||
gnd = next(i + 1 for i, n in enumerate(dsn.nets) if n.name == "GND")
|
||||
assert not any(f[0] == gnd for f in exact.result.failed)
|
||||
assert exact.drc_clean
|
||||
|
||||
Reference in New Issue
Block a user