// Copyright (c) 2020 ETH Zurich and University of Bologna. // Copyright and related rights are licensed under the Solderpad Hardware // License, Version 0.51 (the "License"); you may not use this file except in // compliance with the License. You may obtain a copy of the License at // http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law // or agreed to in writing, software, hardware and materials distributed under // this License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. // // Authors: // - Wolfgang Roenninger <wroennin@iis.ee.ethz.ch> // Directed Random Verification Testbench for `axi_lite_regs`. `include "axi/assign.svh" /// Testbench for validating the module `axi_lite_regs`. module tb_axi_lite_regs #( /// Define the parameter `RegNumBytes` of the DUT. parameter int unsigned TbRegNumBytes = 32'd200, /// Define the parameter `AxiReadOnly` of the DUT. parameter logic [TbRegNumBytes-1:0] TbAxiReadOnly = {{TbRegNumBytes-18{1'b0}}, 18'b101110111111000000}, /// Define the parameter `PrivProtOnly` of the DUT. parameter bit TbPrivProtOnly = 1'b0, /// Define the parameter `SecuProtOnly` of the DUT. parameter bit TbSecuProtOnly = 1'b0, /// Number of random writes generated by the testbench AXI4-Lite random master. parameter int unsigned TbNoWrites = 32'd1000, /// Number of random reads generated by the testbench AXI4-Lite random master. parameter int unsigned TbNoReads = 32'd1500 ); // AXI configuration localparam int unsigned AxiAddrWidth = 32'd32; // Axi Address Width localparam int unsigned AxiDataWidth = 32'd32; // Axi Data Width localparam int unsigned AxiStrbWidth = AxiDataWidth / 32'd8; // timing parameters localparam time CyclTime = 10ns; localparam time ApplTime = 2ns; localparam time TestTime = 8ns; typedef logic [7:0] byte_t; typedef logic [AxiAddrWidth-1:0] axi_addr_t; typedef logic [AxiDataWidth-1:0] axi_data_t; typedef logic [AxiStrbWidth-1:0] axi_strb_t; localparam axi_addr_t StartAddr = axi_addr_t'(0); localparam axi_addr_t EndAddr = axi_addr_t'(StartAddr + TbRegNumBytes + TbRegNumBytes/5); localparam byte_t [TbRegNumBytes-1:0] RegRstVal = '0; typedef axi_test::axi_lite_rand_master #( // AXI interface parameters .AW ( AxiAddrWidth ), .DW ( AxiDataWidth ), // Stimuli application and test time .TA ( ApplTime ), .TT ( TestTime ), .MIN_ADDR ( StartAddr ), .MAX_ADDR ( EndAddr ), .MAX_READ_TXNS ( 10 ), .MAX_WRITE_TXNS ( 10 ) ) rand_lite_master_t; // ------------- // DUT signals // ------------- logic clk; // DUT signals logic rst_n; logic end_of_sim; // Register signals byte_t [TbRegNumBytes-1:0] reg_d, reg_q; logic [TbRegNumBytes-1:0] wr_active, rd_active, reg_load; // ------------------------------- // AXI Interfaces // ------------------------------- AXI_LITE #( .AXI_ADDR_WIDTH ( AxiAddrWidth ), .AXI_DATA_WIDTH ( AxiDataWidth ) ) master (); AXI_LITE_DV #( .AXI_ADDR_WIDTH ( AxiAddrWidth ), .AXI_DATA_WIDTH ( AxiDataWidth ) ) master_dv (clk); `AXI_LITE_ASSIGN(master, master_dv) // ------------------------------- // AXI Rand Masters and Slaves // ------------------------------- // Masters control simulation run time initial begin : proc_generate_axi_traffic automatic rand_lite_master_t lite_axi_master = new ( master_dv, "Lite Master"); automatic axi_data_t data = '0; automatic axi_pkg::resp_t resp = '0; end_of_sim <= 1'b0; lite_axi_master.reset(); @(posedge rst_n); repeat (5) @(posedge clk); // Test known register. // Write to it. lite_axi_master.write(axi_addr_t'(32'h0000_0000), axi_pkg::prot_t'('0), axi_data_t'(64'hDEADBEEFDEADBEEF), axi_strb_t'(8'hFF), resp); if (TbPrivProtOnly || TbSecuProtOnly) begin assert (resp == axi_pkg::RESP_SLVERR) else $fatal(1, "PrivProtOnly || SecuProtOnly: Unprivileged access was falsely granted."); end else begin assert (resp == axi_pkg::RESP_OKAY) else $fatal(1, "Access should be granted"); end // Read from it. lite_axi_master.read(axi_addr_t'(32'h0000_0000), axi_pkg::prot_t'('0), data, resp); if (TbPrivProtOnly || TbSecuProtOnly) begin // Expect error response assert (resp == axi_pkg::RESP_SLVERR) else $fatal(1, "PrivProtOnly || SecuProtOnly: Unprivileged access was falsely granted."); assert (data == axi_data_t'(32'hBA5E1E55)) else $fatal(1, "Data is unexpected, should be axi_data_t'(32'hBA5E1E55)."); end else begin assert (resp == axi_pkg::RESP_OKAY) else $fatal(1, "Access should be granted."); // Checking of the expecetd read data is handled in `proc_check_read_data`. end // Let random stimuli application checking is separate. lite_axi_master.run(TbNoReads, TbNoWrites); end_of_sim <= 1'b1; end initial begin : proc_generate_direct_load for (int unsigned i = 0; i < TbRegNumBytes; i++) begin automatic int unsigned j = i; fork toggle_load(j); join_none end end task automatic toggle_load(int unsigned byte_i); byte_t rand_val; int unsigned rand_wait; @(posedge rst_n); reg_load[byte_i] = 1'b0; reg_d[byte_i] = 8'h00; @(posedge clk); forever begin rand_wait = $urandom_range(0, 20); repeat (rand_wait) @(posedge clk); rand_val = byte_t'($urandom()); reg_d[byte_i] <= #ApplTime rand_val; reg_load[byte_i] <= #ApplTime 1'b1; @(posedge clk); reg_d[byte_i] <= #ApplTime '0; reg_load[byte_i] <= #ApplTime 1'b0; end endtask : toggle_load initial begin : proc_check_read_data automatic axi_data_t exp_rdata[$]; automatic axi_pkg::resp_t exp_resp[$]; @(posedge rst_n); forever begin @(posedge clk); #(TestTime); // Push the expected data back. if (master.ar_valid && master.ar_ready) begin automatic int unsigned ar_idx = ((master.ar_addr - StartAddr) >> $clog2(AxiStrbWidth) << $clog2(AxiStrbWidth)); automatic axi_data_t r_data = axi_data_t'(0); automatic axi_pkg::resp_t r_resp = axi_pkg::RESP_SLVERR; for (int unsigned i = 0; i < AxiStrbWidth; i++) begin if ((ar_idx+i) < TbRegNumBytes) begin r_data[8*i+:8] = reg_q[ar_idx+i]; r_resp = axi_pkg::RESP_OKAY; end else begin r_data[8*i+:8] = 8'hxx; end end // check the right protection access if (TbPrivProtOnly && !master.ar_prot[0]) begin r_resp = axi_pkg::RESP_SLVERR; end if (TbSecuProtOnly && !master.ar_prot[1]) begin r_resp = axi_pkg::RESP_SLVERR; end exp_resp.push_back(r_resp); exp_rdata.push_back(r_data); end // compare data if there is a value expected if (master.r_valid && master.r_ready) begin automatic axi_data_t r_data = exp_rdata.pop_front(); if (master.r_resp == axi_pkg::RESP_OKAY) begin for (int unsigned i = 0; i < AxiStrbWidth; i++) begin automatic byte_t exp_byte = r_data[8*i+:8]; if (exp_byte !== 8'hxx) begin assert (master.r_data[8*i+:8] == exp_byte) else $error("Unexpected read data: exp: %0h observed: %0h", r_data, master.r_data); assert (master.r_resp == axi_pkg::RESP_OKAY); end end end else if (master.r_resp == axi_pkg::RESP_SLVERR) begin assert (master.r_data == axi_data_t'(32'hBA5E1E55)); end else begin $error("Slave responded with false response: %0h", master.r_resp); end end end end axi_pkg::resp_t b_resp_queue[$]; // Only works as module expects both AW & W valid before it does something initial begin : proc_check_write_data @(posedge rst_n); forever begin #TestTime; // AW and W is launched, setup the test tasks if (master.aw_valid && master.aw_ready && master.w_valid && master.w_ready) begin automatic int unsigned aw_idx = ((master.aw_addr - StartAddr) >> $clog2(AxiStrbWidth) << $clog2(AxiStrbWidth)); automatic axi_pkg::resp_t exp_b_resp = (aw_idx < TbRegNumBytes) ? axi_pkg::RESP_OKAY : axi_pkg::RESP_SLVERR; automatic bit all_ro = 1'b1; // check for errors from wrong access protection if (TbPrivProtOnly && !master.aw_prot[0]) begin exp_b_resp = axi_pkg::RESP_SLVERR; end if (TbSecuProtOnly && !master.aw_prot[1]) begin exp_b_resp = axi_pkg::RESP_SLVERR; end // Check if all accesses bytes are read only for (int unsigned i = 0; i < AxiStrbWidth; i++) begin if (!TbAxiReadOnly[aw_idx+i]) begin all_ro = 1'b0; end end if (all_ro) begin exp_b_resp = axi_pkg::RESP_SLVERR; end // do the actual write checking if (exp_b_resp == axi_pkg::RESP_OKAY) begin // go through every byte for (int unsigned i = 0; i < AxiStrbWidth; i++) begin if ((aw_idx+i) < TbRegNumBytes) begin if (master.w_strb[i]) begin automatic int unsigned j = aw_idx + i; automatic byte_t exp_byte = master.w_data[8*i+:8]; fork check_q(j, exp_byte); join_none end assert (master.w_strb[i] == wr_active[aw_idx+i]); end end end b_resp_queue.push_back(exp_b_resp); end @(posedge clk); end end initial begin : proc_check_b automatic axi_pkg::resp_t exp_b_resp; @(posedge rst_n); forever begin #TestTime; if (master.b_valid && master.b_ready) begin if (b_resp_queue.size()) begin exp_b_resp = b_resp_queue.pop_front(); assert (exp_b_resp == master.b_resp) else $error("Unexpected B response: EXP: %b ACT: %b", exp_b_resp, master.b_resp); end else begin $error("B response even no AW was sent."); end end @(posedge clk); end end // check that the expected register byte has been written task automatic check_q(int unsigned byte_i, byte_t exp_byte); automatic byte_t save_byte = (reg_load[byte_i]) ? reg_d[byte_i] : reg_q[byte_i]; @(posedge clk); #TestTime; if (!TbAxiReadOnly[byte_i]) begin assert(exp_byte == reg_q[byte_i]) else $error("AXI write was not registered at byte: %0d", byte_i); end else begin assert (reg_q[byte_i] == save_byte) else $error("AXI write onto read only byte: %0d SAVE: %h EXP: %h ACT %h", byte_i, save_byte, exp_byte, reg_q[byte_i]); end endtask : check_q // Some assertions for additional checking. default disable iff (~rst_n); for (genvar i = 0; i < TbRegNumBytes; i++) begin : gen_check_ro_bytes if (TbAxiReadOnly[i]) begin : gen_check_ro ro_assert_no_load: assert property (@(posedge clk) ((wr_active[i] && !reg_load[i]) |=> $stable(reg_q[i]))) else $fatal(1, "ReadOnly byte %0d changed from AXI write, while not direct loaded.", i); ro_assert_load: assert property (@(posedge clk) ((wr_active[i] && reg_load[i]) |=> (reg_q[i] === $past(reg_d[i])))) else $fatal(1, "ReadOnly byte %0d changed from AXI write, while direct loaded.", i); end load_assert_no_axi: assert property (@(posedge clk) (reg_load[i] && !TbAxiReadOnly[i] |-> !wr_active[i])) else $fatal(1, "Byte %0d, AXI write onto non read-only and direct load are both active!", i); load_assert_data: assert property (@(posedge clk) (reg_load[i] |=> reg_q[i] === $past(reg_d[i]))) else $fatal(1, "Byte %0d, direct load was executed falsely.", i); stable_assert: assert property (@(posedge clk) (!reg_load[i] && !wr_active[i]) |=> $stable(reg_q[i])) else $fatal(1, "Byte %0d is unstable, when no AXI write or direct load.", i); end initial begin : proc_stop_sim wait (end_of_sim); repeat (1000) @(posedge clk); $display("Simulation stopped as Master transferred its data."); $stop(); end //----------------------------------- // Clock generator //----------------------------------- clk_rst_gen #( .ClkPeriod ( CyclTime ), .RstClkCycles ( 5 ) ) i_clk_gen ( .clk_o (clk), .rst_no(rst_n) ); //----------------------------------- // DUT //----------------------------------- axi_lite_regs_intf #( .REG_NUM_BYTES ( TbRegNumBytes ), .AXI_ADDR_WIDTH ( AxiAddrWidth ), .AXI_DATA_WIDTH ( AxiDataWidth ), .PRIV_PROT_ONLY ( TbPrivProtOnly ), .SECU_PROT_ONLY ( TbSecuProtOnly ), .AXI_READ_ONLY ( TbAxiReadOnly ), .REG_RST_VAL ( RegRstVal ) ) i_axi_lite_regs ( .clk_i ( clk ), .rst_ni ( rst_n ), .slv ( master ), .wr_active_o ( wr_active ), .rd_active_o ( rd_active ), .reg_d_i ( reg_d ), .reg_load_i ( reg_load ), .reg_q_o ( reg_q ) ); endmodule