Files
retroSoC/tests/rtl/spisd_wrapper_tb.sv
T
maksyuki 075344df58 feat: add SPI-SD RTL tests and enhance interconnect testbench
- Increased the number of masters in the AXI4 interconnect testbench from 5 to 6.
- Added new testbench for SPI-SD clock functionality (spisd_clock_tb).
- Introduced testbench for SPI-SD command engine (spisd_command_tb).
- Created testbench for SPI-SD CRC calculation (spisd_crc_tb).
- Implemented testbench for SPI-SD data handling (spisd_data_tb).
- Developed wrapper testbench for SPI-SD with APB and training checks (spisd_wrapper_tb).
- Added Python test suite for SPI-SD RTL regressions (test_spisd.py).
- Created a test for verifying register parity between RTL and C header definitions (test_spisd_register_parity.py).
2026-08-20 16:12:15 +08:00

235 lines
8.1 KiB
Systemverilog

`timescale 1ns / 1ps
`include "axi4_define.svh"
module spisd_wrapper_tb;
logic clk_i = 1'b0;
logic rst_n_i = 1'b0;
integer s_train_rise_count;
integer s_training_timeout;
integer s_command_timeout;
logic [31:0] s_read_data;
logic s_access_error;
logic s_card_miso;
logic s_card_power_on;
logic s_count_training;
apb4_if apb4 (
.pclk (clk_i),
.presetn(rst_n_i)
);
axi4_if #(
.ADDR_WIDTH(32),
.DATA_WIDTH(32),
.ID_WIDTH (1),
.USER_WIDTH(1)
) dma_axi4 (
.aclk (clk_i),
.aresetn(rst_n_i)
);
spi_if spi ();
always #5 clk_i = ~clk_i;
assign spi.miso_i = s_card_miso;
assign dma_axi4.awready = 1'b1;
assign dma_axi4.wready = 1'b1;
assign dma_axi4.bid = '0;
assign dma_axi4.bresp = `AXI4_RESP_OKAY;
assign dma_axi4.buser = '0;
assign dma_axi4.bvalid = 1'b0;
assign dma_axi4.arready = 1'b1;
assign dma_axi4.rid = '0;
assign dma_axi4.rdata = '0;
assign dma_axi4.rresp = `AXI4_RESP_OKAY;
assign dma_axi4.rlast = 1'b1;
assign dma_axi4.ruser = '0;
assign dma_axi4.rvalid = 1'b0;
apb4_spisd #(
.InputClockHz(72_000_000),
.AddrWidth (32),
.DataWidth (32),
.DescCount (16),
.FifoDepth (16)
) u_apb4_spisd (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.apb4 (apb4),
.dma_axi4(dma_axi4),
.spi (spi)
);
spisd_card u_spisd_card (
.sck (spi.sck_o),
.cs_n (spi.nss_o),
.mosi (spi.mosi_o),
.miso (s_card_miso),
.power_on(s_card_power_on)
);
always @(posedge spi.sck_o) begin
if (s_count_training) begin
s_train_rise_count = s_train_rise_count + 1;
if (!spi.nss_o) $fatal(1, "chip select asserted during power-up clock training");
end
end
task automatic apb_write(input logic [31:0] addr, input logic [31:0] data,
output logic access_error);
begin
@(negedge clk_i);
apb4.paddr = addr;
apb4.pwrite = 1'b1;
apb4.pwdata = data;
apb4.pstrb = 4'hF;
apb4.psel = 1'b1;
apb4.penable = 1'b0;
@(negedge clk_i);
apb4.penable = 1'b1;
@(posedge clk_i);
while (!apb4.pready) @(posedge clk_i);
#1;
access_error = apb4.pslverr;
@(negedge clk_i);
apb4.psel = 1'b0;
apb4.penable = 1'b0;
apb4.pwrite = 1'b0;
end
endtask
task automatic apb_read(input logic [31:0] addr, output logic [31:0] data,
output logic access_error);
begin
@(negedge clk_i);
apb4.paddr = addr;
apb4.pwrite = 1'b0;
apb4.pstrb = 4'h0;
apb4.psel = 1'b1;
apb4.penable = 1'b0;
@(negedge clk_i);
apb4.penable = 1'b1;
@(posedge clk_i);
while (!apb4.pready) @(posedge clk_i);
#1;
data = apb4.prdata;
access_error = apb4.pslverr;
@(negedge clk_i);
apb4.psel = 1'b0;
apb4.penable = 1'b0;
end
endtask
initial begin
s_train_rise_count = 0;
s_count_training = 1'b1;
s_card_power_on = 1'b0;
apb4.paddr = '0;
apb4.pprot = '0;
apb4.psel = 1'b0;
apb4.penable = 1'b0;
apb4.pwrite = 1'b0;
apb4.pwdata = '0;
apb4.pstrb = '0;
repeat (3) @(posedge clk_i);
rst_n_i = 1'b1;
s_card_power_on = 1'b1;
@(posedge clk_i);
s_card_power_on = 1'b0;
apb_read(32'h0000_0000, s_read_data, s_access_error);
if (s_access_error || (s_read_data != 32'h5350_4953)) begin
$fatal(1, "IP identification read failed: data=%h err=%b", s_read_data, s_access_error);
end
apb_read(32'h0000_0008, s_read_data, s_access_error);
if (s_access_error || (s_read_data[7:0] != 8'hFF)) begin
$fatal(1, "capability read failed: data=%h err=%b", s_read_data, s_access_error);
end
apb_write(32'h0000_0000, 32'd0, s_access_error);
if (!s_access_error) $fatal(1, "read-only register write did not return PSLVERR");
apb_read(32'h0000_03FC, s_read_data, s_access_error);
if (!s_access_error) $fatal(1, "undefined register read did not return PSLVERR");
apb_write(32'h0000_000C, 32'h0000_0001, s_access_error);
if (s_access_error) $fatal(1, "HOST_CTRL write failed");
apb_write(32'h0000_0010, 32'h0000_0103, s_access_error);
if (s_access_error) $fatal(1, "CLOCK_CTRL training write failed");
s_training_timeout = 0;
while ((s_train_rise_count < 80) && (s_training_timeout < 500)) begin
@(posedge clk_i);
s_training_timeout = s_training_timeout + 1;
end
if (s_train_rise_count != 80) begin
$fatal(1, "power-up clock training timed out at %0d rising edges", s_train_rise_count);
end
repeat (6) @(posedge clk_i);
if ((s_train_rise_count != 80) || spi.sck_o || !spi.nss_o) begin
$fatal(1, "training completion failed: edges=%0d sck=%b cs_n=%b", s_train_rise_count,
spi.sck_o, spi.nss_o);
end
apb_read(32'h0000_0024, s_read_data, s_access_error);
if (s_access_error || s_read_data[0]) begin
$fatal(1, "controller remained busy after training: status=%h", s_read_data);
end
apb_read(32'h0000_0014, s_read_data, s_access_error);
if (s_access_error || (s_read_data != 32'd36_000_000)) begin
$fatal(1, "actual clock reporting failed: %0d", s_read_data);
end
s_count_training = 1'b0;
apb_write(32'h0000_0040, 32'd0, s_access_error);
if (s_access_error) $fatal(1, "CMD_ARG write failed");
apb_write(32'h0000_0044, 32'h0000_0100, s_access_error);
if (s_access_error) $fatal(1, "CMD_CFG write failed");
apb_write(32'h0000_0048, 32'd1, s_access_error);
if (s_access_error) $fatal(1, "CMD_START write failed");
s_command_timeout = 0;
apb_read(32'h0000_004C, s_read_data, s_access_error);
while (s_read_data[0] && (s_command_timeout < 500)) begin
apb_read(32'h0000_004C, s_read_data, s_access_error);
s_command_timeout = s_command_timeout + 1;
end
if (s_access_error || s_read_data[2:1] || (s_command_timeout >= 500)) begin
$fatal(1, "CMD0 failed: status=%h timeout=%0d", s_read_data, s_command_timeout);
end
apb_read(32'h0000_0050, s_read_data, s_access_error);
if (s_access_error || (s_read_data[7:0] != 8'h01)) begin
$fatal(1, "CMD0 response failed: response=%h", s_read_data);
end
u_spisd_card.initialized = 1'b1;
apb_write(32'h0000_0080, 32'd512, s_access_error);
apb_write(32'h0000_0084, 32'd1, s_access_error);
apb_write(32'h0000_0088, 32'h0000_0050, s_access_error);
apb_write(32'h0000_00C0, 32'h0000_0001, s_access_error);
apb_write(32'h0000_00C4, 32'd1, s_access_error);
apb_write(32'h0000_0040, 32'd0, s_access_error);
apb_write(32'h0000_0044, 32'h0000_2111, s_access_error);
if (s_access_error) $fatal(1, "DMA command setup failed");
apb_write(32'h0000_00C8, 32'd1, s_access_error);
if (s_access_error) $fatal(1, "DMA command alias start failed");
s_command_timeout = 0;
apb_read(32'h0000_0024, s_read_data, s_access_error);
while (s_read_data[0] && (s_command_timeout < 500)) begin
apb_read(32'h0000_0024, s_read_data, s_access_error);
s_command_timeout = s_command_timeout + 1;
end
if (s_access_error || (s_command_timeout >= 500)) begin
$fatal(1, "descriptor error did not terminate the transaction");
end
apb_read(32'h0000_004C, s_read_data, s_access_error);
if (s_access_error || !s_read_data[1] || (s_read_data[13:6] != 8'h08)) begin
$fatal(1, "descriptor error did not abort the command engine: %h", s_read_data);
end
apb_read(32'h0000_00CC, s_read_data, s_access_error);
if (s_access_error || !s_read_data[2] || (s_read_data[10:3] != 8'h02)) begin
$fatal(1, "descriptor error classification failed: %h", s_read_data);
end
apb_read(32'h0000_010C, s_read_data, s_access_error);
if (s_access_error || !s_read_data[6]) begin
$fatal(1, "descriptor error was not latched: %h", s_read_data);
end
$display("SPISD APB ABI, training, card model, and DMA abort test passed");
$finish;
end
endmodule