Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
"""Tests for R2 — routing checks against the routed board (pour required, IPC-2152 width, max
length, return path). Standalone or pytest: python3 tests/test_routing_check.py"""
import os
import sys
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from pcbflow import findings, routing_check # noqa: E402
from pcbflow.enet import Enet # noqa: E402
from pcbflow.routing_rules import RoutingRules # noqa: E402
def _enet(net_class=None):
return Enet.from_dict({"version": "2.0.0", "netClass": net_class or {}, "components": {
"u": {"props": {"Designator": "U1"}, "pinInfoMap": {"1": {"number": "1", "name": "1", "net": "X"}}}}})
def _board(tracks, zones=()):
"""tracks = [(net, width_mm, length_mm)]; zones = [(net, filled_bool)]. Horizontal segments."""
names = sorted({t[0] for t in tracks} | {z[0] for z in zones})
idmap = {n: i + 1 for i, n in enumerate(names)}
lines = ['(kicad_pcb (version 1)', '(net 0 "")']
lines += [f'(net {i} "{n}")' for n, i in idmap.items()]
for net, w, length in tracks:
lines.append(f'(segment (start 0 0) (end {length} 0) (width {w}) (layer "F.Cu") '
f'(net {idmap[net]}))')
for net, filled in zones:
fp = '(filled_polygon (layer "B.Cu") (pts (xy 0 0)))' if filled else ''
lines.append(f'(zone (net {idmap[net]}) (net_name "{net}") (layer "B.Cu") {fp})')
return "\n".join(lines) + "\n)"
def _rules(fs):
return {f["rule_id"] for f in fs}
R = RoutingRules()
def test_high_current_on_trace_fires():
board = _board([("PWR", 0.5, 5)]) # 3 A on a trace, no pour
fs = routing_check.run(_enet(), board, R, currents={"PWR": 3.0})
assert "high_current_on_trace" in _rules(fs)
def test_poured_high_current_net_is_ok_for_pour_rule():
board = _board([("PWR", 2.0, 5)], zones=[("PWR", True)]) # wide + poured
fs = routing_check.run(_enet(), board, R, currents={"PWR": 3.0})
assert "high_current_on_trace" not in _rules(fs)
def test_trace_underwidth_fires():
board = _board([("SIG", 0.1, 5)]) # 1 A needs ~0.30 mm, only 0.1
fs = routing_check.run(_enet(), board, R, currents={"SIG": 1.0})
assert "trace_underwidth" in _rules(fs)
def test_over_length_fires():
e = _enet({"CLK": {"nets": ["CK"], "$cite": "x"}})
board = _board([("CK", 0.25, 60)]) # 60 mm > CLK max 50 mm
assert "over_length" in _rules(routing_check.run(e, board, R))
def test_return_path_missing_fires():
e = _enet({"USB_DIFF": {"nets": ["DP"], "$cite": "x"}})
board = _board([("DP", 0.25, 10)]) # impedance class, no GND plane
assert "return_path_missing" in _rules(routing_check.run(e, board, R))
def test_return_path_ok_with_gnd_plane():
e = _enet({"USB_DIFF": {"nets": ["DP"], "$cite": "x"}})
board = _board([("DP", 0.25, 10)], zones=[("GND", True)])
assert "return_path_missing" not in _rules(routing_check.run(e, board, R))
def test_routing_findings_harmonized():
board = _board([("PWR", 0.1, 5)])
for f in routing_check.run(_enet(), board, R, currents={"PWR": 3.0}):
assert findings.validate(f) == [] and f["detector"] == "routing"
def _run():
for nm, fn in sorted(globals().items()):
if nm.startswith("test_") and callable(fn):
fn()
print("PASS — routing checks: pour required, poured-ok, under-width, over-length, "
"return-path missing/ok.")
if __name__ == "__main__":
_run()
7315b96d0This file is pinned to the selected release. Download the original to open it in your design tool.