fix attributes, sort out address
[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
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[ashift:3].eq(bus.adr), # Lower Column Address
202 ]
203
204 # Latency count starts from the middle of the command (thus the -4).
205 # In fixed latency mode (default), latency is 2 x Latency count.
206 # We have 4 x sys_clk per RAM clock:
207 latency_cycles = (self.latency * 2 * 4) - 4
208
209 # Bus Latch ----------------------------------------------------
210 bus_adr = Signal(32)
211 bus_we = Signal()
212 bus_sel = Signal(4)
213 bus_latch = Signal()
214 with m.If(bus_latch):
215 with m.If(bus.we):
216 sync += sr.eq(Cat(Const(0, 16), bus.dat_w))
217 sync += [ bus_we.eq(bus.we),
218 bus_sel.eq(bus.sel),
219 bus_adr.eq(bus.adr)
220 ]
221
222
223
224 # Sequencer -------------------------------------------------------
225 cycles = Signal(8)
226 first = Signal()
227 count_inc = Signal()
228 dbg_cyc = Signal(8)
229
230 # when not idle run a cycles counter
231 with m.If(count_inc):
232 sync += dbg_cyc.eq(dbg_cyc+1)
233 with m.Else():
234 sync += dbg_cyc.eq(0)
235
236 # Main FSM
237 with m.FSM() as fsm:
238 comb += count_inc.eq(~fsm.ongoing("IDLE"))
239 with m.State("IDLE"):
240 sync += first.eq(1)
241 with m.If(bus.cyc & bus.stb & (clk_phase == 0)):
242 sync += sr.eq(ca)
243 m.next = "SEND-COMMAND-ADDRESS"
244 sync += cycles.eq(0)
245
246 with m.State("SEND-COMMAND-ADDRESS"):
247 sync += cycles.eq(cycles+1)
248 comb += cs.eq(1) # Set CSn.
249 comb += ca_active.eq(1) # Send Command on DQ.
250 comb += dq_oe.eq(1), # Wait for 6*2 cycles...
251 with m.If(cycles == (6*2 - 1)):
252 m.next = "WAIT-LATENCY"
253 sync += cycles.eq(0)
254
255 with m.State("WAIT-LATENCY"):
256 sync += cycles.eq(cycles+1)
257 comb += cs.eq(1) # Set CSn.
258 # Wait for Latency cycles...
259 with m.If(cycles == (latency_cycles - 1)):
260 comb += bus_latch.eq(1) # Latch Bus.
261 # Early Write Ack (to allow bursting).
262 comb += bus.ack.eq(bus.we)
263 m.next = "READ-WRITE-DATA0"
264 sync += cycles.eq(0)
265
266 states = {8:4, 16:2}[dw]
267 for n in range(states):
268 with m.State("READ-WRITE-DATA%d" % n):
269 sync += cycles.eq(cycles+1)
270 comb += cs.eq(1), # Set CSn.
271 # Send Data on DQ/RWDS (for write).
272 with m.If(bus_we):
273 comb += dq_oe.eq(1)
274 comb += rwds_oe.eq(1)
275 for i in range(dw//8):
276 seli = ~bus_sel[4-1-n*dw//8-i]
277 comb += rwds_o[dw//8-1-i].eq(seli)
278 # Wait for 2 cycles (since HyperRAM's Clk = sys_clk/4).
279 with m.If(cycles == (2 - 1)):
280 # Set next default state (with rollover for bursts).
281 m.next = "READ-WRITE-DATA%d" % ((n + 1) % states)
282 sync += cycles.eq(0)
283 # On last state, see if we can continue the burst
284 # or if we should end it.
285 with m.If(n == (states - 1)):
286 sync += first.eq(0)
287 # Continue burst when consecutive access ready.
288 with m.If(bus.stb & bus.cyc &
289 (bus.we == bus_we) &
290 (bus.adr == (bus_adr + 1))):
291 comb += bus_latch.eq(1), # Latch Bus.
292 # Early Write Ack (to allow bursting).
293 comb += bus.ack.eq(bus.we)
294 # Else end the burst.
295 with m.Elif(bus_we | ~first):
296 m.next = "IDLE"
297 sync += cycles.eq(0)
298 # Read Ack (when dat_r ready).
299 with m.If((n == 0) & ~first):
300 comb += bus.ack.eq(~bus_we)
301
302 return m
303
304 def ports(self):
305 return self.phy.ports() + list(self.bus.fields.values())
306
307
308 if __name__ == '__main__':
309 layout=[('rwds_o', 1), ('rwds_oe', 1),
310 ('csn_o', 1), ('csn_oe', 1),
311 ('ck_o', 1), ('ck_oe', 1)]
312 for i in range(8):
313 layout += [('d%d_o' % i, 1), ('d%d_oe' % i, 1), ('d%d_i' % i, 1)]
314 io = Record(layout=layout)
315 dut = HyperRAM(io=io, phy_kls=HyperRAMASICPhy)
316 vl = rtlil.convert(dut, ports=dut.ports())
317 with open("test_hyperram.il", "w") as f:
318 f.write(vl)
319