Files
retroSoC/tests/rtl/sdio_dma_stress_tb.sv
T
maksyuki a8325ab6b7 feat: add SDIO support and related tests
- Introduced a new testbench for the SDIO wrapper () to validate APB interactions.
- Updated  to include IRQ handling for SDIO0 and SDIO1.
- Enhanced  to account for performance management of SDIO0 and SDIO1.
- Modified memory map tests to include SDIO base addresses and performance wait offsets.
- Updated pin mapping tests to include SDIO1 signals.
- Created a comprehensive suite of SDIO tests () covering various functionalities including CRC, command engine, DMA, and standalone operations.
- Added a new test for verifying SDIO register parity against the RTL definition.
- Adjusted SOC topology tests to reflect the addition of SDIO in the IRQ and APB fabric.
2026-08-20 10:03:51 +08:00

315 lines
10 KiB
Systemverilog

// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// retroSoC is licensed under Mulan PSL v2.
// You can use this software according to the terms and conditions of the Mulan PSL v2.
// You may obtain a copy of the Mulan PSL v2 at:
// http://license.coscl.org.cn/MulanPSL2
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
// INCLUDING, BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A
// PARTICULAR PURPOSE.
// See the Mulan PSL v2 for more details.
`timescale 1ns / 1ps
`include "axi4_define.svh"
module sdio_dma_stress_tb;
logic clk_i = 1'b0;
logic rst_n_i = 1'b0;
logic start_i = 1'b0;
logic abort_i = 1'b0;
logic direction_i = 1'b1;
logic [31:0] desc_base_i = 32'd0;
logic [15:0] desc_count_i = 16'd1;
logic [31:0] total_bytes_i = 32'd0;
logic data_in_valid_i = 1'b0;
logic data_in_ready_o;
logic [31:0] data_in_i = 32'd0;
logic [ 3:0] data_in_strb_i = 4'hF;
logic data_in_last_i = 1'b0;
logic data_out_valid_o;
logic data_out_ready_i = 1'b1;
logic [31:0] data_out_o;
logic [ 3:0] data_out_strb_o;
logic data_out_last_o;
logic busy_o;
logic done_o;
logic error_o;
logic [ 7:0] error_code_o;
logic [31:0] current_desc_o;
logic [31:0] bytes_done_o;
logic [31:0] error_addr_o;
integer output_count;
integer guard;
logic done_seen;
integer abort_read_bursts;
axi4_if #(
.ADDR_WIDTH(32),
.DATA_WIDTH(32),
.ID_WIDTH (1),
.USER_WIDTH(1)
) dma_axi4 (
.aclk (clk_i),
.aresetn(rst_n_i)
);
always #5 clk_i = ~clk_i;
sdio_axi_memory_responder #(
.DepthWords(16384)
) u_memory (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.axi4 (dma_axi4)
);
sdio_dma u_sdio_dma (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.start_i (start_i),
.abort_i (abort_i),
.direction_i (direction_i),
.desc_base_i (desc_base_i),
.desc_count_i (desc_count_i),
.total_bytes_i (total_bytes_i),
.data_in_valid_i (data_in_valid_i),
.data_in_ready_o (data_in_ready_o),
.data_in_i (data_in_i),
.data_in_strb_i (data_in_strb_i),
.data_in_last_i (data_in_last_i),
.data_out_valid_o(data_out_valid_o),
.data_out_ready_i(data_out_ready_i),
.data_out_o (data_out_o),
.data_out_strb_o (data_out_strb_o),
.data_out_last_o (data_out_last_o),
.busy_o (busy_o),
.done_o (done_o),
.error_o (error_o),
.error_code_o (error_code_o),
.current_desc_o (current_desc_o),
.bytes_done_o (bytes_done_o),
.error_addr_o (error_addr_o),
.dma_axi4 (dma_axi4)
);
always @(posedge clk_i) begin
if (data_out_valid_o && data_out_ready_i) begin
output_count = output_count + 1;
end
if (done_o) begin
done_seen = 1'b1;
end
end
task automatic pulse_start;
begin
@(negedge clk_i);
start_i = 1'b1;
@(negedge clk_i);
start_i = 1'b0;
end
endtask
task automatic wait_done;
logic seen_busy;
begin
guard = 0;
seen_busy = busy_o;
while ((!seen_busy || !done_o) && (guard < 200000)) begin
@(posedge clk_i);
guard = guard + 1;
seen_busy = seen_busy || busy_o;
end
if (guard >= 200000) begin
$fatal(1, "SDIO DMA stress transfer did not complete");
end
end
endtask
task automatic clear_run_state;
begin
rst_n_i = 1'b0;
repeat (3) @(posedge clk_i);
rst_n_i = 1'b1;
start_i = 1'b0;
abort_i = 1'b0;
direction_i = 1'b1;
data_out_ready_i = 1'b1;
data_in_valid_i = 1'b0;
output_count = 0;
done_seen = 1'b0;
u_memory.clear_errors();
u_memory.set_backpressure(0, 0, 0, 0);
end
endtask
initial begin
u_memory.clear_memory();
u_memory.write_word(32'h0000_0000, 32'h0000_0100);
u_memory.write_word(32'h0000_0004, 32'd64);
u_memory.write_word(32'h0000_0008, 32'd0);
u_memory.write_word(32'h0000_000C, 32'h0000_0005);
for (integer index = 0; index < 16; index++) begin
u_memory.write_word(32'h0000_0100 + (index * 4), 32'h1000_0000 + index);
end
repeat (3) @(posedge clk_i);
rst_n_i = 1'b1;
done_seen = 1'b0;
// A full 16-beat payload burst under independent channel backpressure.
total_bytes_i = 32'd64;
$display("DMA STRESS full burst");
u_memory.set_backpressure(2, 3, 2, 2);
pulse_start();
wait_done();
if (error_o || (output_count != 16) || (u_memory.max_read_beats != 16)) begin
$fatal(1, "16-beat DMA contract failed err=%b outputs=%0d max=%0d", error_o, output_count,
u_memory.max_read_beats);
end
// The same payload geometry split at the 4 KiB boundary.
clear_run_state();
u_memory.write_word(32'h0000_0000, 32'h0000_0FFC);
u_memory.write_word(32'h0000_0004, 32'd8);
u_memory.write_word(32'h0000_0008, 32'd0);
u_memory.write_word(32'h0000_000C, 32'h0000_0005);
u_memory.write_word(32'h0000_0FFC, 32'hAAAA_0001);
u_memory.write_word(32'h0000_1000, 32'hAAAA_0002);
total_bytes_i = 32'd8;
$display("DMA STRESS split");
pulse_start();
wait_done();
if (error_o || (u_memory.read_burst_count < 3)) begin
$fatal(1, "4 KiB split DMA contract failed err=%b bursts=%0d", error_o,
u_memory.read_burst_count);
end
// A bounded two-descriptor chain must write DONE/clear OWN on both
// descriptors before the terminal event.
clear_run_state();
u_memory.write_word(32'h0000_0000, 32'h0000_0100);
u_memory.write_word(32'h0000_0004, 32'd4);
u_memory.write_word(32'h0000_0008, 32'h0000_0020);
u_memory.write_word(32'h0000_000C, 32'h0000_0003);
u_memory.write_word(32'h0000_0020, 32'h0000_0110);
u_memory.write_word(32'h0000_0024, 32'd4);
u_memory.write_word(32'h0000_0028, 32'd0);
u_memory.write_word(32'h0000_002C, 32'h0000_0005);
u_memory.write_word(32'h0000_0100, 32'h1111_0001);
u_memory.write_word(32'h0000_0110, 32'h2222_0002);
desc_count_i = 16'd2;
total_bytes_i = 32'd8;
pulse_start();
wait_done();
if (error_o || (output_count != 2) || (u_memory.read_word(
32'h0000_000C
) != 32'h0001_0002) || (u_memory.read_word(
32'h0000_002C
) != 32'h0001_0004)) begin
$fatal(1, "descriptor chain/writeback contract failed");
end
// A target read error is converted into descriptor ERROR writeback.
clear_run_state();
u_memory.write_word(32'h0000_0000, 32'h0000_0200);
u_memory.write_word(32'h0000_0004, 32'd4);
u_memory.write_word(32'h0000_0008, 32'd0);
u_memory.write_word(32'h0000_000C, 32'h0000_0005);
u_memory.inject_read_error(32'h0000_0200);
total_bytes_i = 32'd4;
$display("DMA STRESS error");
pulse_start();
wait_done();
if (!error_o || (u_memory.read_word(32'h0000_000C) != 32'h0002_0004)) begin
$fatal(1, "AXI read error did not reach descriptor writeback");
end
// Abort with a stalled stream drains the accepted AXI response and
// terminates through the same bounded writeback path.
clear_run_state();
u_memory.clear_memory();
u_memory.write_word(32'h0000_0000, 32'h0000_0300);
u_memory.write_word(32'h0000_0004, 32'd128);
u_memory.write_word(32'h0000_0008, 32'd0);
u_memory.write_word(32'h0000_000C, 32'h0000_0005);
total_bytes_i = 32'd128;
$display("DMA STRESS abort");
data_out_ready_i = 1'b0;
pulse_start();
wait (u_sdio_dma.s_state_q == 4'd5);
wait (u_sdio_dma.s_read_busy);
@(posedge clk_i);
abort_read_bursts = u_memory.read_burst_count;
repeat (5) @(posedge clk_i);
@(negedge clk_i);
abort_i = 1'b1;
repeat (4) @(posedge clk_i);
abort_i = 1'b0;
if (!done_seen) begin
wait_done();
end
if (!error_o) begin
$fatal(1, "DMA abort did not drain/write back error");
end
if (busy_o || (u_memory.read_burst_count != abort_read_bursts) || (u_memory.read_word(
32'h0000_000C
) != 32'h0002_0004)) begin
$fatal(1, "DMA abort left an accepted AXI read active or issued a new request");
end
// Abort during descriptor fetch still drains the accepted four-beat read
// and writes ERROR/clears OWN on the fetched active descriptor.
clear_run_state();
u_memory.write_word(32'h0000_0000, 32'h0000_0400);
u_memory.write_word(32'h0000_0004, 32'd4);
u_memory.write_word(32'h0000_0008, 32'd0);
u_memory.write_word(32'h0000_000C, 32'h0000_0005);
total_bytes_i = 32'd4;
u_memory.set_backpressure(0, 3, 0, 0);
pulse_start();
wait (u_sdio_dma.s_state_q == 4'd2);
wait (u_sdio_dma.s_read_busy);
@(negedge clk_i);
abort_i = 1'b1;
@(negedge clk_i);
abort_i = 1'b0;
wait_done();
if (!error_o || (u_memory.read_word(
32'h0000_000C
) != 32'h0002_0004) || u_memory.s_read_active_q) begin
$fatal(1, "descriptor-fetch abort did not drain/write back correctly");
end
// Abort while an AXI write response is stalled must wait for B before
// terminalizing the descriptor.
clear_run_state();
direction_i = 1'b0;
u_memory.write_word(32'h0000_0000, 32'h0000_0500);
u_memory.write_word(32'h0000_0004, 32'd4);
u_memory.write_word(32'h0000_0008, 32'd0);
u_memory.write_word(32'h0000_000C, 32'h0000_0005);
total_bytes_i = 32'd4;
u_memory.set_backpressure(0, 0, 0, 5);
pulse_start();
wait (u_sdio_dma.s_state_q == 4'd7);
data_in_i = 32'hDDCC_BBAA;
data_in_strb_i = 4'hF;
data_in_last_i = 1'b1;
data_in_valid_i = 1'b1;
@(posedge clk_i);
data_in_valid_i = 1'b0;
@(negedge clk_i);
abort_i = 1'b1;
@(negedge clk_i);
abort_i = 1'b0;
wait_done();
if (!error_o || (u_memory.read_word(
32'h0000_000C
) != 32'h0002_0004) || u_memory.s_write_active_q) begin
$fatal(1, "write-response abort did not drain/write back correctly");
end
$display("SDIO AXI 16-beat, 4 KiB split, error, and abort test passed");
$finish;
end
endmodule