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 import *
|
||||||
from migen.genlib.record import *
|
from migen.genlib.record import *
|
||||||
|
from migen.genlib.cdc import PulseSynchronizer
|
||||||
|
|
||||||
|
from litedram.common import PhySettings
|
||||||
from litedram.phy.utils import Serializer, Deserializer
|
from litedram.phy.utils import Serializer, Deserializer
|
||||||
|
|
||||||
|
|
||||||
|
@ -227,3 +229,119 @@ class DFIRateConverter(Module):
|
||||||
out_width = len(Cat(sigs_m))
|
out_width = len(Cat(sigs_m))
|
||||||
sig_m_window = sig_m[read_delay*out_width:(read_delay + 1)*out_width]
|
sig_m_window = sig_m[read_delay*out_width:(read_delay + 1)*out_width]
|
||||||
self.comb += Cat(sigs_m).eq(sig_m_window)
|
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