Merge pull request #267 from antmicro/jboc/lpddr4-update

LPDDR4 minor refactor
This commit is contained in:
enjoy-digital 2021-08-06 14:39:42 +02:00 committed by GitHub
commit ae139096c0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 247 additions and 211 deletions

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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 --------------------------------------------------------------------------------

233
litedram/phy/sim_utils.py Normal file
View File

@ -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)),
]

View File

@ -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