|
|
|
@@ -0,0 +1,222 @@
|
|
|
|
|
"""M5 protocol report: measured/limit/margin/source/method, chain, FAIL first, skips visible."""
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
import json
|
|
|
|
|
|
|
|
|
|
from backend.periscopex.models import (
|
|
|
|
|
Component,
|
|
|
|
|
ComponentType,
|
|
|
|
|
DesignGraph,
|
|
|
|
|
Net,
|
|
|
|
|
NetType,
|
|
|
|
|
PinConnection,
|
|
|
|
|
)
|
|
|
|
|
from backend.periscopex.protocol_catalog import ProtocolCatalogError, parse_catalog
|
|
|
|
|
from backend.periscopex.protocol_l0 import protocol_exam
|
|
|
|
|
from backend.periscopex.protocol_report import (
|
|
|
|
|
instance_worst_result,
|
|
|
|
|
result_rank,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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_no_grouping() -> DesignGraph:
|
|
|
|
|
pins = {str(i): f"MEM_DAT{i}" for i in range(8)}
|
|
|
|
|
return DesignGraph(
|
|
|
|
|
components={"U5": _ic("U5", pins, mpn="MT41K256M16TW", value="DDR4")},
|
|
|
|
|
nets={n: _net(n, ("U5", p)) for p, n in pins.items()},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_result_rank_fail_before_warning():
|
|
|
|
|
assert result_rank("FAIL") < result_rank("WARNING")
|
|
|
|
|
assert result_rank("FAIL") < result_rank("UNKNOWN")
|
|
|
|
|
assert instance_worst_result(["UNKNOWN", "FAIL", "PASS"]) == "FAIL"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_usb2_pair_l0_pass_l2_z_unknown_no_invented_ohm():
|
|
|
|
|
sec, _ = protocol_exam(_usb_connected())
|
|
|
|
|
assert sec.max_level_reached == "L2"
|
|
|
|
|
assert sec.macrophase == "M5"
|
|
|
|
|
usb = next(
|
|
|
|
|
r for r in sec.recognized_instances
|
|
|
|
|
if "usb" in str(r.get("logical_protocol_id"))
|
|
|
|
|
)
|
|
|
|
|
checks = usb["report_checks"]
|
|
|
|
|
l0_topo = [c for c in checks if c["level"] == "L0" and c["check"] == "topology"]
|
|
|
|
|
assert l0_topo and l0_topo[0]["result"] == "PASS"
|
|
|
|
|
z = [c for c in checks if c["check"] == "differential_impedance"]
|
|
|
|
|
assert z
|
|
|
|
|
assert z[0]["result"] in {"MISSING_SOURCE", "UNKNOWN"}
|
|
|
|
|
assert z[0]["skip_visible"] is True
|
|
|
|
|
assert z[0]["measured"] is None
|
|
|
|
|
assert z[0]["limit"] is None
|
|
|
|
|
blob = json.dumps(z[0])
|
|
|
|
|
assert "90" not in blob
|
|
|
|
|
chain = z[0]["chain"]
|
|
|
|
|
assert chain["physical_interface_id"]
|
|
|
|
|
assert chain["logical_protocol_id"]
|
|
|
|
|
assert chain["physical_interface_id"] != chain["logical_protocol_id"]
|
|
|
|
|
l3 = [c for c in checks if c["level"] == "L3"]
|
|
|
|
|
assert l3 and l3[0]["skip_visible"]
|
|
|
|
|
assert l3[0]["result"] != "PASS"
|
|
|
|
|
assert "OpenEMS" in l3[0]["notes"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_fail_listed_before_warning_in_report_checks():
|
|
|
|
|
sec, findings = protocol_exam(_usb_dangling_minus())
|
|
|
|
|
usb = next(
|
|
|
|
|
r for r in sec.recognized_instances
|
|
|
|
|
if "usb" in str(r.get("logical_protocol_id"))
|
|
|
|
|
)
|
|
|
|
|
assert usb["worst_result"] == "FAIL"
|
|
|
|
|
assert usb["fail_count"] >= 1
|
|
|
|
|
results = [c["result"] for c in usb["report_checks"]]
|
|
|
|
|
assert "FAIL" in results
|
|
|
|
|
assert results.index("FAIL") < next(
|
|
|
|
|
i for i, r in enumerate(results) if r in {"UNKNOWN", "MISSING_SOURCE", "WARNING"}
|
|
|
|
|
)
|
|
|
|
|
assert any(f.status == "ERROR" and f.rule_id == "PE-PRT-L0-001" for f in findings)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_axi_na_visible_not_fail():
|
|
|
|
|
sec, _ = protocol_exam(_axi_graph())
|
|
|
|
|
row = next(
|
|
|
|
|
r for r in sec.recognized_instances
|
|
|
|
|
if "axi" in str(r.get("logical_protocol_id"))
|
|
|
|
|
)
|
|
|
|
|
checks = row["report_checks"]
|
|
|
|
|
assert any(c["result"] == "NOT_APPLICABLE" for c in checks)
|
|
|
|
|
assert row["worst_result"] != "FAIL"
|
|
|
|
|
assert all(c["result"] != "FAIL" for c in checks if c["level"] != "L3")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_ddr_grouping_skip_visible_not_pass():
|
|
|
|
|
sec, findings = protocol_exam(_ddr_no_grouping())
|
|
|
|
|
ddr = next(
|
|
|
|
|
r for r in sec.recognized_instances
|
|
|
|
|
if str(r.get("logical_protocol_id")).startswith("ddr")
|
|
|
|
|
)
|
|
|
|
|
grouping = [
|
|
|
|
|
c for c in ddr["report_checks"]
|
|
|
|
|
if c["check"] in {"byte_lane_mapping", "dq_to_dqs_skew", "byte_lane_skew"}
|
|
|
|
|
]
|
|
|
|
|
assert grouping
|
|
|
|
|
assert all(c["result"] != "PASS" for c in grouping)
|
|
|
|
|
assert ddr["worst_result"] == "FAIL" or any(c["skip_visible"] for c in grouping)
|
|
|
|
|
notes = " ".join((c.get("notes") or "") for c in grouping).lower()
|
|
|
|
|
assert "byte-lane" in notes or "grouping" in notes or "geometria" in notes or "dq" in notes
|
|
|
|
|
assert any(
|
|
|
|
|
f.rule_id in {"PE-PRT-L0-001", "PE-PRT-L1-002", "PE-PRT-L1-001"}
|
|
|
|
|
for f in findings
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_report_check_has_measured_limit_margin_source_method_keys():
|
|
|
|
|
sec, _ = protocol_exam(_usb_connected())
|
|
|
|
|
usb = sec.recognized_instances[0]
|
|
|
|
|
row = usb["report_checks"][0]
|
|
|
|
|
for key in (
|
|
|
|
|
"measured", "limit", "margin", "source", "method",
|
|
|
|
|
"chain", "skip_visible", "result", "level",
|
|
|
|
|
):
|
|
|
|
|
assert key in row
|
|
|
|
|
chain = row["chain"]
|
|
|
|
|
for key in (
|
|
|
|
|
"net", "group", "physical_interface_id", "logical_protocol_id",
|
|
|
|
|
"constraint_id", "document", "section",
|
|
|
|
|
):
|
|
|
|
|
assert key in chain
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_typical_still_rejected_as_standard():
|
|
|
|
|
raw = {
|
|
|
|
|
"schema_version": "1.0.0",
|
|
|
|
|
"logical_protocols": [{
|
|
|
|
|
"id": "usb2-hs", "name": "USB2 HS", "bus_type": "SERIAL",
|
|
|
|
|
}],
|
|
|
|
|
"physical_interfaces": [{
|
|
|
|
|
"id": "usb2-hs-dpair",
|
|
|
|
|
"logical_protocol_id": "usb2-hs",
|
|
|
|
|
"bus_type": "SERIAL",
|
|
|
|
|
"physical_layer": "SERIAL_DIFFERENTIAL",
|
|
|
|
|
"pcb_relevant": "YES",
|
|
|
|
|
"required_checks": ["differential_impedance"],
|
|
|
|
|
"constraints": [{
|
|
|
|
|
"id": "bad",
|
|
|
|
|
"parameter": "differential_impedance",
|
|
|
|
|
"value_kind": "NUMERIC",
|
|
|
|
|
"mandatory": "MANDATORY",
|
|
|
|
|
"source_type": "STANDARD",
|
|
|
|
|
"source_class": "NORMATIVE",
|
|
|
|
|
"value": 90,
|
|
|
|
|
"unit": "ohm",
|
|
|
|
|
"typical": True,
|
|
|
|
|
"source": {"document": "blog", "organization": "web"},
|
|
|
|
|
}],
|
|
|
|
|
}],
|
|
|
|
|
}
|
|
|
|
|
try:
|
|
|
|
|
parse_catalog(raw)
|
|
|
|
|
raise AssertionError("typical-as-standard must be rejected")
|
|
|
|
|
except ProtocolCatalogError:
|
|
|
|
|
pass
|