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