Add protocol-certification M2 L0 structural certifier (2.66.0).
Presence and connection only. FAIL solely on missing MANDATORY structure. AXI4 internal is NOT_APPLICABLE, not FAIL. RECOMMENDED never FAIL. No L1–L3 and no invented ohms/mm/ps.
This commit is contained in:
@@ -0,0 +1,155 @@
|
||||
"""M2 L0 structural certifier: presence/connection; AXI N/A; RECOMMENDED not FAIL."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
|
||||
from backend.periscopex.models import (
|
||||
Component,
|
||||
ComponentType,
|
||||
DesignGraph,
|
||||
Net,
|
||||
NetType,
|
||||
PinConnection,
|
||||
)
|
||||
from backend.periscopex.protocol_l0 import (
|
||||
PACK_MACROPHASE,
|
||||
_pack,
|
||||
certify_l0,
|
||||
protocol_exam,
|
||||
)
|
||||
from backend.periscopex.protocol_recognize import recognize_physical_buses
|
||||
|
||||
|
||||
def _ic(ref: str, pins: dict[str, str], *, mpn: str = "", value: str = "") -> Component:
|
||||
return Component(
|
||||
reference=ref, value=value or mpn, footprint="",
|
||||
component_type=ComponentType.IC, mpn=mpn or None, pins=pins,
|
||||
)
|
||||
|
||||
|
||||
def _net(name: str, *pairs: tuple[str, str]) -> Net:
|
||||
return Net(
|
||||
name=name, net_type=NetType.SIGNAL,
|
||||
pins=[PinConnection(component_ref=r, pin_number=p) for r, p in pairs],
|
||||
)
|
||||
|
||||
|
||||
def _usb_connected() -> DesignGraph:
|
||||
return DesignGraph(
|
||||
components={
|
||||
"U1": _ic("U1", {"1": "USB_D+", "2": "USB_D-"}, mpn="CH340E"),
|
||||
"J2": Component(
|
||||
reference="J2", value="USB2_TypeA",
|
||||
footprint="USB_A",
|
||||
component_type=ComponentType.CONNECTOR,
|
||||
pins={"2": "USB_D+", "3": "USB_D-"},
|
||||
),
|
||||
},
|
||||
nets={
|
||||
"USB_D+": _net("USB_D+", ("U1", "1"), ("J2", "2")),
|
||||
"USB_D-": _net("USB_D-", ("U1", "2"), ("J2", "3")),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def _usb_dangling_minus() -> DesignGraph:
|
||||
return DesignGraph(
|
||||
components={
|
||||
"U1": _ic("U1", {"1": "USB_D+"}, mpn="CH340E"),
|
||||
"J2": Component(
|
||||
reference="J2", value="USB2_TypeA",
|
||||
footprint="USB_A",
|
||||
component_type=ComponentType.CONNECTOR,
|
||||
pins={"2": "USB_D+", "3": "USB_D-"},
|
||||
),
|
||||
},
|
||||
nets={
|
||||
"USB_D+": _net("USB_D+", ("U1", "1"), ("J2", "2")),
|
||||
"USB_D-": _net("USB_D-", ("J2", "3")),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def _axi_graph() -> DesignGraph:
|
||||
return DesignGraph(
|
||||
components={
|
||||
"U3": _ic("U3", {"1": "AXI_AWVALID"}, mpn="XC7A100T", value="AXI4 interconnect"),
|
||||
},
|
||||
nets={"AXI_AWVALID": _net("AXI_AWVALID", ("U3", "1"))},
|
||||
)
|
||||
|
||||
|
||||
def _ddr_one_ended() -> DesignGraph:
|
||||
pins = {str(i): f"DDR4_DQ{i}" for i in range(8)}
|
||||
pins.update({"8": "DDR4_DQS0_P", "9": "DDR4_DQS0_N"})
|
||||
return DesignGraph(
|
||||
components={"U5": _ic("U5", pins, mpn="MT41K256M16TW", value="DDR4")},
|
||||
nets={n: _net(n, ("U5", p)) for p, n in pins.items()},
|
||||
)
|
||||
|
||||
|
||||
def _l0_for(graph: DesignGraph, logical_sub: str) -> list:
|
||||
insts = recognize_physical_buses(graph)
|
||||
by, _ = certify_l0(graph, insts)
|
||||
rows = []
|
||||
for inst in insts:
|
||||
if logical_sub in inst.logical_protocol_id:
|
||||
rows.extend(by.get(inst.instance_id, []))
|
||||
return rows
|
||||
|
||||
|
||||
def test_usb_connected_pair_l0_topology_pass():
|
||||
rows = _l0_for(_usb_connected(), "usb2")
|
||||
topo = [r for r in rows if r.check == "topology"]
|
||||
assert topo
|
||||
assert topo[0].result == "PASS"
|
||||
assert topo[0].level == "L0"
|
||||
blob = json.dumps(topo[0].model_dump())
|
||||
assert "ohm" not in blob.lower()
|
||||
assert "90" not in blob
|
||||
|
||||
|
||||
def test_usb_unconnected_dminus_l0_fail_mandatory():
|
||||
g = _usb_dangling_minus()
|
||||
insts = recognize_physical_buses(g)
|
||||
usb = [i for i in insts if "usb" in i.logical_protocol_id]
|
||||
assert usb
|
||||
by, findings = certify_l0(g, insts)
|
||||
rows = [r for i in usb for r in by[i.instance_id]]
|
||||
topo = [r for r in rows if r.check == "topology"]
|
||||
assert topo
|
||||
assert topo[0].result == "FAIL"
|
||||
assert topo[0].mandatory == "MANDATORY"
|
||||
assert any(f.status == "ERROR" and f.rule_id == "PE-PRT-L0-001" for f in findings)
|
||||
|
||||
|
||||
def test_axi_internal_l0_not_applicable_not_fail():
|
||||
rows = _l0_for(_axi_graph(), "axi4")
|
||||
assert rows
|
||||
assert all(r.result == "NOT_APPLICABLE" for r in rows)
|
||||
assert all(r.result != "FAIL" for r in rows)
|
||||
sec, findings = protocol_exam(_axi_graph())
|
||||
assert sec.max_level_reached == "L0"
|
||||
assert sec.macrophase == PACK_MACROPHASE
|
||||
assert all(f.status != "ERROR" for f in findings)
|
||||
assert all("AXI_AWVALID" not in (f.net or "") for f in findings if f.rule_id == "PE-PRT-L0-001")
|
||||
|
||||
|
||||
def test_ddr_unconnected_memory_fails_l0_connection():
|
||||
rows = _l0_for(_ddr_one_ended(), "ddr4")
|
||||
mapping = [r for r in rows if r.check == "byte_lane_mapping"]
|
||||
assert mapping
|
||||
assert mapping[0].result == "FAIL"
|
||||
assert "unconnected" in mapping[0].notes
|
||||
|
||||
|
||||
def test_recommended_never_parses_as_fail():
|
||||
rec = _pack("return_path", "FAIL", "RECOMMENDED", notes="missing")
|
||||
assert rec.result != "FAIL"
|
||||
assert rec.status != "ERROR"
|
||||
|
||||
|
||||
def test_l0_does_not_run_impedance():
|
||||
rows = _l0_for(_usb_connected(), "usb2")
|
||||
assert not any(r.check in {"differential_impedance", "intra_pair_skew", "length"} for r in rows)
|
||||
@@ -13,7 +13,6 @@ from backend.periscopex.models import (
|
||||
PinConnection,
|
||||
)
|
||||
from backend.periscopex.protocol_recognize import (
|
||||
PACK_MACROPHASE,
|
||||
protocol_section_for_graph,
|
||||
recognize_physical_buses,
|
||||
)
|
||||
@@ -143,7 +142,6 @@ def test_empty_graph_keeps_nessun_protocollo():
|
||||
sec = protocol_section_for_graph(DesignGraph())
|
||||
assert sec.message == "nessun protocollo riconosciuto"
|
||||
assert sec.recognized_instances == []
|
||||
assert sec.macrophase == PACK_MACROPHASE
|
||||
blob = json.dumps(sec.model_dump())
|
||||
assert "ohm" not in blob.lower()
|
||||
|
||||
@@ -151,7 +149,6 @@ def test_empty_graph_keeps_nessun_protocollo():
|
||||
def test_report_section_lists_usb_without_z():
|
||||
sec = protocol_section_for_graph(_usb_graph())
|
||||
assert sec.recognized_instances
|
||||
assert sec.max_level_reached is None
|
||||
blob = json.dumps(sec.model_dump())
|
||||
assert "90" not in blob
|
||||
assert "Zdiff" not in blob
|
||||
|
||||
Reference in New Issue
Block a user