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.
456 lines
20 KiB
Python
456 lines
20 KiB
Python
"""Tests for gate-optimal placement and shove-in-rooms on the room router.
|
|
|
|
Two opt-in (``shove=True``) upgrades to the continuous expansion-room track:
|
|
|
|
* **Occupancy-aware gate placement** seats a trace at the best point in a door
|
|
rather than the raw midpoint. With ``margin = clearance + half_width`` the room
|
|
decomposition already keeps every door clearance-clear of committed copper, so
|
|
the projection provably reduces to the old clamp where nothing else crosses the
|
|
door — the four foundation fixtures stay byte-for-byte identical.
|
|
* **Shove-in-rooms** nudges a committed trace aside with the exact shove
|
|
primitive when a net would otherwise be dropped, so *both* fit through the same
|
|
channel. ``rooms_shove_channel`` drops a net under the default greedy net order
|
|
and routes both with shove, DRC-clean -- a recovery from greedy ordering (the
|
|
same board is plainly routable under a reorder or without its cosmetic seals,
|
|
see ``test_room_shove_channel_drop_is_ordering_artifact_not_density``), not a
|
|
genuine density limit. The true sub-cell win is occupancy packing, which the
|
|
``rooms_wide_door`` fixture exercises with ``shoves == 0``.
|
|
|
|
The ``true_density`` fixture settles the density question the other way and honestly:
|
|
plain routing drops a net under EVERY net ordering (an order-independent obstruction,
|
|
not a self-inflicted greedy choice), and shove recovers *nothing* there (``shoves == 0``).
|
|
shove relocates a blocker into existing free space; it does not compress copper, so it
|
|
cannot manufacture density where none exists. The recover/fail boundary is exactly the
|
|
free-space boundary -- ``test_true_density_contrast_is_only_the_wall_height`` shows the
|
|
two boards differ only in how far the wall spans.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
import sys
|
|
|
|
import pytest
|
|
|
|
from rpmroute.board import build_board
|
|
from rpmroute.dsn import parse_dsn
|
|
from rpmroute.dsn.sexp import parse
|
|
from rpmroute.geometry import IntBox, IntPoint, Polyline, PolylineShape
|
|
from rpmroute.route import route, route_dsn_board_exact, route_dsn_board_rooms
|
|
from rpmroute.route.pipeline import _rule_clearance_dsn
|
|
|
|
|
|
def _emitted_drc_violations(result, clearance):
|
|
"""Independent DRC over the *emitted* route: reconstruct every wire's copper
|
|
tiles and return the different-net, same-layer pairs that come closer than
|
|
``clearance``. This does NOT consult the search tree, so it catches copper
|
|
that is present in the output but (via a bug) absent from the tree -- exactly
|
|
the failure mode the wire/owner desync produced."""
|
|
items = [] # (net_no, layer, tile)
|
|
for net_no, segments in result.wires.items():
|
|
for layer, corners in segments:
|
|
for box in PolylineShape(Polyline(corners), result.half_width).tiles():
|
|
items.append((net_no, layer, box))
|
|
bad = []
|
|
for i in range(len(items)):
|
|
n1, l1, b1 = items[i]
|
|
expanded = b1.offset(clearance)
|
|
for j in range(i + 1, len(items)):
|
|
n2, l2, b2 = items[j]
|
|
if n1 == n2 or l1 != l2:
|
|
continue
|
|
if b2.overlaps(expanded):
|
|
bad.append((n1, n2))
|
|
return bad
|
|
|
|
|
|
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"
|
|
SHOVE_CHANNEL = FIXTURES / "rooms_shove_channel.dsn"
|
|
WIDE_DOOR = FIXTURES / "rooms_wide_door.dsn"
|
|
MULTIPIN_DESYNC = FIXTURES / "rooms_multipin_desync.dsn"
|
|
MULTIPIN_DESYNC_CRASH = FIXTURES / "rooms_multipin_desync_crash.dsn"
|
|
TRUE_DENSITY = FIXTURES / "true_density.dsn"
|
|
|
|
FOUNDATION = [SIMPLE, CROSSING, KICAD, NARROW]
|
|
|
|
|
|
def _clearance(dsn, scale):
|
|
return round(_rule_clearance_dsn(dsn) * scale)
|
|
|
|
|
|
def _wire_key(result):
|
|
return {
|
|
n: [(layer, [(p.x, p.y) for p in pts]) for layer, pts in segs]
|
|
for n, segs in result.wires.items()
|
|
}
|
|
|
|
|
|
def _pins_by_net(fixture):
|
|
board = build_board(parse_dsn(fixture.read_text()))
|
|
out: dict[int, set[tuple[int, int]]] = {}
|
|
for pin in board.get_pins():
|
|
for net_no in pin.net_nos:
|
|
out.setdefault(net_no, set()).add((pin.location.x, pin.location.y))
|
|
return out
|
|
|
|
|
|
# --- shove recovers a greedy-ordering drop ----------------------------------
|
|
|
|
|
|
def test_room_shove_channel_recovers_greedy_ordering_drop():
|
|
dsn = parse_dsn(SHOVE_CHANNEL.read_text())
|
|
off, _, _ = route_dsn_board_rooms(dsn, shove=False)
|
|
on, scale, _ = route_dsn_board_rooms(dsn, shove=True)
|
|
|
|
# under the default greedy net order NET_A (net 1) walls the channel -> NET_C drops
|
|
assert off.routed_net_numbers == {1}
|
|
assert off.drc_clean
|
|
# with shove NET_A is nudged aside and both nets fit through the same channel
|
|
assert on.routed_net_numbers == {1, 2}
|
|
assert on.shoves >= 1
|
|
assert on.drc_clean
|
|
assert on.tree.has_violation(_clearance(dsn, scale)) is None
|
|
|
|
|
|
def test_room_shove_channel_drop_is_ordering_artifact_not_density():
|
|
"""Honest bound on the shove_channel claim: the plain drop is an artifact of
|
|
the greedy net order, not a genuine density limit. The identical plain engine
|
|
routes BOTH nets with zero shoves if NET_C is listed first, or if the two
|
|
cosmetic edge seals are removed -- so shove here recovers a self-inflicted
|
|
ordering drop rather than overcoming an unroutable density. (The genuine
|
|
sub-cell win is occupancy packing, exercised by the wide_door fixture with
|
|
shoves == 0.)"""
|
|
orig = SHOVE_CHANNEL.read_text()
|
|
|
|
# (1) reorder so NET_C becomes net 1 -> plain routing solves it, no shove
|
|
swapped = orig.replace(
|
|
" (net NET_A\n (pins A1-1 A2-1)\n )\n"
|
|
" (net NET_C\n (pins C1-1 C2-1)\n )",
|
|
" (net NET_C\n (pins C1-1 C2-1)\n )\n"
|
|
" (net NET_A\n (pins A1-1 A2-1)\n )",
|
|
)
|
|
assert swapped != orig
|
|
reordered, _, _ = route_dsn_board_rooms(parse_dsn(swapped), shove=False)
|
|
assert reordered.routed_net_numbers == {1, 2}
|
|
assert reordered.shoves == 0
|
|
assert reordered.drc_clean
|
|
|
|
# (2) drop the seal keepouts -> plain routing solves it, no shove
|
|
noseal = orig.replace(
|
|
' (keepout "seal_left"\n (rect F.Cu 0 -30000 2500 0)\n )\n'
|
|
' (keepout "seal_right"\n (rect F.Cu 197500 -30000 200000 0)\n )\n',
|
|
"",
|
|
)
|
|
assert noseal != orig
|
|
unsealed, _, _ = route_dsn_board_rooms(parse_dsn(noseal), shove=False)
|
|
assert unsealed.routed_net_numbers == {1, 2}
|
|
assert unsealed.shoves == 0
|
|
assert unsealed.drc_clean
|
|
|
|
|
|
def test_room_shove_channel_moves_blocker_but_keeps_its_pads():
|
|
on, _, _ = route_dsn_board_rooms(parse_dsn(SHOVE_CHANNEL.read_text()), shove=True)
|
|
pins = _pins_by_net(SHOVE_CHANNEL)
|
|
# NET_A (net 1) is the shoved blocker: its endpoints stay on its pads...
|
|
seg = on.result.wires[1]
|
|
pts = [(p.x, p.y) for _, run in seg for p in run]
|
|
assert {pts[0], pts[-1]} == pins[1]
|
|
# ...but a middle corner moved off the original straight y (the shove jog)
|
|
ys = {y for _, y in pts}
|
|
assert len(ys) > 1
|
|
|
|
|
|
def test_room_shove_channel_endpoints_on_pads():
|
|
on, _, _ = route_dsn_board_rooms(parse_dsn(SHOVE_CHANNEL.read_text()), shove=True)
|
|
pins = _pins_by_net(SHOVE_CHANNEL)
|
|
for net_no, segments in on.result.wires.items():
|
|
pts = [(p.x, p.y) for _, run in segments for p in run]
|
|
assert {pts[0], pts[-1]} == pins[net_no]
|
|
|
|
|
|
def test_room_shove_channel_is_deterministic():
|
|
a, _, _ = route_dsn_board_rooms(parse_dsn(SHOVE_CHANNEL.read_text()), shove=True)
|
|
b, _, _ = route_dsn_board_rooms(parse_dsn(SHOVE_CHANNEL.read_text()), shove=True)
|
|
assert _wire_key(a.result) == _wire_key(b.result)
|
|
assert a.via_count() == b.via_count()
|
|
|
|
|
|
def test_room_shove_baseline_contrast_with_exact():
|
|
# the exact track (no rip-up) also drops NET_C on this sealed board; the room
|
|
# engine's shove recovers it -- the concrete continuous-shove win.
|
|
dsn = parse_dsn(SHOVE_CHANNEL.read_text())
|
|
exact, _, _ = route_dsn_board_exact(dsn, rip_up=False, shove=False)
|
|
assert len(exact.routed_net_numbers) < 2
|
|
rooms, _, _ = route_dsn_board_rooms(dsn, shove=True)
|
|
assert rooms.routed_net_numbers == {1, 2}
|
|
|
|
|
|
def test_room_shove_channel_valid_ses():
|
|
ses = route(SHOVE_CHANNEL.read_text(), engine="room", 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"}
|
|
|
|
|
|
# --- gate placement: two lanes through one wide door ------------------------
|
|
|
|
|
|
def _wall_crossing_x(result, net_no, wall_y):
|
|
for _, run in result.wires[net_no]:
|
|
for a, b in zip(run, run[1:], strict=False):
|
|
if a.x == b.x and min(a.y, b.y) <= wall_y <= max(a.y, b.y):
|
|
return a.x
|
|
return None
|
|
|
|
|
|
def test_room_wide_door_seats_two_nets_at_distinct_points():
|
|
dsn = parse_dsn(WIDE_DOOR.read_text())
|
|
on, scale, _ = route_dsn_board_rooms(dsn, shove=True)
|
|
assert on.routed_net_numbers == {1, 2}
|
|
assert on.shoves == 0 # gate placement alone, no shove
|
|
assert on.drc_clean
|
|
assert on.tree.has_violation(_clearance(dsn, scale)) is None
|
|
|
|
wall_y = -30000 * scale
|
|
x1 = _wall_crossing_x(on.result, 1, wall_y)
|
|
x2 = _wall_crossing_x(on.result, 2, wall_y)
|
|
assert x1 is not None and x2 is not None
|
|
width = round(2000 * scale)
|
|
clearance = _clearance(dsn, scale)
|
|
assert abs(x1 - x2) >= width + clearance # two DISTINCT lanes
|
|
|
|
|
|
def test_room_wide_door_is_deterministic():
|
|
a, _, _ = route_dsn_board_rooms(parse_dsn(WIDE_DOOR.read_text()), shove=True)
|
|
b, _, _ = route_dsn_board_rooms(parse_dsn(WIDE_DOOR.read_text()), shove=True)
|
|
assert _wire_key(a.result) == _wire_key(b.result)
|
|
|
|
|
|
# --- DRC-clean invariant extends to the new fixtures ------------------------
|
|
|
|
|
|
@pytest.mark.parametrize("fixture", [SHOVE_CHANNEL, WIDE_DOOR])
|
|
def test_new_fixtures_drc_clean_under_shove(fixture):
|
|
dsn = parse_dsn(fixture.read_text())
|
|
on, scale, _ = route_dsn_board_rooms(dsn, shove=True)
|
|
assert on.drc_clean
|
|
assert on.tree.has_violation(_clearance(dsn, scale)) is None
|
|
|
|
|
|
# --- shove/gate placement is a no-op where it isn't needed ------------------
|
|
|
|
|
|
@pytest.mark.parametrize("fixture", FOUNDATION)
|
|
def test_shove_is_byte_identical_on_foundation_fixtures(fixture):
|
|
dsn = parse_dsn(fixture.read_text())
|
|
off, scale, _ = route_dsn_board_rooms(dsn, shove=False)
|
|
on, _, _ = route_dsn_board_rooms(dsn, shove=True)
|
|
# identical connectivity, geometry, DRC-clean, and no shove was needed
|
|
assert on.routed_net_numbers == off.routed_net_numbers
|
|
assert on.shoves == 0
|
|
assert off.drc_clean and on.drc_clean
|
|
assert on.tree.has_violation(_clearance(dsn, scale)) is None
|
|
# doors carry no other-net routed copper here, so projection == the old clamp
|
|
assert _wire_key(on.result) == _wire_key(off.result)
|
|
|
|
|
|
# --- unit coverage for the occupancy-aware projection -----------------------
|
|
|
|
|
|
def test_project_gate_reduces_to_clamp_without_occupancy():
|
|
from rpmroute.board.search_tree import ShapeSearchTree
|
|
from rpmroute.route.room_router import _Door, _edge_gate, _project_gate
|
|
|
|
door = _Door(other=1, kind="edge", lo=IntPoint(0, 100), hi=IntPoint(200, 100))
|
|
tree = ShapeSearchTree()
|
|
from_pt = IntPoint(140, 500)
|
|
plain = _edge_gate(door, from_pt, half_width=1)
|
|
projected = _project_gate(door, from_pt, 1, 2, tree, net_no=1, layer=0)
|
|
assert (projected.x, projected.y) == (plain.x, plain.y) == (140, 100)
|
|
|
|
|
|
def test_project_gate_avoids_other_net_copper_on_the_door():
|
|
from rpmroute.board.search_tree import ShapeSearchTree, TreeShape
|
|
from rpmroute.route.room_router import _Door, _project_gate
|
|
|
|
door = _Door(other=1, kind="edge", lo=IntPoint(0, 100), hi=IntPoint(200, 100))
|
|
tree = ShapeSearchTree()
|
|
# a different-net routed trace crosses the middle of the door
|
|
tree.insert(TreeShape(0, 2, frozenset({0}), IntBox(90, 50, 110, 150), routed=True))
|
|
# ideal projection is x=100, right inside the blocked band -> pushed to a free span
|
|
gate = _project_gate(door, IntPoint(100, 500), 1, 2, tree, net_no=1, layer=0)
|
|
reach = 2 + 1 # clearance + half_width
|
|
forbidden_lo, forbidden_hi = 90 - reach, 110 + reach
|
|
assert not (forbidden_lo < gate.x < forbidden_hi) # outside the open forbidden band
|
|
assert 1 <= gate.x <= 199 # inside the usable span
|
|
|
|
|
|
def test_free_intervals_splits_around_forbidden_band():
|
|
from rpmroute.route.room_router import _best_free_point, _free_intervals
|
|
|
|
free = _free_intervals(0, 200, [(87, 113)])
|
|
assert free == [(0, 87), (113, 200)]
|
|
# tie on size -> lowest-coordinate interval, clamped toward the ideal
|
|
assert _best_free_point(0, 200, [(87, 113)], ideal=100) == 87
|
|
# no forbidden band -> the whole span, clamped to the ideal (old clamp)
|
|
assert _best_free_point(0, 200, [], ideal=100) == 100
|
|
|
|
|
|
# --- regression: multi-pin wire/owner desync in shove recovery ---------------
|
|
|
|
|
|
def test_room_shove_multipin_recovery_keeps_output_drc_clean():
|
|
"""Regression for the wire/owner desync in ``_shove_recover_room``.
|
|
|
|
A 3-pin net commits one connection via ``_commit`` and has a second straight
|
|
connection *recovered* by the shove path. The recovery used to clobber the
|
|
net's ``_Accepted`` with a fresh one whose ``wire_owners`` held only the
|
|
just-recovered wire, leaving it SHORTER than the parallel ``wires`` list. A
|
|
later net's shove then removed the WRONG wire's copper from the search tree;
|
|
that copper stayed in the emitted route but vanished from the tree, so
|
|
``has_violation`` falsely certified clean while a following net was placed on
|
|
top of it -- a hard different-net short in the SES.
|
|
|
|
Assert the invariant against the *emitted* output directly, not just the tree
|
|
(the tree was the thing being fooled)."""
|
|
dsn = parse_dsn(MULTIPIN_DESYNC.read_text())
|
|
on, scale, _ = route_dsn_board_rooms(dsn, shove=True)
|
|
clearance = _clearance(dsn, scale)
|
|
|
|
# the tree self-check and the independent emitted-copper check must agree
|
|
assert on.drc_clean
|
|
assert on.tree.has_violation(clearance) is None
|
|
assert _emitted_drc_violations(on.result, clearance) == []
|
|
# the shove path actually engaged and did not silently drop the multi-pin net
|
|
assert on.shoves >= 1
|
|
assert 3 in on.routed_net_numbers
|
|
|
|
|
|
def test_room_shove_multipin_recovery_is_deterministic():
|
|
a, _, _ = route_dsn_board_rooms(parse_dsn(MULTIPIN_DESYNC.read_text()), shove=True)
|
|
b, _, _ = route_dsn_board_rooms(parse_dsn(MULTIPIN_DESYNC.read_text()), shove=True)
|
|
assert _wire_key(a.result) == _wire_key(b.result)
|
|
assert a.routed_net_numbers == b.routed_net_numbers
|
|
|
|
|
|
def test_room_shove_multipin_recovery_does_not_crash():
|
|
"""Sibling of the desync: when the conflicting wire index exceeds the
|
|
(formerly truncated) ``wire_owners`` length, the recovery raised
|
|
``IndexError`` and aborted the whole public route. Keeping ``wire_owners``
|
|
parallel to ``wires`` fixes both faces; this board must route to completion,
|
|
DRC-clean, through the public ``route(engine="room", shove=True)`` API."""
|
|
ses = route(MULTIPIN_DESYNC_CRASH.read_text(), engine="room", shove=True)
|
|
top = parse(ses)
|
|
assert top.head == "session"
|
|
# and the library path stays DRC-clean on the emitted output
|
|
dsn = parse_dsn(MULTIPIN_DESYNC_CRASH.read_text())
|
|
on, scale, _ = route_dsn_board_rooms(dsn, shove=True)
|
|
clearance = _clearance(dsn, scale)
|
|
assert on.drc_clean
|
|
assert _emitted_drc_violations(on.result, clearance) == []
|
|
|
|
|
|
# --- honest bound: shove recovers ordering drops, it does NOT create density --
|
|
|
|
|
|
def _net_blocks(text):
|
|
import re
|
|
|
|
return re.findall(r" \(net [^\n]*\n \(pins [^\n]*\n \)\n", text)
|
|
|
|
|
|
def _plain_under_all_orderings(fixture):
|
|
"""Route ``fixture`` plainly (shove=False) under every permutation of its net
|
|
declaration order, returning the routed-net set for each ordering. Net numbers
|
|
follow declaration order, so permuting the ``(net ...)`` blocks permutes the
|
|
greedy routing order -- the exact knob that turned the shove_channel drop into
|
|
an ordering artifact."""
|
|
import itertools
|
|
|
|
text = fixture.read_text()
|
|
blocks = _net_blocks(text)
|
|
joined = "".join(blocks)
|
|
assert joined in text and len(blocks) >= 2
|
|
out = []
|
|
for perm in itertools.permutations(range(len(blocks))):
|
|
variant = text.replace(joined, "".join(blocks[i] for i in perm), 1)
|
|
result, _, _ = route_dsn_board_rooms(parse_dsn(variant), shove=False)
|
|
out.append(len(result.routed_net_numbers))
|
|
return out
|
|
|
|
|
|
def test_shove_channel_plain_fits_under_some_ordering():
|
|
"""Baseline for the contrast: on shove_channel the plain drop is an ORDERING
|
|
artifact -- there exists a net order under which plain routing fits both nets
|
|
with no shove. This is exactly what makes it *not* a density limit."""
|
|
counts = _plain_under_all_orderings(SHOVE_CHANNEL)
|
|
assert max(counts) == 2 # some ordering routes both
|
|
assert min(counts) == 1 # the default order drops one
|
|
|
|
|
|
def test_true_density_plain_drops_under_every_ordering():
|
|
"""The order-independent bar: on true_density plain routing drops >= 1 net
|
|
under EVERY net ordering, unlike shove_channel. NET_C is a full-height wall on
|
|
the only signal layer, so whichever net is routed first walls off the other --
|
|
no permutation fits both. The drop is a genuine on-layer obstruction, not a
|
|
self-inflicted ordering choice."""
|
|
counts = _plain_under_all_orderings(TRUE_DENSITY)
|
|
assert len(counts) == 2 # two nets -> two orderings
|
|
assert all(k < 2 for k in counts) # every ordering drops at least one net
|
|
assert all(k == 1 for k in counts) # exactly one survives each way
|
|
|
|
|
|
def test_true_density_shove_cannot_create_space():
|
|
"""The honest result: shove does NOT recover the order-independent drop.
|
|
|
|
shove_segment RELOCATES a blocking trace into existing free space; it does not
|
|
compress copper. NET_C walls the board top-to-bottom on the sole signal layer,
|
|
so there is nowhere to relocate NET_A's crossing run to -- every perpendicular
|
|
displacement lands on NET_C or outside the board. shove therefore adds zero
|
|
shoves and routes no more nets than plain routing. This is the boundary of the
|
|
shove mechanism: it recovers ordering-induced drops (free space exists), it
|
|
cannot manufacture density (free space absent)."""
|
|
dsn = parse_dsn(TRUE_DENSITY.read_text())
|
|
off, _, _ = route_dsn_board_rooms(dsn, shove=False)
|
|
on, scale, _ = route_dsn_board_rooms(dsn, shove=True)
|
|
clearance = _clearance(dsn, scale)
|
|
|
|
assert len(off.routed_net_numbers) == 1 # plain drops one
|
|
assert on.routed_net_numbers == off.routed_net_numbers # shove recovers nothing
|
|
assert on.shoves == 0 # no clean displacement exists to try
|
|
# whatever it does route stays exactly DRC-clean (invariant never traded away)
|
|
assert on.drc_clean
|
|
assert on.tree.has_violation(clearance) is None
|
|
assert _emitted_drc_violations(on.result, clearance) == []
|
|
|
|
|
|
def test_true_density_contrast_is_only_the_wall_height():
|
|
"""Pin down *why* shove recovers shove_channel but not true_density: the two
|
|
boards share the same NET_A blocker and the same channel geometry; they differ
|
|
only in how far NET_C spans. When NET_C leaves headroom (shove_channel) shove
|
|
nudges NET_A into it and both fit; when NET_C spans the full height
|
|
(true_density) that headroom is gone and shove is powerless. The recover/fail
|
|
boundary is the free-space boundary, which is the whole point."""
|
|
short_on, _, _ = route_dsn_board_rooms(parse_dsn(SHOVE_CHANNEL.read_text()), shove=True)
|
|
wall_on, _, _ = route_dsn_board_rooms(parse_dsn(TRUE_DENSITY.read_text()), shove=True)
|
|
# space exists -> shove recovers both, with real shoves
|
|
assert short_on.routed_net_numbers == {1, 2}
|
|
assert short_on.shoves >= 1
|
|
# space absent -> shove recovers neither extra net, zero shoves
|
|
assert len(wall_on.routed_net_numbers) == 1
|
|
assert wall_on.shoves == 0
|
|
|
|
|
|
def test_true_density_is_deterministic():
|
|
a, _, _ = route_dsn_board_rooms(parse_dsn(TRUE_DENSITY.read_text()), shove=True)
|
|
b, _, _ = route_dsn_board_rooms(parse_dsn(TRUE_DENSITY.read_text()), shove=True)
|
|
assert _wire_key(a.result) == _wire_key(b.result)
|
|
assert a.routed_net_numbers == b.routed_net_numbers
|