add first version of hyperram.py
[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) 2021 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)
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):
102 super().__init__()
103 self.io = io
104 self.phy = phy_kls(io)
105 self.bus = wishbone.Interface(addr_width=21,
106 data_width=32, granularity=8)
107 mmap = MemoryMap(addr_width=23, data_width=8)
108 mmap.add_resource(object(), name="hyperram", size=2**23)
109 self.bus.memory_map = mmap
110 self.size = 2**23
111 # # #
112
113 def elaborate(self, platform):
114 m = Module()
115 m.submodules.phy = self.phy
116
117 clk = self.phy.clk
118 clk_phase = Signal(2)
119 cs = self.phy.cs
120 ca = Signal(48)
121 sr = Signal(48)
122
123 dq_o = self.phy.dq_o
124 dq_i = self.phy.dq_i
125 dq_oe = self.phy.dq_oe
126
127 rwds_o = self.phy.rwds_o
128 rwds_oe = self.phy.rwds_oe
129
130 # Clock Generation (sys_clk/4) -----------------------------------
131 m.d.sync += clk_phase.eq(clk_phase + 1)
132 with m.Switch(clk_phase):
133 with m.Case(1):
134 m.d.sync += clk.eq(cs)
135 with m.Case(3):
136 m.d.sync += clk.eq(0)
137
138 # Data Shift Register (for write and read) ------------------------
139 dqi = Signal(8)
140 m.d.sync += dqi.eq(dq_i) # Sample on 90° and 270°
141 with m.Switch(clk_phase):
142 with m.Case(0, 2):
143 m.d.sync += sr.eq(Cat(dqi, sr[:-8]))
144
145 m.d.comb += [
146 self.bus.dat_r.eq(sr), # To Wisbone
147 dq_o.eq(sr[-8:]), # To HyperRAM
148 ]
149
150 # Command generation ----------------------------------------------
151 m.d.comb += [
152 ca[47].eq(~self.bus.we), # R/W#
153 ca[45].eq(1), # Burst Type (Linear)
154 ca[16:35].eq(self.bus.adr[2:21]), # Row & Upper Column Address
155 ca[1:3].eq(self.bus.adr[0:2]), # Lower Column Address
156 ca[0].eq(0), # Lower Column Address
157 ]
158
159 # Sequencer -------------------------------------------------------
160 dt_seq = [
161 # DT, Action
162 (3, []),
163 (12, [cs.eq(1), dq_oe.eq(1), sr.eq(ca)]), # Command: 6 clk
164 (44, [dq_oe.eq(0)]), # Latency(dflt): 2*6 clk
165 (2, [dq_oe.eq(self.bus.we), # Wr/Rd data byte: 2 clk
166 sr[:16].eq(0),
167 sr[16:].eq(self.bus.dat_w),
168 rwds_oe.eq(self.bus.we),
169 rwds_o.eq(~self.bus.sel[3])]),
170 (2, [rwds_o.eq(~self.bus.sel[2])]), # Wr/Rd data byte: 2 clk
171 (2, [rwds_o.eq(~self.bus.sel[1])]), # Wr/Rd data byte: 2 clk
172 (2, [rwds_o.eq(~self.bus.sel[0])]), # Wr/Rd data byte: 2 clk
173 (2, [cs.eq(0), rwds_oe.eq(0), dq_oe.eq(0)]),
174 (1, [self.bus.ack.eq(1)]),
175 (1, [self.bus.ack.eq(0)]),
176 (0, []),
177 ]
178 # Convert delta-time sequencer to time sequencer
179 t_seq = []
180 t_seq_start = (clk_phase == 1)
181 t = 0
182 for dt, a in dt_seq:
183 t_seq.append((t, a))
184 t += dt
185 timeline(m, self.bus.cyc & self.bus.stb & t_seq_start, t_seq)
186 return m
187
188 def ports(self):
189 return self.phy.ports() + list(self.bus.fields.values())
190
191
192 if __name__ == '__main__':
193 layout=[('rwds_o', 1), ('rwds_oe', 1),
194 ('csn_o', 1), ('csn_oe', 1),
195 ('clk_o', 1), ('clk_oe', 1)]
196 for i in range(8):
197 layout += [('d%d_o' % i, 1), ('d%d_oe' % i, 1), ('d%d_i' % i, 1)]
198 io = Record(layout=layout)
199 dut = HyperRAM(io=io, phy_kls=HyperRAMASICPhy)
200 vl = rtlil.convert(dut, ports=dut.ports())
201 with open("test_hyperram.il", "w") as f:
202 f.write(vl)
203