lpddr4: implement ZQC through MPC and include it in init sequence

We do not yet support ZQC during operation (after init sequence)
as LPDDR4 requires 2-stage ZQC (start+latch) and 1us in between,
which requires modifying Refresher (ZQCExecutor) in LiteDRAM.
This commit is contained in:
Jędrzej Boczar 2021-01-19 12:12:10 +01:00
parent 055e2dc597
commit ee9c2b4cf7
4 changed files with 39 additions and 18 deletions

View File

@ -554,11 +554,16 @@ def get_lpddr4_phy_init_sequence(phy_settings, timing_settings):
ba = 0 ba = 0
return ("Load More Register {}".format(ma), a, ba, cmds["MODE_REGISTER"], 200) return ("Load More Register {}".format(ma), a, ba, cmds["MODE_REGISTER"], 200)
from litedram.phy.lpddr4phy import DFIPhaseAdapter
zqc_start = DFIPhaseAdapter.MPC["ZQC-START"]
zqc_latch = DFIPhaseAdapter.MPC["ZQC-LATCH"]
init_sequence = [ init_sequence = [
("Release reset", 0x0000, 0, cmds["UNRESET"], 50000), ("Release reset", 0x0000, 0, cmds["UNRESET"], 50000),
("Bring CKE high", 0x0000, 0, cmds["CKE"], 10000), ("Bring CKE high", 0x0000, 0, cmds["CKE"], 10000),
*[cmd_mr(ma) for ma in sorted(mr.keys())], *[cmd_mr(ma) for ma in sorted(mr.keys())],
# TODO: ZQ calibration ("ZQ Calibration start", zqc_start, 0, "DFII_COMMAND_WE|DFII_COMMAND_CS", 1000), # > tZQCAL=1us
("ZQ Calibration latch", zqc_latch, 0, "DFII_COMMAND_WE|DFII_COMMAND_CS", 200), # > tZQLAT=max(8ck, 30ns)
] ]
return init_sequence, mr return init_sequence, mr

View File

@ -974,7 +974,10 @@ class MT53E256M16D1(SDRAMModule):
nrows = 32768 nrows = 32768
ncols = 1024 ncols = 1024
technology_timings = _TechnologyTimings(tREFI=32e6/8192, tWTR=(8, 10), tCCD=(8, None), tRRD=(4, 10), tZQCS=None) # TODO: tZQCS # TODO: tZQCS - performing ZQC during runtime will require modifying Refresher, as ZQC has to be done in 2 phases
# 1. ZQCAL START is issued 2. ZQCAL LATCH updates the values, the time START->LATCH tZQCAL=1us, so we cannot block
# the controller during this time, after ZQCAL LATCH we have to wait tZQLAT=max(8ck, 30ns)
technology_timings = _TechnologyTimings(tREFI=32e6/8192, tWTR=(8, 10), tCCD=(8, None), tRRD=(4, 10), tZQCS=None)
speedgrade_timings = { speedgrade_timings = {
"1866": _SpeedgradeTimings(tRP=(3, 21), tRCD=(4, 18), tWR=(4, 18), tRFC=180, tFAW=40, tRAS=(3, 42)), # TODO: tRAS_max "1866": _SpeedgradeTimings(tRP=(3, 21), tRCD=(4, 18), tWR=(4, 18), tRFC=180, tFAW=40, tRAS=(3, 42)), # TODO: tRAS_max
} }

View File

@ -384,6 +384,22 @@ class DFIPhaseAdapter(Module):
# Then most "big commands" consist of 2 "small commands" (e.g. ACTIVATE-1, ACTIVATE-2). # Then most "big commands" consist of 2 "small commands" (e.g. ACTIVATE-1, ACTIVATE-2).
# If a command uses 1 "small command", then it shall go as cmd2 so that all command # If a command uses 1 "small command", then it shall go as cmd2 so that all command
# timings can be counted from the same moment (cycle of cmd2 CS low). # timings can be counted from the same moment (cycle of cmd2 CS low).
# MPC (multipurpose command) can be used to perform different actions
# We use ZQC with BA=0 to issue MPC, where OP[6:0] = A[6:0]
MPC = {
"NOP": 0b0000000, # only OP[6] must be 0
"READ-FIFO": 0b1000001,
"READ-DQ-CAL": 0b1000011,
# RFU: 0b1000101
"WRITE-FIFO": 0b1000111,
# RFU: 0b1001001
"START-DQS-OSC": 0b1001011,
"STOP-DQS-OSC": 0b1001101,
"ZQC-START": 0b1001111,
"ZQC-LATCH": 0b1010001,
}
def __init__(self, dfi_phase): def __init__(self, dfi_phase):
# CS/CA values for 4 SDR cycles # CS/CA values for 4 SDR cycles
self.cs = Signal(4) self.cs = Signal(4)
@ -426,11 +442,7 @@ class DFIPhaseAdapter(Module):
_cmd["WR"]: cmds("WRITE-1", "CAS-2"), # TODO: masked write _cmd["WR"]: cmds("WRITE-1", "CAS-2"), # TODO: masked write
_cmd["PRE"]: cmds("DESELECT", "PRECHARGE"), _cmd["PRE"]: cmds("DESELECT", "PRECHARGE"),
_cmd["REF"]: cmds("DESELECT", "REFRESH"), _cmd["REF"]: cmds("DESELECT", "REFRESH"),
# TODO: ZQC init/short/long? start/latch? _cmd["ZQC"]: cmds("DESELECT", "MPC"),
# _cmd["ZQC"]: [
# *cmds("DESELECT", "MPC"),
# self.cmd2.mpc.eq(0b1001111),
# ],
_cmd["MRS"]: cmds("MRW-1", "MRW-2"), _cmd["MRS"]: cmds("MRW-1", "MRW-2"),
"default": cmds("DESELECT", "DESELECT", valid=0), "default": cmds("DESELECT", "DESELECT", valid=0),
}) })
@ -462,7 +474,6 @@ class Command(Module):
def __init__(self, dfi_phase): def __init__(self, dfi_phase):
self.cs = Signal(2) self.cs = Signal(2)
self.ca = Array([Signal(6), Signal(6)]) # CS high, CS low self.ca = Array([Signal(6), Signal(6)]) # CS high, CS low
self.mpc = Signal(7) # special OP values for multipurpose command
self.dfi = dfi_phase self.dfi = dfi_phase
def set(self, cmd): def set(self, cmd):
@ -487,8 +498,7 @@ class Command(Module):
"R(\d+)": lambda i: self.dfi.address[i], # row "R(\d+)": lambda i: self.dfi.address[i], # row
"C(\d+)": lambda i: self.dfi.address[i], # column "C(\d+)": lambda i: self.dfi.address[i], # column
"MA(\d+)": lambda i: self.dfi.address[8+i], # mode register address "MA(\d+)": lambda i: self.dfi.address[8+i], # mode register address
# mode register value, or op code for MPC "OP(\d+)": lambda i: self.dfi.address[i], # mode register value, or operand for MPC
"OP(\d+)": lambda i: self.mpc[i] if is_mpc else self.dfi.address[i],
} }
for pattern, value in rules.items(): for pattern, value in rules.items():
m = re.match(pattern, bit) m = re.match(pattern, bit)

