Add more memory tests
[gram.git] / gram / frontend / adaptation.py
1 # This file is Copyright (c) 2016-2019 Florent Kermarrec <florent@enjoy-digital.fr>
2 # This file is Copyright (c) 2020 LambdaConcept <contact@lambdaconcept.com>
3 # License: BSD
4
5 from nmigen import *
6 from nmigen.compat import Case
7
8 from gram.common import *
9 import gram.stream as stream
10
11 # LiteDRAMNativePortCDC ----------------------------------------------------------------------------
12
13
14 class gramNativePortCDC(Elaboratable):
15 def __init__(self, port_from, port_to,
16 cmd_depth=4,
17 wdata_depth=16,
18 rdata_depth=16):
19 assert port_from.address_width == port_to.address_width
20 assert port_from.data_width == port_to.data_width
21 assert port_from.mode == port_to.mode
22
23 self._port_from = port_from
24 self._port_to = port_to
25 self._cmd_depth = cmd_depth
26 self._wdata_depth = wdata_depth
27 self._rdata_depth = rdata_depth
28
29 def elaborate(self, platform):
30 m = Module()
31
32 port_from = self._port_from
33 port_to = self._port_to
34 cmd_depth = self._cmd_depth
35 wdata_depth = self._wdata_depth
36 rdata_depth = self._rdata_depth
37
38 address_width = port_from.address_width
39 data_width = port_from.data_width
40 mode = port_from.mode
41 clock_domain_from = port_from.clock_domain
42 clock_domain_to = port_to.clock_domain
43
44 cmd_fifo = stream.AsyncFIFO(
45 [("we", 1), ("addr", address_width)], cmd_depth)
46 cmd_fifo = ClockDomainsRenamer(
47 {"write": clock_domain_from,
48 "read": clock_domain_to})(cmd_fifo)
49 m.submodules += cmd_fifo
50 m.submodules += stream.Pipeline(
51 port_from.cmd, cmd_fifo, port_to.cmd)
52
53 if mode == "write" or mode == "both":
54 wdata_fifo = stream.AsyncFIFO(
55 [("data", data_width), ("we", data_width//8)], wdata_depth)
56 wdata_fifo = ClockDomainsRenamer(
57 {"write": clock_domain_from,
58 "read": clock_domain_to})(wdata_fifo)
59 m.submodules += wdata_fifo
60 m.submodules += stream.Pipeline(
61 port_from.wdata, wdata_fifo, port_to.wdata)
62
63 if mode == "read" or mode == "both":
64 rdata_fifo = stream.AsyncFIFO([("data", data_width)], rdata_depth)
65 rdata_fifo = ClockDomainsRenamer(
66 {"write": clock_domain_to,
67 "read": clock_domain_from})(rdata_fifo)
68 m.submodules += rdata_fifo
69 m.submodules += stream.Pipeline(
70 port_to.rdata, rdata_fifo, port_from.rdata)
71
72 return m
73
74 # LiteDRAMNativePortDownConverter ------------------------------------------------------------------
75
76
77 class gramNativePortDownConverter(Elaboratable):
78 """LiteDRAM port DownConverter
79
80 This module reduces user port data width to fit controller data width.
81 With N = port_from.data_width/port_to.data_width:
82 - Address is adapted (multiplied by N + internal increments)
83 - A write from the user is splitted and generates N writes to the
84 controller.
85 - A read from the user generates N reads to the controller and returned
86 datas are regrouped in a single data presented to the user.
87 """
88
89 def __init__(self, port_from, port_to, reverse=False):
90 assert port_from.clock_domain == port_to.clock_domain
91 assert port_from.data_width > port_to.data_width
92 assert port_from.mode == port_to.mode
93 if port_from.data_width % port_to.data_width:
94 raise ValueError("Ratio must be an int")
95
96 self._port_from = port_from
97 self._port_to = port_to
98 self._reverse = reverse
99
100 def elaborate(self, platform):
101 m = Module()
102
103 port_from = self._port_from
104 port_to = self._port_to
105 reverse = self._reverse
106
107 ratio = port_from.data_width//port_to.data_width
108 mode = port_from.mode
109
110 counter = Signal(range(ratio))
111 counter_reset = Signal()
112 counter_ce = Signal()
113
114 with m.If(counter_reset):
115 m.d.sync += counter.eq(0)
116 with m.Elif(counter_ce):
117 m.d.sync += counter.eq(counter+1)
118
119 with m.FSM():
120 with m.State("Idle"):
121 m.d.comb += counter_reset.eq(1)
122 with m.If(port_from.cmd.valid):
123 m.next = "Convert"
124
125 with m.State("Convert"):
126 m.d.comb += [
127 port_to.cmd.valid.eq(1),
128 port_to.cmd.we.eq(port_from.cmd.we),
129 port_to.cmd.addr.eq(port_from.cmd.addr*ratio + counter),
130 ]
131 with m.If(port_to.cmd.ready):
132 m.d.comb += counter_ce.eq(1)
133 with m.If(counter == ratio - 1):
134 m.d.comb += port_from.cmd.ready.eq(1)
135 m.next = "Idle"
136
137 if mode == "write" or mode == "both":
138 wdata_converter = stream.StrideConverter(
139 port_from.wdata.description,
140 port_to.wdata.description,
141 reverse=reverse)
142 m.submodules += wdata_converter
143 m.submodules += stream.Pipeline(
144 port_from.wdata, wdata_converter, port_to.wdata)
145
146 if mode == "read" or mode == "both":
147 rdata_converter = stream.StrideConverter(
148 port_to.rdata.description,
149 port_from.rdata.description,
150 reverse=reverse)
151 m.submodules += rdata_converter
152 m.submodules += stream.Pipeline(
153 port_to.rdata, rdata_converter, port_from.rdata)
154
155 return m
156
157 # LiteDRAMNativeWritePortUpConverter ---------------------------------------------------------------
158
159
160 class gramNativeWritePortUpConverter(Elaboratable):
161 # TODO: finish and remove hack
162 """LiteDRAM write port UpConverter
163
164 This module increase user port data width to fit controller data width.
165 With N = port_to.data_width/port_from.data_width:
166 - Address is adapted (divided by N)
167 - N writes from user are regrouped in a single one to the controller
168 (when possible, ie when consecutive and bursting)
169 """
170
171 def __init__(self, port_from, port_to, reverse=False):
172 assert port_from.clock_domain == port_to.clock_domain
173 assert port_from.data_width < port_to.data_width
174 assert port_from.mode == port_to.mode
175 assert port_from.mode == "write"
176 if port_to.data_width % port_from.data_width:
177 raise ValueError("Ratio must be an int")
178
179 self._port_from = port_from
180 self._port_to = port_to
181 self._reverse = reverse
182
183 def elaborate(self, platform):
184 m = Module()
185
186 port_from = self._port_from
187 port_to = self._port_to
188 reverse = self._reverse
189
190 ratio = port_to.data_width//port_from.data_width
191
192 we = Signal()
193 address = Signal(port_to.address_width)
194
195 counter = Signal(max=ratio)
196 counter_reset = Signal()
197 counter_ce = Signal()
198 self.sync += \
199 If(counter_reset,
200 counter.eq(0)
201 ).Elif(counter_ce,
202 counter.eq(counter + 1)
203 )
204
205 with m.FSM():
206 with m.State("Idle"):
207 m.d.comb += port_from.cmd.ready.eq(1)
208 with m.If(port_from.cmd.valid):
209 m.d.sync += [
210 we.eq(port_from.cmd.we),
211 address.eq(port_from.cmd.addr),
212 ]
213 m.next = "Receive"
214
215 with m.State("Receive"):
216 m.d.comb += port_from.cmd.ready.eq(1)
217 with m.If(port_from.cmd.valid):
218 m.d.comb += counter_ce.eq(1)
219 with m.If(counter == ratio-1):
220 m.next = "Generate"
221
222 with m.State("Generate"):
223 m.d.comb += [
224 port_to.cmd.valid.eq(1),
225 port_to.cmd.we.eq(we),
226 port_to.cmd.addr.eq(address[log2_int(ratio):]),
227 ]
228 with m.If(port_to.cmd.ready):
229 m.next = "Idle"
230
231 wdata_converter = stream.StrideConverter(
232 port_from.wdata.description,
233 port_to.wdata.description,
234 reverse=reverse)
235 m.submodules += wdata_converter
236 m.submodules += stream.Pipeline(
237 port_from.wdata,
238 wdata_converter,
239 port_to.wdata)
240
241 return m
242
243 # LiteDRAMNativeReadPortUpConverter ----------------------------------------------------------------
244
245
246 class gramNativeReadPortUpConverter(Elaboratable):
247 """LiteDRAM port UpConverter
248
249 This module increase user port data width to fit controller data width.
250 With N = port_to.data_width/port_from.data_width:
251 - Address is adapted (divided by N)
252 - N read from user are regrouped in a single one to the controller
253 (when possible, ie when consecutive and bursting)
254 """
255
256 def __init__(self, port_from, port_to, reverse=False):
257 assert port_from.clock_domain == port_to.clock_domain
258 assert port_from.data_width < port_to.data_width
259 assert port_from.mode == port_to.mode
260 assert port_from.mode == "read"
261 if port_to.data_width % port_from.data_width:
262 raise ValueError("Ratio must be an int")
263
264 self._port_from = port_from
265 self._port_to = port_to
266 self._reverse = reverse
267
268 def elaborate(self, platform):
269 m = Module()
270
271 port_from = self._port_from
272 port_to = self._port_to
273 reverse = self._reverse
274
275 ratio = port_to.data_width//port_from.data_width
276
277 # Command ----------------------------------------------------------------------------------
278
279 cmd_buffer = stream.SyncFIFO([("sel", ratio)], 4)
280 m.submodules += cmd_buffer
281
282 counter = Signal(range(ratio))
283 counter_ce = Signal()
284 with m.If(counter_ce):
285 m.d.sync += counter.eq(counter+1)
286
287 with m.If(port_from.cmd.valid):
288 with m.If(counter == 0):
289 m.d.comb += [
290 port_to.cmd.valid.eq(1),
291 port_to.cmd.addr.eq(port_from.cmd.addr[log2_int(ratio):]),
292 port_from.cmd.ready.eq(port_to.cmd.ready),
293 counter_ce.eq(port_to.cmd.ready),
294 ]
295 with m.Else():
296 m.d.comb += [
297 port_from.cmd.ready.eq(1),
298 counter_ce.eq(1),
299 ]
300
301 # TODO: fix sel
302 with m.If(port_to.cmd.valid & port_to.cmd.ready):
303 m.d.comb += [
304 cmd_buffer.sink.valid.eq(1),
305 cmd_buffer.sink.sel.eq(2**ratio-1),
306 ]
307
308 # Datapath ---------------------------------------------------------------------------------
309
310 rdata_buffer = stream.Buffer(port_to.rdata.description)
311 rdata_converter = stream.StrideConverter(
312 port_to.rdata.description,
313 port_from.rdata.description,
314 reverse=reverse)
315 m.submodules += rdata_buffer, rdata_converter
316
317 rdata_chunk = Signal(ratio, reset=1)
318 rdata_chunk_valid = Signal()
319 with m.If(rdata_converter.source.valid & rdata_converter.source.ready):
320 m.d.sync += rdata_chunk.eq(
321 Cat(rdata_chunk[ratio-1], rdata_chunk[:ratio-1]))
322
323 m.d.comb += [
324 port_to.rdata.connect(rdata_buffer.sink),
325 rdata_buffer.source.connect(rdata_converter.sink),
326 rdata_chunk_valid.eq((cmd_buffer.source.sel & rdata_chunk) != 0),
327 cmd_buffer.source.ready.eq(
328 rdata_converter.source.ready & rdata_chunk[ratio-1]),
329 ]
330
331 with m.If(port_from.flush):
332 m.d.comb += rdata_converter.source.ready.eq(1)
333 with m.Elif(cmd_buffer.source.valid):
334 with m.If(rdata_chunk_valid):
335 m.d.comb += [
336 port_from.rdata.valid.eq(rdata_converter.source.valid),
337 port_from.rdata.data.eq(rdata_converter.source.data),
338 rdata_converter.source.ready.eq(port_from.rdata.ready),
339 ]
340 with m.Else():
341 m.d.comb += rdata_converter.source.ready.eq(1)
342
343 return m
344
345 # LiteDRAMNativePortConverter ----------------------------------------------------------------------
346
347
348 class gramNativePortConverter(Elaboratable):
349 def __init__(self, port_from, port_to, reverse=False):
350 assert port_from.clock_domain == port_to.clock_domain
351 assert port_from.mode == port_to.mode
352
353 self._port_from = port_from
354 self._port_to = port_to
355 self._reverse = reverse
356
357 def elaborate(self, platform):
358 m = Module()
359
360 port_from = self._port_from
361 port_to = self._port_to
362 reverse = self._reverse
363
364 mode = port_from.mode
365
366 if port_from.data_width > port_to.data_width:
367 converter = gramNativePortDownConverter(
368 port_from, port_to, reverse)
369 m.submodules += converter
370 elif port_from.data_width < port_to.data_width:
371 if mode == "write":
372 converter = gramNativeWritePortUpConverter(
373 port_from, port_to, reverse)
374 elif mode == "read":
375 converter = gramNativeReadPortUpConverter(
376 port_from, port_to, reverse)
377 else:
378 raise NotImplementedError
379 m.submodules += converter
380 else:
381 m.d.comb += [
382 port_from.cmd.connect(port_to.cmd),
383 port_from.wdata.connect(port_to.wdata),
384 port_to.rdata.connect(port_from.rdata)
385 ]
386
387 return m