litedram/frontend/adapter.py rewrite up converter to optimize for bandwidth

This change is necessary to run litevideo, as old up converter was too slow to support
high bandwidth requirements of HDMI core.
Also old upconverter had two bugs:
* reading sequentially and non-sequentially would return data in the same order
* writing sequentailly and non-sequentially would return different memory state

test/test_adapter.py add new up converter test: test_up_converter_writes_not_sequential
This test checks if non-sequential writes to one dram word will create the same result as sequential writes
Add parameters for rx_buffer, tx_buffer and cmd_buffer depths

Signed-off-by: Maciej Dudek <mdudek@antmicro.com>
This commit is contained in:
Maciej Dudek 2020-12-22 19:22:11 +01:00 committed by Robert Szczepanski
parent 3d066c87f9
commit 7b3c6abd50
4 changed files with 407 additions and 168 deletions

View File

@ -76,7 +76,8 @@ class LiteDRAMCrossbar(Module):
self.masters = [] self.masters = []
def get_port(self, mode="both", data_width=None, clock_domain="sys", reverse=False): def get_port(self, mode="both", data_width=None, clock_domain="sys", reverse=False,
rx_buffer_depth=4, tx_buffer_depth=4, cmd_buffer_depth=4, priority=0):
if self.finalized: if self.finalized:
raise FinalizeError raise FinalizeError
@ -117,7 +118,8 @@ class LiteDRAMCrossbar(Module):
clock_domain = clock_domain, clock_domain = clock_domain,
id = port.id) id = port.id)
self.submodules += ClockDomainsRenamer(clock_domain)( self.submodules += ClockDomainsRenamer(clock_domain)(
LiteDRAMNativePortConverter(new_port, port, reverse)) LiteDRAMNativePortConverter(new_port, port, reverse,
rx_buffer_depth, tx_buffer_depth, cmd_buffer_depth))
port = new_port port = new_port
return port return port

View File

