Merge pull request #267 from antmicro/jboc/lpddr4-update
LPDDR4 minor refactor
This commit is contained in:
commit
ae139096c0
|
@ -59,7 +59,7 @@ class DFIPhaseAdapter(Module):
|
||||||
----------
|
----------
|
||||||
dfi_phase : Record(dfi.phase_description), in
|
dfi_phase : Record(dfi.phase_description), in
|
||||||
Input from a single DFI phase.
|
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
|
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
|
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.
|
not permit masking of data, so if masking is needed MASKED-WRITE has to be used.
|
||||||
|
|
|
@ -14,29 +14,12 @@ from migen import *
|
||||||
from litex.soc.interconnect.stream import ClockDomainCrossing
|
from litex.soc.interconnect.stream import ClockDomainCrossing
|
||||||
from litex.soc.interconnect.csr import AutoCSR
|
from litex.soc.interconnect.csr import AutoCSR
|
||||||
|
|
||||||
from litedram.common import TappedDelayLine, tXXDController
|
from litedram.common import TappedDelayLine
|
||||||
from litedram.phy.utils import delayed, edge, SimLogger
|
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
|
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):
|
class LPDDR4Sim(Module, AutoCSR):
|
||||||
"""LPDDR4 DRAM simulator
|
"""LPDDR4 DRAM simulator
|
||||||
|
|
||||||
|
@ -106,33 +89,6 @@ class LPDDR4Sim(Module, AutoCSR):
|
||||||
|
|
||||||
# Commands -----------------------------------------------------------------------------------------
|
# 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):
|
class CommandsSim(Module, AutoCSR):
|
||||||
"""Command simulation
|
"""Command simulation
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,8 @@
|
||||||
|
|
||||||
from migen import *
|
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
|
from litedram.phy.lpddr4.basephy import LPDDR4PHY, DoubleRateLPDDR4PHY
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,6 @@ import argparse
|
||||||
from migen import *
|
from migen import *
|
||||||
|
|
||||||
from litex.build.generic_platform import Pins, Subsignal
|
from litex.build.generic_platform import Pins, Subsignal
|
||||||
from litex.build.sim import SimPlatform
|
|
||||||
from litex.build.sim.config import SimConfig
|
from litex.build.sim.config import SimConfig
|
||||||
|
|
||||||
from litex.soc.interconnect.csr import CSR
|
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.simphy import LPDDR4SimPHY, DoubleRateLPDDR4SimPHY
|
||||||
from litedram.phy.lpddr4.sim import LPDDR4Sim
|
from litedram.phy.lpddr4.sim import LPDDR4Sim
|
||||||
|
|
||||||
|
from litedram.phy.sim_utils import Clocks, CRG, Platform
|
||||||
|
|
||||||
# Platform -----------------------------------------------------------------------------------------
|
# Platform -----------------------------------------------------------------------------------------
|
||||||
|
|
||||||
_io = [
|
_io = [
|
||||||
# clocks added later
|
# clocks added in main()
|
||||||
("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)),
|
|
||||||
),
|
|
||||||
|
|
||||||
("lpddr4", 0,
|
("lpddr4", 0,
|
||||||
Subsignal("clk", Pins(1)),
|
Subsignal("clk", Pins(1)),
|
||||||
# Subsignal("clk_n", Pins(1)),
|
# Subsignal("clk_n", Pins(1)),
|
||||||
|
@ -56,44 +46,8 @@ _io = [
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
class Platform(SimPlatform):
|
|
||||||
def __init__(self):
|
|
||||||
SimPlatform.__init__(self, "SIM", _io)
|
|
||||||
|
|
||||||
# Clocks -------------------------------------------------------------------------------------------
|
# 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):
|
def get_clocks(sys_clk_freq):
|
||||||
return Clocks({
|
return Clocks({
|
||||||
"sys": dict(freq_hz=sys_clk_freq),
|
"sys": dict(freq_hz=sys_clk_freq),
|
||||||
|
@ -115,7 +69,7 @@ class SimSoC(SoCCore):
|
||||||
def __init__(self, clocks, log_level,
|
def __init__(self, clocks, log_level,
|
||||||
auto_precharge=False, with_refresh=True, trace_reset=0, disable_delay=False,
|
auto_precharge=False, with_refresh=True, trace_reset=0, disable_delay=False,
|
||||||
masked_write=True, double_rate_phy=False, finish_after_memtest=False, **kwargs):
|
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"]
|
sys_clk_freq = clocks["sys"]["freq_hz"]
|
||||||
|
|
||||||
# SoCCore ----------------------------------------------------------------------------------
|
# SoCCore ----------------------------------------------------------------------------------
|
||||||
|
@ -127,7 +81,7 @@ class SimSoC(SoCCore):
|
||||||
**kwargs)
|
**kwargs)
|
||||||
|
|
||||||
# CRG --------------------------------------------------------------------------------------
|
# CRG --------------------------------------------------------------------------------------
|
||||||
self.submodules.crg = _CRG(platform, clocks.names())
|
self.submodules.crg = CRG(platform, clocks)
|
||||||
|
|
||||||
# Debugging --------------------------------------------------------------------------------
|
# Debugging --------------------------------------------------------------------------------
|
||||||
platform.add_debug(self, reset=trace_reset)
|
platform.add_debug(self, reset=trace_reset)
|
||||||
|
@ -341,7 +295,6 @@ def main():
|
||||||
sim_config = SimConfig()
|
sim_config = SimConfig()
|
||||||
sys_clk_freq = int(float(args.sys_clk_freq))
|
sys_clk_freq = int(float(args.sys_clk_freq))
|
||||||
clocks = get_clocks(sys_clk_freq)
|
clocks = get_clocks(sys_clk_freq)
|
||||||
clocks.add_io(_io)
|
|
||||||
clocks.add_clockers(sim_config)
|
clocks.add_clockers(sim_config)
|
||||||
|
|
||||||
# Configuration --------------------------------------------------------------------------------
|
# Configuration --------------------------------------------------------------------------------
|
||||||
|
|
|
@ -0,0 +1,233 @@
|
||||||
|
#
|
||||||
|
# This file is part of LiteDRAM.
|
||||||
|
#
|
||||||
|
# Copyright (c) 2021 Antmicro <www.antmicro.com>
|
||||||
|
# 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)),
|
||||||
|
]
|
|
@ -14,9 +14,8 @@ from collections import defaultdict
|
||||||
from migen import *
|
from migen import *
|
||||||
|
|
||||||
from litex.soc.interconnect import stream
|
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):
|
def bit(n, val):
|
||||||
|
@ -44,6 +43,7 @@ def edge(mod, cond):
|
||||||
mod.sync += cond_d.eq(cond)
|
mod.sync += cond_d.eq(cond)
|
||||||
return ~cond_d & cond
|
return ~cond_d & cond
|
||||||
|
|
||||||
|
|
||||||
class ConstBitSlip(Module):
|
class ConstBitSlip(Module):
|
||||||
def __init__(self, dw, slp, cycles, i=None, o=None, register=True):
|
def __init__(self, dw, slp, cycles, i=None, o=None, register=True):
|
||||||
self.i = Signal(dw, name='i') if i is None else i
|
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"""
|
"""Minimum number of cycles to be able to use given bitslip values"""
|
||||||
return math.ceil((slp + 1) / dw)
|
return math.ceil((slp + 1) / dw)
|
||||||
|
|
||||||
|
|
||||||
# TODO: rewrite DQSPattern in litedram/common.py to support different data widths
|
# TODO: rewrite DQSPattern in litedram/common.py to support different data widths
|
||||||
class DQSPattern(Module):
|
class DQSPattern(Module):
|
||||||
def __init__(self, preamble=None, postamble=None, wlevel_en=0, wlevel_strobe=0, register=False):
|
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)
|
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):
|
class CommandsPipeline(Module):
|
||||||
"""Commands pipeline logic for LPDDR4/LPDDR5
|
"""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]))
|
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):
|
class HoldValid(Module):
|
||||||
"""Hold input data until ready
|
"""Hold input data until ready
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue