From faf2df42bc2a95ad50895034a27141a8ebb361ed Mon Sep 17 00:00:00 2001 From: Ryan Malloy Date: Mon, 13 Jul 2026 09:38:52 -0600 Subject: [PATCH] Add continuity-win fixture and room-router tests Adds narrow_channel.dsn: a keepout wall with a narrow gap positioned off the routing grid, so no grid cell centre falls inside the gap's free centreline. The exact/grid track drops the net (cell quantization can't fit a centreline in the channel); the continuous room track routes it. This is the headline result that continuous free-space beats the grid. Tests: the room router's output is DRC-clean (exact has_violation is None) on simple/crossing/kicad/narrow_channel; it connects simple/crossing (with a via) and all four multi-pin KiCad nets; traces are orthogonal and end on pads; and the continuity contrast asserts exact routes 0 while rooms routes 1, both clean. Oracle-gated connectivity parity on simple/crossing. --- tests/dsn/fixtures/narrow_channel.dsn | 65 ++++++++++++++ tests/route/test_room_router.py | 119 ++++++++++++++++++++++++++ 2 files changed, 184 insertions(+) create mode 100644 tests/dsn/fixtures/narrow_channel.dsn create mode 100644 tests/route/test_room_router.py diff --git a/tests/dsn/fixtures/narrow_channel.dsn b/tests/dsn/fixtures/narrow_channel.dsn new file mode 100644 index 0000000..48d557e --- /dev/null +++ b/tests/dsn/fixtures/narrow_channel.dsn @@ -0,0 +1,65 @@ +(pcb "narrow_channel.dsn" + (parser + (string_quote ") + (space_in_quoted_tokens on) + (host_cad "freeroute-test") + (host_version "1.0") + ) + (resolution um 10) + (unit um) + (structure + (layer F.Cu + (type signal) + (property + (index 0) + ) + ) + (layer B.Cu + (type power) + (property + (index 1) + ) + ) + (boundary + (path pcb 0 0 0 200000 0 200000 -30000 0 -30000 0 0) + ) + (keepout "wall_left" + (rect F.Cu 0 -14000 94000 -16000) + ) + (keepout "wall_right" + (rect F.Cu 102000 -14000 200000 -16000) + ) + (rule + (width 2000) + (clearance 2000) + ) + ) + (library + (padstack Rect_Pad + (shape (rect F.Cu -1000 -1000 1000 1000)) + (attach off) + ) + (image PAD + (pin Rect_Pad 1 0 0) + ) + ) + (placement + (component PAD + (place C1 98000 -5000 front 0) + (place C2 98000 -25000 front 0) + ) + ) + (network + (net NET_C + (pins C1-1 C2-1) + ) + (class default + (rule + (width 2000) + (clearance 2000) + ) + ) + ) + (wiring + ) +) diff --git a/tests/route/test_room_router.py b/tests/route/test_room_router.py new file mode 100644 index 0000000..b5eff6b --- /dev/null +++ b/tests/route/test_room_router.py @@ -0,0 +1,119 @@ +"""Tests for the continuous expansion-room routing track. + +The room router decomposes free space into exact rectangles (rooms) joined by +doors, instead of a fixed grid. The headline result is the *continuity win*: a +board with a narrow, off-grid channel that the grid/exact track cannot route +(no cell centre lands in the channel) but the room track can. +""" + +from __future__ import annotations + +from pathlib import Path +import sys + +import pytest + +from freeroute.board import build_board +from freeroute.dsn import parse_dsn +from freeroute.dsn.sexp import parse +from freeroute.geometry import Polyline +from freeroute.route import route, route_dsn_board_exact, route_dsn_board_rooms + +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" +KICAD = FIXTURES / "kicad_routable.dsn" +NARROW = FIXTURES / "narrow_channel.dsn" + + +def _clearance(dsn, scale): + from freeroute.route.pipeline import _rule_clearance_dsn + + return round(_rule_clearance_dsn(dsn) * scale) + + +# --- DRC-clean invariant (same non-negotiable as the exact track) ----------- + + +@pytest.mark.parametrize("fixture", [SIMPLE, CROSSING, KICAD, NARROW]) +def test_room_router_output_is_drc_clean(fixture): + dsn = parse_dsn(fixture.read_text()) + rooms, scale, _ = route_dsn_board_rooms(dsn) + assert rooms.drc_clean + assert rooms.tree.has_violation(_clearance(dsn, scale)) is None + + +# --- connectivity + geometry ------------------------------------------------ + + +def test_room_router_connects_simple_and_crossing(): + for fixture in (SIMPLE, CROSSING): + rooms, _, _ = route_dsn_board_rooms(parse_dsn(fixture.read_text())) + assert rooms.routed_net_numbers == {1, 2} + + +def test_room_router_uses_a_via_on_the_crossing_board(): + rooms, _, _ = route_dsn_board_rooms(parse_dsn(CROSSING.read_text())) + assert rooms.via_count() >= 1 + + +def test_room_router_routes_the_real_boards_multipin_nets(): + dsn = parse_dsn(KICAD.read_text()) + rooms, _, _ = route_dsn_board_rooms(dsn) + multi = sum(1 for n in dsn.nets if len(n.pins) >= 2) + assert len(rooms.routed_net_numbers) == multi == 4 + + +def test_room_traces_are_orthogonal_and_end_on_pads(): + rooms, _, _ = route_dsn_board_rooms(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 rooms.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] + for _, points in segments: + for a, b in Polyline(points).segments(): + assert a.x == b.x or a.y == b.y + + +def test_room_engine_emits_valid_ses(): + ses = route(SIMPLE.read_text(), engine="room") + assert parse(ses).head == "session" + + +# --- the headline: continuity beats the grid -------------------------------- + + +def test_room_router_routes_a_channel_the_grid_cannot(): + dsn = parse_dsn(NARROW.read_text()) + # the exact/grid track cannot fit a centreline in the narrow, off-grid gap + exact, scale, _ = route_dsn_board_exact(dsn) + assert exact.routed_net_numbers == set() # grid quantization drops the net + assert exact.drc_clean + + # the continuous room track routes it — and stays DRC-clean + rooms, _, _ = route_dsn_board_rooms(dsn) + assert rooms.routed_net_numbers == {1} + assert rooms.drc_clean + assert rooms.tree.has_violation(_clearance(dsn, scale)) is None + + +# --- oracle parity ---------------------------------------------------------- + + +@pytest.mark.oracle +@pytest.mark.parametrize("fixture", [SIMPLE, CROSSING]) +def test_room_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="room")) + theirs = routed_net_set(route_dsn(fixture, max_passes=3, timeout=300)) + assert theirs, "oracle routed nothing on a routable board" + assert theirs <= ours