"""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, 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 == "L2" from backend.periscopex.protocol_report import PACK_MACROPHASE as L5_MACRO assert sec.macrophase == L5_MACRO 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)