b8bff738beafc81dc25d939c64c0efd2c14b7166
[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 nfirst = Signal() # not-first
246 count_inc = Signal()
247 dbg_cyc = Signal(8)
248 comb += nfirst.eq(~first) # convenience
249
250 # when not idle run a cycles counter
251 with m.If(count_inc):
252 sync += dbg_cyc.eq(dbg_cyc+1)
253 with m.Else():
254 sync += dbg_cyc.eq(0)
255
256 # Main FSM
257 with m.FSM() as fsm:
258 comb += count_inc.eq(~fsm.ongoing("IDLE"))
259 with m.State("IDLE"):
260 sync += first.eq(1)
261 with m.If(bus.cyc & bus.stb & (clk_phase == 0)):
262 sync += sr.eq(ca)
263 m.next = "SEND-COMMAND-ADDRESS"
264 sync += cycles.eq(0)
265
266 with m.State("SEND-COMMAND-ADDRESS"):
267 sync += cycles.eq(cycles+1)
268 comb += cs.eq(1) # Set CSn.
269 comb += ca_active.eq(1) # Send Command on DQ.
270 comb += dq_oe.eq(1), # Wait for 6*2 cycles...
271 with m.If(cycles == (6*2 - 1)):
272 m.next = "WAIT-LATENCY"
273 sync += cycles.eq(0)
274
275 with m.State("WAIT-LATENCY"):
276 sync += cycles.eq(cycles+1)
277 comb += cs.eq(1) # Set CSn.
278 # Wait for Latency cycles...
279 with m.If(cycles == (latency_cycles - 1)):
280 comb += bus_latch.eq(1) # Latch Bus.
281 # Early Write Ack (to allow bursting).
282 comb += bus.ack.eq(bus.we)
283 m.next = "READ-WRITE-DATA0"
284 sync += cycles.eq(0)
285
286 states = {8:4, 16:2}[dw]
287 for n in range(states):
288 with m.State("READ-WRITE-DATA%d" % n):
289 sync += cycles.eq(cycles+1)
290 comb += cs.eq(1), # Set CSn.
291 # Send Data on DQ/RWDS (for write).
292 with m.If(bus_we):
293 comb += dq_oe.eq(1)
294 comb += rwds_oe.eq(1)
295 for i in range(dw//8):
296 seli = ~bus_sel[4-1-n*dw//8-i]
297 comb += rwds_o[dw//8-1-i].eq(seli)
298 # Wait for 2 cycles (since HyperRAM's Clk = sys_clk/4).
299 with m.If(cycles == (2 - 1)):
300 # Set next default state (with rollover for bursts).
301 m.next = "READ-WRITE-DATA%d" % ((n + 1) % states)
302 sync += cycles.eq(0)
303 # On last state, see if we can continue the burst
304 # or if we should end it.
305 if n == states - 1:
306 sync += first.eq(0)
307 # Continue burst when consecutive access ready.
308 with m.If(bus.stb & bus.cyc &
309 (bus.we == bus_we) &
310 (bus.adr == (bus_adr + 1))):
311 comb += bus_latch.eq(1), # Latch Bus.
312 # Early Write Ack (to allow bursting).
313 comb += bus.ack.eq(bus.we)
314 # Else end the burst.
315 with m.Elif(bus_we | nfirst):
316 m.next = "IDLE"
317 sync += cycles.eq(0)
318 # Read Ack (when dat_r ready).
319 if n == 0:
320 comb += bus.ack.eq(nfirst & ~bus_we)
321
322 return m
323
324 def ports(self):
325 return self.phy.ports() + list(self.bus.fields.values())
326
327
328 if __name__ == '__main__':
329 layout=[('rwds_o', 1), ('rwds_oe', 1),
330 ('cs_n', 1), ('csn_oe', 1),
331 ('ck_o', 1), ('ck_oe', 1),
332 ('rst_n', 1)]
333 for i in range(8):
334 layout += [('d%d_o' % i, 1), ('d%d_oe' % i, 1), ('d%d_i' % i, 1)]
335 io = Record(layout=layout)
336 dut = HyperRAM(io=io, phy_kls=HyperRAMASICPhy)
337 vl = rtlil.convert(dut, ports=dut.ports())
338 with open("test_hyperram.il", "w") as f:
339 f.write(vl)
340