1 # Basic Implementation of HyperRAM
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>
8 # Code from Lukats, Kermarrec and gatecat is Licensed BSD-2-Clause
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
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:
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")
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
35 from nmigen
import (Elaboratable
, Module
, Signal
, Record
, Cat
, Const
)
36 from nmigen
.cli
import rtlil
38 from nmigen_soc
import wishbone
39 from nmigen_soc
.memory
import MemoryMap
40 from lambdasoc
.periph
import Peripheral
43 # HyperRAM ASIC PHY -----------------------------------------------------------
45 class HyperRAMASICPhy(Elaboratable
):
46 def __init__(self
, io
):
48 self
.ck
= ck
= Signal()
49 self
.cs
= cs
= Signal()
51 self
.dq_o
= dq_o
= Signal(8)
52 self
.dq_i
= dq_i
= Signal(8)
53 self
.dq_oe
= dq_oe
= Signal()
55 self
.rwds_o
= rwds_o
= Signal
.like(self
.io
["rwds_o"])
56 self
.rwds_oe
= rwds_oe
= Signal()
58 def elaborate(self
, platform
):
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
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
),
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"])
84 return list(self
.io
.fields
.values())
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)
92 def __init__(self
, dw
=8):
95 self
.dq
= Record([("oe", 1), ("o", dw
), ("i", dw
)])
96 self
.rwds
= Record([("oe", 1), ("o", dw
//8), ("i", dw
//8)])
99 class HyperRAMPHY(Elaboratable
):
100 def __init__(self
, pads
):
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()
110 def elaborate(self
, platform
):
112 m
.d
.comb
+= self
.pads
.cs_n
.eq(~self
.cs
)
113 m
.d
.comb
+= self
.pads
.rwds
.oe
.eq(self
.rwds_oe
)
117 return [self
.ck
, self
.cs
, self
.dq_o
, self
.dq_i
, self
.dq_oe
,
118 self
.rwds_o
, self
.rwds_oe
]
120 # HyperRAM --------------------------------------------------------------------
122 class HyperRAM(Peripheral
, Elaboratable
):
125 Provides a very simple/minimal HyperRAM core that should work with all
127 - FPGA vendor agnostic.
128 - no setup/chip configuration (use default latency).
130 This core favors portability and ease of use over performance.
132 def __init__(self
, *, io
, phy_kls
, latency
=6, bus
=None,
133 features
=frozenset()):
136 self
.phy
= phy_kls(io
)
137 self
.latency
= latency
138 self
.bus
= wishbone
.Interface(addr_width
=21,
139 data_width
=32, granularity
=8,
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
147 def elaborate(self
, platform
):
149 m
.submodules
.phy
= self
.phy
151 comb
, sync
= m
.d
.comb
, m
.d
.sync
154 clk_phase
= Signal(2)
163 dq_oe
= self
.phy
.dq_oe
164 dw
= len(dq_o
) # data width
166 rwds_o
= self
.phy
.rwds_o
167 rwds_oe
= self
.phy
.rwds_oe
169 # Clock Generation (sys_clk/4) -----------------------------------
170 sync
+= clk_phase
.eq(clk_phase
+ 1)
171 with m
.Switch(clk_phase
):
177 # Data Shift Register (for write and read) ------------------------
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
]))
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°
187 # Data shift-out register ----------------------------------------
188 comb
+= self
.bus
.dat_r
.eq(sr_new
), # To Wisbone
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.
194 # Command generation ----------------------------------------------
195 ashift
= {8:1, 16:0}[dw
]
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
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
209 # Bus Latch ----------------------------------------------------
214 with m
.If(bus_latch
):
216 sync
+= sr
.eq(Cat(Const(0, 16), bus
.dat_w
))
217 sync
+= [ bus_we
.eq(bus
.we
),
224 # Sequencer -------------------------------------------------------
230 # when not idle run a cycles counter
231 with m
.If(count_inc
):
232 sync
+= dbg_cyc
.eq(dbg_cyc
+1)
234 sync
+= dbg_cyc
.eq(0)
238 comb
+= count_inc
.eq(~fsm
.ongoing("IDLE"))
239 with m
.State("IDLE"):
241 with m
.If(bus
.cyc
& bus
.stb
& (clk_phase
== 0)):
243 m
.next
= "SEND-COMMAND-ADDRESS"
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"
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"
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).
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
)
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)):
287 # Continue burst when consecutive access ready.
288 with m
.If(bus
.stb
& bus
.cyc
&
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
):
298 # Read Ack (when dat_r ready).
299 with m
.If((n
== 0) & ~first
):
300 comb
+= bus
.ack
.eq(~bus_we
)
305 return self
.phy
.ports() + list(self
.bus
.fields
.values())
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)]
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
: