A 1.0 mm trace from a 1.27 mm pitch pin (the RN2483 row on the TTN Enschede mote) has to stay within 0.06 mm of the pin's centreline until it is past the neighbouring pads. No grid column is that close, and the router could not get there: - A stub ran as one slanted segment to its cell centre. The exact check sees that as its bounding box, which lands on the next pad. Orthogonal stubs now leave straight along the pin's axis and jog to the cell only at the end. - End nodes were chosen as in free space even in the rip-up search, so once another net crossed a pin's only exit, the pin had no end left and rip-up could never take the exit back. In rip-up mode those cells and stubs are now enterable at the rip penalty, and routes on a stub are ripped along with the ones the path crosses. - Two neighbouring pins' stubs each claim the cells a centred trace would crowd, so their claims overlapped well before their copper did. Stub against stub is now checked on the copper. - Escapes also look one column either side, so two pins leaving the same way can jog apart. TTN Enschede mote, exact engine with --shove: 12 unrouted before, 0 after; KiCad DRC on the result has no clearance violations and no unconnected items. The 0.5 mm pitch row test at resolution 1 now routes all 5 pins (it pinned 2).
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 freeroute.board import build_board
|
|
from freeroute.board.items import Pin
|
|
from freeroute.dsn import parse_dsn
|
|
from freeroute.geometry import IntPoint
|
|
from freeroute.route.grid_router import GridRouter
|
|
from freeroute.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
|