diff --git a/litedram/init.py b/litedram/init.py index f5b882c..15be146 100644 --- a/litedram/init.py +++ b/litedram/init.py @@ -554,11 +554,16 @@ def get_lpddr4_phy_init_sequence(phy_settings, timing_settings): ba = 0 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 = [ ("Release reset", 0x0000, 0, cmds["UNRESET"], 50000), ("Bring CKE high", 0x0000, 0, cmds["CKE"], 10000), *[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 diff --git a/litedram/modules.py b/litedram/modules.py index 716ae82..ac9770a 100644 --- a/litedram/modules.py +++ b/litedram/modules.py @@ -974,7 +974,10 @@ class MT53E256M16D1(SDRAMModule): nrows = 32768 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 = { "1866": _SpeedgradeTimings(tRP=(3, 21), tRCD=(4, 18), tWR=(4, 18), tRFC=180, tFAW=40, tRAS=(3, 42)), # TODO: tRAS_max } diff --git a/litedram/phy/lpddr4phy.py b/litedram/phy/lpddr4phy.py index 9ac1b55..dd7160b 100644 --- a/litedram/phy/lpddr4phy.py +++ b/litedram/phy/lpddr4phy.py @@ -384,6 +384,22 @@ class DFIPhaseAdapter(Module): # 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 # 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): # CS/CA values for 4 SDR cycles self.cs = Signal(4) @@ -426,11 +442,7 @@ class DFIPhaseAdapter(Module): _cmd["WR"]: cmds("WRITE-1", "CAS-2"), # TODO: masked write _cmd["PRE"]: cmds("DESELECT", "PRECHARGE"), _cmd["REF"]: cmds("DESELECT", "REFRESH"), - # TODO: ZQC init/short/long? start/latch? - # _cmd["ZQC"]: [ - # *cmds("DESELECT", "MPC"), - # self.cmd2.mpc.eq(0b1001111), - # ], + _cmd["ZQC"]: cmds("DESELECT", "MPC"), _cmd["MRS"]: cmds("MRW-1", "MRW-2"), "default": cmds("DESELECT", "DESELECT", valid=0), }) @@ -462,7 +474,6 @@ class Command(Module): def __init__(self, dfi_phase): self.cs = Signal(2) 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 def set(self, cmd): @@ -487,8 +498,7 @@ class Command(Module): "R(\d+)": lambda i: self.dfi.address[i], # row "C(\d+)": lambda i: self.dfi.address[i], # column "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.mpc[i] if is_mpc else self.dfi.address[i], + "OP(\d+)": lambda i: self.dfi.address[i], # mode register value, or operand for MPC } for pattern, value in rules.items(): m = re.match(pattern, bit) diff --git a/test/test_lpddr4.py b/test/test_lpddr4.py index a857fbb..2cf64db 100644 --- a/test/test_lpddr4.py +++ b/test/test_lpddr4.py @@ -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) 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 + 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(), dfi_sequence = [ {0: read, 4: write_ap}, {0: activate, 4: refresh_ab}, {0: precharge, 4: mrw}, + {0: zqc_start, 4: zqc_latch}, ], pad_checkers = {"sys8x_90": { # note that refresh and precharge have a single command so these go as cmd2 - # rd wr act ref pre mrw - 'cs': latency + '1010'+'1010' + '1010'+'0010' + '0010'+'1010', - 'ca0': latency + '0100'+'0100' + '1011'+'0000' + '0001'+'0100', - 'ca1': latency + '1010'+'0110' + '0110'+'0000' + '0001'+'1111', - 'ca2': latency + '0101'+'1100' + '0010'+'0001' + '0000'+'1010', - 'ca3': latency + '0x01'+'0x00' + '1110'+'001x' + '000x'+'0001', - 'ca4': latency + '0110'+'0010' + '1010'+'000x' + '001x'+'0110', - 'ca5': latency + '0010'+'0100' + '1001'+'001x' + '000x'+'1101', + # rd wr act ref pre mrw zqcs zqcl + 'cs': latency + '1010'+'1010' + '1010'+'0010' + '0010'+'1010' + '0010'+'0010', + 'ca0': latency + '0100'+'0100' + '1011'+'0000' + '0001'+'0100' + '0001'+'0001', + 'ca1': latency + '1010'+'0110' + '0110'+'0000' + '0001'+'1111' + '0001'+'0000', + 'ca2': latency + '0101'+'1100' + '0010'+'0001' + '0000'+'1010' + '0001'+'0000', + 'ca3': latency + '0x01'+'0x00' + '1110'+'001x' + '000x'+'0001' + '0001'+'0000', + 'ca4': latency + '0110'+'0010' + '1010'+'000x' + '001x'+'0110' + '0000'+'0001', + 'ca5': latency + '0010'+'0100' + '1001'+'001x' + '000x'+'1101' + '0010'+'0010', }}, )