phy/dfi: add automatic PHY wrapper generation for DFIRateConverter

This commit is contained in:
Jędrzej Boczar 2021-07-21 16:32:44 +02:00
parent 89af25a697
commit 1d880c4db9
1 changed files with 118 additions and 0 deletions

View File

@ -7,7 +7,9 @@
from migen import *
from migen.genlib.record import *
from migen.genlib.cdc import PulseSynchronizer
from litedram.common import PhySettings
from litedram.phy.utils import Serializer, Deserializer
@ -227,3 +229,119 @@ class DFIRateConverter(Module):
out_width = len(Cat(sigs_m))
sig_m_window = sig_m[read_delay*out_width:(read_delay + 1)*out_width]
self.comb += Cat(sigs_m).eq(sig_m_window)
@classmethod
def phy_wrapper(cls, phy_cls, ratio, phy_attrs=None, clock_mapping=None, **converter_kwargs):
"""Generate a wrapper class for given PHY
Given PHY `phy_cls` a new Module is generated, which will instantiate `phy_cls` as a
submodule (self.submodules.phy), with DFIRateConverter used to convert its DFI. It will
recalculate `phy_cls` PhySettings to have correct latency values.
Parameters
----------
phy_cls : type
PHY class. It must support a `csr_cdc` argument (function: csr_cdc(Signal) -> Signal)
that it will use to wrap all CSR.re signals to avoid clock domain crossing problems.
ratio : int
Frequency ratio between the new DFI and the DFI of the wrapped PHY.
phy_attrs : list[str]
Names of PHY attributes to be copied to the wrapper (self.attr = self.phy.attr).
clock_mapping : dict[str, str]
Clock remapping for the PHY. Defaults to {"sys": f"sys{ratio}x"}.
converter_kwargs : Any
Keyword arguments forwarded to the DFIRateConverter instance.
"""
if ratio == 1:
return phy_cls
# Generate the wrapper class dynamically
name = f"{phy_cls.__name__}Wrapper"
bases = (Module, object, )
internal_cd = f"sys{ratio}x"
if clock_mapping is None:
clock_mapping = {"sys": internal_cd}
# Constructor
def __init__(self, *args, **kwargs):
# Add the PHY in new clock domain,
self.internal_cd = internal_cd
phy = phy_cls(*args, csr_cdc=self.csr_cdc, **kwargs)
# Remap clock domains in the PHY
# Workaround: do this in two steps to avoid errors due to the fact that renaming is done
# sequentially. Consider mapping {"sys": "sys2x", "sys2x": "sys4x"}, it would lead to:
# sys2x = sys
# sys4x = sys2x
# resulting in all sync operations in sys4x domain.
mapping = [tuple(i) for i in clock_mapping.items()]
map_tmp = {clk_from: f"tmp{i}" for i, (clk_from, clk_to) in enumerate(mapping)}
map_final = {f"tmp{i}": clk_to for i, (clk_from, clk_to) in enumerate(mapping)}
self.submodules.phy = ClockDomainsRenamer(map_final)(ClockDomainsRenamer(map_tmp)(phy))
# Copy some attributes of the PHY
for attr in phy_attrs or []:
setattr(self, attr, getattr(self.phy, attr))
# Insert DFI rate converter to
self.submodules.dfi_converter = DFIRateConverter(phy.dfi,
clkdiv = "sys",
clk = self.internal_cd,
ratio = ratio,
write_delay = phy.settings.write_latency % ratio,
read_delay = phy.settings.read_latency % ratio,
**converter_kwargs,
)
self.dfi = self.dfi_converter.dfi
# Generate new PhySettings
converter_latency = self.dfi_converter.ser_latency + self.dfi_converter.des_latency
self.settings = PhySettings(
phytype = phy.settings.phytype,
memtype = phy.settings.memtype,
databits = phy.settings.databits,
dfi_databits = len(self.dfi.p0.wrdata),
nranks = phy.settings.nranks,
nphases = len(self.dfi.phases),
rdphase = phy.settings.rdphase,
wrphase = phy.settings.wrphase,
cl = phy.settings.cl,
cwl = phy.settings.cwl,
read_latency = phy.settings.read_latency//ratio + converter_latency,
write_latency = phy.settings.write_latency//ratio,
cmd_latency = phy.settings.cmd_latency,
cmd_delay = phy.settings.cmd_delay,
write_leveling = phy.settings.write_leveling,
write_dq_dqs_training = phy.settings.write_dq_dqs_training,
write_latency_calibration = phy.settings.write_latency_calibration,
read_leveling = phy.settings.read_leveling,
delays = phy.settings.delays,
bitslips = phy.settings.bitslips,
)
# Copy any non-default PhySettings (e.g. electrical settings)
for attr, value in vars(self.phy.settings).items():
if not hasattr(self.settings, attr):
setattr(self.settings, attr, value)
def csr_cdc(self, i):
o = Signal()
psync = PulseSynchronizer("sys", self.internal_cd)
self.submodules += psync
self.comb += [
psync.i.eq(i),
o.eq(psync.o),
]
return o
def get_csrs(self):
return self.phy.get_csrs()
# This creates a new class with given name, base classes and attributes/methods
namespace = dict(
__init__ = __init__,
csr_cdc = csr_cdc,
get_csrs = get_csrs,
)
return type(name, bases, namespace)