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