"""M4 L2 ELECTRICAL certifier: Z from cite/datasheet/stackup only; never 90 Ω folklore.""" from __future__ import annotations import json from backend.periscopex.models import ( Component, ComponentConstraints, ComponentType, DesignGraph, LayoutGraph, Net, NetType, PinConnection, ) from backend.periscopex.protocol_catalog import ( ConstraintSource, PhysicalInterface, ProtocolConstraint, ) from backend.periscopex.protocol_l0 import protocol_exam from backend.periscopex.protocol_l2 import ( _pack, certify_instance_l2, certify_l2, electrical_verdict, intersect_windows, ) 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 _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_graph() -> DesignGraph: pins = {str(i): f"DDR4_DQ{i}" for i in range(8)} pins.update({"8": "DDR4_DQS0_P", "9": "DDR4_DQS0_N"}) u_mem = _ic("U5", pins, mpn="MT41K256M16TW", value="DDR4") u_cpu = _ic("U4", {str(i): pins[str(i)] for i in pins}, mpn="STM32", value="MCU") nets = {n: _net(n, ("U5", p), ("U4", p)) for p, n in pins.items()} return DesignGraph(components={"U5": u_mem, "U4": u_cpu}, nets=nets) def _cons(mpn: str, rules: list[dict]) -> ComponentConstraints: return ComponentConstraints( mpn=mpn, pintable=[], absolute_maximum_ratings=[], rules=[], layout_rules=rules, ) def _l2_for(graph: DesignGraph, logical_sub: str, **kwargs): insts = recognize_physical_buses(graph) by, findings = certify_l2(graph, insts, **kwargs) rows = [] for inst in insts: if logical_sub in inst.logical_protocol_id: rows.extend(by.get(inst.instance_id, [])) return rows, findings, insts def _cite() -> ConstraintSource: return ConstraintSource(document="USB 2.0 spec", organization="USB-IF", section="7.1.1") def test_usb_z_missing_source_never_invents_90(): zrep = {"nets": [ {"net_name": "USB_D+", "z0_avg_ohms": 45.0}, {"net_name": "USB_D-", "z0_avg_ohms": 46.0}, ]} rows, findings, _ = _l2_for(_usb_connected(), "usb2", impedance_nets=zrep) z = [r for r in rows if r.check == "differential_impedance"] assert z assert z[0].result in {"MISSING_SOURCE", "UNKNOWN"} assert z[0].result != "PASS" blob = json.dumps(z[0].model_dump()) assert "90" not in blob assert any(f.rule_id == "PE-PRT-L2-001" for f in findings) assert any("non certificata a L2" in (f.finding or "") for f in findings) def test_usb_z_skip_when_no_stackup_measurement(): rows, findings, _ = _l2_for(_usb_connected(), "usb2") z = [r for r in rows if r.check == "differential_impedance"] assert z assert z[0].result in {"MISSING_SOURCE", "UNKNOWN"} assert z[0].measured_ohm is None assert any("mancanza di Z" in (f.finding or "") for f in findings) def test_cited_pack_z_pass_and_fail(): graph = _usb_connected() insts = recognize_physical_buses(graph) usb = [i for i in insts if "usb" in i.logical_protocol_id][0] iface = PhysicalInterface( id="usb2-hs-dpair", logical_protocol_id="usb2-hs", bus_type="SERIAL", physical_layer="SERIAL_DIFFERENTIAL", pcb_relevant="YES", required_checks=["differential_impedance"], constraints=[ProtocolConstraint( id="usb-z", parameter="differential_impedance", value_kind="NUMERIC", mandatory="MANDATORY", source_type="STANDARD", source_class="NORMATIVE", value=90.0, unit="ohm", source=_cite(), limit_kind="STANDARD", )], ) zrep = {"nets": [{"net_name": "USB_D+", "zdiff_ohm": 90.0}]} ok = certify_instance_l2(graph, usb, iface, impedance_nets=zrep) assert ok[0].result == "PASS" assert ok[0].measured_ohm == 90.0 bad = certify_instance_l2( graph, usb, iface, impedance_nets={"nets": [{"net_name": "USB_D+", "zdiff_ohm": 40.0}]}, ) assert bad[0].result == "FAIL" def test_silicon_cross_phy_subset(): assert intersect_windows([(80.0, 100.0), (85.0, 95.0)]) == (85.0, 95.0) graph = _usb_connected() cmap = { "CH340E": _cons("CH340E", [{ "kind": "impedance", "z_min_ohm": 80, "z_max_ohm": 100, "note": "CH340E USB", "source_page": 12, }]), } # second PHY on the same nets extra = _ic("U9", {"1": "USB_D+", "2": "USB_D-"}, mpn="USBPHY1") graph.components["U9"] = extra cmap["USBPHY1"] = _cons("USBPHY1", [{ "kind": "zdiff", "z_min_ohm": 85, "z_max_ohm": 95, "note": "PHY subset", "source_page": 3, }]) zrep = {"nets": [{"net_name": "USB_D+", "zdiff_ohm": 90.0}]} rows, _, _ = _l2_for(graph, "usb2", constraints_map=cmap, impedance_nets=zrep) z = [r for r in rows if r.check == "differential_impedance"] assert z assert z[0].result == "PASS" assert z[0].limit_ohm_min == 85.0 assert z[0].limit_ohm_max == 95.0 zrep_lo = {"nets": [{"net_name": "USB_D+", "zdiff_ohm": 70.0}]} rows_lo, findings, _ = _l2_for( graph, "usb2", constraints_map=cmap, impedance_nets=zrep_lo, ) zlo = [r for r in rows_lo if r.check == "differential_impedance"] assert zlo[0].result == "FAIL" assert any(f.rule_id == "PE-PRT-L2-002" for f in findings) def test_axi_internal_l2_not_applicable(): rows, _, _ = _l2_for(_axi_graph(), "axi4") assert rows assert all(r.result == "NOT_APPLICABLE" for r in rows) sec, findings = protocol_exam(_axi_graph()) assert sec.max_level_reached == "L2" assert sec.macrophase == "M8" assert all(f.status != "ERROR" for f in findings) blob = json.dumps([r.model_dump() for r in rows]) assert "L0/L1 are not electrical" in blob def test_ddr_impedance_phy_dependent_not_invented_ohm(): rows, findings, _ = _l2_for(_ddr_graph(), "ddr4") z = [r for r in rows if r.check == "impedance"] assert z assert z[0].result in {"PHY_DEPENDENT", "UNKNOWN", "VENDOR_DEPENDENT"} assert z[0].result != "PASS" blob = json.dumps(z[0].model_dump()) assert "40" not in blob assert "90" not in blob assert any(f.rule_id == "PE-PRT-L2-001" for f in findings) def test_length_is_not_delay_timing_unknown(): cons = ProtocolConstraint( id="t", parameter="timing", value_kind="NUMERIC", mandatory="MANDATORY", source_type="STANDARD", source_class="NORMATIVE", value=100.0, unit="ps", source=_cite(), ) assert electrical_verdict(cons, 12.0, None) == "UNKNOWN" def test_recommended_never_fail_l2(): rec = _pack("return_path", "FAIL", "RECOMMENDED", notes="weak") assert rec.result != "FAIL" assert rec.status != "ERROR" def test_l2_does_not_run_l3_or_geometry(): rows, _, _ = _l2_for(_usb_connected(), "usb2") assert not any(r.check in {"insertion_loss", "return_loss", "length", "intra_pair_skew"} for r in rows) blob = json.dumps([r.model_dump() for r in rows]) assert "openems" not in blob.lower() def test_protocol_exam_attaches_l2_checks(): sec, _ = protocol_exam(_usb_connected()) assert sec.max_level_reached == "L2" row = sec.recognized_instances[0] assert row.get("l2_checks") assert all(c.get("level") == "L2" for c in row["l2_checks"]) assert row.get("l1_checks") is not None assert row.get("l0_checks") is not None