Add protocol-certification M6/M7 USB-C Ethernet HDMI packs (2.73.0).
USB 2.0 is protocol, Type-C is connector, USB 2.0 over Type-C is the implementation. Ethernet/HDMI skeletons stay UNKNOWN with needed_document. No invented ohms; Mini HDMI is connector-only; RJ45 is not 1000BASE-T.
This commit is contained in:
@@ -123,6 +123,7 @@ class ProtocolConstraint(BaseModel):
|
||||
limit_kind: LimitKind | None = None
|
||||
value_origin: ValueOrigin = "SPEC"
|
||||
typical: bool = False
|
||||
needed_document: str | None = None
|
||||
|
||||
@field_validator("source_type")
|
||||
@classmethod
|
||||
@@ -155,6 +156,11 @@ class ProtocolConstraint(BaseModel):
|
||||
raise ProtocolCatalogError(
|
||||
f"{self.id}: non-NUMERIC value_kind cannot carry a number"
|
||||
)
|
||||
if self.value_kind in {"UNKNOWN", "MISSING_SOURCE"}:
|
||||
if not (self.needed_document or "").strip():
|
||||
raise ProtocolCatalogError(
|
||||
f"{self.id}: UNKNOWN/MISSING_SOURCE needs needed_document"
|
||||
)
|
||||
return self
|
||||
|
||||
def is_fail_mandatory(self) -> bool:
|
||||
@@ -185,6 +191,7 @@ class PhysicalInterface(BaseModel):
|
||||
required_checks: list[str] = Field(default_factory=list)
|
||||
constraints: list[ProtocolConstraint] = Field(default_factory=list)
|
||||
notes: str = ""
|
||||
incomplete: bool = False
|
||||
|
||||
@model_validator(mode="after")
|
||||
def _logical_differs_from_physical(self) -> PhysicalInterface:
|
||||
@@ -298,8 +305,11 @@ def _c(
|
||||
mandatory: MandatoryClass = "MANDATORY",
|
||||
source_type: SourceType = "STANDARD",
|
||||
source_class: SourceClass = "NORMATIVE",
|
||||
value_origin: ValueOrigin = "SPEC",
|
||||
typical: bool = False,
|
||||
conditions: list[str] | None = None,
|
||||
measurement_method: MeasurementMethod | None = None,
|
||||
needed_document: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
row: dict[str, Any] = {
|
||||
"id": f"{iface_id}-{parameter}",
|
||||
@@ -317,6 +327,13 @@ def _c(
|
||||
}
|
||||
if measurement_method:
|
||||
row["measurement_method"] = measurement_method
|
||||
if value_kind in {"UNKNOWN", "MISSING_SOURCE"}:
|
||||
row["needed_document"] = (
|
||||
needed_document
|
||||
or "Official specification not on file (cite before NUMERIC)"
|
||||
)
|
||||
elif needed_document:
|
||||
row["needed_document"] = needed_document
|
||||
return row
|
||||
|
||||
|
||||
@@ -328,6 +345,7 @@ def _skel_constraints(
|
||||
mandatory: MandatoryClass = "MANDATORY",
|
||||
source_type: SourceType = "STANDARD",
|
||||
source_class: SourceClass = "NORMATIVE",
|
||||
needed_document: str | None = None,
|
||||
) -> list[dict[str, Any]]:
|
||||
"""Named checks with no numbers. Cite later; never invent ohm/dB."""
|
||||
return [
|
||||
@@ -336,6 +354,7 @@ def _skel_constraints(
|
||||
mandatory=mandatory,
|
||||
source_type=source_type,
|
||||
source_class=source_class,
|
||||
needed_document=needed_document,
|
||||
)
|
||||
for p in parameters
|
||||
]
|
||||
@@ -353,6 +372,7 @@ def _iface(
|
||||
connector: str = "none",
|
||||
hints: list[str] | None = None,
|
||||
notes: str = "",
|
||||
incomplete: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
return {
|
||||
"id": pid,
|
||||
@@ -365,34 +385,110 @@ def _iface(
|
||||
"required_checks": checks,
|
||||
"constraints": constraints,
|
||||
"notes": notes,
|
||||
"incomplete": incomplete,
|
||||
}
|
||||
|
||||
|
||||
USB2_CHECKS = [
|
||||
"signaling",
|
||||
"data_rate",
|
||||
"electrical_levels",
|
||||
"differential_impedance",
|
||||
"impedance_tolerance",
|
||||
"rise_time",
|
||||
"fall_time",
|
||||
"intra_pair_skew",
|
||||
"max_length",
|
||||
"min_length",
|
||||
"connector_requirements",
|
||||
"termination",
|
||||
"pull_up",
|
||||
"pull_down",
|
||||
"vbus_requirements",
|
||||
"dc_resistance",
|
||||
"topology",
|
||||
"length",
|
||||
"stubs",
|
||||
"vias",
|
||||
"return_path",
|
||||
"test_method",
|
||||
"compliance_test",
|
||||
"length",
|
||||
]
|
||||
USB_C_CHECKS = [
|
||||
"rp",
|
||||
"rd",
|
||||
"ra",
|
||||
"cc",
|
||||
"vbus_requirements",
|
||||
"current_advertisement",
|
||||
"voltage",
|
||||
"timing",
|
||||
"electrical_thresholds",
|
||||
"termination",
|
||||
"connector_requirements",
|
||||
"usb2_channel_requirements",
|
||||
]
|
||||
ETH_CHECKS = [
|
||||
"data_rate",
|
||||
"signaling",
|
||||
"differential_impedance",
|
||||
"pair_skew",
|
||||
"length",
|
||||
"magnetics",
|
||||
"return_path",
|
||||
"impedance_tolerance",
|
||||
"intra_pair_skew",
|
||||
"pair_to_pair_skew",
|
||||
"maximum_channel_length",
|
||||
"insertion_loss",
|
||||
"return_loss",
|
||||
"crosstalk",
|
||||
"magnetics_requirements",
|
||||
"termination",
|
||||
"common_mode",
|
||||
"connector_requirements",
|
||||
"phy_requirements",
|
||||
"return_path",
|
||||
"vias",
|
||||
"topology",
|
||||
"cable_channel",
|
||||
]
|
||||
HDMI_CHECKS = [
|
||||
"differential_impedance",
|
||||
"pair_skew",
|
||||
"lane_relationships",
|
||||
"impedance_tolerance",
|
||||
"intra_pair_skew",
|
||||
"inter_lane_skew",
|
||||
"clock_data_relationship",
|
||||
"data_rate",
|
||||
"transition_rate",
|
||||
"channel_loss",
|
||||
"insertion_loss",
|
||||
"return_loss",
|
||||
"crosstalk",
|
||||
"vias",
|
||||
"connector",
|
||||
"stubs",
|
||||
"connector_contribution",
|
||||
"topology",
|
||||
"return_path",
|
||||
"ac_coupling",
|
||||
"esd",
|
||||
"ddc",
|
||||
"cec",
|
||||
"hpd",
|
||||
]
|
||||
DOC_USB_BASE = (
|
||||
"USB-IF Base Specification (revision to fix when PDF is provided)"
|
||||
)
|
||||
DOC_USB_ELEC = (
|
||||
"USB-IF Electrical Compliance Specification (revision to fix when PDF is provided)"
|
||||
)
|
||||
DOC_USB_CABLE = (
|
||||
"USB-IF Cable and Connector Specification (revision to fix when PDF is provided)"
|
||||
)
|
||||
DOC_USB_CTS = (
|
||||
"USB-IF Compliance Test Specification (revision to fix when PDF is provided)"
|
||||
)
|
||||
DOC_USB_ECN = "USB-IF ECN applicable to the revision used"
|
||||
DOC_IEEE_8023 = "IEEE 802.3 (clause for this variant; not on file)"
|
||||
DOC_HDMI_ADOPTER = (
|
||||
"HDMI Adopter specification (not on file; 1.4 vs 2.x vs FRL must not be mixed)"
|
||||
)
|
||||
DDR_CHECKS = [
|
||||
"byte_lane_mapping",
|
||||
"dq_to_dqs_skew",
|
||||
@@ -486,13 +582,36 @@ def build_m0_catalog() -> dict[str, Any]:
|
||||
"id": pid, "name": name, "bus_type": bus, "family": family, "notes": notes,
|
||||
})
|
||||
|
||||
L("usb2-ls-fs", "USB 2.0 Low/Full Speed", "SERIAL", "USB")
|
||||
L("usb2-ls-fs", "USB 2.0 Low/Full Speed (umbrella)", "SERIAL", "USB",
|
||||
"Umbrella. Prefer usb2-ls / usb2-fs when speed is known.")
|
||||
L("usb2-ls", "USB 2.0 Low Speed", "SERIAL", "USB")
|
||||
L("usb2-fs", "USB 2.0 Full Speed", "SERIAL", "USB")
|
||||
L("usb2-hs", "USB 2.0 High Speed", "SERIAL", "USB")
|
||||
L("usb-c-usb2", "USB Type-C carrying USB 2.x only", "SERIAL", "USB")
|
||||
L("usb-c", "USB Type-C connector/interface system", "SERIAL", "USB",
|
||||
"Connector/interface system, not a data protocol. Not USB3. Not USB4.")
|
||||
L("usb-c-usb2", "USB 2.0 over USB Type-C (physical implementation name)", "SERIAL", "USB",
|
||||
"Physical implementation USB 2.0 over Type-C. Protocol is USB 2.0; connector is Type-C.")
|
||||
L("usb-c-pd", "USB Power Delivery on CC", "SERIAL", "USB")
|
||||
L("usb3-x", "USB 3.x (logical)", "SERIAL", "USB",
|
||||
"Distinct from USB 2.0 and from Type-C connector. Numbers UNKNOWN until USB-IF USB3 spec.")
|
||||
L("usb4", "USB4 (logical)", "SERIAL", "USB",
|
||||
"Distinct from USB 3.x and Type-C. Numbers UNKNOWN until USB-IF USB4 spec.")
|
||||
L("100base-tx", "100BASE-TX Fast Ethernet", "SERIAL", "ETHERNET")
|
||||
L("10base-t", "10BASE-T", "SERIAL", "ETHERNET")
|
||||
L("1000base-t", "1000BASE-T", "SERIAL", "ETHERNET")
|
||||
L("2.5gbase-t", "2.5GBASE-T", "SERIAL", "ETHERNET")
|
||||
L("5gbase-t", "5GBASE-T", "SERIAL", "ETHERNET")
|
||||
L("10gbase-t", "10GBASE-T", "SERIAL", "ETHERNET")
|
||||
L("sgmii", "SGMII", "SERIAL", "ETHERNET")
|
||||
L("1000base-x", "1000BASE-X", "SERIAL", "ETHERNET")
|
||||
L("100base-fx", "100BASE-FX", "SERIAL", "ETHERNET")
|
||||
L("10gbase-r", "10GBASE-R", "SERIAL", "ETHERNET")
|
||||
L("ethernet-unspecified", "Ethernet unspecified (RJ45 alone)", "SERIAL", "ETHERNET",
|
||||
"RJ45 is a connector, not 100BASE-TX or 1000BASE-T.")
|
||||
L("hdmi-1.4", "HDMI 1.4 TMDS", "SERIAL", "HDMI")
|
||||
L("hdmi-2.0", "HDMI 2.0 TMDS", "SERIAL", "HDMI")
|
||||
L("hdmi-frl", "HDMI FRL mode", "SERIAL", "HDMI",
|
||||
"FRL is distinct from TMDS. Do not copy TMDS numbers onto FRL.")
|
||||
L("ddr-family", "DDR family (abstract)", "MEMORY", "DDR")
|
||||
for gen, title in (
|
||||
("ddr1", "DDR1"), ("ddr2", "DDR2"), ("ddr3", "DDR3"),
|
||||
@@ -548,92 +667,214 @@ def build_m0_catalog() -> dict[str, Any]:
|
||||
L("can", "CAN / CAN FD", "PERIPHERAL", "CAN")
|
||||
L("rgmii", "RGMII MAC-PHY", "PERIPHERAL", "ETHERNET")
|
||||
|
||||
missing_usb = [
|
||||
_c("usb2-hs-dpair", "differential_impedance", "MISSING_SOURCE",
|
||||
measurement_method="PCB_STACKUP"),
|
||||
_c("usb2-hs-dpair", "intra_pair_skew", "MISSING_SOURCE",
|
||||
measurement_method="CALCULATION"),
|
||||
_c("usb2-hs-dpair", "topology", "UNKNOWN"),
|
||||
_c("usb2-hs-dpair", "length", "UNKNOWN"),
|
||||
_c("usb2-hs-dpair", "vias", "UNKNOWN"),
|
||||
_c("usb2-hs-dpair", "return_path", "UNKNOWN", mandatory="RECOMMENDED",
|
||||
source_class="VENDOR"),
|
||||
]
|
||||
def _usb2_cons(pid: str) -> list[dict[str, Any]]:
|
||||
docs = {
|
||||
"differential_impedance": DOC_USB_ELEC,
|
||||
"impedance_tolerance": DOC_USB_ELEC,
|
||||
"electrical_levels": DOC_USB_ELEC,
|
||||
"rise_time": DOC_USB_ELEC,
|
||||
"fall_time": DOC_USB_ELEC,
|
||||
"intra_pair_skew": DOC_USB_ELEC,
|
||||
"termination": DOC_USB_ELEC,
|
||||
"dc_resistance": DOC_USB_ELEC,
|
||||
"connector_requirements": DOC_USB_CABLE,
|
||||
"vbus_requirements": DOC_USB_BASE,
|
||||
"pull_up": DOC_USB_BASE,
|
||||
"pull_down": DOC_USB_BASE,
|
||||
"signaling": DOC_USB_BASE,
|
||||
"data_rate": DOC_USB_BASE,
|
||||
"test_method": DOC_USB_CTS,
|
||||
"compliance_test": DOC_USB_CTS,
|
||||
}
|
||||
rec = {"return_path"}
|
||||
out = []
|
||||
for p in USB2_CHECKS:
|
||||
mand: MandatoryClass = "RECOMMENDED" if p in rec else "MANDATORY"
|
||||
out.append(_c(
|
||||
pid, p, "UNKNOWN",
|
||||
mandatory=mand,
|
||||
needed_document=docs.get(p, DOC_USB_BASE),
|
||||
))
|
||||
return out
|
||||
|
||||
def _usbc_cons(pid: str) -> list[dict[str, Any]]:
|
||||
cable = {"rp", "rd", "ra", "cc", "connector_requirements", "current_advertisement"}
|
||||
out = []
|
||||
for p in USB_C_CHECKS:
|
||||
doc = DOC_USB_CABLE if p in cable else DOC_USB_ELEC
|
||||
if p == "usb2_channel_requirements":
|
||||
doc = DOC_USB_ELEC
|
||||
out.append(_c(pid, p, "UNKNOWN", needed_document=doc))
|
||||
return out
|
||||
|
||||
def _eth_cons(pid: str, *, phy: bool = False) -> list[dict[str, Any]]:
|
||||
out = []
|
||||
for p in ETH_CHECKS:
|
||||
if p in {"phy_requirements", "magnetics_requirements"} or phy:
|
||||
if p in {"phy_requirements", "magnetics_requirements"}:
|
||||
out.append(_c(
|
||||
pid, p, "PHY_DEPENDENT",
|
||||
source_type="PHY", source_class="VENDOR",
|
||||
needed_document="PHY vendor datasheet (not IEEE-universal)",
|
||||
))
|
||||
continue
|
||||
out.append(_c(pid, p, "UNKNOWN", needed_document=DOC_IEEE_8023))
|
||||
return out
|
||||
|
||||
def _hdmi_cons(pid: str) -> list[dict[str, Any]]:
|
||||
return [
|
||||
_c(pid, p, "UNKNOWN", needed_document=DOC_HDMI_ADOPTER)
|
||||
for p in HDMI_CHECKS
|
||||
]
|
||||
|
||||
for lid, pid, hints, note in (
|
||||
("usb2-ls", "usb2-ls-dpair", ["USB_D+", "USB_LS"], "USB 2.0 Low Speed. No invented Z."),
|
||||
("usb2-fs", "usb2-fs-dpair", ["USB_D+", "USB_FS"], "USB 2.0 Full Speed. No invented Z."),
|
||||
("usb2-ls-fs", "usb2-ls-fs-dpair", ["USB_D+", "USB_D-", "USB_DP", "USB_DM", "D+", "D-"],
|
||||
"LS/FS umbrella pair. No invented Z."),
|
||||
("usb2-hs", "usb2-hs-dpair", ["USB_D+", "USB_D-", "USB_HS"],
|
||||
"USB 2.0 High Speed. USB-IF cite required before NUMERIC Z."),
|
||||
):
|
||||
physical.append(_iface(
|
||||
pid, lid, "SERIAL", "SERIAL_DIFFERENTIAL", "YES",
|
||||
USB2_CHECKS, _usb2_cons(pid),
|
||||
hints=hints, notes=note,
|
||||
))
|
||||
physical.append(_iface(
|
||||
"usb2-ls-fs-dpair", "usb2-ls-fs", "SERIAL", "SERIAL_DIFFERENTIAL", "YES",
|
||||
USB2_CHECKS,
|
||||
[
|
||||
_c("usb2-ls-fs-dpair", "differential_impedance", "MISSING_SOURCE",
|
||||
measurement_method="PCB_STACKUP"),
|
||||
_c("usb2-ls-fs-dpair", "intra_pair_skew", "MISSING_SOURCE"),
|
||||
_c("usb2-ls-fs-dpair", "topology", "UNKNOWN"),
|
||||
],
|
||||
hints=["USB_D+", "USB_D-", "USB_DP", "USB_DM", "D+", "D-"],
|
||||
notes="LS/FS pair. No invented Z.",
|
||||
))
|
||||
physical.append(_iface(
|
||||
"usb2-hs-dpair", "usb2-hs", "SERIAL", "SERIAL_DIFFERENTIAL", "YES",
|
||||
USB2_CHECKS, missing_usb,
|
||||
hints=["USB_D+", "USB_D-", "USB_HS"],
|
||||
notes="HS pair. Official USB-IF cite required before NUMERIC Z.",
|
||||
))
|
||||
physical.append(_iface(
|
||||
"usb-c-usb2-receptacle", "usb-c-usb2", "SERIAL", "MIXED", "YES",
|
||||
USB2_CHECKS + ["cc_rd_rp", "vbus_gnd", "superspeed_absence"],
|
||||
[
|
||||
_c("usb-c-usb2-receptacle", "differential_impedance", "MISSING_SOURCE"),
|
||||
_c("usb-c-usb2-receptacle", "cc_termination", "MISSING_SOURCE",
|
||||
source_type="CONNECTOR", source_class="NORMATIVE"),
|
||||
_c("usb-c-usb2-receptacle", "superspeed_pairs", "NOT_APPLICABLE",
|
||||
mandatory="INFORMATIONAL",
|
||||
conditions=["usb2_only"]),
|
||||
],
|
||||
"usb-c-receptacle", "usb-c", "SERIAL", "MIXED", "YES",
|
||||
USB_C_CHECKS, _usbc_cons("usb-c-receptacle"),
|
||||
connector="Type-C",
|
||||
hints=["CC1", "CC2", "USB_C", "TYPE_C", "A5", "B5"],
|
||||
notes="Type-C carrying USB2. Electrical USB2 + CC. No invented Rd ohms.",
|
||||
hints=["USB_C", "TYPE_C", "CC1", "CC2"],
|
||||
notes=(
|
||||
"USB Type-C connector/interface system. Not USB 2.0/3.x/USB4 protocol. "
|
||||
"No invented Rd/Rp ohms."
|
||||
),
|
||||
))
|
||||
usb2_over_c = _usb2_cons("usb-c-usb2-receptacle")
|
||||
seen_p = {c["parameter"] for c in usb2_over_c}
|
||||
for c in _usbc_cons("usb-c-usb2-receptacle"):
|
||||
if c["parameter"] not in seen_p:
|
||||
usb2_over_c.append(c)
|
||||
seen_p.add(c["parameter"])
|
||||
usb2_over_c.append(_c(
|
||||
"usb-c-usb2-receptacle", "cc_rd_rp", "UNKNOWN",
|
||||
source_type="CONNECTOR", needed_document=DOC_USB_CABLE,
|
||||
))
|
||||
usb2_over_c.append(_c(
|
||||
"usb-c-usb2-receptacle", "superspeed_pairs", "NOT_APPLICABLE",
|
||||
mandatory="INFORMATIONAL", conditions=["usb2_only"],
|
||||
))
|
||||
physical.append(_iface(
|
||||
"usb-c-usb2-receptacle", "usb2-hs", "SERIAL", "MIXED", "YES",
|
||||
USB2_CHECKS + [p for p in USB_C_CHECKS if p not in USB2_CHECKS] + [
|
||||
"cc_rd_rp", "vbus_gnd", "superspeed_absence",
|
||||
],
|
||||
usb2_over_c,
|
||||
connector="Type-C",
|
||||
hints=["CC1", "CC2", "USB_C", "TYPE_C", "A5", "B5", "USB_D+", "USB_D-"],
|
||||
notes=(
|
||||
"Physical implementation: USB 2.0 over USB Type-C. "
|
||||
"Protocol=USB 2.0, connector=Type-C. Not USB3. Not USB4. No invented Rd."
|
||||
),
|
||||
))
|
||||
physical.append(_iface(
|
||||
"usb3-over-usb-c", "usb3-x", "SERIAL", "SERIAL_DIFFERENTIAL", "YES",
|
||||
["superspeed_pairs", "differential_impedance", "intra_pair_skew"],
|
||||
_skel_constraints(
|
||||
"usb3-over-usb-c",
|
||||
["superspeed_pairs", "differential_impedance", "intra_pair_skew"],
|
||||
"UNKNOWN",
|
||||
needed_document="USB-IF USB 3.x specification (not on file)",
|
||||
),
|
||||
connector="Type-C",
|
||||
hints=["SSTX", "SSRX", "USB3"],
|
||||
notes="USB 3.x over Type-C. Distinct from USB 2.0 and USB4. Numbers UNKNOWN.",
|
||||
incomplete=True,
|
||||
))
|
||||
physical.append(_iface(
|
||||
"usb4-over-usb-c", "usb4", "SERIAL", "SERIAL_DIFFERENTIAL", "YES",
|
||||
["superspeed_pairs", "differential_impedance"],
|
||||
_skel_constraints(
|
||||
"usb4-over-usb-c",
|
||||
["superspeed_pairs", "differential_impedance"],
|
||||
"UNKNOWN",
|
||||
needed_document="USB-IF USB4 specification (not on file)",
|
||||
),
|
||||
connector="Type-C",
|
||||
hints=["USB4", "TBT4"],
|
||||
notes="USB4 over Type-C. Not implied by Type-C connector. Numbers UNKNOWN.",
|
||||
incomplete=True,
|
||||
))
|
||||
physical.append(_iface(
|
||||
"usb-c-pd-cc", "usb-c-pd", "SERIAL", "SERIAL_SINGLE_ENDED", "CONDITIONAL",
|
||||
["cc_pd_contract"],
|
||||
[_c("usb-c-pd-cc", "pd_contract", "MISSING_SOURCE",
|
||||
source_type="STANDARD", conditions=["pd_present"])],
|
||||
[_c("usb-c-pd-cc", "pd_contract", "UNKNOWN",
|
||||
source_type="STANDARD", conditions=["pd_present"],
|
||||
needed_document="USB-IF USB Power Delivery specification (not on file)")],
|
||||
connector="Type-C",
|
||||
hints=["CC1", "CC2", "USB_PD"],
|
||||
notes="PD pack is separate; Type-C connector does not imply PD numbers.",
|
||||
incomplete=True,
|
||||
))
|
||||
physical.append(_iface(
|
||||
"100base-tx-mdi", "100base-tx", "SERIAL", "SERIAL_DIFFERENTIAL", "YES",
|
||||
ETH_CHECKS,
|
||||
[
|
||||
_c("100base-tx-mdi", "differential_impedance", "MISSING_SOURCE",
|
||||
source_type="STANDARD"),
|
||||
_c("100base-tx-mdi", "pair_skew", "MISSING_SOURCE"),
|
||||
_c("100base-tx-mdi", "magnetics", "PHY_DEPENDENT",
|
||||
source_type="PHY", source_class="VENDOR"),
|
||||
_c("100base-tx-mdi", "bob_smith", "UNKNOWN", mandatory="RECOMMENDED",
|
||||
source_type="PHY", source_class="VENDOR"),
|
||||
],
|
||||
"ethernet-rj45-unspecified", "ethernet-unspecified", "SERIAL", "MIXED", "YES",
|
||||
["connector_requirements"],
|
||||
[_c("ethernet-rj45-unspecified", "connector_requirements", "UNKNOWN",
|
||||
source_type="CONNECTOR", needed_document=DOC_IEEE_8023)],
|
||||
connector="RJ45",
|
||||
hints=["TD+", "TD-", "RD+", "RD-", "MDI", "RJ45", "100BASE"],
|
||||
notes="IEEE numbers stay MISSING_SOURCE until the spec is on file.",
|
||||
hints=["RJ45", "8P8C"],
|
||||
notes="RJ45 connector alone is not 100BASE-TX or 1000BASE-T.",
|
||||
incomplete=True,
|
||||
))
|
||||
for lid, pid, hints in (
|
||||
("10base-t", "10base-t-mdi", ["10BASE", "TD+", "RD+"]),
|
||||
("100base-tx", "100base-tx-mdi", ["TD+", "TD-", "RD+", "RD-", "MDI", "100BASE"]),
|
||||
("1000base-t", "1000base-t-mdi", ["1000BASE", "GBE", "MDI"]),
|
||||
("2.5gbase-t", "2.5gbase-t-mdi", ["2.5GBASE", "NBASE"]),
|
||||
("5gbase-t", "5gbase-t-mdi", ["5GBASE"]),
|
||||
("10gbase-t", "10gbase-t-mdi", ["10GBASE-T"]),
|
||||
("sgmii", "sgmii-pcb", ["SGMII"]),
|
||||
("1000base-x", "1000base-x-pcb", ["1000BASE-X", "SFP"]),
|
||||
("100base-fx", "100base-fx-pcb", ["100BASE-FX", "FX"]),
|
||||
("10gbase-r", "10gbase-r-pcb", ["10GBASE-R", "SFI"]),
|
||||
):
|
||||
conn = "none" if lid in {"sgmii", "1000base-x", "100base-fx", "10gbase-r"} else "RJ45"
|
||||
physical.append(_iface(
|
||||
pid, lid, "SERIAL", "SERIAL_DIFFERENTIAL", "YES",
|
||||
ETH_CHECKS, _eth_cons(pid),
|
||||
connector=conn, hints=hints,
|
||||
notes=(
|
||||
f"Incomplete skeleton: IEEE 802.3 not on file. "
|
||||
f"IEEE vs PHY vs PCB vs magnetics are distinct. Not auto from RJ45."
|
||||
),
|
||||
incomplete=True,
|
||||
))
|
||||
for hid, lid, conn, note in (
|
||||
("hdmi-1.4-tmds-type-a", "hdmi-1.4", "HDMI-A", "Type A land"),
|
||||
("hdmi-1.4-tmds-type-a", "hdmi-1.4", "HDMI-A",
|
||||
"HDMI 1.4 TMDS on Type A. Mini is a connector, not a protocol. UNKNOWN until Adopter spec."),
|
||||
("hdmi-1.4-tmds-type-c-mini", "hdmi-1.4", "HDMI-C-mini",
|
||||
"Mini Type C land; electrical HDMI 1.4, not a second standard"),
|
||||
("hdmi-2.0-tmds-type-a", "hdmi-2.0", "HDMI-A", "Type A land"),
|
||||
"Mini HDMI is CONNECTOR only. Electrical protocol remains HDMI 1.4 TMDS, not MINI-HDMI."),
|
||||
("hdmi-2.0-tmds-type-a", "hdmi-2.0", "HDMI-A",
|
||||
"HDMI 2.0 TMDS on Type A. Do not copy 1.4 numbers. UNKNOWN until Adopter spec."),
|
||||
("hdmi-2.0-tmds-type-c-mini", "hdmi-2.0", "HDMI-C-mini",
|
||||
"Mini Type C land; electrical HDMI 2.0"),
|
||||
"Mini HDMI connector + HDMI 2.0 TMDS electrical. Not a separate protocol id."),
|
||||
):
|
||||
physical.append(_iface(
|
||||
hid, lid, "SERIAL", "SERIAL_DIFFERENTIAL", "YES", HDMI_CHECKS,
|
||||
[
|
||||
_c(hid, "differential_impedance", "MISSING_SOURCE"),
|
||||
_c(hid, "pair_skew", "MISSING_SOURCE"),
|
||||
_c(hid, "insertion_loss", "MISSING_SOURCE", mandatory="RECOMMENDED"),
|
||||
],
|
||||
connector=conn, hints=["HDMI", "TMDS", "TX0", "TX1", "TX2", "CLK"],
|
||||
_hdmi_cons(hid),
|
||||
connector=conn,
|
||||
hints=["HDMI", "TMDS", "TX0", "TX1", "TX2", "CLK"],
|
||||
notes=note,
|
||||
incomplete=True,
|
||||
))
|
||||
physical.append(_iface(
|
||||
"hdmi-frl-pcb", "hdmi-frl", "SERIAL", "SERIAL_DIFFERENTIAL", "YES",
|
||||
HDMI_CHECKS, _hdmi_cons("hdmi-frl-pcb"),
|
||||
connector="HDMI-A",
|
||||
hints=["FRL", "HDMI2.1", "HDMI_FRL"],
|
||||
notes="HDMI FRL is distinct from TMDS. Do not apply TMDS rules. UNKNOWN until Adopter spec.",
|
||||
incomplete=True,
|
||||
))
|
||||
|
||||
def _mem_constraints(pid: str, kind: ValueKind, source_type: SourceType) -> list[dict[str, Any]]:
|
||||
cls: SourceClass = "VENDOR" if source_type != "STANDARD" else "NORMATIVE"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -210,6 +210,11 @@
|
||||
"default": "",
|
||||
"title": "Notes",
|
||||
"type": "string"
|
||||
},
|
||||
"incomplete": {
|
||||
"default": false,
|
||||
"title": "Incomplete",
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -377,6 +382,18 @@
|
||||
"default": false,
|
||||
"title": "Typical",
|
||||
"type": "boolean"
|
||||
},
|
||||
"needed_document": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"title": "Needed Document"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
||||
@@ -11,7 +11,7 @@ from typing import Any, Literal
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from backend.periscopex.models import Component, DesignGraph
|
||||
from backend.periscopex.models import Component, ComponentType, DesignGraph
|
||||
from backend.periscopex.pcb_net_match import normalize_kicad_hierarchy_net
|
||||
from backend.periscopex.protocol_catalog import (
|
||||
ProtocolCatalog,
|
||||
@@ -52,11 +52,24 @@ _EVIDENCE_SCORE: dict[str, float] = {
|
||||
"pair_geometry": 0.4,
|
||||
}
|
||||
|
||||
_USB_C_RE = re.compile(r"USB[\s_\-]*C\b|TYPE[\s_\-]*C|USB4085", re.I)
|
||||
_USB_C_RE = re.compile(r"USB[\s_\-]*C(?:[\s_\-]|\b|$)|TYPE[\s_\-]*C|USB4085", re.I)
|
||||
_USB2_RE = re.compile(r"USB[\s_\-]*2|16P", re.I)
|
||||
_USB3_RE = re.compile(r"USB[\s_\-]*3|SUPER\s*SPEED|\bSS\b", re.I)
|
||||
_USB_HS_RE = re.compile(r"HIGH\s*SPEED|\b480\b|\bHS\b", re.I)
|
||||
_USB3_RE = re.compile(r"USB[\s_\-]*3|SUPER\s*SPEED|SSTX|SSRX|SS_T[XR]", re.I)
|
||||
_USB4_RE = re.compile(r"USB[\s_\-]*4|\bUSB4\b|TBT4|THUNDERBOLT\s*4", re.I)
|
||||
_USB_HS_RE = re.compile(r"HIGH\s*SPEED|\b480\b", re.I)
|
||||
_USB_LSFS_RE = re.compile(r"LOW\s*SPEED|FULL\s*SPEED|\b12\s*MB|\b1\.5\s*MB", re.I)
|
||||
_USB_SILICON_RE = re.compile(
|
||||
r"CH340|CP210|FT232|USB\s*PHY|USB\s*CTRL|USB3300|TUSB|ISP150|MAX3421", re.I,
|
||||
)
|
||||
_RJ45_RE = re.compile(r"RJ45|8P8C|MAGJACK", re.I)
|
||||
_ETH_PHY_RE = re.compile(
|
||||
r"88E\d|LAN87|DP838|RTL821|KSZ90|BCM54|ETHERNET\s*PHY|\bPHY\b.*ETH|ETH.*PHY", re.I,
|
||||
)
|
||||
_ETH_MAG_RE = re.compile(r"MAGNETIC|PULSE\s*JACK|ETHERNET\s*XFR|LAN_XFR", re.I)
|
||||
_HDMI_MINI_RE = re.compile(r"MINI\s*HDMI|HDMI[\s_\-]*C\b|HDMI[\s_\-]*MINI", re.I)
|
||||
_HDMI_RE = re.compile(r"\bHDMI\b|TMDS", re.I)
|
||||
_HDMI_FRL_RE = re.compile(r"\bFRL\b|HDMI\s*2\.1", re.I)
|
||||
_HDMI_TMDS_NET = re.compile(r"TMDS|HDMI[_]?TX|HDMI[_]?CLK|HDMI[_]?D\d", re.I)
|
||||
_DDR_GEN = [
|
||||
("ddr5", re.compile(r"\bDDR5\b", re.I)),
|
||||
("ddr4", re.compile(r"\bDDR4\b|MT41|K4A|IS43TR", re.I)),
|
||||
@@ -109,6 +122,8 @@ class PhysicalBusInstance(BaseModel):
|
||||
peer_refs: list[str] = Field(default_factory=list)
|
||||
ambiguous_logical_ids: list[str] = Field(default_factory=list)
|
||||
notes: str = ""
|
||||
connector: str = ""
|
||||
physical_implementation: str = ""
|
||||
|
||||
|
||||
def _leaf(name: str) -> str:
|
||||
@@ -267,6 +282,8 @@ def recognize_physical_buses(
|
||||
out: list[PhysicalBusInstance] = []
|
||||
out.extend(_recognize_declared(graph, ifaces, logical))
|
||||
out.extend(_recognize_usb(graph, ifaces))
|
||||
out.extend(_recognize_ethernet(graph, ifaces))
|
||||
out.extend(_recognize_hdmi(graph, ifaces))
|
||||
out.extend(_recognize_hbm(graph, ifaces))
|
||||
out.extend(_recognize_ddr(graph, ifaces))
|
||||
out.extend(_recognize_axi(graph, ifaces))
|
||||
@@ -324,35 +341,42 @@ def _recognize_declared(
|
||||
|
||||
def _recognize_usb(graph: DesignGraph, ifaces: dict) -> list[PhysicalBusInstance]:
|
||||
instances: list[PhysicalBusInstance] = []
|
||||
connectors = []
|
||||
type_c = []
|
||||
other_usb_conn = []
|
||||
for comp in graph.components.values():
|
||||
if comp.component_type != ComponentType.CONNECTOR:
|
||||
continue
|
||||
blob = _blob(comp, graph)
|
||||
if _USB_C_RE.search(blob) or _USB_C_RE.search(comp.footprint):
|
||||
connectors.append(comp)
|
||||
if _USB_C_RE.search(blob) or _USB_C_RE.search(comp.footprint or ""):
|
||||
type_c.append(comp)
|
||||
elif re.search(r"USB", blob, re.I) or re.search(r"USB", comp.footprint or "", re.I):
|
||||
other_usb_conn.append(comp)
|
||||
pairs = _usb_pairs(list(graph.nets))
|
||||
if not connectors and not pairs:
|
||||
silicon = [
|
||||
c for c in graph.components.values()
|
||||
if c.component_type == ComponentType.IC and (
|
||||
_USB_SILICON_RE.search(_blob(c, graph))
|
||||
or (pairs and any(
|
||||
n in (c.pins or {}).values() for n in [p for pair in pairs for p in pair]
|
||||
))
|
||||
)
|
||||
]
|
||||
ss_nets = [n for n in graph.nets if _USB3_RE.search(_leaf(n)) or _USB3_RE.search(n)]
|
||||
usb4_ev = any(_USB4_RE.search(_blob(c, graph)) for c in graph.components.values())
|
||||
usb3_ev = bool(ss_nets) or any(
|
||||
_USB3_RE.search(_blob(c, graph)) for c in [*type_c, *silicon]
|
||||
)
|
||||
if not type_c and not other_usb_conn and not pairs:
|
||||
return []
|
||||
|
||||
usb3 = any(_USB3_RE.search(_blob(c, graph)) for c in connectors)
|
||||
usb2_mark = any(_USB2_RE.search(_blob(c, graph)) for c in connectors)
|
||||
hs = any(_USB_HS_RE.search(_blob(c, graph)) for c in connectors)
|
||||
lsfs = any(_USB_LSFS_RE.search(_blob(c, graph)) for c in connectors)
|
||||
ambiguous = False
|
||||
notes = ""
|
||||
if connectors and usb3 and usb2_mark:
|
||||
ambiguous = True
|
||||
notes = "USB2 vs USB3 on the same receptacle — REVIEW, not FAIL."
|
||||
if not hs and not lsfs and pairs:
|
||||
ambiguous = True
|
||||
notes = (notes + " " if notes else "") + "USB2 speed not declared; HS vs LS/FS REVIEW."
|
||||
|
||||
hs = any(_USB_HS_RE.search(_blob(c, graph)) for c in [*type_c, *other_usb_conn, *silicon])
|
||||
lsfs = any(_USB_LSFS_RE.search(_blob(c, graph)) for c in [*type_c, *other_usb_conn, *silicon])
|
||||
if hs and not lsfs:
|
||||
logical, phys = "usb2-hs", "usb2-hs-dpair"
|
||||
usb2_log, usb2_phys = "usb2-hs", "usb2-hs-dpair"
|
||||
elif lsfs and not hs:
|
||||
logical, phys = "usb2-ls-fs", "usb2-ls-fs-dpair"
|
||||
usb2_log, usb2_phys = "usb2-ls-fs", "usb2-ls-fs-dpair"
|
||||
else:
|
||||
logical, phys = "usb2-hs", "usb2-hs-dpair"
|
||||
ambiguous = True
|
||||
usb2_log, usb2_phys = "usb2-hs", "usb2-hs-dpair"
|
||||
|
||||
groups = [
|
||||
SignalGroup(
|
||||
@@ -364,50 +388,307 @@ def _recognize_usb(graph: DesignGraph, ifaces: dict) -> list[PhysicalBusInstance
|
||||
for i, (a, b) in enumerate(pairs)
|
||||
]
|
||||
nets = sorted({n for g in groups for n in g.nets})
|
||||
host = connectors[0].reference if connectors else (nets[0] if nets else "usb")
|
||||
evidence: EvidenceKind = "part" if connectors else "net_name"
|
||||
if connectors and any(_USB_C_RE.search(_blob(c, graph)) for c in connectors):
|
||||
cphys = "usb-c-usb2-receptacle"
|
||||
ciface = ifaces[cphys]
|
||||
status: RecognitionStatus = "REVIEW" if ambiguous or usb3 else "RECOGNIZED"
|
||||
|
||||
if type_c:
|
||||
host = type_c[0].reference
|
||||
pin_notes = _usb_c_orientation_note(type_c[0], graph)
|
||||
combo = bool(pairs) and bool(silicon)
|
||||
# Connector alone does not declare USB 2.0 / 3.x / USB4.
|
||||
if not combo:
|
||||
iface = ifaces["usb-c-receptacle"]
|
||||
instances.append(PhysicalBusInstance(
|
||||
instance_id=f"usb-c-receptacle:{host}",
|
||||
logical_protocol_id="usb-c",
|
||||
physical_interface_id="usb-c-receptacle",
|
||||
pcb_relevant=iface.pcb_relevant,
|
||||
confidence=_EVIDENCE_SCORE["part"],
|
||||
evidence_kind="part",
|
||||
recognition_status="REVIEW",
|
||||
nets=nets,
|
||||
groups=groups,
|
||||
host_ref=host,
|
||||
peer_refs=[c.reference for c in type_c],
|
||||
notes=(
|
||||
"USB Type-C connector only; protocol not inferred "
|
||||
"(need nets + silicon + topology). Not USB3. Not USB4. "
|
||||
+ pin_notes
|
||||
),
|
||||
connector="Type-C",
|
||||
physical_implementation="",
|
||||
))
|
||||
return instances
|
||||
iface = ifaces["usb-c-usb2-receptacle"]
|
||||
amb = [] if hs ^ lsfs else ["usb2-ls", "usb2-fs", "usb2-hs"]
|
||||
status: RecognitionStatus = "REVIEW" if amb or usb3_ev else "RECOGNIZED"
|
||||
notes = (
|
||||
"USB 2.0 over USB Type-C (connector+nets+silicon). "
|
||||
"Type-C is the connector, USB 2.0 is the protocol. "
|
||||
+ pin_notes
|
||||
)
|
||||
if usb3_ev:
|
||||
notes += " SuperSpeed evidence present — USB3 is a separate implementation, not implied by Type-C."
|
||||
amb = list(dict.fromkeys(amb + ["usb3-x"]))
|
||||
instances.append(PhysicalBusInstance(
|
||||
instance_id=f"{cphys}:{host}",
|
||||
logical_protocol_id="usb-c-usb2",
|
||||
physical_interface_id=cphys,
|
||||
pcb_relevant=ciface.pcb_relevant,
|
||||
confidence=_EVIDENCE_SCORE[evidence],
|
||||
evidence_kind=evidence,
|
||||
instance_id=f"usb-c-usb2-receptacle:{host}",
|
||||
logical_protocol_id=usb2_log,
|
||||
physical_interface_id="usb-c-usb2-receptacle",
|
||||
pcb_relevant=iface.pcb_relevant,
|
||||
confidence=_EVIDENCE_SCORE["silicon"],
|
||||
evidence_kind="silicon",
|
||||
recognition_status=status,
|
||||
nets=nets,
|
||||
groups=groups,
|
||||
host_ref=host,
|
||||
peer_refs=[c.reference for c in connectors],
|
||||
ambiguous_logical_ids=["usb-c-usb2", "usb2-hs"] if ambiguous else [],
|
||||
peer_refs=[c.reference for c in type_c] + [s.reference for s in silicon],
|
||||
ambiguous_logical_ids=amb,
|
||||
notes=notes,
|
||||
connector="Type-C",
|
||||
physical_implementation="USB 2.0 over USB Type-C",
|
||||
))
|
||||
if usb3_ev and "usb3-over-usb-c" in ifaces:
|
||||
instances.append(PhysicalBusInstance(
|
||||
instance_id=f"usb3-over-usb-c:{host}",
|
||||
logical_protocol_id="usb3-x",
|
||||
physical_interface_id="usb3-over-usb-c",
|
||||
pcb_relevant="YES",
|
||||
confidence=_EVIDENCE_SCORE["net_name"],
|
||||
evidence_kind="net_name",
|
||||
recognition_status="REVIEW",
|
||||
nets=sorted(ss_nets),
|
||||
host_ref=host,
|
||||
notes="USB 3.x over Type-C from SS evidence; not USB4; not implied by connector alone.",
|
||||
connector="Type-C",
|
||||
physical_implementation="USB 3.x over USB Type-C",
|
||||
))
|
||||
if usb4_ev and "usb4-over-usb-c" in ifaces:
|
||||
instances.append(PhysicalBusInstance(
|
||||
instance_id=f"usb4-over-usb-c:{host}",
|
||||
logical_protocol_id="usb4",
|
||||
physical_interface_id="usb4-over-usb-c",
|
||||
pcb_relevant="YES",
|
||||
confidence=_EVIDENCE_SCORE["part"],
|
||||
evidence_kind="part",
|
||||
recognition_status="REVIEW",
|
||||
host_ref=host,
|
||||
notes="USB4 evidence distinct from Type-C and USB3.",
|
||||
connector="Type-C",
|
||||
physical_implementation="USB4 over USB Type-C",
|
||||
))
|
||||
return instances
|
||||
|
||||
iface = ifaces[phys]
|
||||
status = "REVIEW" if ambiguous else "RECOGNIZED"
|
||||
amb = ["usb2-ls-fs", "usb2-hs"] if ambiguous else []
|
||||
if not pairs:
|
||||
return []
|
||||
host = other_usb_conn[0].reference if other_usb_conn else (
|
||||
silicon[0].reference if silicon else (nets[0] if nets else "usb")
|
||||
)
|
||||
if not other_usb_conn and not silicon:
|
||||
# Pairs without connector or silicon: net-name only, REVIEW.
|
||||
evidence: EvidenceKind = "net_name"
|
||||
conf = _EVIDENCE_SCORE["net_name"]
|
||||
status = "REVIEW"
|
||||
notes = "USB 2.0 inferred from D+/D− nets only; connector+silicon missing."
|
||||
else:
|
||||
evidence = "silicon" if silicon else "part"
|
||||
conf = _EVIDENCE_SCORE[evidence]
|
||||
status = "REVIEW" if not (hs ^ lsfs) else "RECOGNIZED"
|
||||
notes = "USB 2.0 from connector+nets+silicon/topology. No invented Z."
|
||||
iface = ifaces[usb2_phys]
|
||||
instances.append(PhysicalBusInstance(
|
||||
instance_id=f"{phys}:{host}",
|
||||
logical_protocol_id=logical,
|
||||
physical_interface_id=phys,
|
||||
instance_id=f"{usb2_phys}:{host}",
|
||||
logical_protocol_id=usb2_log,
|
||||
physical_interface_id=usb2_phys,
|
||||
pcb_relevant=iface.pcb_relevant,
|
||||
confidence=_EVIDENCE_SCORE[evidence],
|
||||
confidence=conf,
|
||||
evidence_kind=evidence,
|
||||
recognition_status=status,
|
||||
nets=nets,
|
||||
groups=groups,
|
||||
host_ref=host,
|
||||
peer_refs=[c.reference for c in connectors],
|
||||
ambiguous_logical_ids=amb,
|
||||
peer_refs=[c.reference for c in other_usb_conn] + [s.reference for s in silicon],
|
||||
ambiguous_logical_ids=["usb2-ls-fs", "usb2-hs"] if status == "REVIEW" else [],
|
||||
notes=notes,
|
||||
connector="",
|
||||
physical_implementation="USB 2.0",
|
||||
))
|
||||
return instances
|
||||
|
||||
|
||||
def _usb_c_orientation_note(comp: Component, graph: DesignGraph) -> str:
|
||||
"""D+/D− from schematic pins; do not assume a mux mapping."""
|
||||
a_side = []
|
||||
b_side = []
|
||||
for pin, net in (comp.pins or {}).items():
|
||||
pu = pin.upper()
|
||||
if pu in {"A6", "A7"} and net:
|
||||
a_side.append(f"{pin}={net}")
|
||||
if pu in {"B6", "B7"} and net:
|
||||
b_side.append(f"{pin}={net}")
|
||||
if a_side and b_side:
|
||||
return (
|
||||
f" D+/D− both orientations on schematic ({', '.join(a_side + b_side)}); "
|
||||
f"mux/switch not assumed."
|
||||
)
|
||||
if a_side or b_side:
|
||||
return (
|
||||
f" D+/D− orientation from schematic pins {', '.join(a_side or b_side)}; "
|
||||
f"opposite-side mux not assumed."
|
||||
)
|
||||
return " D+/D− orientation taken from schematic; mux not assumed."
|
||||
|
||||
|
||||
|
||||
|
||||
def _recognize_ethernet(graph: DesignGraph, ifaces: dict) -> list[PhysicalBusInstance]:
|
||||
rj45 = [
|
||||
c for c in graph.components.values()
|
||||
if c.component_type == ComponentType.CONNECTOR
|
||||
and (_RJ45_RE.search(_blob(c, graph)) or _RJ45_RE.search(c.footprint or ""))
|
||||
]
|
||||
phys = [
|
||||
c for c in graph.components.values()
|
||||
if c.component_type == ComponentType.IC and _ETH_PHY_RE.search(_blob(c, graph))
|
||||
]
|
||||
mags = [c for c in graph.components.values() if _ETH_MAG_RE.search(_blob(c, graph))]
|
||||
named = " ".join(_blob(c, graph) for c in graph.components.values()) + " " + " ".join(graph.nets)
|
||||
if not rj45 and not phys and not re.search(r"RGMII|SGMII|1000BASE|10BASE|100BASE", named, re.I):
|
||||
return []
|
||||
if rj45 and not phys and not mags and not re.search(
|
||||
r"1000BASE-T|100BASE-TX|10BASE-T|SGMII|RGMII", named, re.I,
|
||||
):
|
||||
iface = ifaces.get("ethernet-rj45-unspecified")
|
||||
if iface is None:
|
||||
return []
|
||||
host = rj45[0].reference
|
||||
return [PhysicalBusInstance(
|
||||
instance_id=f"ethernet-rj45-unspecified:{host}",
|
||||
logical_protocol_id="ethernet-unspecified",
|
||||
physical_interface_id="ethernet-rj45-unspecified",
|
||||
pcb_relevant=iface.pcb_relevant,
|
||||
confidence=_EVIDENCE_SCORE["part"],
|
||||
evidence_kind="part",
|
||||
recognition_status="REVIEW",
|
||||
host_ref=host,
|
||||
peer_refs=[c.reference for c in rj45],
|
||||
notes="RJ45 connector alone is not 100BASE-TX or 1000BASE-T.",
|
||||
connector="RJ45",
|
||||
physical_implementation="",
|
||||
)]
|
||||
lid, pid = "100base-tx", "100base-tx-mdi"
|
||||
if re.search(r"1000BASE-T|\bGBE\b|GIGABIT", named, re.I):
|
||||
lid, pid = "1000base-t", "1000base-t-mdi"
|
||||
elif re.search(r"10GBASE-T", named, re.I):
|
||||
lid, pid = "10gbase-t", "10gbase-t-mdi"
|
||||
elif re.search(r"2\.5GBASE", named, re.I):
|
||||
lid, pid = "2.5gbase-t", "2.5gbase-t-mdi"
|
||||
elif re.search(r"5GBASE-T", named, re.I):
|
||||
lid, pid = "5gbase-t", "5gbase-t-mdi"
|
||||
elif re.search(r"SGMII", named, re.I):
|
||||
lid, pid = "sgmii", "sgmii-pcb"
|
||||
elif re.search(r"1000BASE-X", named, re.I):
|
||||
lid, pid = "1000base-x", "1000base-x-pcb"
|
||||
elif re.search(r"100BASE-FX", named, re.I):
|
||||
lid, pid = "100base-fx", "100base-fx-pcb"
|
||||
elif re.search(r"10GBASE-R", named, re.I):
|
||||
lid, pid = "10gbase-r", "10gbase-r-pcb"
|
||||
elif re.search(r"10BASE-T", named, re.I):
|
||||
lid, pid = "10base-t", "10base-t-mdi"
|
||||
elif re.search(r"100BASE-TX", named, re.I):
|
||||
lid, pid = "100base-tx", "100base-tx-mdi"
|
||||
else:
|
||||
return []
|
||||
iface = ifaces.get(pid)
|
||||
if iface is None:
|
||||
return []
|
||||
host = (phys[0].reference if phys else (rj45[0].reference if rj45 else lid))
|
||||
return [PhysicalBusInstance(
|
||||
instance_id=f"{pid}:{host}",
|
||||
logical_protocol_id=lid,
|
||||
physical_interface_id=pid,
|
||||
pcb_relevant=iface.pcb_relevant,
|
||||
confidence=_EVIDENCE_SCORE["silicon"] if phys else _EVIDENCE_SCORE["part"],
|
||||
evidence_kind="silicon" if phys else "part",
|
||||
recognition_status="REVIEW",
|
||||
host_ref=host,
|
||||
notes="Ethernet variant from PHY/magnetics/naming; IEEE vs PHY vs PCB vs magnetics stay distinct. Incomplete without IEEE 802.3 on file.",
|
||||
connector=iface.connector,
|
||||
physical_implementation=lid,
|
||||
)]
|
||||
|
||||
|
||||
def _recognize_hdmi(graph: DesignGraph, ifaces: dict) -> list[PhysicalBusInstance]:
|
||||
conns = [
|
||||
c for c in graph.components.values()
|
||||
if c.component_type == ComponentType.CONNECTOR and (
|
||||
_HDMI_RE.search(_blob(c, graph)) or _HDMI_RE.search(c.footprint or "")
|
||||
or _HDMI_MINI_RE.search(_blob(c, graph))
|
||||
)
|
||||
]
|
||||
tmds = [n for n in graph.nets if _HDMI_TMDS_NET.search(n) or _HDMI_TMDS_NET.search(_leaf(n))]
|
||||
blob = " ".join(_blob(c, graph) for c in graph.components.values())
|
||||
frl = bool(_HDMI_FRL_RE.search(blob) or any(_HDMI_FRL_RE.search(n) for n in graph.nets))
|
||||
mini = any(
|
||||
_HDMI_MINI_RE.search(_blob(c, graph)) or _HDMI_MINI_RE.search(c.footprint or "")
|
||||
for c in conns
|
||||
)
|
||||
if not conns and not tmds and not frl:
|
||||
return []
|
||||
if conns and not tmds and not frl:
|
||||
host = conns[0].reference
|
||||
conn_name = "HDMI-C-mini" if mini else "HDMI-A"
|
||||
pid = "hdmi-1.4-tmds-type-c-mini" if mini else "hdmi-1.4-tmds-type-a"
|
||||
iface = ifaces.get(pid)
|
||||
if iface is None:
|
||||
return []
|
||||
return [PhysicalBusInstance(
|
||||
instance_id=f"{pid}:{host}",
|
||||
logical_protocol_id="hdmi-1.4",
|
||||
physical_interface_id=pid,
|
||||
pcb_relevant=iface.pcb_relevant,
|
||||
confidence=_EVIDENCE_SCORE["part"],
|
||||
evidence_kind="part",
|
||||
recognition_status="REVIEW",
|
||||
host_ref=host,
|
||||
notes=(
|
||||
"HDMI connector only; Mini HDMI is CONNECTOR not a protocol. "
|
||||
"Version/mode not assumed from connector. TMDS vs FRL not inferred."
|
||||
),
|
||||
connector=conn_name,
|
||||
physical_implementation="",
|
||||
)]
|
||||
if frl:
|
||||
lid, pid = "hdmi-frl", "hdmi-frl-pcb"
|
||||
conn_name = "HDMI-C-mini" if mini else "HDMI-A"
|
||||
else:
|
||||
lid = "hdmi-1.4"
|
||||
pid = "hdmi-1.4-tmds-type-c-mini" if mini else "hdmi-1.4-tmds-type-a"
|
||||
conn_name = "HDMI-C-mini" if mini else "HDMI-A"
|
||||
if re.search(r"HDMI\s*2\.0|HDMI2\.0", blob, re.I):
|
||||
lid = "hdmi-2.0"
|
||||
pid = "hdmi-2.0-tmds-type-c-mini" if mini else "hdmi-2.0-tmds-type-a"
|
||||
iface = ifaces.get(pid)
|
||||
if iface is None:
|
||||
return []
|
||||
host = conns[0].reference if conns else (tmds[0] if tmds else lid)
|
||||
mode = "FRL" if frl else "TMDS"
|
||||
return [PhysicalBusInstance(
|
||||
instance_id=f"{pid}:{host}",
|
||||
logical_protocol_id=lid,
|
||||
physical_interface_id=pid,
|
||||
pcb_relevant=iface.pcb_relevant,
|
||||
confidence=_EVIDENCE_SCORE["net_name"],
|
||||
evidence_kind="net_name",
|
||||
recognition_status="REVIEW",
|
||||
nets=tmds,
|
||||
host_ref=host,
|
||||
notes=(
|
||||
f"HDMI {mode} electrical; connector={conn_name}. "
|
||||
f"Mini HDMI is not a protocol id. TMDS rules are not applied to FRL."
|
||||
),
|
||||
connector=conn_name,
|
||||
physical_implementation=f"HDMI {mode}",
|
||||
)]
|
||||
|
||||
|
||||
def _ddr_gen_from_text(text: str) -> str | None:
|
||||
if _DDR4_PART.search(text):
|
||||
return "ddr4"
|
||||
|
||||
@@ -2,6 +2,14 @@
|
||||
|
||||
What's new in Periscope.
|
||||
|
||||
## 2.73.0 — 2026-09-22 — Protocol certification M6/M7 (USB-C / Ethernet / HDMI packs)
|
||||
|
||||
USB 2.0 (LS/FS/HS) is the protocol; USB Type-C is a connector/interface system; USB 2.0 over Type-C is the physical implementation. Type-C does not imply USB3 or USB4. Mini HDMI is a connector, not a protocol. Ethernet variants (10/100/1000/2.5G/5G/10G, SGMII, 1000BASE-X, 100BASE-FX, 10GBASE-R) are incomplete skeletons. All listed numeric fields stay **UNKNOWN** with `needed_document` (USB-IF / IEEE 802.3 / HDMI Adopter). No invented ohms, no typical-as-standard, no OpenEMS.
|
||||
|
||||
- [Changed] `protocol_catalog.py` + `catalog.json`; `needed_document` on UNKNOWN/MISSING_SOURCE.
|
||||
- [Changed] USB/Ethernet/HDMI recognition: connector alone is not the protocol.
|
||||
- [New] pytest `tests/pcb/test_protocol_m6_m7.py`.
|
||||
|
||||
## 2.72.0 — 2026-09-22 — Protocol certification M10 (catalog skeletons)
|
||||
|
||||
Catalog skeletons and `required_checks` for **PCIe / CXL** (logical vs physical), **I2C**, **CAN**, **RGMII**, plus missing high-speed processor ids (PCI, PCI-X, CCIX, OpenCAPI, UPI, DMI, Infinity Fabric, HyperTransport, UCIe). Numbers stay **UNKNOWN / MISSING_SOURCE / PHY_DEPENDENT** until an official cite is in the pack. `pcb_relevant` is YES or CONDITIONAL as appropriate. No invented ohms, no USB-IF fill, no OpenEMS.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "periscope-web",
|
||||
"version": "2.72.0",
|
||||
"version": "2.73.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"sync-version": "node scripts/sync-version.mjs",
|
||||
|
||||
@@ -23,8 +23,9 @@ from backend.periscopex.protocol_catalog import (
|
||||
|
||||
|
||||
REQUIRED_LOGICAL = {
|
||||
"usb2-ls-fs", "usb2-hs", "usb-c-usb2", "100base-tx",
|
||||
"hdmi-1.4", "hdmi-2.0",
|
||||
"usb2-ls-fs", "usb2-ls", "usb2-fs", "usb2-hs", "usb-c", "usb-c-usb2",
|
||||
"usb3-x", "usb4", "100base-tx", "10base-t", "1000base-t",
|
||||
"hdmi-1.4", "hdmi-2.0", "hdmi-frl",
|
||||
"ddr-family", "ddr4", "lpddr-family", "lpddr4",
|
||||
"hbm-family", "hbm3", "hyperbus", "qspi", "octal-spi", "emmc", "ufs",
|
||||
"axi4", "axi4-lite", "ahb", "apb", "wishbone", "avalon-mm", "avalon-st",
|
||||
|
||||
@@ -102,7 +102,7 @@ def test_m10_no_invented_ohms_and_no_usbif_fill():
|
||||
assert token not in blob
|
||||
usb = next(p for p in loaded.physical_interfaces if p.id == "usb2-hs-dpair")
|
||||
z = next(c for c in usb.constraints if c.parameter == "differential_impedance")
|
||||
assert z.value_kind == "MISSING_SOURCE"
|
||||
assert z.value_kind in {"MISSING_SOURCE", "UNKNOWN"}
|
||||
assert z.value is None
|
||||
assert z.source is None
|
||||
|
||||
|
||||
@@ -0,0 +1,224 @@
|
||||
"""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
|
||||
blob = json.dumps([c.model_dump() for p in usb_ifaces for c in p.constraints])
|
||||
assert "90" not in blob
|
||||
for p in usb_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
|
||||
assert "USB-IF" in c.needed_document or "specification" in c.needed_document.lower()
|
||||
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", "100base-tx", "1000base-t", "2.5gbase-t", "5gbase-t",
|
||||
"10gbase-t", "sgmii", "1000base-x", "100base-fx", "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 "")
|
||||
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
|
||||
Reference in New Issue
Block a user