Give pin stubs clearance, and let a route leave a pin by any open side
Every route starts and ends with a stub from the pin to its first grid cell, and the grid never saw that copper: on the rc4 sense board 24 of 39 connections the exact verifier rejected had a later route pass beside an earlier stub. A route's stubs now claim the cells they come near, a stub must be clear of other nets to be used, and a route may start from any enterable neighbour of the pin cell, costed by stub length (Manhattan on the orthogonal track, so a pin on a cell centre still leaves straight). Tests: a route near a rounded corner keeps its edge clearance; and a strict xfail that pins down the remaining gap, a 0.5 mm pin row the grid cannot give a column to every pin of.
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
"""Routing behaviours found on the TerraHawk rc4 boards (2026-09-27)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
|
||||
import pytest
|
||||
|
||||
from freeroute.board import build_board
|
||||
from freeroute.dsn import parse_dsn
|
||||
from freeroute.route.pipeline import route_dsn_board_exact
|
||||
|
||||
# 20 x 20 mm board whose corners are rounded with a 3 mm radius, as polylines
|
||||
_R = 3000.0
|
||||
_arc = [(_R - _R * math.cos(math.radians(a)), _R - _R * math.sin(math.radians(a))) for a in range(0, 91, 15)]
|
||||
|
||||
|
||||
def _outline_coords():
|
||||
pts = []
|
||||
for cx, cy, sx, sy in ((0, 0, 1, 1), (20000, 0, -1, 1), (20000, -20000, -1, -1), (0, -20000, 1, -1)):
|
||||
corner = [(cx + sx * x, cy - sy * y) for x, y in _arc]
|
||||
pts.extend(corner if sx * sy > 0 else corner[::-1])
|
||||
return " ".join(f"{x:.1f} {y:.1f}" for x, y in pts)
|
||||
|
||||
|
||||
def _dsn(placement, network, extra=""):
|
||||
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 {_outline_coords()}))
|
||||
(via "Via[0-1]_600:300_um") (rule (width 250) (clearance 150)) {extra})
|
||||
(placement {placement})
|
||||
(library (image P (pin pad 1 0 0))
|
||||
(padstack pad (shape (rect F.Cu -150 -500 150 500)) (attach off))
|
||||
(padstack "Via[0-1]_600:300_um" (shape (circle F.Cu 600)) (shape (circle B.Cu 600)) (attach off)))
|
||||
(network {network}))"""
|
||||
|
||||
|
||||
def test_routes_keep_edge_clearance_at_a_rounded_corner():
|
||||
# two pads near the top-left corner: the direct path cuts the corner arc
|
||||
text = _dsn("(component P (place A 1800 -6000 front 0) (place B 6000 -1800 front 90))",
|
||||
"(net N (pins A-1 B-1))")
|
||||
dsn = parse_dsn(text)
|
||||
result, scale, _ = route_dsn_board_exact(dsn, edge_clearance_mm=0.5)
|
||||
assert result.routed_net_numbers
|
||||
outline = build_board(dsn).outline
|
||||
half = 125 * scale
|
||||
for segs in result.result.wires.values():
|
||||
for _layer, pts in segs:
|
||||
for p in pts:
|
||||
# every trace point, plus half the trace, keeps 0.5 mm to the edge
|
||||
assert outline.edge_distance(p) >= half + 500 * scale - 1
|
||||
|
||||
|
||||
@pytest.mark.xfail(
|
||||
strict=True,
|
||||
reason="known gap: the grid step (trace + clearance, 0.4 mm here) does not line up "
|
||||
"with a 0.5 mm pin row, so only the outer pins get a column; the FreeRouting JAR, "
|
||||
"being gridless, routes all five. This is most of the gap on an LQFP.",
|
||||
)
|
||||
def test_every_pin_of_a_half_millimetre_pitch_row_escapes():
|
||||
# five pads at 0.5 mm pitch, each wired to its own pad 8 mm away
|
||||
places = " ".join(f"(place R{i} {6000 + 500 * i} -6000 front 0)" for i in range(5))
|
||||
places += " " + " ".join(f"(place F{i} {6000 + 500 * i} -14000 front 0)" for i in range(5))
|
||||
nets = " ".join(f"(net N{i} (pins R{i}-1 F{i}-1))" for i in range(5))
|
||||
result, _, _ = route_dsn_board_exact(parse_dsn(_dsn(f"(component P {places})", nets)))
|
||||
assert len(result.routed_net_numbers) == 5
|
||||
Reference in New Issue
Block a user