3527ee41e5a3deeea8d703a3775f02d9f67da06c
[lambdasoc.git] / lambdasoc / periph / hyperram.py
1 # Basic Implementation of HyperRAM
2 #
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>
7 #
8 # Code from Lukats, Kermarrec and gatecat is Licensed BSD-2-Clause
9 #
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
12
13 """
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:
17
18 from nmigen.resources.memory import HyperRAMResources
19 hyperram_ios = HyperRAMResources(cs="B1",
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")
25
26 this trick will work with the 1-IC HyperRAM PMOD by Piotr Esden, sold
27 by 1bitsquared. however for the *four* IC HyperRAM PMOD, *four* cs_n pins
28 are needed (and is not currently supported).
29 on the TODO list for this module: interleave multiple HyperRAM
30 cs_n's to give striped (like RAID) memory accesses behind one single
31 Wishbone interface.
32 """
33
34
35 from nmigen import (Elaboratable, Module, Signal, Record, Cat, Const)
36 from nmigen.cli import rtlil
37
38 from nmigen_soc import wishbone
39 from nmigen_soc.memory import MemoryMap
40 from lambdasoc.periph import Peripheral
41
42
43 # HyperRAM ASIC PHY -----------------------------------------------------------
44
45 class HyperRAMASICPhy(Elaboratable):
46 def __init__(self, io):
47 self.io = io
48 self.ck = ck = Signal()
49 self.cs = cs = Signal(len(self.io.cs_n))
50 self.rst_n = rst_n = Signal()
51
52 self.dq_o = dq_o = Signal(8)
53 self.dq_i = dq_i = Signal(8)
54 self.dq_oe = dq_oe = Signal()
55
56 self.rwds_o = rwds_o = Signal.like(self.io["rwds_o"])
57 self.rwds_oe = rwds_oe = Signal()
58
59 def elaborate(self, platform):
60 m = Module()
61 comb = m.d.comb
62 ck, cs, rst_n = self.ck, self.cs, self.rst_n
63 dq_o, dq_i, dq_oe = self.dq_o, self.dq_i, self.dq_oe
64 rwds_o, rwds_oe = self.rwds_o, self.rwds_oe
65
66 comb += [
67 self.io["rwds_o"].eq(rwds_o),
68 self.io["cs_n"].eq(~cs),
69 self.io["csn_oe"].eq(0),
70 self.io["ck_o"].eq(ck),
71 self.io["ck_oe"].eq(0),
72 self.io["rwds_oe"].eq(~rwds_oe),
73 self.io["rst_n"].eq(rst_n),
74 ]
75
76 for i in range(8):
77 comb += [
78 self.io[f"d{i}_o"].eq(dq_o[i]),
79 self.io[f"d{i}_oe"].eq(~dq_oe),
80 dq_i[i].eq(self.io[f"d{i}_i"])
81 ]
82
83 return m
84
85 def ports(self):
86 return list(self.io.fields.values())
87
88
89 # HyperRAM pads class (PHY) which can be used for testing and simulation
90 # (without needing a platform instance). use as:
91 # dut = HyperRAM(io=HyperRamPads(), phy_kls=TestHyperRAMPHY)
92
93 class HyperRAMPads:
94 def __init__(self, dw=8, n_cs=1):
95 self.rst_n = Signal()
96 self.ck = Signal()
97 self.cs_n = Signal(n_cs)
98 self.dq = Record([("oe", 1), ("o", dw), ("i", dw)])
99 self.rwds = Record([("oe", 1), ("o", dw//8), ("i", dw//8)])
100 self.dq.o.name = "dq_o"
101 self.dq.i.name = "dq_i"
102 self.dq.oe.name = "dq_oe"
103 self.rwds.o.name = "rwds_o"
104 self.rwds.i.name = "rwds_i"
105 self.rwds.oe.name = "rwds_oe"
106
107 def ports(self):
108 return [self.ck, self.cs, self.dq.o, self.dq.i, self.dq.oe,
109 self.rwds.o, self.rwds.oe, self.reset_n]
110
111
112 class HyperRAMPHY(Elaboratable):
113 def __init__(self, pads):
114 self.pads = pads
115 self.ck = pads.ck
116 self.cs = Signal(len(self.pads.cs_n))
117 self.rst_n = pads.rst_n
118 self.dq_o = pads.dq.o
119 self.dq_i = pads.dq.i
120 self.dq_oe = pads.dq.oe
121 self.rwds_o = pads.rwds.o
122 self.rwds_oe = Signal()
123
124 def elaborate(self, platform):
125 m = Module()
126 m.d.comb += self.pads.cs_n.eq(self.cs)
127 m.d.comb += self.pads.rwds.oe.eq(self.rwds_oe)
128 return m
129
130 def ports(self):
131 return self.pads.ports()
132
133
134 # HyperRAM --------------------------------------------------------------------
135
136 class HyperRAM(Peripheral, Elaboratable):
137 """HyperRAM
138
139 Provides a very simple/minimal HyperRAM core that should work with all
140 FPGA/HyperRam chips:
141 - FPGA vendor agnostic.
142 - no setup/chip configuration (use default latency).
143
144 This core favors portability and ease of use over performance.
145 Tested: Winbond W956D8MBYA latency=7
146 Cypress S27KL0641DABHI020 requires latency=6
147 """
148 def __init__(self, *, io, phy_kls,
149 latency=6,
150 addr_width=23, # 8 GBytes, per IC
151 bus=None, features=frozenset()):
152 super().__init__()
153 self.io = io
154 self.phy = phy_kls(io)
155 self.latency = latency
156 self.bus = wishbone.Interface(addr_width=21,
157 data_width=32, granularity=8,
158 features=features)
159 mmap = MemoryMap(addr_width=23, data_width=8)
160 mmap.add_resource(object(), name="hyperram", size=2**23)
161 self.bus.memory_map = mmap
162 self.size = 2**23
163 # # #
164
165 def elaborate(self, platform):
166 m = Module()
167 m.submodules.phy = self.phy
168 bus = self.bus
169 comb, sync = m.d.comb, m.d.sync
170
171 ck = self.phy.ck
172 clk_phase = Signal(2)
173 cs = self.phy.cs
174 ca = Signal(48)
175 ca_active = Signal()
176 sr = Signal(48)
177 sr_new = Signal(48)
178
179 dq_o = self.phy.dq_o
180 dq_i = self.phy.dq_i
181 dq_oe = self.phy.dq_oe
182 dw = len(dq_o) # data width
183
184 rwds_o = self.phy.rwds_o
185 rwds_oe = self.phy.rwds_oe
186
187 # Clock Generation (sys_clk/4) -----------------------------------
188 sync += clk_phase.eq(clk_phase + 1)
189 with m.Switch(clk_phase):
190 with m.Case(1):
191 sync += ck.eq(cs)
192 with m.Case(3):
193 sync += ck.eq(0)
194
195 # Data Shift Register (for write and read) ------------------------
196 dqi = Signal(dw)
197 sync += dqi.eq(dq_i) # Sample on 90° and 270°
198 with m.If(ca_active):
199 comb += sr_new.eq(Cat(dqi[:8], sr[:-dw]))
200 with m.Else():
201 comb += sr_new.eq(Cat(dqi, sr[:-8]))
202 with m.If(~clk_phase[0]):
203 sync += sr.eq(sr_new) # Shift on 0° and 180°
204
205 # Data shift-out register ----------------------------------------
206 comb += self.bus.dat_r.eq(sr_new), # To Wisbone
207 with m.If(dq_oe):
208 comb += dq_o.eq(sr[-dw:]), # To HyperRAM
209 with m.If(dq_oe & ca_active):
210 comb += dq_o.eq(sr[-8:]), # To HyperRAM, Only 8-bit during CMD/Addr.
211
212 # Command generation ----------------------------------------------
213 ashift = {8:1, 16:0}[dw]
214 la = 3-ashift
215 comb += [
216 ca[47].eq(~self.bus.we), # R/W#
217 ca[45].eq(1), # Burst Type (Linear)
218 ca[16:45].eq(self.bus.adr[la:]), # Row & Upper Column Address
219 ca[ashift:3].eq(bus.adr), # Lower Column Address
220 ]
221
222 # Latency count starts from the middle of the command (thus the -4).
223 # In fixed latency mode (default), latency is 2 x Latency count.
224 # We have 4 x sys_clk per RAM clock:
225 latency_cycles = (self.latency * 2 * 4) - 4
226
227 # Bus Latch ----------------------------------------------------
228 bus_adr = Signal(32)
229 bus_we = Signal()
230 bus_sel = Signal(4)
231 bus_latch = Signal()
232 with m.If(bus_latch):
233 with m.If(bus.we):
234 sync += sr.eq(Cat(Const(0, 16), bus.dat_w))
235 sync += [ bus_we.eq(bus.we),
236 bus_sel.eq(bus.sel),
237 bus_adr.eq(bus.adr)
238 ]
239
240
241
242 # Sequencer -------------------------------------------------------
243 cycles = Signal(8)
244 first = Signal()
245 count_inc = Signal()
246 dbg_cyc = Signal(8)
247
248 # when not idle run a cycles counter
249 with m.If(count_inc):
250 sync += dbg_cyc.eq(dbg_cyc+1)
251 with m.Else():
252 sync += dbg_cyc.eq(0)
253
254 # Main FSM
255 with m.FSM() as fsm:
256 comb += count_inc.eq(~fsm.ongoing("IDLE"))
257 with m.State("IDLE"):
258 sync += first.eq(1)
259 with m.If(bus.cyc & bus.stb & (clk_phase == 0)):
260 sync += sr.eq(ca)
261 m.next = "SEND-COMMAND-ADDRESS"
262 sync += cycles.eq(0)
263
264 with m.State("SEND-COMMAND-ADDRESS"):
265 sync += cycles.eq(cycles+1)
266 comb += cs.eq(1) # Set CSn.
267 comb += ca_active.eq(1) # Send Command on DQ.
268 comb += dq_oe.eq(1), # Wait for 6*2 cycles...
269 with m.If(cycles == (6*2 - 1)):
270 m.next = "WAIT-LATENCY"
271 sync += cycles.eq(0)
272
273 with m.State("WAIT-LATENCY"):
274 sync += cycles.eq(cycles+1)
275 comb += cs.eq(1) # Set CSn.
276 # Wait for Latency cycles...
277 with m.If(cycles == (latency_cycles - 1)):
278 comb += bus_latch.eq(1) # Latch Bus.
279 # Early Write Ack (to allow bursting).
280 comb += bus.ack.eq(bus.we)
281 m.next = "READ-WRITE-DATA0"
282 sync += cycles.eq(0)
283
284 states = {8:4, 16:2}[dw]
285 for n in range(states):
286 with m.State("READ-WRITE-DATA%d" % n):
287 sync += cycles.eq(cycles+1)
288 comb += cs.eq(1), # Set CSn.
289 # Send Data on DQ/RWDS (for write).
290 with m.If(bus_we):
291 comb += dq_oe.eq(1)
292 comb += rwds_oe.eq(1)
293 for i in range(dw//8):
294 seli = ~bus_sel[4-1-n*dw//8-i]
295 comb += rwds_o[dw//8-1-i].eq(seli)
296 # Wait for 2 cycles (since HyperRAM's Clk = sys_clk/4).
297 with m.If(cycles == (2 - 1)):
298 # Set next default state (with rollover for bursts).
299 m.next = "READ-WRITE-DATA%d" % ((n + 1) % states)
300 sync += cycles.eq(0)
301 # On last state, see if we can continue the burst
302 # or if we should end it.
303 with m.If(n == (states - 1)):
304 sync += first.eq(0)
305 # Continue burst when consecutive access ready.
306 with m.If(bus.stb & bus.cyc &
307 (bus.we == bus_we) &
308 (bus.adr == (bus_adr + 1))):
309 comb += bus_latch.eq(1), # Latch Bus.
310 # Early Write Ack (to allow bursting).
311 comb += bus.ack.eq(bus.we)
312 # Else end the burst.
313 with m.Elif(bus_we | ~first):
314 m.next = "IDLE"
315 sync += cycles.eq(0)
316 # Read Ack (when dat_r ready).
317 with m.If((n == 0) & ~first):
318 comb += bus.ack.eq(~bus_we)
319
320 return m
321
322 def ports(self):
323 return self.phy.ports() + list(self.bus.fields.values())
324
325
326 if __name__ == '__main__':
327 layout=[('rwds_o', 1), ('rwds_oe', 1),
328 ('cs_n', 1), ('csn_oe', 1),
329 ('ck_o', 1), ('ck_oe', 1),
330 ('rst_n', 1)]
331 for i in range(8):
332 layout += [('d%d_o' % i, 1), ('d%d_oe' % i, 1), ('d%d_i' % i, 1)]
333 io = Record(layout=layout)
334 dut = HyperRAM(io=io, phy_kls=HyperRAMASICPhy)
335 vl = rtlil.convert(dut, ports=dut.ports())
336 with open("test_hyperram.il", "w") as f:
337 f.write(vl)
338