diff --git a/litedram/core/crossbar.py b/litedram/core/crossbar.py index 624d125..dd6224c 100644 --- a/litedram/core/crossbar.py +++ b/litedram/core/crossbar.py @@ -76,7 +76,8 @@ class LiteDRAMCrossbar(Module): 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: raise FinalizeError @@ -117,7 +118,8 @@ class LiteDRAMCrossbar(Module): clock_domain = clock_domain, id = port.id) 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 return port diff --git a/litedram/frontend/adapter.py b/litedram/frontend/adapter.py index 5c1461e..1bbb8b6 100644 --- a/litedram/frontend/adapter.py +++ b/litedram/frontend/adapter.py @@ -22,9 +22,9 @@ class LiteDRAMNativePortCDC(Module): assert port_from.data_width == port_to.data_width assert port_from.mode == port_to.mode - address_width = port_from.address_width - data_width = port_from.data_width - mode = port_from.mode + address_width = port_from.address_width + data_width = port_from.data_width + mode = port_from.mode # # # @@ -81,6 +81,7 @@ class LiteDRAMNativePortDownConverter(Module): ratio = port_from.data_width//port_to.data_width mode = port_from.mode + count = Signal(max=ratio) 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), If(port_to.cmd.ready, NextValue(count, count + 1), - If(count == (ratio - 1), + If(count == ratio - 1, port_from.cmd.ready.eq(1), 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 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.data_width < port_to.data_width assert port_from.mode == port_to.mode @@ -145,219 +147,419 @@ class LiteDRAMNativePortUpConverter(Module): # # # - ratio = port_to.data_width//port_from.data_width - mode = port_from.mode + self.ratio = ratio = port_to.data_width//port_from.data_width + mode = port_from.mode # Command ---------------------------------------------------------------------------------- - # Defines cmd type and the chunks that have been requested for the current port_to command. - sel = Signal(ratio) - cmd_buffer = stream.SyncFIFO([("sel", ratio), ("we", 1)], 0) - self.submodules += cmd_buffer - # Store last received command. - cmd_addr = Signal.like(port_from.cmd.addr) + # Defines read/write ordering of chunks that have been requested + ordering_layout = [ + ("counter", log2_int(ratio) + 1), + ("ordering", ratio * log2_int(ratio)) + ] + 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_last = Signal() - # Indicates that we need to proceed to the next port_to command. - next_cmd = Signal() + # Indicates that we need to proceed to the next port_to command + cmd_finished = 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() 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: - # - read: new -> cmd -> fill -> commit -> new - # - write: new -> fill -> commit -> cmd -> new - # 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. - self.submodules.fsm = fsm = FSM() - fsm.act("NEW", - port_from.cmd.ready.eq(port_from.cmd.valid & ~read_lock), - If(port_from.cmd.ready, - NextValue(cmd_addr, port_from.cmd.addr), - NextValue(cmd_we, port_from.cmd.we), - NextValue(cmd_last, port_from.cmd.last), - NextValue(sel, 1 << port_from.cmd.addr[:log2_int(ratio)]), - If(port_from.cmd.we, - NextState("FILL"), + # Used to keep access order for reads and writes + counter = Signal(log2_int(ratio) + 1) + ordering = Signal(ratio * log2_int(ratio)) + + # Send Command ----------------------------------------------------------------------------- + + self.send_cmd = send_cmd = Signal() + self.send_cmd_addr = send_cmd_addr = Signal.like(port_to.cmd.addr) + self.send_cmd_we = send_cmd_we = Signal.like(port_to.cmd.we) + self.send_cmd_busy = send_cmd_busy = Signal() + + send_inner_cmd_addr = Signal.like(port_to.cmd.addr) + send_inner_cmd_we = Signal.like(port_to.cmd.we) + + 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( - NextState("CMD"), + NextState("IDLE") ) ) ) - fsm.act("CMD", - port_to.cmd.valid.eq(1), - port_to.cmd.we.eq(cmd_we), - port_to.cmd.addr.eq(cmd_addr[log2_int(ratio):]), - If(port_to.cmd.ready, + # 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( + 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, - 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( 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("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") - ) - ) - ) + 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 += [ - cmd_buffer.source.ready.eq(wdata_finished | rdata_finished), - 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. - rw_collision.eq(cmd_we & (port_from.cmd.valid & ~port_from.cmd.we) & ~addr_changed), + tx_buffer.source.ready.eq(wdata_finished), + rx_buffer.source.ready.eq(rdata_finished), + addr_changed.eq(cmd_addr != port_from.cmd.addr[log2_int(ratio):]), # Go to the next command if one of the following happens: # - port_to address changes. # - cmd type changes. # - we received all the `ratio` commands. # - this is the last command in a sequence. # - 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), ] - 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 ---------------------------------------------------------------------------- if mode in ["read", "both"]: - # Queue received data not to loose it when it comes too fast. - rdata_fifo = stream.SyncFIFO(port_to.rdata.description, ratio - 1) - rdata_converter = stream.StrideConverter( - description_from = port_to.rdata.description, - description_to = port_from.rdata.description, - reverse = reverse) - self.submodules += rdata_fifo, rdata_converter + read_upper_counter = Signal.like(counter) + read_inner_counter = Signal.like(counter) + read_inner_ordering = Signal.like(ordering) - # Shift register with a bitmask of current chunk. - rdata_chunk = Signal(ratio, reset=1) - rdata_chunk_valid = Signal() - self.sync += \ - If(rdata_converter.source.valid & - rdata_converter.source.ready, - rdata_chunk.eq(Cat(rdata_chunk[ratio-1], rdata_chunk[:ratio-1])) - ) + read_chunk = Signal(log2_int(ratio)) + + # Queue received data not to loose it when it comes too fast + rdata_fifo = stream.SyncFIFO(port_to.rdata.description, cmd_buffer.depth + rx_buffer.depth + 1) + self.submodules += rdata_fifo + + 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 += [ - # port_to -> rdata_fifo -> rdata_converter -> port_from + + # Port_to -> rdata_fifo -> order_mux -> port_from port_to.rdata.connect(rdata_fifo.sink), - rdata_fifo.source.connect(rdata_converter.sink), - rdata_chunk_valid.eq((cmd_buffer.source.sel & rdata_chunk) != 0), - 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. - If(rdata_chunk_valid, - port_from.rdata.valid.eq(rdata_converter.source.valid), - port_from.rdata.data.eq(rdata_converter.source.data), - 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 - & rdata_chunk[ratio - 1]) + rdata_fifo.source.ready.eq(rdata_finished), + + 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(read_inner_counter < read_upper_counter, + port_from.rdata.valid.eq(1), + Case(read_chunk, cases), + ) + ) + ] + + cases = {} + 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 --------------------------------------------------------------------------- if mode in ["write", "both"]: - # 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_buffer = stream.SyncFIFO(port_to.wdata.description, 1) - 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 + write_upper_counter = Signal.like(counter) + write_inner_counter = Signal.like(counter) + write_inner_ordering = Signal.like(ordering) - # Shift register with a bitmask of current chunk. - 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])) - ) + write_chunk = Signal(log2_int(ratio)) - # Replicate `sel` bits to match the width of port_to.wdata.we. - wdata_sel = Signal.like(port_to.wdata.we) - if reverse: - wdata_sel_parts = [ - Replicate(cmd_buffer.source.sel[i], port_to.wdata.we.nbits // sel.nbits) - 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)) - ) + # Queue write data not to miss it when the lower chunks haven't been reqested + wdata_fifo = stream.SyncFIFO(port_from.wdata.description, ratio) + wdata_buffer = Record([("data", port_to.wdata.data.nbits), + ("we", port_to.wdata.we.nbits)]) + self.submodules += wdata_fifo self.comb += [ - # port_from -> wdata_fifo -> wdata_converter + # port_from -> wdata_fifo -> wdata_buffer (keeps order) port_from.wdata.connect(wdata_fifo.sink), - wdata_buffer.source.connect(port_to.wdata), - wdata_chunk_valid.eq((cmd_buffer.source.sel & wdata_chunk) != 0), - If(cmd_buffer.source.valid & cmd_buffer.source.we, - # When the current chunk is valid, read it from wdata_fifo. - 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]), + port_to.wdata.data.eq(wdata_buffer.data), + port_to.wdata.we.eq(wdata_buffer.we), + port_to.wdata.valid.eq((write_inner_counter == write_upper_counter) & tx_buffer.source.valid), + wdata_fifo.source.ready.eq(write_inner_counter < write_upper_counter), ] + 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 ---------------------------------------------------------------------- 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.mode == port_to.mode @@ -370,7 +572,9 @@ class LiteDRAMNativePortConverter(Module): self.submodules.converter = LiteDRAMNativePortDownConverter(port_from, port_to, reverse) elif ratio < 1: # 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: # Identity self.comb += port_from.connect(port_to) diff --git a/test/common.py b/test/common.py index 58f81d1..cf75f5c 100644 --- a/test/common.py +++ b/test/common.py @@ -538,6 +538,30 @@ class MemoryTestDataMixin: 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( pattern=[ # address, data diff --git a/test/test_adapter.py b/test/test_adapter.py index 87177fa..d863ea7 100644 --- a/test/test_adapter.py +++ b/test/test_adapter.py @@ -22,6 +22,7 @@ from litex.gen.sim import * class ConverterDUT(Module): def __init__(self, user_data_width, native_data_width, mem_depth, separate_rw=True, read_latency=0): self.separate_rw = separate_rw + if separate_rw: 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) @@ -86,6 +87,7 @@ class TestAdapter(MemoryTestDataMixin, unittest.TestCase): dut.finalize() self.assertIn("ratio must be an int", str(cm.exception).lower()) + def test_up_converter_ratio_must_be_integer(self): with self.assertRaises(ValueError) as cm: 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) 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): assert len(set(adr for adr, _ in pattern)) == len(pattern), "Pattern has duplicates!" read_data = []