mirror of
https://github.com/retroSoC/retroSoC.git
synced 2026-11-06 14:55:14 +00:00
- 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.
678 lines
24 KiB
Systemverilog
678 lines
24 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,
|
|
// EITHER EXPRESS OR IMPLIED, 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_standalone_tb #(
|
|
parameter bit CardHighCapacity = 1'b1,
|
|
parameter bit RunFourBit = 1'b1
|
|
);
|
|
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] DataStatus = 32'h08C;
|
|
localparam logic [31:0] IrqStatus = 32'h100;
|
|
localparam logic [31:0] IrqEnable = 32'h104;
|
|
localparam logic [31:0] TimeoutData = 32'h020;
|
|
localparam logic [31:0] TimeoutBusy = 32'h024;
|
|
localparam logic [31:0] TimeoutCount = 32'h118;
|
|
localparam logic [31:0] CrcCount = 32'h114;
|
|
localparam logic [31:0] AxiCount = 32'h11C;
|
|
localparam logic [31:0] PioData = 32'h090;
|
|
localparam logic [31:0] DescBase = 32'h0C0;
|
|
localparam logic [31:0] DescCount = 32'h0C4;
|
|
localparam logic [31:0] Status = 32'h028;
|
|
localparam logic [31:0] ClockActual = 32'h014;
|
|
|
|
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;
|
|
integer timeout_count_before;
|
|
integer crc_count_before;
|
|
|
|
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_sd_memory_model #(
|
|
.HighCapacity(CardHighCapacity),
|
|
.BlockBytes (512),
|
|
.BlockCount (128),
|
|
.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 #(
|
|
.DepthWords(16384)
|
|
) 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 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;
|
|
for (integer wait_count = 0; wait_count < 100000; wait_count++) begin
|
|
@(posedge clk_i);
|
|
#1;
|
|
if (apb4.pready) begin
|
|
break;
|
|
end
|
|
end
|
|
if (!apb4.pready || apb4.pslverr) begin
|
|
$fatal(1, "SDIO APB write failed addr=%h data=%h err=%b", address_i, data_i, apb4.pslverr);
|
|
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 err=%b", address_i, apb4.pslverr);
|
|
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, "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, "data path did not drain");
|
|
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, input logic crc_check_i,
|
|
input logic index_check_i);
|
|
logic [31:0] status_value;
|
|
begin
|
|
apb_write(CmdArg, argument_i, 4'hF);
|
|
apb_write(CmdCfg, {18'd0, index_check_i, crc_check_i, 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, "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 [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, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, width4_i, direction_i}, 4'h1);
|
|
end
|
|
endtask
|
|
|
|
task automatic configure_dma_data(input logic width4_i, input logic direction_i,
|
|
input logic [15:0] bytes_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, 32'd1, 4'hF);
|
|
apb_write(DataCfg, {25'd0, 1'b0, 1'b0, 1'b1, 1'b0, 1'b0, width4_i, direction_i}, 4'h1);
|
|
apb_write(DescBase, 32'd0, 4'hF);
|
|
apb_write(DescCount, 32'd1, 4'hF);
|
|
end
|
|
endtask
|
|
|
|
task automatic read_pio(input integer address_i, input logic width4_i,
|
|
output logic [31:0] data_o);
|
|
begin
|
|
u_card.arm_read(address_i, 4, width4_i, 1'b0);
|
|
apb_write(DataStart, 32'd1, 4'h1);
|
|
wait_data();
|
|
apb_read(PioData, data_o);
|
|
end
|
|
endtask
|
|
|
|
task automatic wait_pio_valid;
|
|
begin
|
|
timeout_guard = 0;
|
|
while (!u_apb4_sdio.u_sdio_core.s_pio_valid_q && (timeout_guard < 200000)) begin
|
|
@(posedge clk_i);
|
|
timeout_guard = timeout_guard + 1;
|
|
end
|
|
if (timeout_guard >= 200000) begin
|
|
$fatal(1, "PIO read word did not become valid");
|
|
end
|
|
end
|
|
endtask
|
|
|
|
task automatic write_pio(input integer address_i, input logic width4_i,
|
|
input logic [31:0] data_i);
|
|
begin
|
|
u_card.arm_write(address_i, 4, width4_i, 1'b0);
|
|
apb_write(DataStart, 32'd1, 4'h1);
|
|
apb_write(PioData, data_i, 4'hF);
|
|
wait_data();
|
|
if (u_card.read_backing(
|
|
address_i
|
|
) != data_i[7:0] || u_card.read_backing(
|
|
address_i + 1
|
|
) != data_i[15:8] || u_card.read_backing(
|
|
address_i + 2
|
|
) != data_i[23:16] || u_card.read_backing(
|
|
address_i + 3
|
|
) != data_i[31:24]) begin
|
|
$fatal(1, "PIO write mismatch at %0d", address_i);
|
|
end
|
|
end
|
|
endtask
|
|
|
|
task automatic tail_read_test(input integer address_i, input logic width4_i,
|
|
input integer byte_count_i);
|
|
logic [31:0] word0;
|
|
logic [31:0] word1;
|
|
integer word_count;
|
|
integer expected_words;
|
|
begin
|
|
configure_data(width4_i, 1'b0, byte_count_i, 16'd1);
|
|
u_card.arm_read(address_i, byte_count_i, width4_i, 1'b0);
|
|
apb_write(DataStart, 32'd1, 4'h1);
|
|
expected_words = (byte_count_i + 3) / 4;
|
|
word_count = 0;
|
|
while (word_count < expected_words) begin
|
|
wait_pio_valid();
|
|
if (word_count == 0) begin
|
|
apb_read(PioData, word0);
|
|
end else begin
|
|
apb_read(PioData, word1);
|
|
end
|
|
word_count = word_count + 1;
|
|
end
|
|
wait_data();
|
|
for (integer byte_index = 0; byte_index < byte_count_i; byte_index++) begin
|
|
if ((byte_index < 4 ? word0[(byte_index % 4)*8+:8] :
|
|
word1[(byte_index % 4)*8+:8]) !=
|
|
u_card.read_backing(
|
|
address_i + byte_index
|
|
)) begin
|
|
$fatal(1, "tail read mismatch width4=%b bytes=%0d byte=%0d", width4_i, byte_count_i,
|
|
byte_index);
|
|
end
|
|
end
|
|
end
|
|
endtask
|
|
|
|
task automatic tail_write_test(input integer address_i, input logic width4_i,
|
|
input integer byte_count_i);
|
|
integer expected_words;
|
|
logic [31:0] word;
|
|
logic [ 3:0] strb;
|
|
integer remaining;
|
|
begin
|
|
configure_data(width4_i, 1'b1, byte_count_i, 16'd1);
|
|
u_card.arm_write(address_i, byte_count_i, width4_i, 1'b0);
|
|
apb_write(DataStart, 32'd1, 4'h1);
|
|
expected_words = (byte_count_i + 3) / 4;
|
|
for (integer word_index = 0; word_index < expected_words; word_index++) begin
|
|
word = '0;
|
|
remaining = byte_count_i - (word_index * 4);
|
|
for (integer byte_index = 0; byte_index < 4; byte_index++) begin
|
|
if (byte_index < remaining) begin
|
|
word[byte_index*8+:8] = 8'hA0 + (word_index * 4) + byte_index;
|
|
end
|
|
end
|
|
strb = (remaining >= 4) ? 4'hF : ((4'b0001 << remaining) - 1'b1);
|
|
apb_write(PioData, word, strb);
|
|
end
|
|
wait_data();
|
|
for (integer byte_index = 0; byte_index < byte_count_i; byte_index++) begin
|
|
if (u_card.read_backing(address_i + byte_index) != (8'hA0 + byte_index)) begin
|
|
$fatal(1, "tail write mismatch width4=%b bytes=%0d byte=%0d data=%h", width4_i,
|
|
byte_count_i, byte_index, u_card.read_backing(address_i + byte_index));
|
|
end
|
|
end
|
|
end
|
|
endtask
|
|
|
|
initial begin
|
|
apb_idle();
|
|
repeat (4) @(posedge clk_i);
|
|
rst_n_i = 1'b1;
|
|
u_card.fill_memory(8'h20);
|
|
u_card.set_response_delay(1);
|
|
u_card.set_data_delay(1);
|
|
|
|
// 400 kHz command flow at the documented 72 MHz integration clock.
|
|
apb_write(ClockCtrl, 32'h0000_5A01, 4'hF);
|
|
apb_write(HostCtrl, 32'h0000_0005, 4'h1);
|
|
apb_read(ClockActual, apb_value);
|
|
if (apb_value != 32'd400000) begin
|
|
$fatal(1, "400 kHz clock calculation mismatch: %0d", apb_value);
|
|
end
|
|
issue_command(6'd8, 32'h0000_01AA, 4'd8, 1'b1, 1'b1);
|
|
apb_read(Resp0, apb_value);
|
|
if (apb_value[31:8] != 24'h0001AA) begin
|
|
$fatal(1, "CMD8 response echo mismatch: %h", apb_value);
|
|
end
|
|
|
|
apb_write(TimeoutBusy, 32'd4, 4'hF);
|
|
apb_write(CmdArg, 32'd0, 4'hF);
|
|
apb_write(CmdCfg, {18'd0, 1'b1, 1'b1, 4'd2, 2'd0, 6'd12}, 4'hF);
|
|
apb_write(CmdStart, 32'd1, 4'h1);
|
|
wait_command();
|
|
apb_read(CmdStatus, apb_value);
|
|
if ((apb_value & 32'h0000_0004) == 0) begin
|
|
$fatal(1, "R1b command did not use TIMEOUT_BUSY");
|
|
end
|
|
apb_write(TimeoutBusy, 32'd1000000, 4'hF);
|
|
|
|
apb_write(IrqEnable, 32'h0000_0080, 4'h1);
|
|
u_card.set_response_delay(1000);
|
|
apb_write(CmdArg, 32'd0, 4'hF);
|
|
apb_write(CmdCfg, {18'd0, 1'b1, 1'b1, 4'd1, 2'd0, 6'd17}, 4'hF);
|
|
apb_write(CmdStart, 32'd1, 4'h1);
|
|
repeat (8) @(posedge clk_i);
|
|
apb_write(HostCtrl, 32'h0000_0007, 4'h1);
|
|
wait_command();
|
|
apb_read(IrqStatus, apb_value);
|
|
if ((apb_value & 32'h0000_0080) == 0) $fatal(1, "command abort IRQ missing");
|
|
apb_write(IrqStatus, 32'h0000_0080, 4'h1);
|
|
u_card.set_response_delay(1);
|
|
|
|
configure_data(1'b0, 1'b0, 16'd4, 16'd1);
|
|
u_card.set_data_delay(1000);
|
|
u_card.arm_read(3000, 4, 1'b0, 1'b0);
|
|
apb_write(DataStart, 32'd1, 4'h1);
|
|
repeat (8) @(posedge clk_i);
|
|
apb_write(HostCtrl, 32'h0000_0007, 4'h1);
|
|
wait_data();
|
|
apb_read(IrqStatus, apb_value);
|
|
if ((apb_value & 32'h0000_0080) == 0) $fatal(1, "data abort IRQ missing");
|
|
apb_write(IrqStatus, 32'h0000_0080, 4'h1);
|
|
u_card.set_data_delay(1);
|
|
|
|
u_axi_memory.clear_memory();
|
|
u_axi_memory.write_word(32'h0000_0000, 32'h0000_0200);
|
|
u_axi_memory.write_word(32'h0000_0004, 32'd4);
|
|
u_axi_memory.write_word(32'h0000_0008, 32'd0);
|
|
u_axi_memory.write_word(32'h0000_000C, 32'h0000_0005);
|
|
u_axi_memory.set_backpressure(0, 3, 0, 0);
|
|
configure_dma_data(1'b0, 1'b0, 16'd4);
|
|
u_card.arm_read(3004, 4, 1'b0, 1'b0);
|
|
apb_write(DataStart, 32'd1, 4'h1);
|
|
wait (u_apb4_sdio.u_sdio_core.u_sdio_dma.s_state_q == 4'd2);
|
|
apb_write(HostCtrl, 32'h0000_0007, 4'h1);
|
|
wait_data();
|
|
apb_read(IrqStatus, apb_value);
|
|
if ((apb_value & 32'h0000_0080) == 0 || u_axi_memory.read_word(
|
|
32'h0000_000C
|
|
) != 32'h0002_0004) begin
|
|
$fatal(1, "descriptor-fetch host abort did not drain/write back");
|
|
end
|
|
apb_write(IrqStatus, 32'h0000_0080, 4'h1);
|
|
u_axi_memory.set_backpressure(0, 0, 0, 0);
|
|
|
|
apb_write(TimeoutData, 32'd4, 4'hF);
|
|
apb_read(TimeoutCount, apb_value);
|
|
timeout_count_before = apb_value;
|
|
u_card.set_data_delay(1000);
|
|
u_card.arm_read(3200, 4, 1'b0, 1'b0);
|
|
configure_data(1'b0, 1'b0, 16'd4, 16'd1);
|
|
apb_write(DataStart, 32'd1, 4'h1);
|
|
wait_data();
|
|
apb_read(DataStatus, apb_value);
|
|
if ((apb_value & 32'h0000_0004) == 0) $fatal(1, "data timeout phase not reported");
|
|
apb_read(TimeoutCount, apb_value);
|
|
if (apb_value != timeout_count_before + 1) begin
|
|
$fatal(1, "timeout counter incremented incorrectly: before=%0d after=%0d",
|
|
timeout_count_before, apb_value);
|
|
end
|
|
apb_write(TimeoutData, 32'd1000000, 4'hF);
|
|
u_card.set_data_delay(1);
|
|
|
|
// SDSC and SDHC use the same native command path but different address
|
|
// interpretation. The parameterized wrapper is run once for each card
|
|
// geometry by the pytest entry points.
|
|
issue_command(
|
|
6'd17, CardHighCapacity ? (RunFourBit ? 32'd4 : 32'd2) : (RunFourBit ? 32'd2048 : 32'd1024),
|
|
4'd1, 1'b1, 1'b1);
|
|
issue_command(6'd12, 32'd0, 4'd2, 1'b1, 1'b1);
|
|
configure_data(RunFourBit, 1'b0, 16'd4, 16'd1);
|
|
read_pio(RunFourBit ? 2048 : 1024, RunFourBit, apb_value);
|
|
if (apb_value != 32'h2322_2120) begin
|
|
$fatal(1, "4-bit read mismatch: %h", apb_value);
|
|
end
|
|
|
|
// Each APB PIO read consumes one held word. The second word must not
|
|
// repeat the first word or require a reset between reads.
|
|
configure_data(RunFourBit, 1'b0, 16'd8, 16'd1);
|
|
u_card.arm_read(RunFourBit ? 2052 : 1028, 8, RunFourBit, 1'b0);
|
|
apb_write(DataStart, 32'd1, 4'h1);
|
|
wait_pio_valid();
|
|
apb_read(PioData, apb_value);
|
|
if (apb_value != 32'h2726_2524) begin
|
|
$fatal(1, "first back-to-back PIO word mismatch: %h", apb_value);
|
|
end
|
|
wait_pio_valid();
|
|
apb_read(PioData, apb_value);
|
|
if (apb_value != 32'h2B2A_2928) begin
|
|
$fatal(1, "second back-to-back PIO word mismatch: %h", apb_value);
|
|
end
|
|
wait_data();
|
|
|
|
configure_data(1'b0, 1'b1, 16'd4, 16'd1);
|
|
write_pio(3072, 1'b0, 32'hA1B2_C3D4);
|
|
configure_data(1'b1, 1'b1, 16'd4, 16'd1);
|
|
write_pio(4096, 1'b1, 32'h1020_3040);
|
|
|
|
// A complete PIO block uses a real ready/consume FIFO. The APB writes
|
|
// below are intentionally consecutive; PREADY supplies the required
|
|
// wait-states while the serializer drains the queue.
|
|
apb_write(ClockCtrl, 32'h0000_0101, 4'hF);
|
|
|
|
// End-to-end DMA direction checks use the same card model and AXI
|
|
// responder as the PIO path.
|
|
u_axi_memory.clear_memory();
|
|
u_axi_memory.write_word(32'h0000_0000, 32'h0000_0200);
|
|
u_axi_memory.write_word(32'h0000_0004, 32'd5);
|
|
u_axi_memory.write_word(32'h0000_0008, 32'd0);
|
|
u_axi_memory.write_word(32'h0000_000C, 32'h0000_000D);
|
|
configure_dma_data(1'b1, 1'b0, 16'd5);
|
|
u_card.arm_read(20000, 5, 1'b1, 1'b0);
|
|
for (integer byte_index = 0; byte_index < 5; byte_index++) begin
|
|
u_card.write_backing(20000 + byte_index, 8'h60 + byte_index);
|
|
end
|
|
apb_write(DataStart, 32'd1, 4'h1);
|
|
wait_data();
|
|
apb_write(IrqEnable, 32'h0000_0084, 4'h1);
|
|
repeat (2) @(posedge clk_i);
|
|
apb_read(IrqStatus, apb_value);
|
|
if ((apb_value & 32'h0000_0004) == 0) begin
|
|
$fatal(1, "descriptor IRQ was not visible through the core");
|
|
end
|
|
apb_write(IrqStatus, 32'h0000_0004, 4'h1);
|
|
if (u_axi_memory.read_word(
|
|
32'h0000_0200
|
|
) != 32'h6362_6160 || (u_axi_memory.read_word(
|
|
32'h0000_0204
|
|
) & 32'hFF) != 32'h64 || u_axi_memory.read_word(
|
|
32'h0000_000C
|
|
) != 32'h0001_000C) begin
|
|
$fatal(1, "card-to-memory DMA end-to-end direction failed");
|
|
end
|
|
|
|
u_axi_memory.clear_memory();
|
|
u_axi_memory.write_word(32'h0000_0000, 32'h0000_0300);
|
|
u_axi_memory.write_word(32'h0000_0004, 32'd5);
|
|
u_axi_memory.write_word(32'h0000_0008, 32'd0);
|
|
u_axi_memory.write_word(32'h0000_000C, 32'h0000_0005);
|
|
u_axi_memory.write_word(32'h0000_0300, 32'hD4C3_B2A1);
|
|
u_axi_memory.write_word(32'h0000_0304, 32'h0000_00E5);
|
|
configure_dma_data(1'b0, 1'b1, 16'd5);
|
|
u_card.arm_write(21000, 5, 1'b0, 1'b0);
|
|
apb_write(DataStart, 32'd1, 4'h1);
|
|
wait_data();
|
|
if (u_card.read_backing(
|
|
21000
|
|
) != 8'hA1 || u_card.read_backing(
|
|
21001
|
|
) != 8'hB2 || u_card.read_backing(
|
|
21002
|
|
) != 8'hC3 || u_card.read_backing(
|
|
21003
|
|
) != 8'hD4 || u_card.read_backing(
|
|
21004
|
|
) != 8'hE5 || u_axi_memory.read_word(
|
|
32'h0000_000C
|
|
) != 32'h0001_0004) begin
|
|
$fatal(1, "memory-to-card DMA end-to-end direction failed");
|
|
end
|
|
|
|
apb_read(AxiCount, apb_value);
|
|
timeout_count_before = apb_value;
|
|
u_axi_memory.clear_memory();
|
|
u_axi_memory.write_word(32'h0000_0000, 32'h0000_0400);
|
|
u_axi_memory.write_word(32'h0000_0004, 32'd4);
|
|
u_axi_memory.write_word(32'h0000_0008, 32'd0);
|
|
u_axi_memory.write_word(32'h0000_000C, 32'h0000_0005);
|
|
u_axi_memory.write_word(32'h0000_0400, 32'h1122_3344);
|
|
u_axi_memory.inject_read_error(32'h0000_0400);
|
|
configure_dma_data(1'b0, 1'b1, 16'd4);
|
|
u_card.arm_write(22000, 4, 1'b0, 1'b0);
|
|
apb_write(DataStart, 32'd1, 4'h1);
|
|
wait_data();
|
|
apb_read(AxiCount, apb_value);
|
|
if (apb_value != timeout_count_before + 1 || u_axi_memory.read_word(
|
|
32'h0000_000C
|
|
) != 32'h0002_0004) begin
|
|
$fatal(1, "AXI error counter/writeback contract failed: before=%0d after=%0d",
|
|
timeout_count_before, apb_value);
|
|
end
|
|
u_axi_memory.clear_errors();
|
|
|
|
apb_write(ClockCtrl, 32'h0000_0100, 4'hF);
|
|
repeat (4) @(posedge clk_i);
|
|
apb_write(ClockCtrl, 32'h0000_0101, 4'hF);
|
|
configure_data(1'b0, 1'b1, 16'd512, 16'd1);
|
|
u_card.arm_write(8192, 512, 1'b0, 1'b0);
|
|
apb_write(DataStart, 32'd1, 4'h1);
|
|
for (integer word_index = 0; word_index < 128; word_index++) begin
|
|
logic [31:0] word;
|
|
word = '0;
|
|
for (integer byte_index = 0; byte_index < 4; byte_index++) begin
|
|
word[byte_index*8+:8] = (word_index * 4) + byte_index;
|
|
end
|
|
apb_write(PioData, word, 4'hF);
|
|
end
|
|
wait_data();
|
|
for (integer byte_index = 0; byte_index < 512; byte_index++) begin
|
|
if (u_card.read_backing(8192 + byte_index) != byte_index[7:0]) begin
|
|
$fatal(1, "512-byte PIO write mismatch at %0d: %h", byte_index, u_card.read_backing(
|
|
8192 + byte_index));
|
|
end
|
|
end
|
|
|
|
configure_data(1'b0, 1'b0, 16'd512, 16'd1);
|
|
u_card.arm_read(8192, 512, 1'b0, 1'b0);
|
|
apb_write(DataStart, 32'd1, 4'h1);
|
|
for (integer word_index = 0; word_index < 128; word_index++) begin
|
|
wait_pio_valid();
|
|
apb_read(PioData, apb_value);
|
|
for (integer byte_index = 0; byte_index < 4; byte_index++) begin
|
|
if (apb_value[byte_index*8+:8] != ((word_index * 4) + byte_index) % 256) begin
|
|
$fatal(1, "512-byte PIO read mismatch word=%0d byte=%0d: %h", word_index, byte_index,
|
|
apb_value);
|
|
end
|
|
end
|
|
end
|
|
wait_data();
|
|
|
|
for (integer tail_bytes = 1; tail_bytes <= 5; tail_bytes++) begin
|
|
if (tail_bytes != 4) begin
|
|
tail_read_test(12000 + (tail_bytes * 16), 1'b0, tail_bytes);
|
|
tail_write_test(13000 + (tail_bytes * 16), 1'b0, tail_bytes);
|
|
tail_read_test(14000 + (tail_bytes * 16), 1'b1, tail_bytes);
|
|
tail_write_test(15000 + (tail_bytes * 16), 1'b1, tail_bytes);
|
|
end
|
|
end
|
|
|
|
apb_write(TimeoutBusy, 32'd4, 4'hF);
|
|
configure_data(1'b0, 1'b1, 16'd4, 16'd1);
|
|
u_card.arm_write(15996, 4, 1'b0, 1'b0);
|
|
apb_write(DataStart, 32'd1, 4'h1);
|
|
apb_write(PioData, 32'h4433_2211, 4'hF);
|
|
wait_data();
|
|
apb_read(DataStatus, apb_value);
|
|
if ((apb_value & 32'h0000_0010) == 0) begin
|
|
$fatal(1, "DAT0 busy timeout did not use TIMEOUT_BUSY");
|
|
end
|
|
apb_write(TimeoutBusy, 32'd1000000, 4'hF);
|
|
|
|
configure_data(1'b0, 1'b1, 16'd4, 16'd1);
|
|
u_card.inject_next_crc_error();
|
|
u_card.arm_write(16000, 4, 1'b0, 1'b0);
|
|
apb_read(CrcCount, apb_value);
|
|
crc_count_before = apb_value;
|
|
apb_write(DataStart, 32'd1, 4'h1);
|
|
apb_write(PioData, 32'h4433_2211, 4'hF);
|
|
wait_data();
|
|
apb_read(DataStatus, apb_value);
|
|
if ((apb_value & 32'h0000_0008) == 0) begin
|
|
$fatal(1, "CRC-error write response token was not reported: status=%h dataerr=%b crc=%b",
|
|
apb_value, u_apb4_sdio.u_sdio_core.s_data_err, u_apb4_sdio.u_sdio_core.s_data_crc_err);
|
|
end
|
|
apb_read(CrcCount, apb_value);
|
|
if (apb_value != crc_count_before + 1) begin
|
|
$fatal(1, "CRC counter incremented incorrectly: before=%0d after=%0d", crc_count_before,
|
|
apb_value);
|
|
end
|
|
|
|
configure_data(1'b0, 1'b1, 16'd4, 16'd1);
|
|
u_card.inject_next_write_error();
|
|
u_card.arm_write(16004, 4, 1'b0, 1'b0);
|
|
apb_write(DataStart, 32'd1, 4'h1);
|
|
apb_write(PioData, 32'h8877_6655, 4'hF);
|
|
wait_data();
|
|
apb_read(DataStatus, apb_value);
|
|
if (((apb_value & 32'h0000_0002) == 0) || ((apb_value & 32'h0000_0008) != 0)) begin
|
|
$fatal(1, "write-error response token was not distinguished: %h", apb_value);
|
|
end
|
|
|
|
$display("SDIO standalone native SD model test passed (%s)",
|
|
CardHighCapacity ? "SDHC" : "SDSC");
|
|
$finish;
|
|
end
|
|
endmodule
|
|
|
|
module sdio_sdsc_tb;
|
|
sdio_standalone_tb #(.CardHighCapacity(1'b0)) u_tb ();
|
|
endmodule
|
|
|
|
module sdio_onebit_tb;
|
|
sdio_standalone_tb #(.RunFourBit(1'b0)) u_tb ();
|
|
endmodule
|