From 1bd9455216239c8d78423595b0b9c3d44c3224cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Boczar?= Date: Tue, 3 Aug 2021 09:30:13 +0200 Subject: [PATCH 1/2] phy/lpddr4: update docstring --- litedram/phy/lpddr4/commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litedram/phy/lpddr4/commands.py b/litedram/phy/lpddr4/commands.py index e50eb28..737e318 100644 --- a/litedram/phy/lpddr4/commands.py +++ b/litedram/phy/lpddr4/commands.py @@ -59,7 +59,7 @@ class DFIPhaseAdapter(Module): ---------- dfi_phase : Record(dfi.phase_description), in Input from a single DFI phase. - masked_write : bool + masked_write : bool or Signal(1) Specifies how DFI write command (cas_n=0, ras_n=1, we_n=0) is interpreted, either as LPDDR4 WRITE or MASKED-WRITE. MASKED-WRITE requires larger tCCD, but WRITE does not permit masking of data, so if masking is needed MASKED-WRITE has to be used. From d20e8c763b896e21036e6dbc16603f671bb3961e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Boczar?= Date: Tue, 3 Aug 2021 14:31:34 +0200 Subject: [PATCH 2/2] phy: move simulation related utilities to sim_utils.py --- litedram/phy/lpddr4/sim.py | 50 +------- litedram/phy/lpddr4/simphy.py | 3 +- litedram/phy/lpddr4/simsoc.py | 57 +-------- litedram/phy/sim_utils.py | 233 ++++++++++++++++++++++++++++++++++ litedram/phy/utils.py | 113 +---------------- 5 files changed, 246 insertions(+), 210 deletions(-) create mode 100644 litedram/phy/sim_utils.py diff --git a/litedram/phy/lpddr4/sim.py b/litedram/phy/lpddr4/sim.py index b856a4b..a2163ba 100644 --- a/litedram/phy/lpddr4/sim.py +++ b/litedram/phy/lpddr4/sim.py @@ -14,29 +14,12 @@ from migen import * from litex.soc.interconnect.stream import ClockDomainCrossing from litex.soc.interconnect.csr import AutoCSR -from litedram.common import TappedDelayLine, tXXDController -from litedram.phy.utils import delayed, edge, SimLogger +from litedram.common import TappedDelayLine +from litedram.phy.utils import delayed, edge +from litedram.phy.sim_utils import SimLogger, PulseTiming, log_level_getter from litedram.phy.lpddr4.commands import MPC -def log_level_getter(log_level): - """Parse logging level description - - Log level can be presented in a simple form (e.g. `--log-level=DEBUG`) to specify - the same level for all modules, or can set different levels for different modules - e.g. `--log-level=all=INFO,data=DEBUG`. - """ - def get_level(name): - return getattr(SimLogger, name.upper()) - - if "=" not in log_level: # simple log_level, e.g. "INFO" - return lambda _: get_level(log_level) - - # parse log_level in the per-module form, e.g. "--log-level=all=INFO,data=DEBUG" - per_module = dict(part.split("=") for part in log_level.strip().split(",")) - return lambda module: get_level(per_module.get(module, per_module.get("all", None))) - - class LPDDR4Sim(Module, AutoCSR): """LPDDR4 DRAM simulator @@ -106,33 +89,6 @@ class LPDDR4Sim(Module, AutoCSR): # Commands ----------------------------------------------------------------------------------------- -class PulseTiming(Module): - """Timing monitor with pulse input/output - - This module works like `tXXDController` with the following differences: - - * countdown triggered by a low to high pulse on `trigger` - * `ready` is initially low, only after a trigger it can become high - * provides `ready_p` which is high only for 1 cycle when `ready` becomes high - """ - def __init__(self, t): - self.trigger = Signal() - self.ready = Signal() - self.ready_p = Signal() - - ready_d = Signal() - triggered = Signal() - tctrl = tXXDController(t) - self.submodules += tctrl - - self.sync += If(self.trigger, triggered.eq(1)), - self.comb += [ - self.ready.eq(triggered & tctrl.ready), - self.ready_p.eq(edge(self, self.ready)), - tctrl.valid.eq(edge(self, self.trigger)), - ] - - class CommandsSim(Module, AutoCSR): """Command simulation diff --git a/litedram/phy/lpddr4/simphy.py b/litedram/phy/lpddr4/simphy.py index 36cd15b..36107dd 100644 --- a/litedram/phy/lpddr4/simphy.py +++ b/litedram/phy/lpddr4/simphy.py @@ -6,7 +6,8 @@ from migen import * -from litedram.phy.utils import delayed, Serializer, Deserializer, Latency, SimPad, SimulationPads, SimSerDesMixin +from litedram.phy.utils import delayed, Serializer, Deserializer, Latency +from litedram.phy.sim_utils import SimPad, SimulationPads, SimSerDesMixin from litedram.phy.lpddr4.basephy import LPDDR4PHY, DoubleRateLPDDR4PHY diff --git a/litedram/phy/lpddr4/simsoc.py b/litedram/phy/lpddr4/simsoc.py index b284dd0..4024a1a 100644 --- a/litedram/phy/lpddr4/simsoc.py +++ b/litedram/phy/lpddr4/simsoc.py @@ -10,7 +10,6 @@ import argparse from migen import * from litex.build.generic_platform import Pins, Subsignal -from litex.build.sim import SimPlatform from litex.build.sim.config import SimConfig from litex.soc.interconnect.csr import CSR @@ -26,21 +25,12 @@ from litedram.phy.model import DFITimingsChecker, _speedgrade_timings, _technolo from litedram.phy.lpddr4.simphy import LPDDR4SimPHY, DoubleRateLPDDR4SimPHY from litedram.phy.lpddr4.sim import LPDDR4Sim +from litedram.phy.sim_utils import Clocks, CRG, Platform + # Platform ----------------------------------------------------------------------------------------- _io = [ - # clocks added later - ("sys_rst", 0, Pins(1)), - - ("serial", 0, - Subsignal("source_valid", Pins(1)), - Subsignal("source_ready", Pins(1)), - Subsignal("source_data", Pins(8)), - Subsignal("sink_valid", Pins(1)), - Subsignal("sink_ready", Pins(1)), - Subsignal("sink_data", Pins(8)), - ), - + # clocks added in main() ("lpddr4", 0, Subsignal("clk", Pins(1)), # Subsignal("clk_n", Pins(1)), @@ -56,44 +46,8 @@ _io = [ ), ] -class Platform(SimPlatform): - def __init__(self): - SimPlatform.__init__(self, "SIM", _io) - # Clocks ------------------------------------------------------------------------------------------- -class Clocks(dict): # FORMAT: {name: {"freq_hz": _, "phase_deg": _}, ...} - def names(self): - return list(self.keys()) - - def add_io(self, io): - for name in self.names(): - io.append((name + "_clk", 0, Pins(1))) - - def add_clockers(self, sim_config): - for name, desc in self.items(): - sim_config.add_clocker(name + "_clk", **desc) - -class _CRG(Module): - def __init__(self, platform, domains=None): - if domains is None: - domains = ["sys"] - # request() before creating domains to avoid signal renaming problem - domains = {name: platform.request(name + "_clk") for name in domains} - - self.clock_domains.cd_por = ClockDomain(reset_less=True) - for name in domains.keys(): - setattr(self.clock_domains, "cd_" + name, ClockDomain(name=name)) - - int_rst = Signal(reset=1) - self.sync.por += int_rst.eq(0) - self.comb += self.cd_por.clk.eq(self.cd_sys.clk) - - for name, clk in domains.items(): - cd = getattr(self, "cd_" + name) - self.comb += cd.clk.eq(clk) - self.comb += cd.rst.eq(int_rst) - def get_clocks(sys_clk_freq): return Clocks({ "sys": dict(freq_hz=sys_clk_freq), @@ -115,7 +69,7 @@ class SimSoC(SoCCore): def __init__(self, clocks, log_level, auto_precharge=False, with_refresh=True, trace_reset=0, disable_delay=False, masked_write=True, double_rate_phy=False, finish_after_memtest=False, **kwargs): - platform = Platform() + platform = Platform(_io, clocks) sys_clk_freq = clocks["sys"]["freq_hz"] # SoCCore ---------------------------------------------------------------------------------- @@ -127,7 +81,7 @@ class SimSoC(SoCCore): **kwargs) # CRG -------------------------------------------------------------------------------------- - self.submodules.crg = _CRG(platform, clocks.names()) + self.submodules.crg = CRG(platform, clocks) # Debugging -------------------------------------------------------------------------------- platform.add_debug(self, reset=trace_reset) @@ -341,7 +295,6 @@ def main(): sim_config = SimConfig() sys_clk_freq = int(float(args.sys_clk_freq)) clocks = get_clocks(sys_clk_freq) - clocks.add_io(_io) clocks.add_clockers(sim_config) # Configuration -------------------------------------------------------------------------------- diff --git a/litedram/phy/sim_utils.py b/litedram/phy/sim_utils.py new file mode 100644 index 0000000..3c52b98 --- /dev/null +++ b/litedram/phy/sim_utils.py @@ -0,0 +1,233 @@ +# +# This file is part of LiteDRAM. +# +# Copyright (c) 2021 Antmicro +# SPDX-License-Identifier: BSD-2-Clause + +from migen import * + +from litex.build.sim import SimPlatform +from litex.build.sim.config import SimConfig +from litex.build.generic_platform import Pins, Subsignal +from litex.soc.interconnect.csr import CSRStorage, AutoCSR + +from litedram.common import Settings, tXXDController +from litedram.phy.utils import Serializer, Deserializer, edge + + +# PHY ---------------------------------------------------------------------------------------------- + +class SimSerDesMixin: + """Helper class for easier (de-)serialization to simulation pads.""" + def ser(self, *, i, o, clkdiv, clk, name="", **kwargs): + assert len(o) == 1 + kwargs = dict(i=i, i_dw=len(i), o=o, o_dw=1, clk=clk, clkdiv=clkdiv, + name=f"ser_{name}".strip("_"), **kwargs) + self.submodules += Serializer(**kwargs) + + def des(self, *, i, o, clkdiv, clk, name="", **kwargs): + assert len(i) == 1 + kwargs = dict(i=i, i_dw=1, o=o, o_dw=len(o), clk=clk, clkdiv=clkdiv, + name=f"des_{name}".strip("_"), **kwargs) + self.submodules += Deserializer(**kwargs) + +# Platform ----------------------------------------------------------------------------------------- + +class SimPad(Settings): + def __init__(self, name, width, io=False): + self.set_attributes(locals()) + + +class SimulationPads(Module): + """Pads for simulation purpose + + Tristate pads are simulated as separate input/output pins (name_i, name_o) and + an output-enable pin (name_oe). Output pins are to be driven byt the PHY and + input pins are to be driven by the DRAM simulator. An additional pin without + a suffix is created and this module will include logic to set this pin to the + actual value depending on the output-enable signal. + """ + def layout(self, **kwargs): + raise NotImplementedError("Simulation pads layout as a list of SimPad objects") + + def __init__(self, **kwargs): + for pad in self.layout(**kwargs): + if pad.io: + o, i, oe = (f"{pad.name}_{suffix}" for suffix in ["o", "i", "oe"]) + setattr(self, pad.name, Signal(pad.width)) + setattr(self, o, Signal(pad.width, name=o)) + setattr(self, i, Signal(pad.width, name=i)) + setattr(self, oe, Signal(name=oe)) + self.comb += If(getattr(self, oe), + getattr(self, pad.name).eq(getattr(self, o)) + ).Else( + getattr(self, pad.name).eq(getattr(self, i)) + ) + else: + setattr(self, pad.name, Signal(pad.width, name=pad.name)) + + +class Clocks(dict): + """Helper for definiting simulation clocks + + Dictionary format is `{name: {"freq_hz": _, "phase_deg": _}, ...}`. + """ + def names(self): + return list(self.keys()) + + def add_io(self, io): + for name in self.names(): + io.append((name + "_clk", 0, Pins(1))) + + def add_clockers(self, sim_config): + for name, desc in self.items(): + sim_config.add_clocker(name + "_clk", **desc) + + +class CRG(Module): + """Clock & Reset Generator for Verilator-based simulation""" + def __init__(self, platform, clock_domains=None): + if clock_domains is None: + clock_domains = ["sys"] + elif isinstance(clock_domains, Clocks): + clock_domains = list(clock_domains.names()) + + # request() before creating clock_domains to avoid signal renaming problem + clock_domains = {name: platform.request(name + "_clk") for name in clock_domains} + + self.clock_domains.cd_por = ClockDomain(reset_less=True) + for name in clock_domains.keys(): + setattr(self.clock_domains, "cd_" + name, ClockDomain(name=name)) + + int_rst = Signal(reset=1) + self.sync.por += int_rst.eq(0) + self.comb += self.cd_por.clk.eq(self.cd_sys.clk) + + for name, clk in clock_domains.items(): + cd = getattr(self, "cd_" + name) + self.comb += cd.clk.eq(clk) + self.comb += cd.rst.eq(int_rst) + + +class Platform(SimPlatform): + def __init__(self, io, clocks: Clocks): + common_io = [ + ("sys_rst", 0, Pins(1)), + + ("serial", 0, + Subsignal("source_valid", Pins(1)), + Subsignal("source_ready", Pins(1)), + Subsignal("source_data", Pins(8)), + Subsignal("sink_valid", Pins(1)), + Subsignal("sink_ready", Pins(1)), + Subsignal("sink_data", Pins(8)), + ), + ] + clocks.add_io(common_io) + SimPlatform.__init__(self, "SIM", common_io + io) + +# Logging ------------------------------------------------------------------------------------------ + +class SimLogger(Module, AutoCSR): + """Logger for use in simulation + + This module allows for easier message logging when running simulation designs. + The logger can be used from `comb` context so it the methods can be directly + used inside `FSM` code. It also provides logging levels that can be used to + filter messages, either by specifying the default `log_level` or in runtime + by driving to the `level` signal or using a corresponding CSR. + """ + # Allows to use Display inside FSM and to filter log messages by level (statically or dynamically) + DEBUG = 0 + INFO = 1 + WARN = 2 + ERROR = 3 + NONE = 4 + + def __init__(self, log_level=INFO, clk_freq=None): + self.ops = [] + self.level = Signal(reset=log_level, max=self.NONE) + self.time_ps = None + if clk_freq is not None: + self.time_ps = Signal(64) + cnt = Signal(64) + self.sync += cnt.eq(cnt + 1) + self.comb += self.time_ps.eq(cnt * int(1e12/clk_freq)) + + def debug(self, fmt, *args, **kwargs): + return self.log("[DEBUG] " + fmt, *args, level=self.DEBUG, **kwargs) + + def info(self, fmt, *args, **kwargs): + return self.log("[INFO] " + fmt, *args, level=self.INFO, **kwargs) + + def warn(self, fmt, *args, **kwargs): + return self.log("[WARN] " + fmt, *args, level=self.WARN, **kwargs) + + def error(self, fmt, *args, **kwargs): + return self.log("[ERROR] " + fmt, *args, level=self.ERROR, **kwargs) + + def log(self, fmt, *args, level=DEBUG, once=True): + cond = Signal() + if once: # make the condition be triggered only on rising edge + condition = edge(self, cond) + else: + condition = cond + + self.ops.append((level, condition, fmt, args)) + return cond.eq(1) + + def add_csrs(self): + self._level = CSRStorage(len(self.level), reset=self.level.reset.value) + self.comb += self.level.eq(self._level.storage) + + def do_finalize(self): + for level, cond, fmt, args in self.ops: + if self.time_ps is not None: + fmt = f"[%16d ps] {fmt}" + args = (self.time_ps, *args) + self.sync += If((level >= self.level) & cond, Display(fmt, *args)) + +def log_level_getter(log_level): + """Parse logging level description + + Log level can be presented in a simple form (e.g. `--log-level=DEBUG`) to specify + the same level for all modules, or can set different levels for different modules + e.g. `--log-level=all=INFO,data=DEBUG`. + """ + def get_level(name): + return getattr(SimLogger, name.upper()) + + if "=" not in log_level: # simple log_level, e.g. "INFO" + return lambda _: get_level(log_level) + + # parse log_level in the per-module form, e.g. "--log-level=all=INFO,data=DEBUG" + per_module = dict(part.split("=") for part in log_level.strip().split(",")) + return lambda module: get_level(per_module.get(module, per_module.get("all", None))) + +# Simulator ---------------------------------------------------------------------------------------- + +class PulseTiming(Module): + """Timing monitor with pulse input/output + + This module works like `tXXDController` with the following differences: + + * countdown triggered by a low to high pulse on `trigger` + * `ready` is initially low, only after a trigger it can become high + * provides `ready_p` which is high only for 1 cycle when `ready` becomes high + """ + def __init__(self, t): + self.trigger = Signal() + self.ready = Signal() + self.ready_p = Signal() + + ready_d = Signal() + triggered = Signal() + tctrl = tXXDController(t) + self.submodules += tctrl + + self.sync += If(self.trigger, triggered.eq(1)), + self.comb += [ + self.ready.eq(triggered & tctrl.ready), + self.ready_p.eq(edge(self, self.ready)), + tctrl.valid.eq(edge(self, self.trigger)), + ] diff --git a/litedram/phy/utils.py b/litedram/phy/utils.py index e980962..b54a589 100644 --- a/litedram/phy/utils.py +++ b/litedram/phy/utils.py @@ -14,9 +14,8 @@ from collections import defaultdict from migen import * from litex.soc.interconnect import stream -from litex.soc.interconnect.csr import CSRStorage, AutoCSR -from litedram.common import TappedDelayLine, Settings +from litedram.common import TappedDelayLine def bit(n, val): @@ -44,6 +43,7 @@ def edge(mod, cond): mod.sync += cond_d.eq(cond) return ~cond_d & cond + class ConstBitSlip(Module): def __init__(self, dw, slp, cycles, i=None, o=None, register=True): self.i = Signal(dw, name='i') if i is None else i @@ -73,6 +73,7 @@ class ConstBitSlip(Module): """Minimum number of cycles to be able to use given bitslip values""" return math.ceil((slp + 1) / dw) + # TODO: rewrite DQSPattern in litedram/common.py to support different data widths class DQSPattern(Module): def __init__(self, preamble=None, postamble=None, wlevel_en=0, wlevel_strobe=0, register=False): @@ -138,39 +139,6 @@ class Latency: return "Latency({} sys clk)".format(self._sys) -class SimPad(Settings): - def __init__(self, name, width, io=False): - self.set_attributes(locals()) - -class SimulationPads(Module): - """Pads for simulation purpose - - Tristate pads are simulated as separate input/output pins (name_i, name_o) and - an output-enable pin (name_oe). Output pins are to be driven byt the PHY and - input pins are to be driven by the DRAM simulator. An additional pin without - a suffix is created and this module will include logic to set this pin to the - actual value depending on the output-enable signal. - """ - def layout(self, **kwargs): - raise NotImplementedError("Simulation pads layout as a list of SimPad objects") - - def __init__(self, **kwargs): - for pad in self.layout(**kwargs): - if pad.io: - o, i, oe = (f"{pad.name}_{suffix}" for suffix in ["o", "i", "oe"]) - setattr(self, pad.name, Signal(pad.width)) - setattr(self, o, Signal(pad.width, name=o)) - setattr(self, i, Signal(pad.width, name=i)) - setattr(self, oe, Signal(name=oe)) - self.comb += If(getattr(self, oe), - getattr(self, pad.name).eq(getattr(self, o)) - ).Else( - getattr(self, pad.name).eq(getattr(self, i)) - ) - else: - setattr(self, pad.name, Signal(pad.width, name=pad.name)) - - class CommandsPipeline(Module): """Commands pipeline logic for LPDDR4/LPDDR5 @@ -339,81 +307,6 @@ class Deserializer(Module): sd_clkdiv += self.o.eq(Cat(as_array(o_pre_d)[:-1], as_array(o_pre)[-1])) -class SimSerDesMixin: - """Helper class for easier (de-)serialization to simulation pads.""" - def ser(self, *, i, o, clkdiv, clk, name="", **kwargs): - assert len(o) == 1 - kwargs = dict(i=i, i_dw=len(i), o=o, o_dw=1, clk=clk, clkdiv=clkdiv, - name=f"ser_{name}".strip("_"), **kwargs) - self.submodules += Serializer(**kwargs) - - def des(self, *, i, o, clkdiv, clk, name="", **kwargs): - assert len(i) == 1 - kwargs = dict(i=i, i_dw=1, o=o, o_dw=len(o), clk=clk, clkdiv=clkdiv, - name=f"des_{name}".strip("_"), **kwargs) - self.submodules += Deserializer(**kwargs) - - -class SimLogger(Module, AutoCSR): - """Logger for use in simulation - - This module allows for easier message logging when running simulation designs. - The logger can be used from `comb` context so it the methods can be directly - used inside `FSM` code. It also provides logging levels that can be used to - filter messages, either by specifying the default `log_level` or in runtime - by driving to the `level` signal or using a corresponding CSR. - """ - # Allows to use Display inside FSM and to filter log messages by level (statically or dynamically) - DEBUG = 0 - INFO = 1 - WARN = 2 - ERROR = 3 - NONE = 4 - - def __init__(self, log_level=INFO, clk_freq=None): - self.ops = [] - self.level = Signal(reset=log_level, max=self.NONE) - self.time_ps = None - if clk_freq is not None: - self.time_ps = Signal(64) - cnt = Signal(64) - self.sync += cnt.eq(cnt + 1) - self.comb += self.time_ps.eq(cnt * int(1e12/clk_freq)) - - def debug(self, fmt, *args, **kwargs): - return self.log("[DEBUG] " + fmt, *args, level=self.DEBUG, **kwargs) - - def info(self, fmt, *args, **kwargs): - return self.log("[INFO] " + fmt, *args, level=self.INFO, **kwargs) - - def warn(self, fmt, *args, **kwargs): - return self.log("[WARN] " + fmt, *args, level=self.WARN, **kwargs) - - def error(self, fmt, *args, **kwargs): - return self.log("[ERROR] " + fmt, *args, level=self.ERROR, **kwargs) - - def log(self, fmt, *args, level=DEBUG, once=True): - cond = Signal() - if once: # make the condition be triggered only on rising edge - condition = edge(self, cond) - else: - condition = cond - - self.ops.append((level, condition, fmt, args)) - return cond.eq(1) - - def add_csrs(self): - self._level = CSRStorage(len(self.level), reset=self.level.reset.value) - self.comb += self.level.eq(self._level.storage) - - def do_finalize(self): - for level, cond, fmt, args in self.ops: - if self.time_ps is not None: - fmt = f"[%16d ps] {fmt}" - args = (self.time_ps, *args) - self.sync += If((level >= self.level) & cond, Display(fmt, *args)) - - class HoldValid(Module): """Hold input data until ready