feat: add comprehensive tests for AXI4 components and lifecycle controllers

- Implemented tests for AXI4 async bridge functionality, including transfer and warm flush scenarios.
- Added tests for AXI4 data crossbar to validate concurrent outstanding requests and ACL handling.
- Introduced clock frequency monitor tests to ensure detection of clock stop and restart events.
- Developed tests for extension DMA master to verify copy operations, tail strobe behavior, and timeout handling.
- Created tests for HP lifecycle controller to validate drain, flush, and forced reset functionalities.
- Updated existing test scripts to include new RTL components and ensure proper simulation configurations.
This commit is contained in:
maksyuki committed 2026-08-30 13:15:10 +08:00
1 parent 2b6819449c
commit 98eb8ebad6
67 files changed
+4165 -612

No files matched your search

+129
View File
@@ -0,0 +1,129 @@
`timescale 1ns / 1ps
module axi4_async_bridge_tb;
logic src_clk_i = 1'b0;
logic dst_clk_i = 1'b0;
logic src_rst_n_i = 1'b0;
logic dst_rst_n_i = 1'b0;
logic clear_i = 1'b0;
logic clear_busy_o;
logic [7:0] epoch_o;
axi4_if #(
.ADDR_WIDTH(32),
.DATA_WIDTH(32),
.ID_WIDTH (1),
.USER_WIDTH(1)
) source (
.aclk (src_clk_i),
.aresetn(src_rst_n_i)
);
axi4_if #(
.ADDR_WIDTH(32),
.DATA_WIDTH(32),
.ID_WIDTH (1),
.USER_WIDTH(1)
) sink (
.aclk (dst_clk_i),
.aresetn(dst_rst_n_i)
);
always #5 src_clk_i = ~src_clk_i;
always #7 dst_clk_i = ~dst_clk_i;
axi4_async_bridge u_dut (
.src_clk_i (src_clk_i),
.src_rst_n_i (src_rst_n_i),
.dst_clk_i (dst_clk_i),
.dst_rst_n_i (dst_rst_n_i),
.clear_i (clear_i),
.clear_busy_o(clear_busy_o),
.epoch_o (epoch_o),
.src_axi4 (source),
.dst_axi4 (sink)
);
initial begin
source.awid = 0;
source.awaddr = 0;
source.awlen = 0;
source.awsize = 2;
source.awburst = 1;
source.awlock = 0;
source.awcache = 0;
source.awprot = 0;
source.awqos = 0;
source.awregion = 0;
source.awuser = 0;
source.awvalid = 0;
source.wdata = 0;
source.wstrb = 0;
source.wlast = 1;
source.wuser = 0;
source.wvalid = 0;
source.bready = 1;
source.arid = 0;
source.araddr = 0;
source.arlen = 0;
source.arsize = 2;
source.arburst = 1;
source.arlock = 0;
source.arcache = 0;
source.arprot = 0;
source.arqos = 0;
source.arregion = 0;
source.aruser = 0;
source.arvalid = 0;
source.rready = 1;
sink.awready = 1;
sink.wready = 1;
sink.bid = 0;
sink.bresp = 0;
sink.buser = 0;
sink.bvalid = 0;
sink.arready = 1;
sink.rid = 0;
sink.rdata = 32'hCAFE_BABE;
sink.rresp = 0;
sink.rlast = 1;
sink.ruser = 0;
sink.rvalid = 0;
repeat (3) @(posedge src_clk_i);
src_rst_n_i = 1;
dst_rst_n_i = 1;
repeat (8) @(posedge src_clk_i);
@(negedge src_clk_i);
source.araddr = 32'h5000_0040;
source.arvalid = 1;
do @(posedge src_clk_i); while (!source.arready);
@(negedge src_clk_i);
source.arvalid = 0;
wait (sink.arvalid);
if (sink.araddr != 32'h5000_0040) $fatal(1, "async bridge address mismatch");
@(posedge dst_clk_i);
@(negedge dst_clk_i);
sink.rvalid = 1;
do @(posedge dst_clk_i); while (!sink.rready);
@(negedge dst_clk_i);
sink.rvalid = 0;
wait (source.rvalid);
if (source.rdata != 32'hCAFE_BABE) $fatal(1, "async bridge response mismatch");
@(posedge src_clk_i);
@(negedge src_clk_i);
clear_i = 1;
wait (clear_busy_o);
clear_i = 0;
wait (!clear_busy_o);
if (epoch_o != 8'd1) $fatal(1, "async bridge epoch did not advance");
$display("AXI4 async bridge transfer and warm flush test passed");
$finish;
end
initial begin
#20000;
$fatal(1, "AXI4 async bridge test timed out");
end
endmodule
+230
View File
@@ -0,0 +1,230 @@
`timescale 1ns / 1ps
module axi4_data_crossbar_tb;
localparam int NumMasters = 8;
localparam int NumTargets = 6;
logic clk_i = 1'b0;
logic rst_n_i = 1'b0;
logic idle_o;
logic [ 7:0] outstanding_read_o;
logic [ 7:0] outstanding_write_o;
logic fault_valid_o;
logic [ 2:0] fault_master_o;
logic [ 2:0] fault_target_o;
logic [31:0] fault_addr_o;
logic fault_write_o;
logic [ 3:0] fault_reason_o;
axi4_if #(
.ADDR_WIDTH(32),
.DATA_WIDTH(64),
.ID_WIDTH (6),
.USER_WIDTH(1)
) masters[NumMasters] (
.aclk (clk_i),
.aresetn(rst_n_i)
);
axi4_if #(
.ADDR_WIDTH(32),
.DATA_WIDTH(64),
.ID_WIDTH (6),
.USER_WIDTH(1)
) targets[NumTargets] (
.aclk (clk_i),
.aresetn(rst_n_i)
);
always #5 clk_i = ~clk_i;
initial begin
repeat (500) @(posedge clk_i);
$fatal(1, "AXI4 data crossbar test timed out");
end
axi4_data_crossbar u_dut (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.block_new_i (1'b0),
.recovery_i (1'b0),
.mem_pad_mode_i (2'd1),
.ext_h_read_base_i (32'h3000_0000),
.ext_h_read_limit_i (32'h4FFF_FFFF),
.ext_h_write_base_i (32'h3000_0000),
.ext_h_write_limit_i(32'h4FFF_FFFF),
.masters (masters),
.targets (targets),
.idle_o (idle_o),
.outstanding_read_o (outstanding_read_o),
.outstanding_write_o(outstanding_write_o),
.fault_valid_o (fault_valid_o),
.fault_master_o (fault_master_o),
.fault_target_o (fault_target_o),
.fault_addr_o (fault_addr_o),
.fault_write_o (fault_write_o),
.fault_reason_o (fault_reason_o)
);
task automatic issue_master1_read(input logic [5:0] id, input logic [31:0] addr);
begin
@(negedge clk_i);
masters[1].arid = id;
masters[1].araddr = addr;
masters[1].arvalid = 1'b1;
do @(posedge clk_i); while (!masters[1].arready);
@(negedge clk_i);
masters[1].arvalid = 1'b0;
end
endtask
task automatic return_target0_read(input logic [5:0] id, input logic [63:0] data);
begin
@(negedge clk_i);
targets[0].rid = id;
targets[0].rdata = data;
targets[0].rresp = 2'b00;
targets[0].rlast = 1'b1;
targets[0].rvalid = 1'b1;
do @(posedge clk_i); while (!targets[0].rready);
@(negedge clk_i);
targets[0].rvalid = 1'b0;
end
endtask
task automatic return_target1_read(input logic [5:0] id, input logic [63:0] data);
begin
@(negedge clk_i);
targets[1].rid = id;
targets[1].rdata = data;
targets[1].rresp = 2'b00;
targets[1].rlast = 1'b1;
targets[1].rvalid = 1'b1;
do @(posedge clk_i); while (!targets[1].rready);
@(negedge clk_i);
targets[1].rvalid = 1'b0;
end
endtask
`define INIT_MASTER(index) \
masters[index].awid = '0; \
masters[index].awaddr = '0; \
masters[index].awlen = '0; \
masters[index].awsize = 3'd3; \
masters[index].awburst = 2'b01; \
masters[index].awlock = 1'b0; \
masters[index].awcache = '0; \
masters[index].awprot = '0; \
masters[index].awqos = '0; \
masters[index].awregion = '0; \
masters[index].awuser = '0; \
masters[index].awvalid = 1'b0; \
masters[index].wdata = '0; \
masters[index].wstrb = '0; \
masters[index].wlast = 1'b1; \
masters[index].wuser = '0; \
masters[index].wvalid = 1'b0; \
masters[index].bready = 1'b1; \
masters[index].arid = '0; \
masters[index].araddr = '0; \
masters[index].arlen = '0; \
masters[index].arsize = 3'd3; \
masters[index].arburst = 2'b01; \
masters[index].arlock = 1'b0; \
masters[index].arcache = '0; \
masters[index].arprot = '0; \
masters[index].arqos = '0; \
masters[index].arregion = '0; \
masters[index].aruser = '0; \
masters[index].arvalid = 1'b0; \
masters[index].rready = 1'b1;
`define INIT_TARGET(index) \
targets[index].awready = 1'b1; \
targets[index].wready = 1'b1; \
targets[index].bid = '0; \
targets[index].bresp = '0; \
targets[index].buser = '0; \
targets[index].bvalid = 1'b0; \
targets[index].arready = 1'b1; \
targets[index].rid = '0; \
targets[index].rdata = '0; \
targets[index].rresp = '0; \
targets[index].rlast = 1'b1; \
targets[index].ruser = '0; \
targets[index].rvalid = 1'b0;
initial begin
`INIT_MASTER(0)
`INIT_MASTER(1)
`INIT_MASTER(2)
`INIT_MASTER(3)
`INIT_MASTER(4)
`INIT_MASTER(5)
`INIT_MASTER(6)
`INIT_MASTER(7)
`INIT_TARGET(0)
`INIT_TARGET(1)
`INIT_TARGET(2)
`INIT_TARGET(3)
`INIT_TARGET(4)
`INIT_TARGET(5)
repeat (3) @(posedge clk_i);
rst_n_i = 1'b1;
issue_master1_read(6'b001_000, 32'h3000_0000);
issue_master1_read(6'b001_001, 32'h3800_0000);
if (outstanding_read_o != 8'd2 || idle_o) begin
$fatal(1, "different-target reads were not tracked concurrently");
end
@(negedge clk_i);
masters[1].arid = 6'b001_000;
masters[1].araddr = 32'h4000_0000;
masters[1].arvalid = 1'b1;
#1;
if (masters[1].arready) $fatal(1, "same source ID was accepted before completion");
masters[1].arvalid = 1'b0;
fork
return_target1_read(6'b001_001, 64'h2222_2222_2222_2222);
begin
wait (masters[1].rvalid);
if ((masters[1].rid != 6'b001_001) || (masters[1].rdata != 64'h2222_2222_2222_2222)) begin
$fatal(1, "out-of-order different-ID response was misrouted");
end
end
join
return_target0_read(6'b001_000, 64'h1111_1111_1111_1111);
@(negedge clk_i);
if (!idle_o || (outstanding_read_o != 8'd0)) begin
$fatal(1, "read completion did not drain the crossbar");
end
@(negedge clk_i);
masters[6].arid = 6'b110_000;
masters[6].araddr = 32'h3000_0000;
masters[6].arvalid = 1'b1;
do @(posedge clk_i); while (!masters[6].arready);
#1;
if (!fault_valid_o || (fault_master_o != 3'd6) ||
(fault_reason_o != 4'd3) || fault_write_o) begin
$fatal(1, "reserved-master ACL fault attribution mismatch");
end
@(negedge clk_i);
masters[6].arvalid = 1'b0;
targets[5].rid = 6'b110_000;
targets[5].rresp = 2'b10;
targets[5].rlast = 1'b1;
targets[5].rvalid = 1'b1;
do @(posedge clk_i); while (!targets[5].rready);
@(negedge clk_i);
targets[5].rvalid = 1'b0;
$display("AXI4 data crossbar concurrency and ACL test passed");
$finish;
end
`undef INIT_MASTER
`undef INIT_TARGET
endmodule
+58
View File
@@ -0,0 +1,58 @@
`timescale 1ns / 1ps
module clock_frequency_monitor_tb;
logic ref_clk_i = 1'b0;
logic monitored_clk_i = 1'b0;
logic rst_n_i = 1'b0;
logic clear_fault_i = 1'b0;
logic run_monitored = 1'b1;
logic alive_o;
logic fault_o;
logic [7:0] edge_delta_o;
always #5 ref_clk_i = ~ref_clk_i;
always begin
#2;
if (run_monitored) monitored_clk_i = ~monitored_clk_i;
else monitored_clk_i = 1'b0;
end
clock_frequency_monitor #(
.CounterWidth(8),
.WindowCycles(8)
) u_dut (
.ref_clk_i (ref_clk_i),
.ref_rst_n_i (rst_n_i),
.monitored_clk_i (monitored_clk_i),
.monitored_rst_n_i(rst_n_i),
.clear_fault_i (clear_fault_i),
.alive_o (alive_o),
.fault_o (fault_o),
.edge_delta_o (edge_delta_o)
);
initial begin
repeat (2) @(posedge ref_clk_i);
rst_n_i = 1'b1;
repeat (20) @(posedge ref_clk_i);
if (!alive_o || fault_o || (edge_delta_o == 8'd0)) begin
$fatal(1, "running clock was not measured as alive");
end
run_monitored = 1'b0;
wait (fault_o);
if (alive_o) $fatal(1, "stopped clock remained alive");
@(negedge ref_clk_i);
clear_fault_i = 1'b1;
@(negedge ref_clk_i);
clear_fault_i = 1'b0;
@(negedge ref_clk_i);
if (fault_o) $fatal(1, "clock fault did not clear");
run_monitored = 1'b1;
repeat (12) @(posedge ref_clk_i);
if (!alive_o) $fatal(1, "restarted clock did not recover activity status");
$display("Clock frequency activity and sticky fault test passed");
$finish;
end
endmodule
+125
View File
@@ -0,0 +1,125 @@
`timescale 1ns / 1ps
module extension_dma_master_tb;
logic clk_i = 1'b0;
logic rst_n_i = 1'b0;
logic start_i = 1'b0;
logic abort_i = 1'b0;
logic quiesce_i = 1'b0;
logic [31:0] src_addr_i = 32'h3000_0000;
logic [31:0] dst_addr_i = 32'h3000_0100;
logic [31:0] len_i = 32'd12;
logic [31:0] timeout_i = 32'd16;
logic busy_o;
logic done_o;
logic err_o;
logic [31:0] fault_addr_o;
axi4_if #(
.ADDR_WIDTH(32),
.DATA_WIDTH(64),
.ID_WIDTH (3),
.USER_WIDTH(1)
) axi4 (
.aclk (clk_i),
.aresetn(rst_n_i)
);
always #5 clk_i = ~clk_i;
extension_dma_master u_dut (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.start_i (start_i),
.abort_i (abort_i),
.quiesce_i (quiesce_i),
.src_addr_i (src_addr_i),
.dst_addr_i (dst_addr_i),
.len_i (len_i),
.timeout_i (timeout_i),
.busy_o (busy_o),
.done_o (done_o),
.err_o (err_o),
.fault_addr_o(fault_addr_o),
.axi4 (axi4)
);
task automatic start_dma;
begin
@(negedge clk_i);
start_i = 1'b1;
@(negedge clk_i);
start_i = 1'b0;
end
endtask
task automatic serve_beat(input logic [31:0] expected_src, input logic [31:0] expected_dst,
input logic [63:0] data, input logic [7:0] expected_strobe);
begin
wait (axi4.arvalid);
if (axi4.araddr != expected_src) $fatal(1, "DMA read address mismatch");
@(posedge clk_i);
@(negedge clk_i);
axi4.rid = 3'd0;
axi4.rdata = data;
axi4.rresp = 2'b00;
axi4.rlast = 1'b1;
axi4.rvalid = 1'b1;
do @(posedge clk_i); while (!axi4.rready);
@(negedge clk_i);
axi4.rvalid = 1'b0;
wait (axi4.awvalid);
if (axi4.awaddr != expected_dst) $fatal(1, "DMA write address mismatch");
@(posedge clk_i);
wait (axi4.wvalid);
if ((axi4.wdata != data) || (axi4.wstrb != expected_strobe) || !axi4.wlast) begin
$fatal(1, "DMA write data or tail strobe mismatch");
end
@(posedge clk_i);
@(negedge clk_i);
axi4.bid = 3'd0;
axi4.bresp = 2'b00;
axi4.bvalid = 1'b1;
do @(posedge clk_i); while (!axi4.bready);
@(negedge clk_i);
axi4.bvalid = 1'b0;
end
endtask
initial begin
axi4.awready = 1'b1;
axi4.wready = 1'b1;
axi4.bid = '0;
axi4.bresp = '0;
axi4.buser = '0;
axi4.bvalid = 1'b0;
axi4.arready = 1'b1;
axi4.rid = '0;
axi4.rdata = '0;
axi4.rresp = '0;
axi4.rlast = 1'b1;
axi4.ruser = '0;
axi4.rvalid = 1'b0;
repeat (2) @(posedge clk_i);
rst_n_i = 1'b1;
start_dma();
serve_beat(32'h3000_0000, 32'h3000_0100, 64'h1122_3344_5566_7788, 8'hFF);
serve_beat(32'h3000_0008, 32'h3000_0108, 64'hAABB_CCDD_EEFF_0011, 8'h0F);
wait (done_o);
if (err_o || busy_o) $fatal(1, "successful DMA copy reported an error");
@(negedge clk_i);
timeout_i = 32'd4;
len_i = 32'd8;
axi4.arready = 1'b0;
start_dma();
wait (done_o);
if (!err_o || (fault_addr_o != src_addr_i)) begin
$fatal(1, "DMA no-progress timeout was not attributed to the source address");
end
$display("Extension DMA copy, tail strobe, and timeout test passed");
$finish;
end
endmodule
+74
View File
@@ -0,0 +1,74 @@
`timescale 1ns / 1ps
module hp_lifecycle_controller_tb;
logic clk_i = 1'b0;
logic rst_n_i = 1'b0;
logic release_req_i = 1'b0;
logic hp_idle_i = 1'b0;
logic flush_busy_i = 1'b0;
logic hp_release_o;
logic block_new_o;
logic flush_o;
logic draining_o;
logic forced_fault_o;
always #5 clk_i = ~clk_i;
hp_lifecycle_controller u_dut (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.release_req_i (release_req_i),
.hp_idle_i (hp_idle_i),
.flush_busy_i (flush_busy_i),
.timeout_i (16'd4),
.hp_release_o (hp_release_o),
.block_new_o (block_new_o),
.flush_o (flush_o),
.draining_o (draining_o),
.forced_fault_o(forced_fault_o)
);
task automatic complete_flush;
begin
wait (flush_o);
@(negedge clk_i);
flush_busy_i = 1'b1;
wait (!flush_o);
repeat (2) @(negedge clk_i);
flush_busy_i = 1'b0;
wait (!hp_release_o);
end
endtask
initial begin
repeat (2) @(posedge clk_i);
rst_n_i = 1'b1;
@(negedge clk_i);
release_req_i = 1'b1;
wait (hp_release_o);
if (block_new_o || draining_o) $fatal(1, "released HP was unexpectedly blocked");
@(negedge clk_i);
release_req_i = 1'b0;
repeat (2) @(negedge clk_i);
if (!block_new_o || !draining_o || !hp_release_o) begin
$fatal(1, "normal shutdown did not enter drain while HP remained released");
end
hp_idle_i = 1'b1;
complete_flush();
if (forced_fault_o || block_new_o) $fatal(1, "normal shutdown reported a forced fault");
@(negedge clk_i);
hp_idle_i = 1'b0;
release_req_i = 1'b1;
wait (hp_release_o);
@(negedge clk_i);
release_req_i = 1'b0;
wait (flush_o);
if (!forced_fault_o) $fatal(1, "quiesce timeout did not record a forced fault");
complete_flush();
$display("HP lifecycle drain, flush, and forced reset test passed");
$finish;
end
endmodule
+3
View File
@@ -164,6 +164,9 @@ module pll_ctrl_tb;
sysctrl.perf_flash_wait_i = '0;
sysctrl.rtc_wake_i = 1'b0;
sysctrl.hp_present_i = 1'b0;
sysctrl.hp_actual_released_i = 1'b0;
sysctrl.hp_draining_i = 1'b0;
sysctrl.hp_forced_fault_i = 1'b0;
rst_n_i = 1'b1;
#10;
rst_n_i = 1'b0;
+3
View File
@@ -118,6 +118,9 @@ module sysctrl_tb;
sysctrl.perf_flash_wait_i = 64'd18;
sysctrl.rtc_wake_i = 1'b0;
sysctrl.hp_present_i = 1'b0;
sysctrl.hp_actual_released_i = 1'b0;
sysctrl.hp_draining_i = 1'b0;
sysctrl.hp_forced_fault_i = 1'b0;
pll_ctrl.req_ready_i = 1'b1;
pll_ctrl.rsp_active_sel_i = '0;
pll_ctrl.rsp_active_valid_i = 1'b0;