From 1d880c4db9bb27e9efcf88fe2cfc247bd18c6072 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Boczar?= Date: Wed, 21 Jul 2021 16:32:44 +0200 Subject: [PATCH] phy/dfi: add automatic PHY wrapper generation for DFIRateConverter --- litedram/phy/dfi.py | 118 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) diff --git a/litedram/phy/dfi.py b/litedram/phy/dfi.py index 548e9d4..1bf67d6 100644 --- a/litedram/phy/dfi.py +++ b/litedram/phy/dfi.py @@ -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)