diff --git a/src/freeroute/route/exact_router.py b/src/freeroute/route/exact_router.py index dc6b8f9..ae1c954 100644 --- a/src/freeroute/route/exact_router.py +++ b/src/freeroute/route/exact_router.py @@ -31,7 +31,7 @@ from dataclasses import dataclass, field import math from freeroute.board.board import BasicBoard -from freeroute.board.items import Pin +from freeroute.board.items import Pin, Via from freeroute.board.search_tree import ShapeSearchTree, TreeShape from freeroute.geometry import ( IntBox, @@ -178,6 +178,11 @@ def route_board_exact( via_layers = frozenset(signal_layers) accepted: dict[int, _Accepted] = {} + fixed_vias = { + (item.net_nos[0], item.location) + for item in board.get_items() + if isinstance(item, Via) and item.fixed and item.net_nos + } # Verify connection by connection, so one conflicting segment of a # 25-pin supply net costs that one connection, not the whole net. @@ -207,7 +212,11 @@ def route_board_exact( if acc is None: acc = accepted[net_no] = _Accepted(wires=[], vias=[]) acc.wires.extend(wires) - acc.vias.extend(vias) + # 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 + acc.vias.extend( + v for v in dict.fromkeys(vias) if v not in acc.vias and (net_no, v) not in fixed_vias + ) for box, box_layers in boxes: oid = next(owner) tree.insert(TreeShape(oid, net_no, box_layers, box, routed=True)) diff --git a/tests/route/test_via_spacing.py b/tests/route/test_via_spacing.py index 2d42638..f591307 100644 --- a/tests/route/test_via_spacing.py +++ b/tests/route/test_via_spacing.py @@ -34,3 +34,23 @@ def test_a_nets_own_vias_keep_hole_to_hole_spacing(): need = (300 + 250) * scale # drill + 0.25 mm, centre to centre for a, b in itertools.combinations(vias, 2): assert math.hypot(a.x - b.x, a.y - b.y) >= need, f"vias at {a} and {b} too close" + + +def test_connections_sharing_a_plane_via_write_it_once(): + # eight GND pads 0.4 mm apart: the hole spacing makes several of them + # end on the same plane via, which must come out once, not once each + pads = " ".join(f"(place G{i} {9000 + 400 * i} -10000 front 0)" for i in range(8)) + pins = " ".join(f"G{i}-1" for i in range(8)) + dsn = parse_dsn( + _DSN.replace( + "(place G1 9000 -10000 front 0) (place G2 9500 -10000 front 0)\n" + " (place G3 10000 -10000 front 0) (place G4 10500 -10000 front 0)", + pads, + ) + .replace("(pins G1-1 G2-1 G3-1 G4-1)", f"(pins {pins})") + .replace("(rect F.Cu -150 -300 150 300)", "(rect F.Cu -125 -300 125 300)") + ) + exact, _scale, _ = route_dsn_board_exact(dsn, resolution=2) + vias = exact.result.vias[1] + 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"