Files
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

118 lines
4.4 KiB
Python

"""Tests for shove-and-retry on the exact-geometry track.
Shove moves an existing trace aside to make room for a net that would otherwise
be dropped, without ever producing a clearance violation.
The dense fixture ``shove_needed.dsn`` has one signal layer (no vias): with
greedy net ordering NET_A routes as a straight wall that boxes NET_C out, so
without shove NET_C is dropped; with shove NET_A is moved aside and both
connect, DRC-clean. (Rip-up would also reroute NET_A here; the shove test uses
greedy ordering to isolate the shove mechanism.)
"""
from __future__ import annotations
from pathlib import Path
import sys
import pytest
from rpmroute.dsn import parse_dsn
from rpmroute.dsn.sexp import parse
from rpmroute.route import route, route_dsn_board_exact
from rpmroute.route.pipeline import _rule_clearance_dsn
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
FIXTURES = Path(__file__).resolve().parent.parent / "dsn" / "fixtures"
SHOVE = FIXTURES / "shove_needed.dsn"
REGRESSION = ["simple_2net", "crossing_2net", "ripup_needed", "kicad_routable"]
def _exact(fixture, shove):
return route_dsn_board_exact(parse_dsn(fixture.read_text()), shove=shove)
# --- the headline shove contrast (greedy ordering isolates the mechanism) ----
def test_without_shove_a_net_is_dropped():
# greedy exact (no shove) cannot fit NET_C past NET_A's wall
off, _, _ = route_dsn_board_exact(parse_dsn(SHOVE.read_text()), rip_up=False, shove=False)
assert len(off.routed_net_numbers) < 2
assert off.drc_clean # still clean, just incomplete
def test_shove_connects_all_and_stays_drc_clean():
on, scale, _ = route_dsn_board_exact(parse_dsn(SHOVE.read_text()), rip_up=False, shove=True)
assert on.routed_net_numbers == {1, 2} # both nets connected
assert on.shoves >= 1 # at least one trace was moved aside
assert on.drc_clean
clearance = round(_rule_clearance_dsn(parse_dsn(SHOVE.read_text())) * scale)
assert on.tree.has_violation(clearance) is None # exact DRC check
def test_shove_result_is_deterministic():
a, _, _ = route_dsn_board_exact(parse_dsn(SHOVE.read_text()), rip_up=False, shove=True)
b, _, _ = route_dsn_board_exact(parse_dsn(SHOVE.read_text()), rip_up=False, shove=True)
def key(r):
return {
n: [(layer, [(p.x, p.y) for p in pts]) for layer, pts in segs]
for n, segs in r.result.wires.items()
}
assert key(a) == key(b)
def test_shoved_trace_endpoints_still_on_pads():
from rpmroute.board import build_board
on, _, _ = route_dsn_board_exact(parse_dsn(SHOVE.read_text()), rip_up=False, shove=True)
board = build_board(parse_dsn(SHOVE.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 on.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_shove_emits_valid_ses():
ses = route(SHOVE.read_text(), engine="exact", shove=True)
top = parse(ses)
assert top.head == "session"
nets = top.child("routes").child("network_out").children("net")
assert {n.values()[0].text for n in nets} == {"NET_A", "NET_C"}
# --- regression: shove is a no-op where it isn't needed ----------------------
@pytest.mark.parametrize("name", REGRESSION)
def test_shove_does_not_change_or_break_normal_boards(name):
fixture = FIXTURES / f"{name}.dsn"
off, _, _ = _exact(fixture, shove=False)
on, _, _ = _exact(fixture, shove=True)
# same connectivity, both DRC-clean, and no shove was needed
assert on.routed_net_numbers == off.routed_net_numbers
assert off.drc_clean and on.drc_clean
assert on.shoves == 0
# --- oracle parity ----------------------------------------------------------
@pytest.mark.oracle
def test_shove_connectivity_parity_with_oracle():
from oracle import HAS_ORACLE, route_dsn, routed_net_set
if not HAS_ORACLE:
pytest.skip("FreeRouting oracle unavailable")
ours = routed_net_set(route(SHOVE.read_text(), engine="exact", shove=True))
theirs = routed_net_set(route_dsn(SHOVE, max_passes=3, timeout=300))
if not theirs:
pytest.skip("reference JAR routed nothing on the synthetic single-layer board")
assert theirs <= ours