"""M1 physical_bus_instance recognition: USB/DDR fixtures; AXI has no PCB nets.""" from __future__ import annotations import json from backend.periscopex.models import ( Component, ComponentType, DesignGraph, Net, NetType, PinConnection, ) from backend.periscopex.protocol_recognize import ( protocol_section_for_graph, 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_graph() -> DesignGraph: return DesignGraph( components={ "U1": _ic("U1", {"1": "USB_D+", "2": "USB_D-"}, mpn="CH340E"), "J2": Component( reference="J2", value="USB_C_Receptacle_USB2.0_16P", footprint="USB_C_Receptacle_USB2.0_16P", component_type=ComponentType.CONNECTOR, pins={"A6": "USB_D+", "A7": "USB_D-"}, ), }, nets={ "USB_D+": _net("USB_D+", ("U1", "1"), ("J2", "A6")), "USB_D-": _net("USB_D-", ("U1", "2"), ("J2", "A7")), }, ) 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", "10": "DDR4_DM0", "11": "DDR4_CK_P", "12": "DDR4_CK_N", "13": "DDR4_A0", "14": "DDR4_RAS", }) u_mem = _ic("U5", pins, mpn="MT41K256M16TW", value="DDR4") nets = {n: _net(n, ("U5", p)) for p, n in pins.items()} return DesignGraph(components={"U5": u_mem}, nets=nets) def _axi_graph() -> DesignGraph: pins = { "1": "AXI_AWVALID", "2": "AXI_WDATA", "3": "AXI_RDATA", "4": "VCCINT", } return DesignGraph( components={ "U3": _ic("U3", pins, mpn="XC7A100T", value="AXI4 interconnect"), }, nets={ "AXI_AWVALID": _net("AXI_AWVALID", ("U3", "1")), "AXI_WDATA": _net("AXI_WDATA", ("U3", "2")), "AXI_RDATA": _net("AXI_RDATA", ("U3", "3")), "VCCINT": _net("VCCINT", ("U3", "4")), }, ) def test_usb_net_names_make_diff_pair_instance(): insts = recognize_physical_buses(_usb_graph()) usb = [i for i in insts if "usb" in i.logical_protocol_id] assert usb, insts one = usb[0] assert one.physical_interface_id != one.logical_protocol_id assert one.pcb_relevant in {"YES", "CONDITIONAL"} pairs = [g for g in one.groups if g.kind == "DIFFERENTIAL_PAIR"] assert pairs nets = set(pairs[0].nets) assert "USB_D+" in nets and "USB_D-" in nets assert one.recognition_status != "FAIL" assert one.evidence_kind in {"part", "net_name", "silicon"} def test_usb_speed_ambiguity_is_review_not_fail(): insts = recognize_physical_buses(_usb_graph()) usb = [i for i in insts if "usb" in i.logical_protocol_id][0] assert usb.recognition_status == "REVIEW" assert "FAIL" not in usb.recognition_status assert usb.ambiguous_logical_ids def test_ddr_net_names_rebuild_byte_lane(): insts = recognize_physical_buses(_ddr_graph()) ddr = [i for i in insts if i.logical_protocol_id.startswith("ddr")] assert ddr one = ddr[0] assert one.physical_interface_id == "ddr4-sdram-pcb" assert one.logical_protocol_id == "ddr4" lanes = [g for g in one.groups if g.kind == "BYTE_LANE"] assert lanes assert "DDR4_DQ0" in lanes[0].nets assert "DDR4_DQS0_P" in lanes[0].nets clocks = [g for g in one.groups if g.kind == "CLOCK_GROUP"] assert clocks assert one.recognition_status != "FAIL" blob = json.dumps(one.model_dump()) assert "90" not in blob assert "ohm" not in blob.lower() def test_axi4_internal_does_not_create_pcb_nets(): insts = recognize_physical_buses(_axi_graph()) axi = [i for i in insts if i.logical_protocol_id == "axi4"] assert axi one = axi[0] assert one.physical_interface_id == "axi4-internal" assert one.pcb_relevant == "NO" assert one.nets == [] assert one.groups == [] assert one.recognition_status == "NOT_APPLICABLE" assert "AXI_AWVALID" not in one.nets assert "AXI_WDATA" not in one.nets def test_empty_graph_keeps_nessun_protocollo(): sec = protocol_section_for_graph(DesignGraph()) assert sec.message == "nessun protocollo riconosciuto" assert sec.recognized_instances == [] blob = json.dumps(sec.model_dump()) assert "ohm" not in blob.lower() def test_report_section_lists_usb_without_z(): sec = protocol_section_for_graph(_usb_graph()) assert sec.recognized_instances blob = json.dumps(sec.model_dump()) assert "90" not in blob assert "Zdiff" not in blob assert all(row.get("recognition_status") != "FAIL" for row in sec.recognized_instances)