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:
parent
3d066c87f9
commit
7b3c6abd50
|
@ -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
|
||||||
|
|
|
@ -22,9 +22,9 @@ class LiteDRAMNativePortCDC(Module):
|
||||||
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
|
||||||
|
|
||||||
address_width = port_from.address_width
|
address_width = port_from.address_width
|
||||||
data_width = port_from.data_width
|
data_width = port_from.data_width
|
||||||
mode = port_from.mode
|
mode = port_from.mode
|
||||||
|
|
||||||
# # #
|
# # #
|
||||||
|
|
||||||
|
@ -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),
|
||||||
|
("cmd_we", send_cmd_we.nbits)],
|
||||||
|
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",
|
||||||
|
port_to.cmd.valid.eq(1),
|
||||||
|
port_to.cmd.addr.eq(send_inner_cmd_addr),
|
||||||
|
port_to.cmd.we.eq(send_inner_cmd_we),
|
||||||
|
If(port_to.cmd.ready,
|
||||||
|
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")
|
||||||
).Else(
|
).Else(
|
||||||
NextState("CMD"),
|
NextState("IDLE")
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
fsm.act("CMD",
|
# Command flow is quite complicate, nonlinear and it depends on type read/write,
|
||||||
port_to.cmd.valid.eq(1),
|
# so here is summary:
|
||||||
port_to.cmd.we.eq(cmd_we),
|
# This FSM receives commands from `port_from` and pushes them to `cmd_buffer` queue,
|
||||||
port_to.cmd.addr.eq(cmd_addr[log2_int(ratio):]),
|
# which is then handled by the `send_fsm` which sends commands to `port_to`.
|
||||||
If(port_to.cmd.ready,
|
# 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(
|
||||||
|
NextState("FILL")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
fsm.act("WAIT-TO-SEND",
|
||||||
|
send_cmd.eq(1),
|
||||||
|
send_cmd_addr.eq(cmd_addr),
|
||||||
|
send_cmd_we.eq(cmd_we),
|
||||||
|
If(~send_cmd_busy,
|
||||||
If(cmd_we,
|
If(cmd_we,
|
||||||
NextState("NEW")
|
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(
|
||||||
|
NextState("WAIT-FOR-CMD")
|
||||||
|
)
|
||||||
).Else(
|
).Else(
|
||||||
NextState("FILL")
|
NextState("FILL")
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
fsm.act("FILL",
|
cases = {}
|
||||||
If(next_cmd,
|
for i in range(ratio):
|
||||||
NextState("COMMIT")
|
cases[i] = NextValue(ordering[i * log2_int(ratio) : (i + 1) * log2_int(ratio)],
|
||||||
).Else( # Acknowledge incomming commands, while filling `sel`.
|
port_from.cmd.addr[:log2_int(ratio)])
|
||||||
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("COMMIT",
|
|
||||||
cmd_buffer.sink.valid.eq(1),
|
|
||||||
cmd_buffer.sink.sel.eq(sel),
|
|
||||||
cmd_buffer.sink.we.eq(cmd_we),
|
|
||||||
If(cmd_buffer.sink.ready,
|
|
||||||
If(cmd_we,
|
|
||||||
NextState("CMD")
|
|
||||||
).Else(
|
|
||||||
NextState("NEW")
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
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),
|
|
||||||
wdata_converter.sink.we.eq(wdata_fifo.source.we),
|
|
||||||
wdata_fifo.source.ready.eq(wdata_converter.sink.ready),
|
|
||||||
).Else( # If chunk is not valid, send any data and do not advance fifo.
|
|
||||||
wdata_converter.sink.valid.eq(1),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
wdata_buffer.sink.valid.eq(wdata_converter.source.valid),
|
|
||||||
wdata_buffer.sink.data.eq(wdata_converter.source.data),
|
|
||||||
wdata_buffer.sink.we.eq(wdata_converter.source.we & wdata_sel),
|
|
||||||
wdata_converter.source.ready.eq(wdata_buffer.sink.ready),
|
|
||||||
wdata_finished.eq(wdata_converter.sink.valid & wdata_converter.sink.ready
|
|
||||||
& wdata_chunk[ratio-1]),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
cases = {}
|
||||||
|
for i in range(ratio):
|
||||||
|
cases[i] = write_chunk.eq(write_inner_ordering[
|
||||||
|
i * log2_int(ratio) : (i + 1) * log2_int(ratio)]),
|
||||||
|
|
||||||
|
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)
|
||||||
|
),
|
||||||
|
If(wdata_finished,
|
||||||
|
write_inner_counter.eq(0),
|
||||||
|
wdata_buffer.we.eq(0),
|
||||||
|
).Elif(wdata_fifo.source.valid & wdata_fifo.source.ready,
|
||||||
|
write_inner_counter.eq(write_inner_counter + 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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 = []
|
||||||
|
|
Loading…
Reference in New Issue