diff --git a/litedram/phy/lpddr5/sim.py b/litedram/phy/lpddr5/sim.py index a740a40..88ff8ca 100644 --- a/litedram/phy/lpddr5/sim.py +++ b/litedram/phy/lpddr5/sim.py @@ -18,6 +18,8 @@ from litedram.common import TappedDelayLine from litedram.phy.utils import edge, delayed from litedram.phy.sim_utils import SimLogger, Timing, PulseTiming, log_level_getter +from litedram.phy.lpddr5.basephy import get_frange + CMD_INFO_LAYOUT = [ ("we", 1), @@ -33,7 +35,7 @@ gtkw_dbg = {} class LPDDR5Sim(Module, AutoCSR): """LPDDR5 DRAM simulation """ - def __init__(self, pads, *, ck_freq, log_level, logger_kwargs, check_timings=True): + def __init__(self, pads, *, ck_freq, wck_ck_ratio, log_level, logger_kwargs, check_timings=True): log_level = log_level_getter(log_level) self.clock_domains.cd_ck = ClockDomain(reset_less=True) @@ -61,6 +63,8 @@ class LPDDR5Sim(Module, AutoCSR): data = DataSim(pads, cmd_info, latency_ready = cmd.data_timer.ready_p, mode_regs = cmd.mode_regs, + ck_freq = ck_freq, + wck_ck_ratio = wck_ck_ratio, logger_kwargs = logger_kwargs, log_level = log_level ) @@ -222,6 +226,7 @@ class ModeRegisters(Module, AutoCSR): ], ) + class Sync(list): # Helper for combining comb and sync def __init__(self, arg): @@ -701,7 +706,7 @@ class CommandsSim(Module, AutoCSR): class DataSim(Module, AutoCSR): - def __init__(self, pads, cmd_info, latency_ready, mode_regs, *, log_level, logger_kwargs, nrows=32768, ncols=1024, nbanks=16): + def __init__(self, pads, cmd_info, latency_ready, mode_regs, ck_freq, wck_ck_ratio, *, log_level, logger_kwargs, nrows=32768, ncols=1024, nbanks=16): self.submodules.log = SimLogger(log_level=log_level("data"), **logger_kwargs) # CommandsSim produces the data required for handling a data command via cmd_info endpoint. @@ -709,17 +714,48 @@ class DataSim(Module, AutoCSR): # and store the information in a FIFO, so that it is possible to pipeline data commands. self.submodules.cmd_buf = stream.PipeValid(CMD_INFO_LAYOUT) gtkw_dbg["cmd_buf"] = self.cmd_buf + + # The CommandsSim data handling generated command is clocked under the system clock domain. + # Instead, the DataSim is clocked in the wck clock domain. + # Given that, with a dynamic wck regime, wck needs to get through a preamble phase where it + # is not active for a certain amount of clock cycles, the command buffer pipeline may not + # be able to see changes in the data commands generated by CommandSim. + # + # The following logic verifies whether a delay needs to be added and adds it accordingly to + # the data command, so that the pipe stream can intercept the various commands from the ck + # domain correctly. + twck = 1 / (ck_freq * wck_ck_ratio) + frange = get_frange(twck, wck_ck_ratio).for_set(wl_set="A", rl_set=0) + taps = max(1, max(frange.t_wckenl_wr, frange.t_wckenl_rd) + frange.t_wckpre_static) - 1 + + def delay_cmd_info(signals): + for signal in signals: + tapped_delay = ClockDomainsRenamer("ck")(TappedDelayLine(getattr(cmd_info, signal), ntaps=taps)) + setattr(self.submodules, f"{signal}_tap", tapped_delay) + + self.comb += [ + getattr(self.cmd_buf.sink, signal).eq(reduce(or_, getattr(self, f"{signal}_tap").taps[0:taps])), + ] + + if taps > 0: + delay_cmd_info([tup[0] for tup in CMD_INFO_LAYOUT] + ["valid", "ready", "first", "last"]) + + else: + self.comb += [ + cmd_info.connect(self.cmd_buf.sink, omit={"ready", "valid"}), + # to latch a command only once we use an edge here, which we can do as there is no way + # for 2 valid commands cycle-by-cycle + self.cmd_buf.sink.valid.eq(edge(self, cmd_info.valid)), + # if for some reason buffer hasn't been cleared, then we have an error + If(self.cmd_buf.sink.valid & ~self.cmd_buf.sink.ready, + self.log.error("Simulator internal error: CMD-to-DATA overflow") + ), + ] + self.comb += [ - cmd_info.connect(self.cmd_buf.sink, omit={"ready", "valid"}), cmd_info.ready.eq(1), - # to latch a command only once we use an edge here, which we can do as there is no way - # for 2 valid commands cycle-by-cycle - self.cmd_buf.sink.valid.eq(edge(self, cmd_info.valid)), - # if for some reason buffer hasn't been cleared, then we have an error - If(self.cmd_buf.sink.valid & ~self.cmd_buf.sink.ready, - self.log.error("Simulator internal error: CMD-to-DATA overflow") - ), ] + cmd = self.cmd_buf.source # DRAM Memory storage diff --git a/litedram/phy/lpddr5/simsoc.py b/litedram/phy/lpddr5/simsoc.py index 0d03af0..0bb44dd 100644 --- a/litedram/phy/lpddr5/simsoc.py +++ b/litedram/phy/lpddr5/simsoc.py @@ -168,6 +168,7 @@ class SimSoC(SoCCore): self.submodules.lpddr5sim = LPDDR5Sim( pads = self.ddrphy.pads, ck_freq = dfi_converter_ratio*sys_clk_freq, + wck_ck_ratio = wck_ck_ratio, check_timings = not disable_delay, log_level = log_level, logger_kwargs = dict(