diff --git a/litedram/common.py b/litedram/common.py index 79064dc..1503774 100644 --- a/litedram/common.py +++ b/litedram/common.py @@ -229,6 +229,7 @@ class PhySettings(Settings): write_dq_dqs_training: bool = False, write_latency_calibration: bool = False, read_leveling: bool = False, + is_clam_shell: bool = False, ): if strobes is None: strobes = databits // 8 diff --git a/litedram/core/__init__.py b/litedram/core/__init__.py index 715eefc..872eea0 100644 --- a/litedram/core/__init__.py +++ b/litedram/core/__init__.py @@ -17,11 +17,12 @@ from litedram.core.crossbar import LiteDRAMCrossbar class LiteDRAMCore(Module, AutoCSR): def __init__(self, phy, geom_settings, timing_settings, clk_freq, **kwargs): self.submodules.dfii = DFIInjector( - addressbits = max(geom_settings.addressbits, getattr(phy, "addressbits", 0)), - bankbits = max(geom_settings.bankbits, getattr(phy, "bankbits", 0)), - nranks = phy.settings.nranks, - databits = phy.settings.dfi_databits, - nphases = phy.settings.nphases) + addressbits = max(geom_settings.addressbits, getattr(phy, "addressbits", 0)), + bankbits = max(geom_settings.bankbits, getattr(phy, "bankbits", 0)), + nranks = phy.settings.nranks, + databits = phy.settings.dfi_databits, + nphases = phy.settings.nphases, + is_clam_shell = phy.settings.is_clam_shell) self.comb += self.dfii.master.connect(phy.dfi) self.submodules.controller = controller = LiteDRAMController( diff --git a/litedram/dfii.py b/litedram/dfii.py index b8a167f..7bb2d0a 100644 --- a/litedram/dfii.py +++ b/litedram/dfii.py @@ -21,6 +21,9 @@ class PhaseInjector(Module, AutoCSR): CSRField("ras", size=1, description="DFI row address strobe bus"), CSRField("wren", size=1, description="DFI write data enable bus"), CSRField("rden", size=1, description="DFI read data enable bus"), + # Separate cs for clam shell topology + CSRField("cs_top", size=1, description="DFI chip select bus for top half only"), + CSRField("cs_bottom", size=1, description="DFI chip select bus for bottom half only"), ], description="Control DFI signals on a single phase") self._command_issue = CSR() # description="The command gets commited on a write to this register" @@ -33,7 +36,15 @@ class PhaseInjector(Module, AutoCSR): self.comb += [ If(self._command_issue.re, - phase.cs_n.eq(Replicate(~self._command.fields.cs, len(phase.cs_n))), + If(self._command.fields.cs_top, + phase.cs_n.eq(2), # cs_n=0b10 + ).Else( + If(self._command.fields.cs_bottom, + phase.cs_n.eq(1), # cs_n=0b01 + ).Else( + phase.cs_n.eq(Replicate(~self._command.fields.cs, len(phase.cs_n))), + ), + ), phase.we_n.eq(~self._command.fields.we), phase.cas_n.eq(~self._command.fields.cas), phase.ras_n.eq(~self._command.fields.ras) @@ -55,10 +66,10 @@ class PhaseInjector(Module, AutoCSR): # DFIInjector -------------------------------------------------------------------------------------- class DFIInjector(Module, AutoCSR): - def __init__(self, addressbits, bankbits, nranks, databits, nphases=1): + def __init__(self, addressbits, bankbits, nranks, databits, nphases=1, is_clam_shell=False): self.slave = dfi.Interface(addressbits, bankbits, nranks, databits, nphases) - self.master = dfi.Interface(addressbits, bankbits, nranks, databits, nphases) - csr_dfi = dfi.Interface(addressbits, bankbits, nranks, databits, nphases) + self.master = dfi.Interface(addressbits, bankbits, nranks*2 if is_clam_shell else nranks, databits, nphases) + csr_dfi = dfi.Interface(addressbits, bankbits, nranks*2 if is_clam_shell else nranks, databits, nphases) self.ext_dfi = dfi.Interface(addressbits, bankbits, nranks, databits, nphases) self.ext_dfi_sel = Signal() @@ -87,7 +98,11 @@ class DFIInjector(Module, AutoCSR): self.ext_dfi.connect(self.master) # Through LiteDRAM controller. ).Else( - self.slave.connect(self.master) + self.slave.connect(self.master), + # Broadcast cs_n for clam shell topology + If(is_clam_shell, + [self.master.phases[i].cs_n.eq(Replicate(self.slave.phases[i].cs_n, 2)) for i in range(nphases)], + ) ) # Software Control (through CSRs). # -------------------------------- diff --git a/litedram/init.py b/litedram/init.py index 5be9060..94b68c4 100644 --- a/litedram/init.py +++ b/litedram/init.py @@ -23,6 +23,14 @@ cmds = { "CKE": "DFII_CONTROL_CKE|DFII_CONTROL_ODT|DFII_CONTROL_RESET_N" } +# Swap two bits in num +# https://www.techiedelight.com/swap-two-bits-given-position-integer/ +def swap_bit(num, a, b): + if ((num >> a) & 1) != ((num >> b) & 1): + num = num ^ (1 << a) + num = num ^ (1 << b) + return num + def reg(fields): # takes a list of tuples: [(bit_offset, bit_width, value), ...] regval = 0 @@ -894,6 +902,9 @@ def get_sdram_phy_c_header(phy_settings, timing_settings, geom_settings): r.define("DFII_COMMAND_RAS", "0x08") r.define("DFII_COMMAND_WRDATA", "0x10") r.define("DFII_COMMAND_RDDATA", "0x20") + if phy_settings.is_clam_shell: + r.define("DFII_COMMAND_CS_TOP", "0x40") + r.define("DFII_COMMAND_CS_BOTTOM", "0x80") r.newline() phytype = phy_settings.phytype.upper() @@ -941,6 +952,9 @@ def get_sdram_phy_c_header(phy_settings, timing_settings, geom_settings): if phy_settings.is_rdimm: assert phy_settings.memtype == "DDR4" r.define("SDRAM_PHY_DDR4_RDIMM") + if phy_settings.is_clam_shell: + assert phy_settings.memtype == "DDR4" + r.define("SDRAM_PHY_CLAM_SHELL") # litedram doesn't support multiple ranks supported_memory = 2 ** (geom_settings.bankbits + @@ -1010,6 +1024,33 @@ def get_sdram_phy_c_header(phy_settings, timing_settings, geom_settings): invert_masks.append((0b10101111111000, 0b1111)) for a_inv, ba_inv in invert_masks: + # handle clam shell topology + if cmd == cmds["MODE_REGISTER"] and phy_settings.is_clam_shell: + b += f"/* {comment} for top */" + b += f"sdram_dfii_pi0_address_write({a ^ a_inv:#x});" + b += f"sdram_dfii_pi0_baddress_write({ba ^ ba_inv:d});" + b += f"command_p0({cmd}|DFII_COMMAND_CS_TOP);" + if delay: + b += f"cdelay({delay});\n" + b.newline() + + # swap addr and pass to bottom + b += f"/* {comment} for bottom */" + addr = a ^ a_inv + addr = swap_bit(addr, 3, 4) + addr = swap_bit(addr, 5, 6) + addr = swap_bit(addr, 7, 8) + addr = swap_bit(addr, 11, 13) + b += f"sdram_dfii_pi0_address_write({addr:#x});" + baddr = ba ^ ba_inv + baddr = swap_bit(baddr, 0, 1) + b += f"sdram_dfii_pi0_baddress_write({baddr:d});" + b += f"command_p0({cmd}|DFII_COMMAND_CS_BOTTOM);" + if delay: + b += f"cdelay({delay});\n" + b.newline() + continue + b += f"/* {comment} */" b += f"sdram_dfii_pi0_address_write({a ^ a_inv:#x});" b += f"sdram_dfii_pi0_baddress_write({ba ^ ba_inv:d});" diff --git a/litedram/phy/usddrphy.py b/litedram/phy/usddrphy.py index 68f0f2a..9a7ad43 100644 --- a/litedram/phy/usddrphy.py +++ b/litedram/phy/usddrphy.py @@ -32,7 +32,8 @@ class USDDRPHY(Module, AutoCSR): cwl = None, cmd_latency = 0, cmd_delay = None, - is_rdimm = False): + is_rdimm = False, + is_clam_shell = False): phytype = self.__class__.__name__ device = {"USDDRPHY": "ULTRASCALE", "USPDDRPHY": "ULTRASCALE_PLUS"}[phytype] pads = PHYPadsCombiner(pads) @@ -97,7 +98,7 @@ class USDDRPHY(Module, AutoCSR): memtype = memtype, databits = databits, dfi_databits = 2*databits, - nranks = nranks, + nranks = nranks//2 if is_clam_shell else nranks, nphases = nphases, rdphase = self._rdphase.storage, wrphase = self._wrphase.storage, @@ -112,6 +113,7 @@ class USDDRPHY(Module, AutoCSR): read_leveling = True, delays = 512, bitslips = 8, + is_clam_shell = is_clam_shell, ) if is_rdimm: