lpddr4: improve documentation

This commit is contained in:
Jędrzej Boczar 2021-02-15 16:24:46 +01:00
parent 1b65e858b3
commit 052dc19246
6 changed files with 183 additions and 22 deletions

View File

@ -14,6 +14,7 @@ from litedram.phy.lpddr4.commands import DFIPhaseAdapter
class Latency:
"""Used to specify latency for LPDDR4PHY"""
def __init__(self, sys, sys8x=0):
self.sys = sys + sys8x//8
self.sys8x = sys8x % 8
@ -47,6 +48,42 @@ class LPDDR4Output:
class LPDDR4PHY(Module, AutoCSR):
"""Core of LPDDR4 PHYs.
This class implements all the logic required to convert DFI to/from pads.
It works in a single clock domain. Signals for DRAM pads are stored in
LPDDR4Output (self.out). Concrete implementations of LPDDR4 PHYs derive
from this class and perform (de-)serialization of LPDDR4Output to pads.
DFI commands
------------
Not all LPDDR4 commands map directly to DFI commands. For this reason ZQC
is treated specially in that DFI ZQC is translated into LPDDR4 MPC and has
different interpretation depending on DFI.address.
Also, due to the fact that LPDDR4 has 256-bit Mode Register space, the DFI
MRS command encodes both register address *and* value in DFI.address (instead
of the default in LiteDRAM to split them between DFI.address and DFI.bank).
Refer to the documentation in `commands.py` for further information.
Parameters
----------
pads : object
Object containing LPDDR4 pads.
sys_clk_freq : float
Frequency of memory controller's clock.
ser_latency : Latency
Additional latency introduced due to signal serialization.
des_latency : Latency
Additional latency introduced during signal deserialization.
phytype : str
Name of the PHY (concrete implementation).
masked_write : bool
Use MASKED-WRITE commands if True else use WRITE commands (data masking will not work).
cmd_delay : int
Used to force cmd delay during initialization in BIOS.
"""
def __init__(self, pads, *,
sys_clk_freq, ser_latency, des_latency, phytype,
masked_write=True, cmd_delay=None):

View File

