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.
This commit is contained in:
maksyuki committed 2026-08-20 10:03:51 +08:00
1 parent faad445e42
commit a8325ab6b7
83 files changed
+11777 -153

No files matched your search

+5 -2
View File
@@ -16,7 +16,7 @@ module apb4_topology_tb;
begin
s_decode_addr = address;
#1;
if (s_psel_comb !== (18'd1 << slot)) begin
if (s_psel_comb !== (19'd1 << slot)) begin
$fatal(1, "unexpected APB4 target slot for %h: got %b", address, s_psel_comb);
end
end
@@ -40,7 +40,10 @@ module apb4_topology_tb;
expect_select(`SOC_ADDR_APB4_SDRAM_BASE, 13);
expect_select(`SOC_ADDR_APB4_OPIPSRAM_BASE, 16);
expect_select(`SOC_ADDR_APB4_I2C1_BASE, 17);
expect_disabled(`SOC_ADDR_APB4_SDIO_BASE);
expect_select(`SOC_ADDR_APB4_SDIO0_BASE, 15);
expect_select(`SOC_ADDR_APB4_SDIO1_BASE, 18);
expect_disabled(`SOC_ADDR_APB4_GA_BASE);
expect_disabled(`SOC_ADDR_APB4_APU_BASE);
$display("SoC topology APB4 routing test passed");
$finish;
end
+87 -5
View File
@@ -4,7 +4,7 @@
`include "rib_defs.svh"
module axi4_interconnect_tb;
localparam int NumMasters = 3;
localparam int NumMasters = 5;
localparam int NumTargets = 9;
logic clk_i = 1'b0;
@@ -14,7 +14,7 @@ module axi4_interconnect_tb;
logic [ 3:0] fault_wstrb_o;
logic fault_reserved_o;
logic fault_access_o;
logic [ 1:0] fault_master_o;
logic [ 2:0] fault_master_o;
logic [ 2:0] fault_code_o;
logic user_bus_idle_o;
@@ -62,11 +62,14 @@ module axi4_interconnect_tb;
.perf_mgmt_wait_o (),
.perf_user_wait_o (),
.perf_dma_wait_o (),
.perf_sdio0_wait_o (),
.perf_sdio1_wait_o (),
.perf_apb4_periph_wait_o(),
.perf_apb4_system_wait_o(),
.perf_sdram_wait_o (),
.perf_psram_wait_o (),
.perf_flash_wait_o ()
.perf_flash_wait_o (),
.perf_opipsram_wait_o ()
);
for (genvar target = 0; target < NumTargets; target++) begin : GEN_TARGETS
@@ -136,7 +139,7 @@ module axi4_interconnect_tb;
end
if ((beat == int'(length)) &&
(!fault_valid_o || (fault_addr_o != address) || fault_access_o ||
(fault_master_o != 2'd0) || (fault_code_o != fault_code))) begin
(fault_master_o != 3'd0) || (fault_code_o != fault_code))) begin
$fatal(1, "management fault classification mismatch");
end
beat = beat + 1;
@@ -168,7 +171,7 @@ module axi4_interconnect_tb;
#1;
if ((masters[1].bresp != `AXI4_RESP_SLAVE_ERROR) || !fault_valid_o ||
!fault_access_o || (fault_addr_o != address) || (fault_wstrb_o != 4'hF) ||
(fault_master_o != 2'd1) || (fault_code_o != `RIB_RESP_PROTERR)) begin
(fault_master_o != 3'd1) || (fault_code_o != `RIB_RESP_PROTERR)) begin
$fatal(1, "user access fault classification mismatch");
end
@(negedge clk_i);
@@ -176,16 +179,95 @@ module axi4_interconnect_tb;
end
endtask
task automatic issue_contending_sdio_faults;
integer cycles;
begin
@(negedge clk_i);
masters[3].araddr = 32'hA000_1000;
masters[3].arlen = 8'd0;
masters[3].arvalid = 1'b1;
masters[4].araddr = 32'hA000_2000;
masters[4].arlen = 8'd0;
masters[4].arvalid = 1'b1;
do @(posedge clk_i); while (!(masters[3].arready && masters[4].arready));
@(negedge clk_i);
masters[3].arvalid = 1'b0;
masters[4].arvalid = 1'b0;
cycles = 0;
while (!masters[3].rvalid && !masters[4].rvalid && cycles < 100) begin
@(negedge clk_i);
cycles = cycles + 1;
end
if (cycles >= 100) $fatal(1, "SDIO master contention did not produce a response");
if (masters[3].rvalid) begin
masters[3].rready = 1'b1;
#1;
if ((masters[3].rresp != `AXI4_RESP_DECODE_ERROR) || !fault_valid_o ||
(fault_master_o != 3'd3) || (fault_addr_o != 32'hA000_1000) ||
(fault_code_o != `RIB_RESP_DECERR)) begin
$fatal(1, "SDIO0 fault attribution mismatch");
end
@(posedge clk_i);
@(negedge clk_i);
masters[3].rready = 1'b0;
end else begin
masters[4].rready = 1'b1;
#1;
if ((masters[4].rresp != `AXI4_RESP_DECODE_ERROR) || !fault_valid_o ||
(fault_master_o != 3'd4) || (fault_addr_o != 32'hA000_2000) ||
(fault_code_o != `RIB_RESP_DECERR)) begin
$fatal(1, "SDIO1 fault attribution mismatch");
end
@(posedge clk_i);
@(negedge clk_i);
masters[4].rready = 1'b0;
end
cycles = 0;
while (!masters[3].rvalid && !masters[4].rvalid && cycles < 100) begin
@(negedge clk_i);
cycles = cycles + 1;
end
if (cycles >= 100) $fatal(1, "second SDIO master response was lost");
if (masters[3].rvalid) begin
masters[3].rready = 1'b1;
#1;
if ((masters[3].rresp != `AXI4_RESP_DECODE_ERROR) || !fault_valid_o ||
(fault_master_o != 3'd3) || (fault_addr_o != 32'hA000_1000) ||
(fault_code_o != `RIB_RESP_DECERR)) begin
$fatal(1, "second SDIO0 fault attribution mismatch");
end
@(posedge clk_i);
@(negedge clk_i);
masters[3].rready = 1'b0;
end else begin
masters[4].rready = 1'b1;
#1;
if ((masters[4].rresp != `AXI4_RESP_DECODE_ERROR) || !fault_valid_o ||
(fault_master_o != 3'd4) || (fault_addr_o != 32'hA000_2000) ||
(fault_code_o != `RIB_RESP_DECERR)) begin
$fatal(1, "second SDIO1 fault attribution mismatch");
end
@(posedge clk_i);
@(negedge clk_i);
masters[4].rready = 1'b0;
end
end
endtask
initial begin
`INIT_MASTER(0)
`INIT_MASTER(1)
`INIT_MASTER(2)
`INIT_MASTER(3)
`INIT_MASTER(4)
repeat (4) @(posedge clk_i);
rst_n_i = 1'b1;
issue_mgmt_error_read(32'hA000_0000, 8'd0, `AXI4_RESP_DECODE_ERROR, `RIB_RESP_DECERR);
issue_mgmt_error_read(32'h3000_0000, 8'd16, `AXI4_RESP_SLAVE_ERROR, `RIB_RESP_BURSTERR);
issue_denied_user_write(32'h1000_B000);
issue_contending_sdio_faults();
$display("AXI4 interconnect fault classification test passed");
$finish;
+1 -1
View File
@@ -154,7 +154,7 @@ module bus_fault_tb;
repeat (2) @(posedge clk_i);
rst_n_i = 1'b1;
expect_fault(32'h1000_F000, 4'hF, 1'b1, `RIB_RESP_RESERVED);
expect_fault(32'h1001_2000, 4'hF, 1'b1, `RIB_RESP_RESERVED);
expect_fault(32'hA000_0000, 4'h0, 1'b0, `RIB_RESP_DECERR);
expect_user_denied(32'h1000_B000, 4'hF);
$display("bus fault responder test passed");
+72
View File
@@ -0,0 +1,72 @@
// 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_50mhz_tb;
logic clk_i = 1'b0;
logic rst_n_i = 1'b0;
logic enable_i = 1'b0;
logic [15:0] half_period_i = 16'd1;
logic sck_o;
logic launch_tick_o;
logic sample_tick_o;
logic running_o;
integer rising_count;
integer falling_count;
logic previous_sck;
always #5 clk_i = ~clk_i;
sdio_clock u_sdio_clock (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.enable_i (enable_i),
.half_period_i(half_period_i),
.sck_o (sck_o),
.launch_tick_o(launch_tick_o),
.sample_tick_o(sample_tick_o),
.running_o (running_o)
);
always @(posedge clk_i) begin
if (rst_n_i && (sck_o != previous_sck)) begin
if (sck_o) begin
rising_count = rising_count + 1;
end else begin
falling_count = falling_count + 1;
end
end
previous_sck = sck_o;
end
initial begin
rising_count = 0;
falling_count = 0;
previous_sck = 1'b0;
repeat (2) @(posedge clk_i);
rst_n_i = 1'b1;
enable_i = 1'b1;
repeat (12) @(posedge clk_i);
if ((rising_count < 5) || (falling_count < 5) ||
(rising_count > falling_count + 1) ||
(falling_count > rising_count + 1) || !running_o) begin
$fatal(1, "50 MHz phase count mismatch rise=%0d fall=%0d running=%b", rising_count,
falling_count, running_o);
end
enable_i = 1'b0;
repeat (3) @(posedge clk_i);
if (sck_o || running_o) begin
$fatal(1, "50 MHz clock did not stop low");
end
$display("SDIO 100 MHz to 50 MHz SDCLK test passed");
$finish;
end
endmodule
+267
View File
@@ -0,0 +1,267 @@
// 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
`include "axi4_define.svh"
// Deterministic AXI4 memory target for SDIO standalone verification.
// It models the actual Mini-fabric contract: one transaction per direction,
// 32-bit data, INCR/FIXED bursts, and no outstanding IDs.
module sdio_axi_memory_responder #(
parameter int DepthWords = 16384
) (
input logic clk_i,
input logic rst_n_i,
axi4_if.slave axi4
);
logic [31:0] s_memory [0:DepthWords-1];
logic s_read_active_q;
logic [31:0] s_read_addr_q;
logic [ 7:0] s_read_len_q;
logic [ 7:0] s_read_index_q;
logic s_read_error_q;
logic s_read_bad_id_q;
logic s_read_bad_last_q;
logic s_write_active_q;
logic [31:0] s_write_addr_q;
logic [ 7:0] s_write_len_q;
logic [ 7:0] s_write_index_q;
logic s_write_error_q;
logic s_write_bad_id_q;
logic s_aw_stall_q;
logic s_ar_stall_q;
logic s_w_stall_q;
logic s_b_stall_q;
integer s_read_error_address;
integer s_write_error_address;
integer s_aw_stall_cycles;
integer s_ar_stall_cycles;
integer s_w_stall_cycles;
integer s_b_stall_cycles;
integer read_burst_count;
integer write_burst_count;
integer max_read_beats;
integer max_write_beats;
function automatic integer word_index(input logic [31:0] address_i);
integer index;
begin
index = address_i >> 2;
if ((index < 0) || (index >= DepthWords)) begin
index = 0;
end
return index;
end
endfunction
task automatic clear_memory;
begin
for (integer index = 0; index < DepthWords; index++) begin
s_memory[index] = 32'd0;
end
end
endtask
task automatic write_word(input logic [31:0] address_i, input logic [31:0] data_i);
begin
s_memory[word_index(address_i)] = data_i;
end
endtask
function automatic logic [31:0] read_word(input logic [31:0] address_i);
begin
read_word = s_memory[word_index(address_i)];
end
endfunction
task automatic set_backpressure(input integer aw_cycles_i, input integer ar_cycles_i,
input integer w_cycles_i, input integer b_cycles_i);
begin
s_aw_stall_cycles = (aw_cycles_i < 0) ? 0 : aw_cycles_i;
s_ar_stall_cycles = (ar_cycles_i < 0) ? 0 : ar_cycles_i;
s_w_stall_cycles = (w_cycles_i < 0) ? 0 : w_cycles_i;
s_b_stall_cycles = (b_cycles_i < 0) ? 0 : b_cycles_i;
end
endtask
task automatic inject_read_error(input integer address_i);
begin
s_read_error_address = address_i;
end
endtask
task automatic inject_write_error(input integer address_i);
begin
s_write_error_address = address_i;
end
endtask
task automatic inject_read_bad_id;
begin
s_read_bad_id_q = 1'b1;
end
endtask
task automatic inject_read_bad_last;
begin
s_read_bad_last_q = 1'b1;
end
endtask
task automatic inject_write_bad_id;
begin
s_write_bad_id_q = 1'b1;
end
endtask
task automatic clear_errors;
begin
s_read_error_address = -1;
s_write_error_address = -1;
s_read_bad_id_q = 1'b0;
s_write_bad_id_q = 1'b0;
s_read_bad_last_q = 1'b0;
end
endtask
assign axi4.arready = !s_read_active_q && !s_ar_stall_q;
assign axi4.rid = s_read_bad_id_q ? '1 : '0;
assign axi4.rdata = s_memory[word_index(
s_read_addr_q+((axi4.arburst==`AXI4_BURST_TYPE_FIXED)?32'd0 : ({24'd0, s_read_index_q}<<2))
)];
assign axi4.rresp = (s_read_error_q) ? `AXI4_RESP_SLAVE_ERROR : `AXI4_RESP_OKAY;
assign axi4.rlast = s_read_bad_last_q || ((s_read_index_q + 1'b1) >= (s_read_len_q + 1'b1));
assign axi4.ruser = '0;
assign axi4.rvalid = s_read_active_q;
assign axi4.awready = !s_write_active_q && !axi4.bvalid && !s_aw_stall_q;
assign axi4.wready = s_write_active_q && !s_w_stall_q;
assign axi4.bid = s_write_bad_id_q ? '1 : '0;
assign axi4.bresp = s_write_error_q ? `AXI4_RESP_SLAVE_ERROR : `AXI4_RESP_OKAY;
assign axi4.buser = '0;
assign axi4.bvalid = s_write_active_q && !s_b_stall_q &&
(s_write_index_q >= s_write_len_q + 1'b1);
always @(posedge clk_i or negedge rst_n_i) begin
if (!rst_n_i) begin
s_read_active_q <= 1'b0;
s_read_addr_q <= 32'd0;
s_read_len_q <= 8'd0;
s_read_index_q <= 8'd0;
s_read_error_q <= 1'b0;
s_read_bad_id_q <= 1'b0;
s_read_bad_last_q <= 1'b0;
s_write_active_q <= 1'b0;
s_write_addr_q <= 32'd0;
s_write_len_q <= 8'd0;
s_write_index_q <= 8'd0;
s_write_error_q <= 1'b0;
s_write_bad_id_q <= 1'b0;
s_aw_stall_q <= 1'b0;
s_ar_stall_q <= 1'b0;
s_w_stall_q <= 1'b0;
s_b_stall_q <= 1'b0;
s_read_error_address = -1;
s_write_error_address = -1;
s_aw_stall_cycles = 0;
s_ar_stall_cycles = 0;
s_w_stall_cycles = 0;
s_b_stall_cycles = 0;
read_burst_count = 0;
write_burst_count = 0;
max_read_beats = 0;
max_write_beats = 0;
end else begin
if (s_aw_stall_cycles > 0) begin
s_aw_stall_cycles = s_aw_stall_cycles - 1;
s_aw_stall_q = 1'b1;
end else begin
s_aw_stall_q = 1'b0;
end
if (s_ar_stall_cycles > 0) begin
s_ar_stall_cycles = s_ar_stall_cycles - 1;
s_ar_stall_q = 1'b1;
end else begin
s_ar_stall_q = 1'b0;
end
if (s_w_stall_cycles > 0) begin
s_w_stall_cycles = s_w_stall_cycles - 1;
s_w_stall_q = 1'b1;
end else begin
s_w_stall_q = 1'b0;
end
if (s_b_stall_cycles > 0) begin
s_b_stall_cycles = s_b_stall_cycles - 1;
s_b_stall_q = 1'b1;
end else begin
s_b_stall_q = 1'b0;
end
if (axi4.arvalid && axi4.arready) begin
s_read_active_q = 1'b1;
s_read_addr_q = axi4.araddr;
s_read_len_q = axi4.arlen;
s_read_index_q = 8'd0;
s_read_error_q = (s_read_error_address >= 0) && (axi4.araddr == s_read_error_address);
s_read_bad_last_q = 1'b0;
read_burst_count = read_burst_count + 1;
if (({24'd0, axi4.arlen} + 32'd1) > max_read_beats) begin
max_read_beats = {24'd0, axi4.arlen} + 32'd1;
end
end
if (axi4.rvalid && axi4.rready) begin
if (axi4.rlast) begin
s_read_active_q = 1'b0;
if (s_read_error_q) begin
s_read_error_address = -1;
end
end else begin
s_read_index_q = s_read_index_q + 1'b1;
end
end
if (axi4.awvalid && axi4.awready) begin
s_write_active_q = 1'b1;
s_write_addr_q = axi4.awaddr;
s_write_len_q = axi4.awlen;
s_write_index_q = 8'd0;
s_write_error_q = (s_write_error_address >= 0) && (axi4.awaddr == s_write_error_address);
write_burst_count = write_burst_count + 1;
if (({24'd0, axi4.awlen} + 32'd1) > max_write_beats) begin
max_write_beats = {24'd0, axi4.awlen} + 32'd1;
end
end
if (axi4.wvalid && axi4.wready) begin
for (integer byte_index = 0; byte_index < 4; byte_index++) begin
if (axi4.wstrb[byte_index]) begin
s_memory[word_index(s_write_addr_q + ((axi4.awburst == `AXI4_BURST_TYPE_FIXED)
? 32'd0
: ({24'd0, s_write_index_q} << 2)))][byte_index*8+:8] <=
axi4.wdata[byte_index*8+:8];
end
end
s_write_index_q = s_write_index_q + 1'b1;
end
if (axi4.bvalid && axi4.bready) begin
s_write_active_q = 1'b0;
if (s_write_error_q) begin
s_write_error_address = -1;
end
end
end
end
initial begin
clear_memory();
clear_errors();
end
endmodule
+52
View File
@@ -0,0 +1,52 @@
`timescale 1ns / 1ps
module sdio_clock_tb;
logic clk_i = 1'b0;
logic rst_n_i = 1'b0;
logic enable_i = 1'b0;
logic [15:0] half_period_i = 16'd2;
logic sck_o;
logic launch_tick_o;
logic sample_tick_o;
logic running_o;
integer launch_count;
integer sample_count;
always #1 clk_i = ~clk_i;
sdio_clock u_sdio_clock (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.enable_i (enable_i),
.half_period_i(half_period_i),
.sck_o (sck_o),
.launch_tick_o(launch_tick_o),
.sample_tick_o(sample_tick_o),
.running_o (running_o)
);
always @(posedge clk_i) begin
if (launch_tick_o) launch_count = launch_count + 1;
if (sample_tick_o) sample_count = sample_count + 1;
end
initial begin
launch_count = 0;
sample_count = 0;
repeat (2) @(posedge clk_i);
rst_n_i = 1'b1;
enable_i = 1'b1;
repeat (8) @(posedge clk_i);
if ((launch_count != 2) || (sample_count != 2) || !running_o) begin
$fatal(1, "phase counts mismatch: launch=%0d sample=%0d running=%b", launch_count,
sample_count, running_o);
end
enable_i = 1'b0;
repeat (4) @(posedge clk_i);
if (sck_o || running_o) begin
$fatal(1, "clock did not stop on a complete low phase");
end
$display("SDIO phase clock test passed");
$finish;
end
endmodule
+121
View File
@@ -0,0 +1,121 @@
`timescale 1ns / 1ps
module sdio_command_tb;
logic clk_i = 1'b0;
logic rst_n_i = 1'b0;
logic clock_enable_i = 1'b0;
logic [ 15:0] half_period_i = 16'd1;
logic launch_tick;
logic sample_tick;
logic sck;
logic running;
logic start_i = 1'b0;
logic [ 5:0] cmd_index_i = 6'd0;
logic [ 31:0] cmd_arg_i = '0;
sdio_pkg::sdio_resp_type_e resp_type_i = sdio_pkg::SdioRespNone;
logic crc_check_i = 1'b0;
logic index_check_i = 1'b0;
logic [ 31:0] timeout_cycles_i = 32'd1000;
logic cmd_di_i = 1'b1;
logic dat0_i = 1'b1;
logic cmd_oe_o;
logic cmd_do_o;
logic busy_o;
logic done_o;
logic error_o;
logic timeout_o;
logic crc_error_o;
logic index_error_o;
logic busy_timeout_o;
logic [135:0] response_o;
logic [ 5:0] last_cmd_index_o;
integer launch_count;
integer captured_bits;
logic [ 47:0] captured_command;
always #1 clk_i = ~clk_i;
sdio_clock u_sdio_clock (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.enable_i (clock_enable_i),
.half_period_i(half_period_i),
.sck_o (sck),
.launch_tick_o(launch_tick),
.sample_tick_o(sample_tick),
.running_o (running)
);
sdio_command u_sdio_command (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.launch_tick_i (launch_tick),
.sample_tick_i (sample_tick),
.start_i (start_i),
.cmd_index_i (cmd_index_i),
.cmd_arg_i (cmd_arg_i),
.resp_type_i (resp_type_i),
.crc_check_i (crc_check_i),
.index_check_i (index_check_i),
.timeout_cycles_i(timeout_cycles_i),
.cmd_di_i (cmd_di_i),
.dat0_i (dat0_i),
.cmd_oe_o (cmd_oe_o),
.cmd_do_o (cmd_do_o),
.busy_o (busy_o),
.done_o (done_o),
.error_o (error_o),
.timeout_o (timeout_o),
.crc_error_o (crc_error_o),
.index_error_o (index_error_o),
.busy_timeout_o (busy_timeout_o),
.response_o (response_o),
.last_cmd_index_o(last_cmd_index_o)
);
always @(posedge clk_i) begin
if (launch_tick) launch_count = launch_count + 1;
end
always @(posedge sck) begin
if (cmd_oe_o) begin
captured_command = {captured_command[46:0], cmd_do_o};
captured_bits = captured_bits + 1;
end
end
initial begin
launch_count = 0;
captured_bits = 0;
captured_command = '0;
repeat (2) @(posedge clk_i);
rst_n_i = 1'b1;
@(negedge clk_i);
start_i = 1'b1;
@(negedge clk_i);
start_i = 1'b0;
clock_enable_i = 1'b1;
wait (done_o);
if (error_o || (launch_count != 48) || (captured_bits != 48) ||
(captured_command[47:46] != 2'b01) || (captured_command[0] != 1'b1) ||
(captured_command[7:1] != sdio_pkg::sdio_crc7_calc(
captured_command[47:8]
)) || (last_cmd_index_o != 6'd0) || cmd_oe_o) begin
$fatal(1, "no-response command frame failed: err=%b launches=%0d captured=%0d frame=%h oe=%b",
error_o, launch_count, captured_bits, captured_command, cmd_oe_o);
end
@(posedge clk_i);
resp_type_i = sdio_pkg::SdioRespR1;
timeout_cycles_i = 32'd4;
@(negedge clk_i);
start_i = 1'b1;
@(negedge clk_i);
start_i = 1'b0;
wait (done_o);
if (!error_o || !timeout_o) begin
$fatal(1, "response timeout was not reported");
end
$display("SDIO command engine test passed");
$finish;
end
endmodule
+66
View File
@@ -0,0 +1,66 @@
`timescale 1ns / 1ps
module sdio_crc_tb;
logic [39:0] crc7_data;
logic [ 6:0] crc7_value;
logic [15:0] crc16_value;
logic [15:0] crc16_seed;
logic [ 7:0] crc16_data;
sdio_crc7 u_sdio_crc7 (
.data_i(crc7_data),
.crc_o (crc7_value)
);
sdio_crc16 u_sdio_crc16 (
.crc_i (crc16_seed),
.data_i(crc16_data),
.crc_o (crc16_value)
);
initial begin
crc7_data = {1'b0, 1'b1, 6'd0, 32'd0};
#1;
if (crc7_value != 7'h4A) begin
$fatal(1, "CMD0 CRC7 mismatch: %h", crc7_value);
end
crc7_data = {1'b0, 1'b1, 6'd8, 32'h0000_01AA};
#1;
if (crc7_value != 7'h43) begin
$fatal(1, "CMD8 CRC7 mismatch: %h", crc7_value);
end
crc16_seed = 16'h0000;
crc16_data = "1";
#1;
crc16_seed = crc16_value;
crc16_data = "2";
#1;
crc16_seed = crc16_value;
crc16_data = "3";
#1;
crc16_seed = crc16_value;
crc16_data = "4";
#1;
crc16_seed = crc16_value;
crc16_data = "5";
#1;
crc16_seed = crc16_value;
crc16_data = "6";
#1;
crc16_seed = crc16_value;
crc16_data = "7";
#1;
crc16_seed = crc16_value;
crc16_data = "8";
#1;
crc16_seed = crc16_value;
crc16_data = "9";
#1;
if (crc16_value != 16'h31C3) begin
$fatal(1, "CRC16 mismatch: %h", crc16_value);
end
$display("SDIO CRC vectors passed");
$finish;
end
endmodule
+68
View File
@@ -0,0 +1,68 @@
`timescale 1ns / 1ps
module sdio_descriptor_tb;
logic [31:0] buffer_addr_i = 32'h4000_0000;
logic [31:0] byte_count_i = 32'd16;
logic [31:0] next_addr_i = '0;
logic [31:0] control_status_i = 32'h0001_0005;
logic [15:0] desc_index_i = 16'd0;
logic own_o;
logic chain_o;
logic end_o;
logic irq_o;
logic valid_o;
logic error_o;
logic address_error_o;
logic length_error_o;
logic chain_error_o;
sdio_dma_descriptor #(
.DescCount(4)
) u_sdio_dma_descriptor (
.buffer_addr_i (buffer_addr_i),
.byte_count_i (byte_count_i),
.next_addr_i (next_addr_i),
.control_status_i(control_status_i),
.desc_index_i (desc_index_i),
.own_o (own_o),
.chain_o (chain_o),
.end_o (end_o),
.irq_o (irq_o),
.valid_o (valid_o),
.error_o (error_o),
.address_error_o (address_error_o),
.length_error_o (length_error_o),
.chain_error_o (chain_error_o)
);
initial begin
#1;
if (!valid_o || error_o || !own_o || !end_o) begin
$fatal(1, "valid terminal descriptor rejected");
end
control_status_i = 32'h0000_0003;
next_addr_i = 32'h4000_0100;
#1;
if (!valid_o || error_o || !chain_o || end_o) begin
$fatal(1, "valid chained descriptor rejected");
end
next_addr_i = 32'h4000_0FF0;
#1;
if (!error_o || !address_error_o) $fatal(1, "crossing descriptor chain not rejected");
buffer_addr_i = 32'h4000_0002;
#1;
if (!error_o || !address_error_o) $fatal(1, "unaligned buffer not rejected");
buffer_addr_i = 32'h4000_0000;
byte_count_i = 32'd0;
#1;
if (!error_o || !length_error_o) $fatal(1, "zero length not rejected");
byte_count_i = 32'd16;
control_status_i = 32'h0000_0001;
next_addr_i = 32'h4000_0100;
desc_index_i = 16'd3;
#1;
if (!error_o || !chain_error_o) $fatal(1, "chain bound not rejected");
$display("SDIO descriptor validation test passed");
$finish;
end
endmodule
+314
View File
@@ -0,0 +1,314 @@
// 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
+324
View File
@@ -0,0 +1,324 @@
`timescale 1ns / 1ps
`include "axi4_define.svh"
module sdio_dma_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'b0;
logic [31:0] desc_base_i = 32'd0;
logic [15:0] desc_count_i = 16'd1;
logic [31:0] total_bytes_i = 32'd5;
logic data_in_valid_i = 1'b0;
logic data_in_ready_o;
logic [31:0] data_in_i = '0;
logic [ 3:0] data_in_strb_i = '0;
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;
logic [31:0] memory [0:2047];
logic read_active_q = 1'b0;
logic [31:0] read_addr_q = '0;
logic [ 4:0] read_beats_q = '0;
logic [ 4:0] read_index_q = '0;
logic write_active_q = 1'b0;
logic [31:0] write_addr_q = '0;
logic write_response_q = 1'b0;
integer read_bursts;
integer max_read_beats;
integer output_count;
logic descriptor_irq_seen;
logic [31:0] output_data [ 0:3];
logic [ 3:0] output_strb [ 0:3];
logic output_last [ 0:3];
logic error_mode = 1'b0;
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;
assign dma_axi4.arready = !read_active_q;
assign dma_axi4.rid = 1'b0;
assign dma_axi4.rdata = memory[(read_addr_q>>2)+read_index_q];
assign dma_axi4.rresp =
error_mode && (read_addr_q == 32'h0000_0200) ? `AXI4_RESP_SLAVE_ERROR :
`AXI4_RESP_OKAY;
assign dma_axi4.rlast = (read_index_q + 1'b1) >= read_beats_q;
assign dma_axi4.ruser = '0;
assign dma_axi4.rvalid = read_active_q;
assign dma_axi4.awready = !write_active_q && !write_response_q;
assign dma_axi4.wready = write_active_q;
assign dma_axi4.bid = 1'b0;
assign dma_axi4.bresp = `AXI4_RESP_OKAY;
assign dma_axi4.buser = '0;
assign dma_axi4.bvalid = write_response_q;
always_ff @(posedge clk_i or negedge rst_n_i) begin
if (!rst_n_i) begin
read_active_q <= 1'b0;
read_addr_q <= '0;
read_beats_q <= '0;
read_index_q <= '0;
write_active_q <= 1'b0;
write_addr_q <= '0;
write_response_q <= 1'b0;
read_bursts <= 0;
max_read_beats <= 0;
end else begin
if (dma_axi4.arvalid && dma_axi4.arready) begin
read_active_q <= 1'b1;
read_addr_q <= dma_axi4.araddr;
read_beats_q <= dma_axi4.arlen + 1'b1;
read_index_q <= '0;
read_bursts <= read_bursts + 1;
if ((dma_axi4.arlen + 1'b1) > max_read_beats) begin
max_read_beats <= dma_axi4.arlen + 1'b1;
end
end
if (dma_axi4.rvalid && dma_axi4.rready) begin
if (dma_axi4.rlast) begin
read_active_q <= 1'b0;
end else begin
read_index_q <= read_index_q + 1'b1;
end
end
if (dma_axi4.awvalid && dma_axi4.awready) begin
write_active_q <= 1'b1;
write_addr_q <= dma_axi4.awaddr;
end
if (dma_axi4.wvalid && dma_axi4.wready) begin
for (integer byte_index = 0; byte_index < 4; byte_index++) begin
if (dma_axi4.wstrb[byte_index]) begin
memory[(write_addr_q>>2)][byte_index*8+:8] <= dma_axi4.wdata[byte_index*8+:8];
end
end
if (dma_axi4.wlast) begin
write_active_q <= 1'b0;
write_response_q <= 1'b1;
end else begin
write_addr_q <= write_addr_q + 4;
end
end
if (dma_axi4.bvalid && dma_axi4.bready) begin
write_response_q <= 1'b0;
end
end
end
always_ff @(posedge clk_i) begin
if (data_out_valid_o && data_out_ready_i) begin
output_data[output_count] <= data_out_o;
output_strb[output_count] <= data_out_strb_o;
output_last[output_count] <= data_out_last_o;
output_count <= output_count + 1;
end
if (u_sdio_dma.descriptor_irq_o) begin
descriptor_irq_seen <= 1'b1;
end
end
sdio_dma #(
.AddrWidth(32),
.DataWidth(32),
.DescCount(16)
) 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)
);
initial begin
for (integer index = 0; index < 2048; index++) memory[index] = '0;
output_count = 0;
descriptor_irq_seen = 1'b0;
direction_i = 1'b1;
memory[0] = 32'h0000_0FFC;
memory[1] = 32'd5;
memory[2] = 32'd0;
memory[3] = 32'h0000_000D;
memory[32'h0FFC>>2] = 32'h4433_2211;
memory[32'h1000>>2] = 32'h0000_0055;
repeat (3) @(posedge clk_i);
rst_n_i = 1'b1;
@(negedge clk_i);
start_i = 1'b1;
@(negedge clk_i);
start_i = 1'b0;
wait (done_o);
if (error_o || (output_count != 2) || (bytes_done_o != 32'd5) ||
(read_bursts < 3) || (max_read_beats > 16) ||
(output_strb[0] != 4'hF) || (output_strb[1] != 4'h1) ||
(output_data[0] != 32'h4433_2211) || (output_data[1] != 32'h0000_0055) ||
!output_last[1] || (memory[3] != 32'h0001_000C)) begin
$fatal(1, "DMA test failed err=%b code=%h outputs=%0d bytes=%0d bursts=%0d max=%0d wb=%h",
error_o, error_code_o, output_count, bytes_done_o, read_bursts, max_read_beats,
memory[3]);
end
@(posedge clk_i);
#1;
if (!descriptor_irq_seen) $fatal(1, "descriptor IRQ event was not visible");
// Card-to-host is the opposite stream direction: input stream bytes are
// AXI-written with low lane zero as the first byte.
rst_n_i = 1'b0;
repeat (3) @(posedge clk_i);
rst_n_i = 1'b1;
output_count = 0;
direction_i = 1'b0;
desc_base_i = 32'd0;
total_bytes_i = 32'd5;
error_mode = 1'b0;
memory[0] = 32'h0000_0200;
memory[1] = 32'd5;
memory[2] = 32'd0;
memory[3] = 32'h0000_000D;
memory[32'h0200>>2] = '0;
@(negedge clk_i);
start_i = 1'b1;
@(negedge clk_i);
start_i = 1'b0;
@(negedge clk_i);
data_in_i = 32'h8877_6655;
data_in_strb_i = 4'hF;
data_in_last_i = 1'b0;
data_in_valid_i = 1'b1;
while (u_sdio_dma.s_state_q != 4'd7) @(posedge clk_i);
@(posedge clk_i);
@(negedge clk_i);
data_in_i = 32'h0000_0099;
data_in_strb_i = 4'h1;
data_in_last_i = 1'b1;
while (u_sdio_dma.s_state_q != 4'd7) @(posedge clk_i);
@(posedge clk_i);
@(negedge clk_i);
data_in_valid_i = 1'b0;
wait (done_o);
if (error_o || (bytes_done_o != 32'd5) ||
(memory[32'h0200>>2] != 32'h8877_6655) ||
(memory[32'h0204>>2][7:0] != 8'h99) ||
(memory[3] != 32'h0001_000C) || !descriptor_irq_seen) begin
$fatal(1, "card-to-host DMA failed err=%b code=%h bytes=%0d data=%h tail=%h wb=%h", error_o,
error_code_o, bytes_done_o, memory[32'h0200>>2], memory[32'h0204>>2], memory[3]);
end
for (integer tail_bytes = 1; tail_bytes <= 3; tail_bytes++) begin
rst_n_i = 1'b0;
repeat (3) @(posedge clk_i);
rst_n_i = 1'b1;
data_in_valid_i = 1'b0;
direction_i = 1'b0;
desc_base_i = 32'd0;
total_bytes_i = tail_bytes;
memory[0] = 32'h0000_0300 + (tail_bytes * 32);
memory[1] = tail_bytes;
memory[2] = 32'd0;
memory[3] = 32'h0000_0005;
@(negedge clk_i);
start_i = 1'b1;
@(negedge clk_i);
start_i = 1'b0;
while (u_sdio_dma.s_state_q != 4'd7) @(posedge clk_i);
data_in_i = 32'h0403_0201;
data_in_strb_i = (4'b0001 << tail_bytes) - 1'b1;
data_in_last_i = 1'b1;
data_in_valid_i = 1'b1;
@(posedge clk_i);
@(negedge clk_i);
data_in_valid_i = 1'b0;
wait (done_o);
for (integer byte_index = 0; byte_index < tail_bytes; byte_index++) begin
if (memory[(32'h0000_0300 + (tail_bytes * 32) >> 2) + (byte_index >> 2)]
[(byte_index % 4)*8+:8] != byte_index + 1) begin
$fatal(1, "DMA tail readback failed bytes=%0d byte=%0d", tail_bytes, byte_index);
end
end
if (error_o || (bytes_done_o != tail_bytes) || (memory[3] != 32'h0001_0004)) begin
$fatal(1, "DMA tail transfer failed bytes=%0d err=%b wb=%h", tail_bytes, error_o,
memory[3]);
end
end
// A descriptor at ...FF0 is rejected before an AXI fetch.
rst_n_i = 1'b0;
repeat (3) @(posedge clk_i);
rst_n_i = 1'b1;
desc_base_i = 32'h0000_0FF0;
direction_i = 1'b1;
total_bytes_i = 32'd4;
@(negedge clk_i);
start_i = 1'b1;
@(negedge clk_i);
start_i = 1'b0;
wait (done_o);
if (!error_o || (read_bursts != 0)) begin
$fatal(1, "descriptor boundary rejection failed err=%b bursts=%0d", error_o, read_bursts);
end
// Restore a clean responder state for the directed AXI error check.
rst_n_i = 1'b0;
repeat (3) @(posedge clk_i);
rst_n_i = 1'b1;
memory[0] = 32'h0000_0200;
memory[1] = 32'd4;
memory[2] = 32'd0;
memory[3] = 32'h0000_0005;
memory[32'h0200>>2] = 32'hAABB_CCDD;
desc_base_i = 32'd0;
total_bytes_i = 32'd4;
direction_i = 1'b1;
error_mode = 1'b1;
@(negedge clk_i);
start_i = 1'b1;
@(negedge clk_i);
start_i = 1'b0;
wait (done_o);
if (!error_o || (memory[3] != 32'h0002_0004)) begin
$fatal(1, "AXI error was not written back: err=%b code=%h wb=%h", error_o, error_code_o,
memory[3]);
end
$display("SDIO DMA descriptor, tail, and 4 KiB split test passed");
$finish;
end
endmodule
+365
View File
@@ -0,0 +1,365 @@
// 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_dual_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] Status = 32'h028;
localparam logic [31:0] ErrorStatus = 32'h10C;
logic clk_i = 1'b0;
logic rst0_n_i = 1'b0;
logic rst1_n_i = 1'b0;
apb4_if apb0 (
.pclk (clk_i),
.presetn(rst0_n_i)
);
apb4_if apb1 (
.pclk (clk_i),
.presetn(rst1_n_i)
);
axi4_if #(
.ADDR_WIDTH(32),
.DATA_WIDTH(32),
.ID_WIDTH (1),
.USER_WIDTH(1)
) axi0 (
.aclk (clk_i),
.aresetn(rst0_n_i)
);
axi4_if #(
.ADDR_WIDTH(32),
.DATA_WIDTH(32),
.ID_WIDTH (1),
.USER_WIDTH(1)
) axi1 (
.aclk (clk_i),
.aresetn(rst1_n_i)
);
sdio_if sdio0 ();
sdio_if sdio1 ();
logic card0_cmd_do;
logic [ 3:0] card0_dat_do;
logic card0_irq;
logic card1_cmd_do;
logic [ 3:0] card1_dat_do;
logic card1_irq;
logic [31:0] value0;
logic [31:0] value1;
integer guard0;
integer guard1;
assign sdio0.cmd_di_i = sdio0.cmd_oe_o ? sdio0.cmd_do_o : card0_cmd_do;
assign sdio0.dat_di_i = (sdio0.dat_oe_o & sdio0.dat_do_o) | (~sdio0.dat_oe_o & card0_dat_do);
assign sdio1.cmd_di_i = sdio1.cmd_oe_o ? sdio1.cmd_do_o : card1_cmd_do;
assign sdio1.dat_di_i = (sdio1.dat_oe_o & sdio1.dat_do_o) | (~sdio1.dat_oe_o & card1_dat_do);
always #5 clk_i = ~clk_i;
apb4_sdio u_sdio0 (
.clk_i (clk_i),
.rst_n_i (rst0_n_i),
.apb4 (apb0),
.dma_axi4(axi0),
.sdio (sdio0)
);
apb4_sdio u_sdio1 (
.clk_i (clk_i),
.rst_n_i (rst1_n_i),
.apb4 (apb1),
.dma_axi4(axi1),
.sdio (sdio1)
);
sdio_sd_memory_model #(
.HighCapacity(1'b1),
.BusyCycles (8)
) u_card0 (
.rst_n_i (rst0_n_i),
.sck_i (sdio0.sck_o),
.cmd_oe_i(sdio0.cmd_oe_o),
.cmd_do_i(sdio0.cmd_do_o),
.cmd_do_o(card0_cmd_do),
.dat_oe_i(sdio0.dat_oe_o),
.dat_do_i(sdio0.dat_do_o),
.dat_do_o(card0_dat_do),
.irq_o (card0_irq)
);
sdio_sdio_model #(
.FunctionCount(2),
.FunctionBytes(4096),
.BusyCycles (8)
) u_card1 (
.rst_n_i (rst1_n_i),
.sck_i (sdio1.sck_o),
.cmd_oe_i(sdio1.cmd_oe_o),
.cmd_do_i(sdio1.cmd_do_o),
.cmd_do_o(card1_cmd_do),
.dat_oe_i(sdio1.dat_oe_o),
.dat_do_i(sdio1.dat_do_o),
.dat_do_o(card1_dat_do),
.irq_o (card1_irq)
);
sdio_axi_memory_responder u_mem0 (
.clk_i (clk_i),
.rst_n_i(rst0_n_i),
.axi4 (axi0)
);
sdio_axi_memory_responder u_mem1 (
.clk_i (clk_i),
.rst_n_i(rst1_n_i),
.axi4 (axi1)
);
task automatic apb0_idle;
begin
apb0.psel = 1'b0;
apb0.penable = 1'b0;
apb0.pwrite = 1'b0;
apb0.paddr = '0;
apb0.pwdata = '0;
apb0.pstrb = '0;
end
endtask
task automatic apb1_idle;
begin
apb1.psel = 1'b0;
apb1.penable = 1'b0;
apb1.pwrite = 1'b0;
apb1.paddr = '0;
apb1.pwdata = '0;
apb1.pstrb = '0;
end
endtask
task automatic apb0_write(input logic [31:0] address_i, input logic [31:0] data_i);
begin
@(negedge clk_i);
apb0.paddr = address_i;
apb0.pwdata = data_i;
apb0.pstrb = 4'hF;
apb0.pwrite = 1'b1;
apb0.psel = 1'b1;
apb0.penable = 1'b0;
@(negedge clk_i);
apb0.penable = 1'b1;
@(posedge clk_i);
#1;
if (!apb0.pready || apb0.pslverr) $fatal(1, "dual APB0 write failed %h", address_i);
apb0_idle();
end
endtask
task automatic apb1_write(input logic [31:0] address_i, input logic [31:0] data_i);
begin
@(negedge clk_i);
apb1.paddr = address_i;
apb1.pwdata = data_i;
apb1.pstrb = 4'hF;
apb1.pwrite = 1'b1;
apb1.psel = 1'b1;
apb1.penable = 1'b0;
@(negedge clk_i);
apb1.penable = 1'b1;
@(posedge clk_i);
#1;
if (!apb1.pready || apb1.pslverr) $fatal(1, "dual APB1 write failed %h", address_i);
apb1_idle();
end
endtask
task automatic apb0_read(input logic [31:0] address_i, output logic [31:0] data_o);
begin
@(negedge clk_i);
apb0.paddr = address_i;
apb0.pwrite = 1'b0;
apb0.psel = 1'b1;
apb0.penable = 1'b0;
apb0.pstrb = '0;
@(negedge clk_i);
apb0.penable = 1'b1;
@(posedge clk_i);
#1;
if (!apb0.pready || apb0.pslverr) $fatal(1, "dual APB0 read failed %h", address_i);
data_o = apb0.prdata;
apb0_idle();
end
endtask
task automatic apb1_read(input logic [31:0] address_i, output logic [31:0] data_o);
begin
@(negedge clk_i);
apb1.paddr = address_i;
apb1.pwrite = 1'b0;
apb1.psel = 1'b1;
apb1.penable = 1'b0;
apb1.pstrb = '0;
@(negedge clk_i);
apb1.penable = 1'b1;
@(posedge clk_i);
#1;
if (!apb1.pready || apb1.pslverr) $fatal(1, "dual APB1 read failed %h", address_i);
data_o = apb1.prdata;
apb1_idle();
end
endtask
task automatic wait0_command;
logic seen_busy;
begin
guard0 = 0;
seen_busy = 1'b0;
while (guard0 < 20000) begin
@(posedge clk_i);
guard0 = guard0 + 1;
if (u_sdio0.u_sdio_core.s_cmd_busy) seen_busy = 1'b1;
if (seen_busy && (u_sdio0.u_sdio_core.u_sdio_command.s_state_q == 3'd0)) begin
break;
end
end
if (guard0 >= 20000) $fatal(1, "dual SDIO0 command did not drain");
end
endtask
task automatic wait1_command;
logic seen_busy;
begin
guard1 = 0;
seen_busy = 1'b0;
while (guard1 < 20000) begin
@(posedge clk_i);
guard1 = guard1 + 1;
if (u_sdio1.u_sdio_core.s_cmd_busy) seen_busy = 1'b1;
if (seen_busy && (u_sdio1.u_sdio_core.u_sdio_command.s_state_q == 3'd0)) begin
break;
end
end
if (guard1 >= 20000) $fatal(1, "dual SDIO1 command did not drain");
end
endtask
task automatic wait0_data;
logic seen_busy;
begin
guard0 = 0;
seen_busy = 1'b0;
while (guard0 < 200000) begin
@(posedge clk_i);
guard0 = guard0 + 1;
if (u_sdio0.u_sdio_core.s_data_busy || u_sdio0.u_sdio_core.s_dma_busy) begin
seen_busy = 1'b1;
end
if (seen_busy && !u_sdio0.u_sdio_core.s_data_busy && !u_sdio0.u_sdio_core.s_dma_busy) begin
break;
end
end
if (guard0 >= 200000) $fatal(1, "dual SDIO0 data did not drain");
end
endtask
task automatic wait1_data;
logic seen_busy;
begin
guard1 = 0;
seen_busy = 1'b0;
while (guard1 < 200000) begin
@(posedge clk_i);
guard1 = guard1 + 1;
if (u_sdio1.u_sdio_core.s_data_busy || u_sdio1.u_sdio_core.s_dma_busy) begin
seen_busy = 1'b1;
end
if (seen_busy && !u_sdio1.u_sdio_core.s_data_busy && !u_sdio1.u_sdio_core.s_dma_busy) begin
break;
end
end
if (guard1 >= 200000) $fatal(1, "dual SDIO1 data did not drain");
end
endtask
task automatic run_memory_context;
begin
apb0_write(ClockCtrl, 32'h0000_0101);
apb0_write(HostCtrl, 32'h0000_0005);
apb0_write(CmdArg, 32'd2);
apb0_write(CmdCfg, {18'd0, 1'b1, 1'b1, 4'd1, 2'd0, 6'd17});
apb0_write(CmdStart, 32'd1);
wait0_command();
apb0_read(CmdStatus, value0);
if ((value0 & 32'h1E) != 0) $fatal(1, "dual SDIO0 CMD17 failed");
apb0_write(BusCtrl, 32'd0);
apb0_write(BlockSize, 32'd4);
apb0_write(BlockCount, 32'd1);
apb0_write(DataCfg, 32'd0);
u_card0.arm_read(1024, 4, 1'b0, 1'b0);
apb0_write(DataStart, 32'd1);
wait0_data();
apb0_read(PioData, value0);
if (value0 != 32'h0000_0000) $fatal(1, "dual SDIO0 data mismatch");
end
endtask
task automatic run_sdio_context;
begin
apb1_write(ClockCtrl, 32'h0000_0101);
apb1_write(HostCtrl, 32'h0000_0005);
apb1_write(CmdArg, 32'd0);
apb1_write(CmdCfg, {18'd0, 1'b1, 1'b1, 4'd5, 2'd0, 6'd5});
apb1_write(CmdStart, 32'd1);
wait1_command();
apb1_write(CmdArg, {1'b0, 3'd0, 1'b0, 1'b0, 17'h00000, 1'b0, 8'h00});
apb1_write(CmdCfg, {18'd0, 1'b1, 1'b1, 4'd6, 2'd0, 6'd52});
apb1_write(CmdStart, 32'd1);
wait1_command();
apb1_write(CmdArg, {1'b0, 3'd1, 1'b0, 1'b1, 17'h00020, 9'd4});
apb1_write(CmdCfg, {18'd0, 1'b0, 1'b0, 4'd0, 2'd0, 6'd53});
apb1_write(CmdStart, 32'd1);
wait1_command();
apb1_write(BusCtrl, 32'd1);
apb1_write(BlockSize, 32'd4);
apb1_write(BlockCount, 32'd1);
apb1_write(DataCfg, 32'h0000_0002);
u_card1.arm_read(4096 + 32, 4, 1'b1, 1'b0);
apb1_write(DataStart, 32'd1);
wait1_data();
apb1_read(PioData, value1);
if (value1 != 32'd0) $fatal(1, "dual SDIO1 data mismatch");
u_card1.set_irq();
end
endtask
initial begin
apb0_idle();
apb1_idle();
repeat (4) @(posedge clk_i);
rst0_n_i = 1'b1;
rst1_n_i = 1'b1;
fork
run_memory_context();
run_sdio_context();
join
apb0_read(Status, value0);
apb1_read(Status, value1);
if (value0 == value1) $fatal(1, "dual status contexts were aliased");
if (sdio1.dat_di_i[1] != 1'b0) begin
$fatal(1, "dual SDIO1 DAT1 context is not isolated");
end
apb0_read(ErrorStatus, value0);
if (value0 != 0) $fatal(1, "SDIO0 inherited SDIO1 error state: %h", value0);
$display("SDIO dual-instance isolation and concurrent traffic test passed");
$finish;
end
endmodule
File diff suppressed because it is too large. Load diff
+294
View File
@@ -0,0 +1,294 @@
`timescale 1ns / 1ps
module sdio_register_tb;
logic clk_i = 1'b0;
logic rst_n_i = 1'b0;
logic busy_i = 1'b0;
logic [ 31:0] status_i = 32'h0000_0011;
logic [ 31:0] clock_actual_i = 32'd400000;
logic [ 31:0] cmd_status_i = '0;
logic [135:0] response_i = '0;
logic [ 31:0] data_status_i = '0;
logic [ 31:0] fifo_status_i = '0;
logic [ 31:0] dma_status_i = '0;
logic [ 31:0] current_desc_i = '0;
logic [ 31:0] bytes_done_i = '0;
logic [ 31:0] dma_error_addr_i = '0;
logic [ 31:0] dma_error_i = '0;
logic [ 31:0] error_status_i = '0;
logic [ 5:0] last_cmd_i = '0;
logic [ 31:0] crc_error_count_i = '0;
logic [ 31:0] timeout_count_i = '0;
logic [ 31:0] axi_error_count_i = '0;
logic [ 31:0] stall_count_i = '0;
logic [ 7:0] irq_event_i = '0;
logic [ 31:0] pio_rdata_i = '0;
logic pio_valid_i = 1'b0;
logic pio_ready_i = 1'b0;
logic pio_read_consume_o;
logic host_enable_o;
logic host_irq_enable_o;
logic clock_enable_o;
logic [ 15:0] half_period_o;
logic [ 1:0] bus_width_o;
logic [ 31:0] timeout_cmd_o;
logic [ 31:0] timeout_data_o;
logic [ 31:0] timeout_busy_o;
logic [ 5:0] cmd_index_o;
logic [ 31:0] cmd_arg_o;
sdio_pkg::sdio_resp_type_e cmd_resp_type_o;
logic cmd_crc_check_o;
logic cmd_index_check_o;
logic cmd_start_o;
logic [ 15:0] block_size_o;
logic [ 15:0] block_count_o;
logic data_direction_o;
logic data_dma_enable_o;
logic data_block_mode_o;
logic data_fixed_addr_o;
logic data_start_o;
logic pio_wvalid_o;
logic [ 31:0] pio_wdata_o;
logic [ 3:0] pio_wstrb_o;
logic [ 31:0] desc_base_o;
logic [ 15:0] desc_count_o;
logic dma_start_o;
logic dma_abort_o;
logic irq_dat1_enable_o;
logic irq_o;
integer pio_consume_count;
apb4_if apb4 (
.pclk (clk_i),
.presetn(rst_n_i)
);
always #5 clk_i = ~clk_i;
always @(posedge clk_i) begin
if (pio_read_consume_o) begin
pio_consume_count = pio_consume_count + 1;
end
end
sdio_reg u_sdio_reg (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.busy_i (busy_i),
.status_i (status_i),
.clock_actual_i (clock_actual_i),
.cmd_status_i (cmd_status_i),
.response_i (response_i),
.data_status_i (data_status_i),
.fifo_status_i (fifo_status_i),
.dma_status_i (dma_status_i),
.current_desc_i (current_desc_i),
.bytes_done_i (bytes_done_i),
.dma_error_addr_i (dma_error_addr_i),
.dma_error_i (dma_error_i),
.error_status_i (error_status_i),
.last_cmd_i (last_cmd_i),
.crc_error_count_i (crc_error_count_i),
.timeout_count_i (timeout_count_i),
.axi_error_count_i (axi_error_count_i),
.stall_count_i (stall_count_i),
.irq_event_i (irq_event_i),
.pio_rdata_i (pio_rdata_i),
.pio_valid_i (pio_valid_i),
.pio_ready_i (pio_ready_i),
.pio_read_consume_o(pio_read_consume_o),
.host_enable_o (host_enable_o),
.host_irq_enable_o (host_irq_enable_o),
.clock_enable_o (clock_enable_o),
.half_period_o (half_period_o),
.bus_width_o (bus_width_o),
.timeout_cmd_o (timeout_cmd_o),
.timeout_data_o (timeout_data_o),
.timeout_busy_o (timeout_busy_o),
.cmd_index_o (cmd_index_o),
.cmd_arg_o (cmd_arg_o),
.cmd_resp_type_o (cmd_resp_type_o),
.cmd_crc_check_o (cmd_crc_check_o),
.cmd_index_check_o (cmd_index_check_o),
.cmd_start_o (cmd_start_o),
.block_size_o (block_size_o),
.block_count_o (block_count_o),
.data_direction_o (data_direction_o),
.data_dma_enable_o (data_dma_enable_o),
.data_block_mode_o (data_block_mode_o),
.data_fixed_addr_o (data_fixed_addr_o),
.data_start_o (data_start_o),
.pio_wvalid_o (pio_wvalid_o),
.pio_wdata_o (pio_wdata_o),
.pio_wstrb_o (pio_wstrb_o),
.desc_base_o (desc_base_o),
.desc_count_o (desc_count_o),
.dma_start_o (dma_start_o),
.dma_abort_o (dma_abort_o),
.irq_dat1_enable_o (irq_dat1_enable_o),
.irq_o (irq_o),
.apb4 (apb4)
);
task automatic apb_write(input logic [31:0] address, input logic [31:0] data,
input logic [3:0] strobe, input logic expected_error);
begin
@(negedge clk_i);
apb4.paddr = address;
apb4.pwdata = data;
apb4.pstrb = strobe;
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 != expected_error)) begin
$fatal(1, "APB write %h ready=%b err=%b expected=%b", address, apb4.pready, apb4.pslverr,
expected_error);
end
@(negedge clk_i);
apb4.psel = 1'b0;
apb4.penable = 1'b0;
end
endtask
task automatic apb_read_error(input logic [31:0] address);
begin
@(negedge clk_i);
apb4.paddr = address;
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, "APB read %h did not report empty PIO error", address);
end
@(negedge clk_i);
apb4.psel = 1'b0;
apb4.penable = 1'b0;
end
endtask
task automatic apb_read(input logic [31:0] address, output logic [31:0] data);
begin
@(negedge clk_i);
apb4.paddr = address;
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, "APB read %h ready=%b err=%b", address, apb4.pready, apb4.pslverr);
end
data = apb4.prdata;
@(negedge clk_i);
apb4.psel = 1'b0;
apb4.penable = 1'b0;
end
endtask
initial begin
logic [31:0] value;
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;
apb_read(32'h000, value);
if (value != 32'h5344_494F) $fatal(1, "IP ID mismatch");
apb_read(32'h008, value);
if (value != 32'h0000_01FF) $fatal(1, "capability mismatch");
apb_write(32'h010, 32'h0000_0501, 4'hF, 1'b0);
apb_read(32'h010, value);
if (value != 32'h0000_0501) $fatal(1, "clock control mismatch: %h", value);
apb_write(32'h040, 32'hCAFE_BABE, 4'hF, 1'b0);
apb_write(32'h044, 32'h0000_3108, 4'hF, 1'b0);
apb_write(32'h048, 32'h0000_0001, 4'h1, 1'b0);
#1;
if (!cmd_start_o || (cmd_index_o != 6'd8) || (cmd_arg_o != 32'hCAFE_BABE)) begin
$fatal(1, "command pulse/configuration mismatch");
end
apb_write(32'h3FC, 32'h0, 4'hF, 1'b1);
busy_i = 1'b1;
apb_write(32'h040, 32'h0, 4'hF, 1'b1);
busy_i = 1'b0;
apb_write(32'h104, 32'h0000_0001, 4'h1, 1'b0);
apb_write(32'h108, 32'h0000_0001, 4'h1, 1'b0);
if (!irq_o) $fatal(1, "IRQ test did not assert IRQ");
apb_read(32'h100, value);
if ((value & 32'h1) == 0) $fatal(1, "IRQ status did not latch");
apb_write(32'h100, 32'h0000_0001, 4'h1, 1'b0);
if (irq_o) $fatal(1, "IRQ W1C did not clear IRQ");
apb_write(32'h104, 32'h0000_0004, 4'h1, 1'b0);
irq_event_i = 8'h04;
@(posedge clk_i);
irq_event_i = 8'h00;
apb_read(32'h100, value);
if ((value & 32'h04) == 0 || !irq_o) $fatal(1, "descriptor IRQ event did not latch");
apb_write(32'h100, 32'h0000_0004, 4'h1, 1'b0);
apb_read(32'h100, value);
if ((value & 32'h04) != 0 || irq_o) $fatal(1, "descriptor IRQ W1C failed");
error_status_i = 32'h0000_0001;
@(posedge clk_i);
error_status_i = 32'h0000_0000;
apb_read(32'h10C, value);
if ((value & 32'h01) == 0) $fatal(1, "error event did not latch");
apb_write(32'h10C, 32'h0000_0001, 4'h1, 1'b0);
apb_read(32'h10C, value);
if ((value & 32'h01) != 0) $fatal(1, "ERROR_STATUS W1C did not clear");
apb_read(32'h10C, value);
if ((value & 32'h01) != 0) $fatal(1, "ERROR_STATUS clear was not persistent");
error_status_i = 32'h0000_0001;
@(posedge clk_i);
error_status_i = 32'h0000_0000;
apb_read(32'h10C, value);
if ((value & 32'h01) == 0) $fatal(1, "new error event did not re-latch");
pio_rdata_i = 32'h1122_3344;
pio_valid_i = 1'b1;
pio_ready_i = 1'b0;
apb_read(32'h124, value);
if (value != 32'h0000_0001) $fatal(1, "DEBUG PIO valid bit mismatch: %h", value);
pio_valid_i = 1'b0;
pio_ready_i = 1'b1;
apb_read(32'h124, value);
if (value != 32'h0000_0002) $fatal(1, "DEBUG PIO ready bit mismatch: %h", value);
pio_rdata_i = 32'h1122_3344;
pio_valid_i = 1'b1;
pio_consume_count = 0;
apb_read(32'h090, value);
if ((value != 32'h1122_3344) || (pio_consume_count != 1)) begin
$fatal(1, "PIO read did not return and consume one word: %h count=%0d", value,
pio_consume_count);
end
pio_valid_i = 1'b0;
apb_read_error(32'h090);
pio_rdata_i = 32'h5566_7788;
pio_valid_i = 1'b1;
apb_read(32'h090, value);
if ((value != 32'h5566_7788) || (pio_consume_count != 2)) begin
$fatal(1, "consecutive PIO read contract failed: %h count=%0d", value, pio_consume_count);
end
$display("SDIO APB register test passed");
$finish;
end
endmodule
+342
View File
@@ -0,0 +1,342 @@
// 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
+677
View File
@@ -0,0 +1,677 @@
// 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
+74
View File
@@ -0,0 +1,74 @@
`timescale 1ns / 1ps
`include "axi4_define.svh"
module sdio_wrapper_tb;
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 ();
always #5 clk_i = ~clk_i;
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_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)
);
initial begin
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;
@(negedge clk_i);
apb4.paddr = 32'h0000_0000;
apb4.psel = 1'b1;
@(negedge clk_i);
apb4.penable = 1'b1;
@(posedge clk_i);
#1;
if (!apb4.pready || apb4.pslverr || (apb4.prdata != 32'h5344_494F)) begin
$fatal(1, "wrapper APB identification failed");
end
$display("SDIO wrapper elaboration and APB smoke test passed");
$finish;
end
endmodule
+5 -2
View File
@@ -36,6 +36,8 @@ module soc_irq_topology_tb;
case (bit_index)
13: expect_irq(32'd1 << 15);
14: expect_irq(32'd1 << 20);
16: expect_irq(32'd1 << 10);
17: expect_irq(32'd1 << 21);
default: expect_irq(32'd1 << (bit_index + 7));
endcase
end
@@ -56,8 +58,9 @@ module soc_irq_topology_tb;
s_apb4_system_irq = '0;
expect_irq('0);
if (s_irq[10] !== 1'b0) begin
$fatal(1, "removed core IRQ bit is not low: IRQ10=%b", s_irq[10]);
if ((s_irq[10] !== 1'b0) || (s_irq[21] !== 1'b0)) begin
$fatal(1, "SDIO core IRQ bits are not low when sources are low: IRQ10=%b IRQ21=%b",
s_irq[10], s_irq[21]);
end
if ((s_irq[31:21] !== '0) || (s_irq[19:18] !== '0)) begin
$fatal(1, "unallocated core IRQ bits are not low: %h", s_irq);
+21 -11
View File
@@ -84,6 +84,8 @@ module sysctrl_tb;
sysctrl.perf_mgmt_wait_i = 64'd11;
sysctrl.perf_user_wait_i = 64'd12;
sysctrl.perf_dma_wait_i = 64'd13;
sysctrl.perf_sdio0_wait_i = 64'd131;
sysctrl.perf_sdio1_wait_i = 64'd141;
sysctrl.perf_apb4_periph_wait_i = 64'd14;
sysctrl.perf_apb4_system_wait_i = 64'd15;
sysctrl.perf_sdram_wait_i = 64'd16;
@@ -102,32 +104,36 @@ module sysctrl_tb;
rst_n_i = 1'b1;
@(negedge clk_i);
fault_valid_i = 1'b1;
fault_addr_i = 32'h1000_F000;
fault_wstrb_i = 4'hF;
fault_reserved_i = 1'b1;
sysctrl.fault_master_i = 3'd3;
fault_valid_i = 1'b1;
fault_addr_i = 32'h1001_2000;
fault_wstrb_i = 4'hF;
fault_reserved_i = 1'b1;
@(negedge clk_i);
fault_valid_i = 1'b0;
read_register(32'h1000_B010, read_data);
if (read_data !== 32'h0000_000B) $fatal(1, "fault status was not recorded");
read_register(32'h1000_B014, read_data);
if (read_data !== 32'h1000_F000) $fatal(1, "fault address was not recorded");
if (read_data !== 32'h1001_2000) $fatal(1, "fault address was not recorded");
read_register(32'h1000_B018, read_data);
if (read_data !== 32'h0000_0001) $fatal(1, "fault count was not incremented");
read_register(32'h1000_B028, read_data);
if (read_data !== 32'h0000_0003) $fatal(1, "three-bit fault master was not recorded");
read_register(32'h1000_B02C, read_data);
if (read_data !== `RIB_RESP_RESERVED) $fatal(1, "fault detail was not recorded");
@(negedge clk_i);
sysctrl.fault_code_i = `RIB_RESP_DECERR;
fault_valid_i = 1'b1;
fault_addr_i = 32'hA000_0000;
fault_wstrb_i = 4'h0;
fault_reserved_i = 1'b0;
sysctrl.fault_master_i = 3'd4;
sysctrl.fault_code_i = `RIB_RESP_DECERR;
fault_valid_i = 1'b1;
fault_addr_i = 32'hA000_0000;
fault_wstrb_i = 4'h0;
fault_reserved_i = 1'b0;
@(negedge clk_i);
fault_valid_i = 1'b0;
read_register(32'h1000_B014, read_data);
if (read_data !== 32'h1000_F000) $fatal(1, "later fault overwrote first fault address");
if (read_data !== 32'h1001_2000) $fatal(1, "later fault overwrote first fault address");
read_register(32'h1000_B018, read_data);
if (read_data !== 32'h0000_0002) $fatal(1, "later fault did not increment count");
read_register(32'h1000_B02C, read_data);
@@ -140,6 +146,10 @@ module sysctrl_tb;
write_register(32'h1000_B040, 32'h0000_0005);
read_register(32'h1000_B044, read_data);
if (read_data !== 32'd11) $fatal(1, "performance snapshot was not recorded");
read_register(32'h1000_B08C, read_data);
if (read_data !== 32'd131) $fatal(1, "SDIO0 performance snapshot was not recorded");
read_register(32'h1000_B094, read_data);
if (read_data !== 32'd141) $fatal(1, "SDIO1 performance snapshot was not recorded");
read_register(32'h1000_B020, read_data);
if (read_data !== 32'h0000_003F) $fatal(1, "user cores were not held in reset");