View File

@ -506,22 +506,25 @@ class TestLPDDR4(unittest.TestCase):
refresh_ab = dict(cs_n=0, cas_n=0, ras_n=0, we_n=1, bank=0b100, address=0b10000000000) refresh_ab = dict(cs_n=0, cas_n=0, ras_n=0, we_n=1, bank=0b100, address=0b10000000000)
precharge = dict(cs_n=0, cas_n=1, ras_n=0, we_n=0, bank=0b011, address=0) precharge = dict(cs_n=0, cas_n=1, ras_n=0, we_n=0, bank=0b011, address=0)
mrw = dict(cs_n=0, cas_n=0, ras_n=0, we_n=0, bank=0, address=(0b110011 << 8) | 0b10101010) # 6-bit address | 8-bit op code mrw = dict(cs_n=0, cas_n=0, ras_n=0, we_n=0, bank=0, address=(0b110011 << 8) | 0b10101010) # 6-bit address | 8-bit op code
zqc_start = dict(cs_n=0, cas_n=1, ras_n=1, we_n=0, bank=0, address=0b1001111) # MPC with ZQCAL START operand
zqc_latch = dict(cs_n=0, cas_n=1, ras_n=1, we_n=0, bank=0, address=0b1010001) # MPC with ZQCAL LATCH operand
self.run_test(SimulationPHY(), self.run_test(SimulationPHY(),
dfi_sequence = [ dfi_sequence = [
{0: read, 4: write_ap}, {0: read, 4: write_ap},
{0: activate, 4: refresh_ab}, {0: activate, 4: refresh_ab},
{0: precharge, 4: mrw}, {0: precharge, 4: mrw},
{0: zqc_start, 4: zqc_latch},
], ],
pad_checkers = {"sys8x_90": { pad_checkers = {"sys8x_90": {
# note that refresh and precharge have a single command so these go as cmd2 # note that refresh and precharge have a single command so these go as cmd2
# rd wr act ref pre mrw # rd wr act ref pre mrw zqcs zqcl
'cs': latency + '1010'+'1010' + '1010'+'0010' + '0010'+'1010', 'cs': latency + '1010'+'1010' + '1010'+'0010' + '0010'+'1010' + '0010'+'0010',
'ca0': latency + '0100'+'0100' + '1011'+'0000' + '0001'+'0100', 'ca0': latency + '0100'+'0100' + '1011'+'0000' + '0001'+'0100' + '0001'+'0001',
'ca1': latency + '1010'+'0110' + '0110'+'0000' + '0001'+'1111', 'ca1': latency + '1010'+'0110' + '0110'+'0000' + '0001'+'1111' + '0001'+'0000',
'ca2': latency + '0101'+'1100' + '0010'+'0001' + '0000'+'1010', 'ca2': latency + '0101'+'1100' + '0010'+'0001' + '0000'+'1010' + '0001'+'0000',
'ca3': latency + '0x01'+'0x00' + '1110'+'001x' + '000x'+'0001', 'ca3': latency + '0x01'+'0x00' + '1110'+'001x' + '000x'+'0001' + '0001'+'0000',
'ca4': latency + '0110'+'0010' + '1010'+'000x' + '001x'+'0110', 'ca4': latency + '0110'+'0010' + '1010'+'000x' + '001x'+'0110' + '0000'+'0001',
'ca5': latency + '0010'+'0100' + '1001'+'001x' + '000x'+'1101', 'ca5': latency + '0010'+'0100' + '1001'+'001x' + '000x'+'1101' + '0010'+'0010',
}}, }},
) )