@ -32,8 +32,25 @@ class DFIPhaseAdapter(Module):
consist only of a single "small command". To make counting DRAM timings easier, such
a "small command" shall be sent on the 2nd slot (i.e. 3rd and 4th cycle). All timings
are then counted starting from CS low on the 4th cycle.
"""
Parameters
----------
dfi_phase : Record(dfi.phase_description), in
Input from a single DFI phase.
masked_write : bool
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.
Attributes
----------
cs : Signal(4), out
Values of CS on 4 subsequent DRAM SDR clock cycles.
ca : Array(4, Signal(6)), out
Values of CA[5:0] on 4 subsequent DRAM SDR clock cycles.
valid : Signal, out
Indicates that a valid command is presented on the `cs` and `ca` outputs.
"""
def __init__(self, dfi_phase, masked_write=True):
# CS/CA values for 4 SDR cycles
self.cs = Signal(4)
@ -90,11 +107,20 @@ class Command(Module):
Decodes a command from single DFI phase into LPDDR4 "small command"
consisting of 2 CS values and 2 CA[5:0] values.
LPDDR4 "small commands" are transmited over 2 clock cycles. In first
LPDDR4 "small commands" are transmited over 2 clock cycles. In the first
cycle CS is driven high and in the second cycle it stays low. In each
of the cycles the bits on CA[5:0] are latched and interpreted differently.
This module translates a DFI command into the values of CS/CA that shall
be transmitted over 2 DRAM clock cycles.
Attributes
----------
dfi : Record(dfi.phase_description), in
Input from single DFI phase.
cs : Signal(2), out
CS values over 2 subsequent DRAM SDR clock cycles.
ca : Array(2, Signal(6)), out
CA[5:0] values over 2 subsequent DRAM SDR clock cycles.
"""
# String description of 1st and 2nd edge of each command, later parsed to
@ -126,9 +152,9 @@ class Command(Module):
def set(self, cmd):
ops = []
for i, description in enumerate(self.TRUTH_TABLE[cmd]):
for j, bit in enumerate(description.split()):
ops.append(self.ca[i][j].eq(self.parse_bit(bit, is_mpc=cmd == "MPC")))
for cyc, description in enumerate(self.TRUTH_TABLE[cmd]):
for bit, bit_desc in enumerate(description.split()):
ops.append(self.ca[cyc][bit].eq(self.parse_bit(bit_desc)))
if cmd != "DESELECT":
ops.append(self.cs[0].eq(1))
return ops

View File

@ -14,18 +14,58 @@ 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())
# simple log_level, e.g. "INFO"
if "=" not in log_level:
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):
def __init__(self, pads, *, sys_clk_freq, disable_delay, settings, log_level):
"""LPDDR4 DRAM simulator
This module simulates an LPDDR4 DRAM chip to aid LPDDR4 PHY development/testing.
It does not aim to simulate the internals of an LPDDR4 chip, rather it's behavior
as seen by the PHY.
The simulator monitors CS/CA pads listening for LPDDR4 commands and updates the module
state depending on the command received. Any unexpected sequences are logged in simulation
as errors/warnings. On read/write commands the data simulation module is triggered
after CL/CWL and a data burst is handled, updating memory state.
The simulator requires the following clock domains:
sys8x: 8x the memory controller clock frequency, phase aligned.
sys8x_90: Phase shifted by 90 degrees vs sys8x.
sys8x_ddr: Phase aligned with sys8x, double the frequency.
sys8x_90_ddr: Phase aligned with sys8x_90, double the frequency.
Parameters
----------
pads : LPDDR4SimulationPads
DRAM pads
sys_clk_freq : float
System clock frequency
cl : int
LPDDR4 read latency (RL).
cwl : int
LPDDR4 write latency (WL).
disable_delay : bool
Disable checking of timings that rely on long CPU delays (mostly init sequence
timings). This is useful when running LiteX BIOS with CONFIG_DISABLE_DELAYS on.
log_level : str
SimLogger initial logging level (formatted for parsing with `log_level_getter`).
"""
def __init__(self, pads, *, sys_clk_freq, cl, cwl, disable_delay, log_level):
log_level = log_level_getter(log_level)
cd_cmd = "sys8x_90"
@ -34,12 +74,12 @@ class LPDDR4Sim(Module, AutoCSR):
cd_dq_rd = "sys8x_90_ddr"
cd_dqs_rd = "sys8x_ddr"
self.submodules.data = ClockDomainCrossing(
self.submodules.data_cdc = ClockDomainCrossing(
[("we", 1), ("masked", 1), ("bank", 3), ("row", 17), ("col", 10)],
cd_from=cd_cmd, cd_to=cd_dq_wr)
cmd = CommandsSim(pads,
data_cdc = self.data,
data_cdc = self.data_cdc,
clk_freq = 8*sys_clk_freq,
log_level = log_level("cmd"),
init_delays = not disable_delay,
@ -52,8 +92,8 @@ class LPDDR4Sim(Module, AutoCSR):
cd_dq_rd = cd_dq_rd,
cd_dqs_rd = cd_dqs_rd,
clk_freq = 2*8*sys_clk_freq,
cl = settings.phy.cl,
cwl = settings.phy.cwl,
cl = cl,
cwl = cwl,
log_level = log_level("data"),
)
self.submodules.data = ClockDomainsRenamer(cd_dq_wr)(data)
@ -61,10 +101,14 @@ class LPDDR4Sim(Module, AutoCSR):
# Commands -----------------------------------------------------------------------------------------
class PulseTiming(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
"""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()
@ -83,7 +127,16 @@ class PulseTiming(Module):
]
class CommandsSim(Module, AutoCSR): # clock domain: clk_p
class CommandsSim(Module, AutoCSR):
"""Command simulation
This module interprets LPDDR4 commands found on the CS/CA pads. It keeps track of currently
opened rows (per bank) and stores the values of Mode Registers. It also checks that the DRAM
initialization sequence is performed according to specification. On any read/write commands
signals indicating a burst are sent to the data simulator for handling.
Command simulator should work in the clock domain of `pads.clk_p` (SDR).
"""
def __init__(self, pads, data_cdc, *, clk_freq, log_level, init_delays=False):
self.submodules.log = log = SimLogger(log_level=log_level, clk_freq=clk_freq)
self.log.add_csrs()
@ -439,7 +492,15 @@ class CommandsSim(Module, AutoCSR): # clock domain: clk_p
# Data ---------------------------------------------------------------------------------------------
class DataSim(Module, AutoCSR): # clock domain: ddr
class DataSim(Module, AutoCSR):
"""Data simulator
This module is responsible for handling read/write bursts. It's operation has to be triggered
by the command simulator. Data is stored in an internal memory, no state is verified (row
open/closed, etc.), this must be checked by command simulation.
This module runs with DDR clocks (simulation clocks with double the frequency of `pads.clk_p`).
"""
def __init__(self, pads, cmds_sim, *, cd_dq_wr, cd_dq_rd, cd_dqs_wr, cd_dqs_rd, cl, cwl, clk_freq, log_level):
self.submodules.log = log = SimLogger(log_level=log_level, clk_freq=clk_freq)
self.log.add_csrs()

