Ship Fase B slices: hierarchy, derating bands, timing, PI, ESD/return.
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.
This commit is contained in:
@@ -0,0 +1,185 @@
|
||||
"""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
|
||||
Reference in New Issue
Block a user