work through the CSns to give the appearance of having a larger hyperram
authorLuke Kenneth Casson Leighton <lkcl@lkcl.net>
Mon, 28 Mar 2022 14:12:25 +0000 (15:12 +0100)
committerLuke Kenneth Casson Leighton <lkcl@lkcl.net>
Mon, 28 Mar 2022 14:12:25 +0000 (15:12 +0100)
memory when in fact, when one IC runs out of address space the next
one is activated.  this is not particularly fast but it is simple
and functional

lambdasoc/periph/hyperram.py

index c992a29c765efecc1bb62e68a58519f72e0da767..a5b536e465a88265422cd6c656f12d3c70b90fba 100644 (file)
@@ -16,19 +16,31 @@ Usage example when wiring up an external pmod.
 use platform.add_extension to first define the pins:
 
     from nmigen.resources.memory import HyperRAMResources
-    hyperram_ios = HyperRAMResources(cs="B1",
+    hyperram_ios = HyperRAMResources(cs="B1", # or cs="C0 C1 C2 C3" for Quad
                                      dq="D0 D1 D2 D3 D4 D7 D6 D7",
                                      rwds="B2", rst_n="B3", ck_p="B4",
                                      attrs=Attrs(IOSTANDARD="LVCMOS33"))
     self.platform.add_resources(hyperram_ios)
     io = self.platform.request("hyperram")
 
+and then declare the instance using those pins:
+
+    hyperram = HyperRAM(io=io, phy_kls=HyperRAMPHY,
+                    latency=7) # Winbond W956D8MBYA
+                               # latency=6 for Cypress S27KL0641DABHI020
+
 this trick will work with the 1-IC HyperRAM PMOD by Piotr Esden, sold
 by 1bitsquared.  however for the *four* IC HyperRAM PMOD, *four* cs_n pins
-are needed (and is not currently supported).
-on the TODO list for this module: interleave multiple HyperRAM
-cs_n's to give striped (like RAID) memory accesses behind one single
-Wishbone interface.
+are needed. These are then used to select, in turn, each IC, sequentially:
+    * Access to 0x00000-0xfffff will activate CS0n,
+    * Access to 0x100000-0x1fffff will activate CS1n,
+    * Access to 0x200000-0x2fffff will activate CS2n,
+    * Access to 0x300000-0x3fffff will activate CS3n
+
+TODO: interleave multiple HyperRAM cs_n's to give striped (like RAID)
+memory accesses behind one single Wishbone interface.
+TODO: investigate whether HyperBUS can do CSn-striping in hardware
+(it should do, but this will require configuration registers to be written)
 """
 
 
@@ -150,22 +162,27 @@ class HyperRAM(Peripheral, Elaboratable):
                           addr_width=23, # 8 GBytes, per IC
                           bus=None, features=frozenset()):
         super().__init__()
+        self.n_cs = n_cs = len(io.cs_n)
+        self.cs_bits = cs_bits = n_cs.bit_length()-1
         self.io = io
         self.phy = phy_kls(io)
         self.latency = latency
-        self.bus = wishbone.Interface(addr_width=21,
+        # per IC, times n_cs
+        addr_width += cs_bits
+        self.bus = wishbone.Interface(addr_width=addr_width-2,
                                       data_width=32, granularity=8,
                                       features=features)
-        mmap = MemoryMap(addr_width=23, data_width=8)
-        mmap.add_resource(object(), name="hyperram", size=2**23)
+        self.size = 2**addr_width
+        mmap = MemoryMap(addr_width=addr_width, data_width=8)
+        mmap.add_resource(object(), name="hyperram", size=self.size)
         self.bus.memory_map = mmap
-        self.size = 2**23
         # # #
 
     def elaborate(self, platform):
         m = Module()
         m.submodules.phy = self.phy
         bus = self.bus
+        cs_bits = self.cs_bits
         comb, sync = m.d.comb, m.d.sync
 
         ck       = self.phy.ck
@@ -185,7 +202,21 @@ class HyperRAM(Peripheral, Elaboratable):
         rwds_o = self.phy.rwds_o
         rwds_oe = self.phy.rwds_oe
 
+        # chip&address selection: use the MSBs of the address for chip-select
+        # (bus_adr_hi) by doing "1<<bus_adr_hi". this has to be captured
+        # (cs_latch) and asserted as part of bus_latch.  therefore *before*
+        # that happens (SEND-COMMAND-ADDRESS and WAIT-STATE) cs has to be
+        # set to the "unlatched" version.
+        bus_adr_lo = self.bus.adr[:-cs_bits]
+        if cs_bits != 0:
+            bus_adr_hi = self.bus.adr[-cs_bits:]
+        else:
+            bus_adr_hi = 0
+
         # Clock Generation (sys_clk/4) -----------------------------------
+        # this is a cheap-and-cheerful way to create phase-offsetted DDR:
+        # simply divide the main clock into 4 phases.  it does mean that
+        # the HyperRAM IC is being run at 1/4 rate. sigh.
         sync += clk_phase.eq(clk_phase + 1)
         with m.Switch(clk_phase):
             with m.Case(1):
@@ -214,10 +245,10 @@ class HyperRAM(Peripheral, Elaboratable):
         ashift = {8:1, 16:0}[dw]
         la = 3-ashift
         comb += [
-            ca[47].eq(~self.bus.we),          # R/W#
-            ca[45].eq(1),                     # Burst Type (Linear)
-            ca[16:45].eq(self.bus.adr[la:]),  # Row & Upper Column Address
-            ca[ashift:3].eq(bus.adr),         # Lower Column Address
+            ca[47].eq(~self.bus.we),     # R/W#
+            ca[45].eq(1),                # Burst Type (Linear)
+            ca[16:45].eq(bus_adr_lo[la:]),  # Row & Upper Column Address
+            ca[ashift:3].eq(bus_adr_lo),    # Lower Column Address
         ]
 
         # Latency count starts from the middle of the command (thus the -4).
@@ -236,12 +267,10 @@ class HyperRAM(Peripheral, Elaboratable):
                 sync += sr.eq(Cat(Const(0, 16), bus.dat_w))
             sync += [ bus_we.eq(bus.we),
                       bus_sel.eq(bus.sel),
-                      bus_adr.eq(bus.adr),
+                      bus_adr.eq(bus_adr_lo),
                       cs_latch.eq(cs)
                     ]
 
-
-
         # Sequencer -------------------------------------------------------
         cycles = Signal(8)
         first  = Signal()
@@ -268,7 +297,7 @@ class HyperRAM(Peripheral, Elaboratable):
 
             with m.State("SEND-COMMAND-ADDRESS"):
                 sync += cycles.eq(cycles+1)
-                comb += cs.eq(1)        # Set CSn.
+                comb += cs.eq(1<<bus_adr_hi) # Set CSn direct (not via latch)
                 comb += ck_active.eq(1) # Activate clock
                 comb += ca_active.eq(1) # Send Command on DQ.
                 comb += dq_oe.eq(1),    # Wait for 6*2 cycles...
@@ -278,7 +307,7 @@ class HyperRAM(Peripheral, Elaboratable):
 
             with m.State("WAIT-LATENCY"):
                 sync += cycles.eq(cycles+1)
-                comb += cs.eq(1) # Set CSn directly (not via latch)
+                comb += cs.eq(1<<bus_adr_hi) # Set CSn directly (not via latch)
                 comb += ck_active.eq(1) # Activate clock
                 # Wait for Latency cycles...
                 with m.If(cycles == (latency_cycles - 1)):
@@ -288,11 +317,13 @@ class HyperRAM(Peripheral, Elaboratable):
                     m.next = "READ-WRITE-DATA0"
                     sync += cycles.eq(0)
 
+            # for-loop creates multple READ-WRITE-DATA states, to send/get
+            # dw bits at a time.
             states = {8:4, 16:2}[dw]
             for n in range(states):
                 with m.State("READ-WRITE-DATA%d" % n):
                     sync += cycles.eq(cycles+1)
-                    comb += cs.eq(cs_latch), # Set CSn from Latch
+                    comb += cs.eq(cs_latch), # *now* set CSn from Latch
                     comb += ck_active.eq(1) # Activate clock
                     # Send Data on DQ/RWDS (for write).
                     with m.If(bus_we):
@@ -313,7 +344,8 @@ class HyperRAM(Peripheral, Elaboratable):
                             # Continue burst when consecutive access ready.
                             with m.If(bus.stb & bus.cyc & 
                                       (bus.we == bus_we) & 
-                                      (bus.adr == (bus_adr + 1))):
+                                      (bus_adr_lo == (bus_adr + 1)) &
+                                      ((1<<bus_adr_hi) == cs_latch)):
                                 comb += bus_latch.eq(1), # Latch Bus (and cs)
                                 # Early Write Ack (to allow bursting).
                                 comb += bus.ack.eq(bus.we)