phy/dfi: add automatic PHY wrapper generation for DFIRateConverter
This commit is contained in:
parent
89af25a697
commit
1d880c4db9
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue