feat(spi): Increase Spi2TLUL bulk transfer size to 16-bit This change increases the maximum bulk transfer size of the Spi2TLUL module from 256 bytes to 65536 bytes by widening the length registers from 8 to 16 bits. The key changes include: - Updated the Spi2TLUL Chisel module to use 16-bit length registers for both bulk and regular TileLink transfers. - Modified the SPI command state machine to handle two-byte length transactions. - Re-implemented the internal data buffers using SyncReadMem to support larger, configurable depths, and increased the buffer size to 4KB. - Introduced pipelining for SRAM reads to improve performance with larger data transfers. - Updated the Python SPI master utility and cocotb tests to support 16-bit register accesses and larger data transfers. - Added new cocotb tests to verify large (up to 4KB) packed writes and pipelined reads. Change-Id: I4420f969584976c4fb16b25513c1791e54aced4c
diff --git a/hdl/chisel/src/bus/Spi2TLUL.scala b/hdl/chisel/src/bus/Spi2TLUL.scala index 8cb6027..bdba746 100644 --- a/hdl/chisel/src/bus/Spi2TLUL.scala +++ b/hdl/chisel/src/bus/Spi2TLUL.scala
@@ -32,6 +32,10 @@ val tl = new OpenTitanTileLink.Host2Device(new TLULParameters(p)) }) + val kBufferDepth = 256 + val kBufferWidth = p.lsuDataBits + require(kBufferDepth * kBufferWidth / 8 <= 65536, "Total buffer size cannot exceed 65536 bytes") + // Synchronize the main asynchronous reset to the SPI clock domain. val spi_domain_reset = withClock(io.spi.clk) { @@ -69,14 +73,14 @@ val byte_received = bit_count_reg === 7.U val spi_bulk_read_len_reg = withClockAndReset(io.spi.clk, spi_domain_reset.asAsyncReset) { - RegInit(0.U(8.W)) + RegInit(0.U(16.W)) } val spi_bulk_read_sent_count_reg = withClockAndReset(io.spi.clk, spi_domain_reset.asAsyncReset) { - RegInit(0.U(8.W)) + RegInit(0.U(16.W)) } object SpiCmdState extends ChiselEnum { - val sIdle, sGotBulkReadAddr, sSendData, sGotOtherCmd = Value + val sIdle, sGotBulkReadAddr, sGotBulkReadLenL, sWaitBulkReadLenH, sSendData, sGotOtherCmd = Value } val spi_cmd_state = withClock(io.spi.clk) { RegInit(SpiCmdState.sIdle) } @@ -88,21 +92,29 @@ val is_write_cmd = completed_byte(7) val cmd_addr = completed_byte(6,0) - val is_truly_a_bulk_read_cmd = (spi_cmd_state === SpiCmdState.sIdle) && is_first_byte && is_write_cmd && (cmd_addr === SpiRegAddress.BULK_READ_PORT.asUInt) + val is_bulk_read_cmd_l = is_first_byte && is_write_cmd && (cmd_addr === SpiRegAddress.BULK_READ_PORT_L.asUInt) + val is_bulk_read_cmd_h = !is_first_byte && is_write_cmd && (cmd_addr === SpiRegAddress.BULK_READ_PORT_H.asUInt) val next_spi_cmd_state = MuxCase(spi_cmd_state, Seq( - (spi_cmd_state === SpiCmdState.sIdle && is_truly_a_bulk_read_cmd) -> SpiCmdState.sGotBulkReadAddr, - (spi_cmd_state === SpiCmdState.sIdle && !is_truly_a_bulk_read_cmd && is_first_byte && is_write_cmd) -> SpiCmdState.sGotOtherCmd, - (spi_cmd_state === SpiCmdState.sGotBulkReadAddr) -> SpiCmdState.sSendData, + (spi_cmd_state === SpiCmdState.sIdle && is_bulk_read_cmd_l) -> SpiCmdState.sGotBulkReadAddr, + (spi_cmd_state === SpiCmdState.sIdle && !is_bulk_read_cmd_l && is_first_byte && is_write_cmd) -> SpiCmdState.sGotOtherCmd, + (spi_cmd_state === SpiCmdState.sGotBulkReadAddr) -> SpiCmdState.sGotBulkReadLenL, + (spi_cmd_state === SpiCmdState.sGotBulkReadLenL && is_bulk_read_cmd_h) -> SpiCmdState.sWaitBulkReadLenH, + (spi_cmd_state === SpiCmdState.sWaitBulkReadLenH) -> SpiCmdState.sSendData, (spi_cmd_state === SpiCmdState.sSendData && spi_bulk_read_sent_count_reg === spi_bulk_read_len_reg) -> SpiCmdState.sIdle, (spi_cmd_state === SpiCmdState.sGotOtherCmd) -> SpiCmdState.sIdle, )) spi_cmd_state := Mux(byte_received, next_spi_cmd_state, spi_cmd_state) val is_bulk_read_start = byte_received && (spi_cmd_state === SpiCmdState.sGotBulkReadAddr) - val block_cmd_enqueue = (spi_cmd_state === SpiCmdState.sGotBulkReadAddr) || (is_truly_a_bulk_read_cmd && byte_received) + val is_bulk_read_len_h_byte = byte_received && (spi_cmd_state === SpiCmdState.sWaitBulkReadLenH) + val block_cmd_enqueue = (is_bulk_read_cmd_l && byte_received) || + (spi_cmd_state === SpiCmdState.sGotBulkReadAddr) || + (spi_cmd_state === SpiCmdState.sGotBulkReadLenL) || + (spi_cmd_state === SpiCmdState.sWaitBulkReadLenH) || + (spi_cmd_state === SpiCmdState.sSendData) - val is_bulk_status_read = is_first_byte && !completed_byte(7) && (completed_byte(6,0) === SpiRegAddress.BULK_READ_STATUS_REG.asUInt) && (spi_cmd_state =/= SpiCmdState.sGotOtherCmd) + val is_bulk_status_read = is_first_byte && !completed_byte(7) && (cmd_addr === SpiRegAddress.BULK_READ_STATUS_REG_L.asUInt || cmd_addr === SpiRegAddress.BULK_READ_STATUS_REG_H.asUInt) && (spi_cmd_state =/= SpiCmdState.sGotOtherCmd) spi2tlul_q.io.enq.valid := byte_received && !is_bulk_status_read && !block_cmd_enqueue spi2tlul_q.io.enq.bits := completed_byte @@ -119,37 +131,42 @@ val TL_ADDR_REG_1 = 0x01.U val TL_ADDR_REG_2 = 0x02.U val TL_ADDR_REG_3 = 0x03.U - val TL_LEN_REG = 0x04.U - val TL_CMD_REG = 0x05.U - val TL_STATUS_REG = 0x06.U - val DATA_BUF_PORT = 0x07.U - val TL_WRITE_STATUS_REG = 0x08.U - val BULK_WRITE_PORT = 0x09.U - val BULK_READ_PORT = 0x0A.U - val BULK_READ_STATUS_REG = 0x0B.U + val TL_LEN_REG_L = 0x04.U + val TL_LEN_REG_H = 0x05.U + val TL_CMD_REG = 0x06.U + val TL_STATUS_REG = 0x07.U + val DATA_BUF_PORT = 0x08.U + val TL_WRITE_STATUS_REG = 0x09.U + val BULK_WRITE_PORT_L = 0x0A.U + val BULK_WRITE_PORT_H = 0x0B.U + val BULK_READ_PORT_L = 0x0C.U + val BULK_READ_PORT_H = 0x0D.U + val BULK_READ_STATUS_REG_L = 0x0E.U + val BULK_READ_STATUS_REG_H = 0x0F.U } // Physical registers backing the map val tl_addr_reg = RegInit(VecInit(Seq.fill(4)(0.U(8.W)))) - val tl_len_reg = RegInit(0.U(8.W)) - val bulk_len_reg = RegInit(0.U(8.W)) - val bulk_count_reg = RegInit(0.U(8.W)) + val tl_len_reg = RegInit(0.U(16.W)) + val bulk_len_reg = RegInit(0.U(16.W)) + val bulk_count_reg = RegInit(0.U(16.W)) // Command and Status registers are handled by the TL FSM, not stored directly here. - val write_data_buffer = RegInit(VecInit(Seq.fill(16)(0.U(128.W)))) - val read_data_buffer = withClockAndReset(io.spi.clk, spi_domain_reset.asAsyncReset) { - RegInit(VecInit(Seq.fill(16)(0.U(128.W)))) + // val write_data_buffer = SyncReadMem(kBufferDepth, UInt(kBufferWidth.W)) + val write_data_buffer = SRAM(kBufferDepth, UInt(kBufferWidth.W), /*readPorts=*/3, /*writePorts=*/1, /*readWritePorts=*/0) + val read_data_buffer = withClock(io.spi.clk) { + SRAM(kBufferDepth, UInt(kBufferWidth.W), /*readPorts=*/1, /*writePorts=*/1, /*readWritePorts=*/0) } val bulk_read_write_ptr = withClockAndReset(io.spi.clk, spi_domain_reset.asAsyncReset) { - RegInit(0.U(4.W)) + RegInit(0.U(log2Ceil(kBufferDepth).W)) } val spi_bulk_read_ptr = withClockAndReset(io.spi.clk, spi_domain_reset.asAsyncReset) { - RegInit(0.U(8.W)) // Byte pointer into the data buffer + RegInit(0.U(log2Ceil(kBufferDepth * kBufferWidth / 8).W)) // Byte pointer into the data buffer } - val bulk_write_ptr = RegInit(0.U(8.W)) // Byte pointer for writes + val bulk_write_ptr = RegInit(0.U(log2Ceil(kBufferDepth * kBufferWidth / 8).W)) // Byte pointer for writes - val bytes_written = Cat(bulk_read_write_ptr, 0.U(4.W)) + val bytes_written = bulk_read_write_ptr << log2Ceil(kBufferWidth / 8) val bytes_read = spi_bulk_read_ptr - val bulk_read_bytes_available = Wire(UInt(9.W)) + val bulk_read_bytes_available = Wire(UInt(17.W)) bulk_read_bytes_available := bytes_written - bytes_read val addr_reg = RegInit(0.U(7.W)) @@ -162,8 +179,8 @@ // Internal registers for the TL transaction val tl_addr_fsm_reg = RegInit(0.U(32.W)) - val tl_len_fsm_reg = RegInit(0.U(8.W)) - val tl_beat_count_reg = RegInit(0.U(8.W)) + val tl_len_fsm_reg = RegInit(0.U(16.W)) + val tl_beat_count_reg = RegInit(0.U(16.W)) // === TileLink Write FSM === object TlWriteState extends ChiselEnum { @@ -173,15 +190,16 @@ // Internal registers for the TL write transaction val tl_write_addr_fsm_reg = RegInit(0.U(32.W)) - val tl_write_len_fsm_reg = RegInit(0.U(8.W)) - val tl_write_beat_count_reg = RegInit(0.U(8.W)) + val tl_write_len_fsm_reg = RegInit(0.U(16.W)) + val tl_write_beat_count_reg = RegInit(0.U(16.W)) + val sram_addr_reg = RegInit(0.U(log2Ceil(kBufferDepth).W)) // Wire to detect a write to the command register val do_write = spi_state_reg === SpiState.sWAIT_WRITE_DATA && spi2tlul_q.io.deq.fire val tl_cmd_reg_write = do_write && (addr_reg === SpiRegAddress.TL_CMD_REG.asUInt) val tl_cmd_reg_data = spi2tlul_q.io.deq.bits - val tl_to_spi_bulk_q = Module(new AsyncQueue(UInt(128.W), AsyncQueueParams(depth = 2, safe = false))) + val tl_to_spi_bulk_q = Module(new AsyncQueue(UInt(kBufferWidth.W), AsyncQueueParams(depth = 2, safe = false))) tl_to_spi_bulk_q.io.enq_clock := clock tl_to_spi_bulk_q.io.enq_reset := reset.asBool tl_to_spi_bulk_q.io.deq_clock := io.spi.clk @@ -222,8 +240,8 @@ (spi_state_reg === SpiState.sIDLE && spi2tlul_q.io.deq.fire) -> Mux(is_write, SpiState.sWAIT_WRITE_DATA, SpiState.sSEND_READ_DATA), (spi_state_reg === SpiState.sWAIT_WRITE_DATA && spi2tlul_q.io.deq.fire) -> - Mux(addr_reg === SpiRegAddress.BULK_WRITE_PORT.asUInt, SpiState.sBULK_WRITE_DATA, - Mux(addr_reg === SpiRegAddress.BULK_READ_PORT.asUInt, SpiState.sBULK_READ_DATA, + Mux(addr_reg === SpiRegAddress.BULK_WRITE_PORT_H.asUInt, SpiState.sBULK_WRITE_DATA, + Mux(addr_reg === SpiRegAddress.BULK_READ_PORT_H.asUInt, SpiState.sBULK_READ_DATA, SpiState.sIDLE)), (spi_state_reg === SpiState.sSEND_READ_DATA && tlul2spi_q.io.enq.fire) -> SpiState.sIDLE, @@ -247,31 +265,51 @@ tl_addr_reg(i) := Mux(writing_addr_reg && (addr_reg === (SpiRegAddress.TL_ADDR_REG_0 + i.U)), data, tl_addr_reg(i)) } - val writing_len_reg = do_write && addr_reg === SpiRegAddress.TL_LEN_REG.asUInt - tl_len_reg := Mux(writing_len_reg, data, tl_len_reg) + val writing_len_reg_l = do_write && addr_reg === SpiRegAddress.TL_LEN_REG_L.asUInt + val writing_len_reg_h = do_write && addr_reg === SpiRegAddress.TL_LEN_REG_H.asUInt + tl_len_reg := Cat( + Mux(writing_len_reg_h, data, tl_len_reg(15, 8)), + Mux(writing_len_reg_l, data, tl_len_reg(7, 0)) + ) - val writing_bulk_write_port = do_write && addr_reg === SpiRegAddress.BULK_WRITE_PORT.asUInt - val writing_bulk_read_port = do_write && addr_reg === SpiRegAddress.BULK_READ_PORT.asUInt - bulk_len_reg := Mux(writing_bulk_write_port || writing_bulk_read_port, data, bulk_len_reg) + val writing_bulk_write_port_l = do_write && addr_reg === SpiRegAddress.BULK_WRITE_PORT_L.asUInt + val writing_bulk_write_port_h = do_write && addr_reg === SpiRegAddress.BULK_WRITE_PORT_H.asUInt + val writing_bulk_read_port_l = do_write && addr_reg === SpiRegAddress.BULK_READ_PORT_L.asUInt + val writing_bulk_read_port_h = do_write && addr_reg === SpiRegAddress.BULK_READ_PORT_H.asUInt + val writing_bulk_len_l = writing_bulk_write_port_l || writing_bulk_read_port_l + val writing_bulk_len_h = writing_bulk_write_port_h || writing_bulk_read_port_h + bulk_len_reg := Cat( + Mux(writing_bulk_len_h, data, bulk_len_reg(15, 8)), + Mux(writing_bulk_len_l, data, bulk_len_reg(7, 0)) + ) - val write_word_index = bulk_write_ptr(7,4) - val write_byte_index = bulk_write_ptr(3,0) + val byte_index_bits = log2Ceil(kBufferWidth / 8) + val write_word_index = bulk_write_ptr >> byte_index_bits + val write_byte_index = bulk_write_ptr(byte_index_bits - 1, 0) + + // Pipelined read for single-byte access + val single_read_addr_reg = RegNext(spi_bulk_read_ptr >> byte_index_bits, 0.U) + write_data_buffer.readPorts(0).enable := true.B + write_data_buffer.readPorts(0).address := single_read_addr_reg + val selected_word_reg = RegNext(write_data_buffer.readPorts(0).data, 0.U) + val write_shift = write_byte_index << 3 val write_mask = ~(0xFF.U << write_shift) - val write_old_word = write_data_buffer(write_word_index) + write_data_buffer.readPorts(1).enable := true.B + write_data_buffer.readPorts(1).address := write_word_index + val write_old_word = write_data_buffer.readPorts(1).data // Still combinational for write val write_new_word = (write_old_word & write_mask) | (data << write_shift) val write_cmd_fire = tl_cmd_reg_write && tl_cmd_reg_data === 2.U val writing_data_buf_single = do_write && addr_reg === SpiRegAddress.DATA_BUF_PORT.asUInt val writing_bulk_data = spi_state_reg === SpiState.sBULK_WRITE_DATA && spi2tlul_q.io.deq.fire val writing_data_buf = writing_data_buf_single || writing_bulk_data - val start_bulk_write = do_write && addr_reg === SpiRegAddress.BULK_WRITE_PORT.asUInt + val start_bulk_write = do_write && addr_reg === SpiRegAddress.BULK_WRITE_PORT_H.asUInt bulk_write_ptr := Mux(write_cmd_fire || start_bulk_write, 0.U, Mux(writing_data_buf, bulk_write_ptr + 1.U, bulk_write_ptr)) // sSEND_READ_DATA - val word_index = spi_bulk_read_ptr(7,4) - val byte_index = spi_bulk_read_ptr(3,0) - val selected_word = write_data_buffer(word_index) + val selected_word = selected_word_reg + val byte_index = RegNext(spi_bulk_read_ptr(byte_index_bits - 1, 0), 0.U) val status_map = Seq( TlReadState.sIdle.asUInt -> 0x00.U, @@ -294,18 +332,18 @@ SpiRegAddress.TL_ADDR_REG_1.asUInt -> tl_addr_reg(1), SpiRegAddress.TL_ADDR_REG_2.asUInt -> tl_addr_reg(2), SpiRegAddress.TL_ADDR_REG_3.asUInt -> tl_addr_reg(3), - SpiRegAddress.TL_LEN_REG.asUInt -> tl_len_reg, + SpiRegAddress.TL_LEN_REG_L.asUInt -> tl_len_reg(7, 0), + SpiRegAddress.TL_LEN_REG_H.asUInt -> tl_len_reg(15, 8), SpiRegAddress.TL_STATUS_REG.asUInt -> MuxLookup(tl_read_state_reg.asUInt, 0.U)(status_map), SpiRegAddress.TL_WRITE_STATUS_REG.asUInt -> MuxLookup(tl_write_state_reg.asUInt, 0.U)(write_status_map), SpiRegAddress.DATA_BUF_PORT.asUInt -> (selected_word.asUInt >> (byte_index << 3.U))(7,0), - SpiRegAddress.BULK_READ_PORT.asUInt -> (selected_word.asUInt >> (byte_index << 3.U))(7,0), ) tlul2spi_q.io.enq.bits := MuxLookup(addr_reg, 0.U(8.W))(read_map) val read_cmd_fire = tl_cmd_reg_write && tl_cmd_reg_data === 1.U val reading_bulk_data = spi_state_reg === SpiState.sBULK_READ_DATA && tlul2spi_q.io.enq.fire - val start_bulk_read = do_write && addr_reg === SpiRegAddress.BULK_READ_PORT.asUInt + val start_bulk_read = do_write && addr_reg === SpiRegAddress.BULK_READ_PORT_H.asUInt bulk_count_reg := Mux(start_bulk_write || start_bulk_read, 0.U, Mux(writing_bulk_data || reading_bulk_data, bulk_count_reg + 1.U, bulk_count_reg)) @@ -314,7 +352,9 @@ // Combinational signal for decrementing byte counter val reading_bulk_data_byte = spi_cmd_state === SpiCmdState.sSendData && byte_received - read_data_buffer(bulk_read_write_ptr) := Mux(tl_to_spi_bulk_q.io.deq.fire, tl_to_spi_bulk_q.io.deq.bits, read_data_buffer(bulk_read_write_ptr)) + read_data_buffer.writePorts(0).enable := tl_to_spi_bulk_q.io.deq.fire + read_data_buffer.writePorts(0).address := bulk_read_write_ptr + read_data_buffer.writePorts(0).data := tl_to_spi_bulk_q.io.deq.bits bulk_read_write_ptr := Mux(tl_to_spi_bulk_q.io.deq.fire, bulk_read_write_ptr + 1.U, bulk_read_write_ptr) spi_bulk_read_ptr := Mux(reading_bulk_data_byte, spi_bulk_read_ptr + 1.U, spi_bulk_read_ptr) @@ -323,22 +363,31 @@ spi_bulk_read_sent_count_reg := Mux(reset_sent_count, 0.U, Mux(reading_bulk_data_byte, spi_bulk_read_sent_count_reg + 1.U, spi_bulk_read_sent_count_reg)) - spi_bulk_read_len_reg := Mux(is_bulk_read_start, completed_byte, spi_bulk_read_len_reg) + spi_bulk_read_len_reg := Cat( + Mux(is_bulk_read_len_h_byte, completed_byte, spi_bulk_read_len_reg(15, 8)), + Mux(is_bulk_read_start, completed_byte, spi_bulk_read_len_reg(7, 0)) + ) mosi_data_reg := Cat(mosi_data_reg(6,0), io.spi.mosi) bit_count_reg := bit_count_reg + 1.U - val read_word_index = spi_bulk_read_ptr(7,4) - val read_byte_index = spi_bulk_read_ptr(3,0) - val selected_read_word = read_data_buffer(read_word_index) - val selected_read_byte = (selected_read_word >> (read_byte_index << 3.U))(7,0) + val read_word_index = spi_bulk_read_ptr >> byte_index_bits + val read_byte_index = spi_bulk_read_ptr(byte_index_bits - 1, 0) + val read_byte_index_reg = RegNext(read_byte_index, 0.U) + read_data_buffer.readPorts(0).enable := true.B + read_data_buffer.readPorts(0).address := read_word_index + val selected_read_word = read_data_buffer.readPorts(0).data + val selected_read_byte = (selected_read_word >> (read_byte_index_reg << 3.U))(7,0) // --- MISO Path Refactor with Forwarding --- // 1. Define the single source of new data and its validity + val selected_status_byte = Mux(cmd_addr === SpiRegAddress.BULK_READ_STATUS_REG_L.asUInt, + bulk_read_bytes_available(7, 0), + bulk_read_bytes_available(15, 8)) val miso_data_source_bits = MuxCase(0.U, Seq( (is_bulk_read_start || reading_bulk_data_byte) -> selected_read_byte, - is_bulk_status_read -> bulk_read_bytes_available, + is_bulk_status_read -> selected_status_byte, tlul2spi_q.io.deq.fire -> tlul2spi_q.io.deq.bits )) val miso_data_source_valid = is_bulk_read_start || reading_bulk_data_byte || is_bulk_status_read || tlul2spi_q.io.deq.fire @@ -393,16 +442,18 @@ a_bits.opcode := Mux(write_fsm_active, TLULOpcodesA.PutFullData.asUInt, TLULOpcodesA.Get.asUInt) a_bits.address := Mux(write_fsm_active, - tl_write_addr_fsm_reg + (tl_write_beat_count_reg << log2Ceil(tlul_p.w)), + tl_write_addr_fsm_reg + (sram_addr_reg << log2Ceil(tlul_p.w)), tl_addr_fsm_reg + (tl_beat_count_reg << log2Ceil(tlul_p.w))) - a_bits.data := Mux(write_fsm_active, write_data_buffer(tl_write_beat_count_reg(3,0)), 0.U) + write_data_buffer.readPorts(2).enable := true.B + write_data_buffer.readPorts(2).address := sram_addr_reg + val tl_write_data = write_data_buffer.readPorts(2).data + a_bits.data := Mux(write_fsm_active, RegNext(tl_write_data, 0.U), 0.U) tl_a_q.io.enq.bits := a_bits - for (i <- 0 until write_data_buffer.length) { - val write_to_buffer = i.U === write_word_index && writing_data_buf - write_data_buffer(i) := Mux(write_to_buffer, write_new_word, write_data_buffer(i)) - } + write_data_buffer.writePorts(0).enable := writing_data_buf + write_data_buffer.writePorts(0).address := write_word_index + write_data_buffer.writePorts(0).data := write_new_word val clear_command = tl_cmd_reg_write && tl_cmd_reg_data === 0.U @@ -451,8 +502,13 @@ !tl_d_q.io.deq.bits.error, tl_write_beat_count_reg + 1.U, tl_write_beat_count_reg) + val is_last_beat_ack = (tl_write_state_reg === TlWriteState.sWaitBeatAck) && tl_d_q.io.deq.fire && (tl_write_beat_count_reg === tl_write_len_fsm_reg) tl_write_beat_count_reg := Mux(write_cmd_fire, 0.U, tl_write_beat_count_next) + val sram_addr_inc = tl_a_q.io.enq.fire && write_fsm_active + val sram_addr_next = Mux(sram_addr_inc, sram_addr_reg + 1.U, sram_addr_reg) + sram_addr_reg := Mux(is_last_beat_ack, 0.U, sram_addr_next) + tl_write_addr_fsm_reg := Mux(write_cmd_fire, tl_addr_reg.asUInt, tl_write_addr_fsm_reg) tl_write_len_fsm_reg := Mux(write_cmd_fire, tl_len_reg, tl_write_len_fsm_reg) }
diff --git a/kelvin_test_utils/spi_constants.py b/kelvin_test_utils/spi_constants.py index 197e63f..adf4cc4 100644 --- a/kelvin_test_utils/spi_constants.py +++ b/kelvin_test_utils/spi_constants.py
@@ -19,14 +19,18 @@ TL_ADDR_REG_1 = 0x01 TL_ADDR_REG_2 = 0x02 TL_ADDR_REG_3 = 0x03 - TL_LEN_REG = 0x04 - TL_CMD_REG = 0x05 - TL_STATUS_REG = 0x06 - DATA_BUF_PORT = 0x07 - TL_WRITE_STATUS_REG = 0x08 - BULK_WRITE_PORT = 0x09 - BULK_READ_PORT = 0x0A - BULK_READ_STATUS_REG = 0x0B + TL_LEN_REG_L = 0x04 + TL_LEN_REG_H = 0x05 + TL_CMD_REG = 0x06 + TL_STATUS_REG = 0x07 + DATA_BUF_PORT = 0x08 + TL_WRITE_STATUS_REG = 0x09 + BULK_WRITE_PORT_L = 0x0A + BULK_WRITE_PORT_H = 0x0B + BULK_READ_PORT_L = 0x0C + BULK_READ_PORT_H = 0x0D + BULK_READ_STATUS_REG_L = 0x0E + BULK_READ_STATUS_REG_H = 0x0F class SpiCommand(IntEnum): CMD_NULL = 0x00
diff --git a/kelvin_test_utils/spi_master.py b/kelvin_test_utils/spi_master.py index 060fba5..9eb22fd 100644 --- a/kelvin_test_utils/spi_master.py +++ b/kelvin_test_utils/spi_master.py
@@ -83,6 +83,13 @@ if wait_cycles > 0: await ClockCycles(self.main_clk, wait_cycles) + async def write_reg_16b(self, base_addr, data, wait_cycles=10): + """Writes a 16-bit value to a register pair via SPI.""" + await self.write_reg(base_addr, data & 0xFF, wait_cycles=0) + await self.write_reg(base_addr + 1, (data >> 8) & 0xFF, wait_cycles=0) + if wait_cycles > 0: + await ClockCycles(self.main_clk, wait_cycles) + async def read_reg(self, reg_addr): """Reads a byte from a register via SPI.""" read_cmd = reg_addr # MSB is 0 for read @@ -106,6 +113,12 @@ await ClockCycles(self.main_clk, 1) return read_data + async def read_spi_domain_reg_16b(self, base_addr): + """Reads a 16-bit value from a register pair in the SPI clock domain.""" + val_l = await self.read_spi_domain_reg(base_addr) + val_h = await self.read_spi_domain_reg(base_addr + 1) + return (val_h << 8) | val_l + async def poll_reg_for_value(self, reg_addr, expected_value, max_polls=20): """Polls a register until it reads an expected value.""" read_cmd = reg_addr # MSB is 0 for read @@ -149,8 +162,11 @@ await self._clock_byte((target_addr >> 24) & 0xFF) # Write beats - await self._clock_byte(CMD_WRITE | SpiRegAddress.TL_LEN_REG) - await self._clock_byte(len(data) - 1) + num_beats = len(data) + await self._clock_byte(CMD_WRITE | SpiRegAddress.TL_LEN_REG_L) + await self._clock_byte((num_beats - 1) & 0xFF) + await self._clock_byte(CMD_WRITE | SpiRegAddress.TL_LEN_REG_H) + await self._clock_byte(((num_beats - 1) >> 8) & 0xFF) # Write data using bulk transfer all_data_bytes = [] @@ -159,9 +175,11 @@ all_data_bytes.append((beat >> (i * 8)) & 0xFF) # Command for bulk write - await self._clock_byte(CMD_WRITE | SpiRegAddress.BULK_WRITE_PORT) - # Length - await self._clock_byte(len(all_data_bytes) - 1) + num_bytes = len(all_data_bytes) + await self._clock_byte(CMD_WRITE | SpiRegAddress.BULK_WRITE_PORT_L) + await self._clock_byte((num_bytes - 1) & 0xFF) + await self._clock_byte(CMD_WRITE | SpiRegAddress.BULK_WRITE_PORT_H) + await self._clock_byte(((num_bytes - 1) >> 8) & 0xFF) # Data stream for byte in all_data_bytes: await self._clock_byte(byte) @@ -180,12 +198,12 @@ await self.start_clock() - # Command byte for bulk write - await self._clock_byte(CMD_WRITE | SpiRegAddress.BULK_WRITE_PORT) - - # Length byte + # Command and Length for bulk write (L, H) num_bytes = len(data) - await self._clock_byte(num_bytes - 1) + await self._clock_byte(CMD_WRITE | SpiRegAddress.BULK_WRITE_PORT_L) + await self._clock_byte((num_bytes - 1) & 0xFF) + await self._clock_byte(CMD_WRITE | SpiRegAddress.BULK_WRITE_PORT_H) + await self._clock_byte(((num_bytes - 1) >> 8) & 0xFF) # Data stream for byte in data: @@ -202,21 +220,18 @@ await self.start_clock() - # Command byte to initiate a bulk read (this is a WRITE command) - await self._clock_byte(CMD_WRITE | SpiRegAddress.BULK_READ_PORT) + # Command and Length to initiate a bulk read (L, H) + await self._clock_byte(CMD_WRITE | SpiRegAddress.BULK_READ_PORT_L) + await self._clock_byte((num_bytes - 1) & 0xFF) + await self._clock_byte(CMD_WRITE | SpiRegAddress.BULK_READ_PORT_H) + await self._clock_byte(((num_bytes - 1) >> 8) & 0xFF) - # Length byte - await self._clock_byte(num_bytes - 1) + # The MISO pipeline has latency. The first dummy transfer flushes a junk byte. + await self._clock_byte(0x00) - # The MISO pipeline is two bytes deep. We need to send two dummy transfers - # to discard the junk bytes from the command/length phases before the - # first valid data byte is received. - await self._clock_byte(0x00) # Flush junk from command phase - - # Read data stream + # The subsequent transfers clock in the actual data. received_bytes = [] for _ in range(num_bytes): - # The data is clocked out on MISO during this dummy byte transfer byte_in = await self._clock_byte(0x00) received_bytes.append(byte_in)
diff --git a/tests/cocotb/tlul/BUILD b/tests/cocotb/tlul/BUILD index c8f4683..2044827 100644 --- a/tests/cocotb/tlul/BUILD +++ b/tests/cocotb/tlul/BUILD
@@ -358,6 +358,9 @@ "test_tlul_bulk_write", "test_tlul_bulk_read", "test_large_tlul_transfer", + "test_large_packed_write_transaction", + "test_large_pipelined_read", + "test_large_write_then_pipelined_read", ] # END_TESTCASES_FOR_spi2tlul_cocotb @@ -374,6 +377,7 @@ "deps": [ "//kelvin_test_utils:TileLinkULInterface", "//kelvin_test_utils:spi_master", + "//kelvin_test_utils:spi_constants", ], }, verilator_model = "//hdl/chisel/src/bus:spi2tlul_128_model",
diff --git a/tests/cocotb/tlul/test_spi_to_tlul.py b/tests/cocotb/tlul/test_spi_to_tlul.py index 419d68e..976b663 100644 --- a/tests/cocotb/tlul/test_spi_to_tlul.py +++ b/tests/cocotb/tlul/test_spi_to_tlul.py
@@ -20,6 +20,7 @@ from cocotb.triggers import RisingEdge, ClockCycles, FallingEdge from kelvin_test_utils.TileLinkULInterface import TileLinkULInterface from kelvin_test_utils.spi_master import SPIMaster +from kelvin_test_utils.spi_constants import SpiRegAddress, SpiCommand, TlStatus async def setup_dut(dut, spi_master): # Main clock started by the test @@ -50,10 +51,10 @@ # Write Transaction write_data = random.randint(0, 255) - await spi_master.write_reg(0x04, write_data) + await spi_master.write_reg(SpiRegAddress.TL_LEN_REG_L, write_data) # Read Transaction - read_data = await spi_master.read_reg(0x04) + read_data = await spi_master.read_reg(SpiRegAddress.TL_LEN_REG_L) assert read_data == write_data, f"Read data 0x{read_data:x} does not match written data 0x{write_data:x}" await ClockCycles(dut.clock, 20) @@ -105,20 +106,20 @@ # Write address (32 bits) byte by byte for j in range(4): addr_byte = (target_addr >> (j * 8)) & 0xFF - await spi_master.write_reg(0x00 + j, addr_byte) + await spi_master.write_reg(SpiRegAddress.TL_ADDR_REG_0 + j, addr_byte) # Write length (0 means 1 beat) - await spi_master.write_reg(0x04, 0x00) + await spi_master.write_reg_16b(SpiRegAddress.TL_LEN_REG_L, 0) # 2. Issue the read command - await spi_master.write_reg(0x05, 0x01, wait_cycles=0) + await spi_master.write_reg(SpiRegAddress.TL_CMD_REG, SpiCommand.CMD_READ_START, wait_cycles=0) # --- Verification --- # 1. Poll the status register until the transaction is done - assert await spi_master.poll_reg_for_value(0x06, 0x02), "Timed out waiting for status to be Done" + assert await spi_master.poll_reg_for_value(SpiRegAddress.TL_STATUS_REG, TlStatus.DONE), "Timed out waiting for status to be Done" # 2. Check that the correct number of bytes are available - bytes_available = await spi_master.read_spi_domain_reg(0x0B) + bytes_available = await spi_master.read_spi_domain_reg_16b(SpiRegAddress.BULK_READ_STATUS_REG_L) assert bytes_available == 16 # 3. Read the data from the buffer port using the new bulk read @@ -132,7 +133,7 @@ assert read_data == expected_data # 4. Clear the status to return FSM to Idle - await spi_master.write_reg(0x05, 0x00) + await spi_master.write_reg(SpiRegAddress.TL_CMD_REG, SpiCommand.CMD_NULL) await responder_task @@ -184,24 +185,21 @@ # Write address (32 bits) byte by byte for j in range(4): addr_byte = (target_addr >> (j * 8)) & 0xFF - await spi_master.write_reg(0x00 + j, addr_byte) + await spi_master.write_reg(SpiRegAddress.TL_ADDR_REG_0 + j, addr_byte) # Write length (N-1 for N beats) - await spi_master.write_reg(0x04, num_beats - 1) + await spi_master.write_reg_16b(SpiRegAddress.TL_LEN_REG_L, num_beats - 1) # 2. Issue the read command - await spi_master.write_reg(0x05, 0x01, wait_cycles=0) - - # Add a delay to allow the status to propagate across the CDC - await ClockCycles(dut.clock, 20) + await spi_master.write_reg(SpiRegAddress.TL_CMD_REG, SpiCommand.CMD_READ_START, wait_cycles=0) # --- Verification --- # 1. Poll the status register until the transaction is done - assert await spi_master.poll_reg_for_value(0x06, 0x02), "Timed out waiting for status to be Done" + assert await spi_master.poll_reg_for_value(SpiRegAddress.TL_STATUS_REG, TlStatus.DONE), "Timed out waiting for status to be Done" # 2. Check that the correct number of bytes are available bytes_to_read = num_beats * 16 - bytes_available = await spi_master.read_spi_domain_reg(0x0B) + bytes_available = await spi_master.read_spi_domain_reg_16b(SpiRegAddress.BULK_READ_STATUS_REG_L) assert bytes_available == bytes_to_read # 3. Read the data from the buffer port using the new bulk read @@ -219,7 +217,7 @@ assert read_data == expected_data # 4. Clear the status to return FSM to Idle - await spi_master.write_reg(0x05, 0x00) + await spi_master.write_reg(SpiRegAddress.TL_CMD_REG, SpiCommand.CMD_NULL) await responder_task @@ -281,20 +279,20 @@ # Write address (32 bits) byte by byte for j in range(4): addr_byte = (target_addr >> (j * 8)) & 0xFF - await spi_master.write_reg(0x00 + j, addr_byte) + await spi_master.write_reg(SpiRegAddress.TL_ADDR_REG_0 + j, addr_byte) # Write length (0 means 1 beat) - await spi_master.write_reg(0x04, 0x00) + await spi_master.write_reg_16b(SpiRegAddress.TL_LEN_REG_L, 0) # 3. Issue the write command - await spi_master.write_reg(0x05, 0x02, wait_cycles=20) # Start write command + await spi_master.write_reg(SpiRegAddress.TL_CMD_REG, SpiCommand.CMD_WRITE_START, wait_cycles=20) # Start write command # --- Verification --- # 1. Poll the status register until the transaction is done - assert await spi_master.poll_reg_for_value(0x08, 0x02), "Timed out waiting for write status to be Done" + assert await spi_master.poll_reg_for_value(SpiRegAddress.TL_WRITE_STATUS_REG, TlStatus.DONE), "Timed out waiting for write status to be Done" # 4. Clear the status to return FSM to Idle - await spi_master.write_reg(0x05, 0x00) + await spi_master.write_reg(SpiRegAddress.TL_CMD_REG, SpiCommand.CMD_NULL) # Wait for the responder to finish handling all requests await responder_task @@ -361,17 +359,17 @@ # Write address (32 bits) byte by byte for j in range(4): addr_byte = (target_addr >> (j * 8)) & 0xFF - await spi_master.write_reg(0x00 + j, addr_byte) + await spi_master.write_reg(SpiRegAddress.TL_ADDR_REG_0 + j, addr_byte) # Write length (N-1 for N beats) - await spi_master.write_reg(0x04, num_beats - 1) + await spi_master.write_reg_16b(SpiRegAddress.TL_LEN_REG_L, num_beats - 1) # 3. Issue the write command - await spi_master.write_reg(0x05, 0x02, wait_cycles=20) # Start write command + await spi_master.write_reg(SpiRegAddress.TL_CMD_REG, SpiCommand.CMD_WRITE_START, wait_cycles=20) # Start write command # --- Verification --- # 1. Poll the status register until the transaction is done - assert await spi_master.poll_reg_for_value(0x08, 0x02), "Timed out waiting for write status to be Done" + assert await spi_master.poll_reg_for_value(SpiRegAddress.TL_WRITE_STATUS_REG, TlStatus.DONE), "Timed out waiting for write status to be Done" # 2. Wait for the responder to finish await responder_task @@ -381,7 +379,7 @@ assert received_data_list == expected_data_list, f"Received data {received_data_list} does not match expected data {expected_data_list}" # 4. Clear the status to return FSM to Idle - await spi_master.write_reg(0x05, 0x00) + await spi_master.write_reg(SpiRegAddress.TL_CMD_REG, SpiCommand.CMD_NULL) @cocotb.test() async def test_packed_write_transaction(dut): @@ -425,8 +423,8 @@ data=data, ) - assert await spi_master.poll_reg_for_value(0x08, 0x02), "Timed out waiting for write status to be Done" - await spi_master.write_reg(0x05, 0x00) + assert await spi_master.poll_reg_for_value(SpiRegAddress.TL_WRITE_STATUS_REG, TlStatus.DONE), "Timed out waiting for write status to be Done" + await spi_master.write_reg(SpiRegAddress.TL_CMD_REG, SpiCommand.CMD_NULL) await responder_task @@ -485,16 +483,16 @@ target_addr = 0x40003000 for j in range(4): addr_byte = (target_addr >> (j * 8)) & 0xFF - await spi_master.write_reg(0x00 + j, addr_byte) + await spi_master.write_reg(SpiRegAddress.TL_ADDR_REG_0 + j, addr_byte) - await spi_master.write_reg(0x04, num_beats - 1) + await spi_master.write_reg_16b(SpiRegAddress.TL_LEN_REG_L, num_beats - 1) # 3. Issue the write command - await spi_master.write_reg(0x05, 0x02, wait_cycles=20) + await spi_master.write_reg(SpiRegAddress.TL_CMD_REG, SpiCommand.CMD_WRITE_START, wait_cycles=20) # --- Verification --- # 1. Poll the status register until the transaction is done - assert await spi_master.poll_reg_for_value(0x08, 0x02), "Timed out waiting for write status to be Done" + assert await spi_master.poll_reg_for_value(SpiRegAddress.TL_WRITE_STATUS_REG, TlStatus.DONE), "Timed out waiting for write status to be Done" # 2. Wait for the responder to finish await responder_task @@ -504,7 +502,7 @@ assert received_data_list == expected_data_list # 4. Clear the status to return FSM to Idle - await spi_master.write_reg(0x05, 0x00) + await spi_master.write_reg(SpiRegAddress.TL_CMD_REG, SpiCommand.CMD_NULL) @cocotb.test() @@ -554,18 +552,18 @@ target_addr = 0x40001000 for j in range(4): addr_byte = (target_addr >> (j * 8)) & 0xFF - await spi_master.write_reg(0x00 + j, addr_byte) + await spi_master.write_reg(SpiRegAddress.TL_ADDR_REG_0 + j, addr_byte) - await spi_master.write_reg(0x04, num_beats - 1) + await spi_master.write_reg_16b(SpiRegAddress.TL_LEN_REG_L, num_beats - 1) # 2. Issue the read command - await spi_master.write_reg(0x05, 0x01, wait_cycles=0) + await spi_master.write_reg(SpiRegAddress.TL_CMD_REG, SpiCommand.CMD_READ_START, wait_cycles=0) # 3. Poll the status register until the transaction is done - assert await spi_master.poll_reg_for_value(0x06, 0x02), "Timed out waiting for status to be Done" + assert await spi_master.poll_reg_for_value(SpiRegAddress.TL_STATUS_REG, TlStatus.DONE), "Timed out waiting for status to be Done" # 3a. Read the bulk read status register - bytes_available = await spi_master.read_spi_domain_reg(0x0B) + bytes_available = await spi_master.read_spi_domain_reg_16b(SpiRegAddress.BULK_READ_STATUS_REG_L) dut._log.info(f"BULK_READ_STATUS_REG = {bytes_available}") # 4. Initiate the bulk read from the data buffer @@ -585,7 +583,7 @@ assert read_data_list == expected_data_list, f"{[hex(x) for x in read_data_list]} =/= {[hex(x) for x in expected_data_list]}" # 3. Clear the status to return FSM to Idle - await spi_master.write_reg(0x05, 0x00) + await spi_master.write_reg(SpiRegAddress.TL_CMD_REG, SpiCommand.CMD_NULL) await responder_task @@ -686,13 +684,13 @@ for j in range(4): addr_byte = (current_addr >> (j * 8)) & 0xFF - await spi_master.write_reg(0x00 + j, addr_byte) + await spi_master.write_reg(SpiRegAddress.TL_ADDR_REG_0 + j, addr_byte) - await spi_master.write_reg(0x04, num_beats - 1) - await spi_master.write_reg(0x05, 0x02, wait_cycles=20) + await spi_master.write_reg_16b(SpiRegAddress.TL_LEN_REG_L, num_beats - 1) + await spi_master.write_reg(SpiRegAddress.TL_CMD_REG, SpiCommand.CMD_WRITE_START, wait_cycles=20) - assert await spi_master.poll_reg_for_value(0x08, 0x02), f"Timed out waiting for write status at addr {current_addr:x}" - await spi_master.write_reg(0x05, 0x00) + assert await spi_master.poll_reg_for_value(SpiRegAddress.TL_WRITE_STATUS_REG, TlStatus.DONE), f"Timed out waiting for write status at addr {current_addr:x}" + await spi_master.write_reg(SpiRegAddress.TL_CMD_REG, SpiCommand.CMD_NULL) dut._log.info("Write phase complete.") # --- Main Test Logic: Read Phase --- @@ -704,20 +702,20 @@ for j in range(4): addr_byte = (current_addr >> (j * 8)) & 0xFF - await spi_master.write_reg(0x00 + j, addr_byte) + await spi_master.write_reg(SpiRegAddress.TL_ADDR_REG_0 + j, addr_byte) - await spi_master.write_reg(0x04, num_beats - 1) - await spi_master.write_reg(0x05, 0x01, wait_cycles=0) + await spi_master.write_reg_16b(SpiRegAddress.TL_LEN_REG_L, num_beats - 1) + await spi_master.write_reg(SpiRegAddress.TL_CMD_REG, SpiCommand.CMD_READ_START, wait_cycles=0) - assert await spi_master.poll_reg_for_value(0x06, 0x02), f"Timed out waiting for read status at addr {current_addr:x}" + assert await spi_master.poll_reg_for_value(SpiRegAddress.TL_STATUS_REG, TlStatus.DONE), f"Timed out waiting for read status at addr {current_addr:x}" - bytes_available = await spi_master.read_spi_domain_reg(0x0B) + bytes_available = await spi_master.read_spi_domain_reg_16b(SpiRegAddress.BULK_READ_STATUS_REG_L) assert bytes_available == read_chunk_size read_data_bytes = await spi_master.bulk_read(read_chunk_size) read_back_data.extend(read_data_bytes) - await spi_master.write_reg(0x05, 0x00) + await spi_master.write_reg(SpiRegAddress.TL_CMD_REG, SpiCommand.CMD_NULL) dut._log.info("Read phase complete.") # --- Verification --- @@ -728,3 +726,277 @@ await responder_task dut._log.info(f"--- {total_size // 1024}KB Transfer Test Passed ---") + +@cocotb.test(timeout_time=300, timeout_unit="sec") +async def test_large_packed_write_transaction(dut): + """Tests a single large (4KB) packed write transaction.""" + clock = Clock(dut.clock, 10) + cocotb.start_soon(clock.start()) + + spi_master = SPIMaster( + clk=dut.io_spi_clk, + csb=dut.io_spi_csb, + mosi=dut.io_spi_mosi, + miso=dut.io_spi_miso, + main_clk=dut.clock, + log=dut._log + ) + await setup_dut(dut, spi_master) + tl_device = TileLinkULInterface(dut, device_if_name="io_tl", width=128) + await tl_device.init() + + num_beats = 256 # 256 beats * 16 bytes/beat = 4096 bytes + dut._log.info(f"Generating {num_beats * 16 // 1024}KB of random data...") + golden_data = [random.randint(0, (1 << 128) - 1) for _ in range(num_beats)] + dut._log.info("Data generation complete.") + + async def device_responder(): + dut._log.info(f"Device responder waiting for {num_beats} beats...") + for i in range(num_beats): + req = await tl_device.device_get_request() + assert int(req['opcode']) in [0, 1], f"Expected PutFullData or PutPartialData, got opcode {req['opcode']}" + assert req['data'] == golden_data[i], f"Data mismatch on beat {i}" + + # Send an AccessAck after each beat + await tl_device.device_respond( + opcode=0, # AccessAck + param=0, + size=req['size'], + source=req['source'], + error=0, + width=128 + ) + dut._log.info("Device responder received all beats successfully.") + + responder_task = cocotb.start_soon(device_responder()) + + await spi_master.packed_write_transaction( + target_addr=0x40001000, + data=golden_data, + ) + + # Need large max_polls here for the large transfer size. + assert await spi_master.poll_reg_for_value(SpiRegAddress.TL_WRITE_STATUS_REG, TlStatus.DONE, max_polls=2000), "Timed out waiting for write status to be Done" + await spi_master.write_reg(SpiRegAddress.TL_CMD_REG, SpiCommand.CMD_NULL) + await responder_task + dut._log.info("--- Large Packed Write Test Passed ---") + + +@cocotb.test(timeout_time=400, timeout_unit="sec") +async def test_large_pipelined_read(dut): + """Tests two back-to-back large (2KB) read transactions.""" + clock = Clock(dut.clock, 10) + cocotb.start_soon(clock.start()) + + spi_master = SPIMaster( + clk=dut.io_spi_clk, + csb=dut.io_spi_csb, + mosi=dut.io_spi_mosi, + miso=dut.io_spi_miso, + main_clk=dut.clock, + log=dut._log + ) + await setup_dut(dut, spi_master) + tl_device = TileLinkULInterface(dut, device_if_name="io_tl", width=128) + await tl_device.init() + + total_size = 4096 + read_chunk_size = 2048 + bus_width_bytes = 128 // 8 + num_beats_total = total_size // bus_width_bytes + num_beats_per_read = read_chunk_size // bus_width_bytes + base_addr = 0x20000000 + + dut._log.info(f"Generating {total_size // 1024}KB of random data...") + golden_data_words = [random.randint(0, (1 << 128) - 1) for _ in range(num_beats_total)] + golden_data_bytes = bytearray() + for word in golden_data_words: + golden_data_bytes.extend(word.to_bytes(16, 'little')) + dut._log.info("Data generation complete.") + + # --- Device Responder Task --- + async def device_responder(): + mem = {} # Byte-addressable memory + # Pre-populate memory + for i, byte_val in enumerate(golden_data_bytes): + mem[base_addr + i] = byte_val + + # --- Read Phase: Serve data from memory for two separate reads --- + dut._log.info(f"Device responder waiting for {num_beats_total} read beats...") + for i in range(num_beats_total): + req = await tl_device.device_get_request() + assert int(req['opcode']) == 4 + addr = int(req['address']) + response_data = 0 + for byte_idx in range(bus_width_bytes): + byte_val = mem.get(addr + byte_idx, 0) + response_data |= (byte_val << (byte_idx * 8)) + await tl_device.device_respond( + opcode=1, param=0, size=req['size'], source=req['source'], data=response_data, error=0, width=128 + ) + dut._log.info("Device responder read phase complete.") + + responder_task = cocotb.start_soon(device_responder()) + + # --- Main Test Logic --- + # Read Phase: Read the 4KB back in two 2KB chunks + read_data_bytes = bytearray() + + # First 2KB read + dut._log.info("Starting first 2KB read from device...") + await spi_master.write_reg_16b(SpiRegAddress.TL_LEN_REG_L, num_beats_per_read - 1) + for j in range(4): + addr_byte = (base_addr >> (j * 8)) & 0xFF + await spi_master.write_reg(SpiRegAddress.TL_ADDR_REG_0 + j, addr_byte) + await spi_master.write_reg(SpiRegAddress.TL_CMD_REG, SpiCommand.CMD_READ_START, wait_cycles=0) + assert await spi_master.poll_reg_for_value(SpiRegAddress.TL_STATUS_REG, TlStatus.DONE, max_polls=2000), "Timed out waiting for first read status to be Done" + bytes_available = await spi_master.read_spi_domain_reg_16b(SpiRegAddress.BULK_READ_STATUS_REG_L) + assert bytes_available == read_chunk_size, f"Expected {read_chunk_size} bytes available, but got {bytes_available}" + read_data_bytes.extend(await spi_master.bulk_read(read_chunk_size)) + await spi_master.write_reg(SpiRegAddress.TL_CMD_REG, SpiCommand.CMD_NULL) + dut._log.info("First 2KB read complete.") + + # Second 2KB read + dut._log.info("Starting second 2KB read from device...") + second_chunk_addr = base_addr + read_chunk_size + await spi_master.write_reg_16b(SpiRegAddress.TL_LEN_REG_L, num_beats_per_read - 1) + for j in range(4): + addr_byte = (second_chunk_addr >> (j * 8)) & 0xFF + await spi_master.write_reg(SpiRegAddress.TL_ADDR_REG_0 + j, addr_byte) + await spi_master.write_reg(SpiRegAddress.TL_CMD_REG, SpiCommand.CMD_READ_START, wait_cycles=0) + assert await spi_master.poll_reg_for_value(SpiRegAddress.TL_STATUS_REG, TlStatus.DONE, max_polls=2000), "Timed out waiting for second read status to be Done" + bytes_available = await spi_master.read_spi_domain_reg_16b(SpiRegAddress.BULK_READ_STATUS_REG_L) + assert bytes_available == read_chunk_size, f"Expected {read_chunk_size} bytes available, but got {bytes_available}" + read_data_bytes.extend(await spi_master.bulk_read(read_chunk_size)) + await spi_master.write_reg(SpiRegAddress.TL_CMD_REG, SpiCommand.CMD_NULL) + dut._log.info("Second 2KB read complete.") + + # --- Verification --- + dut._log.info("Verifying data...") + assert read_data_bytes == golden_data_bytes, "Read-back data does not match golden data" + dut._log.info("Data verification successful!") + + await responder_task + dut._log.info("--- Large Pipelined Read Test Passed ---") + + +@cocotb.test(timeout_time=500, timeout_unit="sec") +async def test_large_write_then_pipelined_read(dut): + """Tests a large write (4KB) followed by two pipelined reads (2KB each).""" + clock = Clock(dut.clock, 10) + cocotb.start_soon(clock.start()) + + spi_master = SPIMaster( + clk=dut.io_spi_clk, + csb=dut.io_spi_csb, + mosi=dut.io_spi_mosi, + miso=dut.io_spi_miso, + main_clk=dut.clock, + log=dut._log + ) + await setup_dut(dut, spi_master) + tl_device = TileLinkULInterface(dut, device_if_name="io_tl", width=128) + await tl_device.init() + + total_size = 4096 + read_chunk_size = 2048 + bus_width_bytes = 128 // 8 + num_beats_total = total_size // bus_width_bytes + num_beats_per_read = read_chunk_size // bus_width_bytes + base_addr = 0x20000000 + + dut._log.info(f"Generating {total_size // 1024}KB of random data...") + golden_data = [random.randint(0, (1 << 128) - 1) for _ in range(num_beats_total)] + dut._log.info("Data generation complete.") + + # --- Device Responder Task --- + async def device_responder(): + mem = {} # Byte-addressable memory + + # --- Write Phase: Populate memory --- + dut._log.info(f"Device responder waiting for {num_beats_total} write beats...") + for i in range(num_beats_total): + req = await tl_device.device_get_request() + assert int(req['opcode']) in [0, 1] + addr = int(req['address']) + data = int(req['data']) + for byte_idx in range(bus_width_bytes): + byte_val = (data >> (byte_idx * 8)) & 0xFF + mem[addr + byte_idx] = byte_val + await tl_device.device_respond( + opcode=0, param=0, size=req['size'], source=req['source'], error=0, width=128 + ) + dut._log.info("Device responder write phase complete.") + + # --- Read Phase: Serve data from memory for two separate reads --- + dut._log.info(f"Device responder waiting for {num_beats_total} read beats...") + for i in range(num_beats_total): + req = await tl_device.device_get_request() + assert int(req['opcode']) == 4 + addr = int(req['address']) + response_data = 0 + for byte_idx in range(bus_width_bytes): + byte_val = mem.get(addr + byte_idx, 0) + response_data |= (byte_val << (byte_idx * 8)) + await tl_device.device_respond( + opcode=1, param=0, size=req['size'], source=req['source'], data=response_data, error=0, width=128 + ) + dut._log.info("Device responder read phase complete.") + + responder_task = cocotb.start_soon(device_responder()) + + # --- Main Test Logic --- + # 1. Write Phase: Write the 4KB of data to the device's memory + dut._log.info("Starting 4KB write to device...") + await spi_master.packed_write_transaction( + target_addr=base_addr, + data=golden_data, + ) + assert await spi_master.poll_reg_for_value(SpiRegAddress.TL_WRITE_STATUS_REG, TlStatus.DONE, max_polls=2000), "Timed out waiting for write status to be Done" + await spi_master.write_reg(SpiRegAddress.TL_CMD_REG, SpiCommand.CMD_NULL) + dut._log.info("Write to device complete.") + + # 2. Read Phase: Read the 4KB back in two 2KB chunks + read_data_bytes = bytearray() + + # First 2KB read + dut._log.info("Starting first 2KB read from device...") + await spi_master.write_reg_16b(SpiRegAddress.TL_LEN_REG_L, num_beats_per_read - 1) + for j in range(4): + addr_byte = (base_addr >> (j * 8)) & 0xFF + await spi_master.write_reg(SpiRegAddress.TL_ADDR_REG_0 + j, addr_byte) + await spi_master.write_reg(SpiRegAddress.TL_CMD_REG, SpiCommand.CMD_READ_START, wait_cycles=0) + assert await spi_master.poll_reg_for_value(SpiRegAddress.TL_STATUS_REG, TlStatus.DONE, max_polls=2000), "Timed out waiting for first read status to be Done" + bytes_available = await spi_master.read_spi_domain_reg_16b(SpiRegAddress.BULK_READ_STATUS_REG_L) + assert bytes_available == read_chunk_size, f"Expected {read_chunk_size} bytes available, but got {bytes_available}" + read_data_bytes.extend(await spi_master.bulk_read(read_chunk_size)) + await spi_master.write_reg(SpiRegAddress.TL_CMD_REG, SpiCommand.CMD_NULL) + dut._log.info("First 2KB read complete.") + + # Second 2KB read + dut._log.info("Starting second 2KB read from device...") + second_chunk_addr = base_addr + read_chunk_size + await spi_master.write_reg_16b(SpiRegAddress.TL_LEN_REG_L, num_beats_per_read - 1) + for j in range(4): + addr_byte = (second_chunk_addr >> (j * 8)) & 0xFF + await spi_master.write_reg(SpiRegAddress.TL_ADDR_REG_0 + j, addr_byte) + await spi_master.write_reg(SpiRegAddress.TL_CMD_REG, SpiCommand.CMD_READ_START, wait_cycles=0) + assert await spi_master.poll_reg_for_value(SpiRegAddress.TL_STATUS_REG, TlStatus.DONE, max_polls=2000), "Timed out waiting for second read status to be Done" + bytes_available = await spi_master.read_spi_domain_reg_16b(SpiRegAddress.BULK_READ_STATUS_REG_L) + assert bytes_available == read_chunk_size, f"Expected {read_chunk_size} bytes available, but got {bytes_available}" + read_data_bytes.extend(await spi_master.bulk_read(read_chunk_size)) + await spi_master.write_reg(SpiRegAddress.TL_CMD_REG, SpiCommand.CMD_NULL) + dut._log.info("Second 2KB read complete.") + + # --- Verification --- + dut._log.info("Verifying data...") + golden_data_bytes = bytearray() + for word in golden_data: + golden_data_bytes.extend(word.to_bytes(16, 'little')) + + assert read_data_bytes == golden_data_bytes, "Read-back data does not match golden data" + dut._log.info("Data verification successful!") + + await responder_task + dut._log.info("--- Large Write/Pipelined Read Test Passed ---") +