Prove shove-in-rooms recovers ordering drops but does not create density
Add true_density.dsn: NET_C is a full-height wall on the sole signal layer, so plain room routing drops a net under every net ordering (an order-independent obstruction, verified over all permutations), unlike rooms_shove_channel whose plain drop fits under a reorder. Test that shove recovers nothing on this board (shoves == 0, same routed set as plain) while staying DRC-clean, and that the only difference from the recoverable shove_channel is the wall's span. shove relocates a blocker into existing free space; where the wall leaves no headroom there is nowhere to relocate to, so it cannot manufacture density. The recover/fail boundary is the free-space boundary.
This commit is contained in:
parent
9cf2392c4b
commit
99c3d20de5
64
tests/dsn/fixtures/true_density.dsn
Normal file
64
tests/dsn/fixtures/true_density.dsn
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
(pcb "true_density.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)
|
||||||
|
)
|
||||||
|
(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 A1 7000 -15000 front 0)
|
||||||
|
(place A2 193000 -15000 front 0)
|
||||||
|
(place C1 100000 -500 front 0)
|
||||||
|
(place C2 100000 -29500 front 0)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(network
|
||||||
|
(net NET_A
|
||||||
|
(pins A1-1 A2-1)
|
||||||
|
)
|
||||||
|
(net NET_C
|
||||||
|
(pins C1-1 C2-1)
|
||||||
|
)
|
||||||
|
(class default
|
||||||
|
(rule
|
||||||
|
(width 2000)
|
||||||
|
(clearance 2000)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(wiring
|
||||||
|
)
|
||||||
|
)
|
||||||
@ -15,6 +15,14 @@ Two opt-in (``shove=True``) upgrades to the continuous expansion-room track:
|
|||||||
see ``test_room_shove_channel_drop_is_ordering_artifact_not_density``), not a
|
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
|
genuine density limit. The true sub-cell win is occupancy packing, which the
|
||||||
``rooms_wide_door`` fixture exercises with ``shoves == 0``.
|
``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 __future__ import annotations
|
||||||
@ -67,6 +75,7 @@ SHOVE_CHANNEL = FIXTURES / "rooms_shove_channel.dsn"
|
|||||||
WIDE_DOOR = FIXTURES / "rooms_wide_door.dsn"
|
WIDE_DOOR = FIXTURES / "rooms_wide_door.dsn"
|
||||||
MULTIPIN_DESYNC = FIXTURES / "rooms_multipin_desync.dsn"
|
MULTIPIN_DESYNC = FIXTURES / "rooms_multipin_desync.dsn"
|
||||||
MULTIPIN_DESYNC_CRASH = FIXTURES / "rooms_multipin_desync_crash.dsn"
|
MULTIPIN_DESYNC_CRASH = FIXTURES / "rooms_multipin_desync_crash.dsn"
|
||||||
|
TRUE_DENSITY = FIXTURES / "true_density.dsn"
|
||||||
|
|
||||||
FOUNDATION = [SIMPLE, CROSSING, KICAD, NARROW]
|
FOUNDATION = [SIMPLE, CROSSING, KICAD, NARROW]
|
||||||
|
|
||||||
@ -346,3 +355,101 @@ def test_room_shove_multipin_recovery_does_not_crash():
|
|||||||
clearance = _clearance(dsn, scale)
|
clearance = _clearance(dsn, scale)
|
||||||
assert on.drc_clean
|
assert on.drc_clean
|
||||||
assert _emitted_drc_violations(on.result, clearance) == []
|
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
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user