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.
59 lines
2.5 KiB
Python
59 lines
2.5 KiB
Python
"""Pins of a 1.27 mm pitch row routed at 1.0 mm (the RN2483 module on the
|
|
TTN Enschede mote). A trace centre has to stay within 0.06 mm of a pin's
|
|
centreline to pass its neighbours, which no grid column hits, so a route
|
|
leaves each pin straight along that line and only jogs to its column once
|
|
past the pads. FreeRouting routes this row in full the same way."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from rpmroute.board import build_board
|
|
from rpmroute.board.items import Pin
|
|
from rpmroute.dsn import parse_dsn
|
|
from rpmroute.geometry import IntPoint
|
|
from rpmroute.route.grid_router import GridRouter
|
|
from rpmroute.route.pipeline import route_dsn_board_exact
|
|
|
|
|
|
def _row(x0: int, pins: int = 6, skew: int = 3000) -> str:
|
|
places = " ".join(f"(place R{i} {x0 + 1270 * i} -5000 front 0)" for i in range(pins))
|
|
places += " " + " ".join(
|
|
f"(place T{i} {x0 + 1270 * i + skew} -15000 front 0)" for i in range(pins)
|
|
)
|
|
nets = " ".join(f"(net N{i} (pins R{i}-1 T{i}-1))" for i in range(pins))
|
|
return f"""(pcb t (parser) (resolution um 10) (unit um)
|
|
(structure (layer F.Cu (type signal)) (layer B.Cu (type signal))
|
|
(boundary (path pcb 0 0 0 30000 0 30000 -20000 0 -20000 0 0))
|
|
(via "Via[0-1]_600:400_um") (rule (width 1000) (clearance 200)))
|
|
(placement (component P {places}))
|
|
(library (image P (pin pad 1 0 0))
|
|
(padstack pad (shape (rect F.Cu -508 -762 508 762)) (attach off))
|
|
(padstack "Via[0-1]_600:400_um" (shape (circle F.Cu 600)) (shape (circle B.Cu 600))
|
|
(attach off)))
|
|
(network {nets}))"""
|
|
|
|
|
|
@pytest.mark.parametrize("x0", [5000, 5150, 5300])
|
|
def test_every_pin_of_a_row_leaves_straight_and_routes(x0):
|
|
# three offsets of the row against the grid; before, 2 or 3 of 6 routed
|
|
result, _, _ = route_dsn_board_exact(parse_dsn(_row(x0)), resolution=2)
|
|
assert len(result.routed_net_numbers) == 6
|
|
assert result.drc_clean
|
|
|
|
|
|
def test_a_walled_in_pin_may_escape_to_the_column_either_side():
|
|
# two pins side by side leaving the same way can then jog apart
|
|
board = build_board(parse_dsn(_row(5000)))
|
|
router = GridRouter(board, trace_width=10000, clearance=2000, orthogonal=True, resolution=2)
|
|
router._conns = router._build_connections()
|
|
pin = next(
|
|
p for p in board.get_items()
|
|
if isinstance(p, Pin) and p.location == IntPoint(50000 + 2 * 12700, -50000)
|
|
)
|
|
net = pin.net_nos[0]
|
|
cx, cy = router._cell_of(pin.location)
|
|
nodes = router._escape_nodes(pin, net, router._conns, [0], (cx, cy))
|
|
columns = {x for x, y, _ in nodes if y != cy}
|
|
assert {cx - 1, cx, cx + 1} <= columns
|