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