Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
"""Placement gate — score a placement plan against the design intent, before routing.
Checks (emitting harmonized findings):
- **decoupling proximity:** cap ≤ 2.0 mm from its IC = ok; 2.0–4.0 mm = WARN; > 4.0 mm = FAIL
(dense boards shouldn't hard-block on a marginal placement). Distance is center-to-center
(the plan carries ref centers). Ref: knowledge/design-standards.md (decap ≤ 2 mm).
- **connectors on their assigned edge** (else the connector can't open out).
- **courtyard spacing** (reuses pcbflow.geometry) and **keep-out intrusion**.
- **HPWL** reported as an info metric (the trace-length objective the placer minimized).
Pure Python 3 standard library.
"""
import math
from . import geometry
from .findings import finding
from .placer import hpwl, net_refs
DECAP_OK_MM = 2.0
DECAP_WARN_MM = 4.0
def _dist(a, b):
return math.hypot(a["x"] - b["x"], a["y"] - b["y"])
def _bbox(p, size):
sw, sh = size
return [p["x"] - sw / 2, p["y"] - sh / 2, p["x"] + sw / 2, p["y"] + sh / 2]
def _on_edge(p, edge, w, h, tol):
return {"left": p["x"] <= tol, "right": p["x"] >= w - tol,
"top": p["y"] <= tol, "bottom": p["y"] >= h - tol}[edge]
def run(plan, intent, enet, min_gap_mm=0.15): # 0.15 mm = the JLCPCB clearance floor (dfm.py)
V = []
for cap, ic in intent.decoupling().items():
if cap not in plan or ic not in plan:
continue
d = _dist(plan[cap], plan[ic])
if d > DECAP_WARN_MM:
V.append(finding(detector="placement", rule_id="decoupling_too_far", category="placement",
severity="error", confidence="deterministic", evidence_source="geometry",
summary=f"{cap} is {d:.1f} mm from {ic} (> {DECAP_WARN_MM} mm)",
where=cap, components=[cap, ic], provenance={"dist_mm": round(d, 2)},
recommendation="move the decoupling cap adjacent to the IC power pin"))
elif d > DECAP_OK_MM:
V.append(finding(detector="placement", rule_id="decoupling_far", category="placement",
severity="warning", confidence="deterministic", evidence_source="geometry",
summary=f"{cap} is {d:.1f} mm from {ic} (want ≤ {DECAP_OK_MM} mm)",
where=cap, components=[cap, ic], provenance={"dist_mm": round(d, 2)},
recommendation="tighten the decap-to-IC spacing if the layout allows"))
w, h = intent.outline()
tol = 2.0
for ref, edge in intent.connectors().items():
if ref in plan and not _on_edge(plan[ref], edge, w, h, tol):
V.append(finding(detector="placement", rule_id="connector_off_edge", category="placement",
severity="error", confidence="deterministic", evidence_source="geometry",
summary=f"connector {ref} is not on its assigned '{edge}' edge",
where=ref, components=[ref],
recommendation=f"place {ref} against the {edge} board edge"))
parts = [{"ref": r, "layer": plan[r]["layer"], "bbox": _bbox(plan[r], intent.size(r))} for r in plan]
for v in geometry.spacing_audit(parts, min_gap=min_gap_mm):
V.append(finding(detector="placement", rule_id="courtyard_overlap", category="placement",
severity="error", confidence="deterministic", evidence_source="geometry",
summary=f"courtyards overlap: {v['a']} & {v['b']} gap {v['gap']} mm",
where=f"{v['a']}<->{v['b']}", components=[v["a"], v["b"]],
recommendation="increase spacing"))
for p in parts: # keep-out intrusion (rect overlap)
x0, y0, x1, y1 = p["bbox"]
for k in intent.keepouts():
if x0 < k["x"] + k["w"] and x1 > k["x"] and y0 < k["y"] + k["h"] and y1 > k["y"]:
V.append(finding(detector="placement", rule_id="keepout_intrusion", category="placement",
severity="error", confidence="deterministic", evidence_source="geometry",
summary=f"{p['ref']} intrudes keep-out '{k.get('name','?')}'",
where=p["ref"], components=[p["ref"]],
recommendation="move the part out of the keep-out"))
V.append(finding(detector="placement", rule_id="hpwl", category="placement", severity="info",
confidence="deterministic", evidence_source="geometry",
summary=f"total HPWL (trace-length proxy) = "
f"{hpwl({r: [plan[r]['x'], plan[r]['y']] for r in plan}, net_refs(enet)):.1f} mm",
where="<board>"))
return V
7315b96d0This file is pinned to the selected release. Download the original to open it in your design tool.