frontend/adaptation: clean up LiteDRAMNativePortUpConverter code
This commit is contained in:
parent
2f35e9714d
commit
efe9a44c93
|
@ -139,6 +139,9 @@ class LiteDRAMNativePortUpConverter(Module):
|
||||||
(when possible, ie when consecutive and bursting)
|
(when possible, ie when consecutive and bursting)
|
||||||
- N writes from user are regrouped in a single one to the controller
|
- N writes from user are regrouped in a single one to the controller
|
||||||
(when possible, ie when consecutive and bursting)
|
(when possible, ie when consecutive and bursting)
|
||||||
|
Incomplete writes/reads (i.e. with n < N) are handled automatically in the
|
||||||
|
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).
|
||||||
"""
|
"""
|
||||||
def __init__(self, port_from, port_to, reverse=False):
|
def __init__(self, port_from, port_to, reverse=False):
|
||||||
assert port_from.clock_domain == port_to.clock_domain
|
assert port_from.clock_domain == port_to.clock_domain
|
||||||
|
@ -155,24 +158,25 @@ class LiteDRAMNativePortUpConverter(Module):
|
||||||
# Command ----------------------------------------------------------------------------------
|
# Command ----------------------------------------------------------------------------------
|
||||||
|
|
||||||
# defines cmd type and the chunks that have been requested for the current port_to command
|
# defines cmd type and the chunks that have been requested for the current port_to command
|
||||||
sel = Signal(ratio)
|
sel = Signal(ratio)
|
||||||
cmd_buffer = stream.SyncFIFO([("sel", ratio), ("we", 1)], 4)
|
cmd_buffer = stream.SyncFIFO([("sel", ratio), ("we", 1)], 4)
|
||||||
self.submodules += cmd_buffer
|
self.submodules += cmd_buffer
|
||||||
# store last received command
|
# store last received command
|
||||||
cmd_addr = Signal.like(port_from.cmd.addr)
|
cmd_addr = Signal.like(port_from.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()
|
next_cmd = 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
|
# used to prevent reading old memory value if previous command has written the same address
|
||||||
read_lock = Signal()
|
read_lock = Signal()
|
||||||
rw_collision = Signal()
|
rw_collision = Signal()
|
||||||
|
|
||||||
self.comb += [
|
self.comb += [
|
||||||
|
cmd_buffer.source.ready.eq(wdata_finished | rdata_finished),
|
||||||
addr_changed.eq(cmd_addr[log2_int(ratio):] != port_from.cmd.addr[log2_int(ratio):]),
|
addr_changed.eq(cmd_addr[log2_int(ratio):] != port_from.cmd.addr[log2_int(ratio):]),
|
||||||
# collision happens on write to read transition when address does not change
|
# collision happens on write to read transition when address does not change
|
||||||
rw_collision.eq(cmd_we & (port_from.cmd.valid & ~port_from.cmd.we) & ~addr_changed),
|
rw_collision.eq(cmd_we & (port_from.cmd.valid & ~port_from.cmd.we) & ~addr_changed),
|
||||||
|
@ -182,8 +186,8 @@ class LiteDRAMNativePortUpConverter(Module):
|
||||||
# * 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
|
||||||
next_cmd.eq(addr_changed | (cmd_we != port_from.cmd.we) | (sel == 2**ratio - 1) | cmd_last),
|
next_cmd.eq(addr_changed | (cmd_we != port_from.cmd.we) | (sel == 2**ratio - 1) | cmd_last),
|
||||||
# when the first command is received, send it immediatelly
|
|
||||||
If(sel == 0,
|
If(sel == 0,
|
||||||
|
# when the first command is received, send it immediatelly
|
||||||
If(port_from.cmd.valid & ~read_lock,
|
If(port_from.cmd.valid & ~read_lock,
|
||||||
port_to.cmd.valid.eq(1),
|
port_to.cmd.valid.eq(1),
|
||||||
port_to.cmd.we.eq(port_from.cmd.we),
|
port_to.cmd.we.eq(port_from.cmd.we),
|
||||||
|
@ -192,7 +196,7 @@ class LiteDRAMNativePortUpConverter(Module):
|
||||||
)
|
)
|
||||||
).Else(
|
).Else(
|
||||||
# we have already sent the initial command, now either continue sending cmd.ready
|
# we have already sent the initial command, now either continue sending cmd.ready
|
||||||
# to the master or send the current command if we have to go to next command
|
# to the master or send the current command if we have to go to next one faster
|
||||||
If(next_cmd,
|
If(next_cmd,
|
||||||
cmd_buffer.sink.valid.eq(1),
|
cmd_buffer.sink.valid.eq(1),
|
||||||
cmd_buffer.sink.sel.eq(sel),
|
cmd_buffer.sink.sel.eq(sel),
|
||||||
|
@ -201,11 +205,10 @@ class LiteDRAMNativePortUpConverter(Module):
|
||||||
port_from.cmd.ready.eq(port_from.cmd.valid),
|
port_from.cmd.ready.eq(port_from.cmd.valid),
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
cmd_buffer.source.ready.eq(wdata_finished | rdata_finished)
|
|
||||||
]
|
]
|
||||||
|
|
||||||
self.sync += [
|
self.sync += [
|
||||||
# whenever a command gets accepted, update `sel` bitmask and store the command info
|
# whenever a command gets accepted, store it and update `sel` based on address
|
||||||
If(port_from.cmd.valid & port_from.cmd.ready,
|
If(port_from.cmd.valid & port_from.cmd.ready,
|
||||||
cmd_addr.eq(port_from.cmd.addr),
|
cmd_addr.eq(port_from.cmd.addr),
|
||||||
cmd_we.eq(port_from.cmd.we),
|
cmd_we.eq(port_from.cmd.we),
|
||||||
|
@ -227,19 +230,17 @@ class LiteDRAMNativePortUpConverter(Module):
|
||||||
# Read Datapath ----------------------------------------------------------------------------
|
# Read Datapath ----------------------------------------------------------------------------
|
||||||
|
|
||||||
if mode == "read" or mode == "both":
|
if mode == "read" or mode == "both":
|
||||||
# buffers output from port_to
|
# queue received data not to loose it when it comes too fast
|
||||||
rdata_fifo = stream.SyncFIFO(port_to.rdata.description, ratio)
|
rdata_fifo = stream.SyncFIFO(port_to.rdata.description, ratio - 1)
|
||||||
# connected to the buffered output
|
|
||||||
rdata_converter = stream.StrideConverter(
|
rdata_converter = stream.StrideConverter(
|
||||||
port_to.rdata.description,
|
port_to.rdata.description,
|
||||||
port_from.rdata.description,
|
port_from.rdata.description,
|
||||||
reverse=reverse)
|
reverse=reverse)
|
||||||
self.submodules += rdata_fifo, rdata_converter
|
self.submodules += rdata_fifo, rdata_converter
|
||||||
|
|
||||||
# bitmask shift register with single 1 bit and all other 0s
|
# shift register with a bitmask of current chunk
|
||||||
rdata_chunk = Signal(ratio, reset=1)
|
rdata_chunk = Signal(ratio, reset=1)
|
||||||
rdata_chunk_valid = Signal()
|
rdata_chunk_valid = Signal()
|
||||||
# whenever the converter spits data chunk we shift the chunk bitmask
|
|
||||||
self.sync += \
|
self.sync += \
|
||||||
If(rdata_converter.source.valid &
|
If(rdata_converter.source.valid &
|
||||||
rdata_converter.source.ready,
|
rdata_converter.source.ready,
|
||||||
|
@ -247,84 +248,75 @@ class LiteDRAMNativePortUpConverter(Module):
|
||||||
)
|
)
|
||||||
|
|
||||||
self.comb += [
|
self.comb += [
|
||||||
# port_to -> rdata_fifo -> rdata_converter
|
# port_to -> rdata_fifo -> rdata_converter -> 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.connect(rdata_converter.sink),
|
||||||
# chunk is valid if it's bit is in `sel` sent previously to the FIFO
|
|
||||||
rdata_chunk_valid.eq((cmd_buffer.source.sel & rdata_chunk) != 0),
|
rdata_chunk_valid.eq((cmd_buffer.source.sel & rdata_chunk) != 0),
|
||||||
# whenever `sel` from FIFO is valid
|
|
||||||
If(cmd_buffer.source.valid & ~cmd_buffer.source.we,
|
If(cmd_buffer.source.valid & ~cmd_buffer.source.we,
|
||||||
# if that chunk is valid we send it to the user port and wait for ready from user
|
# if that chunk is valid we send it to the user port and wait for ready
|
||||||
If(rdata_chunk_valid,
|
If(rdata_chunk_valid,
|
||||||
port_from.rdata.valid.eq(rdata_converter.source.valid),
|
port_from.rdata.valid.eq(rdata_converter.source.valid),
|
||||||
port_from.rdata.data.eq(rdata_converter.source.data),
|
port_from.rdata.data.eq(rdata_converter.source.data),
|
||||||
rdata_converter.source.ready.eq(port_from.rdata.ready)
|
rdata_converter.source.ready.eq(port_from.rdata.ready)
|
||||||
# if this was not requested by `sel` then we just ack it
|
).Else( # if this chunk was not requested in `sel`, ignore it
|
||||||
).Else(
|
|
||||||
rdata_converter.source.ready.eq(1)
|
rdata_converter.source.ready.eq(1)
|
||||||
),
|
),
|
||||||
rdata_finished.eq(rdata_converter.source.valid & rdata_converter.source.ready & rdata_chunk[ratio - 1])
|
rdata_finished.eq(rdata_converter.source.valid & rdata_converter.source.ready
|
||||||
|
& rdata_chunk[ratio - 1])
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
# Write Datapath ---------------------------------------------------------------------------
|
# Write Datapath ---------------------------------------------------------------------------
|
||||||
|
|
||||||
if mode == "write" or mode == "both":
|
if mode == "write" or mode == "both":
|
||||||
wdata_fifo = stream.SyncFIFO(port_from.wdata.description, ratio)
|
# queue write data not to miss it when the lower chunks haven't been reqested
|
||||||
|
wdata_fifo = stream.SyncFIFO(port_from.wdata.description, ratio - 1)
|
||||||
wdata_converter = stream.StrideConverter(
|
wdata_converter = stream.StrideConverter(
|
||||||
port_from.wdata.description,
|
port_from.wdata.description,
|
||||||
port_to.wdata.description,
|
port_to.wdata.description,
|
||||||
reverse=reverse)
|
reverse=reverse)
|
||||||
self.submodules += wdata_converter, wdata_fifo
|
self.submodules += wdata_converter, wdata_fifo
|
||||||
|
|
||||||
# bitmask shift register with single 1 bit and all other 0s
|
# shift register with a bitmask of current chunk
|
||||||
wdata_chunk = Signal(ratio, reset=1)
|
wdata_chunk = Signal(ratio, reset=1)
|
||||||
wdata_chunk_valid = Signal()
|
wdata_chunk_valid = Signal()
|
||||||
# whenever the converter spits data chunk we shift the chunk bitmask
|
|
||||||
self.sync += \
|
self.sync += \
|
||||||
If(wdata_converter.sink.valid & wdata_converter.sink.ready,
|
If(wdata_converter.sink.valid & wdata_converter.sink.ready,
|
||||||
wdata_chunk.eq(Cat(wdata_chunk[ratio-1], wdata_chunk[:ratio-1]))
|
wdata_chunk.eq(Cat(wdata_chunk[ratio-1], wdata_chunk[:ratio-1]))
|
||||||
)
|
)
|
||||||
|
|
||||||
# replicate sel so that each bit covers according part of we bitmask (1 sel bit may cover
|
# replicate `sel` bits to match the width of port_to.wdata.we
|
||||||
# multiple bytes)
|
|
||||||
wdata_sel = Signal.like(port_to.wdata.we)
|
wdata_sel = Signal.like(port_to.wdata.we)
|
||||||
# self.comb += wdata_sel.eq(
|
wdata_sel_parts = [
|
||||||
# Cat([Replicate(cmd_buffer.source.sel[i], port_to.wdata.we.nbits // sel.nbits) for i in range(ratio)])
|
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([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.connect(wdata_fifo.sink),
|
port_from.wdata.connect(wdata_fifo.sink),
|
||||||
|
|
||||||
wdata_chunk_valid.eq((cmd_buffer.source.sel & wdata_chunk) != 0),
|
wdata_chunk_valid.eq((cmd_buffer.source.sel & wdata_chunk) != 0),
|
||||||
|
|
||||||
If(cmd_buffer.source.valid & cmd_buffer.source.we,
|
If(cmd_buffer.source.valid & cmd_buffer.source.we,
|
||||||
|
# when the current chunk is valid, read it from wdata_fifo
|
||||||
If(wdata_chunk_valid,
|
If(wdata_chunk_valid,
|
||||||
wdata_converter.sink.valid.eq(wdata_fifo.source.valid),
|
wdata_converter.sink.valid.eq(wdata_fifo.source.valid),
|
||||||
wdata_converter.sink.data.eq(wdata_fifo.source.data),
|
wdata_converter.sink.data.eq(wdata_fifo.source.data),
|
||||||
wdata_converter.sink.we.eq(wdata_fifo.source.we),
|
wdata_converter.sink.we.eq(wdata_fifo.source.we),
|
||||||
wdata_fifo.source.ready.eq(1),
|
wdata_fifo.source.ready.eq(1),
|
||||||
).Else(
|
).Else( # if chunk is not valid, send any data and do not advance fifo
|
||||||
wdata_converter.sink.valid.eq(1),
|
wdata_converter.sink.valid.eq(1),
|
||||||
wdata_converter.sink.data.eq(0),
|
|
||||||
wdata_converter.sink.we.eq(0),
|
|
||||||
wdata_fifo.source.ready.eq(0),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
port_to.wdata.valid.eq(wdata_converter.source.valid),
|
port_to.wdata.valid.eq(wdata_converter.source.valid),
|
||||||
port_to.wdata.data.eq(wdata_converter.source.data),
|
port_to.wdata.data.eq(wdata_converter.source.data),
|
||||||
port_to.wdata.we.eq(wdata_converter.source.we & wdata_sel),
|
port_to.wdata.we.eq(wdata_converter.source.we & wdata_sel),
|
||||||
wdata_converter.source.ready.eq(port_to.wdata.ready),
|
wdata_converter.source.ready.eq(port_to.wdata.ready),
|
||||||
# wdata_finished.eq(wdata_converter.source.valid & wdata_converter.source.ready),
|
wdata_finished.eq(wdata_converter.sink.valid & wdata_converter.sink.ready
|
||||||
wdata_finished.eq(wdata_converter.sink.valid & wdata_converter.sink.ready & wdata_chunk[ratio-1]),
|
& wdata_chunk[ratio-1]),
|
||||||
]
|
]
|
||||||
|
|
||||||
# LiteDRAMNativePortConverter ----------------------------------------------------------------------
|
# LiteDRAMNativePortConverter ----------------------------------------------------------------------
|
||||||
|
|
|
@ -77,13 +77,13 @@ class CDCDUT(ConverterDUT):
|
||||||
|
|
||||||
|
|
||||||
class TestAdaptation(MemoryTestDataMixin, unittest.TestCase):
|
class TestAdaptation(MemoryTestDataMixin, unittest.TestCase):
|
||||||
def test_converter_down_ratio_must_be_integer(self):
|
def test_down_converter_ratio_must_be_integer(self):
|
||||||
with self.assertRaises(ValueError) as cm:
|
with self.assertRaises(ValueError) as cm:
|
||||||
dut = ConverterDUT(user_data_width=64, native_data_width=24, mem_depth=128)
|
dut = ConverterDUT(user_data_width=64, native_data_width=24, mem_depth=128)
|
||||||
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_converter_up_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)
|
||||||
dut.finalize()
|
dut.finalize()
|
||||||
|
@ -155,7 +155,7 @@ class TestAdaptation(MemoryTestDataMixin, unittest.TestCase):
|
||||||
# Verify 32-bit to 256-bit up-conversion.
|
# Verify 32-bit to 256-bit up-conversion.
|
||||||
self.converter_test(test_data="32bit_to_256bit", user_data_width=32, native_data_width=256)
|
self.converter_test(test_data="32bit_to_256bit", user_data_width=32, native_data_width=256)
|
||||||
|
|
||||||
def test_converter_up_read_latencies(self):
|
def test_up_converter_read_latencies(self):
|
||||||
# Verify that up-conversion works with different port reader latencies
|
# Verify that up-conversion works with different port reader latencies
|
||||||
cases = {
|
cases = {
|
||||||
"1to2": dict(test_data="8bit_to_16bit", user_data_width=8, native_data_width=16),
|
"1to2": dict(test_data="8bit_to_16bit", user_data_width=8, native_data_width=16),
|
||||||
|
@ -168,7 +168,7 @@ class TestAdaptation(MemoryTestDataMixin, unittest.TestCase):
|
||||||
with self.subTest(conversion=conversion):
|
with self.subTest(conversion=conversion):
|
||||||
self.converter_test(**kwargs, read_latency=latency)
|
self.converter_test(**kwargs, read_latency=latency)
|
||||||
|
|
||||||
def test_converter_down_read_latencies(self):
|
def test_down_converter_read_latencies(self):
|
||||||
# Verify that down-conversion works with different port reader latencies
|
# Verify that down-conversion works with different port reader latencies
|
||||||
cases = {
|
cases = {
|
||||||
"2to1": dict(test_data="64bit_to_32bit", user_data_width=64, native_data_width=32),
|
"2to1": dict(test_data="64bit_to_32bit", user_data_width=64, native_data_width=32),
|
||||||
|
@ -330,7 +330,7 @@ class TestAdaptation(MemoryTestDataMixin, unittest.TestCase):
|
||||||
self.converter_readback_test(dut, pattern=[], mem_expected=mem_expected,
|
self.converter_readback_test(dut, pattern=[], mem_expected=mem_expected,
|
||||||
main_generator=main_generator)
|
main_generator=main_generator)
|
||||||
|
|
||||||
def test_converter_up_not_aligned(self):
|
def test_up_converter_not_aligned(self):
|
||||||
data = self.pattern_test_data["8bit_to_32bit_not_aligned"]
|
data = self.pattern_test_data["8bit_to_32bit_not_aligned"]
|
||||||
dut = ConverterDUT(user_data_width=8, native_data_width=32,
|
dut = ConverterDUT(user_data_width=8, native_data_width=32,
|
||||||
mem_depth=len(data["expected"]), separate_rw=False)
|
mem_depth=len(data["expected"]), separate_rw=False)
|
||||||
|
|
Loading…
Reference in New Issue