Every connection is routed N times on the empty board, where routes may share cells at a price that doubles each round for every other net on a cell, and a cell that stays shared gains history. The real routing then pays that history on free cells, so the first routes stop taking the space later routes (a fine-pitch part's escapes) have no way round. Off by default. The old history term never steered: it only accrued at rip-up fights, after the first routes had claimed the contested space, and routed connections are never revisited in later passes.
66 lines
2.5 KiB
Python
66 lines
2.5 KiB
Python
"""--negotiate: PathFinder-style rounds on the empty board before routing."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from freeroute.board import build_board
|
|
from freeroute.dsn import parse_dsn
|
|
from freeroute.route.grid_router import GridRouter
|
|
from freeroute.route.pipeline import _rule_clearance_dsn, _rule_width_dsn, route_dsn_board
|
|
|
|
FIXTURES = Path(__file__).resolve().parent.parent / "dsn" / "fixtures"
|
|
|
|
|
|
def _router(name, negotiate):
|
|
dsn = parse_dsn((FIXTURES / name).read_text())
|
|
s = dsn.resolution.value
|
|
return GridRouter(
|
|
build_board(dsn),
|
|
trace_width=round(_rule_width_dsn(dsn) * s),
|
|
clearance=round(_rule_clearance_dsn(dsn) * s),
|
|
negotiate=negotiate,
|
|
)
|
|
|
|
|
|
def test_negotiation_leaves_the_board_empty_and_the_ends_as_they_were():
|
|
router = _router("true_density.dsn", 3)
|
|
conns = router._conns = router._build_connections()
|
|
ends = [(c.a_end, c.b_end) for c in conns]
|
|
router._negotiate(list(conns), conns)
|
|
assert router._history # a dense board has contested cells
|
|
assert not router._cell_owner and not router._halo and not router._via_claims
|
|
assert not any(c.routed for c in conns)
|
|
assert [(c.a_end, c.b_end) for c in conns] == ends
|
|
assert router._present is None
|
|
|
|
|
|
def test_sharing_is_priced_only_for_other_nets():
|
|
router = _router("true_density.dsn", 1)
|
|
node = (3, 4, 0)
|
|
router._history[node] = 2.0
|
|
router._present, router._present_cost = {node: {7: 1, 9: 2}}, 0.5
|
|
assert router._congestion(node, 7) == pytest.approx(2.5) # one other net
|
|
assert router._congestion(node, 5) == pytest.approx(3.0) # two other nets
|
|
router._present = None
|
|
assert router._congestion(node, 5) == pytest.approx(2.0) # history only, after
|
|
|
|
|
|
@pytest.mark.parametrize("name", ["ripup_needed.dsn", "true_density.dsn", "kicad_routable.dsn"])
|
|
def test_negotiated_routing_keeps_the_occupancy_honest(name):
|
|
"""Only routed connections hold cells afterwards, as without negotiation."""
|
|
router = _router(name, 3)
|
|
router.route()
|
|
conns = router._conns
|
|
assert all(conns[i].routed for i in router._cell_owner.values())
|
|
assert all(conns[i].routed for claims in router._halo.values() for i in claims)
|
|
|
|
|
|
def test_option_reaches_the_router_and_is_off_by_default():
|
|
text = (FIXTURES / "kicad_routable.dsn").read_text()
|
|
plain, _, _ = route_dsn_board(parse_dsn(text))
|
|
again, _, _ = route_dsn_board(parse_dsn(text), negotiate=0)
|
|
assert plain.unrouted == again.unrouted
|