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.
This commit is contained in:
2026-09-22 14:43:26 +02:00
parent 949f5f1541
commit f3be485b75
9 changed files with 1805 additions and 472 deletions
@@ -297,6 +297,25 @@ def parse_catalog(raw: Any) -> ProtocolCatalog:
raise ProtocolCatalogError(str(exc)) from exc
def _cite(
*,
document: str,
organization: str,
revision: str,
section: str | None = None,
table: str | None = None,
page: str | None = None,
) -> dict[str, str | None]:
return {
"document": document,
"organization": organization,
"revision": revision,
"section": section,
"table": table,
"page": page,
}
def _c(
iface_id: str,
parameter: str,
@@ -310,6 +329,9 @@ def _c(
conditions: list[str] | None = None,
measurement_method: MeasurementMethod | None = None,
needed_document: str | None = None,
value: float | None = None,
unit: str | None = None,
source: dict[str, Any] | None = None,
) -> dict[str, Any]:
row: dict[str, Any] = {
"id": f"{iface_id}-{parameter}",
@@ -318,12 +340,12 @@ def _c(
"mandatory": mandatory,
"source_type": source_type,
"source_class": source_class,
"value": None,
"unit": None,
"source": None,
"value": value,
"unit": unit,
"source": source,
"conditions": conditions or [],
"value_origin": "SPEC",
"typical": False,
"value_origin": value_origin,
"typical": typical,
}
if measurement_method:
row["measurement_method"] = measurement_method
@@ -337,6 +359,47 @@ def _c(
return row
def _num(
iface_id: str,
parameter: str,
value: float,
unit: str,
*,
document: str,
organization: str,
revision: str,
section: str | None = None,
table: str | None = None,
page: str | None = None,
mandatory: MandatoryClass = "MANDATORY",
source_type: SourceType = "STANDARD",
source_class: SourceClass = "NORMATIVE",
conditions: list[str] | None = None,
measurement_method: MeasurementMethod | None = None,
) -> dict[str, Any]:
"""NUMERIC row. Every field carries title, revision, section/table, page."""
return _c(
iface_id,
parameter,
"NUMERIC",
mandatory=mandatory,
source_type=source_type,
source_class=source_class,
conditions=conditions,
measurement_method=measurement_method,
value=value,
unit=unit,
source=_cite(
document=document,
organization=organization,
revision=revision,
section=section,
table=table,
page=page,
),
)
def _skel_constraints(
iface_id: str,
parameters: list[str],
@@ -472,20 +535,77 @@ HDMI_CHECKS = [
"cec",
"hpd",
]
DVI_CHECKS = [
"termination",
"differential_impedance",
"impedance_tolerance",
"rise_time",
"fall_time",
"intra_pair_skew",
"inter_lane_skew",
"electrical_levels",
"voltage",
"connector_requirements",
"data_rate",
"vias",
"stubs",
"return_path",
"topology",
]
# Recovered PDFs (USB Electrical CTS v1.08, Type-C Functional Test Rev 0.91, DVI 1.0).
# IEEE 802.3 is not on file — do not fill Ethernet numbers from these documents.
DOC_USB_CTS_108 = "USB 2.0 Electrical Compliance Test Specification"
REV_USB_CTS_108 = "Version 1.08, April 2026"
ORG_USBIF = "USB-IF"
DOC_USBC_FTS = (
"Universal Serial Bus Type-C Functional Test Specification Chapters 4 and 5"
)
REV_USBC_FTS = "Revision 0.91, March 3, 2024"
DOC_DVI_10 = "Digital Visual Interface (DVI)"
REV_DVI_10 = "Revision 1.0, 02 April 1999"
ORG_DDWG = "DDWG"
DOC_USB_BASE = (
"USB-IF Base Specification (revision to fix when PDF is provided)"
"USB-IF USB 2.0 Specification (not on file; Electrical CTS v1.08 does not state this field)"
)
DOC_USB_ELEC = (
"USB-IF Electrical Compliance Specification (revision to fix when PDF is provided)"
"USB 2.0 Electrical Compliance Test Specification Version 1.08 April 2026 "
"(recovered; this PCB limit is not stated in the CTS)"
)
DOC_USB_CABLE = (
"USB-IF Cable and Connector Specification (revision to fix when PDF is provided)"
"USB Type-C Cable and Connector Specification (not on file; "
"Functional Test Spec Rev 0.91 references Table 4-15 / 4.4.2 without stating ohms or volts)"
)
DOC_USB_CTS = (
"USB-IF Compliance Test Specification (revision to fix when PDF is provided)"
DOC_USB_CTS = DOC_USB_CTS_108 + " " + REV_USB_CTS_108
DOC_USB_ECN = "USB ENGINEERING CHANGE NOTICE USB 2.0 DC Resistance (cited by Electrical CTS v1.08 §3.9)"
DOC_USB_LSFS = (
"USB 2.0 Specification LS/FS electrical (not on file; Electrical CTS v1.08 "
"§2 lists LS/FS test names without numeric criteria)"
)
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_IEEE_2012 = "IEEE Std 802.3-2012 IEEE Standard for Ethernet"
REV_IEEE_2012 = "2012"
ORG_IEEE = "IEEE"
DOC_IEEE_S1_C14 = (
"IEEE Std 802.3-2012 Section One Clause 14 (10BASE-T; not in Section Two)"
)
DOC_IEEE_S3_C40 = (
"IEEE Std 802.3-2012 Section Three Clause 40 (1000BASE-T; not in Section Two)"
)
DOC_IEEE_S3_C36 = (
"IEEE Std 802.3-2012 Section Three (1000BASE-X; not in Section Two)"
)
DOC_IEEE_TP_PMD = (
"ANSI X3.263:1995 TP-PMD (incorporated by IEEE 802.3-2012 Clause 25; not on file. "
"Section Two states 100 Ω as cabling/link-segment and as a ±1% test termination, "
"not as a PCB trace differential impedance)"
)
DOC_IEEE_OTHER = {
"2.5gbase-t": "IEEE 802.3bz / later 802.3 2.5GBASE-T clause (not in 2012 Section Two)",
"5gbase-t": "IEEE 802.3bz / later 802.3 5GBASE-T clause (not in 2012 Section Two)",
"10gbase-t": "IEEE Std 802.3-2012 Section Four Clause 55 (10GBASE-T; not in Section Two)",
"sgmii": "SGMII is not an IEEE 802.3 MDI clause in 2012 Section Two",
"10gbase-r": "IEEE 802.3 10GBASE-R clauses (not in 2012 Section Two)",
}
DOC_HDMI_ADOPTER = (
"HDMI Adopter specification (not on file; 1.4 vs 2.x vs FRL must not be mixed)"
)
@@ -573,7 +693,7 @@ HYPERBUS_CHECKS = [
def build_m0_catalog() -> dict[str, Any]:
"""Skeleton catalog. No numeric USB/ETH/HDMI/DDR values."""
"""Catalog. NUMERIC only with recovered-PDF cites. No invented ohms."""
logical: list[dict[str, Any]] = []
physical: list[dict[str, Any]] = []
@@ -612,6 +732,8 @@ def build_m0_catalog() -> dict[str, Any]:
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("dvi", "DVI 1.0 TMDS", "SERIAL", "DVI",
"DVI is not HDMI. Numbers from DDWG DVI Revision 1.0 only.")
L("ddr-family", "DDR family (abstract)", "MEMORY", "DDR")
for gen, title in (
("ddr1", "DDR1"), ("ddr2", "DDR2"), ("ddr3", "DDR3"),
@@ -667,22 +789,22 @@ def build_m0_catalog() -> dict[str, Any]:
L("can", "CAN / CAN FD", "PERIPHERAL", "CAN")
L("rgmii", "RGMII MAC-PHY", "PERIPHERAL", "ETHERNET")
def _usb2_cons(pid: str) -> list[dict[str, Any]]:
def _usb2_cons(pid: str, *, hs: bool = False) -> 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,
"differential_impedance": DOC_USB_ELEC if hs else DOC_USB_LSFS,
"impedance_tolerance": DOC_USB_ELEC if hs else DOC_USB_LSFS,
"electrical_levels": DOC_USB_ELEC if hs else DOC_USB_LSFS,
"rise_time": DOC_USB_ELEC if hs else DOC_USB_LSFS,
"fall_time": DOC_USB_ELEC if hs else DOC_USB_LSFS,
"intra_pair_skew": DOC_USB_ELEC if hs else DOC_USB_LSFS,
"termination": DOC_USB_ELEC if hs else DOC_USB_LSFS,
"dc_resistance": DOC_USB_ELEC if hs else DOC_USB_LSFS,
"connector_requirements": DOC_USB_CABLE,
"vbus_requirements": DOC_USB_BASE,
"pull_up": DOC_USB_BASE,
"pull_up": DOC_USB_BASE if not hs else DOC_USB_ELEC,
"pull_down": DOC_USB_BASE,
"signaling": DOC_USB_BASE,
"data_rate": DOC_USB_BASE,
"signaling": DOC_USB_BASE if not hs else DOC_USB_CTS_108,
"data_rate": DOC_USB_LSFS if not hs else DOC_USB_CTS_108,
"test_method": DOC_USB_CTS,
"compliance_test": DOC_USB_CTS,
}
@@ -690,10 +812,62 @@ def build_m0_catalog() -> dict[str, Any]:
out = []
for p in USB2_CHECKS:
mand: MandatoryClass = "RECOMMENDED" if p in rec else "MANDATORY"
if hs and p == "data_rate":
out.append(_num(
pid, p, 480.0, "Mb/s",
document=DOC_USB_CTS_108, organization=ORG_USBIF,
revision=REV_USB_CTS_108, section="3.2 EL_2", page="3",
conditions=["HS transmitter", "tolerance ±0.05%"],
))
continue
if hs and p in {"rise_time", "fall_time"}:
out.append(_num(
pid, p, 300.0, "ps",
document=DOC_USB_CTS_108, organization=ORG_USBIF,
revision=REV_USB_CTS_108, section="3.2 EL_6", page="3",
conditions=[
"HS driver 10% to 90% differential",
"minimum greater than 300 ps",
],
))
continue
if hs and p == "pull_up":
out.append(_num(
pid, p, 1500.0, "ohm",
document=DOC_USB_CTS_108, organization=ORG_USBIF,
revision=REV_USB_CTS_108, section="3.5 EL_31", page="4",
conditions=["device speed detection 1.5K pull-up"],
))
continue
if hs and p == "dc_resistance":
out.append(_num(
pid, p, 17.0, "ohm",
document=DOC_USB_CTS_108, organization=ORG_USBIF,
revision=REV_USB_CTS_108, section="3.9.1", page="7",
conditions=[
"HS device-only upstream",
"D+ and D- individually",
"maximum series DC resistance",
],
))
continue
out.append(_c(
pid, p, "UNKNOWN",
mandatory=mand,
needed_document=docs.get(p, DOC_USB_BASE),
needed_document=docs.get(p, DOC_USB_LSFS if not hs else DOC_USB_BASE),
))
if hs:
out.append(_num(
pid, "dc_resistance_captive_cable_device", 23.0, "ohm",
document=DOC_USB_CTS_108, organization=ORG_USBIF,
revision=REV_USB_CTS_108, section="3.9.1", page="7",
conditions=["HS captive-cable device", "D+ and D- individually", "maximum"],
))
out.append(_num(
pid, "dc_resistance_host_hub_downstream", 13.0, "ohm",
document=DOC_USB_CTS_108, organization=ORG_USBIF,
revision=REV_USB_CTS_108, section="3.9.2", page="7",
conditions=["HS host/hub or dual-role device downstream", "D+ and D- individually", "maximum"],
))
return out
@@ -704,21 +878,122 @@ def build_m0_catalog() -> dict[str, Any]:
doc = DOC_USB_CABLE if p in cable else DOC_USB_ELEC
if p == "usb2_channel_requirements":
doc = DOC_USB_ELEC
if p in {"vbus_requirements", "voltage"}:
doc = DOC_USB_CABLE
out.append(_c(pid, p, "UNKNOWN", needed_document=doc))
out.append(_num(
pid, "gnd_ir_drop_cable", 250.0, "mV",
document=DOC_USBC_FTS, organization=ORG_USBIF,
revision=REV_USBC_FTS, section="4.4.1#1", page="10",
mandatory="OPTIONAL", source_type="CABLE",
conditions=["cable IR drop for ground; not a receptacle PCB ohm"],
))
out.append(_num(
pid, "vbus_ir_drop_cable", 500.0, "mV",
document=DOC_USBC_FTS, organization=ORG_USBIF,
revision=REV_USBC_FTS, section="4.4.1#1", page="10",
mandatory="OPTIONAL", source_type="CABLE",
conditions=["cable IR drop for VBUS; not a receptacle PCB ohm"],
))
return out
def _eth_cons(pid: str, *, phy: bool = False) -> list[dict[str, Any]]:
def _eth_cons(pid: str, lid: str) -> list[dict[str, Any]]:
if lid == "10base-t":
missing = DOC_IEEE_S1_C14
elif lid == "1000base-t":
missing = DOC_IEEE_S3_C40
elif lid == "1000base-x":
missing = DOC_IEEE_S3_C36
elif lid == "100base-tx":
missing = DOC_IEEE_TP_PMD
elif lid == "100base-fx":
missing = (
"ISO/IEC 9314-3:1990 fiber-PMD (incorporated by IEEE 802.3-2012 Clause 26; "
"optical MDI, not copper PCB ohms)"
)
else:
missing = DOC_IEEE_OTHER.get(lid, DOC_IEEE_8023)
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))
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
if lid == "100base-tx" and p == "data_rate":
out.append(_num(
pid, p, 100.0, "Mb/s",
document=DOC_IEEE_2012, organization=ORG_IEEE,
revision=REV_IEEE_2012, section="21.1", page="39",
conditions=["100BASE-T family including 100BASE-TX"],
))
continue
if lid == "100base-tx" and p == "maximum_channel_length":
out.append(_num(
pid, p, 100.0, "m",
document=DOC_IEEE_2012, organization=ORG_IEEE,
revision=REV_IEEE_2012, section="25.4.9.2", page="232",
source_type="CABLE",
conditions=["Category 5 link segment of up to 100 m"],
))
continue
if lid == "100base-tx" and p == "cable_channel":
out.append(_num(
pid, p, 100.0, "ohm",
document=DOC_IEEE_2012, organization=ORG_IEEE,
revision=REV_IEEE_2012, section="25.4.9.2.2", page="232",
source_type="CABLE",
conditions=[
"nominal differential characteristic impedance of the link segment",
"1 MHz to 100 MHz including cords and connecting hardware",
"not a PCB trace differential impedance",
],
))
continue
if lid == "100base-fx" and p == "data_rate":
out.append(_num(
pid, p, 100.0, "Mb/s",
document=DOC_IEEE_2012, organization=ORG_IEEE,
revision=REV_IEEE_2012, section="26.1", page="245",
conditions=["100BASE-FX"],
))
continue
if lid == "100base-fx" and p == "differential_impedance":
out.append(_c(
pid, p, "NOT_APPLICABLE",
mandatory="INFORMATIONAL", source_type="STANDARD",
conditions=["optical fiber MDI"],
))
continue
nd = missing
if lid == "100base-tx" and p in {
"differential_impedance", "impedance_tolerance", "termination",
}:
nd = DOC_IEEE_TP_PMD
out.append(_c(pid, p, "UNKNOWN", needed_document=nd))
if lid == "100base-tx":
out.append(_num(
pid, "isolation_after_test", 2.0e6, "ohm",
document=DOC_IEEE_2012, organization=ORG_IEEE,
revision=REV_IEEE_2012, section="25.4.6", page="231",
conditions=[
"PMD MDI that is not a PI",
"resistance after isolation test, measured at 500 V dc",
"minimum 2 MΩ",
],
))
out.append(_num(
pid, "isolation_rms", 1500.0, "V",
document=DOC_IEEE_2012, organization=ORG_IEEE,
revision=REV_IEEE_2012, section="25.4.6 a", page="231",
mandatory="OPTIONAL",
conditions=[
"one of three allowed isolation tests",
"1500 V rms at 50 Hz to 60 Hz for 60 s",
],
))
return out
def _hdmi_cons(pid: str) -> list[dict[str, Any]]:
@@ -727,17 +1002,102 @@ def build_m0_catalog() -> dict[str, Any]:
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."),
def _dvi_cons(pid: str) -> list[dict[str, Any]]:
out = []
for p in DVI_CHECKS:
if p == "termination":
out.append(_num(
pid, p, 50.0, "ohm",
document=DOC_DVI_10, organization=ORG_DDWG,
revision=REV_DVI_10, section="4.2", table="Table 4-2", page="35",
conditions=["TMDS termination resistance", "±10%"],
))
continue
if p == "differential_impedance":
out.append(_num(
pid, p, 100.0, "ohm",
document=DOC_DVI_10, organization=ORG_DDWG,
revision=REV_DVI_10, section="4.4", table="Table 4-7", page="38",
conditions=["receiver at termination", "±10%"],
))
continue
if p == "impedance_tolerance":
out.append(_num(
pid, p, 10.0, "percent",
document=DOC_DVI_10, organization=ORG_DDWG,
revision=REV_DVI_10, section="4.4", table="Table 4-7", page="38",
conditions=["at termination ±10%"],
))
continue
if p in {"rise_time", "fall_time"}:
out.append(_num(
pid, p, 75.0, "ps",
document=DOC_DVI_10, organization=ORG_DDWG,
revision=REV_DVI_10, section="4.3", table="Table 4-4", page="36",
conditions=["20%80%", "minimum 75 ps", "maximum 0.4 Tbit"],
))
continue
if p == "intra_pair_skew":
out.append(_num(
pid, p, 0.15, "Tbit",
document=DOC_DVI_10, organization=ORG_DDWG,
revision=REV_DVI_10, section="4.3", table="Table 4-4", page="36",
conditions=["transmitter connector maximum"],
))
continue
if p == "inter_lane_skew":
out.append(_num(
pid, p, 0.20, "Tpixel",
document=DOC_DVI_10, organization=ORG_DDWG,
revision=REV_DVI_10, section="4.3", table="Table 4-4", page="36",
conditions=["transmitter connector maximum"],
))
continue
if p == "electrical_levels":
out.append(_num(
pid, p, 400.0, "mV",
document=DOC_DVI_10, organization=ORG_DDWG,
revision=REV_DVI_10, section="4.3", table="Table 4-3", page="35",
conditions=["single-ended Vswing minimum 400 mV", "maximum 600 mV"],
))
continue
if p == "voltage":
out.append(_num(
pid, p, 3.3, "V",
document=DOC_DVI_10, organization=ORG_DDWG,
revision=REV_DVI_10, section="4.2", table="Table 4-2", page="35",
conditions=["AVcc termination supply", "±5%"],
))
continue
out.append(_c(
pid, p, "UNKNOWN",
needed_document=(
"Digital Visual Interface (DVI) Revision 1.0 (recovered; this field is not a "
"single PCB number in the cited tables)"
),
))
out.append(_num(
pid, "through_connection_impedance", 100.0, "ohm",
document=DOC_DVI_10, organization=ORG_DDWG,
revision=REV_DVI_10, section="4.4", table="Table 4-7", page="38",
source_type="CONNECTOR",
conditions=["through a mated connector", "±20%"],
))
return out
for lid, pid, hints, note, hs in (
("usb2-ls", "usb2-ls-dpair", ["USB_D+", "USB_LS"],
"USB 2.0 Low Speed. Electrical CTS v1.08 lists LS tests without numeric criteria.", False),
("usb2-fs", "usb2-fs-dpair", ["USB_D+", "USB_FS"],
"USB 2.0 Full Speed. Electrical CTS v1.08 lists FS tests without numeric criteria.", False),
("usb2-ls-fs", "usb2-ls-fs-dpair", ["USB_D+", "USB_D-", "USB_DP", "USB_DM", "D+", "D-"],
"LS/FS umbrella pair. No invented Z."),
"LS/FS umbrella pair. No LS/FS ohms in Electrical CTS v1.08.", False),
("usb2-hs", "usb2-hs-dpair", ["USB_D+", "USB_D-", "USB_HS"],
"USB 2.0 High Speed. USB-IF cite required before NUMERIC Z."),
"USB 2.0 High Speed. Zdiff is not stated in Electrical CTS v1.08 (do not invent differential ohms).", True),
):
physical.append(_iface(
pid, lid, "SERIAL", "SERIAL_DIFFERENTIAL", "YES",
USB2_CHECKS, _usb2_cons(pid),
USB2_CHECKS, _usb2_cons(pid, hs=hs),
hints=hints, notes=note,
))
physical.append(_iface(
@@ -750,7 +1110,7 @@ def build_m0_catalog() -> dict[str, Any]:
"No invented Rd/Rp ohms."
),
))
usb2_over_c = _usb2_cons("usb-c-usb2-receptacle")
usb2_over_c = _usb2_cons("usb-c-usb2-receptacle", hs=True)
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:
@@ -841,11 +1201,14 @@ def build_m0_catalog() -> dict[str, Any]:
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),
ETH_CHECKS, _eth_cons(pid, lid),
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."
"IEEE 802.3-2012 Section Two (Clauses 2133) on file. "
"10BASE-T is Clause 14 (Section One, not here). "
"1000BASE-T is Clause 40 (Section Three, not here). "
"100 Ω in Clause 25 is cabling/link-segment, not PCB Z. "
"Not auto from RJ45. IEEE vs PHY vs PCB vs magnetics are distinct."
),
incomplete=True,
))
@@ -875,6 +1238,16 @@ def build_m0_catalog() -> dict[str, Any]:
notes="HDMI FRL is distinct from TMDS. Do not apply TMDS rules. UNKNOWN until Adopter spec.",
incomplete=True,
))
physical.append(_iface(
"dvi-1.0-tmds", "dvi", "SERIAL", "SERIAL_DIFFERENTIAL", "YES",
DVI_CHECKS, _dvi_cons("dvi-1.0-tmds"),
connector="DVI",
hints=["DVI", "TMDS", "TX0", "TX1", "TX2"],
notes=(
"DVI 1.0 TMDS. Not HDMI. Table 4-2 termination 50 Ω ±10%; "
"Table 4-7 receiver at termination 100 Ω ±10%. Table 4-8 cable skew is informative."
),
))
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
@@ -67,9 +67,10 @@ _ETH_PHY_RE = re.compile(
)
_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_RE = re.compile(r"\bHDMI\b", 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)
_HDMI_TMDS_NET = re.compile(r"HDMI[_]?TX|HDMI[_]?CLK|HDMI[_]?D\d|TMDS", re.I)
_DVI_RE = re.compile(r"\bDVI\b|DVI[\s_\-]*[DAI]", re.I)
_DDR_GEN = [
("ddr5", re.compile(r"\bDDR5\b", re.I)),
("ddr4", re.compile(r"\bDDR4\b|MT41|K4A|IS43TR", re.I)),
@@ -283,6 +284,7 @@ def recognize_physical_buses(
out.extend(_recognize_declared(graph, ifaces, logical))
out.extend(_recognize_usb(graph, ifaces))
out.extend(_recognize_ethernet(graph, ifaces))
out.extend(_recognize_dvi(graph, ifaces))
out.extend(_recognize_hdmi(graph, ifaces))
out.extend(_recognize_hbm(graph, ifaces))
out.extend(_recognize_ddr(graph, ifaces))
@@ -615,6 +617,40 @@ def _recognize_ethernet(graph: DesignGraph, ifaces: dict) -> list[PhysicalBusIns
)]
def _recognize_dvi(graph: DesignGraph, ifaces: dict) -> list[PhysicalBusInstance]:
conns = [
c for c in graph.components.values()
if c.component_type == ComponentType.CONNECTOR and (
_DVI_RE.search(_blob(c, graph)) or _DVI_RE.search(c.footprint or "")
)
]
if not conns:
return []
pid = "dvi-1.0-tmds"
iface = ifaces.get(pid)
if iface is None:
return []
host = conns[0].reference
tmds = [
n for n in graph.nets
if _HDMI_TMDS_NET.search(n) or _HDMI_TMDS_NET.search(_leaf(n)) or _DVI_RE.search(n)
]
return [PhysicalBusInstance(
instance_id=f"{pid}:{host}",
logical_protocol_id="dvi",
physical_interface_id=pid,
pcb_relevant=iface.pcb_relevant,
confidence=_EVIDENCE_SCORE["part"],
evidence_kind="part",
recognition_status="REVIEW" if not tmds else "RECOGNIZED",
nets=tmds,
host_ref=host,
notes="DVI 1.0 TMDS. Not HDMI. Connector is not HDMI Type A.",
connector="DVI",
physical_implementation="DVI 1.0 TMDS",
)]
def _recognize_hdmi(graph: DesignGraph, ifaces: dict) -> list[PhysicalBusInstance]:
conns = [
c for c in graph.components.values()
@@ -625,6 +661,12 @@ def _recognize_hdmi(graph: DesignGraph, ifaces: dict) -> list[PhysicalBusInstanc
]
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())
dvi_present = any(
_DVI_RE.search(_blob(c, graph)) or _DVI_RE.search(c.footprint or "")
for c in graph.components.values()
)
if dvi_present and not re.search(r"\bHDMI\b", blob, re.I):
return []
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 "")
@@ -2,6 +2,13 @@
What's new in Periscope.
## 2.74.0 — 2026-09-22 — Protocol packs from recovered PDFs (USB CTS, Type-C FTS, DVI 1.0, IEEE 802.3-2012 §2)
NUMERIC constraints now carry title, revision, section/table, and page from the recovered PDFs. USB 2.0 HS fills data rate / rise-fall / 1.5 kΩ pull-up / DCR from Electrical CTS v1.08; Zdiff stays UNKNOWN (CTS does not state 90 Ω). Type-C Rp/Rd/VBUS volts stay UNKNOWN (Functional Test Spec points at Cable and Connector tables). DVI 1.0 is a separate protocol from HDMI. IEEE 802.3-2012 Section Two fills 100BASE-TX cabling 100 Ω / 100 m / isolation and 100 Mb/s; 10BASE-T Clause 14 and 1000BASE-T Clause 40 are not in Section Two.
- [Changed] `protocol_catalog.py` + `catalog.json`; DVI recognition.
- [New] pytest `tests/pcb/test_protocol_pdf_pack.py`.
## 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.
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "periscope-web",
"version": "2.73.0",
"version": "2.74.0",
"private": true,
"scripts": {
"sync-version": "node scripts/sync-version.mjs",
+20 -3
View File
@@ -26,6 +26,7 @@ REQUIRED_LOGICAL = {
"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",
"dvi",
"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",
@@ -98,12 +99,28 @@ def test_hdmi_mini_is_same_logical_different_connector():
assert mini.connector == "HDMI-C-mini"
def test_catalog_has_no_invented_ohm_numbers():
def test_catalog_numeric_requires_official_cite_no_invented_usb_90ohm():
cat = load_catalog()
usb_z = []
for iface in cat.physical_interfaces:
for c in iface.constraints:
assert c.value is None
assert c.value_kind != "NUMERIC"
if c.value_kind == "NUMERIC":
assert c.value is not None
assert c.source is not None
assert c.source.document and c.source.organization
assert c.source.revision
assert c.source.page
assert c.source.section or c.source.table
if (
iface.logical_protocol_id.startswith("usb")
and c.parameter == "differential_impedance"
):
usb_z.append(c)
assert c.value_kind != "NUMERIC"
assert c.value is None
assert usb_z
blob = json.dumps([c.model_dump() for c in usb_z])
assert "90" not in blob
def test_recommended_is_not_fail_mandatory():
+5 -3
View File
@@ -90,9 +90,11 @@ def test_m10_no_invented_ohms_and_no_usbif_fill():
loaded = load_catalog()
for iface in loaded.physical_interfaces:
for c in iface.constraints:
assert c.value is None
assert c.value_kind != "NUMERIC" or (c.source is not None)
if c.value_kind != "NUMERIC":
if c.value_kind == "NUMERIC":
assert c.source is not None
assert c.source.document
else:
assert c.value is None
assert c.unit is None
blob = json.dumps([
c.model_dump() for p in loaded.physical_interfaces for c in p.constraints
+14 -8
View File
@@ -84,15 +84,21 @@ def test_usb2_and_usb_c_unknown_with_usbif_needed_document_no_90ohm():
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
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:
assert c.value is None
assert c.value_kind != "NUMERIC"
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()
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"
@@ -202,8 +208,8 @@ def test_mini_hdmi_is_connector_not_protocol_id():
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",
"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]
@@ -218,7 +224,7 @@ def test_ethernet_hdmi_skeletons_unknown_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 "")
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
+148
View File
@@ -0,0 +1,148 @@
"""Recovered-PDF pack: USB Electrical CTS, Type-C FTS, DVI 1.0, IEEE 802.3-2012 §2."""
from __future__ import annotations
import json
from backend.periscopex.models import (
Component,
ComponentType,
DesignGraph,
Net,
NetType,
PinConnection,
)
from backend.periscopex.protocol_catalog import load_catalog
from backend.periscopex.protocol_recognize import recognize_physical_buses
def _cite_ok(c) -> None:
assert c.value_kind == "NUMERIC"
assert c.value is not None
src = c.source
assert src is not None
assert src.document
assert src.organization
assert src.revision
assert src.page
assert src.section or src.table
def test_usb_cts_hs_numeric_cited_no_zdiff_90():
cat = load_catalog()
hs = next(p for p in cat.physical_interfaces if p.id == "usb2-hs-dpair")
by = {c.parameter: c for c in hs.constraints}
_cite_ok(by["data_rate"])
assert by["data_rate"].value == 480.0
assert by["data_rate"].unit == "Mb/s"
assert by["data_rate"].source.document == "USB 2.0 Electrical Compliance Test Specification"
assert "1.08" in by["data_rate"].source.revision
assert "EL_2" in (by["data_rate"].source.section or "")
assert by["rise_time"].value == 300.0
assert by["fall_time"].value == 300.0
assert by["pull_up"].value == 1500.0
assert by["dc_resistance"].value == 17.0
assert by["dc_resistance_captive_cable_device"].value == 23.0
assert by["dc_resistance_host_hub_downstream"].value == 13.0
for key in (
"data_rate", "rise_time", "fall_time", "pull_up", "dc_resistance",
"dc_resistance_captive_cable_device", "dc_resistance_host_hub_downstream",
):
_cite_ok(by[key])
assert by[key].source.organization == "USB-IF"
z = by["differential_impedance"]
assert z.value_kind == "UNKNOWN"
assert z.value is None
ls = next(p for p in cat.physical_interfaces if p.id == "usb2-ls-dpair")
ls_rate = next(c for c in ls.constraints if c.parameter == "data_rate")
assert ls_rate.value_kind == "UNKNOWN"
blob = json.dumps(z.model_dump())
assert "90" not in blob
def test_type_c_rp_rd_unknown_fts_does_not_state_ohms():
cat = load_catalog()
rec = next(p for p in cat.physical_interfaces if p.id == "usb-c-receptacle")
by = {c.parameter: c for c in rec.constraints}
for p in ("rp", "rd", "ra", "cc"):
assert by[p].value_kind == "UNKNOWN"
assert "Cable and Connector" in (by[p].needed_document or "")
assert by["voltage"].value_kind == "UNKNOWN"
_cite_ok(by["gnd_ir_drop_cable"])
assert by["gnd_ir_drop_cable"].value == 250.0
assert by["gnd_ir_drop_cable"].source_type == "CABLE"
assert by["gnd_ir_drop_cable"].mandatory == "OPTIONAL"
_cite_ok(by["vbus_ir_drop_cable"])
assert by["vbus_ir_drop_cable"].value == 500.0
assert "Functional Test" in by["vbus_ir_drop_cable"].source.document
def test_dvi_not_hdmi_numeric_from_dvi_10():
cat = load_catalog()
logical = {p.id for p in cat.logical_protocols}
assert "dvi" in logical
assert "mini-hdmi" not in logical
dvi = next(p for p in cat.physical_interfaces if p.id == "dvi-1.0-tmds")
assert dvi.logical_protocol_id == "dvi"
assert dvi.connector == "DVI"
by = {c.parameter: c for c in dvi.constraints}
_cite_ok(by["termination"])
assert by["termination"].value == 50.0
assert by["termination"].source.table == "Table 4-2"
_cite_ok(by["differential_impedance"])
assert by["differential_impedance"].value == 100.0
assert by["differential_impedance"].source.table == "Table 4-7"
assert by["differential_impedance"].source.organization == "DDWG"
hdmi = next(p for p in cat.physical_interfaces if p.id == "hdmi-1.4-tmds-type-a")
hz = next(c for c in hdmi.constraints if c.parameter == "differential_impedance")
assert hz.value_kind == "UNKNOWN"
g = DesignGraph(
components={
"J3": Component(
reference="J3", value="DVI-D receptacle",
footprint="DVI_D",
component_type=ComponentType.CONNECTOR,
pins={"1": "TX0+"},
),
},
nets={"TX0+": Net(
name="TX0+", net_type=NetType.SIGNAL,
pins=[PinConnection(component_ref="J3", pin_number="1")],
)},
)
insts = recognize_physical_buses(g)
logs = {i.logical_protocol_id for i in insts}
assert "dvi" in logs
assert "hdmi-1.4" not in logs
assert "hdmi-2.0" not in logs
def test_ieee8023_section2_100base_tx_not_10base_or_1000():
cat = load_catalog()
tx = next(p for p in cat.physical_interfaces if p.id == "100base-tx-mdi")
by = {c.parameter: c for c in tx.constraints}
_cite_ok(by["data_rate"])
assert by["data_rate"].value == 100.0
assert by["data_rate"].source.document.startswith("IEEE Std 802.3-2012")
assert by["data_rate"].source.revision == "2012"
assert by["data_rate"].source.section == "21.1"
_cite_ok(by["cable_channel"])
assert by["cable_channel"].value == 100.0
assert by["cable_channel"].source_type == "CABLE"
assert by["cable_channel"].source.section == "25.4.9.2.2"
z = by["differential_impedance"]
assert z.value_kind == "UNKNOWN"
assert "TP-PMD" in (z.needed_document or "")
ten = next(p for p in cat.physical_interfaces if p.id == "10base-t-mdi")
tdr = next(c for c in ten.constraints if c.parameter == "data_rate")
assert tdr.value_kind == "UNKNOWN"
assert "Clause 14" in (tdr.needed_document or "")
gbe = next(p for p in cat.physical_interfaces if p.id == "1000base-t-mdi")
gdr = next(c for c in gbe.constraints if c.parameter == "data_rate")
assert gdr.value_kind == "UNKNOWN"
assert "Clause 40" in (gdr.needed_document or "")
fx = next(p for p in cat.physical_interfaces if p.id == "100base-fx-pcb")
fxr = next(c for c in fx.constraints if c.parameter == "data_rate")
_cite_ok(fxr)
assert fxr.value == 100.0
assert fxr.source.section == "26.1"