document useful "add_extension" for HyperRAM PMODs
[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_n="B1",
20 dq="D0 D1 D2 D3 D4 D7 D6 D7",
21 rwds="B2", rst_n="B3", clk_p="B4",
22 attrs=IOStandard("LVCMOS33"))
23 self.platform.add_extension(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*
28 separate and distinct instances are needed, each with a different
29 cs_n pin. 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 # for Migen compat
44 def timeline(m, trigger, events):
45 lastevent = max([e[0] for e in events])
46 counter = Signal(range(lastevent+1))
47
48 # insert counter reset if it doesn't naturally overflow
49 # (test if lastevent+1 is a power of 2)
50 with m.If(((lastevent & (lastevent + 1)) != 0) & (counter == lastevent)):
51 m.d.sync += counter.eq(0)
52 with m.Elif(counter != 0):
53 m.d.sync += counter.eq(counter + 1)
54 with m.Elif(trigger):
55 m.d.sync += counter.eq(1)
56
57 def get_cond(e):
58 if e[0] == 0:
59 return trigger & (counter == 0)
60 else:
61 return counter == e[0]
62 for ev in events:
63 with m.If(get_cond(ev)):
64 m.d.sync += ev[1]
65
66
67 # HyperRAM ASIC PHY -----------------------------------------------------------
68
69 class HyperRAMASICPhy(Elaboratable):
70 def __init__(self, io):
71 self.io = io
72 self.clk = clk = Signal()
73 self.cs = cs = Signal()
74
75 self.dq_o = dq_o = Signal(8)
76 self.dq_i = dq_i = Signal(8)
77 self.dq_oe = dq_oe = Signal()
78
79 self.rwds_o = rwds_o = Signal.like(self.io["rwds_o"])
80 self.rwds_oe = rwds_oe = Signal()
81
82 def elaborate(self, platform):
83 m = Module()
84 comb = m.d.comb
85 clk, cs = self.clk, self.cs
86 dq_o, dq_i, dq_oe = self.dq_o, self.dq_i, self.dq_oe
87 rwds_o, rwds_oe = self.rwds_o, self.rwds_oe
88
89 comb += [
90 self.io["rwds_o"].eq(rwds_o),
91 self.io["csn_o"].eq(~cs),
92 self.io["csn_oe"].eq(0),
93 self.io["clk_o"].eq(clk),
94 self.io["clk_oe"].eq(0),
95 self.io["rwds_oe"].eq(~rwds_oe),
96 ]
97
98 for i in range(8):
99 comb += [
100 self.io[f"d{i}_o"].eq(dq_o[i]),
101 self.io[f"d{i}_oe"].eq(~dq_oe),
102 dq_i[i].eq(self.io[f"d{i}_i"])
103 ]
104
105 return m
106
107 def ports(self):
108 return list(self.io.fields.values())
109
110 # HyperRAM --------------------------------------------------------------------
111
112 class HyperRAM(Peripheral, Elaboratable):
113 """HyperRAM
114
115 Provides a very simple/minimal HyperRAM core that should work with all
116 FPGA/HyperRam chips:
117 - FPGA vendor agnostic.
118 - no setup/chip configuration (use default latency).
119
120 This core favors portability and ease of use over performance.
121 """
122 def __init__(self, *, io, phy_kls, latency=6):
123 super().__init__()
124 self.io = io
125 self.phy = phy_kls(io)
126 self.latency = latency
127 self.bus = wishbone.Interface(addr_width=21,
128 data_width=32, granularity=8)
129 mmap = MemoryMap(addr_width=23, data_width=8)
130 mmap.add_resource(object(), name="hyperram", size=2**23)
131 self.bus.memory_map = mmap
132 self.size = 2**23
133 # # #
134
135 def elaborate(self, platform):
136 m = Module()
137 m.submodules.phy = self.phy
138 bus = self.bus
139 comb, sync = m.d.comb, m.d.sync
140
141 clk = self.phy.clk
142 clk_phase = Signal(2)
143 cs = self.phy.cs
144 ca = Signal(48)
145 ca_active = Signal()
146 sr = Signal(48)
147 sr_new = Signal(48)
148
149 dq_o = self.phy.dq_o
150 dq_i = self.phy.dq_i
151 dq_oe = self.phy.dq_oe
152 dw = len(dq_o) # data width
153
154 rwds_o = self.phy.rwds_o
155 rwds_oe = self.phy.rwds_oe
156
157 # Clock Generation (sys_clk/4) -----------------------------------
158 sync += clk_phase.eq(clk_phase + 1)
159 with m.Switch(clk_phase):
160 with m.Case(1):
161 sync += clk.eq(cs)
162 with m.Case(3):
163 sync += clk.eq(0)
164
165 # Data Shift Register (for write and read) ------------------------
166 dqi = Signal(dw)
167 sync += dqi.eq(dq_i) # Sample on 90° and 270°
168 with m.If(ca_active):
169 comb += sr_new.eq(Cat(dqi[:8], sr[:-dw]))
170 with m.Else():
171 comb += sr_new.eq(Cat(dqi, sr[:-8]))
172 with m.If(~clk_phase[0]):
173 sync += sr.eq(sr_new) # Shift on 0° and 180°
174
175 # Data shift-out register ----------------------------------------
176 comb += self.bus.dat_r.eq(sr_new), # To Wisbone
177 with m.If(dq_oe):
178 comb += dq_o.eq(sr[-dw:]), # To HyperRAM
179 with m.If(dq_oe & ca_active):
180 comb += dq_o.eq(sr[-8:]), # To HyperRAM, Only 8-bit during CMD/Addr.
181
182 # Command generation ----------------------------------------------
183 ashift = {8:1, 16:0}[dw]
184 la = 3-ashift
185 comb += [
186 ca[47].eq(~self.bus.we), # R/W#
187 ca[45].eq(1), # Burst Type (Linear)
188 ca[16:45].eq(self.bus.adr[la:]), # Row & Upper Column Address
189 ca[1:3].eq(self.bus.adr[0:2]), # Lower Column Address
190 ca[ashift:3].eq(bus.adr), # Lower Column Address
191 ]
192
193 # Latency count starts from the middle of the command (thus the -4).
194 # In fixed latency mode (default), latency is 2 x Latency count.
195 # We have 4 x sys_clk per RAM clock:
196 latency_cycles = (self.latency * 2 * 4) - 4
197
198 # Bus Latch ----------------------------------------------------
199 bus_adr = Signal(32)
200 bus_we = Signal()
201 bus_sel = Signal(4)
202 bus_latch = Signal()
203 with m.If(bus_latch):
204 with m.If(bus.we):
205 sync += sr.eq(Cat(Const(0, 16), bus.dat_w))
206 sync += [ bus_we.eq(bus.we),
207 bus_sel.eq(bus.sel),
208 bus_adr.eq(bus.adr)
209 ]
210
211
212
213 # Sequencer -------------------------------------------------------
214 cycles = Signal(8)
215 first = Signal()
216 count_inc = Signal()
217 dbg_cyc = Signal(8)
218
219 # when not idle run a cycles counter
220 with m.If(count_inc):
221 sync += dbg_cyc.eq(dbg_cyc+1)
222 with m.Else():
223 sync += dbg_cyc.eq(0)
224
225 # Main FSM
226 with m.FSM() as fsm:
227 comb += count_inc.eq(~fsm.ongoing("IDLE"))
228 with m.State("IDLE"):
229 sync += first.eq(1)
230 with m.If(bus.cyc & bus.stb & (clk_phase == 0)):
231 sync += sr.eq(ca)
232 m.next = "SEND-COMMAND-ADDRESS"
233 sync += cycles.eq(0)
234
235 with m.State("SEND-COMMAND-ADDRESS"):
236 sync += cycles.eq(cycles+1)
237 comb += cs.eq(1) # Set CSn.
238 comb += ca_active.eq(1) # Send Command on DQ.
239 comb += dq_oe.eq(1), # Wait for 6*2 cycles...
240 with m.If(cycles == (6*2 - 1)):
241 m.next = "WAIT-LATENCY"
242 sync += cycles.eq(0)
243
244 with m.State("WAIT-LATENCY"):
245 sync += cycles.eq(cycles+1)
246 comb += cs.eq(1) # Set CSn.
247 # Wait for Latency cycles...
248 with m.If(cycles == (latency_cycles - 1)):
249 comb += bus_latch.eq(1) # Latch Bus.
250 # Early Write Ack (to allow bursting).
251 comb += bus.ack.eq(bus.we)
252 m.next = "READ-WRITE-DATA0"
253 sync += cycles.eq(0)
254
255 states = {8:4, 16:2}[dw]
256 for n in range(states):
257 with m.State("READ-WRITE-DATA%d" % n):
258 sync += cycles.eq(cycles+1)
259 comb += cs.eq(1), # Set CSn.
260 # Send Data on DQ/RWDS (for write).
261 with m.If(bus_we):
262 comb += dq_oe.eq(1)
263 comb += rwds_oe.eq(1)
264 for i in range(dw//8):
265 seli = ~bus_sel[4-1-n*dw//8-i]
266 comb += rwds_o[dw//8-1-i].eq(seli)
267 # Wait for 2 cycles (since HyperRAM's Clk = sys_clk/4).
268 with m.If(cycles == (2 - 1)):
269 # Set next default state (with rollover for bursts).
270 m.next = "READ-WRITE-DATA%d" % ((n + 1) % states)
271 sync += cycles.eq(0)
272 # On last state, see if we can continue the burst
273 # or if we should end it.
274 with m.If(n == (states - 1)):
275 sync += first.eq(0)
276 # Continue burst when consecutive access ready.
277 with m.If(bus.stb & bus.cyc &
278 (bus.we == bus_we) &
279 (bus.adr == (bus_adr + 1))):
280 comb += bus_latch.eq(1), # Latch Bus.
281 # Early Write Ack (to allow bursting).
282 comb += bus.ack.eq(bus.we)
283 # Else end the burst.
284 with m.Elif(bus_we | ~first):
285 m.next = "IDLE"
286 sync += cycles.eq(0)
287 # Read Ack (when dat_r ready).
288 with m.If((n == 0) & ~first):
289 comb += bus.ack.eq(~bus_we)
290
291 return m
292
293 def ports(self):
294 return self.phy.ports() + list(self.bus.fields.values())
295
296
297 if __name__ == '__main__':
298 layout=[('rwds_o', 1), ('rwds_oe', 1),
299 ('csn_o', 1), ('csn_oe', 1),
300 ('clk_o', 1), ('clk_oe', 1)]
301 for i in range(8):
302 layout += [('d%d_o' % i, 1), ('d%d_oe' % i, 1), ('d%d_i' % i, 1)]
303 io = Record(layout=layout)
304 dut = HyperRAM(io=io, phy_kls=HyperRAMASICPhy)
305 vl = rtlil.convert(dut, ports=dut.ports())
306 with open("test_hyperram.il", "w") as f:
307 f.write(vl)
308