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:
@@ -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,
|
||||
|
||||
@@ -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:
|
||||
|
||||
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, "PASS", mand, nets=_instance_nets(inst), value_kind=kind,
|
||||
notes="CMD/DAT grouping present. L1 is not electrical certification.",
|
||||
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 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 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 L0–L3 (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,6 +1,6 @@
|
||||
{
|
||||
"name": "periscope-web",
|
||||
"version": "2.69.0",
|
||||
"version": "2.70.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"sync-version": "node scripts/sync-version.mjs",
|
||||
|
||||
@@ -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"
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"))
|
||||
|
||||
Reference in New Issue
Block a user