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