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",
|
"dq_to_dqs_skew",
|
||||||
"byte_lane_skew",
|
"byte_lane_skew",
|
||||||
"ck_to_command_skew",
|
"ck_to_command_skew",
|
||||||
|
"ck_to_address_skew",
|
||||||
"topology",
|
"topology",
|
||||||
"impedance",
|
"impedance",
|
||||||
"termination",
|
"termination",
|
||||||
@@ -574,7 +575,7 @@ def build_m0_catalog() -> dict[str, Any]:
|
|||||||
cls: SourceClass = "VENDOR" if source_type != "STANDARD" else "NORMATIVE"
|
cls: SourceClass = "VENDOR" if source_type != "STANDARD" else "NORMATIVE"
|
||||||
params = [
|
params = [
|
||||||
"impedance", "max_length", "dq_to_dqs_skew", "byte_to_byte_skew",
|
"impedance", "max_length", "dq_to_dqs_skew", "byte_to_byte_skew",
|
||||||
"topology", "termination",
|
"topology", "termination", "ck_to_command_skew", "ck_to_address_skew",
|
||||||
]
|
]
|
||||||
return [
|
return [
|
||||||
_c(pid, p, kind, source_type=source_type, source_class=cls)
|
_c(pid, p, kind, source_type=source_type, source_class=cls)
|
||||||
|
|||||||
@@ -1041,6 +1041,7 @@
|
|||||||
"dq_to_dqs_skew",
|
"dq_to_dqs_skew",
|
||||||
"byte_lane_skew",
|
"byte_lane_skew",
|
||||||
"ck_to_command_skew",
|
"ck_to_command_skew",
|
||||||
|
"ck_to_address_skew",
|
||||||
"topology",
|
"topology",
|
||||||
"impedance",
|
"impedance",
|
||||||
"termination",
|
"termination",
|
||||||
@@ -1132,6 +1133,34 @@
|
|||||||
"conditions": [],
|
"conditions": [],
|
||||||
"value_origin": "SPEC",
|
"value_origin": "SPEC",
|
||||||
"typical": false
|
"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."
|
"notes": "No universal mm/ps for DDR."
|
||||||
@@ -1154,6 +1183,7 @@
|
|||||||
"dq_to_dqs_skew",
|
"dq_to_dqs_skew",
|
||||||
"byte_lane_skew",
|
"byte_lane_skew",
|
||||||
"ck_to_command_skew",
|
"ck_to_command_skew",
|
||||||
|
"ck_to_address_skew",
|
||||||
"topology",
|
"topology",
|
||||||
"impedance",
|
"impedance",
|
||||||
"termination",
|
"termination",
|
||||||
@@ -1245,6 +1275,34 @@
|
|||||||
"conditions": [],
|
"conditions": [],
|
||||||
"value_origin": "SPEC",
|
"value_origin": "SPEC",
|
||||||
"typical": false
|
"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."
|
"notes": "Controller/PHY dependent. Zero universal skew/length."
|
||||||
@@ -1267,6 +1325,7 @@
|
|||||||
"dq_to_dqs_skew",
|
"dq_to_dqs_skew",
|
||||||
"byte_lane_skew",
|
"byte_lane_skew",
|
||||||
"ck_to_command_skew",
|
"ck_to_command_skew",
|
||||||
|
"ck_to_address_skew",
|
||||||
"topology",
|
"topology",
|
||||||
"impedance",
|
"impedance",
|
||||||
"termination",
|
"termination",
|
||||||
@@ -1358,6 +1417,34 @@
|
|||||||
"conditions": [],
|
"conditions": [],
|
||||||
"value_origin": "SPEC",
|
"value_origin": "SPEC",
|
||||||
"typical": false
|
"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."
|
"notes": "Controller/PHY dependent. Zero universal skew/length."
|
||||||
@@ -1380,6 +1467,7 @@
|
|||||||
"dq_to_dqs_skew",
|
"dq_to_dqs_skew",
|
||||||
"byte_lane_skew",
|
"byte_lane_skew",
|
||||||
"ck_to_command_skew",
|
"ck_to_command_skew",
|
||||||
|
"ck_to_address_skew",
|
||||||
"topology",
|
"topology",
|
||||||
"impedance",
|
"impedance",
|
||||||
"termination",
|
"termination",
|
||||||
@@ -1471,6 +1559,34 @@
|
|||||||
"conditions": [],
|
"conditions": [],
|
||||||
"value_origin": "SPEC",
|
"value_origin": "SPEC",
|
||||||
"typical": false
|
"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."
|
"notes": "Controller/PHY dependent. Zero universal skew/length."
|
||||||
@@ -1493,6 +1609,7 @@
|
|||||||
"dq_to_dqs_skew",
|
"dq_to_dqs_skew",
|
||||||
"byte_lane_skew",
|
"byte_lane_skew",
|
||||||
"ck_to_command_skew",
|
"ck_to_command_skew",
|
||||||
|
"ck_to_address_skew",
|
||||||
"topology",
|
"topology",
|
||||||
"impedance",
|
"impedance",
|
||||||
"termination",
|
"termination",
|
||||||
@@ -1584,6 +1701,34 @@
|
|||||||
"conditions": [],
|
"conditions": [],
|
||||||
"value_origin": "SPEC",
|
"value_origin": "SPEC",
|
||||||
"typical": false
|
"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."
|
"notes": "Controller/PHY dependent. Zero universal skew/length."
|
||||||
@@ -1606,6 +1751,7 @@
|
|||||||
"dq_to_dqs_skew",
|
"dq_to_dqs_skew",
|
||||||
"byte_lane_skew",
|
"byte_lane_skew",
|
||||||
"ck_to_command_skew",
|
"ck_to_command_skew",
|
||||||
|
"ck_to_address_skew",
|
||||||
"topology",
|
"topology",
|
||||||
"impedance",
|
"impedance",
|
||||||
"termination",
|
"termination",
|
||||||
@@ -1697,6 +1843,34 @@
|
|||||||
"conditions": [],
|
"conditions": [],
|
||||||
"value_origin": "SPEC",
|
"value_origin": "SPEC",
|
||||||
"typical": false
|
"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."
|
"notes": "Controller/PHY dependent. Zero universal skew/length."
|
||||||
@@ -1719,6 +1893,7 @@
|
|||||||
"dq_to_dqs_skew",
|
"dq_to_dqs_skew",
|
||||||
"byte_lane_skew",
|
"byte_lane_skew",
|
||||||
"ck_to_command_skew",
|
"ck_to_command_skew",
|
||||||
|
"ck_to_address_skew",
|
||||||
"topology",
|
"topology",
|
||||||
"impedance",
|
"impedance",
|
||||||
"termination",
|
"termination",
|
||||||
@@ -1810,6 +1985,34 @@
|
|||||||
"conditions": [],
|
"conditions": [],
|
||||||
"value_origin": "SPEC",
|
"value_origin": "SPEC",
|
||||||
"typical": false
|
"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."
|
"notes": "Controller/PHY dependent. Zero universal skew/length."
|
||||||
@@ -1833,6 +2036,7 @@
|
|||||||
"dq_to_dqs_skew",
|
"dq_to_dqs_skew",
|
||||||
"byte_lane_skew",
|
"byte_lane_skew",
|
||||||
"ck_to_command_skew",
|
"ck_to_command_skew",
|
||||||
|
"ck_to_address_skew",
|
||||||
"topology",
|
"topology",
|
||||||
"impedance",
|
"impedance",
|
||||||
"termination",
|
"termination",
|
||||||
@@ -1924,6 +2128,34 @@
|
|||||||
"conditions": [],
|
"conditions": [],
|
||||||
"value_origin": "SPEC",
|
"value_origin": "SPEC",
|
||||||
"typical": false
|
"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": ""
|
"notes": ""
|
||||||
@@ -1948,6 +2180,7 @@
|
|||||||
"dq_to_dqs_skew",
|
"dq_to_dqs_skew",
|
||||||
"byte_lane_skew",
|
"byte_lane_skew",
|
||||||
"ck_to_command_skew",
|
"ck_to_command_skew",
|
||||||
|
"ck_to_address_skew",
|
||||||
"topology",
|
"topology",
|
||||||
"impedance",
|
"impedance",
|
||||||
"termination",
|
"termination",
|
||||||
@@ -2039,6 +2272,34 @@
|
|||||||
"conditions": [],
|
"conditions": [],
|
||||||
"value_origin": "SPEC",
|
"value_origin": "SPEC",
|
||||||
"typical": false
|
"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": ""
|
"notes": ""
|
||||||
@@ -2063,6 +2324,7 @@
|
|||||||
"dq_to_dqs_skew",
|
"dq_to_dqs_skew",
|
||||||
"byte_lane_skew",
|
"byte_lane_skew",
|
||||||
"ck_to_command_skew",
|
"ck_to_command_skew",
|
||||||
|
"ck_to_address_skew",
|
||||||
"topology",
|
"topology",
|
||||||
"impedance",
|
"impedance",
|
||||||
"termination",
|
"termination",
|
||||||
@@ -2154,6 +2416,34 @@
|
|||||||
"conditions": [],
|
"conditions": [],
|
||||||
"value_origin": "SPEC",
|
"value_origin": "SPEC",
|
||||||
"typical": false
|
"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": ""
|
"notes": ""
|
||||||
@@ -2178,6 +2468,7 @@
|
|||||||
"dq_to_dqs_skew",
|
"dq_to_dqs_skew",
|
||||||
"byte_lane_skew",
|
"byte_lane_skew",
|
||||||
"ck_to_command_skew",
|
"ck_to_command_skew",
|
||||||
|
"ck_to_address_skew",
|
||||||
"topology",
|
"topology",
|
||||||
"impedance",
|
"impedance",
|
||||||
"termination",
|
"termination",
|
||||||
@@ -2269,6 +2560,34 @@
|
|||||||
"conditions": [],
|
"conditions": [],
|
||||||
"value_origin": "SPEC",
|
"value_origin": "SPEC",
|
||||||
"typical": false
|
"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": ""
|
"notes": ""
|
||||||
@@ -2293,6 +2612,7 @@
|
|||||||
"dq_to_dqs_skew",
|
"dq_to_dqs_skew",
|
||||||
"byte_lane_skew",
|
"byte_lane_skew",
|
||||||
"ck_to_command_skew",
|
"ck_to_command_skew",
|
||||||
|
"ck_to_address_skew",
|
||||||
"topology",
|
"topology",
|
||||||
"impedance",
|
"impedance",
|
||||||
"termination",
|
"termination",
|
||||||
@@ -2384,6 +2704,34 @@
|
|||||||
"conditions": [],
|
"conditions": [],
|
||||||
"value_origin": "SPEC",
|
"value_origin": "SPEC",
|
||||||
"typical": false
|
"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": ""
|
"notes": ""
|
||||||
@@ -2408,6 +2756,7 @@
|
|||||||
"dq_to_dqs_skew",
|
"dq_to_dqs_skew",
|
||||||
"byte_lane_skew",
|
"byte_lane_skew",
|
||||||
"ck_to_command_skew",
|
"ck_to_command_skew",
|
||||||
|
"ck_to_address_skew",
|
||||||
"topology",
|
"topology",
|
||||||
"impedance",
|
"impedance",
|
||||||
"termination",
|
"termination",
|
||||||
@@ -2499,6 +2848,34 @@
|
|||||||
"conditions": [],
|
"conditions": [],
|
||||||
"value_origin": "SPEC",
|
"value_origin": "SPEC",
|
||||||
"typical": false
|
"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": ""
|
"notes": ""
|
||||||
@@ -2523,6 +2900,7 @@
|
|||||||
"dq_to_dqs_skew",
|
"dq_to_dqs_skew",
|
||||||
"byte_lane_skew",
|
"byte_lane_skew",
|
||||||
"ck_to_command_skew",
|
"ck_to_command_skew",
|
||||||
|
"ck_to_address_skew",
|
||||||
"topology",
|
"topology",
|
||||||
"impedance",
|
"impedance",
|
||||||
"termination",
|
"termination",
|
||||||
@@ -2614,6 +2992,34 @@
|
|||||||
"conditions": [],
|
"conditions": [],
|
||||||
"value_origin": "SPEC",
|
"value_origin": "SPEC",
|
||||||
"typical": false
|
"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": ""
|
"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",
|
"topology": "topology",
|
||||||
"byte_lane_mapping": "topology",
|
"byte_lane_mapping": "topology",
|
||||||
"byte_lane_skew": "byte_to_byte_skew",
|
"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",
|
"length": "max_length",
|
||||||
"clock_length": "max_length",
|
"clock_length": "max_length",
|
||||||
"data_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_l1 import certify_l1
|
||||||
from backend.periscopex.protocol_l2 import LEVEL as L2_LEVEL, certify_l2
|
from backend.periscopex.protocol_l2 import LEVEL as L2_LEVEL, certify_l2
|
||||||
from backend.periscopex.protocol_report import (
|
from backend.periscopex.protocol_report import (
|
||||||
PACK_MACROPHASE as L5_MACRO,
|
PACK_MACROPHASE as L8_MACRO,
|
||||||
attach_instance_report,
|
attach_instance_report,
|
||||||
result_rank,
|
result_rank,
|
||||||
)
|
)
|
||||||
@@ -313,7 +315,7 @@ def protocol_exam(
|
|||||||
instances = recognize_physical_buses(graph, cat)
|
instances = recognize_physical_buses(graph, cat)
|
||||||
if not instances:
|
if not instances:
|
||||||
sec = empty_protocol_section()
|
sec = empty_protocol_section()
|
||||||
sec.macrophase = L5_MACRO
|
sec.macrophase = L8_MACRO
|
||||||
return sec, []
|
return sec, []
|
||||||
l0, findings = certify_l0(graph, instances, cat)
|
l0, findings = certify_l0(graph, instances, cat)
|
||||||
l1, l1_findings_out = certify_l1(graph, instances, layout, 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")))
|
dumps.sort(key=lambda r: result_rank(str(r.get("worst_result") or "UNKNOWN")))
|
||||||
sec = ProtocolCertificationSection(
|
sec = ProtocolCertificationSection(
|
||||||
schema_version=SCHEMA_VERSION,
|
schema_version=SCHEMA_VERSION,
|
||||||
macrophase=L5_MACRO,
|
macrophase=L8_MACRO,
|
||||||
recognized_instances=dumps,
|
recognized_instances=dumps,
|
||||||
message="",
|
message="",
|
||||||
max_level_reached=L2_LEVEL,
|
max_level_reached=L2_LEVEL,
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ L1_CHECKS = frozenset({
|
|||||||
"dq_to_dqs_skew",
|
"dq_to_dqs_skew",
|
||||||
"byte_lane_skew",
|
"byte_lane_skew",
|
||||||
"ck_to_command_skew",
|
"ck_to_command_skew",
|
||||||
|
"ck_to_address_skew",
|
||||||
"clock_to_data_skew",
|
"clock_to_data_skew",
|
||||||
"vias",
|
"vias",
|
||||||
"via_count",
|
"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(
|
def certify_instance_l1(
|
||||||
graph: DesignGraph,
|
graph: DesignGraph,
|
||||||
inst: PhysicalBusInstance,
|
inst: PhysicalBusInstance,
|
||||||
@@ -209,30 +261,40 @@ def _run_l1_check(
|
|||||||
mand = _mandatory(iface, check)
|
mand = _mandatory(iface, check)
|
||||||
cons = _constraint_for(iface, check)
|
cons = _constraint_for(iface, check)
|
||||||
kind = cons.value_kind if cons else "UNKNOWN"
|
kind = cons.value_kind if cons else "UNKNOWN"
|
||||||
if layout is None:
|
grouping_recon = {
|
||||||
return _skip_no_geometry(check, mand, inst)
|
|
||||||
|
|
||||||
grouping_names = {
|
|
||||||
"byte_lane_mapping", "dq_group", "dqs_relationship_if_present",
|
"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"]
|
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 in grouping_recon:
|
||||||
if check == "cmd_dat_grouping":
|
return _grouping_recon(inst, check, mand, kind)
|
||||||
cmd = [g for g in inst.groups if g.kind in {"COMMAND_GROUP", "ADDRESS_GROUP", "CLOCK_GROUP"}]
|
|
||||||
if not cmd:
|
if check in skew_checks:
|
||||||
|
if check in {
|
||||||
|
"dq_to_dqs_skew", "byte_lane_skew",
|
||||||
|
} and not lanes:
|
||||||
return _skip_grouping(check, mand, inst)
|
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(
|
return _pack(
|
||||||
check, "PASS", mand, nets=_instance_nets(inst), value_kind=kind,
|
check, dep, mand, nets=_instance_nets(inst), value_kind=kind,
|
||||||
notes="CMD/DAT grouping present. L1 is not electrical certification.",
|
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"}:
|
if layout is None:
|
||||||
return _pack(
|
return _skip_no_geometry(check, mand, inst)
|
||||||
check, "PASS", mand, nets=_instance_nets(inst), value_kind=kind,
|
|
||||||
notes="Geometric grouping present. L1 is not electrical certification.",
|
|
||||||
)
|
|
||||||
|
|
||||||
if check == "topology":
|
if check == "topology":
|
||||||
nets = _instance_nets(inst)
|
nets = _instance_nets(inst)
|
||||||
@@ -291,7 +353,7 @@ def _run_l1_check(
|
|||||||
if check == "byte_lane_skew":
|
if check == "byte_lane_skew":
|
||||||
return _lane_spread(lanes, layout, cons, mand, kind, "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)
|
measured = _group_spread(inst, layout)
|
||||||
verdict = geometric_verdict(cons, measured)
|
verdict = geometric_verdict(cons, measured)
|
||||||
return _pack(
|
return _pack(
|
||||||
|
|||||||
@@ -267,6 +267,7 @@ def recognize_physical_buses(
|
|||||||
out: list[PhysicalBusInstance] = []
|
out: list[PhysicalBusInstance] = []
|
||||||
out.extend(_recognize_declared(graph, ifaces, logical))
|
out.extend(_recognize_declared(graph, ifaces, logical))
|
||||||
out.extend(_recognize_usb(graph, ifaces))
|
out.extend(_recognize_usb(graph, ifaces))
|
||||||
|
out.extend(_recognize_hbm(graph, ifaces))
|
||||||
out.extend(_recognize_ddr(graph, ifaces))
|
out.extend(_recognize_ddr(graph, ifaces))
|
||||||
out.extend(_recognize_axi(graph, ifaces))
|
out.extend(_recognize_axi(graph, ifaces))
|
||||||
return _dedupe_instances(out)
|
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]:
|
def _recognize_ddr(graph: DesignGraph, ifaces: dict) -> list[PhysicalBusInstance]:
|
||||||
memories: list[tuple[Component, str, EvidenceKind]] = []
|
from backend.periscopex.protocol_ddr import recognize_ddr_instances
|
||||||
for comp in graph.components.values():
|
return recognize_ddr_instances(graph, ifaces)
|
||||||
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 []
|
|
||||||
|
|
||||||
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"
|
def _recognize_hbm(graph: DesignGraph, ifaces: dict) -> list[PhysicalBusInstance]:
|
||||||
if gen == "ddr-family":
|
from backend.periscopex.protocol_ddr import recognize_hbm
|
||||||
phys = "ddr-family-sdram-pcb"
|
return recognize_hbm(graph, ifaces)
|
||||||
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_axi(graph: DesignGraph, ifaces: dict) -> list[PhysicalBusInstance]:
|
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_l0 import _constraint_for
|
||||||
from backend.periscopex.protocol_recognize import PhysicalBusInstance
|
from backend.periscopex.protocol_recognize import PhysicalBusInstance
|
||||||
|
|
||||||
PACK_MACROPHASE = "M5"
|
PACK_MACROPHASE = "M8"
|
||||||
|
|
||||||
_SKIP = frozenset({
|
_SKIP = frozenset({
|
||||||
"UNKNOWN", "MISSING_SOURCE", "VENDOR_DEPENDENT",
|
"UNKNOWN", "MISSING_SOURCE", "VENDOR_DEPENDENT",
|
||||||
|
|||||||
@@ -2,6 +2,13 @@
|
|||||||
|
|
||||||
What's new in Periscope.
|
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)
|
## 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.
|
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",
|
"name": "periscope-web",
|
||||||
"version": "2.69.0",
|
"version": "2.70.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"sync-version": "node scripts/sync-version.mjs",
|
"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)
|
assert all(r.result != "FAIL" for r in rows)
|
||||||
sec, findings = protocol_exam(_axi_graph())
|
sec, findings = protocol_exam(_axi_graph())
|
||||||
assert sec.max_level_reached == "L2"
|
assert sec.max_level_reached == "L2"
|
||||||
from backend.periscopex.protocol_report import PACK_MACROPHASE as L5_MACRO
|
from backend.periscopex.protocol_report import PACK_MACROPHASE as L8_MACRO
|
||||||
assert sec.macrophase == L5_MACRO
|
assert sec.macrophase == L8_MACRO
|
||||||
assert all(f.status != "ERROR" for f in findings)
|
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")
|
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)
|
assert all(r.result != "FAIL" for r in rows)
|
||||||
sec, exam_findings = protocol_exam(_axi_graph(), layout=LayoutGraph())
|
sec, exam_findings = protocol_exam(_axi_graph(), layout=LayoutGraph())
|
||||||
assert sec.max_level_reached == "L2"
|
assert sec.max_level_reached == "L2"
|
||||||
assert sec.macrophase == "M5"
|
assert sec.macrophase == "M8"
|
||||||
assert all(f.status != "ERROR" for f in exam_findings)
|
assert all(f.status != "ERROR" for f in exam_findings)
|
||||||
blob = json.dumps([r.model_dump() for r in rows])
|
blob = json.dumps([r.model_dump() for r in rows])
|
||||||
assert "electrical certification" not in blob.lower() or "not electrical" in blob.lower()
|
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)
|
assert all(r.result == "NOT_APPLICABLE" for r in rows)
|
||||||
sec, findings = protocol_exam(_axi_graph())
|
sec, findings = protocol_exam(_axi_graph())
|
||||||
assert sec.max_level_reached == "L2"
|
assert sec.max_level_reached == "L2"
|
||||||
assert sec.macrophase == "M5"
|
assert sec.macrophase == "M8"
|
||||||
assert all(f.status != "ERROR" for f in findings)
|
assert all(f.status != "ERROR" for f in findings)
|
||||||
blob = json.dumps([r.model_dump() for r in rows])
|
blob = json.dumps([r.model_dump() for r in rows])
|
||||||
assert "L0/L1 are not electrical" in blob
|
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():
|
def test_usb2_pair_l0_pass_l2_z_unknown_no_invented_ohm():
|
||||||
sec, _ = protocol_exam(_usb_connected())
|
sec, _ = protocol_exam(_usb_connected())
|
||||||
assert sec.max_level_reached == "L2"
|
assert sec.max_level_reached == "L2"
|
||||||
assert sec.macrophase == "M5"
|
assert sec.macrophase == "M8"
|
||||||
usb = next(
|
usb = next(
|
||||||
r for r in sec.recognized_instances
|
r for r in sec.recognized_instances
|
||||||
if "usb" in str(r.get("logical_protocol_id"))
|
if "usb" in str(r.get("logical_protocol_id"))
|
||||||
|
|||||||
Reference in New Issue
Block a user