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,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
|
||||
Reference in New Issue
Block a user