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:
2026-09-20 09:35:04 +02:00
parent facbaa2305
commit 342792df2d
16 changed files with 1259 additions and 27 deletions
+160
View File
@@ -0,0 +1,160 @@
"""Component → pin → net → block hierarchy (usable inventory, not a platform)."""
from __future__ import annotations
from pydantic import BaseModel
from backend.periscopex.functional_groups import FunctionalGroupsReport
from backend.periscopex.models import DesignGraph, Finding
class HierarchyPin(BaseModel):
number: str
net: str = ""
class HierarchyComponent(BaseModel):
ref: str
component_type: str
pins: list[HierarchyPin] = []
nets: list[str] = []
block_id: str | None = None
class HierarchyNet(BaseModel):
name: str
net_type: str
components: list[str] = []
block_id: str | None = None
class HierarchyBlock(BaseModel):
block_id: str
kind: str # domain | group | rail
ic_refs: list[str] = []
nets: list[str] = []
class HierarchyReport(BaseModel):
components: list[HierarchyComponent] = []
nets: list[HierarchyNet] = []
blocks: list[HierarchyBlock] = []
def build_hierarchy(
graph: DesignGraph,
plan: FunctionalGroupsReport | None = None,
) -> HierarchyReport:
"""Walk component.pins → nets, then group into domains / rail blocks."""
ref_block: dict[str, str] = {}
blocks: list[HierarchyBlock] = []
if plan is not None:
for d in plan.domains:
blocks.append(HierarchyBlock(
block_id=d.domain_id,
kind="domain",
ic_refs=list(d.ic_refs),
nets=list(d.power_nets),
))
for ref in d.ic_refs:
ref_block[ref] = d.domain_id
for g in plan.groups:
bid = ref_block.get(g.ref) or f"group:{g.ref}"
if g.ref not in ref_block:
blocks.append(HierarchyBlock(
block_id=bid, kind="group", ic_refs=[g.ref], nets=[],
))
ref_block[g.ref] = bid
else:
for net in graph.nets.values():
if net.net_type.value != "power":
continue
ics = [
p.component_ref for p in net.pins
if (graph.components.get(p.component_ref)
and graph.components[p.component_ref].component_type.value == "ic")
]
if not ics:
continue
bid = f"rail:{net.name}"
blocks.append(HierarchyBlock(
block_id=bid, kind="rail", ic_refs=sorted(set(ics)), nets=[net.name],
))
for ref in ics:
ref_block.setdefault(ref, bid)
net_block: dict[str, str] = {}
for b in blocks:
for n in b.nets:
net_block[n] = b.block_id
comps: list[HierarchyComponent] = []
for ref, comp in sorted(graph.components.items()):
pins = [
HierarchyPin(number=str(num), net=n or "")
for num, n in sorted(comp.pins.items(), key=lambda x: str(x[0]))
]
nets = sorted({p.net for p in pins if p.net})
bid = ref_block.get(ref)
if bid is None and nets:
for n in nets:
if n in net_block:
bid = net_block[n]
break
comps.append(HierarchyComponent(
ref=ref,
component_type=comp.component_type.value,
pins=pins,
nets=nets,
block_id=bid,
))
hnets: list[HierarchyNet] = []
for name, net in sorted(graph.nets.items()):
refs = sorted({p.component_ref for p in net.pins})
bid = net_block.get(name)
if bid is None:
for r in refs:
if r in ref_block:
bid = ref_block[r]
break
hnets.append(HierarchyNet(
name=name,
net_type=net.net_type.value,
components=refs,
block_id=bid,
))
return HierarchyReport(components=comps, nets=hnets, blocks=blocks)
def check_hierarchy(graph: DesignGraph, plan: FunctionalGroupsReport | None = None) -> list[Finding]:
"""FACT: IC pin with an empty net. Skip unnamed power-flags."""
out: list[Finding] = []
for ref, comp in sorted(graph.components.items()):
if comp.component_type.value != "ic":
continue
dangling = [str(n) for n, net in comp.pins.items() if not (net or "").strip()]
if not dangling:
continue
rec = f"Connect {ref} pin(s) {', '.join(dangling)} to a named net, or mark NC."
out.append(Finding(
designator=ref,
mpn=comp.mpn or "",
aspect="hierarchy",
finding=f"{ref} pin(s) {', '.join(dangling)} have no net.",
facts=f"{ref} pins {dangling} net=''.",
requirement="Every IC pin lands on a named net (or is explicitly NC).",
inference="Empty pin map is a graph FACT, not a layout millimetre claim.",
why="Component → pin → net hierarchy.",
status="INFO",
recommendation=rec,
action=rec,
source="hierarchy_check",
rule_id="PE-HIER-001",
finding_class="INFO",
provenance="TYPICAL",
evidence_status="SUFFICIENT",
pins=[f"{ref}.{p}" for p in dangling],
))
_ = plan
return out