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

View File

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

View File

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

View File

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

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