Compare only specified IQ plus I_load to Iout_max and series-R drop, flag sequencing only when power_sequence is in specs, and treat DNP as a fitted-variant graph so a missing enable pull is ERROR only when the BOM actually marks DNP. Co-authored-by: Cursor <cursoragent@cursor.com>
110 lines
3.6 KiB
Python
110 lines
3.6 KiB
Python
"""Power-good → enable sequencing when the IC specs declare a sequence.
|
|
|
|
No RC time constants are invented. Missing power_sequence means skip.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
|
|
from backend.pinscopex.models import (
|
|
ComponentConstraints,
|
|
ComponentType,
|
|
DesignGraph,
|
|
Finding,
|
|
)
|
|
from backend.pinscopex.thermal_check import (
|
|
_VIN_PIN,
|
|
_VOUT_PIN,
|
|
_is_ldo,
|
|
_pin_net_by_role,
|
|
_specs_values,
|
|
)
|
|
from backend.pinscopex.validate import _match_constraints
|
|
|
|
_PG_RE = re.compile(r"(?:^|[_/])(PG|PGOOD|PWRGD|POWER_GOOD|POK)(?:$|[_/\d])", re.I)
|
|
_EN_RE = re.compile(
|
|
r"(?:^|[_/])(EN|ENA|ENABLE|n?SHDN|nEN|EN_N)(?:$|[_/\d])",
|
|
re.I,
|
|
)
|
|
|
|
|
|
def _has_sequence(comp) -> bool:
|
|
values = _specs_values(comp)
|
|
raw = values.get("power_sequence")
|
|
if raw is None or raw == "" or raw is False:
|
|
return False
|
|
if isinstance(raw, (int, float)) and raw == 0:
|
|
return False
|
|
return True
|
|
|
|
|
|
def check_power_sequencing(
|
|
graph: DesignGraph,
|
|
constraints_map: dict[str, ComponentConstraints] | None = None,
|
|
) -> list[Finding]:
|
|
cmap = constraints_map or {}
|
|
regs: list[tuple] = []
|
|
for ref, comp in sorted(graph.components.items()):
|
|
if comp.component_type != ComponentType.IC:
|
|
continue
|
|
cons = _match_constraints(comp.mpn or comp.value, cmap)
|
|
vin = _pin_net_by_role(graph, comp, cons, _VIN_PIN)
|
|
vout = _pin_net_by_role(graph, comp, cons, _VOUT_PIN)
|
|
if not (vin and vout) and not _is_ldo(comp, cons):
|
|
continue
|
|
pg = _pin_net_by_role(graph, comp, cons, _PG_RE, exclude_re=None)
|
|
en = _pin_net_by_role(graph, comp, cons, _EN_RE, exclude_re=None)
|
|
regs.append((ref, comp, cons, vin, vout, pg, en))
|
|
|
|
findings: list[Finding] = []
|
|
for dref, dcomp, dcons, dvin, _dvout, _dpg, den in regs:
|
|
if not _has_sequence(dcomp):
|
|
continue
|
|
if not den or not dvin:
|
|
continue
|
|
upstream = [
|
|
row for row in regs
|
|
if row[0] != dref and row[4] and row[4] == dvin
|
|
]
|
|
if not upstream:
|
|
continue
|
|
uref, ucomp, _ucons, _uvin, _uvout, upg, _uen = upstream[0]
|
|
if not upg:
|
|
findings.append(Finding(
|
|
designator=dref,
|
|
mpn=dcomp.mpn or "",
|
|
aspect="sequencing",
|
|
source="sequencing_check",
|
|
status="WARNING",
|
|
finding=(
|
|
f"{dref} specs declare power_sequence but upstream {uref} "
|
|
f"has no PG pin feeding {dref} EN '{den}'."
|
|
),
|
|
why="Sequence was listed in IC specs; delay milliseconds were not estimated.",
|
|
recommendation="Tie the upstream power-good to this enable, or remove the sequence spec if unused.",
|
|
reference="power_sequence",
|
|
net=den,
|
|
pins=[dref, uref],
|
|
rule_id="PS-SEQ-001",
|
|
))
|
|
continue
|
|
if upg != den:
|
|
findings.append(Finding(
|
|
designator=dref,
|
|
mpn=dcomp.mpn or "",
|
|
aspect="sequencing",
|
|
source="sequencing_check",
|
|
status="WARNING",
|
|
finding=(
|
|
f"{uref} PG '{upg}' does not connect to {dref} EN '{den}'."
|
|
),
|
|
why="Declared power_sequence expects PG to enable the next rail.",
|
|
recommendation="Net the upstream PG to the downstream EN.",
|
|
reference="power_sequence",
|
|
net=den,
|
|
pins=[f"{uref}", f"{dref}"],
|
|
rule_id="PS-SEQ-001",
|
|
))
|
|
return findings
|