@ -81,6 +81,7 @@ class LiteDRAMNativePortDownConverter(Module):
ratio = port_from.data_width//port_to.data_width ratio = port_from.data_width//port_to.data_width
mode = port_from.mode mode = port_from.mode
count = Signal(max=ratio) count = Signal(max=ratio)
self.submodules.fsm = fsm = FSM(reset_state="IDLE") self.submodules.fsm = fsm = FSM(reset_state="IDLE")
@ -96,7 +97,7 @@ class LiteDRAMNativePortDownConverter(Module):
port_to.cmd.addr.eq(port_from.cmd.addr*ratio + count), port_to.cmd.addr.eq(port_from.cmd.addr*ratio + count),
If(port_to.cmd.ready, If(port_to.cmd.ready,
NextValue(count, count + 1), NextValue(count, count + 1),
If(count == (ratio - 1), If(count == ratio - 1,
port_from.cmd.ready.eq(1), port_from.cmd.ready.eq(1),
NextState("IDLE") NextState("IDLE")
) )
@ -136,7 +137,8 @@ class LiteDRAMNativePortUpConverter(Module):
middle of a burst, but last command has to use cmd.last=1 if the last burst middle of a burst, but last command has to use cmd.last=1 if the last burst
is not complete (not all N addresses have been used). is not complete (not all N addresses have been used).
""" """
def __init__(self, port_from, port_to, reverse=False): def __init__(self, port_from, port_to, reverse=False,
rx_buffer_depth=4, tx_buffer_depth=4, cmd_buffer_depth=4):
assert port_from.clock_domain == port_to.clock_domain assert port_from.clock_domain == port_to.clock_domain
assert port_from.data_width < port_to.data_width assert port_from.data_width < port_to.data_width
assert port_from.mode == port_to.mode assert port_from.mode == port_to.mode
@ -145,219 +147,419 @@ class LiteDRAMNativePortUpConverter(Module):
# # # # # #
ratio = port_to.data_width//port_from.data_width self.ratio = ratio = port_to.data_width//port_from.data_width
mode = port_from.mode mode = port_from.mode
# Command ---------------------------------------------------------------------------------- # Command ----------------------------------------------------------------------------------
# Defines cmd type and the chunks that have been requested for the current port_to command. # Defines read/write ordering of chunks that have been requested
sel = Signal(ratio) ordering_layout = [
cmd_buffer = stream.SyncFIFO([("sel", ratio), ("we", 1)], 0) ("counter", log2_int(ratio) + 1),
self.submodules += cmd_buffer ("ordering", ratio * log2_int(ratio))
# Store last received command. ]
cmd_addr = Signal.like(port_from.cmd.addr) self.rx_buffer = rx_buffer = stream.SyncFIFO(ordering_layout, rx_buffer_depth)
self.tx_buffer = tx_buffer = stream.SyncFIFO(ordering_layout, tx_buffer_depth)
self.submodules += rx_buffer, tx_buffer
# Store last received command
cmd_addr = Signal.like(port_to.cmd.addr)
cmd_we = Signal() cmd_we = Signal()
cmd_last = Signal() cmd_last = Signal()
# Indicates that we need to proceed to the next port_to command. # Indicates that we need to proceed to the next port_to command
next_cmd = Signal() cmd_finished = Signal()
addr_changed = Signal() addr_changed = Signal()
# Signals that indicate that write/read convertion has finished. # Signals that indicate that write/read convertion has finished
wdata_finished = Signal() wdata_finished = Signal()
rdata_finished = Signal() rdata_finished = Signal()
# Used to prevent reading old memory value if previous command has written the same address.
read_lock = Signal()
read_unlocked = Signal()
rw_collision = Signal()
# Different order depending on read/write: # Used to keep access order for reads and writes
# - read: new -> cmd -> fill -> commit -> new counter = Signal(log2_int(ratio) + 1)
# - write: new -> fill -> commit -> cmd -> new ordering = Signal(ratio * log2_int(ratio))
# For writes we have to send the command at the end to prevent situations when, during
# a burst, LiteDRAM expects data (wdata_ready=1) but write converter is still converting. # Send Command -----------------------------------------------------------------------------
self.submodules.fsm = fsm = FSM()
fsm.act("NEW", self.send_cmd = send_cmd = Signal()
port_from.cmd.ready.eq(port_from.cmd.valid & ~read_lock), self.send_cmd_addr = send_cmd_addr = Signal.like(port_to.cmd.addr)
If(port_from.cmd.ready, self.send_cmd_we = send_cmd_we = Signal.like(port_to.cmd.we)
NextValue(cmd_addr, port_from.cmd.addr), self.send_cmd_busy = send_cmd_busy = Signal()
NextValue(cmd_we, port_from.cmd.we),
NextValue(cmd_last, port_from.cmd.last), send_inner_cmd_addr = Signal.like(port_to.cmd.addr)
NextValue(sel, 1 << port_from.cmd.addr[:log2_int(ratio)]), send_inner_cmd_we = Signal.like(port_to.cmd.we)
If(port_from.cmd.we,
NextState("FILL"), self.cmd_buffer = cmd_buffer = stream.SyncFIFO([ ("cmd_addr", send_cmd_addr.nbits),
).Else( ("cmd_we", send_cmd_we.nbits)],
NextState("CMD"), cmd_buffer_depth)
self.submodules += cmd_buffer
self.comb += [
send_cmd_busy.eq(~cmd_buffer.sink.ready),
cmd_buffer.sink.valid.eq(send_cmd),
cmd_buffer.sink.cmd_addr.eq(send_cmd_addr),
cmd_buffer.sink.cmd_we.eq(send_cmd_we)
]
self.submodules.sender = send_fsm = FSM(reset_state="IDLE")
send_fsm.act("IDLE",
If(cmd_buffer.source.valid,
cmd_buffer.source.ready.eq(1),
NextValue(send_inner_cmd_addr, cmd_buffer.source.cmd_addr),
NextValue(send_inner_cmd_we, cmd_buffer.source.cmd_we),
NextState("SEND")
) )
) )
) send_fsm.act("SEND",
fsm.act("CMD",
port_to.cmd.valid.eq(1), port_to.cmd.valid.eq(1),
port_to.cmd.we.eq(cmd_we), port_to.cmd.addr.eq(send_inner_cmd_addr),
port_to.cmd.addr.eq(cmd_addr[log2_int(ratio):]), port_to.cmd.we.eq(send_inner_cmd_we),
If(port_to.cmd.ready, If(port_to.cmd.ready,
If(cmd_we, If(cmd_buffer.source.valid,
NextState("NEW") cmd_buffer.source.ready.eq(1),
NextValue(send_inner_cmd_addr, cmd_buffer.source.cmd_addr),
NextValue(send_inner_cmd_we, cmd_buffer.source.cmd_we),
NextState("SEND")
).Else(
NextState("IDLE")
)
)
)
# Command flow is quite complicate, nonlinear and it depends on type read/write,
# so here is summary:
# This FSM receives commands from `port_from` and pushes them to `cmd_buffer` queue,
# which is then handled by the `send_fsm` which sends commands to `port_to`.
# In the FILL phase we gather the requested ordering of data chunks that map to a single
# `port_to` transaction. The order of states in the FSM depends on command type: read
# commands are queued on reception, write commands after the FILL phase.
# Data order is also queued after FILL phase, at that point we know right order
# Longer version
# WAIT-FOR-CMD:
# - if there is new cmd available do
# - set internal variable
# - check if it's read or write
# - write: go to FILL
# - read: try to send (queue) read cmd
# - if successful goto FILL
# - else goto WAIT-TO-SEND
#
# WAIT-TO-SEND:
# - set cmd addr and cmd we to be sent (queued)
# - if we can send (queue)
# - if cmd was read goto FILL
# - else
# - if there is new cmd waiting and it's write goto FILL
# - else goto WAIT-FOR-CMD
# - else goto WAIT-TO-SEND
#
# FILL:
# - if cmd finished
# - if cmd was write do tx_commit
# - else do rx_commit
# - else
# - acknowledged incoming cmds
# - store their relative addresses (which subword of DRAM word)
#
# tx_commit(not a state, just combinational logic):
# - try to store data ordering in tx_buffer
# - if successful
# - try to send (queue) cmd:
# - if successful
# - if there is new cmd and it's write
# - set internal variables as in WAIT-FOR-CMD
# - goto FILL
# -else goto WAIT-FOR-CMD
# - else goto WAIT_TO_SEND
# - else goto WAIT-FOR-SPACE-IN-TX_BUFFER
#
# rx_commit(not a state, just combinational logic):
# - try to store data ordering in rx_buffer
# - if successful
# - if there is new cmd and it's read
# - set internal variables as in WAIT-FOR-CMD
# - try to send (queue) cmd
# - if successful
# - acknowledge cmd
# - store their relative address
# - goto FILL
# - else goto WAIT-TO-SEND
# - else goto WAIT-FOR-CMD
# - else goto WAIT-FOR-SPACE-IN-RX_BUFFER
#
# WAIT-FOR-SPACE-IN-TX_BUFFER:
# - tx_commit
#
# WAIT-FOR-SPACE-IN-RX_BUFFER:
# - rx_commit
self.submodules.fsm = fsm = FSM(reset_state="WAIT-FOR-CMD")
fsm.act("WAIT-FOR-CMD",
If(port_from.cmd.valid,
NextValue(counter, 0),
NextValue(cmd_addr, port_from.cmd.addr[log2_int(ratio):]),
NextValue(cmd_we, port_from.cmd.we),
If(port_from.cmd.we,
NextState("FILL")
).Else(
self.send_cmd.eq(1),
self.send_cmd_addr.eq(port_from.cmd.addr[log2_int(ratio):]),
self.send_cmd_we.eq(port_from.cmd.we),
If(self.send_cmd_busy,
NextState("WAIT-TO-SEND")
).Else( ).Else(
NextState("FILL") NextState("FILL")
) )
) )
) )
fsm.act("FILL",
If(next_cmd,
NextState("COMMIT")
).Else( # Acknowledge incomming commands, while filling `sel`.
port_from.cmd.ready.eq(port_from.cmd.valid),
NextValue(cmd_last, port_from.cmd.last),
If(port_from.cmd.valid,
NextValue(sel, sel | 1 << port_from.cmd.addr[:log2_int(ratio)])
) )
) fsm.act("WAIT-TO-SEND",
) send_cmd.eq(1),
fsm.act("COMMIT", send_cmd_addr.eq(cmd_addr),
cmd_buffer.sink.valid.eq(1), send_cmd_we.eq(cmd_we),
cmd_buffer.sink.sel.eq(sel), If(~send_cmd_busy,
cmd_buffer.sink.we.eq(cmd_we),
If(cmd_buffer.sink.ready,
If(cmd_we, If(cmd_we,
NextState("CMD") If(port_from.cmd.valid & port_from.cmd.we,
NextValue(counter, 0),
NextValue(cmd_addr, port_from.cmd.addr[log2_int(ratio):]),
NextValue(cmd_we, port_from.cmd.we),
NextState("FILL")
).Else( ).Else(
NextState("NEW") NextState("WAIT-FOR-CMD")
)
).Else(
NextState("FILL")
) )
) )
) )
cases = {}
for i in range(ratio):
cases[i] = NextValue(ordering[i * log2_int(ratio) : (i + 1) * log2_int(ratio)],
port_from.cmd.addr[:log2_int(ratio)])
fsm.act("FILL",
If(cmd_finished,
If(cmd_we,
self.tx_commit(cmd_addr, cmd_we, cmd_last, port_from, counter, ordering)
).Else(
self.rx_commit(cmd_addr, cmd_we, cmd_last, port_from, counter, ordering)
)
).Else(
port_from.cmd.ready.eq(1),
If(port_from.cmd.valid,
NextValue(cmd_last, port_from.cmd.last),
NextValue(counter, counter + 1),
Case(counter, cases),
)
)
)
fsm.act("WAIT-FOR-SPACE-IN-RX_BUFFER",
self.rx_commit(cmd_addr, cmd_we, cmd_last, port_from, counter, ordering)
)
fsm.act("WAIT-FOR-SPACE-IN-TX_BUFFER",
self.tx_commit(cmd_addr, cmd_we, cmd_last, port_from, counter, ordering)
)
self.comb += [ self.comb += [
cmd_buffer.source.ready.eq(wdata_finished | rdata_finished), tx_buffer.source.ready.eq(wdata_finished),
addr_changed.eq(cmd_addr[log2_int(ratio):] != port_from.cmd.addr[log2_int(ratio):]), rx_buffer.source.ready.eq(rdata_finished),
# Collision happens on write to read transition when address does not change. addr_changed.eq(cmd_addr != port_from.cmd.addr[log2_int(ratio):]),
rw_collision.eq(cmd_we & (port_from.cmd.valid & ~port_from.cmd.we) & ~addr_changed),
# Go to the next command if one of the following happens: # Go to the next command if one of the following happens:
# - port_to address changes. # - port_to address changes.
# - cmd type changes. # - cmd type changes.
# - we received all the `ratio` commands. # - we received all the `ratio` commands.
# - this is the last command in a sequence. # - this is the last command in a sequence.
# - master requests a flush (even after the command has been sent). # - master requests a flush (even after the command has been sent).
next_cmd.eq(addr_changed | (cmd_we != port_from.cmd.we) | (sel == 2**ratio - 1) cmd_finished.eq(addr_changed | (cmd_we != port_from.cmd.we) | (counter == ratio)
| cmd_last | port_from.flush), | cmd_last | port_from.flush),
] ]
self.sync += [
# Block sending read command if we have just written to that address
If(wdata_finished,
read_lock.eq(0),
read_unlocked.eq(1),
).Elif(rw_collision & ~port_to.cmd.valid & ~read_unlocked,
read_lock.eq(1)
),
If(port_from.cmd.valid & port_from.cmd.ready,
read_unlocked.eq(0)
)
]
# Read Datapath ---------------------------------------------------------------------------- # Read Datapath ----------------------------------------------------------------------------
if mode in ["read", "both"]: if mode in ["read", "both"]:
# Queue received data not to loose it when it comes too fast. read_upper_counter = Signal.like(counter)
rdata_fifo = stream.SyncFIFO(port_to.rdata.description, ratio - 1) read_inner_counter = Signal.like(counter)
rdata_converter = stream.StrideConverter( read_inner_ordering = Signal.like(ordering)
description_from = port_to.rdata.description,
description_to = port_from.rdata.description,
reverse = reverse)
self.submodules += rdata_fifo, rdata_converter
# Shift register with a bitmask of current chunk. read_chunk = Signal(log2_int(ratio))
rdata_chunk = Signal(ratio, reset=1)
rdata_chunk_valid = Signal() # Queue received data not to loose it when it comes too fast
self.sync += \ rdata_fifo = stream.SyncFIFO(port_to.rdata.description, cmd_buffer.depth + rx_buffer.depth + 1)
If(rdata_converter.source.valid & self.submodules += rdata_fifo
rdata_converter.source.ready,
rdata_chunk.eq(Cat(rdata_chunk[ratio-1], rdata_chunk[:ratio-1])) cases = {}
) for i in range(ratio):
n = ratio-i-1 if reverse else i
cases[i] = port_from.rdata.data.eq(rdata_fifo.source.data[
n * port_from.data_width :(n + 1) * port_from.data_width]),
self.comb += [ self.comb += [
# port_to -> rdata_fifo -> rdata_converter -> port_from
# Port_to -> rdata_fifo -> order_mux -> port_from
port_to.rdata.connect(rdata_fifo.sink), port_to.rdata.connect(rdata_fifo.sink),
rdata_fifo.source.connect(rdata_converter.sink), rdata_fifo.source.ready.eq(rdata_finished),
rdata_chunk_valid.eq((cmd_buffer.source.sel & rdata_chunk) != 0),
If(cmd_buffer.source.valid & ~cmd_buffer.source.we, If(rdata_fifo.source.valid & rx_buffer.source.valid,
# If that chunk is valid we send it to the user port and wait for ready. # If that chunk is valid we send it to the user port and wait for ready
If(rdata_chunk_valid, If(read_inner_counter < read_upper_counter,
port_from.rdata.valid.eq(rdata_converter.source.valid), port_from.rdata.valid.eq(1),
port_from.rdata.data.eq(rdata_converter.source.data), Case(read_chunk, cases),
rdata_converter.source.ready.eq(port_from.rdata.ready) )
).Else( # If this chunk was not requested in `sel`, ignore it. )
rdata_converter.source.ready.eq(1) ]
),
rdata_finished.eq(rdata_converter.source.valid & rdata_converter.source.ready cases = {}
& rdata_chunk[ratio - 1]) for i in range(ratio):
cases[i] = read_chunk.eq(read_inner_ordering[
i * log2_int(ratio) : (i + 1) * log2_int(ratio)]),
self.comb += [
# Select source of address order
If(rx_buffer.source.valid,
read_upper_counter.eq(rx_buffer.source.counter),
read_inner_ordering.eq(rx_buffer.source.ordering)
), ),
# Select read chunk
Case(read_inner_counter, cases),
rdata_finished.eq((read_inner_counter == read_upper_counter - 1) & rx_buffer.source.valid
& (port_from.rdata.valid & port_from.rdata.ready))
]
self.sync += [
If(rdata_finished,
read_inner_counter.eq(0)
).Elif(port_from.rdata.valid & port_from.rdata.ready &
(read_inner_counter < read_upper_counter),
read_inner_counter.eq(read_inner_counter + 1)
)
] ]
# Write Datapath --------------------------------------------------------------------------- # Write Datapath ---------------------------------------------------------------------------
if mode in ["write", "both"]: if mode in ["write", "both"]:
# Queue write data not to miss it when the lower chunks haven't been reqested. write_upper_counter = Signal.like(counter)
wdata_fifo = stream.SyncFIFO(port_from.wdata.description, ratio - 1) write_inner_counter = Signal.like(counter)
wdata_buffer = stream.SyncFIFO(port_to.wdata.description, 1) write_inner_ordering = Signal.like(ordering)
wdata_converter = stream.StrideConverter(
description_from = port_from.wdata.description,
description_to = port_to.wdata.description,
reverse = reverse)
self.submodules += wdata_converter, wdata_fifo, wdata_buffer
# Shift register with a bitmask of current chunk. write_chunk = Signal(log2_int(ratio))
wdata_chunk = Signal(ratio, reset=1)
wdata_chunk_valid = Signal()
self.sync += \
If(wdata_converter.sink.valid & wdata_converter.sink.ready,
wdata_chunk.eq(Cat(wdata_chunk[ratio-1], wdata_chunk[:ratio-1]))
)
# Replicate `sel` bits to match the width of port_to.wdata.we. # Queue write data not to miss it when the lower chunks haven't been reqested
wdata_sel = Signal.like(port_to.wdata.we) wdata_fifo = stream.SyncFIFO(port_from.wdata.description, ratio)
if reverse: wdata_buffer = Record([("data", port_to.wdata.data.nbits),
wdata_sel_parts = [ ("we", port_to.wdata.we.nbits)])
Replicate(cmd_buffer.source.sel[i], port_to.wdata.we.nbits // sel.nbits) self.submodules += wdata_fifo
for i in reversed(range(ratio))
]
else:
wdata_sel_parts = [
Replicate(cmd_buffer.source.sel[i], port_to.wdata.we.nbits // sel.nbits)
for i in range(ratio)
]
self.sync += \
If(cmd_buffer.source.valid & cmd_buffer.source.we & wdata_chunk[ratio - 1],
wdata_sel.eq(Cat(wdata_sel_parts))
)
self.comb += [ self.comb += [
# port_from -> wdata_fifo -> wdata_converter # port_from -> wdata_fifo -> wdata_buffer (keeps order)
port_from.wdata.connect(wdata_fifo.sink), port_from.wdata.connect(wdata_fifo.sink),
wdata_buffer.source.connect(port_to.wdata), port_to.wdata.data.eq(wdata_buffer.data),
wdata_chunk_valid.eq((cmd_buffer.source.sel & wdata_chunk) != 0), port_to.wdata.we.eq(wdata_buffer.we),
If(cmd_buffer.source.valid & cmd_buffer.source.we, port_to.wdata.valid.eq((write_inner_counter == write_upper_counter) & tx_buffer.source.valid),
# When the current chunk is valid, read it from wdata_fifo. wdata_fifo.source.ready.eq(write_inner_counter < write_upper_counter),
If(wdata_chunk_valid, ]
wdata_converter.sink.valid.eq(wdata_fifo.source.valid),
wdata_converter.sink.data.eq(wdata_fifo.source.data), cases = {}
wdata_converter.sink.we.eq(wdata_fifo.source.we), for i in range(ratio):
wdata_fifo.source.ready.eq(wdata_converter.sink.ready), cases[i] = write_chunk.eq(write_inner_ordering[
).Else( # If chunk is not valid, send any data and do not advance fifo. i * log2_int(ratio) : (i + 1) * log2_int(ratio)]),
wdata_converter.sink.valid.eq(1),
self.comb += [
# Select source of address order
If(tx_buffer.source.valid,
write_upper_counter.eq(tx_buffer.source.counter),
write_inner_ordering.eq(tx_buffer.source.ordering)
).Else(
write_upper_counter.eq(counter),
write_inner_ordering.eq(ordering)
), ),
Case(write_inner_counter, cases),
wdata_finished.eq(port_to.wdata.valid & port_to.wdata.ready),
]
cases = {}
for i in range(ratio):
n = ratio-i-1 if reverse else i
cases[i] = [
wdata_buffer.data[n * port_from.data_width : (n + 1) * port_from.data_width].eq(
wdata_fifo.source.data),
wdata_buffer.we[n * port_from.wdata.we.nbits : (n + 1) * port_from.wdata.we.nbits].eq(
wdata_fifo.source.we),
]
self.sync += [
If(wdata_fifo.source.valid & wdata_fifo.source.ready,
Case(write_chunk, cases)
), ),
wdata_buffer.sink.valid.eq(wdata_converter.source.valid), If(wdata_finished,
wdata_buffer.sink.data.eq(wdata_converter.source.data), write_inner_counter.eq(0),
wdata_buffer.sink.we.eq(wdata_converter.source.we & wdata_sel), wdata_buffer.we.eq(0),
wdata_converter.source.ready.eq(wdata_buffer.sink.ready), ).Elif(wdata_fifo.source.valid & wdata_fifo.source.ready,
wdata_finished.eq(wdata_converter.sink.valid & wdata_converter.sink.ready write_inner_counter.eq(write_inner_counter + 1)
& wdata_chunk[ratio-1]), )
]
def tx_commit (self, cmd_addr, cmd_we, cmd_last, port_from, counter, ordering):
return [
self.tx_buffer.sink.valid.eq(1),
self.tx_buffer.sink.counter.eq(counter),
self.tx_buffer.sink.ordering.eq(ordering),
If(self.tx_buffer.sink.ready,
self.send_cmd.eq(1),
self.send_cmd_addr.eq(cmd_addr),
self.send_cmd_we.eq(cmd_we),
If(self.send_cmd_busy,
NextState("WAIT-TO-SEND")
).Else(
If(port_from.cmd.valid & port_from.cmd.we,
NextValue(counter, 0),
NextValue(cmd_addr, port_from.cmd.addr[log2_int(self.ratio):]),
NextValue(cmd_we, port_from.cmd.we),
NextState("FILL")
).Else(
NextState("WAIT-FOR-CMD")
)
)
).Else(
NextState("WAIT-FOR-SPACE-IN-TX_BUFFER")
)
]
def rx_commit (self, cmd_addr, cmd_we, cmd_last, port_from, counter, ordering):
return [
self.rx_buffer.sink.valid.eq(1),
self.rx_buffer.sink.counter.eq(counter),
self.rx_buffer.sink.ordering.eq(ordering),
If(self.rx_buffer.sink.ready,
If(port_from.cmd.valid & ~port_from.cmd.we,
self.send_cmd.eq(1),
self.send_cmd_addr.eq(port_from.cmd.addr[log2_int(self.ratio):]),
self.send_cmd_we.eq(port_from.cmd.we),
NextValue(cmd_addr, port_from.cmd.addr[log2_int(self.ratio):]),
NextValue(cmd_we, port_from.cmd.we),
If(self.send_cmd_busy,
NextValue(counter, 0),
NextState("WAIT-TO-SEND")
).Else(
port_from.cmd.ready.eq(1),
NextValue(counter, 1),
NextValue(ordering[:1 * log2_int(self.ratio)],
port_from.cmd.addr[:log2_int(self.ratio)]),
NextValue(cmd_last, port_from.cmd.last),
NextState("FILL")
)
).Else(
NextState("WAIT-FOR-CMD")
)
).Else(
NextState("WAIT-FOR-SPACE-IN-RX_BUFFER")
)
] ]
# LiteDRAMNativePortConverter ---------------------------------------------------------------------- # LiteDRAMNativePortConverter ----------------------------------------------------------------------
class LiteDRAMNativePortConverter(Module): class LiteDRAMNativePortConverter(Module):
def __init__(self, port_from, port_to, reverse=False): def __init__(self, port_from, port_to, reverse=False,
rx_buffer_depth=4, tx_buffer_depth=4, cmd_buffer_depth=4):
assert port_from.clock_domain == port_to.clock_domain assert port_from.clock_domain == port_to.clock_domain
assert port_from.mode == port_to.mode assert port_from.mode == port_to.mode
@ -370,7 +572,9 @@ class LiteDRAMNativePortConverter(Module):
self.submodules.converter = LiteDRAMNativePortDownConverter(port_from, port_to, reverse) self.submodules.converter = LiteDRAMNativePortDownConverter(port_from, port_to, reverse)
elif ratio < 1: elif ratio < 1:
# UpConverter # UpConverter
self.submodules.converter = LiteDRAMNativePortUpConverter(port_from, port_to, reverse) self.submodules.converter = LiteDRAMNativePortUpConverter(
port_from, port_to, reverse,
rx_buffer_depth, tx_buffer_depth, cmd_buffer_depth)
else: else:
# Identity # Identity
self.comb += port_from.connect(port_to) self.comb += port_from.connect(port_to)

View File

@ -538,6 +538,30 @@ class MemoryTestDataMixin:
0x00440000, # 0x1c 0x00440000, # 0x1c
] ]
), ),
"8bit_to_32bit_not_sequential": dict(
pattern=[
# address, data
(0x03, 0x00),
(0x02, 0x11),
(0x01, 0x22),
(0x00, 0x33),
(0x12, 0x44),
(0x11, 0x55),
(0x13, 0x66),
(0x10, 0x77),
],
expected=[
# data, address
0x00112233, # 0x00
0x00000000, # 0x04
0x00000000, # 0x08
0x00000000, # 0x0c
0x66445577, # 0x10
0x00000000, # 0x14
0x00000000, # 0x18
0x00000000, # 0x1c
]
),
"32bit_to_256bit": dict( "32bit_to_256bit": dict(
pattern=[ pattern=[
# address, data # address, data

View File

@ -22,6 +22,7 @@ from litex.gen.sim import *
class ConverterDUT(Module): class ConverterDUT(Module):
def __init__(self, user_data_width, native_data_width, mem_depth, separate_rw=True, read_latency=0): def __init__(self, user_data_width, native_data_width, mem_depth, separate_rw=True, read_latency=0):
self.separate_rw = separate_rw self.separate_rw = separate_rw
if separate_rw: if separate_rw:
self.write_user_port = LiteDRAMNativeWritePort(address_width=32, data_width=user_data_width) self.write_user_port = LiteDRAMNativeWritePort(address_width=32, data_width=user_data_width)
self.write_crossbar_port = LiteDRAMNativeWritePort(address_width=32, data_width=native_data_width) self.write_crossbar_port = LiteDRAMNativeWritePort(address_width=32, data_width=native_data_width)
@ -86,6 +87,7 @@ class TestAdapter(MemoryTestDataMixin, unittest.TestCase):
dut.finalize() dut.finalize()
self.assertIn("ratio must be an int", str(cm.exception).lower()) self.assertIn("ratio must be an int", str(cm.exception).lower())
def test_up_converter_ratio_must_be_integer(self): def test_up_converter_ratio_must_be_integer(self):
with self.assertRaises(ValueError) as cm: with self.assertRaises(ValueError) as cm:
dut = ConverterDUT(user_data_width=32, native_data_width=48, mem_depth=128) dut = ConverterDUT(user_data_width=32, native_data_width=48, mem_depth=128)
@ -339,6 +341,13 @@ class TestAdapter(MemoryTestDataMixin, unittest.TestCase):
mem_depth=len(data["expected"]), separate_rw=False) mem_depth=len(data["expected"]), separate_rw=False)
self.converter_readback_test(dut, data["pattern"], data["expected"]) self.converter_readback_test(dut, data["pattern"], data["expected"])
def test_up_converter_writes_not_sequential(self):
# Verify that not sequential writes to single DRAM word creates same result as sequential one
data = self.pattern_test_data["8bit_to_32bit_not_sequential"]
dut = ConverterDUT(user_data_width=8, native_data_width=32,
mem_depth=len(data["expected"]), separate_rw=False)
self.converter_readback_test(dut, data["pattern"], data["expected"])
def cdc_readback_test(self, dut, pattern, mem_expected, clocks): def cdc_readback_test(self, dut, pattern, mem_expected, clocks):
assert len(set(adr for adr, _ in pattern)) == len(pattern), "Pattern has duplicates!" assert len(set(adr for adr, _ in pattern)) == len(pattern), "Pattern has duplicates!"
read_data = [] read_data = []