Files
rpmroute/tests/route/test_exact_router.py
T
rsp2k a6aa1991a1 Rename to rpmroute
The router began as a port of FreeRouting and has grown its own engine
(negotiated congestion, tree nets, exact checks, axis escapes), and
'freeroute' was one letter from the project it no longer follows. The
package, module and command are now rpmroute; the freeroute command stays
as an alias, and the RPMROUTE / RPMROUTE_ORACLE_JAVA variables fall back
to the old FREEROUTE names. Version 2026.10.07. Routing is unchanged: a
PCBench board routes to a byte-identical session either way.
2026-10-07 14:10:36 -06:00

155 lines
6.0 KiB
Python

"""Tests for the exact-geometry (orthogonal, DRC-verified) routing track.
The headline invariant: the exact router's output has **no clearance violation**
by exact ``IntBox`` intersection — where the grid MVP's centrelines, given real
width, may. Endpoints stay exactly on pads; vias sit at real layer transitions;
connectivity parity holds against the JAR oracle on the designed boards.
Fixtures: simple_2net (single-layer), crossing_2net (needs a via), ripup_needed.
"""
from __future__ import annotations
from pathlib import Path
import sys
import pytest
from rpmroute.board import ShapeSearchTree, TreeShape, build_board
from rpmroute.dsn import parse_dsn
from rpmroute.dsn.sexp import parse
from rpmroute.geometry import IntBox, Polyline, PolylineShape
from rpmroute.route import route, route_dsn_board, route_dsn_board_exact
from rpmroute.route.pipeline import _rule_clearance_dsn, _rule_width_dsn
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
FIXTURES = Path(__file__).resolve().parent.parent / "dsn" / "fixtures"
SIMPLE = FIXTURES / "simple_2net.dsn"
CROSSING = FIXTURES / "crossing_2net.dsn"
RIPUP = FIXTURES / "ripup_needed.dsn"
KICAD = FIXTURES / "kicad_routable.dsn"
def _tree_from_result(board, result, half_width, signal_layers):
"""Index a RouteResult's copper (with width) plus pads into a search tree."""
tree = ShapeSearchTree()
owner = 0
for pin in board.get_pins():
if pin.shape is None:
continue
net = pin.net_nos[0] if pin.net_nos else -1
layers = frozenset(layer for layer in pin.layers if layer in signal_layers)
if layers:
tree.insert(TreeShape(owner, net, layers, pin.shape.bounding_box()))
owner += 1
for net_no, segments in result.wires.items():
for layer, points in segments:
for box in PolylineShape(Polyline(points), half_width).tiles():
tree.insert(TreeShape(owner, net_no, frozenset({layer}), box, routed=True))
owner += 1
for net_no, locations in result.vias.items():
for loc in locations:
box = IntBox(
loc.x - half_width, loc.y - half_width, loc.x + half_width, loc.y + half_width
)
tree.insert(TreeShape(owner, net_no, frozenset(signal_layers), box, routed=True))
owner += 1
return tree
# --- headline: exact router output is DRC-clean -----------------------------
@pytest.mark.parametrize("fixture", [SIMPLE, CROSSING, RIPUP, KICAD])
def test_exact_router_output_is_drc_clean(fixture):
dsn = parse_dsn(fixture.read_text())
exact, scale, _ = route_dsn_board_exact(dsn)
assert exact.drc_clean
clearance = round(_rule_clearance_dsn(dsn) * scale)
assert exact.tree.has_violation(clearance) is None
def test_exact_router_connects_all_nets_on_designed_boards():
for fixture in (SIMPLE, CROSSING, RIPUP):
exact, _, _ = route_dsn_board_exact(parse_dsn(fixture.read_text()))
assert exact.routed_net_numbers == {1, 2}
def test_exact_crossing_uses_a_via():
exact, _, _ = route_dsn_board_exact(parse_dsn(CROSSING.read_text()))
assert exact.via_count() >= 1
# --- exact geometry properties ----------------------------------------------
def test_exact_endpoints_are_exactly_on_pads():
exact, _, _ = route_dsn_board_exact(parse_dsn(SIMPLE.read_text()))
board = build_board(parse_dsn(SIMPLE.read_text()))
pins_by_net: dict[int, set[tuple[int, int]]] = {}
for pin in board.get_pins():
for net_no in pin.net_nos:
pins_by_net.setdefault(net_no, set()).add((pin.location.x, pin.location.y))
for net_no, segments in exact.result.wires.items():
pts = [(p.x, p.y) for _, seg in segments for p in seg]
assert {pts[0], pts[-1]} == pins_by_net[net_no]
def test_exact_traces_are_orthogonal():
exact, _, _ = route_dsn_board_exact(parse_dsn(CROSSING.read_text()))
for segments in exact.result.wires.values():
for _, points in segments:
for a, b in Polyline(points).segments():
assert a.x == b.x or a.y == b.y # axis-aligned
def test_exact_engine_emits_valid_ses_with_vias():
ses = route(CROSSING.read_text(), engine="exact")
assert parse(ses).head == "session"
network = parse(ses).child("routes").child("network_out")
total_vias = sum(len(net.children("via")) for net in network.children("net"))
assert total_vias >= 1
# --- exact passes DRC where the grid MVP does not ---------------------------
def test_exact_clean_where_grid_mvp_violates_on_real_board():
dsn = parse_dsn(KICAD.read_text())
board = build_board(dsn)
scale = dsn.resolution.value
half_width = round(_rule_width_dsn(dsn) * scale) // 2
clearance = round(_rule_clearance_dsn(dsn) * scale)
signal_layers = [i for i, layer in enumerate(board.layer_structure.arr) if layer.is_signal]
# grid MVP (8-connected): route, then give the centrelines real width
grid_result, _, _ = route_dsn_board(dsn)
grid_tree = _tree_from_result(board, grid_result, half_width, signal_layers)
grid_has_violation = grid_tree.has_violation(clearance) is not None
# exact track: DRC-clean by construction
exact, _, _ = route_dsn_board_exact(dsn)
assert exact.drc_clean
# the grid MVP's widened centrelines are not DRC-clean on this dense board;
# the exact track is. (If a future grid change makes it clean too, that is
# still fine — the exact track's guarantee is the point.)
assert grid_has_violation
# --- oracle parity ----------------------------------------------------------
@pytest.mark.oracle
@pytest.mark.parametrize("fixture", [SIMPLE, CROSSING])
def test_exact_connectivity_parity_with_oracle(fixture):
from oracle import HAS_ORACLE, route_dsn, routed_net_set
if not HAS_ORACLE:
pytest.skip("FreeRouting oracle unavailable")
ours = routed_net_set(route(fixture.read_text(), engine="exact"))
theirs = routed_net_set(route_dsn(fixture, max_passes=3, timeout=300))
assert theirs, "oracle routed nothing on a routable board"
assert theirs <= ours