Skip to content

Commit 54b33f8

Browse files
Merge pull request #385 from enjoy-digital/fix-upconverter-subword-ordering
frontend: preserve upconverter subword ordering
2 parents 5b469c5 + 9d1fa03 commit 54b33f8

2 files changed

Lines changed: 174 additions & 94 deletions

File tree

litedram/frontend/adapter.py

Lines changed: 114 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -150,16 +150,24 @@ def __init__(self, port_from, port_to, reverse=False):
150150

151151
# # #
152152

153-
ratio = port_to.data_width//port_from.data_width
154-
mode = port_from.mode
153+
ratio = port_to.data_width//port_from.data_width
154+
mode = port_from.mode
155+
chunk_bits = log2_int(ratio)
155156

156157
# Command ----------------------------------------------------------------------------------
157158

158-
# Defines cmd type and the chunks that have been requested for the current port_to command.
159-
sel = Signal(ratio)
159+
# Store the subword request order for the current port_to command.
160+
# This preserves command order for reads and maps write data back to address lanes.
161+
cmd_count = Signal(max=ratio + 1)
162+
cmd_order = Signal(ratio*chunk_bits)
163+
cmd_chunks = Signal(ratio)
160164
cmd_sel = Signal(ratio)
161165
cmd_selected = Signal()
162-
cmd_buffer = stream.SyncFIFO([("sel", ratio), ("we", 1)], 0)
166+
cmd_buffer = stream.SyncFIFO([
167+
("we", 1),
168+
("count", len(cmd_count)),
169+
("order", len(cmd_order)),
170+
], 0)
163171
self.submodules += cmd_buffer
164172
# Store last received command.
165173
cmd_addr = Signal.like(port_from.cmd.addr)
@@ -168,7 +176,7 @@ def __init__(self, port_from, port_to, reverse=False):
168176
# Indicates that we need to proceed to the next port_to command.
169177
next_cmd = Signal()
170178
addr_changed = Signal()
171-
# Signals that indicate that write/read convertion has finished.
179+
# Signals that indicate that write/read conversion has finished.
172180
wdata_finished = Signal()
173181
rdata_finished = Signal()
174182
# Used to prevent reading old memory value if previous command has written the same address.
@@ -188,7 +196,9 @@ def __init__(self, port_from, port_to, reverse=False):
188196
NextValue(cmd_addr, port_from.cmd.addr),
189197
NextValue(cmd_we, port_from.cmd.we),
190198
NextValue(cmd_last, port_from.cmd.last),
191-
NextValue(sel, cmd_sel),
199+
NextValue(cmd_count, 1),
200+
NextValue(cmd_order[:chunk_bits], port_from.cmd.addr[:chunk_bits]),
201+
NextValue(cmd_chunks, cmd_sel),
192202
If(port_from.cmd.we,
193203
NextState("FILL"),
194204
).Else(
@@ -199,7 +209,7 @@ def __init__(self, port_from, port_to, reverse=False):
199209
fsm.act("CMD",
200210
port_to.cmd.valid.eq(1),
201211
port_to.cmd.we.eq(cmd_we),
202-
port_to.cmd.addr.eq(cmd_addr[log2_int(ratio):]),
212+
port_to.cmd.addr.eq(cmd_addr[chunk_bits:]),
203213
If(port_to.cmd.ready,
204214
If(cmd_we,
205215
NextState("NEW")
@@ -208,21 +218,30 @@ def __init__(self, port_from, port_to, reverse=False):
208218
)
209219
)
210220
)
221+
cmd_order_cases = {}
222+
for i in range(ratio):
223+
cmd_order_cases[i] = NextValue(
224+
cmd_order[i*chunk_bits:(i + 1)*chunk_bits],
225+
port_from.cmd.addr[:chunk_bits])
226+
211227
fsm.act("FILL",
212228
If(next_cmd,
213229
NextState("COMMIT")
214-
).Else( # Acknowledge incomming commands, while filling `sel`.
230+
).Else( # Acknowledge incoming commands, while filling the request order.
215231
port_from.cmd.ready.eq(port_from.cmd.valid),
216232
NextValue(cmd_last, port_from.cmd.last),
217233
If(port_from.cmd.valid,
218-
NextValue(sel, sel | cmd_sel)
234+
NextValue(cmd_count, cmd_count + 1),
235+
Case(cmd_count, cmd_order_cases),
236+
NextValue(cmd_chunks, cmd_chunks | cmd_sel)
219237
)
220238
)
221239
)
222240
fsm.act("COMMIT",
223241
cmd_buffer.sink.valid.eq(1),
224-
cmd_buffer.sink.sel.eq(sel),
225242
cmd_buffer.sink.we.eq(cmd_we),
243+
cmd_buffer.sink.count.eq(cmd_count),
244+
cmd_buffer.sink.order.eq(cmd_order),
226245
If(cmd_buffer.sink.ready,
227246
If(cmd_we,
228247
NextState("CMD")
@@ -234,9 +253,9 @@ def __init__(self, port_from, port_to, reverse=False):
234253

235254
self.comb += [
236255
cmd_sel.eq(1 << port_from.cmd.addr[:log2_int(ratio)]),
237-
cmd_selected.eq((sel & cmd_sel) != 0),
256+
cmd_selected.eq((cmd_chunks & cmd_sel) != 0),
238257
cmd_buffer.source.ready.eq(wdata_finished | rdata_finished),
239-
addr_changed.eq(cmd_addr[log2_int(ratio):] != port_from.cmd.addr[log2_int(ratio):]),
258+
addr_changed.eq(cmd_addr[chunk_bits:] != port_from.cmd.addr[chunk_bits:]),
240259
# Collision happens on write to read transition when address does not change.
241260
rw_collision.eq(cmd_we & (port_from.cmd.valid & ~port_from.cmd.we) & ~addr_changed),
242261
# Go to the next command if one of the following happens:
@@ -248,7 +267,7 @@ def __init__(self, port_from, port_to, reverse=False):
248267
# - master requests a flush (even after the command has been sent).
249268
next_cmd.eq(addr_changed | (cmd_we != port_from.cmd.we)
250269
| (port_from.cmd.valid & cmd_selected)
251-
| (sel == 2**ratio - 1) | cmd_last | port_from.flush),
270+
| (cmd_count == ratio) | cmd_last | port_from.flush),
252271
]
253272

254273
self.sync += [
@@ -267,102 +286,103 @@ def __init__(self, port_from, port_to, reverse=False):
267286
# Read Datapath ----------------------------------------------------------------------------
268287

269288
if mode in ["read", "both"]:
270-
# Queue received data not to loose it when it comes too fast.
289+
# Queue received data not to lose it when it comes too fast.
271290
rdata_fifo = stream.SyncFIFO(port_to.rdata.description, ratio - 1)
272-
rdata_converter = stream.StrideConverter(
273-
description_from = port_to.rdata.description,
274-
description_to = port_from.rdata.description,
275-
reverse = reverse)
276-
self.submodules += rdata_fifo, rdata_converter
277-
278-
# Shift register with a bitmask of current chunk.
279-
rdata_chunk = Signal(ratio, reset=1)
280-
rdata_chunk_valid = Signal()
281-
self.sync += \
282-
If(rdata_converter.source.valid &
283-
rdata_converter.source.ready,
284-
rdata_chunk.eq(Cat(rdata_chunk[ratio-1], rdata_chunk[:ratio-1]))
285-
)
291+
self.submodules += rdata_fifo
292+
293+
rdata_count = Signal(max=ratio)
294+
rdata_chunk = Signal(chunk_bits)
295+
rdata_valid = Signal()
296+
297+
rdata_order_cases = {}
298+
rdata_mux_cases = {}
299+
for i in range(ratio):
300+
n = ratio - 1 - i if reverse else i
301+
rdata_order_cases[i] = rdata_chunk.eq(
302+
cmd_buffer.source.order[i*chunk_bits:(i + 1)*chunk_bits])
303+
rdata_mux_cases[i] = port_from.rdata.data.eq(
304+
rdata_fifo.source.data[
305+
n*port_from.data_width:(n + 1)*port_from.data_width])
286306

287307
self.comb += [
288-
# port_to -> rdata_fifo -> rdata_converter -> port_from
308+
# port_to -> rdata_fifo -> order mux -> port_from
289309
port_to.rdata.connect(rdata_fifo.sink),
290-
rdata_fifo.source.connect(rdata_converter.sink),
291-
rdata_chunk_valid.eq((cmd_buffer.source.sel & rdata_chunk) != 0),
292-
If(cmd_buffer.source.valid & ~cmd_buffer.source.we,
293-
# If that chunk is valid we send it to the user port and wait for ready.
294-
If(rdata_chunk_valid,
295-
port_from.rdata.valid.eq(rdata_converter.source.valid),
296-
port_from.rdata.data.eq(rdata_converter.source.data),
297-
rdata_converter.source.ready.eq(port_from.rdata.ready)
298-
).Else( # If this chunk was not requested in `sel`, ignore it.
299-
rdata_converter.source.ready.eq(1)
300-
),
301-
rdata_finished.eq(rdata_converter.source.valid & rdata_converter.source.ready
302-
& rdata_chunk[ratio - 1])
303-
),
310+
Case(rdata_count, rdata_order_cases),
311+
Case(rdata_chunk, rdata_mux_cases),
312+
rdata_valid.eq(cmd_buffer.source.valid & ~cmd_buffer.source.we & rdata_fifo.source.valid),
313+
port_from.rdata.valid.eq(rdata_valid),
314+
rdata_fifo.source.ready.eq(rdata_finished),
315+
rdata_finished.eq(rdata_valid & port_from.rdata.ready
316+
& (rdata_count == (cmd_buffer.source.count - 1))),
317+
]
318+
self.sync += [
319+
If(rdata_finished,
320+
rdata_count.eq(0)
321+
).Elif(rdata_valid & port_from.rdata.ready,
322+
rdata_count.eq(rdata_count + 1)
323+
)
304324
]
305325

306326
# Write Datapath ---------------------------------------------------------------------------
307327

308328
if mode in ["write", "both"]:
309-
# Queue write data not to miss it when the lower chunks haven't been reqested.
329+
# Queue write data not to miss it when the lower chunks haven't been requested.
310330
wdata_fifo = stream.SyncFIFO(port_from.wdata.description, ratio - 1)
311331
wdata_buffer = stream.SyncFIFO(port_to.wdata.description, 1)
312-
wdata_converter = stream.StrideConverter(
313-
description_from = port_from.wdata.description,
314-
description_to = port_to.wdata.description,
315-
reverse = reverse)
316-
self.submodules += wdata_converter, wdata_fifo, wdata_buffer
317-
318-
# Shift register with a bitmask of current chunk.
319-
wdata_chunk = Signal(ratio, reset=1)
320-
wdata_chunk_valid = Signal()
321-
self.sync += \
322-
If(wdata_converter.sink.valid & wdata_converter.sink.ready,
323-
wdata_chunk.eq(Cat(wdata_chunk[ratio-1], wdata_chunk[:ratio-1]))
324-
)
325-
326-
# Replicate `sel` bits to match the width of port_to.wdata.we.
327-
wdata_sel = Signal.like(port_to.wdata.we)
328-
if reverse:
329-
wdata_sel_parts = [
330-
Replicate(cmd_buffer.source.sel[i], port_to.wdata.we.nbits // sel.nbits)
331-
for i in reversed(range(ratio))
332-
]
333-
else:
334-
wdata_sel_parts = [
335-
Replicate(cmd_buffer.source.sel[i], port_to.wdata.we.nbits // sel.nbits)
336-
for i in range(ratio)
332+
self.submodules += wdata_fifo, wdata_buffer
333+
334+
wdata_count = Signal(max=ratio)
335+
wdata_chunk = Signal(chunk_bits)
336+
wdata_data = Signal.like(port_to.wdata.data)
337+
wdata_we = Signal.like(port_to.wdata.we)
338+
wdata_pending = Signal()
339+
wdata_accept = Signal()
340+
wdata_last = Signal()
341+
342+
wdata_order_cases = {}
343+
wdata_store_cases = {}
344+
for i in range(ratio):
345+
n = ratio - 1 - i if reverse else i
346+
wdata_order_cases[i] = wdata_chunk.eq(
347+
cmd_buffer.source.order[i*chunk_bits:(i + 1)*chunk_bits])
348+
wdata_store_cases[i] = [
349+
wdata_data[
350+
n*port_from.data_width:(n + 1)*port_from.data_width
351+
].eq(wdata_fifo.source.data),
352+
wdata_we[
353+
n*port_from.wdata.we.nbits:(n + 1)*port_from.wdata.we.nbits
354+
].eq(wdata_fifo.source.we),
337355
]
338356

339-
self.sync += \
340-
If(cmd_buffer.source.valid & cmd_buffer.source.we & wdata_chunk[ratio - 1],
341-
wdata_sel.eq(Cat(wdata_sel_parts))
342-
)
343-
344357
self.comb += [
345-
# port_from -> wdata_fifo -> wdata_converter
358+
# port_from -> wdata_fifo -> ordered wide buffer -> port_to
346359
port_from.wdata.connect(wdata_fifo.sink),
347360
wdata_buffer.source.connect(port_to.wdata),
348-
wdata_chunk_valid.eq((cmd_buffer.source.sel & wdata_chunk) != 0),
349-
If(cmd_buffer.source.valid & cmd_buffer.source.we,
350-
# When the current chunk is valid, read it from wdata_fifo.
351-
If(wdata_chunk_valid,
352-
wdata_converter.sink.valid.eq(wdata_fifo.source.valid),
353-
wdata_converter.sink.data.eq(wdata_fifo.source.data),
354-
wdata_converter.sink.we.eq(wdata_fifo.source.we),
355-
wdata_fifo.source.ready.eq(wdata_converter.sink.ready),
356-
).Else( # If chunk is not valid, send any data and do not advance fifo.
357-
wdata_converter.sink.valid.eq(1),
358-
),
359-
),
360-
wdata_buffer.sink.valid.eq(wdata_converter.source.valid),
361-
wdata_buffer.sink.data.eq(wdata_converter.source.data),
362-
wdata_buffer.sink.we.eq(wdata_converter.source.we & wdata_sel),
363-
wdata_converter.source.ready.eq(wdata_buffer.sink.ready),
364-
wdata_finished.eq(wdata_converter.sink.valid & wdata_converter.sink.ready
365-
& wdata_chunk[ratio-1]),
361+
wdata_buffer.sink.valid.eq(wdata_pending),
362+
wdata_buffer.sink.data.eq(wdata_data),
363+
wdata_buffer.sink.we.eq(wdata_we),
364+
Case(wdata_count, wdata_order_cases),
365+
wdata_fifo.source.ready.eq(cmd_buffer.source.valid & cmd_buffer.source.we
366+
& ~wdata_pending),
367+
wdata_accept.eq(wdata_fifo.source.valid & wdata_fifo.source.ready),
368+
wdata_last.eq(wdata_count == (cmd_buffer.source.count - 1)),
369+
wdata_finished.eq(wdata_buffer.sink.valid & wdata_buffer.sink.ready),
370+
]
371+
372+
self.sync += [
373+
If(wdata_finished,
374+
wdata_count.eq(0),
375+
wdata_data.eq(0),
376+
wdata_we.eq(0),
377+
wdata_pending.eq(0),
378+
).Elif(wdata_accept,
379+
Case(wdata_chunk, wdata_store_cases),
380+
If(wdata_last,
381+
wdata_pending.eq(1)
382+
).Else(
383+
wdata_count.eq(wdata_count + 1)
384+
)
385+
)
366386
]
367387

368388
# LiteDRAMNativePortConverter ----------------------------------------------------------------------

test/test_adapter.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,66 @@ def main_generator(dut):
364364
self.converter_readback_test(dut, pattern=[], mem_expected=mem_expected,
365365
main_generator=main_generator)
366366

367+
def test_up_converter_write_ordering(self):
368+
# Verify that write data is placed according to subword addresses, not command order.
369+
pattern = [
370+
(0x03, 0x00),
371+
(0x02, 0x11),
372+
(0x01, 0x22),
373+
(0x00, 0x33),
374+
(0x12, 0x44),
375+
(0x11, 0x55),
376+
(0x13, 0x66),
377+
(0x10, 0x77),
378+
]
379+
mem_expected = [
380+
# data, address
381+
0x00112233, # 0x00
382+
0x00000000, # 0x04
383+
0x00000000, # 0x08
384+
0x00000000, # 0x0c
385+
0x66445577, # 0x10
386+
0x00000000, # 0x14
387+
0x00000000, # 0x18
388+
0x00000000, # 0x1c
389+
]
390+
391+
for separate_rw in [True, False]:
392+
with self.subTest(separate_rw=separate_rw):
393+
dut = ConverterDUT(user_data_width=8, native_data_width=32,
394+
mem_depth=len(mem_expected), separate_rw=separate_rw)
395+
self.converter_readback_test(dut, pattern=pattern, mem_expected=mem_expected)
396+
397+
def test_up_converter_read_ordering(self):
398+
# Verify that read data is returned in command order, not ascending subword order.
399+
pattern = [
400+
(0x03, 0x33),
401+
(0x02, 0x22),
402+
(0x01, 0x11),
403+
(0x00, 0x00),
404+
]
405+
mem_expected = [
406+
# data, address
407+
0x33221100, # 0x00
408+
0x00000000, # 0x04
409+
0x00000000, # 0x08
410+
0x00000000, # 0x0c
411+
]
412+
413+
def main_generator(dut):
414+
for adr, _ in pattern[:-1]:
415+
yield from dut.read(adr, wait_data=False)
416+
yield from dut.read(pattern[-1][0], wait_data=False, last=1)
417+
yield from dut.read_driver.wait_all()
418+
419+
for separate_rw in [True, False]:
420+
with self.subTest(separate_rw=separate_rw):
421+
dut = ConverterDUT(user_data_width=8, native_data_width=32,
422+
mem_depth=len(mem_expected), separate_rw=separate_rw)
423+
dut.memory.mem = mem_expected.copy()
424+
self.converter_readback_test(dut, pattern=pattern, mem_expected=mem_expected,
425+
main_generator=main_generator)
426+
367427
def test_up_converter_not_aligned(self):
368428
data = self.pattern_test_data["8bit_to_32bit_not_aligned"]
369429
dut = ConverterDUT(user_data_width=8, native_data_width=32,

0 commit comments

Comments
 (0)