ERROR stays only for RULE+MANDATORY. Hierarchy walks component→pin→net→block. Derating is PASS/MARGIN/RISK from Vop/Vrated. Timing and PI skip without numbers. ESD and HS return path are REVIEW, not RULE ERROR.
186 lines
7.0 KiB
Python
186 lines
7.0 KiB
Python
"""Reset RC and strap dividers — only when R, C, and datasheet times/levels exist."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from backend.periscopex.models import (
|
|
CapacitorSpecs,
|
|
ComponentConstraints,
|
|
ComponentType,
|
|
DesignGraph,
|
|
Finding,
|
|
ResistorSpecs,
|
|
)
|
|
from backend.periscopex.passive_rail_check import (
|
|
_is_reset_pin,
|
|
_pin_name_tokens,
|
|
_resistor_to_ground,
|
|
_resistor_to_power,
|
|
)
|
|
from backend.periscopex.validate import _match_constraints
|
|
|
|
_T_RESET_KEYS = ("t_reset_min_s", "t_reset_min_ms", "reset_delay_ms", "t_por_ms")
|
|
_VIH_KEYS = ("vih", "vih_min_v", "v_ih_min", "vih_min")
|
|
_VIL_KEYS = ("vil", "vil_max_v", "v_il_max", "vil_max")
|
|
_STRAP_RE = __import__("re").compile(
|
|
r"(?:PSEL|BOOT|STRAP|VSENSE|VSET|CFG)",
|
|
__import__("re").I,
|
|
)
|
|
|
|
|
|
def _specs_map(comp) -> dict:
|
|
specs = getattr(comp, "specs", None)
|
|
if specs is None:
|
|
return {}
|
|
vals = getattr(specs, "values", None)
|
|
return dict(vals) if isinstance(vals, dict) else {}
|
|
|
|
|
|
def _first_num(values: dict, keys: tuple[str, ...]) -> float | None:
|
|
for k in keys:
|
|
v = values.get(k)
|
|
if isinstance(v, bool) or v is None:
|
|
continue
|
|
try:
|
|
n = float(v)
|
|
except (TypeError, ValueError):
|
|
continue
|
|
if k.endswith("_ms"):
|
|
n = n / 1000.0
|
|
return n
|
|
return None
|
|
|
|
|
|
def _r_on_net(graph: DesignGraph, net: str) -> list[float]:
|
|
out: list[float] = []
|
|
for ref in graph.components_on_net(net):
|
|
comp = graph.components.get(ref)
|
|
if not comp or comp.component_type != ComponentType.RESISTOR:
|
|
continue
|
|
if isinstance(comp.specs, ResistorSpecs) and comp.specs.value_ohms > 0:
|
|
out.append(float(comp.specs.value_ohms))
|
|
return out
|
|
|
|
|
|
def _c_on_net(graph: DesignGraph, net: str) -> list[float]:
|
|
out: list[float] = []
|
|
for ref in graph.components_on_net(net):
|
|
comp = graph.components.get(ref)
|
|
if not comp or comp.component_type != ComponentType.CAPACITOR:
|
|
continue
|
|
if isinstance(comp.specs, CapacitorSpecs) and comp.specs.value_farads > 0:
|
|
out.append(float(comp.specs.value_farads))
|
|
return out
|
|
|
|
|
|
def check_timing(
|
|
graph: DesignGraph,
|
|
constraints_map: dict[str, ComponentConstraints] | None = None,
|
|
) -> list[Finding]:
|
|
"""PE-TIM-001 RC vs t_reset; PE-TIM-002 strap vs Vih/Vil. Skip without numbers."""
|
|
cmap = constraints_map or {}
|
|
out: list[Finding] = []
|
|
seen: set[str] = set()
|
|
for ref, comp in sorted(graph.components.items()):
|
|
if comp.component_type != ComponentType.IC:
|
|
continue
|
|
cons = _match_constraints(comp.mpn or comp.value, cmap)
|
|
values = _specs_map(comp)
|
|
t_min = _first_num(values, _T_RESET_KEYS)
|
|
vih = _first_num(values, _VIH_KEYS)
|
|
vil = _first_num(values, _VIL_KEYS)
|
|
for pin_num, net in sorted(comp.pins.items(), key=lambda x: str(x[0])):
|
|
if not net or net in seen:
|
|
continue
|
|
if _is_reset_pin(graph, cons, pin_num, net) and t_min is not None:
|
|
rs = _r_on_net(graph, net)
|
|
cs = _c_on_net(graph, net)
|
|
if not rs or not cs:
|
|
continue
|
|
tau = min(rs) * min(cs)
|
|
seen.add(net)
|
|
if tau + 1e-18 >= t_min:
|
|
continue
|
|
rec = (
|
|
f"Increase R or C on {net} so τ=RC ≥ {t_min:g} s "
|
|
f"(measured τ={tau:g} s)."
|
|
)
|
|
out.append(Finding(
|
|
designator=ref,
|
|
mpn=comp.mpn or "",
|
|
aspect="timing",
|
|
finding=(
|
|
f"{ref} reset net {net} has τ=RC={tau:g} s "
|
|
f"below t_reset={t_min:g} s."
|
|
),
|
|
facts=f"R={min(rs):g} Ω, C={min(cs):g} F, τ={tau:g} s.",
|
|
requirement=f"Datasheet t_reset_min={t_min:g} s.",
|
|
inference="τ=R·C compared to t_reset; no 10 ms default.",
|
|
why="Reset delay only when R, C, and t_reset are numeric.",
|
|
status="ERROR",
|
|
recommendation=rec,
|
|
action=rec,
|
|
source="timing_check",
|
|
rule_id="PE-TIM-001",
|
|
calculation=f"τ=R·C={tau:g}; t_min={t_min:g}",
|
|
evidence_status="SUFFICIENT",
|
|
net=net,
|
|
pins=[f"{ref}.{pin_num}"],
|
|
))
|
|
continue
|
|
tokens = _pin_name_tokens(cons, pin_num) or [net, str(pin_num)]
|
|
if not any(_STRAP_RE.search(t or "") for t in tokens):
|
|
continue
|
|
if vih is None and vil is None:
|
|
continue
|
|
if not (_resistor_to_power(graph, net) and _resistor_to_ground(graph, net)):
|
|
continue
|
|
rs = _r_on_net(graph, net)
|
|
if len(rs) < 2:
|
|
continue
|
|
rhi, rlo = max(rs), min(rs)
|
|
vrail = None
|
|
nobj = graph.nets.get(net)
|
|
# Strap mid-point: need rail voltage on the pull-up net.
|
|
for rref in graph.components_on_net(net):
|
|
rcomp = graph.components.get(rref)
|
|
if not rcomp or rcomp.component_type != ComponentType.RESISTOR:
|
|
continue
|
|
for pnet in rcomp.pins.values():
|
|
other = graph.nets.get(pnet)
|
|
if other and other.voltage and other.voltage > 0:
|
|
vrail = float(other.voltage)
|
|
if vrail is None:
|
|
continue
|
|
vstrap = vrail * rlo / (rhi + rlo)
|
|
seen.add(net)
|
|
fail = (vih is not None and vstrap < vih) or (vil is not None and vstrap > vil and vih is None)
|
|
if not fail:
|
|
continue
|
|
rec = f"Adjust the strap divider on {net} so Vstrap meets Vih/Vil."
|
|
out.append(Finding(
|
|
designator=ref,
|
|
mpn=comp.mpn or "",
|
|
aspect="timing",
|
|
finding=(
|
|
f"{ref} strap {net} V={vstrap:g} V from {rhi:g}/{rlo:g} Ω "
|
|
f"on {vrail:g} V rail."
|
|
),
|
|
facts=f"Vstrap={vstrap:g} V; Rhi={rhi:g}; Rlo={rlo:g}; Vrail={vrail:g}.",
|
|
requirement=(
|
|
f"Vih={vih if vih is not None else '—'} V, "
|
|
f"Vil={vil if vil is not None else '—'} V from specs."
|
|
),
|
|
inference="Divider ratio vs Vih/Vil; no 50% default.",
|
|
why="Strap level only when divider ohms and Vih/Vil exist.",
|
|
status="ERROR",
|
|
recommendation=rec,
|
|
action=rec,
|
|
source="timing_check",
|
|
rule_id="PE-TIM-002",
|
|
calculation=f"V=Vrail·Rlo/(Rhi+Rlo)={vstrap:g}",
|
|
evidence_status="SUFFICIENT",
|
|
net=net,
|
|
pins=[f"{ref}.{pin_num}"],
|
|
))
|
|
return out
|