lpddr4: improve documentation
This commit is contained in:
parent
1b65e858b3
commit
052dc19246
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue