773 lines
35 KiB
Python
773 lines
35 KiB
Python
#
|
|
# This file is part of LiteDRAM.
|
|
#
|
|
# Copyright (c) 2021 Antmicro <www.antmicro.com>
|
|
# SPDX-License-Identifier: BSD-2-Clause
|
|
|
|
import unittest
|
|
from typing import Mapping
|
|
from collections import defaultdict
|
|
from functools import partial, wraps
|
|
|
|
from migen import *
|
|
|
|
from litedram.phy.lpddr5.simphy import LPDDR5SimPHY
|
|
from litedram.phy.lpddr5 import simsoc
|
|
from litedram.phy.sim_utils import SimLogger
|
|
|
|
import test.phy_common
|
|
from test.phy_common import DFISequencer, PadChecker, run_simulation as _run_simulation
|
|
|
|
|
|
def generate_clocks(max):
|
|
def phase(ck, phase):
|
|
assert ck % 2 == 0
|
|
assert phase % 90 == 0 and 0 <= phase < 360
|
|
if phase in [90, 270]:
|
|
assert ck % 4 == 0
|
|
p = ck // 2 - 1
|
|
p -= (ck // 4) * phase//90
|
|
p %= ck
|
|
assert 1 <= p <= ck-1
|
|
return p
|
|
|
|
sys = 8 * max
|
|
clocks = {
|
|
"sys": (sys, phase(sys, 0)),
|
|
"sys_90": (sys, phase(sys, 90)),
|
|
"sys_180": (sys, phase(sys, 180)),
|
|
"sys_270": (sys, phase(sys, 270)),
|
|
}
|
|
|
|
for i in range(1, log2_int(max) + 1):
|
|
n = 2**i
|
|
assert sys % n == 0
|
|
clocks[f"sys{n}x"] = (sys // n, phase(sys // n, 0))
|
|
if n < max or True:
|
|
clocks[f"sys{n}x_90"] = (sys // n, phase(sys // n, 90))
|
|
clocks[f"sys{n}x_180"] = (sys // n, phase(sys // n, 180))
|
|
clocks[f"sys{n}x_270"] = (sys // n, phase(sys // n, 270))
|
|
|
|
return clocks
|
|
|
|
|
|
# Clocks are set up such that the first rising edge is on tic 1 (not 0), just as in test_lpddr4.
|
|
run_simulation = partial(test.phy_common.run_simulation, clocks=generate_clocks(max=8))
|
|
|
|
|
|
dfi_data_to_dq = partial(test.phy_common.dfi_data_to_dq, databits=16, nphases=1, burst=16)
|
|
dq_pattern = partial(test.phy_common.dq_pattern, databits=16, nphases=1, burst=16)
|
|
|
|
|
|
def wck_ratio_subtests(testfunc):
|
|
"""Wraps a test running it for both WCK:CK=2:1 and 4:1. Passes wrapped LPDDR5SimPHY constructor as an argument."""
|
|
@wraps(testfunc)
|
|
def wrapper(self):
|
|
for wck_ck_ratio in [2, 4]:
|
|
with self.subTest(wck_ck_ratio=wck_ck_ratio):
|
|
Phy = lambda *args, **kwargs: LPDDR5SimPHY(*args, wck_ck_ratio=wck_ck_ratio, **kwargs)
|
|
testfunc(self, Phy)
|
|
return wrapper
|
|
|
|
|
|
class LPDDR5Tests(unittest.TestCase):
|
|
SYS_CLK_FREQ = 100e6
|
|
|
|
def run_test(self, dut, dfi_sequence, pad_checkers: Mapping[str, Mapping[str, str]], pad_generators=None, chunk_size=8, **kwargs):
|
|
# pad_checkers: {clock: {sig: values}}
|
|
dfi = DFISequencer(dfi_sequence)
|
|
checkers = {clk: PadChecker(dut.pads, pad_signals) for clk, pad_signals in pad_checkers.items()}
|
|
generators = defaultdict(list)
|
|
generators["sys"].append(dfi.generator(dut.dfi))
|
|
generators["sys"].append(dfi.reader(dut.dfi))
|
|
for clock, checker in checkers.items():
|
|
generators[clock].append(checker.run())
|
|
pad_generators = pad_generators or {}
|
|
for clock, gens in pad_generators.items():
|
|
gens = gens if isinstance(gens, list) else [gens]
|
|
for gen in gens:
|
|
generators[clock].append(gen(dut.pads))
|
|
run_simulation(dut, generators, **kwargs)
|
|
PadChecker.assert_ok(self, checkers, chunk_size=chunk_size)
|
|
dfi.assert_ok(self)
|
|
|
|
@wck_ratio_subtests
|
|
def test_lpddr5_reset_n(self, Phy):
|
|
# Test serialization of DFI reset_n
|
|
phy = Phy(sys_clk_freq=self.SYS_CLK_FREQ)
|
|
read = dict(cs_n=0, cas_n=0, ras_n=1, we_n=1)
|
|
self.run_test(phy,
|
|
dfi_sequence = [
|
|
{0: dict(reset_n=1, **read)},
|
|
{},
|
|
{}, {},
|
|
{0: dict(reset_n=0, **read)},
|
|
{},
|
|
{0: dict(reset_n=0)},
|
|
{0: dict(reset_n=0)},
|
|
],
|
|
pad_checkers = {"sys_270": {
|
|
"cs": "0 1100 1100",
|
|
"reset_n": "x 1111 0100",
|
|
}},
|
|
)
|
|
|
|
@wck_ratio_subtests
|
|
def test_lpddr5_cs(self, Phy):
|
|
# Test that CS is serialized correctly
|
|
phy = Phy(sys_clk_freq=self.SYS_CLK_FREQ)
|
|
self.run_test(phy,
|
|
dfi_sequence = [
|
|
{0: dict(cs_n=0, cas_n=1, ras_n=0, we_n=1)}, # ACT
|
|
{},
|
|
{0: dict(cs_n=0, cas_n=1, ras_n=0, we_n=0)}, # PRE
|
|
{},
|
|
{},
|
|
{0: dict(cs_n=0, cas_n=1, ras_n=0, we_n=1)}, # ACT
|
|
{0: dict(cs_n=0, cas_n=1, ras_n=0, we_n=1)}, # ACT (will be ignored)
|
|
],
|
|
# use 270 phase shift to sample when the data is valid, i.e. CS is shifted 180 deg
|
|
# by PHY (to make it center aligned with CK), then we add 90 to sample in the center of CS
|
|
pad_checkers = {"sys_270": {
|
|
'cs': '0 1101011000',
|
|
}},
|
|
)
|
|
|
|
@wck_ratio_subtests
|
|
def test_lpddr5_ck(self, Phy):
|
|
# Test clock serialization, first cycle is undefined so ignore them
|
|
phy = Phy(sys_clk_freq=self.SYS_CLK_FREQ)
|
|
self.run_test(phy,
|
|
dfi_sequence = [
|
|
{0: dict(cs_n=0, cas_n=0, ras_n=1, we_n=1)},
|
|
],
|
|
pad_checkers = {"sys2x_90": { # sampling at DDR CK
|
|
'ck': 'xx' + '10101010' * 3,
|
|
}},
|
|
)
|
|
|
|
@wck_ratio_subtests
|
|
def test_lpddr5_ca(self, Phy):
|
|
# Test proper serialization of commands to CA pads and that overlapping commands are handled
|
|
phy = Phy(sys_clk_freq=self.SYS_CLK_FREQ)
|
|
read = {0: dict(cs_n=0, cas_n=0, ras_n=1, we_n=1)} # CAS+RD16
|
|
precharge = {0: dict(cs_n=0, cas_n=1, ras_n=0, we_n=0)}
|
|
self.run_test(phy,
|
|
dfi_sequence = [
|
|
read,
|
|
{},
|
|
{},
|
|
precharge,
|
|
read, # ignored
|
|
],
|
|
pad_checkers = {
|
|
"sys_270": {
|
|
'cs': '0' ' 1 1 0 0 1 ', },
|
|
"sys2x": { # it is serialized on DDR sys_270 so check at sys2x with additional cycle (00)
|
|
'ca0': '00' '00' '0010 00 0000',
|
|
'ca1': '00' '00' '0000 00 0000',
|
|
'ca2': '00' '00' '1000 00 0000',
|
|
'ca3': '00' '00' '1000 00 0010',
|
|
'ca4': '00' '00' '0000 00 0010',
|
|
'ca5': '00' '00' '1000 00 0010',
|
|
'ca6': '00' '00' '0000 00 0010',
|
|
}
|
|
},
|
|
chunk_size=4,
|
|
)
|
|
|
|
@wck_ratio_subtests
|
|
def test_lpddr5_cas_wck_sync_read(self, Phy):
|
|
# Test that WCK sync bit in CAS command is set on first read command
|
|
phy = Phy(sys_clk_freq=self.SYS_CLK_FREQ)
|
|
read = {0: dict(cs_n=0, cas_n=0, ras_n=1, we_n=1)} # CAS+RD16
|
|
self.run_test(phy,
|
|
dfi_sequence = [
|
|
read, # with WCK sync
|
|
{},
|
|
{},
|
|
read, # with WCK sync
|
|
{},
|
|
{},
|
|
],
|
|
pad_checkers = {
|
|
"sys_270": {
|
|
'cs': '0' ' 1 1 0 1 1 0 ', },
|
|
"sys2x": {
|
|
'ca0': '00' '00' '0010 0000 1000',
|
|
'ca1': '00' '00' '0000 0000 0000',
|
|
'ca2': '00' '00' '1000 0010 0000',
|
|
'ca3': '00' '00' '1000 0010 0000',
|
|
'ca4': '00' '00' '0000 0000 0000',
|
|
'ca5': '00' '00' '1000 0010 0000',
|
|
'ca6': '00' '00' '0000 0000 0000',
|
|
}
|
|
},
|
|
chunk_size=4,
|
|
)
|
|
|
|
@wck_ratio_subtests
|
|
def test_lpddr5_cas_wck_sync_mrr(self, Phy):
|
|
# Test that WCK sync bit in CAS command is set on first MRR command (CAS with WS_RD)
|
|
phy = Phy(sys_clk_freq=self.SYS_CLK_FREQ)
|
|
mrr = {0: dict(cs_n=0, cas_n=1, ras_n=1, we_n=0, bank=1)} # MRR is ZQC with bank=1
|
|
self.run_test(phy,
|
|
dfi_sequence = [
|
|
mrr, # with WCK sync
|
|
{},
|
|
{},
|
|
mrr, # with WCK sync
|
|
{},
|
|
{},
|
|
],
|
|
pad_checkers = {
|
|
"sys_270": {
|
|
'cs': '0' ' 1 1 0 1 1 0 ', },
|
|
"sys2x": {
|
|
'ca0': '00' '00' '0000 0000 0000',
|
|
'ca1': '00' '00' '0000 0000 0000',
|
|
'ca2': '00' '00' '1000 0010 0000',
|
|
'ca3': '00' '00' '1010 0010 1000',
|
|
'ca4': '00' '00' '0010 0000 1000',
|
|
'ca5': '00' '00' '1000 0010 0000',
|
|
'ca6': '00' '00' '0000 0000 0000',
|
|
}
|
|
},
|
|
chunk_size=4,
|
|
)
|
|
|
|
@wck_ratio_subtests
|
|
def test_lpddr5_cas_wck_sync_write(self, Phy):
|
|
# Test that WCK sync bit in CAS command is set on first write command
|
|
write = {0: dict(cs_n=0, cas_n=0, ras_n=1, we_n=0)} # CAS+WR16
|
|
for masked_write in [True, False]:
|
|
with self.subTest(masked_write=masked_write):
|
|
phy = Phy(sys_clk_freq=self.SYS_CLK_FREQ, masked_write=masked_write)
|
|
w1 = f"10{int(not masked_write)}0"
|
|
w2 = f"{int(not masked_write)}000"
|
|
self.run_test(phy,
|
|
dfi_sequence = [
|
|
write, # with WCK sync
|
|
{},
|
|
{},
|
|
write, # with WCK sync
|
|
{},
|
|
{},
|
|
],
|
|
pad_checkers = {
|
|
"sys_270": {
|
|
'cs': '0' ' 1 1 0 1 1 0 ', },
|
|
"sys2x": {
|
|
'ca0': '00' '00' '0000 0000 0000',
|
|
'ca1': '00' '00' '0010 0000 1000',
|
|
'ca2': '00' '00'f'{w1} 0010 {w2}',
|
|
'ca3': '00' '00' '1000 0010 0000',
|
|
'ca4': '00' '00' '1000 0010 0000',
|
|
'ca5': '00' '00' '0000 0000 0000',
|
|
'ca6': '00' '00' '0000 0000 0000',
|
|
}
|
|
},
|
|
chunk_size=4,
|
|
)
|
|
|
|
@wck_ratio_subtests
|
|
def test_lpddr5_ca_addressing(self, Phy):
|
|
# Test that bank/address for different commands are correctly serialized to CA pads
|
|
# LPDDR5 has only 64 columns, but uses optional 4-bit "burst address"
|
|
read = dict(cs_n=0, cas_n=0, ras_n=1, we_n=1, bank=0b1111, address=0b1101010000)
|
|
write_ap = dict(cs_n=0, cas_n=0, ras_n=1, we_n=0, bank=0b1010, address=0b10000000000)
|
|
activate = dict(cs_n=0, cas_n=1, ras_n=0, we_n=1, bank=0b0010, address=0b111110000111100001)
|
|
refresh_ab = dict(cs_n=0, cas_n=0, ras_n=0, we_n=1, bank=0b1001, address=0b10000000000)
|
|
precharge = dict(cs_n=0, cas_n=1, ras_n=0, we_n=0, bank=0b0111, address=0)
|
|
mrw = dict(cs_n=0, cas_n=0, ras_n=0, we_n=0, bank=0b1010011, address=0b10101010) # bank=7-bit address, address=8-bit op code
|
|
mrr = dict(cs_n=0, cas_n=1, ras_n=1, we_n=0, bank=1, address=0b1101101) # 7-bit address (bank=1 selects MRR)
|
|
zqc_start = dict(cs_n=0, cas_n=1, ras_n=1, we_n=0, bank=0, address=0b10000101) # MPC with ZQCAL START operand
|
|
zqc_latch = dict(cs_n=0, cas_n=1, ras_n=1, we_n=0, bank=0, address=0b10000110) # MPC with ZQCAL LATCH operand
|
|
|
|
for masked_write in [True, False]:
|
|
with self.subTest(masked_write=masked_write):
|
|
phy = Phy(sys_clk_freq=self.SYS_CLK_FREQ, masked_write=masked_write)
|
|
mw = f"10{int(not masked_write)}0"
|
|
self.run_test(phy,
|
|
dfi_sequence = [
|
|
{},
|
|
{0: read}, {}, # WCK sync
|
|
{0: write_ap}, {}, # with WCK sync
|
|
{0: activate}, {},
|
|
{0: refresh_ab},{},
|
|
{0: precharge}, {},
|
|
{0: mrw}, {},
|
|
{0: mrr}, {}, # with WCK sync
|
|
{0: zqc_start}, {},
|
|
{0: zqc_latch}, {},
|
|
],
|
|
pad_checkers = {
|
|
"sys_270": { # RD WR ACT REF PRE MRW MRR ZQCS ZQCL
|
|
'cs': '0 0 ' '1 1 1 1 1 1 0 1 0 1 1 1 1 1 0 1 0 1 ', },
|
|
"sys2x": {
|
|
'ca0': '00' '0000' '0011 0000 1011 0001 0001 0100 0001 0001 0000',
|
|
'ca1': '00' '0000' '0001 0011 1110 0000 0001 0101 0000 0000 0001',
|
|
'ca2': '00' '0000'f'1001 {mw} 1000 0000 0001 0000 1001 0001 0001',
|
|
'ca3': '00' '0000' '1011 1001 1010 0010 0010 1011 1011 0000 0000',
|
|
'ca4': '00' '0000' '0000 1000 1010 0010 001x 1100 0010 0010 0010',
|
|
'ca5': '00' '0000' '1011 0000 1001 0010 001x 0001 1001 0010 0010',
|
|
'ca6': '00' '0000' '0010 0001 1101 0001 0010 1110 0001 0010 0010',
|
|
}
|
|
},
|
|
chunk_size=4,
|
|
)
|
|
|
|
def test_lpddr5_dq_out_2to1(self):
|
|
# Test serialization of dfi wrdata to DQ pads
|
|
phy = LPDDR5SimPHY(sys_clk_freq=self.SYS_CLK_FREQ)
|
|
dfi_data = {
|
|
0: dict(wrdata=0x111122223333444455556666777788889999aaaabbbbccccddddeeeeffff0000),
|
|
}
|
|
dfi_wrdata_en = {0: dict(wrdata_en=1)}
|
|
latency = [{}] * (phy.settings.write_latency - 1)
|
|
self.run_test(phy,
|
|
dfi_sequence = [dfi_wrdata_en, *latency, dfi_data],
|
|
pad_checkers = {"sys4x_90": {
|
|
f'dq{i}': "0000"*phy.settings.write_latency + "0000 0000" + dq_pattern(i, dfi_data, "wrdata") + "0000"
|
|
for i in range(16)
|
|
}},
|
|
chunk_size=4,
|
|
)
|
|
|
|
def test_lpddr5_dq_out_4to1(self):
|
|
# Test serialization of dfi wrdata to DQ pads
|
|
phy = LPDDR5SimPHY(sys_clk_freq=self.SYS_CLK_FREQ, wck_ck_ratio=4)
|
|
dfi_data = {
|
|
0: dict(wrdata=0x111122223333444455556666777788889999aaaabbbbccccddddeeeeffff0000),
|
|
}
|
|
dfi_wrdata_en = {0: dict(wrdata_en=1)}
|
|
latency = [{}] * (phy.settings.write_latency - 1)
|
|
self.run_test(phy,
|
|
dfi_sequence = [dfi_wrdata_en, *latency, dfi_data],
|
|
pad_checkers = {"sys8x_90": {
|
|
f'dq{i}': "00000000"*phy.settings.write_latency + "00000000 00000000" + dq_pattern(i, dfi_data, "wrdata") + "00000000"
|
|
for i in range(16)
|
|
}},
|
|
)
|
|
|
|
def test_lpddr5_dmi_out_2to1(self):
|
|
# Test serialization of dfi wrdata to DQ pads
|
|
for masked_write in [False, True]:
|
|
with self.subTest(masked_write=masked_write):
|
|
phy = LPDDR5SimPHY(sys_clk_freq=self.SYS_CLK_FREQ, masked_write=masked_write)
|
|
wl = phy.settings.write_latency
|
|
dfi_data = {
|
|
0: dict( # all DQs have the same value on each cycle, each mask bit is 1 byte
|
|
wrdata = 0xffff0000ffffffff00000000ffffffff0000ffff00000000ffffffff0000ffff,
|
|
wrdata_mask = 0b11001000110110110101010010110011,
|
|
),
|
|
}
|
|
dfi_wrdata_en = {0: dict(wrdata_en=1)}
|
|
latency = [{}] * (wl - 1)
|
|
pads = {
|
|
f"dq{i}": "0000"*wl + "0000 0000" "1011 0010 1100 1101" "0000"
|
|
for i in range(16)
|
|
}
|
|
pads["dmi0"] = "0000"*wl + "0000 0000" + ("1010011110110001" if masked_write else 16*"0") + "0000"
|
|
pads["dmi1"] = "0000"*wl + "0000 0000" + ("1011000011010101" if masked_write else 16*"0") + "0000"
|
|
self.run_test(phy,
|
|
dfi_sequence = [dfi_wrdata_en, *latency, dfi_data],
|
|
pad_checkers = {"sys4x_90": pads},
|
|
chunk_size=4,
|
|
)
|
|
|
|
def test_lpddr5_dmi_out_4to1(self):
|
|
# Test serialization of dfi wrdata to DQ pads
|
|
for masked_write in [False, True]:
|
|
with self.subTest(masked_write=masked_write):
|
|
phy = LPDDR5SimPHY(sys_clk_freq=self.SYS_CLK_FREQ, masked_write=masked_write, wck_ck_ratio=4)
|
|
wl = phy.settings.write_latency
|
|
dfi_data = {
|
|
0: dict( # all DQs have the same value on each cycle, each mask bit is 1 byte
|
|
wrdata = 0xffff0000ffffffff00000000ffffffff0000ffff00000000ffffffff0000ffff,
|
|
wrdata_mask = 0b11001000110110110101010010110011,
|
|
),
|
|
}
|
|
dfi_wrdata_en = {0: dict(wrdata_en=1)}
|
|
latency = [{}] * (wl - 1)
|
|
pads = {
|
|
f"dq{i}": "00000000"*wl + "00000000 00000000" "1011 0010 1100 1101" "00000000"
|
|
for i in range(16)
|
|
}
|
|
pads["dmi0"] = "00000000"*wl + "00000000 00000000" + ("1010011110110001" if masked_write else 16*"0") + "00000000"
|
|
pads["dmi1"] = "00000000"*wl + "00000000 00000000" + ("1011000011010101" if masked_write else 16*"0") + "00000000"
|
|
self.run_test(phy,
|
|
dfi_sequence = [dfi_wrdata_en, *latency, dfi_data],
|
|
pad_checkers = {"sys8x_90": pads},
|
|
)
|
|
|
|
def test_lpddr5_dq_out_only_1_cycle(self):
|
|
# Test that only single cycle of wrdata after write_latency gets serialized
|
|
phy = LPDDR5SimPHY(sys_clk_freq=self.SYS_CLK_FREQ)
|
|
dfi_data = {
|
|
0: dict(wrdata=0x111122223333444455556666777788889999aaaabbbbccccddddeeeeffff0000),
|
|
}
|
|
dfi_wrdata_en = {0: dict(wrdata_en=1)}
|
|
latency = [dfi_data] * (phy.settings.write_latency - 1)
|
|
self.run_test(phy,
|
|
dfi_sequence = [dfi_wrdata_en, *latency, dfi_data],
|
|
pad_checkers = {"sys4x_90": {
|
|
f'dq{i}': "0000"*phy.settings.write_latency + "0000 0000" + dq_pattern(i, dfi_data, "wrdata") + "0000"
|
|
for i in range(16)
|
|
}},
|
|
chunk_size=4,
|
|
)
|
|
|
|
@wck_ratio_subtests
|
|
def test_lpddr5_dq_in_rddata_valid(self, Phy):
|
|
# Test that rddata_valid is set with correct delay
|
|
phy = Phy(sys_clk_freq=self.SYS_CLK_FREQ)
|
|
dfi_sequence = [
|
|
{0: dict(rddata_en=1)}, # command is issued by MC (appears on next cycle)
|
|
*[{0: dict(rddata_valid=0)} for _ in range(phy.settings.read_latency - 1)], # nothing is sent during write latency
|
|
{0: dict(rddata_valid=1)},
|
|
{},
|
|
]
|
|
self.run_test(phy,
|
|
dfi_sequence = dfi_sequence,
|
|
pad_checkers = {},
|
|
pad_generators = {},
|
|
)
|
|
|
|
def test_lpddr5_dq_in_rddata(self):
|
|
# Test that data on DQ pads is deserialized correctly to DFI rddata.
|
|
phy = LPDDR5SimPHY(sys_clk_freq=self.SYS_CLK_FREQ)
|
|
dfi_data = {
|
|
0: dict(
|
|
rddata=0x111122223333444455556666777788889999aaaabbbbccccddddeeeeffff0000,
|
|
rddata_valid=1
|
|
),
|
|
}
|
|
|
|
def sim_dq(pads):
|
|
i = 0
|
|
while not (yield pads.cs):
|
|
i += 1
|
|
assert i < 40, "Timeout waiting for RD cmd"
|
|
yield
|
|
# RD is registered on the second CS, then wait for RL (everyting in CK domain)
|
|
for _ in range(4 * (1 + phy.settings.cl)):
|
|
yield
|
|
# wait one more cycle, need to verify the latencies on actual hardware
|
|
for _ in range(2):
|
|
yield
|
|
for cyc in range(16): # send a burst of data on pads
|
|
for bit in range(16):
|
|
yield pads.dq_i[bit].eq(int(dq_pattern(bit, dfi_data, "rddata")[cyc]))
|
|
yield
|
|
for bit in range(16):
|
|
yield pads.dq_i[bit].eq(0)
|
|
yield
|
|
|
|
read_des_delay = 3 # phy.read_des_delay
|
|
dfi_sequence = [
|
|
{0: dict(cs_n=0, cas_n=0, ras_n=1, we_n=1, rddata_en=1)},
|
|
*[{} for _ in range(phy.settings.read_latency - 1)],
|
|
dfi_data,
|
|
{},
|
|
]
|
|
|
|
self.run_test(phy,
|
|
dfi_sequence = dfi_sequence,
|
|
pad_checkers = {},
|
|
pad_generators = {
|
|
"sys4x_180": sim_dq,
|
|
},
|
|
)
|
|
|
|
def test_lpddr5_wck_sync_2to1_write(self):
|
|
# Test that correct WCK sequence is generated during WCK sync before burst write for WCK:CK=2:1
|
|
cases = { # sys_clk_freq: timings
|
|
100e6: dict(t_wckenl_wr=1, t_wckenl_static=1, t_wckenl_toggle_wr=3), # data rate 400 MT/s
|
|
200e6: dict(t_wckenl_wr=0, t_wckenl_static=2, t_wckenl_toggle_wr=3), # 800 MT/s
|
|
300e6: dict(t_wckenl_wr=1, t_wckenl_static=2, t_wckenl_toggle_wr=4), # 1200 MT/s
|
|
500e6: dict(t_wckenl_wr=2, t_wckenl_static=3, t_wckenl_toggle_wr=4), # 2000 MT/s
|
|
600e6: dict(t_wckenl_wr=1, t_wckenl_static=4, t_wckenl_toggle_wr=4), # 2400 MT/s
|
|
800e6: dict(t_wckenl_wr=3, t_wckenl_static=4, t_wckenl_toggle_wr=4), # 3200 MT/s
|
|
}
|
|
for sys_clk_freq, t in cases.items():
|
|
with self.subTest(sys_clk_freq=sys_clk_freq, timings=t):
|
|
phy = LPDDR5SimPHY(sys_clk_freq=sys_clk_freq)
|
|
wl = phy.settings.write_latency
|
|
dfi_data = { # `10101010...` pattern on dq0 and `11111...` on others
|
|
0: dict(wrdata=0xfffefffffffefffffffefffffffefffffffefffffffefffffffefffffffeffff),
|
|
}
|
|
latency = [{}] * (wl - 1)
|
|
|
|
# minimum latency to have correct wck synchronization
|
|
consecutive_burst_latency = [{}] * 6
|
|
|
|
wck_preamble = "00 00" * t["t_wckenl_wr"] + "00 00" * t["t_wckenl_static"] + "10 10" * t["t_wckenl_toggle_wr"]
|
|
wck_burst = "10 10" * (16//4)
|
|
wck_postamble = "10 10" + "10 00"
|
|
|
|
self.run_test(phy,
|
|
dfi_sequence = [
|
|
{0: dict(cs_n=0, cas_n=0, ras_n=1, we_n=0, wrdata_en=1)},
|
|
*latency,
|
|
dfi_data,
|
|
*consecutive_burst_latency,
|
|
{0: dict(cs_n=0, cas_n=0, ras_n=1, we_n=0, wrdata_en=1)},
|
|
*latency,
|
|
dfi_data,
|
|
*consecutive_burst_latency,
|
|
{0: dict(cs_n=0, cas_n=0, ras_n=1, we_n=0, wrdata_en=1)},
|
|
*latency,
|
|
dfi_data,
|
|
*consecutive_burst_latency,
|
|
{0: dict(cs_n=0, cas_n=0, ras_n=1, we_n=0, wrdata_en=1)},
|
|
*latency,
|
|
dfi_data,
|
|
],
|
|
pad_checkers = {
|
|
"sys_270": {
|
|
"cs": "01100000",
|
|
},
|
|
"sys4x_90": { # DQ just for reference
|
|
"dq0": "0000"*wl + "0000 0000" + "10101010 10101010" + "0000",
|
|
"dq1": "0000"*wl + "0000 0000" + "11111111 11111111" + "0000",
|
|
},
|
|
"sys4x_270": {
|
|
# tWCKENL_WR starts counting from first command (CAS) so we add command latency,
|
|
# then preamble, then toggle for the whole burst, then postamble for tWCKPST=2.5tCK
|
|
# (but for now we assume that WCK is never disabled)
|
|
"wck0": "0000 0000" + \
|
|
"0000" + wck_preamble + wck_burst + wck_postamble + \
|
|
"0000" + wck_preamble + wck_burst + wck_postamble + \
|
|
"0000" + wck_preamble + wck_burst + wck_postamble + \
|
|
"0000" + wck_preamble + wck_burst + wck_postamble,
|
|
},
|
|
},
|
|
chunk_size=4,
|
|
)
|
|
|
|
def test_lpddr5_wck_sync_4to1_write(self):
|
|
# Test that correct WCK sequence is generated during WCK sync before burst write for WCK:CK=4:1
|
|
cases = { # sys_clk_freq: timings
|
|
50e6: dict(t_wckenl_wr=0, t_wckenl_static=1, t_wckenl_toggle_wr=2), # data rate 400 MT/s
|
|
100e6: dict(t_wckenl_wr=0, t_wckenl_static=1, t_wckenl_toggle_wr=2), # 800 MT/s
|
|
150e6: dict(t_wckenl_wr=1, t_wckenl_static=1, t_wckenl_toggle_wr=2), # 1200 MT/s
|
|
250e6: dict(t_wckenl_wr=1, t_wckenl_static=2, t_wckenl_toggle_wr=2), # 2000 MT/s
|
|
300e6: dict(t_wckenl_wr=1, t_wckenl_static=2, t_wckenl_toggle_wr=2), # 2400 MT/s
|
|
400e6: dict(t_wckenl_wr=2, t_wckenl_static=2, t_wckenl_toggle_wr=2), # 3200 MT/s
|
|
}
|
|
for sys_clk_freq, t in cases.items():
|
|
with self.subTest(sys_clk_freq=sys_clk_freq, timings=t):
|
|
phy = LPDDR5SimPHY(sys_clk_freq=sys_clk_freq, wck_ck_ratio=4)
|
|
wl = phy.settings.write_latency
|
|
dfi_data = { # `10101010...` pattern on dq0 and `11111...` on others
|
|
0: dict(wrdata=0xfffefffffffefffffffefffffffefffffffefffffffefffffffefffffffeffff),
|
|
}
|
|
latency = [{}] * (wl - 1)
|
|
|
|
# minimum latency to have correct wck synchronization
|
|
consecutive_burst_latency = [{}] * 3
|
|
|
|
wck_preamble = "00000000" * (t["t_wckenl_wr"] + t["t_wckenl_static"]) + "11001100" + "10101010" * (t["t_wckenl_toggle_wr"] - 1)
|
|
wck_burst = "10101010" * (16//8)
|
|
wck_postamble = "10101000"
|
|
|
|
self.run_test(phy,
|
|
dfi_sequence = [
|
|
{0: dict(cs_n=0, cas_n=0, ras_n=1, we_n=0, wrdata_en=1)},
|
|
*latency,
|
|
dfi_data,
|
|
*consecutive_burst_latency,
|
|
{0: dict(cs_n=0, cas_n=0, ras_n=1, we_n=0, wrdata_en=1)},
|
|
*latency,
|
|
dfi_data,
|
|
*consecutive_burst_latency,
|
|
{0: dict(cs_n=0, cas_n=0, ras_n=1, we_n=0, wrdata_en=1)},
|
|
*latency,
|
|
dfi_data,
|
|
*consecutive_burst_latency,
|
|
{0: dict(cs_n=0, cas_n=0, ras_n=1, we_n=0, wrdata_en=1)},
|
|
*latency,
|
|
dfi_data,
|
|
],
|
|
pad_checkers = {
|
|
"sys_270": {
|
|
"cs": "01100000",
|
|
},
|
|
"sys8x_90": { # DQ just for reference
|
|
"dq0": "00000000"*wl + "00000000 00000000" + "10101010 10101010" + "00000000",
|
|
"dq1": "00000000"*wl + "00000000 00000000" + "11111111 11111111" + "00000000",
|
|
},
|
|
"sys8x_270": {
|
|
# tWCKENL_WR starts counting from first command (CAS) so we add command latency,
|
|
# then preamble, then toggle for the whole burst, then postamble for tWCKPST=2.5tCK
|
|
# (but for now we assume that WCK is never disabled)
|
|
"wck0": "00000000 00000000" + \
|
|
"00000000" + wck_preamble + wck_burst + wck_postamble + \
|
|
"00000000" + wck_preamble + wck_burst + wck_postamble + \
|
|
"00000000" + wck_preamble + wck_burst + wck_postamble + \
|
|
"00000000" + wck_preamble + wck_burst + wck_postamble,
|
|
},
|
|
},
|
|
)
|
|
|
|
def test_lpddr5_wck_sync_2to1_read(self):
|
|
# Test that correct WCK sequence is generated during WCK sync before burst read for WCK:CK=2:1
|
|
cases = { # sys_clk_freq: timings
|
|
100e6: dict(t_wckenl_rd=0, t_wckenl_static=1, t_wckenl_toggle_rd=6), # data rate 400 MT/s
|
|
200e6: dict(t_wckenl_rd=0, t_wckenl_static=2, t_wckenl_toggle_rd=7), # 800 MT/s
|
|
300e6: dict(t_wckenl_rd=1, t_wckenl_static=2, t_wckenl_toggle_rd=8), # 1200 MT/s
|
|
500e6: dict(t_wckenl_rd=2, t_wckenl_static=3, t_wckenl_toggle_rd=8), # 2000 MT/s
|
|
600e6: dict(t_wckenl_rd=3, t_wckenl_static=4, t_wckenl_toggle_rd=10), # 2400 MT/s
|
|
800e6: dict(t_wckenl_rd=5, t_wckenl_static=4, t_wckenl_toggle_rd=10), # 3200 MT/s
|
|
}
|
|
for sys_clk_freq, t in cases.items():
|
|
with self.subTest(sys_clk_freq=sys_clk_freq, timings=t):
|
|
phy = LPDDR5SimPHY(sys_clk_freq=sys_clk_freq)
|
|
rl = phy.settings.read_latency
|
|
latency = [{}] * (rl - 1)
|
|
|
|
wck_preamble = "00 00" * t["t_wckenl_rd"] + "00 00" * t["t_wckenl_static"] + "10 10" * t["t_wckenl_toggle_rd"]
|
|
wck_burst = "10 10" * (16//4)
|
|
wck_postamble = "10 10" + "10 00"
|
|
|
|
self.run_test(phy,
|
|
dfi_sequence = [
|
|
{0: dict(cs_n=0, cas_n=0, ras_n=1, we_n=1, rddata_en=1)},
|
|
*latency,
|
|
{0: dict(rddata_valid=1)},
|
|
],
|
|
pad_checkers = {
|
|
"sys_270": {
|
|
"cs": "01100000",
|
|
},
|
|
"sys4x_270": {
|
|
"wck0": "0000 0000 0000" + wck_preamble + wck_burst + wck_postamble + "00 00",
|
|
},
|
|
},
|
|
chunk_size=4,
|
|
)
|
|
|
|
def test_lpddr5_wck_sync_4to1_read(self):
|
|
# Test that correct WCK sequence is generated during WCK sync before burst read for WCK:CK=4:1
|
|
cases = { # sys_clk_freq: timings
|
|
50e6: dict(t_wckenl_rd=0, t_wckenl_static=1, t_wckenl_toggle_rd=3), # data rate 400 MT/s
|
|
100e6: dict(t_wckenl_rd=0, t_wckenl_static=1, t_wckenl_toggle_rd=4), # 800 MT/s
|
|
150e6: dict(t_wckenl_rd=1, t_wckenl_static=1, t_wckenl_toggle_rd=4), # 1200 MT/s
|
|
250e6: dict(t_wckenl_rd=1, t_wckenl_static=2, t_wckenl_toggle_rd=4), # 2000 MT/s
|
|
300e6: dict(t_wckenl_rd=2, t_wckenl_static=2, t_wckenl_toggle_rd=5), # 2400 MT/s
|
|
400e6: dict(t_wckenl_rd=3, t_wckenl_static=2, t_wckenl_toggle_rd=5), # 3200 MT/s
|
|
}
|
|
for sys_clk_freq, t in cases.items():
|
|
with self.subTest(sys_clk_freq=sys_clk_freq, timings=t):
|
|
phy = LPDDR5SimPHY(sys_clk_freq=sys_clk_freq, wck_ck_ratio=4)
|
|
rl = phy.settings.read_latency
|
|
latency = [{}] * (rl - 1)
|
|
|
|
wck_preamble = "00000000" * (t["t_wckenl_rd"] + t["t_wckenl_static"]) + "11001100" + "10101010" * (t["t_wckenl_toggle_rd"] - 1)
|
|
wck_burst = "10101010" * (16//8)
|
|
wck_postamble = "10101000"
|
|
|
|
self.run_test(phy,
|
|
dfi_sequence = [
|
|
{0: dict(cs_n=0, cas_n=0, ras_n=1, we_n=1, rddata_en=1)},
|
|
*latency,
|
|
{0: dict(rddata_valid=1)},
|
|
],
|
|
pad_checkers = {
|
|
"sys_270": {
|
|
"cs": "01100000",
|
|
},
|
|
"sys8x_270": {
|
|
"wck0": "00000000 00000000 00000000" + wck_preamble + wck_burst + wck_postamble + "00000000",
|
|
},
|
|
},
|
|
)
|
|
|
|
def test_lpddr5_wck_leveling(self):
|
|
|
|
# Test that correct WCK sequence is generated during WCK sync before burst write for WCK:CK=4:1
|
|
for wck_ck_ratio in [2, 4]:
|
|
with self.subTest(wck_ck_ratio=wck_ck_ratio):
|
|
phy = LPDDR5SimPHY(sys_clk_freq=50e6, wck_ck_ratio=wck_ck_ratio)
|
|
|
|
def write_leveling(pads):
|
|
for _ in range(4):
|
|
yield from phy._wlevel_en.write(1)
|
|
yield from phy._wlevel_strobe.write(1)
|
|
for i in range(4):
|
|
yield
|
|
yield from phy._wlevel_en.write(0)
|
|
|
|
|
|
self.run_test(phy,
|
|
dfi_sequence = {},
|
|
pad_checkers = {
|
|
f"sys4x_270": {
|
|
"wck0": "0000" + \
|
|
"0000" * 3 + "1010" * 4 + \
|
|
"0000" * 3 + "1010" * 4 + \
|
|
"0000" * 3 + "1010" * 4 + \
|
|
"0000" * 3 + "1010" * 4,
|
|
},
|
|
},
|
|
pad_generators = {
|
|
"sys": write_leveling,
|
|
}
|
|
)
|
|
|
|
|
|
class VerilatorLPDDR5Tests(unittest.TestCase):
|
|
def check_logs(self, logs, allowed):
|
|
for match in SimLogger.LOG_PATTERN.finditer(logs):
|
|
if match.group("level") in ["WARN", "ERROR"]:
|
|
is_allowed = any(
|
|
lvl == match.group("level") and msg in match.group("msg")
|
|
for lvl, msg in allowed
|
|
)
|
|
self.assertTrue(is_allowed, msg=match.group(0))
|
|
|
|
def run_test(self, args, allowed=None, **kwargs):
|
|
import pexpect
|
|
|
|
command = ["python3", simsoc.__file__, *args]
|
|
timeout = 12 * 60 # give more than enough time
|
|
p = pexpect.spawn(" ".join(command), timeout=timeout, **kwargs)
|
|
|
|
res = p.expect(["Memtest OK", "Memtest KO"])
|
|
self.assertEqual(res, 0, msg="{}\nGot '{}'".format(p.before.decode(), p.after.decode()))
|
|
|
|
# print(p.before.decode())
|
|
self.check_logs(p.before.decode(), allowed=allowed or [])
|
|
|
|
def test_lpddr5_sim_no_delays(self):
|
|
# Fast test of simulation with L2 cache (so no data masking is required)
|
|
for wck_ck_ratio in [2, 4]:
|
|
with self.subTest(wck_ck_ratio=wck_ck_ratio):
|
|
self.run_test([
|
|
"--finish-after-memtest", "--log-level=warn",
|
|
"--output-dir", "build/test_lpddr5_sim_no_delays",
|
|
"--disable-delay",
|
|
f"--wck-ck-ratio={wck_ck_ratio}",
|
|
"--no-refresh", # FIXME: avoids warnings before initialization
|
|
])
|
|
|
|
def test_lpddr5_sim_delays_no_cache(self):
|
|
# Test simulation with regular delays and no L2 cache (masked write must work)
|
|
for wck_ck_ratio in [2, 4]:
|
|
with self.subTest(wck_ck_ratio=wck_ck_ratio):
|
|
# These happen due the fact that LiteDRAM starts in hw control mode which holds reset_n=1
|
|
# all the time. When the DRAM initialization starts we do a reset once more, this time properly.
|
|
allowed = [
|
|
("WARN", "tPW_RESET violated: RESET_n held low for too short"),
|
|
("WARN", "tINIT1 violated: RESET deasserted too fast"),
|
|
("WARN", "tINIT2 violated: CS LOW for too short before deasserting RESET (1/1 ck)"),
|
|
]
|
|
self.run_test([
|
|
"--finish-after-memtest", "--log-level=warn",
|
|
"--output-dir", "build/test_lpddr5_sim_delays_no_cache",
|
|
"--l2-size=0",
|
|
f"--wck-ck-ratio={wck_ck_ratio}",
|
|
"--no-refresh", # FIXME: LiteDRAM sends refresh commands when only MRW/MRR are allowed
|
|
], allowed=allowed)
|