phy/lpddr5/sim: add verification of initialization sequence

This commit is contained in:
Jędrzej Boczar 2021-07-14 14:00:42 +02:00 committed by Alessandro Comodi
parent 27da8c5c43
commit c795bafda7
2 changed files with 138 additions and 7 deletions

View File

@ -4,6 +4,7 @@
# Copyright (c) 2021 Antmicro <www.antmicro.com>
# SPDX-License-Identifier: BSD-2-Clause
import math
from operator import or_
from functools import reduce
from collections import OrderedDict
@ -15,7 +16,7 @@ from litex.soc.interconnect.csr import AutoCSR
#
from litedram.common import TappedDelayLine
from litedram.phy.utils import edge, delayed
from litedram.phy.sim_utils import SimLogger, PulseTiming, log_level_getter
from litedram.phy.sim_utils import SimLogger, Timing, PulseTiming, log_level_getter
CMD_INFO_LAYOUT = [
@ -32,7 +33,7 @@ gtkw_dbg = {}
class LPDDR5Sim(Module, AutoCSR):
"""LPDDR5 DRAM simulation
"""
def __init__(self, pads, *, log_level, logger_kwargs):
def __init__(self, pads, *, ck_freq, log_level, logger_kwargs):
log_level = log_level_getter(log_level)
self.clock_domains.cd_ck = ClockDomain(reset_less=True)
@ -53,7 +54,7 @@ class LPDDR5Sim(Module, AutoCSR):
cmd_info = stream.Endpoint(CMD_INFO_LAYOUT)
gtkw_dbg["cmd_info"] = cmd_info
cmd = CommandsSim(pads, cmd_info, logger_kwargs=logger_kwargs, log_level=log_level)
cmd = CommandsSim(pads, cmd_info, ck_freq=ck_freq, logger_kwargs=logger_kwargs, log_level=log_level)
self.submodules.cmd = ClockDomainsRenamer("ck")(cmd)
data = DataSim(pads, cmd_info, cmd.data_timer.ready_p, logger_kwargs=logger_kwargs, log_level=log_level)
@ -224,7 +225,7 @@ class Sync(list):
class CommandsSim(Module, AutoCSR):
def __init__(self, pads, cmd_info, *, log_level, logger_kwargs):
def __init__(self, pads, cmd_info, *, ck_freq, log_level, logger_kwargs):
self.submodules.log = SimLogger(log_level=log_level("cmd"), **logger_kwargs)
self.comb += self.log.info("Simulation start")
@ -235,6 +236,13 @@ class CommandsSim(Module, AutoCSR):
self.active_banks = Array([Signal(name=f"bank{i}_active") for i in range(self.nbanks)])
self.active_rows = Array([Signal(18, name=f"bank{i}_active_row") for i in range(self.nbanks)])
# MPC operand
self.mpc_op = Signal(8)
# ZQ calibration TODO: implement ZQC logic
self.zqc_start = Signal()
self.zqc_latch = Signal()
# The captured command is delayed and the timer starts 1 cycle later:
# CK --____----____----____----____----____--
# CS __--------______________________________ (center-aligned to CK)
@ -281,11 +289,12 @@ class CommandsSim(Module, AutoCSR):
MRW = self.mrw_handler(),
DATA = self.data_handler(),
CAS = self.cas_handler(),
MPC = self.mpc_handler(),
# MRR
# WRITE/MASKED-WRITE
# READ
# CAS
# MPC
# MRR
# WFF/RFF?
# RDC?
)
@ -294,10 +303,116 @@ class CommandsSim(Module, AutoCSR):
If(self.handle_cmd & ~reduce(or_, cmd_handlers.values()),
self.log.error("Unexpected command: CA_p=0b%07b CA_n=0b%07b", self.ca_p, self.ca_n)
),
cmds_enabled.eq(1),
If(cs & ~cmds_enabled,
self.log.warn("Received command when no commands should be sent: CA_p=0b%07b CA_n=0b%07b", self.ca_p, self.ca_n)
),
]
ck = lambda t: math.ceil(t * ck_freq)
ms, us, ns = 1e-3, 1e-6, 1e-9
self.submodules.tinit0 = PulseTiming(ck(20*ms)) # (max) voltage-ramp at power-up; not applicable in the simulation
self.submodules.tinit1 = PulseTiming(ck(200*us)) # (min) reset_n low time after voltage-ramp completion
self.submodules.tinit2 = Timing(ck(10*ns)) # (min) CS low time before reset deassertion
self.submodules.tinit3 = PulseTiming(ck(2*ms)) # (min) CS low time after reset deassertion
self.submodules.tinit4 = PulseTiming(5) # (min) stabilized CK before CS high; not really applicable in this simulation
self.submodules.tinit5 = PulseTiming(ck(2*us)) # (min) idle time before first MRW/MRR cmmand
self.submodules.tzqlat = PulseTiming(max(4, ck(30*ns))) # (min) ZQCAL latch quiet time
self.comb += [
If(edge(self, pads.reset_n),
self.log.info("RESET released"),
).Elif(edge(self, ~pads.reset_n),
self.log.info("RESET asserted"),
),
]
# NOTE: for simulation purpose we assume that CK is always running because CommandsSim is clocked
# from it, or else the states up to Tc would make no sense because the timings would not be counted
self.submodules.fsm = fsm = FSM()
fsm.act("RESET",
self.tinit1.trigger.eq(1),
self.tinit2.valid.eq(~pads.cs),
If(edge(self, pads.reset_n),
If(~self.tinit1.ready,
self.log.warn("tINIT1 violated: RESET deasserted too fast")
),
If(~self.tinit2.ready,
self.log.warn("tINIT2 violated: CS LOW for too short before deasserting RESET")
),
NextState("WAIT-NOP") # Tc
),
)
fsm.act("WAIT-NOP",
self.tinit3.trigger.eq(1),
If(cs & ~self.tinit3.ready,
self.log.warn("tINIT3 violated: CS high too fast after RESET deassertion")
),
self.tinit4.trigger.eq(1),
If(cs & ~self.tinit4.ready,
self.log.warn("tINIT4 violated: CS high too fast after stable CK")
),
If(cs,
self.tinit5.trigger.eq(1),
If(~self.tinit4.ready,
self.log.warn("tINIT4 violated: CS HIGH too fast while waiting for initial NOP")
),
If((self.ca_p != 0) | (self.ca_n != 0),
self.log.warn("Waiting for NOP but got CA_p=0b%07b CA_n=0b%07b", self.ca_p, self.ca_n)
),
NextState("NO-CMDS"), # Te
)
)
fsm.act("NO-CMDS",
self.tinit5.trigger.eq(1),
If(cs,
self.log.warn("tINIT5 violated: command issued too fast after initial NOP")
),
If(self.tinit5.ready,
NextState("MODE-REGS") # Tf
)
)
fsm.act("MODE-REGS",
cmds_enabled.eq(1),
# If(self.handle_cmd & ~cmd_handlers["MRW"] & ~cmd_handlers["MRR"] & ~cmd_handlers["MPC"], # TODO: MRR
If(self.handle_cmd & ~cmd_handlers["MRW"] & ~cmd_handlers["MPC"],
self.log.warn("Only MRW/MRR commands expected before ZQ Latch ..."),
self.log.warn("... " + " ".join("{}=%d".format(cmd) for cmd in cmd_handlers.keys()), *cmd_handlers.values()),
),
If(cmd_handlers["MPC"],
If(~self.zqc_latch,
self.log.error("ZQC-LATCH expected, got MPC with op=0b%07b", self.mpc_op)
).Else(
NextState("ZQC-LATCH") # Tg
)
),
)
fsm.act("ZQC-LATCH",
cmds_enabled.eq(1),
self.tzqlat.trigger.eq(1),
If(self.handle_cmd,
self.log.error("tZQCAL violated: new command issued too fast: CA_p=0b%07b CA_n=0b%07b", self.ca_p, self.ca_n)
),
If(self.tzqlat.ready,
NextState("NORMAL"), # Th
)
)
fsm.act("NORMAL",
cmds_enabled.eq(1)
)
# Log state transitions
fsm.finalize()
prev_state = delayed(self, fsm.state)
self.comb += If(prev_state != fsm.state,
Case(prev_state, {
state: Case(fsm.state, {
next_state: self.log.info(f"FSM: {state_name} -> {next_state_name}")
for next_state, next_state_name in fsm.decoding.items()
})
for state, state_name in fsm.decoding.items()
})
)
def activate_handler(self):
bank = Signal(max=self.nbanks)
row1 = Signal(4)
@ -384,6 +499,21 @@ class CommandsSim(Module, AutoCSR):
]
)
def mpc_handler(self):
op = Signal(8)
return self.cmd_one_step("MRW",
cond = self.ca_p[:6] == 0b110000,
body = [
op.eq(Cat(self.ca_n, self.ca_p[6])),
self.mpc_op.eq(op),
self.zqc_start.eq(op == 0b10000101),
self.zqc_latch.eq(op == 0b10000110),
If(~(self.zqc_start | self.zqc_latch),
self.log.warn("Unsupported MPC command: op=0b%08b", op),
),
],
)
def cas_handler(self):
ws_wr = Signal()
ws_rd = Signal()

View File

@ -139,6 +139,7 @@ class SimSoC(SoCCore):
# LPDDR5 Sim -------------------------------------------------------------------------------
self.submodules.lpddr5sim = LPDDR5Sim(
pads = self.ddrphy.pads,
ck_freq = sys_clk_freq,
log_level = log_level,
logger_kwargs = dict(
clk_freq_cd = f"sys{2*wck_ck_ratio}x",