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.
This commit is contained in:
parent
6a24ffc540
commit
faf2df42bc
65
tests/dsn/fixtures/narrow_channel.dsn
Normal file
65
tests/dsn/fixtures/narrow_channel.dsn
Normal file
@ -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
|
||||||
|
)
|
||||||
|
)
|
||||||
119
tests/route/test_room_router.py
Normal file
119
tests/route/test_room_router.py
Normal file
@ -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
|
||||||
Loading…
x
Reference in New Issue
Block a user