"""--fanout: fine-pitch pins get their way out committed before routing.""" from __future__ import annotations from freeroute.board import build_board from freeroute.board.items import Trace, Via from freeroute.dsn import parse_dsn from freeroute.route.fanout import FanoutRules, plan_fanout from freeroute.route.pipeline import route_dsn_board_exact # a 12-pad package, 3 pads a side at 0.5 mm pitch, over a GND plane on In1; # pad 1 (left side, top) is GND, the rest are signals to far connector pads _SIDES = { "L": [(-2000, y, 0) for y in (500, 0, -500)], "R": [(2000, y, 0) for y in (500, 0, -500)], "T": [(x, 2000, 90) for x in (-500, 0, 500)], "B": [(x, -2000, 90) for x in (-500, 0, 500)], } _PADS = [p for side in "LRTB" for p in _SIDES[side]] def _dsn(): pins = "\n ".join( f"(pin {'padv' if r else 'padh'} {i + 1} {x} {y})" for i, (x, y, r) in enumerate(_PADS) ) far = " ".join(f"(place C{i} {3000 + 2000 * (i % 5)} {-12000 - 2000 * (i // 5)} front 0)" for i in range(11)) nets = "(net GND (pins U1-1))\n " + "\n ".join(f"(net S{i} (pins U1-{i + 2} C{i}-1))" for i in range(11)) return f"""(pcb pkg (parser) (resolution um 10) (unit um) (structure (layer F.Cu (type signal)) (layer In1.Cu (type power)) (layer In2.Cu (type power)) (layer B.Cu (type signal)) (boundary (path pcb 0 -15000 15000 25000 15000 25000 -25000 -15000 -25000 -15000 15000)) (plane GND (polygon In1.Cu 0 -14500 14500 24500 14500 24500 -24500 -14500 -24500 -14500 14500)) (via "Via[0-3]_450:200_um") (rule (width 150) (clearance 150))) (placement (component Q (place U1 0 0 front 0)) (component P {far})) (library (image Q {pins}) (image P (pin padc 1 0 0)) (padstack padh (shape (rect F.Cu -400 -125 400 125)) (attach off)) (padstack padv (shape (rect F.Cu -125 -400 125 400)) (attach off)) (padstack padc (shape (rect F.Cu -500 -500 500 500)) (attach off)) (padstack "Via[0-3]_450:200_um" (shape (circle F.Cu 450)) (shape (circle B.Cu 450)) (attach off))) (network {nets}))""" 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") def test_every_pin_gets_a_stub_outward_past_its_pad(): dsn = parse_dsn(_dsn()) board = build_board(dsn) items = plan_fanout(board, _rules(dsn)) stubs = [i for i in items if isinstance(i, Trace)] assert len(stubs) == 12 for stub in stubs: a, b = stub.corners pin = next(p for p in board.get_pins() if p.location == a) pad = pin.shape.bounding_box() # outward: the stub ends further from the package centre, outside its pad assert abs(b.x) + abs(b.y) > abs(a.x) + abs(a.y) assert not (pad.ll.x <= b.x <= pad.ur.x and pad.ll.y <= b.y <= pad.ur.y) def test_a_plane_pin_gets_a_via_and_needs_no_route(): dsn = parse_dsn(_dsn()) 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) 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 assert len(exact.result.vias.get(gnd, [])) == 1 # the fan-out's, written out