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.
135 lines
4.7 KiB
Python
135 lines
4.7 KiB
Python
"""Single-point-of-failure on power rails — REVIEW, never RULE ERROR.
|
|
|
|
FACT: how many regulators drive a rail and how many ICs sit on it.
|
|
REQUIREMENT: redundancy is not a datasheet shall unless stated.
|
|
INFERENCE: this rail is a SPOF — review, do not invent IEC SIL.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from collections import defaultdict
|
|
|
|
from backend.periscopex.models import (
|
|
ComponentConstraints,
|
|
ComponentType,
|
|
DesignGraph,
|
|
Finding,
|
|
NetType,
|
|
)
|
|
from backend.periscopex.pcb_net_match import normalize_kicad_hierarchy_net
|
|
from backend.periscopex.power_margin_check import _regulators
|
|
|
|
_SKIP_RAIL = frozenset({"GND", "AGND", "DGND", "PGND", "VSS"})
|
|
|
|
|
|
def check_spof(
|
|
graph: DesignGraph,
|
|
constraints_map: dict[str, ComponentConstraints] | None = None,
|
|
) -> list[Finding]:
|
|
cmap = constraints_map or {}
|
|
producers: dict[str, list[str]] = defaultdict(list)
|
|
for ref, _comp, _cons, _vin, vout in _regulators(graph, cmap):
|
|
key = normalize_kicad_hierarchy_net(vout)
|
|
if key.upper() in _SKIP_RAIL:
|
|
continue
|
|
producers[key].append(ref)
|
|
|
|
out: list[Finding] = []
|
|
seen: set[str] = set()
|
|
for net_name, net in sorted(graph.nets.items()):
|
|
key = normalize_kicad_hierarchy_net(net_name)
|
|
if key in seen or key.upper() in _SKIP_RAIL:
|
|
continue
|
|
if net.net_type != NetType.POWER and not producers.get(key):
|
|
continue
|
|
regs = producers.get(key) or []
|
|
if len(regs) != 1:
|
|
continue
|
|
ics = [
|
|
r for r in graph.components_on_net(net_name)
|
|
if r in graph.components
|
|
and graph.components[r].component_type == ComponentType.IC
|
|
and r not in regs
|
|
]
|
|
if len(ics) < 1:
|
|
continue
|
|
seen.add(key)
|
|
reg = regs[0]
|
|
rcomp = graph.components[reg]
|
|
rec = (
|
|
f"Review whether '{net_name}' needs a second source; {reg} is the "
|
|
f"only regulator feeding {len(ics)} IC(s). This is not a design-rule ERROR."
|
|
)
|
|
out.append(Finding(
|
|
designator=reg,
|
|
mpn=rcomp.mpn or "",
|
|
aspect="spof",
|
|
finding=(
|
|
f"Rail '{net_name}' has a single regulator ({reg}) and "
|
|
f"{len(ics)} downstream IC(s) ({', '.join(ics[:8])}"
|
|
f"{'…' if len(ics) > 8 else ''})."
|
|
),
|
|
facts=(
|
|
f"producers={regs}; downstream_ics={ics}; "
|
|
f"net_type={net.net_type.value}."
|
|
),
|
|
requirement=(
|
|
"Redundancy / dual-supply is REVIEW unless a datasheet shall "
|
|
"names a second source (none assumed)."
|
|
),
|
|
inference="Single point of failure on this rail — engineering review.",
|
|
why="SPOF is not a measured RULE; no IEC SIL invented.",
|
|
status="INFO",
|
|
recommendation=rec,
|
|
action=rec,
|
|
source="spof_check",
|
|
rule_id="PE-SPOF-001",
|
|
evidence_status="SUFFICIENT",
|
|
net=net_name,
|
|
pins=[],
|
|
))
|
|
|
|
# Crystal / clock: one XTAL feeding one MCU is normal — only flag when
|
|
# several ICs share one crystal net with no second oscillator FACT.
|
|
for ref, comp in sorted(graph.components.items()):
|
|
if comp.component_type != ComponentType.CRYSTAL:
|
|
continue
|
|
nets = [n for n in set(comp.pins.values()) if n]
|
|
ics: list[str] = []
|
|
for n in nets:
|
|
for r in graph.components_on_net(n):
|
|
c = graph.components.get(r)
|
|
if c and c.component_type == ComponentType.IC and r not in ics:
|
|
ics.append(r)
|
|
xtals = [
|
|
r for r, c in graph.components.items()
|
|
if c.component_type == ComponentType.CRYSTAL
|
|
]
|
|
if len(ics) < 2 or len(xtals) > 1:
|
|
continue
|
|
rec = (
|
|
f"Review clock redundancy: {ref} is the only crystal and feeds "
|
|
f"{len(ics)} ICs. Not a RULE."
|
|
)
|
|
out.append(Finding(
|
|
designator=ref,
|
|
mpn=comp.mpn or "",
|
|
aspect="spof",
|
|
finding=(
|
|
f"{ref} is the only crystal and clocks {len(ics)} ICs "
|
|
f"({', '.join(ics)})."
|
|
),
|
|
facts=f"xtals={xtals}; ics_on_crystal_nets={ics}.",
|
|
requirement="A second oscillator is not assumed from IEC or folklore.",
|
|
inference="Clock SPOF — REVIEW.",
|
|
why="Counted crystals and IC pins on those nets.",
|
|
status="INFO",
|
|
recommendation=rec,
|
|
action=rec,
|
|
source="spof_check",
|
|
rule_id="PE-SPOF-001",
|
|
evidence_status="SUFFICIENT",
|
|
pins=[],
|
|
))
|
|
return out
|