Files
retroSoC/tests/rtl/sdio_sdio_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

343 lines
11 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
module sdio_sdio_tb;
localparam logic [31:0] HostCtrl = 32'h00C;
localparam logic [31:0] ClockCtrl = 32'h010;
localparam logic [31:0] BusCtrl = 32'h018;
localparam logic [31:0] CmdArg = 32'h040;
localparam logic [31:0] CmdCfg = 32'h044;
localparam logic [31:0] CmdStart = 32'h048;
localparam logic [31:0] CmdStatus = 32'h04C;
localparam logic [31:0] Resp0 = 32'h050;
localparam logic [31:0] BlockSize = 32'h080;
localparam logic [31:0] BlockCount = 32'h084;
localparam logic [31:0] DataCfg = 32'h088;
localparam logic [31:0] DataStart = 32'h08C;
localparam logic [31:0] PioData = 32'h090;
localparam logic [31:0] IrqStatus = 32'h100;
localparam logic [31:0] IrqEnable = 32'h104;
logic clk_i = 1'b0;
logic rst_n_i = 1'b0;
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)
);
sdio_if sdio ();
logic card_cmd_do;
logic [ 3:0] card_dat_do;
logic card_irq;
logic [31:0] apb_value;
integer timeout_guard;
assign sdio.cmd_di_i = sdio.cmd_oe_o ? sdio.cmd_do_o : card_cmd_do;
assign sdio.dat_di_i = (sdio.dat_oe_o & sdio.dat_do_o) | (~sdio.dat_oe_o & card_dat_do);
always #5 clk_i = ~clk_i;
apb4_sdio #(
.InputClockHz(72_000_000),
.AddrWidth (32),
.DataWidth (32),
.DescCount (16)
) u_apb4_sdio (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.apb4 (apb4),
.dma_axi4(dma_axi4),
.sdio (sdio)
);
sdio_sdio_model #(
.FunctionCount(2),
.FunctionBytes(4096),
.BusyCycles (8)
) u_card (
.rst_n_i (rst_n_i),
.sck_i (sdio.sck_o),
.cmd_oe_i(sdio.cmd_oe_o),
.cmd_do_i(sdio.cmd_do_o),
.cmd_do_o(card_cmd_do),
.dat_oe_i(sdio.dat_oe_o),
.dat_do_i(sdio.dat_do_o),
.dat_do_o(card_dat_do),
.irq_o (card_irq)
);
sdio_axi_memory_responder u_axi_memory (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.axi4 (dma_axi4)
);
task automatic apb_idle;
begin
apb4.psel = 1'b0;
apb4.penable = 1'b0;
apb4.pwrite = 1'b0;
apb4.paddr = '0;
apb4.pwdata = '0;
apb4.pstrb = '0;
end
endtask
task automatic issue_command_unchecked(input logic [5:0] index_i, input logic [31:0] argument_i,
input logic [3:0] response_i);
begin
apb_write(CmdArg, argument_i, 4'hF);
apb_write(CmdCfg, {18'd0, 1'b0, 1'b0, response_i, 2'd0, index_i}, 4'hF);
apb_write(CmdStart, 32'd1, 4'h1);
wait_command();
end
endtask
task automatic apb_write(input logic [31:0] address_i, input logic [31:0] data_i,
input logic [3:0] strobe_i);
begin
@(negedge clk_i);
apb4.paddr = address_i;
apb4.pwdata = data_i;
apb4.pstrb = strobe_i;
apb4.pwrite = 1'b1;
apb4.psel = 1'b1;
apb4.penable = 1'b0;
@(negedge clk_i);
apb4.penable = 1'b1;
@(posedge clk_i);
#1;
if (!apb4.pready || apb4.pslverr) begin
$fatal(1, "SDIO APB write failed addr=%h data=%h", address_i, data_i);
end
apb_idle();
end
endtask
task automatic apb_read(input logic [31:0] address_i, output logic [31:0] data_o);
begin
@(negedge clk_i);
apb4.paddr = address_i;
apb4.pwdata = '0;
apb4.pstrb = '0;
apb4.pwrite = 1'b0;
apb4.psel = 1'b1;
apb4.penable = 1'b0;
@(negedge clk_i);
apb4.penable = 1'b1;
@(posedge clk_i);
#1;
if (!apb4.pready || apb4.pslverr) begin
$fatal(1, "SDIO APB read failed addr=%h", address_i);
end
data_o = apb4.prdata;
apb_idle();
end
endtask
task automatic wait_command;
logic seen_busy;
begin
timeout_guard = 0;
seen_busy = 1'b0;
while (timeout_guard < 20000) begin
@(posedge clk_i);
timeout_guard = timeout_guard + 1;
if (u_apb4_sdio.u_sdio_core.s_cmd_busy) begin
seen_busy = 1'b1;
end
if (seen_busy && (u_apb4_sdio.u_sdio_core.u_sdio_command.s_state_q == 3'd0)) begin
break;
end
end
if (timeout_guard >= 20000) begin
$fatal(1, "SDIO command did not drain");
end
end
endtask
task automatic wait_data;
logic seen_busy;
begin
timeout_guard = 0;
seen_busy = 1'b0;
while (timeout_guard < 200000) begin
@(posedge clk_i);
timeout_guard = timeout_guard + 1;
if (u_apb4_sdio.u_sdio_core.s_data_busy || u_apb4_sdio.u_sdio_core.s_dma_busy) begin
seen_busy = 1'b1;
end
if (seen_busy && !u_apb4_sdio.u_sdio_core.s_data_busy &&
!u_apb4_sdio.u_sdio_core.s_dma_busy) begin
break;
end
end
if (timeout_guard >= 200000) begin
$fatal(1, "SDIO data path did not drain");
end
end
endtask
task automatic wait_write_ready;
begin
timeout_guard = 0;
while ((u_apb4_sdio.u_sdio_core.u_sdio_data.s_state_q != 4'd1) &&
(timeout_guard < 1000)) begin
@(posedge clk_i);
timeout_guard = timeout_guard + 1;
end
if (timeout_guard >= 1000) begin
$fatal(1, "SDIO write engine did not become ready");
end
end
endtask
task automatic issue_command(input logic [5:0] index_i, input logic [31:0] argument_i,
input logic [3:0] response_i);
logic [31:0] status_value;
begin
apb_write(CmdArg, argument_i, 4'hF);
apb_write(CmdCfg, {18'd0, 1'b1, 1'b1, response_i, 2'd0, index_i}, 4'hF);
apb_write(CmdStart, 32'd1, 4'h1);
wait_command();
apb_read(CmdStatus, status_value);
if ((status_value & 32'h0000_001E) != 0) begin
$fatal(1, "SDIO command %0d failed status=%h", index_i, status_value);
end
end
endtask
task automatic configure_data(input logic width4_i, input logic direction_i,
input logic block_mode_i, input logic fixed_address_i,
input logic [15:0] bytes_i, input logic [15:0] count_i);
begin
apb_write(BusCtrl, width4_i ? 32'd1 : 32'd0, 4'h1);
apb_write(BlockSize, {16'd0, bytes_i}, 4'hF);
apb_write(BlockCount, {16'd0, count_i}, 4'hF);
apb_write(DataCfg, {
25'd0, fixed_address_i, block_mode_i, 1'b0, 1'b0, 1'b0, width4_i, direction_i},
4'h1);
end
endtask
initial begin
apb_idle();
repeat (4) @(posedge clk_i);
rst_n_i = 1'b1;
apb_write(ClockCtrl, 32'h0000_0101, 4'hF);
apb_write(HostCtrl, 32'h0000_0005, 4'h1);
// SDIO enumeration and CCCR/FBR/CIS-backed direct accesses.
issue_command(6'd5, 32'h0000_0000, 4'd5);
repeat (8) @(posedge clk_i);
issue_command(6'd52, 32'h0000_0000, 4'd6);
apb_read(Resp0, apb_value);
if (apb_value[15:8] != 8'h03) begin
$fatal(1, "CCCR version mismatch: %h", apb_value);
end
issue_command(6'd52, {1'b0, 3'd0, 1'b0, 1'b0, 17'h00013, 1'b0, 8'h00}, 4'd6);
apb_read(Resp0, apb_value);
if (apb_value[15:8] != 8'h01) $fatal(1, "CCCR high-speed capability missing");
issue_command(6'd52, {1'b1, 3'd0, 1'b0, 1'b0, 17'h00013, 1'b0, 8'h03}, 4'd6);
issue_command(6'd52, {1'b0, 3'd0, 1'b0, 1'b0, 17'h00013, 1'b0, 8'h00}, 4'd6);
apb_read(Resp0, apb_value);
if (apb_value[15:8] != 8'h03) $fatal(1, "CCCR high-speed enable did not stick");
issue_command(6'd52, {1'b1, 3'd0, 1'b0, 1'b0, 17'h00004, 1'b0, 8'h5A}, 4'd6);
apb_read(Resp0, apb_value);
if (apb_value[15:8] != 8'h5A) begin
$fatal(1, "CCCR write response mismatch: %h", apb_value);
end
// CMD53 byte mode, incrementing address, 4-bit SDR.
issue_command_unchecked(6'd53, {1'b0, 3'd1, 1'b0, 1'b1, 17'h00020, 9'd4}, 4'd0);
configure_data(1'b1, 1'b0, 1'b0, 1'b0, 16'd4, 16'd1);
u_card.arm_read(4096 + 32, 4, 1'b1, 1'b0);
apb_write(DataStart, 32'd1, 4'h1);
wait_data();
apb_read(PioData, apb_value);
if (apb_value != 32'h0000_0000) begin
$fatal(1, "unexpected initial function memory: %h", apb_value);
end
// CMD53 block mode, fixed address, 1-bit SDR write.
issue_command_unchecked(6'd53, {1'b1, 3'd1, 1'b1, 1'b0, 17'h00030, 9'd1}, 4'd0);
configure_data(1'b0, 1'b1, 1'b1, 1'b1, 16'd4, 16'd1);
u_card.arm_write(4096 + 48, 4, 1'b0, 1'b1);
apb_write(DataStart, 32'd1, 4'h1);
wait_write_ready();
apb_write(PioData, 32'h1122_3344, 4'hF);
wait_data();
if (u_card.read_backing(4096 + 48) != 8'h11) begin
$fatal(1, "fixed-address CMD53 write did not land: %h", u_card.read_backing(4096 + 48));
end
apb_write(IrqEnable, 32'h0000_0040, 4'h1);
apb_write(IrqStatus, 32'h0000_00FF, 4'h1);
u_card.clear_irq();
repeat (4) @(posedge clk_i);
apb_read(IrqStatus, apb_value);
if ((apb_value & 32'h0000_0040) != 0 || sdio.irq_o) begin
$fatal(1, "DAT1 high level caused a false host IRQ");
end
// DAT1 is an active-low function interrupt. It is synchronized in clk_i,
// qualified only while the host releases the data path, and remains sticky
// while SDCLK is stopped.
u_card.set_irq();
repeat (4) @(posedge clk_i);
if (sdio.dat_di_i[1] != 1'b0 || !card_irq) begin
$fatal(1, "SDIO DAT1 interrupt was not asserted low");
end
apb_read(IrqStatus, apb_value);
if ((apb_value & 32'h0000_0040) == 0 || !sdio.irq_o) begin
$fatal(1, "active-low DAT1 did not latch host IRQ");
end
apb_write(IrqStatus, 32'h0000_0040, 4'h1);
repeat (8) @(posedge clk_i);
apb_read(IrqStatus, apb_value);
if ((apb_value & 32'h0000_0040) != 0 || sdio.irq_o) begin
$fatal(1, "DAT1 W1C did not clear sticky host IRQ");
end
if (sdio.sck_o != 1'b0) begin
$fatal(1, "SDCLK was not stopped for DAT1 persistence check");
end
repeat (8) @(posedge clk_i);
apb_read(IrqStatus, apb_value);
if ((apb_value & 32'h0000_0040) != 0) begin
$fatal(1, "cleared DAT1 IRQ reasserted while line remained low");
end
u_card.clear_irq();
repeat (4) @(posedge clk_i);
if (sdio.dat_di_i[1] != 1'b1) begin
$fatal(1, "DAT1 interrupt did not release");
end
u_card.set_irq();
repeat (4) @(posedge clk_i);
apb_read(IrqStatus, apb_value);
if ((apb_value & 32'h0000_0040) == 0) begin
$fatal(1, "DAT1 did not re-arm after returning high");
end
$display("SDIO-only CMD5/CMD52/CMD53/DAT1 test passed");
$finish;
end
endmodule