A cell was exactly one trace plus its clearance, so a trace owning its cell was the whole clearance model, and the 0.4 mm grid could not reach most pins of a 0.5 mm pitch package: between two 0.3 mm pads a trace's centre has a legal band only 0.15 mm wide. Resolution K makes cells K times finer and has every trace claim a halo, the cells whose centre is nearer than trace + clearance to one of its own, released again on rip-up. K = 1 has an empty halo and is the old grid exactly (tested). On a 0.5 mm row, K = 4 gets every pin out where K = 1 gets two. On the rc4 boards, K = 2 cuts the sense board's opens from 66 to 41 (JAR: 32) and the power board's to 7 (JAR: 12), still with no DRC errors from freeroute's own copper.
76 lines
3.3 KiB
Python
76 lines
3.3 KiB
Python
"""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.parametrize(("resolution", "escape"), [(1, 2), (4, 5)])
|
|
def test_every_pin_of_a_half_millimetre_pitch_row_escapes(resolution, escape):
|
|
# Five 0.3 mm pads at 0.5 mm pitch, each wired to its own pad 8 mm away.
|
|
# Between two neighbours a trace's centre has a legal band only 0.15 mm
|
|
# wide. At resolution 1 the 0.4 mm grid misses most bands; at 4 its
|
|
# 0.1 mm columns cannot, and every pin gets out, as the JAR manages.
|
|
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)), resolution=resolution
|
|
)
|
|
assert len(result.routed_net_numbers) == escape
|
|
|
|
|
|
def test_resolution_one_has_no_halo_so_the_grid_is_unchanged():
|
|
from freeroute.route.grid_router import GridRouter
|
|
|
|
board = build_board(parse_dsn(_dsn("", "")))
|
|
assert GridRouter(board, trace_width=2500, clearance=1500)._halo_offsets == []
|
|
fine = GridRouter(board, trace_width=2500, clearance=1500, resolution=2)
|
|
assert fine.step == 2000 and (1, 0) in fine._halo_offsets and (2, 0) not in fine._halo_offsets
|