Package/pinout/ratings mismatches, SPOF as REVIEW, EMI only with a datasheet quote, and Tj from copper+vias+θJA when every parameter exists. Re-extract SI layout_rules from 1.12.0; ImpedenceFinder license stays UNKNOWN.
134 lines
4.6 KiB
Python
134 lines
4.6 KiB
Python
"""EMI beyond ESD — only when a datasheet FACT names a filter.
|
|
|
|
No IEC 61000 invention. Common-mode / ferrite / shield from layout_rules
|
|
notes or kind, checked against parts on the same HS connector net.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
|
|
from backend.periscopex.models import (
|
|
ComponentConstraints,
|
|
ComponentType,
|
|
DesignGraph,
|
|
Finding,
|
|
LayoutGraph,
|
|
)
|
|
from backend.periscopex.pcb_net_match import (
|
|
normalize_kicad_hierarchy_net,
|
|
refs_on_matched_net,
|
|
)
|
|
from backend.periscopex.si_check import bus_class, skip_si_net
|
|
from backend.periscopex.validate import _match_constraints
|
|
|
|
_EMI_RE = re.compile(
|
|
r"(emi|emc|common[-\s]?mode|cmc|choke|ferrite\s*bead|shield(?:ing)?|"
|
|
r"pi\s*filter|lc\s*filter)",
|
|
re.I,
|
|
)
|
|
_EMI_KINDS = frozenset({"emi", "common_mode", "shield"})
|
|
|
|
|
|
def _is_filter_part(comp) -> bool:
|
|
sub = (comp.component_subtype or "").lower()
|
|
val = (comp.value or "").lower()
|
|
blob = f"{sub} {val} {comp.mpn or ''}"
|
|
if "esd" in sub or "protection" in sub:
|
|
return False
|
|
if "ferrite" in blob or "common" in blob or "choke" in blob or "cmc" in blob:
|
|
return True
|
|
if comp.component_type == ComponentType.INDUCTOR and _EMI_RE.search(blob):
|
|
return True
|
|
if (comp.reference or "").upper().startswith(("FB", "L")) and _EMI_RE.search(blob):
|
|
return True
|
|
return False
|
|
|
|
|
|
def _emi_rules(cons: ComponentConstraints | None) -> list[dict]:
|
|
if not cons:
|
|
return []
|
|
rows: list[dict] = []
|
|
for rule in cons.layout_rules or []:
|
|
if not isinstance(rule, dict):
|
|
continue
|
|
kind = str(rule.get("kind") or "")
|
|
note = str(rule.get("note") or "")
|
|
if kind in _EMI_KINDS or _EMI_RE.search(note) or _EMI_RE.search(kind):
|
|
rows.append(rule)
|
|
return rows
|
|
|
|
|
|
def check_emi(
|
|
graph: DesignGraph,
|
|
constraints_map: dict[str, ComponentConstraints] | None = None,
|
|
layout: LayoutGraph | None = None,
|
|
) -> list[Finding]:
|
|
"""REVIEW when a sourced EMI/filter FACT has no matching part on the net."""
|
|
_ = layout
|
|
cmap = constraints_map or {}
|
|
out: list[Finding] = []
|
|
seen: set[tuple[str, str]] = set()
|
|
|
|
for ref, comp in sorted(graph.components.items()):
|
|
if comp.component_type not in (ComponentType.IC, ComponentType.CONNECTOR):
|
|
continue
|
|
cons = _match_constraints(comp.mpn or comp.value, cmap)
|
|
rules = _emi_rules(cons)
|
|
if not rules:
|
|
continue
|
|
for pin, net in sorted(comp.pins.items(), key=lambda x: str(x[0])):
|
|
if not net or skip_si_net(net):
|
|
continue
|
|
if not bus_class(net) and comp.component_type != ComponentType.CONNECTOR:
|
|
continue
|
|
key = (ref, normalize_kicad_hierarchy_net(net))
|
|
if key in seen:
|
|
continue
|
|
on_net = refs_on_matched_net(graph, net)
|
|
if any(
|
|
r in graph.components and _is_filter_part(graph.components[r])
|
|
for r in on_net
|
|
):
|
|
continue
|
|
quote = next(
|
|
(str(r.get("note") or "") for r in rules if r.get("note")),
|
|
"",
|
|
)
|
|
page = next(
|
|
(r.get("source_page") for r in rules if r.get("source_page")),
|
|
None,
|
|
)
|
|
rec = (
|
|
f"Add the datasheet EMI/filter part on '{net}' "
|
|
f"(common-mode / ferrite / shield as quoted). Not IEC 61000."
|
|
)
|
|
seen.add(key)
|
|
out.append(Finding(
|
|
designator=ref,
|
|
mpn=comp.mpn or "",
|
|
aspect="emi",
|
|
finding=(
|
|
f"{net} has a datasheet EMI/filter FACT and no ferrite/"
|
|
f"common-mode/choke part on the net."
|
|
),
|
|
facts=(
|
|
f"net={net}; pin={pin}; filter_parts=none; "
|
|
f"quote={quote!r}; page={page}."
|
|
),
|
|
requirement=quote or "Datasheet layout_rules EMI/filter note.",
|
|
inference="Filter missing vs sourced note — REVIEW, not IEC.",
|
|
why="EMI only when the library quotes a filter; ESD is PE-ESD-001.",
|
|
status="WARNING",
|
|
recommendation=rec,
|
|
action=rec,
|
|
source="emi_check",
|
|
source_page=int(page) if isinstance(page, int) else None,
|
|
source_quote=quote,
|
|
rule_id="PE-EMI-001",
|
|
evidence_status="SUFFICIENT",
|
|
net=net,
|
|
pins=[str(pin)],
|
|
))
|
|
return out
|