L3 CHANNEL structure for timing budget, insertion loss, return loss, crosstalk, and complete channel. Numeric PASS/FAIL only from existing channel FACT; otherwise a visible skip. No OpenEMS, no invented S-parameters, no M6 packs.
317 lines
11 KiB
Python
317 lines
11 KiB
Python
"""M9 L3 CHANNEL certifier: structure + skip; numeric only with existing FACT."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
|
|
from backend.periscopex.models import (
|
|
Component,
|
|
ComponentType,
|
|
DesignGraph,
|
|
Net,
|
|
NetType,
|
|
PinConnection,
|
|
)
|
|
from backend.periscopex.protocol_catalog import (
|
|
ConstraintSource,
|
|
PhysicalInterface,
|
|
ProtocolConstraint,
|
|
)
|
|
from backend.periscopex.protocol_l0 import protocol_exam
|
|
from backend.periscopex.protocol_l3 import (
|
|
_pack,
|
|
certify_instance_l3,
|
|
certify_l3,
|
|
remaining_margin_ps,
|
|
)
|
|
from backend.periscopex.protocol_recognize import (
|
|
PhysicalBusInstance,
|
|
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 _hdmi_graph() -> DesignGraph:
|
|
pins = {"1": "HDMI_TX0_P", "2": "HDMI_TX0_N", "3": "HDMI_CLK_P", "4": "HDMI_CLK_N"}
|
|
return DesignGraph(
|
|
components={
|
|
"U8": _ic("U8", pins, mpn="ADV7511", value="HDMI transmitter"),
|
|
"J8": Component(
|
|
reference="J8", value="HDMI-A",
|
|
footprint="HDMI",
|
|
component_type=ComponentType.CONNECTOR,
|
|
pins=pins,
|
|
),
|
|
},
|
|
nets={n: _net(n, ("U8", p), ("J8", p)) for p, n in pins.items()},
|
|
schematic_fields={"J8": {"protocol": "hdmi-1.4-tmds-type-a"}},
|
|
)
|
|
|
|
|
|
def _pcie_graph() -> DesignGraph:
|
|
pins = {"1": "PCIE_TX_P", "2": "PCIE_TX_N", "3": "PCIE_RX_P", "4": "PCIE_RX_N"}
|
|
return DesignGraph(
|
|
components={
|
|
"U7": _ic("U7", pins, mpn="PI7C9X2G304GP", value="PCIe switch"),
|
|
"J7": Component(
|
|
reference="J7", value="PCIe x1",
|
|
footprint="PCIE",
|
|
component_type=ComponentType.CONNECTOR,
|
|
pins=pins,
|
|
),
|
|
},
|
|
nets={n: _net(n, ("U7", p), ("J7", p)) for p, n in pins.items()},
|
|
schematic_fields={"U7": {"protocol": "pcie-phy-pcb"}},
|
|
)
|
|
|
|
|
|
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 _cite() -> ConstraintSource:
|
|
return ConstraintSource(
|
|
document="Fixture channel pack", organization="TESTLAB", section="4.1",
|
|
)
|
|
|
|
|
|
def _iface_with_numeric(*, il_db: float = -8.0, rl_db: float = -10.0,
|
|
margin_ps: float = 0.0) -> PhysicalInterface:
|
|
return PhysicalInterface(
|
|
id="fixture-hs-dpair",
|
|
logical_protocol_id="fixture-hs",
|
|
bus_type="SERIAL",
|
|
physical_layer="SERIAL_DIFFERENTIAL",
|
|
pcb_relevant="YES",
|
|
required_checks=[
|
|
"insertion_loss", "return_loss", "crosstalk",
|
|
"timing_budget", "channel",
|
|
],
|
|
constraints=[
|
|
ProtocolConstraint(
|
|
id="fixture-il", parameter="insertion_loss",
|
|
value_kind="NUMERIC", mandatory="MANDATORY",
|
|
source_type="STANDARD", source_class="NORMATIVE",
|
|
value=il_db, unit="dB", source=_cite(),
|
|
),
|
|
ProtocolConstraint(
|
|
id="fixture-rl", parameter="return_loss",
|
|
value_kind="NUMERIC", mandatory="MANDATORY",
|
|
source_type="STANDARD", source_class="NORMATIVE",
|
|
value=rl_db, unit="dB", source=_cite(),
|
|
),
|
|
ProtocolConstraint(
|
|
id="fixture-xt", parameter="crosstalk",
|
|
value_kind="NUMERIC", mandatory="RECOMMENDED",
|
|
source_type="STANDARD", source_class="NORMATIVE",
|
|
value=-20.0, unit="dB", source=_cite(),
|
|
),
|
|
ProtocolConstraint(
|
|
id="fixture-tb", parameter="timing_budget",
|
|
value_kind="NUMERIC", mandatory="MANDATORY",
|
|
source_type="STANDARD", source_class="NORMATIVE",
|
|
value=margin_ps, unit="ps", source=_cite(),
|
|
),
|
|
],
|
|
)
|
|
|
|
|
|
def _inst() -> PhysicalBusInstance:
|
|
return PhysicalBusInstance(
|
|
instance_id="fixture-1",
|
|
logical_protocol_id="fixture-hs",
|
|
physical_interface_id="fixture-hs-dpair",
|
|
pcb_relevant="YES",
|
|
confidence=1.0,
|
|
evidence_kind="net_name",
|
|
recognition_status="RECOGNIZED",
|
|
nets=["CH_P", "CH_N"],
|
|
host_ref="U1",
|
|
)
|
|
|
|
|
|
def test_remaining_margin_is_total_minus_used():
|
|
assert remaining_margin_ps(200.0, 120.0) == 80.0
|
|
assert remaining_margin_ps(200.0, 120.0, 77.0) == 77.0
|
|
assert remaining_margin_ps(None, 10.0) is None
|
|
|
|
|
|
def test_usb_hdmi_pcie_skip_without_channel_data():
|
|
for graph, token in ((_usb_connected(), "usb"), (_hdmi_graph(), "hdmi"), (_pcie_graph(), "pcie")):
|
|
sec, findings = protocol_exam(graph)
|
|
assert sec.max_level_reached == "L3"
|
|
assert sec.macrophase == "M9"
|
|
row = next(
|
|
r for r in sec.recognized_instances
|
|
if token in str(r.get("logical_protocol_id")).lower()
|
|
or token in str(r.get("physical_interface_id")).lower()
|
|
)
|
|
l3 = row["l3_checks"]
|
|
assert l3
|
|
names = {c["check"] for c in l3}
|
|
assert {"timing_budget", "insertion_loss", "return_loss", "crosstalk", "channel"} <= names
|
|
assert all(c["result"] != "PASS" for c in l3)
|
|
notes = " ".join(c.get("notes") or "" for c in l3)
|
|
assert "non certificata a L3 per mancanza" in notes
|
|
assert "OpenEMS" in notes
|
|
blob = json.dumps(l3)
|
|
assert "90" not in blob
|
|
assert any(f.rule_id == "PE-PRT-L3-001" for f in findings)
|
|
assert "s21" not in blob.lower() or "invent" in notes.lower()
|
|
|
|
|
|
def test_axi_l3_not_applicable_not_fail():
|
|
sec, findings = protocol_exam(_axi_graph())
|
|
row = next(
|
|
r for r in sec.recognized_instances
|
|
if "axi" in str(r.get("logical_protocol_id"))
|
|
)
|
|
l3 = row["l3_checks"]
|
|
assert l3
|
|
assert all(c["result"] == "NOT_APPLICABLE" for c in l3)
|
|
assert all(c["result"] != "FAIL" for c in l3)
|
|
assert all(f.rule_id != "PE-PRT-L3-002" for f in findings)
|
|
assert all(f.status != "ERROR" or f.source != "protocol_l3" for f in findings)
|
|
|
|
|
|
def test_fixture_channel_pass_with_existing_fact():
|
|
iface = _iface_with_numeric()
|
|
inst = _inst()
|
|
data = {
|
|
"instances": {
|
|
inst.instance_id: {
|
|
"insertion_loss_db": -3.0,
|
|
"return_loss_db": -15.0,
|
|
"crosstalk_db": -28.0,
|
|
"total_budget_ps": 200.0,
|
|
"used_budget_ps": 120.0,
|
|
"channel_complete": True,
|
|
}
|
|
}
|
|
}
|
|
rows = certify_instance_l3(inst, iface, channel_data=data)
|
|
by_check = {r.check: r for r in rows}
|
|
assert by_check["insertion_loss"].result == "PASS"
|
|
assert by_check["return_loss"].result == "PASS"
|
|
assert by_check["timing_budget"].result == "PASS"
|
|
assert by_check["timing_budget"].remaining_margin_ps == 80.0
|
|
assert by_check["channel"].result == "PASS"
|
|
assert by_check["insertion_loss"].measured_db == -3.0
|
|
notes = " ".join(r.notes.lower() for r in rows)
|
|
assert "not invented" in notes
|
|
assert "not openems" in notes
|
|
|
|
|
|
def test_fixture_channel_fail_mandatory_il():
|
|
iface = _iface_with_numeric()
|
|
inst = _inst()
|
|
data = {
|
|
inst.instance_id: {
|
|
"s21_db": -12.0,
|
|
"s11_db": -15.0,
|
|
"total_budget_ps": 200.0,
|
|
"used_budget_ps": 50.0,
|
|
}
|
|
}
|
|
rows = certify_instance_l3(inst, iface, channel_data=data)
|
|
il = next(r for r in rows if r.check == "insertion_loss")
|
|
assert il.result == "FAIL"
|
|
findings = []
|
|
from backend.periscopex.protocol_l3 import l3_findings
|
|
findings = l3_findings(inst, rows)
|
|
assert any(f.rule_id == "PE-PRT-L3-002" and f.status == "ERROR" for f in findings)
|
|
|
|
|
|
def test_recommended_never_fail_l3():
|
|
rec = _pack("crosstalk", "FAIL", "RECOMMENDED", notes="xt")
|
|
assert rec.result != "FAIL"
|
|
assert rec.status != "ERROR"
|
|
|
|
|
|
def test_raw_touchstone_without_fact_is_skip_not_invented():
|
|
iface = _iface_with_numeric()
|
|
inst = _inst()
|
|
data = {inst.instance_id: {"sparam_file": "lane.s4p", "touchstone": "lane.s4p"}}
|
|
rows = certify_instance_l3(inst, iface, channel_data=data)
|
|
assert all(r.result != "PASS" for r in rows)
|
|
notes = " ".join(r.notes for r in rows)
|
|
assert "non certificata a L3" in notes
|
|
assert "invent" in notes.lower() or "non inventati" in notes.lower() or "not invented" in notes.lower()
|
|
blob = json.dumps([r.model_dump() for r in rows])
|
|
# no synthesized S21 numbers
|
|
assert '"measured_db": null' in blob or all(r.measured_db is None for r in rows)
|
|
|
|
|
|
def test_openems_method_is_explicit_skip():
|
|
iface = _iface_with_numeric()
|
|
inst = _inst()
|
|
data = {
|
|
inst.instance_id: {
|
|
"insertion_loss_db": -2.0,
|
|
"method": "OpenEMS",
|
|
"solver": "FEM",
|
|
}
|
|
}
|
|
rows = certify_instance_l3(inst, iface, channel_data=data)
|
|
notes = " ".join(r.notes for r in rows)
|
|
assert "non certificata a L3" in notes
|
|
assert all(r.result != "PASS" for r in rows)
|
|
|
|
|
|
def test_no_sparam_invention_on_empty_channel_data():
|
|
insts = recognize_physical_buses(_usb_connected())
|
|
by, findings = certify_l3(insts, channel_data=None)
|
|
rows = [r for lst in by.values() for r in lst]
|
|
blob = json.dumps([r.model_dump() for r in rows])
|
|
assert "s21" not in blob.lower()
|
|
assert all(r.measured_db is None for r in rows)
|
|
assert any("mancanza" in r.notes for r in rows)
|
|
assert any(f.rule_id == "PE-PRT-L3-001" for f in findings)
|
|
|
|
|
|
def test_protocol_exam_attaches_l3_and_does_not_call_l0_electrical():
|
|
sec, _ = protocol_exam(_usb_connected())
|
|
row = sec.recognized_instances[0]
|
|
assert row.get("l3_checks")
|
|
assert all(c.get("level") == "L3" for c in row["l3_checks"])
|
|
notes = " ".join(c.get("notes") or "" for c in row["l3_checks"])
|
|
assert "L0/L1 are not electrical" in notes
|
|
l0notes = json.dumps(row.get("l0_checks") or [])
|
|
assert "ohm" not in l0notes.lower() or "not" in l0notes.lower()
|