Add protocol-certification M8 DDR instance grouping (2.70.0).

Controller-aware per-memory instances, DQ/DQS vs addr/cmd/ctrl/ck, HBM not classic DDR PCB DQ. No universal mm/ps. No M6 packs, no L3 solver.
This commit is contained in:
2026-09-22 12:46:01 +02:00
parent 24c4beecc9
commit 14272d7446
14 changed files with 1006 additions and 98 deletions
@@ -377,6 +377,7 @@ DDR_CHECKS = [
"dq_to_dqs_skew",
"byte_lane_skew",
"ck_to_command_skew",
"ck_to_address_skew",
"topology",
"impedance",
"termination",
@@ -574,7 +575,7 @@ def build_m0_catalog() -> dict[str, Any]:
cls: SourceClass = "VENDOR" if source_type != "STANDARD" else "NORMATIVE"
params = [
"impedance", "max_length", "dq_to_dqs_skew", "byte_to_byte_skew",
"topology", "termination",
"topology", "termination", "ck_to_command_skew", "ck_to_address_skew",
]
return [
_c(pid, p, kind, source_type=source_type, source_class=cls)
@@ -1041,6 +1041,7 @@
"dq_to_dqs_skew",
"byte_lane_skew",
"ck_to_command_skew",
"ck_to_address_skew",
"topology",
"impedance",
"termination",
@@ -1132,6 +1133,34 @@
"conditions": [],
"value_origin": "SPEC",
"typical": false
},
{
"id": "ddr-family-sdram-pcb-ck_to_command_skew",
"parameter": "ck_to_command_skew",
"value_kind": "CONTROLLER_DEPENDENT",
"mandatory": "MANDATORY",
"source_type": "CONTROLLER",
"source_class": "VENDOR",
"value": null,
"unit": null,
"source": null,
"conditions": [],
"value_origin": "SPEC",
"typical": false
},
{
"id": "ddr-family-sdram-pcb-ck_to_address_skew",
"parameter": "ck_to_address_skew",
"value_kind": "CONTROLLER_DEPENDENT",
"mandatory": "MANDATORY",
"source_type": "CONTROLLER",
"source_class": "VENDOR",
"value": null,
"unit": null,
"source": null,
"conditions": [],
"value_origin": "SPEC",
"typical": false
}
],
"notes": "No universal mm/ps for DDR."
@@ -1154,6 +1183,7 @@
"dq_to_dqs_skew",
"byte_lane_skew",
"ck_to_command_skew",
"ck_to_address_skew",
"topology",
"impedance",
"termination",
@@ -1245,6 +1275,34 @@
"conditions": [],
"value_origin": "SPEC",
"typical": false
},
{
"id": "ddr1-sdram-pcb-ck_to_command_skew",
"parameter": "ck_to_command_skew",
"value_kind": "CONTROLLER_DEPENDENT",
"mandatory": "MANDATORY",
"source_type": "CONTROLLER",
"source_class": "VENDOR",
"value": null,
"unit": null,
"source": null,
"conditions": [],
"value_origin": "SPEC",
"typical": false
},
{
"id": "ddr1-sdram-pcb-ck_to_address_skew",
"parameter": "ck_to_address_skew",
"value_kind": "CONTROLLER_DEPENDENT",
"mandatory": "MANDATORY",
"source_type": "CONTROLLER",
"source_class": "VENDOR",
"value": null,
"unit": null,
"source": null,
"conditions": [],
"value_origin": "SPEC",
"typical": false
}
],
"notes": "Controller/PHY dependent. Zero universal skew/length."
@@ -1267,6 +1325,7 @@
"dq_to_dqs_skew",
"byte_lane_skew",
"ck_to_command_skew",
"ck_to_address_skew",
"topology",
"impedance",
"termination",
@@ -1358,6 +1417,34 @@
"conditions": [],
"value_origin": "SPEC",
"typical": false
},
{
"id": "ddr2-sdram-pcb-ck_to_command_skew",
"parameter": "ck_to_command_skew",
"value_kind": "CONTROLLER_DEPENDENT",
"mandatory": "MANDATORY",
"source_type": "CONTROLLER",
"source_class": "VENDOR",
"value": null,
"unit": null,
"source": null,
"conditions": [],
"value_origin": "SPEC",
"typical": false
},
{
"id": "ddr2-sdram-pcb-ck_to_address_skew",
"parameter": "ck_to_address_skew",
"value_kind": "CONTROLLER_DEPENDENT",
"mandatory": "MANDATORY",
"source_type": "CONTROLLER",
"source_class": "VENDOR",
"value": null,
"unit": null,
"source": null,
"conditions": [],
"value_origin": "SPEC",
"typical": false
}
],
"notes": "Controller/PHY dependent. Zero universal skew/length."
@@ -1380,6 +1467,7 @@
"dq_to_dqs_skew",
"byte_lane_skew",
"ck_to_command_skew",
"ck_to_address_skew",
"topology",
"impedance",
"termination",
@@ -1471,6 +1559,34 @@
"conditions": [],
"value_origin": "SPEC",
"typical": false
},
{
"id": "ddr3-sdram-pcb-ck_to_command_skew",
"parameter": "ck_to_command_skew",
"value_kind": "CONTROLLER_DEPENDENT",
"mandatory": "MANDATORY",
"source_type": "CONTROLLER",
"source_class": "VENDOR",
"value": null,
"unit": null,
"source": null,
"conditions": [],
"value_origin": "SPEC",
"typical": false
},
{
"id": "ddr3-sdram-pcb-ck_to_address_skew",
"parameter": "ck_to_address_skew",
"value_kind": "CONTROLLER_DEPENDENT",
"mandatory": "MANDATORY",
"source_type": "CONTROLLER",
"source_class": "VENDOR",
"value": null,
"unit": null,
"source": null,
"conditions": [],
"value_origin": "SPEC",
"typical": false
}
],
"notes": "Controller/PHY dependent. Zero universal skew/length."
@@ -1493,6 +1609,7 @@
"dq_to_dqs_skew",
"byte_lane_skew",
"ck_to_command_skew",
"ck_to_address_skew",
"topology",
"impedance",
"termination",
@@ -1584,6 +1701,34 @@
"conditions": [],
"value_origin": "SPEC",
"typical": false
},
{
"id": "ddr3l-sdram-pcb-ck_to_command_skew",
"parameter": "ck_to_command_skew",
"value_kind": "CONTROLLER_DEPENDENT",
"mandatory": "MANDATORY",
"source_type": "CONTROLLER",
"source_class": "VENDOR",
"value": null,
"unit": null,
"source": null,
"conditions": [],
"value_origin": "SPEC",
"typical": false
},
{
"id": "ddr3l-sdram-pcb-ck_to_address_skew",
"parameter": "ck_to_address_skew",
"value_kind": "CONTROLLER_DEPENDENT",
"mandatory": "MANDATORY",
"source_type": "CONTROLLER",
"source_class": "VENDOR",
"value": null,
"unit": null,
"source": null,
"conditions": [],
"value_origin": "SPEC",
"typical": false
}
],
"notes": "Controller/PHY dependent. Zero universal skew/length."
@@ -1606,6 +1751,7 @@
"dq_to_dqs_skew",
"byte_lane_skew",
"ck_to_command_skew",
"ck_to_address_skew",
"topology",
"impedance",
"termination",
@@ -1697,6 +1843,34 @@
"conditions": [],
"value_origin": "SPEC",
"typical": false
},
{
"id": "ddr4-sdram-pcb-ck_to_command_skew",
"parameter": "ck_to_command_skew",
"value_kind": "PHY_DEPENDENT",
"mandatory": "MANDATORY",
"source_type": "PHY",
"source_class": "VENDOR",
"value": null,
"unit": null,
"source": null,
"conditions": [],
"value_origin": "SPEC",
"typical": false
},
{
"id": "ddr4-sdram-pcb-ck_to_address_skew",
"parameter": "ck_to_address_skew",
"value_kind": "PHY_DEPENDENT",
"mandatory": "MANDATORY",
"source_type": "PHY",
"source_class": "VENDOR",
"value": null,
"unit": null,
"source": null,
"conditions": [],
"value_origin": "SPEC",
"typical": false
}
],
"notes": "Controller/PHY dependent. Zero universal skew/length."
@@ -1719,6 +1893,7 @@
"dq_to_dqs_skew",
"byte_lane_skew",
"ck_to_command_skew",
"ck_to_address_skew",
"topology",
"impedance",
"termination",
@@ -1810,6 +1985,34 @@
"conditions": [],
"value_origin": "SPEC",
"typical": false
},
{
"id": "ddr5-sdram-pcb-ck_to_command_skew",
"parameter": "ck_to_command_skew",
"value_kind": "PHY_DEPENDENT",
"mandatory": "MANDATORY",
"source_type": "PHY",
"source_class": "VENDOR",
"value": null,
"unit": null,
"source": null,
"conditions": [],
"value_origin": "SPEC",
"typical": false
},
{
"id": "ddr5-sdram-pcb-ck_to_address_skew",
"parameter": "ck_to_address_skew",
"value_kind": "PHY_DEPENDENT",
"mandatory": "MANDATORY",
"source_type": "PHY",
"source_class": "VENDOR",
"value": null,
"unit": null,
"source": null,
"conditions": [],
"value_origin": "SPEC",
"typical": false
}
],
"notes": "Controller/PHY dependent. Zero universal skew/length."
@@ -1833,6 +2036,7 @@
"dq_to_dqs_skew",
"byte_lane_skew",
"ck_to_command_skew",
"ck_to_address_skew",
"topology",
"impedance",
"termination",
@@ -1924,6 +2128,34 @@
"conditions": [],
"value_origin": "SPEC",
"typical": false
},
{
"id": "lpddr-family-pcb-ck_to_command_skew",
"parameter": "ck_to_command_skew",
"value_kind": "CONTROLLER_DEPENDENT",
"mandatory": "MANDATORY",
"source_type": "CONTROLLER",
"source_class": "VENDOR",
"value": null,
"unit": null,
"source": null,
"conditions": [],
"value_origin": "SPEC",
"typical": false
},
{
"id": "lpddr-family-pcb-ck_to_address_skew",
"parameter": "ck_to_address_skew",
"value_kind": "CONTROLLER_DEPENDENT",
"mandatory": "MANDATORY",
"source_type": "CONTROLLER",
"source_class": "VENDOR",
"value": null,
"unit": null,
"source": null,
"conditions": [],
"value_origin": "SPEC",
"typical": false
}
],
"notes": ""
@@ -1948,6 +2180,7 @@
"dq_to_dqs_skew",
"byte_lane_skew",
"ck_to_command_skew",
"ck_to_address_skew",
"topology",
"impedance",
"termination",
@@ -2039,6 +2272,34 @@
"conditions": [],
"value_origin": "SPEC",
"typical": false
},
{
"id": "lpddr2-pcb-ck_to_command_skew",
"parameter": "ck_to_command_skew",
"value_kind": "CONTROLLER_DEPENDENT",
"mandatory": "MANDATORY",
"source_type": "CONTROLLER",
"source_class": "VENDOR",
"value": null,
"unit": null,
"source": null,
"conditions": [],
"value_origin": "SPEC",
"typical": false
},
{
"id": "lpddr2-pcb-ck_to_address_skew",
"parameter": "ck_to_address_skew",
"value_kind": "CONTROLLER_DEPENDENT",
"mandatory": "MANDATORY",
"source_type": "CONTROLLER",
"source_class": "VENDOR",
"value": null,
"unit": null,
"source": null,
"conditions": [],
"value_origin": "SPEC",
"typical": false
}
],
"notes": ""
@@ -2063,6 +2324,7 @@
"dq_to_dqs_skew",
"byte_lane_skew",
"ck_to_command_skew",
"ck_to_address_skew",
"topology",
"impedance",
"termination",
@@ -2154,6 +2416,34 @@
"conditions": [],
"value_origin": "SPEC",
"typical": false
},
{
"id": "lpddr3-pcb-ck_to_command_skew",
"parameter": "ck_to_command_skew",
"value_kind": "CONTROLLER_DEPENDENT",
"mandatory": "MANDATORY",
"source_type": "CONTROLLER",
"source_class": "VENDOR",
"value": null,
"unit": null,
"source": null,
"conditions": [],
"value_origin": "SPEC",
"typical": false
},
{
"id": "lpddr3-pcb-ck_to_address_skew",
"parameter": "ck_to_address_skew",
"value_kind": "CONTROLLER_DEPENDENT",
"mandatory": "MANDATORY",
"source_type": "CONTROLLER",
"source_class": "VENDOR",
"value": null,
"unit": null,
"source": null,
"conditions": [],
"value_origin": "SPEC",
"typical": false
}
],
"notes": ""
@@ -2178,6 +2468,7 @@
"dq_to_dqs_skew",
"byte_lane_skew",
"ck_to_command_skew",
"ck_to_address_skew",
"topology",
"impedance",
"termination",
@@ -2269,6 +2560,34 @@
"conditions": [],
"value_origin": "SPEC",
"typical": false
},
{
"id": "lpddr4-pcb-ck_to_command_skew",
"parameter": "ck_to_command_skew",
"value_kind": "PHY_DEPENDENT",
"mandatory": "MANDATORY",
"source_type": "PHY",
"source_class": "VENDOR",
"value": null,
"unit": null,
"source": null,
"conditions": [],
"value_origin": "SPEC",
"typical": false
},
{
"id": "lpddr4-pcb-ck_to_address_skew",
"parameter": "ck_to_address_skew",
"value_kind": "PHY_DEPENDENT",
"mandatory": "MANDATORY",
"source_type": "PHY",
"source_class": "VENDOR",
"value": null,
"unit": null,
"source": null,
"conditions": [],
"value_origin": "SPEC",
"typical": false
}
],
"notes": ""
@@ -2293,6 +2612,7 @@
"dq_to_dqs_skew",
"byte_lane_skew",
"ck_to_command_skew",
"ck_to_address_skew",
"topology",
"impedance",
"termination",
@@ -2384,6 +2704,34 @@
"conditions": [],
"value_origin": "SPEC",
"typical": false
},
{
"id": "lpddr4x-pcb-ck_to_command_skew",
"parameter": "ck_to_command_skew",
"value_kind": "PHY_DEPENDENT",
"mandatory": "MANDATORY",
"source_type": "PHY",
"source_class": "VENDOR",
"value": null,
"unit": null,
"source": null,
"conditions": [],
"value_origin": "SPEC",
"typical": false
},
{
"id": "lpddr4x-pcb-ck_to_address_skew",
"parameter": "ck_to_address_skew",
"value_kind": "PHY_DEPENDENT",
"mandatory": "MANDATORY",
"source_type": "PHY",
"source_class": "VENDOR",
"value": null,
"unit": null,
"source": null,
"conditions": [],
"value_origin": "SPEC",
"typical": false
}
],
"notes": ""
@@ -2408,6 +2756,7 @@
"dq_to_dqs_skew",
"byte_lane_skew",
"ck_to_command_skew",
"ck_to_address_skew",
"topology",
"impedance",
"termination",
@@ -2499,6 +2848,34 @@
"conditions": [],
"value_origin": "SPEC",
"typical": false
},
{
"id": "lpddr5-pcb-ck_to_command_skew",
"parameter": "ck_to_command_skew",
"value_kind": "PHY_DEPENDENT",
"mandatory": "MANDATORY",
"source_type": "PHY",
"source_class": "VENDOR",
"value": null,
"unit": null,
"source": null,
"conditions": [],
"value_origin": "SPEC",
"typical": false
},
{
"id": "lpddr5-pcb-ck_to_address_skew",
"parameter": "ck_to_address_skew",
"value_kind": "PHY_DEPENDENT",
"mandatory": "MANDATORY",
"source_type": "PHY",
"source_class": "VENDOR",
"value": null,
"unit": null,
"source": null,
"conditions": [],
"value_origin": "SPEC",
"typical": false
}
],
"notes": ""
@@ -2523,6 +2900,7 @@
"dq_to_dqs_skew",
"byte_lane_skew",
"ck_to_command_skew",
"ck_to_address_skew",
"topology",
"impedance",
"termination",
@@ -2614,6 +2992,34 @@
"conditions": [],
"value_origin": "SPEC",
"typical": false
},
{
"id": "lpddr5x-pcb-ck_to_command_skew",
"parameter": "ck_to_command_skew",
"value_kind": "PHY_DEPENDENT",
"mandatory": "MANDATORY",
"source_type": "PHY",
"source_class": "VENDOR",
"value": null,
"unit": null,
"source": null,
"conditions": [],
"value_origin": "SPEC",
"typical": false
},
{
"id": "lpddr5x-pcb-ck_to_address_skew",
"parameter": "ck_to_address_skew",
"value_kind": "PHY_DEPENDENT",
"mandatory": "MANDATORY",
"source_type": "PHY",
"source_class": "VENDOR",
"value": null,
"unit": null,
"source": null,
"conditions": [],
"value_origin": "SPEC",
"typical": false
}
],
"notes": ""
@@ -0,0 +1,319 @@
"""M8: DDR physical instance grouping. Controller-aware. No universal mm/ps/ohm.
DQ/DQS byte lanes are separate from ADDRESS / COMMAND / CONTROL / CLOCK.
HBM is package/interposer — not classic DDR PCB DQ rules.
"""
from __future__ import annotations
import re
from backend.periscopex.models import Component, ComponentType, DesignGraph
from backend.periscopex.pcb_net_match import normalize_kicad_hierarchy_net
from backend.periscopex.protocol_recognize import (
EvidenceKind,
PhysicalBusInstance,
SignalGroup,
_blob,
_EVIDENCE_SCORE,
)
PACK_MACROPHASE = "M8"
_DQ_RE = re.compile(r"(?:^|[_/.])DQ(\d+)(?:$|[_/.])", re.I)
_DQS_RE = re.compile(r"(?:^|[_/.])DQS(\d*)(?:[_#]?(P|N|PINS|PLUS|MINUS))?(?:$|[_/.])", re.I)
_DM_RE = re.compile(r"(?:^|[_/.])(?:DM|DBI)(\d+)(?:$|[_/.])", re.I)
_CK_RE = re.compile(r"(?:^|[_/.])(?:CK)(?:[_#]?(P|N))?(?:$|[_/.])", re.I)
_WCK_RE = re.compile(r"(?:^|[_/.])WCK", re.I)
_ADDR_RE = re.compile(r"(?:^|[_/.])(?:A|ADDR|BA|BG)(\d+)(?:$|[_/.])", re.I)
_CA_RE = re.compile(r"(?:^|[_/.])CA(\d+)(?:$|[_/.])", re.I)
_CMD_RE = re.compile(r"(?:^|[_/.])(?:RAS|CAS|WE|ACT)(?:[#_N]*)(?:$|[_/.])", re.I)
_CTRL_RE = re.compile(
r"(?:^|[_/.])(?:CS|CKE|ODT|RESET|ZQ)(?:[#_N]*)(?:$|[_/.])", re.I,
)
_HBM_RE = re.compile(
r"\bHBM(?:2E|3E|[23])?\b|H26M|MT54A|interposer",
re.I,
)
_DDR_GEN = [
("ddr5", re.compile(r"\bDDR5\b", re.I)),
("ddr4", re.compile(r"\bDDR4\b|MT41|K4A|IS43TR", re.I)),
("ddr3l", re.compile(r"\bDDR3L\b", re.I)),
("ddr3", re.compile(r"\bDDR3\b|K4B", re.I)),
("ddr2", re.compile(r"\bDDR2\b|MT47", re.I)),
("ddr1", re.compile(r"\bDDR1\b", re.I)),
]
_LPDDR_GEN = [
("lpddr5x", re.compile(r"LPDDR5X", re.I)),
("lpddr5", re.compile(r"LPDDR5", re.I)),
("lpddr4x", re.compile(r"LPDDR4X", re.I)),
("lpddr4", re.compile(r"LPDDR4", re.I)),
("lpddr3", re.compile(r"LPDDR3", re.I)),
("lpddr2", re.compile(r"LPDDR2", re.I)),
("lpddr-family", re.compile(r"\bLPDDR\b", re.I)),
]
_DDR4_PART = re.compile(r"MT41K|K4A|IS43TR|W631|EDY4016", re.I)
_MEMORY_PART = re.compile(r"MT41|MT47|K4A|K4B|IS43TR|W631|EDY4016", re.I)
_CONTROLLER_HINT = re.compile(
r"FPGA|XC7|XCKU|Artix|Kintex|Zynq|STM32|i\.?MX|RK33|controller|MIG|\bSoC\b",
re.I,
)
def _leaf(name: str) -> str:
n = normalize_kicad_hierarchy_net(name)
return n.split("/")[-1] if n else ""
def is_hbm_blob(text: str) -> bool:
return bool(_HBM_RE.search(text or ""))
def ddr_gen_from_text(text: str) -> str | None:
if is_hbm_blob(text):
return None
for gen, cre in _LPDDR_GEN:
if cre.search(text):
return gen
if _DDR4_PART.search(text):
return "ddr4"
for gen, cre in _DDR_GEN:
if cre.search(text):
return gen
return None
def hbm_gen_from_text(text: str) -> str | None:
if not is_hbm_blob(text):
return None
t = re.sub(r"[\s_\-]", "", text.upper())
for gen in ("HBM3E", "HBM2E", "HBM3", "HBM2", "HBM"):
if gen in t:
return gen.lower()
return "hbm-family"
def byte_lanes(net_names: list[str]) -> list[SignalGroup]:
dq: dict[int, str] = {}
dqs: dict[int, dict[str, str]] = {}
dm: dict[int, str] = {}
for n in net_names:
leaf = _leaf(n)
m = _DQ_RE.search(leaf)
if m and "DQS" not in leaf.upper():
dq[int(m.group(1))] = n
continue
m = _DQS_RE.search(leaf)
if m:
idx = int(m.group(1) or "0")
pol = (m.group(2) or "P").upper()[:1]
dqs.setdefault(idx, {})[pol] = n
continue
m = _DM_RE.search(leaf)
if m:
dm[int(m.group(1))] = n
lanes: list[SignalGroup] = []
lane_ids = set(i // 8 for i in dq) | set(dqs) | set(dm)
for lane in sorted(lane_ids):
roles: dict[str, str] = {}
nets: list[str] = []
for bit in range(lane * 8, lane * 8 + 8):
if bit in dq:
roles[f"DQ{bit}"] = dq[bit]
nets.append(dq[bit])
if lane in dqs:
for pol, net in dqs[lane].items():
roles[f"DQS{lane}_{pol}"] = net
nets.append(net)
if lane in dm:
roles[f"DM{lane}"] = dm[lane]
nets.append(dm[lane])
if nets:
lanes.append(SignalGroup(
kind="BYTE_LANE", id=f"BYTE_LANE_{lane}", nets=nets, roles=roles,
))
return lanes
def memory_groups(net_names: list[str]) -> list[SignalGroup]:
"""DQ/DQS lanes vs ADDRESS/COMMAND/CONTROL vs CLOCK. No invented numbers."""
groups: list[SignalGroup] = list(byte_lanes(net_names))
data_nets = {n for g in groups for n in g.nets}
def _take(kind: str, gid: str, pred) -> None:
nets = [n for n in net_names if pred(_leaf(n)) and n not in data_nets]
if nets:
groups.append(SignalGroup(kind=kind, id=gid, nets=nets, roles={})) # type: ignore[arg-type]
_take("CLOCK_GROUP", "CK", lambda lf: _CK_RE.search(lf) and not _WCK_RE.search(lf))
_take("CLOCK_GROUP", "WCK", lambda lf: bool(_WCK_RE.search(lf)))
_take("ADDRESS_GROUP", "ADDRESS", lambda lf: bool(_ADDR_RE.search(lf)) and not _CA_RE.search(lf))
_take("ADDRESS_GROUP", "CA", lambda lf: bool(_CA_RE.search(lf)))
_take("COMMAND_GROUP", "COMMAND", lambda lf: bool(_CMD_RE.search(lf)))
_take("CONTROL_GROUP", "CONTROL", lambda lf: bool(_CTRL_RE.search(lf)))
return groups
def data_vs_addr_overlap(groups: list[SignalGroup]) -> list[str]:
data = {n for g in groups if g.kind == "BYTE_LANE" for n in g.nets}
other = {
n for g in groups
if g.kind in {"ADDRESS_GROUP", "COMMAND_GROUP", "CONTROL_GROUP", "CLOCK_GROUP"}
for n in g.nets
}
return sorted(data & other)
def find_controller(graph: DesignGraph, memory: Component, nets: list[str]) -> Component | None:
netset = set(nets)
for comp in graph.components.values():
if comp.reference == memory.reference:
continue
if comp.component_type != ComponentType.IC:
continue
blob = _blob(comp, graph)
if is_hbm_blob(blob):
continue
if ddr_gen_from_text(blob) and _MEMORY_PART.search(blob):
continue
pins = set(comp.pins.values())
if pins & netset:
return comp
return None
def phys_id_for_gen(gen: str, ifaces: dict) -> str:
if gen.startswith("lpddr"):
pid = f"{gen}-pcb"
return pid if pid in ifaces else "lpddr-family-pcb"
if gen == "ddr-family":
return "ddr-family-sdram-pcb"
pid = f"{gen}-sdram-pcb"
return pid if pid in ifaces else "ddr-family-sdram-pcb"
def build_ddr_instance(
graph: DesignGraph,
memory: Component,
gen: str,
evidence: EvidenceKind,
ifaces: dict,
) -> PhysicalBusInstance:
iface_id = phys_id_for_gen(gen, ifaces)
iface = ifaces[iface_id]
nets = [n for n in memory.pins.values() if n]
groups = memory_groups(nets)
overlap = data_vs_addr_overlap(groups)
controller = find_controller(graph, memory, nets)
lanes = [g for g in groups if g.kind == "BYTE_LANE"]
incomplete = not lanes
mixed = bool(overlap)
status = "REVIEW" if incomplete or mixed or gen in {"ddr-family", "lpddr-family"} else "RECOGNIZED"
notes = (
f"Controller-aware DDR instance. memory={memory.reference}"
f"{(' controller=' + controller.reference) if controller else ' (no controller IC on nets)'}."
" No universal mm/ps or Z."
)
if incomplete:
notes += " Byte-lane grouping incomplete — REVIEW, not PASS."
if mixed:
notes += f" Address/command mixed into DQ: {overlap}. Not certified as DDR data."
status = "REVIEW"
host = controller.reference if controller else memory.reference
peers = [memory.reference]
if controller:
peers = [memory.reference]
group_nets = sorted({n for g in groups for n in g.nets})
return PhysicalBusInstance(
instance_id=f"{iface_id}:{memory.reference}",
logical_protocol_id=gen,
physical_interface_id=iface_id,
pcb_relevant=iface.pcb_relevant,
confidence=_EVIDENCE_SCORE[evidence],
evidence_kind=evidence,
recognition_status=status,
nets=group_nets,
groups=groups,
host_ref=host,
peer_refs=peers,
ambiguous_logical_ids=["ddr-family"] if gen == "ddr-family" else [],
notes=notes,
)
def recognize_hbm(graph: DesignGraph, ifaces: dict) -> list[PhysicalBusInstance]:
out: list[PhysicalBusInstance] = []
for comp in graph.components.values():
blob = _blob(comp, graph)
gen = hbm_gen_from_text(blob)
if not gen:
continue
pid = f"{gen}-package" if f"{gen}-package" in ifaces else "hbm-family-package"
iface = ifaces[pid]
out.append(PhysicalBusInstance(
instance_id=f"{pid}:{comp.reference}",
logical_protocol_id=gen if gen in {
"hbm", "hbm2", "hbm2e", "hbm3", "hbm3e", "hbm-family",
} else "hbm-family",
physical_interface_id=pid,
pcb_relevant="NO",
confidence=_EVIDENCE_SCORE["part" if comp.mpn else "silicon"],
evidence_kind="part" if comp.mpn else "silicon",
recognition_status="NOT_APPLICABLE",
nets=[],
groups=[],
host_ref=comp.reference,
notes=(
"HBM is package/interposer. Classic DDR PCB DQ rules do not apply. "
"pcb_relevant=NO."
),
))
return out
def recognize_ddr_instances(graph: DesignGraph, ifaces: dict) -> list[PhysicalBusInstance]:
memories: list[tuple[Component, str, EvidenceKind]] = []
for comp in graph.components.values():
blob = _blob(comp, graph)
if is_hbm_blob(blob):
continue
gen = ddr_gen_from_text(blob)
if not gen:
continue
if _CONTROLLER_HINT.search(blob) and not _MEMORY_PART.search(blob):
continue
kind: EvidenceKind = "part" if (comp.mpn or _DDR4_PART.search(blob)) else "silicon"
memories.append((comp, gen, kind))
out = [build_ddr_instance(graph, m, gen, kind, ifaces) for m, gen, kind in memories]
if out:
return out
if any(is_hbm_blob(_blob(c, graph)) for c in graph.components.values()):
return []
net_gen = None
for n in graph.nets:
g = ddr_gen_from_text(_leaf(n))
if g:
net_gen = g
break
if not net_gen:
dq = [n for n in graph.nets if _DQ_RE.search(_leaf(n)) and "DQS" not in _leaf(n).upper()]
dqs = [n for n in graph.nets if "DQS" in _leaf(n).upper()]
if len(dq) >= 8 and dqs:
net_gen = "ddr-family"
else:
return []
groups = memory_groups(list(graph.nets))
pid = phys_id_for_gen(net_gen, ifaces)
iface = ifaces[pid]
return [PhysicalBusInstance(
instance_id=f"{pid}:nets",
logical_protocol_id=net_gen,
physical_interface_id=pid,
pcb_relevant=iface.pcb_relevant,
confidence=_EVIDENCE_SCORE["net_name"],
evidence_kind="net_name",
recognition_status="REVIEW",
nets=sorted({n for g in groups for n in g.nets}),
groups=groups,
notes="DDR nets without a memory MPN. Controller-aware grouping only. No universal mm/ps.",
)]
@@ -94,6 +94,8 @@ def _constraint_for(iface: PhysicalInterface, check: str) -> ProtocolConstraint
"topology": "topology",
"byte_lane_mapping": "topology",
"byte_lane_skew": "byte_to_byte_skew",
"ck_to_command_skew": "ck_to_command_skew",
"ck_to_address_skew": "ck_to_address_skew",
"length": "max_length",
"clock_length": "max_length",
"data_length": "max_length",
@@ -302,7 +304,7 @@ def protocol_exam(
from backend.periscopex.protocol_l1 import certify_l1
from backend.periscopex.protocol_l2 import LEVEL as L2_LEVEL, certify_l2
from backend.periscopex.protocol_report import (
PACK_MACROPHASE as L5_MACRO,
PACK_MACROPHASE as L8_MACRO,
attach_instance_report,
result_rank,
)
@@ -313,7 +315,7 @@ def protocol_exam(
instances = recognize_physical_buses(graph, cat)
if not instances:
sec = empty_protocol_section()
sec.macrophase = L5_MACRO
sec.macrophase = L8_MACRO
return sec, []
l0, findings = certify_l0(graph, instances, cat)
l1, l1_findings_out = certify_l1(graph, instances, layout, cat)
@@ -343,7 +345,7 @@ def protocol_exam(
dumps.sort(key=lambda r: result_rank(str(r.get("worst_result") or "UNKNOWN")))
sec = ProtocolCertificationSection(
schema_version=SCHEMA_VERSION,
macrophase=L5_MACRO,
macrophase=L8_MACRO,
recognized_instances=dumps,
message="",
max_level_reached=L2_LEVEL,
+83 -21
View File
@@ -39,6 +39,7 @@ L1_CHECKS = frozenset({
"dq_to_dqs_skew",
"byte_lane_skew",
"ck_to_command_skew",
"ck_to_address_skew",
"clock_to_data_skew",
"vias",
"via_count",
@@ -182,6 +183,57 @@ def _skip_grouping(check: str, mand: MandatoryClass, inst: PhysicalBusInstance)
)
def _grouping_recon(
inst: PhysicalBusInstance,
check: str,
mand: MandatoryClass,
kind: str,
) -> L1CheckResult:
lanes = [g for g in inst.groups if g.kind == "BYTE_LANE"]
addr_cmd = [
g for g in inst.groups
if g.kind in {"ADDRESS_GROUP", "COMMAND_GROUP", "CONTROL_GROUP", "CLOCK_GROUP"}
]
data_nets = {n for g in lanes for n in g.nets}
other_nets = {n for g in addr_cmd for n in g.nets}
mixed = sorted(data_nets & other_nets)
if mixed:
return _pack(
check, "UNKNOWN", mand, nets=mixed, value_kind=kind,
notes=(
f"Interfaccia {inst.physical_interface_id} non certificata a L1 "
f"per grouping misto DQ e ADDRESS/COMMAND ({mixed}). Not PASS. "
"No universal mm/ps."
),
)
if check == "cmd_dat_grouping":
if not lanes or not addr_cmd:
return _skip_grouping(check, mand, inst)
return _pack(
check, "PASS", mand, nets=_instance_nets(inst), value_kind=kind,
notes=(
"DQ/DQS vs ADDRESS/COMMAND/CONTROL/CLOCK reconstructed separately. "
"Controller-aware. L1 is not electrical. No universal mm/ps."
),
)
if not lanes:
return _skip_grouping(check, mand, inst)
has_dqs = any(
k.upper().startswith("DQS") for g in lanes for k in g.roles
)
if check == "dqs_relationship_if_present" and not has_dqs:
return _skip_grouping(check, mand, inst)
if check == "byte_lane_mapping" and not has_dqs:
return _skip_grouping(check, mand, inst)
return _pack(
check, "PASS", mand, nets=_instance_nets(inst), value_kind=kind,
notes=(
"Byte-lane DQ/DQS grouping reconstructed. Not a global length-match PASS. "
"L1 is not electrical certification. No universal mm/ps."
),
)
def certify_instance_l1(
graph: DesignGraph,
inst: PhysicalBusInstance,
@@ -209,30 +261,40 @@ def _run_l1_check(
mand = _mandatory(iface, check)
cons = _constraint_for(iface, check)
kind = cons.value_kind if cons else "UNKNOWN"
if layout is None:
return _skip_no_geometry(check, mand, inst)
grouping_names = {
grouping_recon = {
"byte_lane_mapping", "dq_group", "dqs_relationship_if_present",
"dq_to_dqs_skew", "byte_lane_skew",
"cmd_dat_grouping",
}
skew_checks = {
"dq_to_dqs_skew", "byte_lane_skew", "ck_to_command_skew",
"ck_to_address_skew", "clock_to_data_skew", "lane_relationships",
"intra_pair_skew", "pair_skew",
}
lanes = [g for g in inst.groups if g.kind == "BYTE_LANE"]
if check in grouping_names and not lanes:
return _skip_grouping(check, mand, inst)
if check == "cmd_dat_grouping":
cmd = [g for g in inst.groups if g.kind in {"COMMAND_GROUP", "ADDRESS_GROUP", "CLOCK_GROUP"}]
if not cmd:
return _skip_grouping(check, mand, inst)
return _pack(
check, "PASS", mand, nets=_instance_nets(inst), value_kind=kind,
notes="CMD/DAT grouping present. L1 is not electrical certification.",
)
if check in {"byte_lane_mapping", "dq_group", "dqs_relationship_if_present"}:
return _pack(
check, "PASS", mand, nets=_instance_nets(inst), value_kind=kind,
notes="Geometric grouping present. L1 is not electrical certification.",
)
if check in grouping_recon:
return _grouping_recon(inst, check, mand, kind)
if check in skew_checks:
if check in {
"dq_to_dqs_skew", "byte_lane_skew",
} and not lanes:
return _skip_grouping(check, mand, inst)
if layout is None:
dep = kind if kind in {
"CONTROLLER_DEPENDENT", "PHY_DEPENDENT", "VENDOR_DEPENDENT",
"UNKNOWN", "MISSING_SOURCE",
} else "CONTROLLER_DEPENDENT"
return _pack(
check, dep, mand, nets=_instance_nets(inst), value_kind=kind,
notes=(
"Controller/PHY dependent skew — no universal mm/ps. "
"L1 grouping is not a length-match PASS. Not electrical."
),
)
if layout is None:
return _skip_no_geometry(check, mand, inst)
if check == "topology":
nets = _instance_nets(inst)
@@ -291,7 +353,7 @@ def _run_l1_check(
if check == "byte_lane_skew":
return _lane_spread(lanes, layout, cons, mand, kind, "byte_lane_skew")
if check in {"ck_to_command_skew", "clock_to_data_skew", "lane_relationships"}:
if check in {"ck_to_command_skew", "ck_to_address_skew", "clock_to_data_skew", "lane_relationships"}:
measured = _group_spread(inst, layout)
verdict = geometric_verdict(cons, measured)
return _pack(
@@ -267,6 +267,7 @@ def recognize_physical_buses(
out: list[PhysicalBusInstance] = []
out.extend(_recognize_declared(graph, ifaces, logical))
out.extend(_recognize_usb(graph, ifaces))
out.extend(_recognize_hbm(graph, ifaces))
out.extend(_recognize_ddr(graph, ifaces))
out.extend(_recognize_axi(graph, ifaces))
return _dedupe_instances(out)
@@ -417,73 +418,13 @@ def _ddr_gen_from_text(text: str) -> str | None:
def _recognize_ddr(graph: DesignGraph, ifaces: dict) -> list[PhysicalBusInstance]:
memories: list[tuple[Component, str, EvidenceKind]] = []
for comp in graph.components.values():
blob = _blob(comp, graph)
gen = _ddr_gen_from_text(blob)
if not gen:
continue
kind: EvidenceKind = "part" if (comp.mpn or _DDR4_PART.search(blob)) else "silicon"
memories.append((comp, gen, kind))
net_gen = None
for n in graph.nets:
g = _ddr_gen_from_text(_leaf(n))
if g:
net_gen = g
break
if not memories and not net_gen:
dq = [n for n in graph.nets if _DQ_RE.search(_leaf(n)) and "DQS" not in _leaf(n).upper()]
dqs = [n for n in graph.nets if "DQS" in _leaf(n).upper()]
if len(dq) >= 8 and dqs:
net_gen = "ddr-family"
else:
return []
from backend.periscopex.protocol_ddr import recognize_ddr_instances
return recognize_ddr_instances(graph, ifaces)
gen = memories[0][1] if memories else net_gen or "ddr-family"
phys = f"{gen}-sdram-pcb" if f"{gen}-sdram-pcb" in ifaces else "ddr-family-sdram-pcb"
if gen == "ddr-family":
phys = "ddr-family-sdram-pcb"
iface = ifaces[phys]
host = memories[0][0].reference if memories else ""
evidence: EvidenceKind = memories[0][2] if memories else "net_name"
all_nets = list(graph.nets)
if host:
host_nets = list(graph.components[host].pins.values())
all_nets = host_nets or all_nets
lanes = _byte_lanes(all_nets)
groups = list(lanes)
ck = [n for n in all_nets if _CK_RE.search(_leaf(n))]
if ck:
groups.append(SignalGroup(kind="CLOCK_GROUP", id="CK", nets=ck, roles={}))
addr = [n for n in all_nets if _ADDR_RE.search(_leaf(n))]
if addr:
groups.append(SignalGroup(kind="ADDRESS_GROUP", id="ADDRESS", nets=addr, roles={}))
cmd = [n for n in all_nets if _CMD_RE.search(_leaf(n))]
if cmd:
groups.append(SignalGroup(kind="COMMAND_GROUP", id="COMMAND_CONTROL", nets=cmd, roles={}))
nets = sorted({n for g in groups for n in g.nets})
incomplete = bool(memories) and not lanes
status: RecognitionStatus = "REVIEW" if incomplete or gen == "ddr-family" else "RECOGNIZED"
notes = ""
if incomplete:
notes = "DDR device present but byte-lane grouping incomplete — REVIEW, not FAIL."
if gen == "ddr-family":
notes = (notes + " " if notes else "") + "DDR generation not unique."
return [PhysicalBusInstance(
instance_id=f"{phys}:{host or 'nets'}",
logical_protocol_id=gen,
physical_interface_id=phys,
pcb_relevant=iface.pcb_relevant,
confidence=_EVIDENCE_SCORE[evidence],
evidence_kind=evidence,
recognition_status=status,
nets=nets,
groups=groups,
host_ref=host,
peer_refs=[c.reference for c, _, _ in memories],
ambiguous_logical_ids=["ddr-family"] if gen == "ddr-family" else [],
notes=notes,
)]
def _recognize_hbm(graph: DesignGraph, ifaces: dict) -> list[PhysicalBusInstance]:
from backend.periscopex.protocol_ddr import recognize_hbm
return recognize_hbm(graph, ifaces)
def _recognize_axi(graph: DesignGraph, ifaces: dict) -> list[PhysicalBusInstance]:
@@ -12,7 +12,7 @@ from backend.periscopex.protocol_catalog import PhysicalInterface, ProtocolConst
from backend.periscopex.protocol_l0 import _constraint_for
from backend.periscopex.protocol_recognize import PhysicalBusInstance
PACK_MACROPHASE = "M5"
PACK_MACROPHASE = "M8"
_SKIP = frozenset({
"UNKNOWN", "MISSING_SOURCE", "VENDOR_DEPENDENT",
@@ -2,6 +2,13 @@
What's new in Periscope.
## 2.70.0 — 2026-09-22 — Protocol certification M8 (DDR instance + L1 grouping)
DDR **physical instances** are controller-aware: one instance per memory device, DQ/DQS **byte lanes** separate from ADDRESS / COMMAND / CONTROL / CLOCK. Skew/length stay **CONTROLLER_DEPENDENT / PHY_DEPENDENT / UNKNOWN** — no universal mm/ps/ohm. HBM is package/interposer (`pcb_relevant: NO`); classic DDR PCB DQ rules are not applied. M6 numeric packs and L3 solver are not in this release.
- [New] `protocol_ddr.py`; L1 grouping reconstruction without PCB geometry.
- [New] pytest `tests/pcb/test_protocol_ddr_m8.py`.
## 2.69.0 — 2026-09-22 — Protocol certification M5 (Protocolli report)
Report section **Protocolli**: recognised instances, max level L0L3 (L3 is a visible not-run skip, no solver), each check with measured / limit / margin / source / method, traceability chain net → group → physical interface → protocol → constraint → document → section. FAIL is listed first and styled as Error — not hidden under Warning (HubAudio). Visible skips stay on the page. No pack numbers, no invented Z, no OpenEMS.
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "periscope-web",
"version": "2.69.0",
"version": "2.70.0",
"private": true,
"scripts": {
"sync-version": "node scripts/sync-version.mjs",
+170
View File
@@ -0,0 +1,170 @@
"""M8 DDR instance + L1 grouping: byte lanes, addr/cmd/ctrl/ck, controller-aware, HBM N/A."""
from __future__ import annotations
import json
from backend.periscopex.models import (
Component,
ComponentType,
DesignGraph,
Net,
NetType,
PinConnection,
)
from backend.periscopex.protocol_l0 import protocol_exam
from backend.periscopex.protocol_l1 import certify_l1
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 _ddr4_pins() -> dict[str, str]:
pins = {str(i): f"DDR4_DQ{i}" for i in range(8)}
pins.update({
"8": "DDR4_DQS0_P", "9": "DDR4_DQS0_N", "10": "DDR4_DM0",
"11": "DDR4_CK_P", "12": "DDR4_CK_N",
"13": "DDR4_A0", "14": "DDR4_RAS",
"15": "DDR4_CS", "16": "DDR4_CKE",
})
return pins
def _fpga_ddr_graph() -> DesignGraph:
pins = _ddr4_pins()
mem = _ic("U5", pins, mpn="MT41K256M16TW", value="DDR4")
fpga = _ic("U3", dict(pins), mpn="XC7A100T", value="Artix-7 DDR3/DDR4 controller")
nets = {n: _net(n, ("U5", p), ("U3", p)) for p, n in pins.items()}
return DesignGraph(components={"U5": mem, "U3": fpga}, nets=nets)
def _two_ddr_graph() -> DesignGraph:
a = _ddr4_pins()
b = {p: n.replace("DDR4_", "DDR4B_") for p, n in a.items()}
m1 = _ic("U5", a, mpn="MT41K256M16TW", value="DDR4")
m2 = _ic("U6", b, mpn="MT41K256M16TW", value="DDR4")
fpga = _ic(
"U3",
{**{f"a{p}": n for p, n in a.items()}, **{f"b{p}": n for p, n in b.items()}},
mpn="XC7A100T", value="FPGA memory controller",
)
nets = {n: _net(n, ("U5", p), ("U3", f"a{p}")) for p, n in a.items()}
nets.update({n: _net(n, ("U6", p), ("U3", f"b{p}")) for p, n in b.items()})
return DesignGraph(components={"U5": m1, "U6": m2, "U3": fpga}, nets=nets)
def _hbm_graph() -> DesignGraph:
pins = {str(i): f"HBM_DQ{i}" for i in range(8)}
pins["8"] = "HBM_DQS0_P"
return DesignGraph(
components={"U8": _ic("U8", pins, mpn="H26M64202AMR", value="HBM2e stack")},
nets={n: _net(n, ("U8", p)) for p, n in pins.items()},
)
def test_controller_aware_groups_split():
insts = recognize_physical_buses(_fpga_ddr_graph())
ddr = [i for i in insts if i.logical_protocol_id == "ddr4"]
assert len(ddr) == 1
one = ddr[0]
assert one.host_ref == "U3"
assert "U5" in one.peer_refs
kinds = {g.kind for g in one.groups}
assert "BYTE_LANE" in kinds
assert "CLOCK_GROUP" in kinds
assert "ADDRESS_GROUP" in kinds
assert "COMMAND_GROUP" in kinds
assert "CONTROL_GROUP" in kinds
data = {n for g in one.groups if g.kind == "BYTE_LANE" for n in g.nets}
addr = {n for g in one.groups if g.kind == "ADDRESS_GROUP" for n in g.nets}
cmd = {n for g in one.groups if g.kind == "COMMAND_GROUP" for n in g.nets}
ctrl = {n for g in one.groups if g.kind == "CONTROL_GROUP" for n in g.nets}
assert "DDR4_DQ0" in data and "DDR4_DQS0_P" in data
assert "DDR4_A0" in addr
assert "DDR4_RAS" in cmd
assert "DDR4_CS" in ctrl or "DDR4_CKE" in ctrl
assert not (data & addr)
assert not (data & cmd)
assert not (data & ctrl)
blob = json.dumps(one.model_dump())
assert "90" not in blob
assert "ohm" not in blob.lower()
assert "50ps" not in blob.lower()
def test_two_ddr4_are_two_instances():
insts = recognize_physical_buses(_two_ddr_graph())
ddr = [i for i in insts if i.logical_protocol_id == "ddr4"]
assert len(ddr) == 2
refs = {tuple(sorted(i.peer_refs)) for i in ddr}
assert ("U5",) in refs and ("U6",) in refs
def test_l1_grouping_pass_without_layout_skew_stays_dependent():
g = _fpga_ddr_graph()
insts = recognize_physical_buses(g)
by, findings = certify_l1(g, insts, layout=None)
rows = [r for i in insts if i.logical_protocol_id == "ddr4" for r in by[i.instance_id]]
mapping = [r for r in rows if r.check == "byte_lane_mapping"]
assert mapping and mapping[0].result == "PASS"
skew = [r for r in rows if r.check == "dq_to_dqs_skew"]
assert skew
assert skew[0].result in {"PHY_DEPENDENT", "CONTROLLER_DEPENDENT", "VENDOR_DEPENDENT", "UNKNOWN"}
assert skew[0].result != "PASS"
assert skew[0].limit_mm is None
blob = json.dumps([r.model_dump() for r in rows])
assert "90" not in blob
assert "universal" in blob.lower() or "controller" in blob.lower()
def test_hbm_not_classic_ddr_dq_rules():
insts = recognize_physical_buses(_hbm_graph())
hbm = [i for i in insts if "hbm" in i.logical_protocol_id]
ddr = [i for i in insts if i.logical_protocol_id.startswith("ddr")]
assert hbm
assert not ddr
one = hbm[0]
assert one.pcb_relevant == "NO"
assert one.nets == []
assert one.groups == []
assert one.physical_interface_id.endswith("-package")
sec, findings = protocol_exam(_hbm_graph())
assert sec.macrophase == "M8"
assert all(f.status != "ERROR" for f in findings)
rows = [
c for r in sec.recognized_instances if "hbm" in str(r.get("logical_protocol_id"))
for c in (r.get("l0_checks") or []) + (r.get("l1_checks") or [])
]
assert rows
assert all(c.get("result") == "NOT_APPLICABLE" for c in rows)
blob = json.dumps(one.model_dump())
assert "BYTE_LANE" not in blob
def test_incomplete_grouping_not_pass():
pins = {str(i): f"MEM_DAT{i}" for i in range(8)}
g = DesignGraph(
components={"U5": _ic("U5", pins, mpn="MT41K256M16TW", value="DDR4")},
nets={n: _net(n, ("U5", p)) for p, n in pins.items()},
)
insts = recognize_physical_buses(g)
ddr = [i for i in insts if "ddr" in i.logical_protocol_id]
assert ddr
assert not any(g.kind == "BYTE_LANE" for g in ddr[0].groups)
by, _ = certify_l1(g, insts, layout=None)
rows = [r for i in ddr for r in by[i.instance_id]]
mapping = [r for r in rows if r.check == "byte_lane_mapping"]
assert mapping
assert mapping[0].result != "PASS"
+2 -2
View File
@@ -130,8 +130,8 @@ def test_axi_internal_l0_not_applicable_not_fail():
assert all(r.result != "FAIL" for r in rows)
sec, findings = protocol_exam(_axi_graph())
assert sec.max_level_reached == "L2"
from backend.periscopex.protocol_report import PACK_MACROPHASE as L5_MACRO
assert sec.macrophase == L5_MACRO
from backend.periscopex.protocol_report import PACK_MACROPHASE as L8_MACRO
assert sec.macrophase == L8_MACRO
assert all(f.status != "ERROR" for f in findings)
assert all("AXI_AWVALID" not in (f.net or "") for f in findings if f.rule_id == "PE-PRT-L0-001")
+1 -1
View File
@@ -166,7 +166,7 @@ def test_axi_internal_l1_not_applicable():
assert all(r.result != "FAIL" for r in rows)
sec, exam_findings = protocol_exam(_axi_graph(), layout=LayoutGraph())
assert sec.max_level_reached == "L2"
assert sec.macrophase == "M5"
assert sec.macrophase == "M8"
assert all(f.status != "ERROR" for f in exam_findings)
blob = json.dumps([r.model_dump() for r in rows])
assert "electrical certification" not in blob.lower() or "not electrical" in blob.lower()
+1 -1
View File
@@ -205,7 +205,7 @@ def test_axi_internal_l2_not_applicable():
assert all(r.result == "NOT_APPLICABLE" for r in rows)
sec, findings = protocol_exam(_axi_graph())
assert sec.max_level_reached == "L2"
assert sec.macrophase == "M5"
assert sec.macrophase == "M8"
assert all(f.status != "ERROR" for f in findings)
blob = json.dumps([r.model_dump() for r in rows])
assert "L0/L1 are not electrical" in blob
+1 -1
View File
@@ -96,7 +96,7 @@ def test_result_rank_fail_before_warning():
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"
assert sec.macrophase == "M8"
usb = next(
r for r in sec.recognized_instances
if "usb" in str(r.get("logical_protocol_id"))