|
|
|
@@ -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"
|