1 # Basic Implementation of HyperRAM
3 # Copyright (c) 2019 Antti Lukats <antti.lukats@gmail.com>
4 # Copyright (c) 2019 Florent Kermarrec <florent@enjoy-digital.fr>
5 # Copyright (c) 2021 gatecat <gatecat@ds0.me> [nmigen-soc port]
6 # Copyright (C) 2022 Luke Kenneth Casson Leighton <lkcl@lkcl.net>
8 # Code from Lukats, Kermarrec and gatecat is Licensed BSD-2-Clause
10 # Modifications for the Libre-SOC Project funded by NLnet and NGI POINTER
11 # under EU Grants 871528 and 957073, and Licensed under the LGPLv3+ License
14 Usage example when wiring up an external pmod.
15 (thanks to daveshah for this tip)
16 use platform.add_extension to first define the pins:
18 from nmigen.resources.memory import HyperRAMResources
19 hyperram_ios = HyperRAMResources(cs="B1", # or cs="C0 C1 C2 C3" for Quad
20 dq="D0 D1 D2 D3 D4 D7 D6 D7",
21 rwds="B2", rst_n="B3", ck_p="B4",
22 attrs=Attrs(IOSTANDARD="LVCMOS33"))
23 self.platform.add_resources(hyperram_ios)
24 io = self.platform.request("hyperram")
26 and then declare the instance using those pins:
28 hyperram = HyperRAM(io=io, phy_kls=HyperRAMPHY,
29 latency=7) # Winbond W956D8MBYA
30 # latency=6 for Cypress S27KL0641DABHI020
32 this trick will work with the 1-IC HyperRAM PMOD by Piotr Esden, sold
33 by 1bitsquared. however for the *four* IC HyperRAM PMOD, *four* cs_n pins
34 are needed. These are then used to select, in turn, each IC, sequentially:
35 * Access to 0x00000-0xfffff will activate CS0n,
36 * Access to 0x100000-0x1fffff will activate CS1n,
37 * Access to 0x200000-0x2fffff will activate CS2n,
38 * Access to 0x300000-0x3fffff will activate CS3n
40 TODO: interleave multiple HyperRAM cs_n's to give striped (like RAID)
41 memory accesses behind one single Wishbone interface.
42 TODO: investigate whether HyperBUS can do CSn-striping in hardware
43 (it should do, but this will require configuration registers to be written)
47 from nmigen
import (Elaboratable
, Module
, Signal
, Record
, Cat
, Const
)
48 from nmigen
.cli
import rtlil
50 from nmigen_soc
import wishbone
51 from nmigen_soc
.memory
import MemoryMap
52 from lambdasoc
.periph
import Peripheral
55 # HyperRAM ASIC PHY -----------------------------------------------------------
57 class HyperRAMASICPhy(Elaboratable
):
58 def __init__(self
, io
):
60 self
.ck
= ck
= Signal()
61 self
.cs
= cs
= Signal(len(self
.io
.cs_n
))
62 self
.rst_n
= rst_n
= Signal()
64 self
.dq_o
= dq_o
= Signal(8)
65 self
.dq_i
= dq_i
= Signal(8)
66 self
.dq_oe
= dq_oe
= Signal()
68 self
.rwds_o
= rwds_o
= Signal
.like(self
.io
["rwds_o"])
69 self
.rwds_oe
= rwds_oe
= Signal()
71 def elaborate(self
, platform
):
74 ck
, cs
, rst_n
= self
.ck
, self
.cs
, self
.rst_n
75 dq_o
, dq_i
, dq_oe
= self
.dq_o
, self
.dq_i
, self
.dq_oe
76 rwds_o
, rwds_oe
= self
.rwds_o
, self
.rwds_oe
79 self
.io
["rwds_o"].eq(rwds_o
),
80 self
.io
["cs_n"].eq(~cs
),
81 self
.io
["csn_oe"].eq(0),
82 self
.io
["ck_o"].eq(ck
),
83 self
.io
["ck_oe"].eq(0),
84 self
.io
["rwds_oe"].eq(~rwds_oe
),
85 self
.io
["rst_n"].eq(rst_n
),
90 self
.io
[f
"d{i}_o"].eq(dq_o
[i
]),
91 self
.io
[f
"d{i}_oe"].eq(~dq_oe
),
92 dq_i
[i
].eq(self
.io
[f
"d{i}_i"])
98 return list(self
.io
.fields
.values())
101 # HyperRAM pads class (PHY) which can be used for testing and simulation
102 # (without needing a platform instance). use as:
103 # dut = HyperRAM(io=HyperRamPads(), phy_kls=TestHyperRAMPHY)
106 def __init__(self
, dw
=8, n_cs
=1):
107 self
.rst_n
= Signal()
109 self
.cs_n
= Signal(n_cs
)
110 self
.dq
= Record([("oe", 1), ("o", dw
), ("i", dw
)])
111 self
.rwds
= Record([("oe", 1), ("o", dw
//8), ("i", dw
//8)])
112 self
.dq
.o
.name
= "dq_o"
113 self
.dq
.i
.name
= "dq_i"
114 self
.dq
.oe
.name
= "dq_oe"
115 self
.rwds
.o
.name
= "rwds_o"
116 self
.rwds
.i
.name
= "rwds_i"
117 self
.rwds
.oe
.name
= "rwds_oe"
120 return [self
.ck
, self
.cs_n
, self
.dq
.o
, self
.dq
.i
, self
.dq
.oe
,
121 self
.rwds
.o
, self
.rwds
.oe
, self
.rst_n
]
124 class HyperRAMPHY(Elaboratable
):
125 def __init__(self
, pads
):
128 self
.cs
= Signal(len(self
.pads
.cs_n
))
129 self
.rst_n
= pads
.rst_n
130 self
.dq_o
= pads
.dq
.o
131 self
.dq_i
= pads
.dq
.i
132 self
.dq_oe
= pads
.dq
.oe
133 self
.rwds_o
= pads
.rwds
.o
134 self
.rwds_oe
= Signal()
136 def elaborate(self
, platform
):
138 m
.d
.comb
+= self
.pads
.cs_n
.eq(self
.cs
)
139 m
.d
.comb
+= self
.pads
.rwds
.oe
.eq(self
.rwds_oe
)
143 return self
.pads
.ports()
146 # HyperRAM --------------------------------------------------------------------
148 class HyperRAM(Peripheral
, Elaboratable
):
151 Provides a very simple/minimal HyperRAM core that should work with all
153 - FPGA vendor agnostic.
154 - no setup/chip configuration (use default latency).
156 This core favors portability and ease of use over performance.
157 Tested: Winbond W956D8MBYA latency=7
158 Cypress S27KL0641DABHI020 requires latency=6
160 def __init__(self
, *, io
, phy_kls
,
163 addr_width
=23, # 8 GBytes, per IC
164 bus
=None, features
=frozenset()):
165 super().__init
__(name
=name
)
166 self
.n_cs
= n_cs
= len(io
.cs_n
)
167 self
.cs_bits
= cs_bits
= n_cs
.bit_length()-1
169 self
.phy
= phy_kls(io
)
170 self
.latency
= latency
172 addr_width
+= cs_bits
173 self
.bus
= wishbone
.Interface(addr_width
=addr_width
-2,
174 data_width
=32, granularity
=8,
176 self
.size
= 2**addr_width
177 mmap
= MemoryMap(addr_width
=addr_width
, data_width
=8)
180 mmap
.add_resource(object(), name
=name
, size
=self
.size
)
181 self
.bus
.memory_map
= mmap
184 def elaborate(self
, platform
):
186 m
.submodules
.phy
= self
.phy
188 cs_bits
= self
.cs_bits
189 comb
, sync
= m
.d
.comb
, m
.d
.sync
192 clk_phase
= Signal(2)
202 dq_oe
= self
.phy
.dq_oe
203 dw
= len(dq_o
) # data width
205 rwds_o
= self
.phy
.rwds_o
206 rwds_oe
= self
.phy
.rwds_oe
208 # chip&address selection: use the MSBs of the address for chip-select
209 # (bus_adr_hi) by doing "1<<bus_adr_hi". this has to be captured
210 # (cs_latch) and asserted as part of bus_latch. therefore *before*
211 # that happens (SEND-COMMAND-ADDRESS and WAIT-STATE) cs has to be
212 # set to the "unlatched" version.
213 bus_adr_lo
= self
.bus
.adr
[:-cs_bits
]
215 bus_adr_hi
= self
.bus
.adr
[-cs_bits
:]
219 # Clock Generation (sys_clk/4) -----------------------------------
220 # this is a cheap-and-cheerful way to create phase-offsetted DDR:
221 # simply divide the main clock into 4 phases. it does mean that
222 # the HyperRAM IC is being run at 1/4 rate. sigh.
223 sync
+= clk_phase
.eq(clk_phase
+ 1)
224 with m
.Switch(clk_phase
):
226 sync
+= ck
.eq(ck_active
)
230 # Data Shift Register (for write and read) ------------------------
232 sync
+= dqi
.eq(dq_i
) # Sample on 90° and 270°
233 with m
.If(ca_active
):
234 comb
+= sr_new
.eq(Cat(dqi
[:8], sr
[:-dw
]))
236 comb
+= sr_new
.eq(Cat(dqi
, sr
[:-8]))
237 with m
.If(~clk_phase
[0]):
238 sync
+= sr
.eq(sr_new
) # Shift on 0° and 180°
240 # Data shift-out register ----------------------------------------
241 comb
+= self
.bus
.dat_r
.eq(sr_new
), # To Wisbone
243 comb
+= dq_o
.eq(sr
[-dw
:]), # To HyperRAM
244 with m
.If(dq_oe
& ca_active
):
245 comb
+= dq_o
.eq(sr
[-8:]), # To HyperRAM, Only 8-bit during CMD/Addr.
247 # Command generation ----------------------------------------------
248 ashift
= {8:1, 16:0}[dw
]
251 ca
[47].eq(~self
.bus
.we
), # R/W#
252 ca
[45].eq(1), # Burst Type (Linear)
253 ca
[16:45].eq(bus_adr_lo
[la
:]), # Row & Upper Column Address
254 ca
[ashift
:3].eq(bus_adr_lo
), # Lower Column Address
257 # Latency count starts from the middle of the command (thus the -4).
258 # In fixed latency mode (default), latency is 2 x Latency count.
259 # We have 4 x sys_clk per RAM clock:
260 latency_cycles
= (self
.latency
* 2 * 4) - 4
262 # Bus Latch ----------------------------------------------------
267 cs_latch
= Signal
.like(cs
)
268 with m
.If(bus_latch
):
270 sync
+= sr
.eq(Cat(Const(0, 16), bus
.dat_w
))
271 sync
+= [ bus_we
.eq(bus
.we
),
273 bus_adr
.eq(bus_adr_lo
),
277 # Sequencer -------------------------------------------------------
280 nfirst
= Signal() # not-first
283 comb
+= nfirst
.eq(~first
) # convenience
285 # when not idle run a cycles counter
286 with m
.If(count_inc
):
287 sync
+= dbg_cyc
.eq(dbg_cyc
+1)
289 sync
+= dbg_cyc
.eq(0)
293 comb
+= count_inc
.eq(~fsm
.ongoing("IDLE"))
294 with m
.State("IDLE"):
296 with m
.If(bus
.cyc
& bus
.stb
& (clk_phase
== 0)):
298 m
.next
= "SEND-COMMAND-ADDRESS"
301 with m
.State("SEND-COMMAND-ADDRESS"):
302 sync
+= cycles
.eq(cycles
+1)
303 comb
+= cs
.eq(1<<bus_adr_hi
) # Set CSn direct (not via latch)
304 comb
+= ck_active
.eq(1) # Activate clock
305 comb
+= ca_active
.eq(1) # Send Command on DQ.
306 comb
+= dq_oe
.eq(1), # Wait for 6*2 cycles...
307 with m
.If(cycles
== (6*2 - 1)):
308 m
.next
= "WAIT-LATENCY"
311 with m
.State("WAIT-LATENCY"):
312 sync
+= cycles
.eq(cycles
+1)
313 comb
+= cs
.eq(1<<bus_adr_hi
) # Set CSn directly (not via latch)
314 comb
+= ck_active
.eq(1) # Activate clock
315 # Wait for Latency cycles...
316 with m
.If(cycles
== (latency_cycles
- 1)):
317 comb
+= bus_latch
.eq(1) # Latch Bus (and cs)
318 # Early Write Ack (to allow bursting).
319 comb
+= bus
.ack
.eq(bus
.we
)
320 m
.next
= "READ-WRITE-DATA0"
323 # for-loop creates multple READ-WRITE-DATA states, to send/get
325 states
= {8:4, 16:2}[dw
]
326 for n
in range(states
):
327 with m
.State("READ-WRITE-DATA%d" % n
):
328 sync
+= cycles
.eq(cycles
+1)
329 comb
+= cs
.eq(cs_latch
), # *now* set CSn from Latch
330 comb
+= ck_active
.eq(1) # Activate clock
331 # Send Data on DQ/RWDS (for write).
334 comb
+= rwds_oe
.eq(1)
335 for i
in range(dw
//8):
336 seli
= ~bus_sel
[4-1-n
*dw
//8-i
]
337 comb
+= rwds_o
[dw
//8-1-i
].eq(seli
)
338 # Wait for 2 cycles (since HyperRAM's Clk = sys_clk/4).
339 with m
.If(cycles
== (2 - 1)):
340 # Set next default state (with rollover for bursts).
341 m
.next
= "READ-WRITE-DATA%d" % ((n
+ 1) % states
)
343 # On last state, see if we can continue the burst
344 # or if we should end it.
347 # Continue burst when consecutive access ready.
348 with m
.If(bus
.stb
& bus
.cyc
&
350 (bus_adr_lo
== (bus_adr
+ 1)) &
351 ((1<<bus_adr_hi
) == cs_latch
)):
352 comb
+= bus_latch
.eq(1), # Latch Bus (and cs)
353 # Early Write Ack (to allow bursting).
354 comb
+= bus
.ack
.eq(bus
.we
)
355 # Else end the burst.
356 with m
.Elif(bus_we | nfirst
):
358 sync
+= cycles
.eq(0) # reset to start
359 sync
+= cs_latch
.eq(0) # helps debugging
360 # Read Ack (when dat_r ready).
362 comb
+= bus
.ack
.eq(nfirst
& ~bus_we
)
367 return self
.phy
.ports() + list(self
.bus
.fields
.values())
370 if __name__
== '__main__':
371 layout
=[('rwds_o', 1), ('rwds_oe', 1),
372 ('cs_n', 1), ('csn_oe', 1),
373 ('ck_o', 1), ('ck_oe', 1),
376 layout
+= [('d%d_o' % i
, 1), ('d%d_oe' % i
, 1), ('d%d_i' % i
, 1)]
377 io
= Record(layout
=layout
)
378 dut
= HyperRAM(io
=io
, phy_kls
=HyperRAMASICPhy
)
379 vl
= rtlil
.convert(dut
, ports
=dut
.ports())
380 with
open("test_hyperram.il", "w") as f
: