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