add cs_latch as a peer of bus_latch in case the address changes
[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 ck_active = Signal()
174 cs = self.phy.cs
175 ca = Signal(48)
176 ca_active = Signal()
177 sr = Signal(48)
178 sr_new = Signal(48)
179
180 dq_o = self.phy.dq_o
181 dq_i = self.phy.dq_i
182 dq_oe = self.phy.dq_oe
183 dw = len(dq_o) # data width
184
185 rwds_o = self.phy.rwds_o
186 rwds_oe = self.phy.rwds_oe
187
188 # Clock Generation (sys_clk/4) -----------------------------------
189 sync += clk_phase.eq(clk_phase + 1)
190 with m.Switch(clk_phase):
191 with m.Case(1):
192 sync += ck.eq(ck_active)
193 with m.Case(3):
194 sync += ck.eq(0)
195
196 # Data Shift Register (for write and read) ------------------------
197 dqi = Signal(dw)
198 sync += dqi.eq(dq_i) # Sample on 90° and 270°
199 with m.If(ca_active):
200 comb += sr_new.eq(Cat(dqi[:8], sr[:-dw]))
201 with m.Else():
202 comb += sr_new.eq(Cat(dqi, sr[:-8]))
203 with m.If(~clk_phase[0]):
204 sync += sr.eq(sr_new) # Shift on 0° and 180°
205
206 # Data shift-out register ----------------------------------------
207 comb += self.bus.dat_r.eq(sr_new), # To Wisbone
208 with m.If(dq_oe):
209 comb += dq_o.eq(sr[-dw:]), # To HyperRAM
210 with m.If(dq_oe & ca_active):
211 comb += dq_o.eq(sr[-8:]), # To HyperRAM, Only 8-bit during CMD/Addr.
212
213 # Command generation ----------------------------------------------
214 ashift = {8:1, 16:0}[dw]
215 la = 3-ashift
216 comb += [
217 ca[47].eq(~self.bus.we), # R/W#
218 ca[45].eq(1), # Burst Type (Linear)
219 ca[16:45].eq(self.bus.adr[la:]), # Row & Upper Column Address
220 ca[ashift:3].eq(bus.adr), # Lower Column Address
221 ]
222
223 # Latency count starts from the middle of the command (thus the -4).
224 # In fixed latency mode (default), latency is 2 x Latency count.
225 # We have 4 x sys_clk per RAM clock:
226 latency_cycles = (self.latency * 2 * 4) - 4
227
228 # Bus Latch ----------------------------------------------------
229 bus_adr = Signal(32)
230 bus_we = Signal()
231 bus_sel = Signal(4)
232 bus_latch = Signal()
233 cs_latch = Signal.like(cs)
234 with m.If(bus_latch):
235 with m.If(bus.we):
236 sync += sr.eq(Cat(Const(0, 16), bus.dat_w))
237 sync += [ bus_we.eq(bus.we),
238 bus_sel.eq(bus.sel),
239 bus_adr.eq(bus.adr),
240 cs_latch.eq(cs)
241 ]
242
243
244
245 # Sequencer -------------------------------------------------------
246 cycles = Signal(8)
247 first = Signal()
248 nfirst = Signal() # not-first
249 count_inc = Signal()
250 dbg_cyc = Signal(8)
251 comb += nfirst.eq(~first) # convenience
252
253 # when not idle run a cycles counter
254 with m.If(count_inc):
255 sync += dbg_cyc.eq(dbg_cyc+1)
256 with m.Else():
257 sync += dbg_cyc.eq(0)
258
259 # Main FSM
260 with m.FSM() as fsm:
261 comb += count_inc.eq(~fsm.ongoing("IDLE"))
262 with m.State("IDLE"):
263 sync += first.eq(1)
264 with m.If(bus.cyc & bus.stb & (clk_phase == 0)):
265 sync += sr.eq(ca)
266 m.next = "SEND-COMMAND-ADDRESS"
267 sync += cycles.eq(0)
268
269 with m.State("SEND-COMMAND-ADDRESS"):
270 sync += cycles.eq(cycles+1)
271 comb += cs.eq(1) # Set CSn.
272 comb += ck_active.eq(1) # Activate clock
273 comb += ca_active.eq(1) # Send Command on DQ.
274 comb += dq_oe.eq(1), # Wait for 6*2 cycles...
275 with m.If(cycles == (6*2 - 1)):
276 m.next = "WAIT-LATENCY"
277 sync += cycles.eq(0)
278
279 with m.State("WAIT-LATENCY"):
280 sync += cycles.eq(cycles+1)
281 comb += cs.eq(1) # Set CSn directly (not via latch)
282 comb += ck_active.eq(1) # Activate clock
283 # Wait for Latency cycles...
284 with m.If(cycles == (latency_cycles - 1)):
285 comb += bus_latch.eq(1) # Latch Bus (and cs)
286 # Early Write Ack (to allow bursting).
287 comb += bus.ack.eq(bus.we)
288 m.next = "READ-WRITE-DATA0"
289 sync += cycles.eq(0)
290
291 states = {8:4, 16:2}[dw]
292 for n in range(states):
293 with m.State("READ-WRITE-DATA%d" % n):
294 sync += cycles.eq(cycles+1)
295 comb += cs.eq(cs_latch), # Set CSn from Latch
296 comb += ck_active.eq(1) # Activate clock
297 # Send Data on DQ/RWDS (for write).
298 with m.If(bus_we):
299 comb += dq_oe.eq(1)
300 comb += rwds_oe.eq(1)
301 for i in range(dw//8):
302 seli = ~bus_sel[4-1-n*dw//8-i]
303 comb += rwds_o[dw//8-1-i].eq(seli)
304 # Wait for 2 cycles (since HyperRAM's Clk = sys_clk/4).
305 with m.If(cycles == (2 - 1)):
306 # Set next default state (with rollover for bursts).
307 m.next = "READ-WRITE-DATA%d" % ((n + 1) % states)
308 sync += cycles.eq(0)
309 # On last state, see if we can continue the burst
310 # or if we should end it.
311 if n == states - 1:
312 sync += first.eq(0)
313 # Continue burst when consecutive access ready.
314 with m.If(bus.stb & bus.cyc &
315 (bus.we == bus_we) &
316 (bus.adr == (bus_adr + 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 # Else end the burst.
321 with m.Elif(bus_we | nfirst):
322 m.next = "IDLE"
323 sync += cycles.eq(0) # reset to start
324 sync += cs_latch.eq(0) # helps debugging
325 # Read Ack (when dat_r ready).
326 if n == 0:
327 comb += bus.ack.eq(nfirst & ~bus_we)
328
329 return m
330
331 def ports(self):
332 return self.phy.ports() + list(self.bus.fields.values())
333
334
335 if __name__ == '__main__':
336 layout=[('rwds_o', 1), ('rwds_oe', 1),
337 ('cs_n', 1), ('csn_oe', 1),
338 ('ck_o', 1), ('ck_oe', 1),
339 ('rst_n', 1)]
340 for i in range(8):
341 layout += [('d%d_o' % i, 1), ('d%d_oe' % i, 1), ('d%d_i' % i, 1)]
342 io = Record(layout=layout)
343 dut = HyperRAM(io=io, phy_kls=HyperRAMASICPhy)
344 vl = rtlil.convert(dut, ports=dut.ports())
345 with open("test_hyperram.il", "w") as f:
346 f.write(vl)
347