View File

@ -5,6 +5,13 @@ from litedram.phy.lpddr4.basephy import LPDDR4PHY, DoubleRateLPDDR4PHY, Latency
class LPDDR4SimulationPads(Module):
"""Pads for simulation purpose
To avoid simulate tristate behavior of DQ/DQS/DMI pins separate input and output
pins (_i/_o) are provided. Output pins are to be driven by the PHY and input pins
are to be driven by the DRAM simulator. This module sets the actual values on pins
`dq`, `dqs` and `dmi` based on output enable signals.
"""
def __init__(self, databits=16):
self.clk_p = Signal()
self.clk_n = Signal()
@ -36,6 +43,17 @@ class LPDDR4SimulationPads(Module):
class _LPDDR4SimPHYMixin:
"""Common serialization logic for simulation PHYs
This mixin provides `do_serialization` method for constructing the boilerplate
serialization/deserialization paths for a simulation PHY. This can serve as a
reference for implemeing PHYs for concrete FPGAs.
To make the (de-)serialization work in simulation two additional clock domains
are required: `sys8x_ddr` and `sys8x_90_ddr`. These correspond to `sys8x` and
`sys8x_90`, are phase aligned with them and at twice their frequency. These
clock domains are requried to implement DDR (de-)serialization at 8x sys clock.
"""
def _add_name(self, prefix, kwargs):
name = prefix + "_" + kwargs.pop("name", "")
kwargs["name"] = name.strip("_")
@ -102,6 +120,7 @@ class _LPDDR4SimPHYMixin:
class LPDDR4SimPHY(_LPDDR4SimPHYMixin, LPDDR4PHY):
"""LPDDR4 simulation PHY with direct 16:1 serializers"""
def __init__(self, aligned_reset_zero=False, **kwargs):
pads = LPDDR4SimulationPads()
self.submodules += pads
@ -119,6 +138,11 @@ class LPDDR4SimPHY(_LPDDR4SimPHYMixin, LPDDR4PHY):
class DoubleRateLPDDR4SimPHY(_LPDDR4SimPHYMixin, DoubleRateLPDDR4PHY):
"""LPDDR4 simulation PHY basing of DoubleRateLPDDR4PHY
`DoubleRateLPDDR4PHY` performs a single serialization step between `sys` and `sys2x`,
so this PHY wrapper has to do the serialization between `sys2x` and `sys8x` (SDR/DDR).
"""
def __init__(self, aligned_reset_zero=False, **kwargs):
pads = LPDDR4SimulationPads()
self.submodules += pads

View File

@ -103,9 +103,13 @@ def get_clocks(sys_clk_freq):
# SoC ----------------------------------------------------------------------------------------------
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):
"""Simulation of SoC with LPDDR4 DRAM
This is a SoC used to run Verilator-based simulations of LiteDRAM with a simulated LPDDR4 chip.
"""
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()
sys_clk_freq = clocks["sys"]["freq_hz"]
@ -161,7 +165,8 @@ class SimSoC(SoCCore):
# LPDDR4 Sim -------------------------------------------------------------------------------
self.submodules.lpddr4sim = LPDDR4Sim(
pads = self.ddrphy.pads,
settings = self.sdram.controller.settings,
cl = self.sdram.controller.settings.phy.cl,
cwl = self.sdram.controller.settings.phy.cwl,
sys_clk_freq = sys_clk_freq,
log_level = log_level,
disable_delay = disable_delay,

View File

@ -172,6 +172,14 @@ class Deserializer(Module):
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