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