Files
periscope/tests/pcb/test_protocol_m6_m7.py
T
michele f3be485b75 Fill protocol packs from recovered USB CTS, Type-C FTS, DVI 1.0, and IEEE 802.3-2012 Section Two (2.74.0).
NUMERIC rows cite document title, revision, section/table, and page. USB Zdiff, Type-C Rp/Rd, 10BASE-T, and 1000BASE-T stay UNKNOWN. PDFs are not in git.
2026-09-22 14:43:26 +02:00

231 lines
8.4 KiB
Python

"""M6/M7 USB-C/Ethernet/HDMI packs: UNKNOWN + needed_document, no invented ohms."""
from __future__ import annotations
import copy
import json
import pytest
from backend.periscopex.models import (
Component,
ComponentType,
DesignGraph,
Net,
NetType,
PinConnection,
)
from backend.periscopex.protocol_catalog import (
ProtocolCatalogError,
build_m0_catalog,
load_catalog,
parse_catalog,
)
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 test_unknown_requires_needed_document():
raw = build_m0_catalog()
iface = copy.deepcopy(raw["physical_interfaces"][0])
iface["id"] = "usb2-hs-no-doc"
bad = copy.deepcopy(iface["constraints"][0])
bad["id"] = "no-doc"
bad["value_kind"] = "UNKNOWN"
bad["value"] = None
bad["needed_document"] = None
iface["constraints"] = [bad]
raw["physical_interfaces"].append(iface)
with pytest.raises(ProtocolCatalogError, match="needed_document"):
parse_catalog(raw)
def test_reject_typical_as_standard_still_holds():
raw = build_m0_catalog()
iface = copy.deepcopy(raw["physical_interfaces"][0])
typical = copy.deepcopy(iface["constraints"][0])
typical["id"] = "bad-typical-m67"
typical["typical"] = True
typical["value_origin"] = "TYPICAL"
typical["source_type"] = "STANDARD"
typical["source_class"] = "NORMATIVE"
typical["mandatory"] = "MANDATORY"
typical["value_kind"] = "UNKNOWN"
typical["value"] = None
typical["needed_document"] = "USB-IF Base Specification"
iface["id"] = "usb2-hs-typical-m67"
iface["constraints"] = [typical]
raw["physical_interfaces"].append(iface)
with pytest.raises(ProtocolCatalogError, match="typical"):
parse_catalog(raw)
def test_usb2_and_usb_c_unknown_with_usbif_needed_document_no_90ohm():
cat = load_catalog()
logical = {p.id for p in cat.logical_protocols}
assert {"usb2-ls", "usb2-fs", "usb2-hs", "usb-c", "usb3-x", "usb4"} <= logical
assert "mini-hdmi" not in logical
usb_ifaces = [
p for p in cat.physical_interfaces
if p.logical_protocol_id in {"usb2-ls", "usb2-fs", "usb2-hs", "usb2-ls-fs", "usb-c", "usb3-x", "usb4"}
or p.id.startswith("usb-")
]
assert usb_ifaces
z_blob = json.dumps([
c.model_dump() for p in usb_ifaces for c in p.constraints
if c.parameter == "differential_impedance"
])
assert "90" not in z_blob
for p in usb_ifaces:
for c in p.constraints:
if c.parameter == "differential_impedance":
assert c.value is None
assert c.value_kind != "NUMERIC"
if c.value_kind in {"UNKNOWN", "MISSING_SOURCE"}:
assert c.needed_document
assert "USB-IF" in c.needed_document or "specification" in c.needed_document.lower() or "Specification" in c.needed_document
if p.logical_protocol_id == "usb-c" and c.parameter in {"rp", "rd"}:
assert c.value_kind == "UNKNOWN"
over = next(p for p in cat.physical_interfaces if p.id == "usb-c-usb2-receptacle")
assert over.logical_protocol_id == "usb2-hs"
assert over.connector == "Type-C"
sys = next(p for p in cat.physical_interfaces if p.id == "usb-c-receptacle")
assert sys.logical_protocol_id == "usb-c"
assert sys.connector == "Type-C"
def test_usb_c_connector_alone_is_not_usb3_or_usb2_protocol():
g = DesignGraph(
components={
"J1": Component(
reference="J1", value="USB Type-C receptacle",
footprint="USB_C_Receptacle",
component_type=ComponentType.CONNECTOR,
pins={"A5": "CC1"},
),
},
nets={"CC1": _net("CC1", ("J1", "A5"))},
)
insts = recognize_physical_buses(g)
logs = {i.logical_protocol_id for i in insts}
assert "usb3-x" not in logs
assert "usb4" not in logs
assert "usb2-hs" not in logs
assert any(i.logical_protocol_id == "usb-c" for i in insts)
blob = json.dumps([i.model_dump() for i in insts])
assert "90" not in blob
def test_usb2_over_type_c_from_connector_nets_silicon_not_usb3():
g = 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")),
},
)
insts = recognize_physical_buses(g)
usb = [i for i in insts if i.logical_protocol_id.startswith("usb2")]
assert usb
one = usb[0]
assert one.connector == "Type-C"
assert "USB 2.0 over USB Type-C" in one.physical_implementation
assert one.logical_protocol_id != "usb3-x"
assert all(i.logical_protocol_id != "usb3-x" for i in insts)
assert "mux not assumed" in one.notes.lower() or "mux/switch not assumed" in one.notes.lower()
blob = json.dumps(one.model_dump())
assert "90" not in blob
def test_rj45_alone_is_not_1000base_t():
g = DesignGraph(
components={
"J9": Component(
reference="J9", value="RJ45",
footprint="RJ45",
component_type=ComponentType.CONNECTOR,
pins={"1": "TD+"},
),
},
nets={"TD+": _net("TD+", ("J9", "1"))},
)
insts = recognize_physical_buses(g)
logs = {i.logical_protocol_id for i in insts}
assert "1000base-t" not in logs
assert "100base-tx" not in logs
assert any(i.logical_protocol_id == "ethernet-unspecified" for i in insts)
def test_mini_hdmi_is_connector_not_protocol_id():
cat = load_catalog()
logical = {p.id for p in cat.logical_protocols}
assert "mini-hdmi" not in logical
assert "hdmi-mini" not in logical
mini = next(p for p in cat.physical_interfaces if p.id == "hdmi-1.4-tmds-type-c-mini")
assert mini.logical_protocol_id == "hdmi-1.4"
assert mini.connector == "HDMI-C-mini"
assert "CONNECTOR" in mini.notes or "connector" in mini.notes.lower()
g = DesignGraph(
components={
"J8": Component(
reference="J8", value="Mini HDMI",
footprint="HDMI_C_Mini",
component_type=ComponentType.CONNECTOR,
pins={"1": "HPD"},
),
},
nets={"HPD": _net("HPD", ("J8", "1"))},
)
insts = recognize_physical_buses(g)
assert all(i.logical_protocol_id != "mini-hdmi" for i in insts)
hdmi = [i for i in insts if "hdmi" in i.logical_protocol_id]
assert hdmi
assert hdmi[0].connector == "HDMI-C-mini"
assert hdmi[0].logical_protocol_id.startswith("hdmi-")
def test_ethernet_hdmi_skeletons_unknown_needed_document():
cat = load_catalog()
for lid in (
"10base-t", "1000base-t", "2.5gbase-t", "5gbase-t",
"10gbase-t", "sgmii", "1000base-x", "10gbase-r",
"hdmi-1.4", "hdmi-2.0", "hdmi-frl",
):
ifaces = [p for p in cat.physical_interfaces if p.logical_protocol_id == lid]
assert ifaces, lid
assert all(p.incomplete for p in ifaces)
for p in ifaces:
for c in p.constraints:
assert c.value is None
assert c.value_kind != "NUMERIC"
if c.value_kind in {"UNKNOWN", "MISSING_SOURCE"}:
assert c.needed_document
if lid.startswith("hdmi"):
assert "HDMI" in c.needed_document
else:
assert "IEEE" in c.needed_document or "PHY" in (c.needed_document or "") or "SGMII" in (c.needed_document or "")
tmds = next(p for p in cat.physical_interfaces if p.id == "hdmi-1.4-tmds-type-a")
frl = next(p for p in cat.physical_interfaces if p.id == "hdmi-frl-pcb")
assert tmds.logical_protocol_id != frl.logical_protocol_id