Testing an Integer Adder with AXI-Stream Interfaces on SystemVerilog

Having recently changed jobs, moving from VHDL to SystemVerilog and finding myself in a team with a separate group of verifiers, I realized that I was seriously behind in verification. In VHDL, I had previously written only simple developer tests that showed that the block performed the required function and nothing more. Each test was written from scratch and there was no code reuse. Determined to fix this problem, I immersed myself in reading SystemVerilog for Verification A Guide to Learning the Testbench Language Features for authorship Chris Spear, Greg Tumbush. After reading the book, I realized that I needed to write some kind of test project to consolidate the information I had received. I remembered that I had seen a series of articles on verification from @pcbteach on Habr, written in Verilog, and decided to write a test for an adder with AXI-Stream interfaces in SystemVerilog.

Content

1 General structure of the test environment

2 Test environment for checking the adder

3 Implementation of interfaces for testing the adder

4 Implementation of classes for testing the adder

5 Connecting the test environment and the device under test

6 Conclusion

1 General structure of the test environment

This diagram is given in the book as a support for writing a test environment.

Thus, when going beyond the block Environment the transaction class instance is translated into a physical representation of signals and fed to the device under test, and from the output of the device under test the physical representation of signals is translated into a transaction class instance. Each block performs only one action, so such blocks will be more universal.

2 Test environment for checking the adder

The adder under consideration has two inputs with AXI-Stream interfaces – for loading operand 1 and operand 2. And one output with AXI-Stream interface – the result of the adder's operation. Accordingly, two copies of Generator blocks, two copies of Agent blocks, two copies of Driver blocks, one copy of Monitor block, one copy of Cheker block, one copy of Scoreboard block are required.

SystemVerilog allows you to implement interactions between blocks within an Environment in several ways:

Also, SystemVerilog provides the ability to implement interaction between Environment and DUT using:

The interaction between blocks within the Environment will be carried out using mailboxes, the interaction between the Environment and the DUT will be carried out using virtual interfaces, which seemed easier to use to me.

Figure 1 - Structural diagram of the test environment taking into account the features of the adder.

Figure 1 – Structural diagram of the test environment taking into account the features of the adder.

3 Implementation of interfaces for testing the adder

interface AXIS_Bus #(parameter DW=8) (input clk);

    logic [DW-1:0]  tdata;
    logic           tvalid;
    logic           tready;

    modport slave (
        input tdata,
        input tvalid,
        output tready
    );

    modport master (
        output tdata,
        output tvalid,
        input tready
    );

endinterface

To interact with the device under test, the AXIS-Bus interface is used, which describes the tdata, tvalid, tready signals used by the adder block.

4 Implementation of classes for testing the adder

4.1 Implementation of classes for testing the adder

`ifndef OPERAND_ITEM_CLS_PKG__SV
`define OPERAND_ITEM_CLS_PKG__SV

package operand_item_cls_pkg;

    class operand_item_cls #(parameter int DW = 4);

        rand bit [DW-1:0] m_data;

        function void print(string tag = "");
            $display("[%s] Item value = 0x%0h", tag, m_data);
        endfunction : print

    endclass : operand_item_cls

endpackage : operand_item_cls_pkg

`endif //OPERAND_ITEM_CLS_PKG__SV

The operand_item_cls class is used to specify the operand that is fed to the adder input. The class has a DW parameter that defines the width (in bits) of the m_data field. The class also has a method for printing the current value of the tdata field. The m_data field has a rand modifier, which means that each time the function is called, randomize for a class object, the value of the m_data field will take a random value.

4.2 result_item_cls

`ifndef RESULT_ITEM_CLS_PKG__SV
`define RESULT_ITEM_CLS_PKG__SV

package result_item_cls_pkg;

    class result_item_cls #(parameter int DW = 5);

        bit [DW-1:0] m_data;

        function void print(string tag = "");
            $display("[%s] Item value = 0x%0h", tag, m_data);
        endfunction : print

    endclass : result_item_cls

endpackage : result_item_cls_pkg

`endif //RESULT_ITEM_CLS_PKG__SV

The result_item_cls class is used to get the result from the adder output. The class has a DW parameter that defines the width (in bits) of the m_data field. The class also has a method for printing the current value of the m_data field.

4.3 generator_cls

`ifndef GENERATOR_CLS_PKG__SV
`define GENERATOR_CLS_PKG__SV

package generator_cls_pkg;

    `include "rand_check.svh"

    import operand_item_cls_pkg::*;

    class generator_cls #(parameter int DW = 4);

        operand_item_cls    #(.DW(DW))                      item;
        mailbox             #(operand_item_cls #(.DW(DW)))  mbx;

        function new (input mailbox #(operand_item_cls #(.DW(DW))) mbx);
            this.mbx = mbx;
        endfunction : new

        task run (input int count);
            repeat (count) begin
                item = new();
                `SV_RAND_CHECK(item.randomize());
                mbx.put(item);
            end
        endtask : run

    endclass : generator_cls

endpackage : generator_cls_pkg

`endif //GENERATOR_CLS_PKG__SV

The generator_cls class is used to form a transaction and pass it to the agent_cls block. The transaction is an instance of the operand_item_cls class. When creating an instance of the class, a pointer to the mailbox is passed to the constructor (the new function), which is used to pass the transaction to the agent_cls block.

Transactions are created in the run method, which takes as input the number of transactions to be sent. In the loop, a transaction object is created (the mailbox does not store the object, only a reference to it), filled with a random value, and placed in the mailbox.

`SV_RAND_CHECK – a macro that checks that the filling of class instances with random values ​​was successful.

4.4 agent_cls

`ifndef AGENT_CLS_PKG__SV
`define AGENT_CLS_PKG__SV

package agent_cls_pkg;

    import operand_item_cls_pkg::*;

    class agent_cls #(parameter int DW = 4);

        mailbox             #(operand_item_cls #(.DW(DW)))  gen2agt, agt2drv, agt2scb;
        operand_item_cls    #(.DW(DW))                      item;

        function new(input mailbox #(operand_item_cls #(.DW(DW))) gen2agt, agt2drv, agt2scb);
            this.gen2agt = gen2agt;
            this.agt2drv = agt2drv;
            this.agt2scb = agt2scb;
        endfunction : new

        task run();
            forever begin
                gen2agt.get(item);
                agt2scb.put(item);
                agt2drv.put(item);
            end
        endtask : run

    endclass : agent_cls

endpackage : agent_cls_pkg

`endif //AGENT_CLS_PKG__SV

The agent_cls class is used to receive the transaction from generator_cls and pass it to driver_cls and scoreboard_cls (in fact, the transaction is duplicated). The transaction is an instance of the operand_item_cls class. When creating an instance of the class, the constructor (the new function) is passed pointers to mailboxes used to receive the transaction and pass it to the next blocks.

The transaction is sent in the run method. In an infinite loop, the transaction is received from the mailbox from generator_cls and the transaction is placed in the driver_cls and scoreboard_cls mailboxes.

4.5 driver_cls

`ifndef DRIVER_CLS_PKG__SV
`define DRIVER_CLS_PKG__SV

package driver_cls_pkg;

    import operand_item_cls_pkg::*;

    class driver_cls #(parameter int DATA_WIDTH = 4, parameter int AXIS_WIDTH = 8);

        virtual AXIS_Bus    #(.DW(AXIS_WIDTH))                      vif;
        mailbox             #(operand_item_cls #(.DW(DATA_WIDTH)))  mbx;
        operand_item_cls    #(.DW(DATA_WIDTH))                      item;

        function new (input mailbox #(operand_item_cls #(.DW(DATA_WIDTH))) mbx, input virtual AXIS_Bus #(.DW(AXIS_WIDTH)) vif);
            this.mbx = mbx;
            this.vif = vif;
        endfunction : new

        task run (input int count, input int min_delay, input int max_delay, input bit en_display = 0);

            if (en_display)
                $display("[driver_cls] Starting...");

            vif.tvalid <= 1'b0;

            repeat (count) begin
                if (en_display)
                    $display("[driver_cls][ Waiting for item...");

                mbx.get(item);
                if (en_display)
                    item.print("driver_cls");

                repeat ($urandom_range(min_delay, max_delay)) begin
                    @(posedge vif.clk);
                end
                vif.tdata <= item.m_data;
                vif.tvalid <= 1'b1;
                @(posedge vif.clk);
                while (!vif.tready) begin
                    @(posedge vif.clk);
                end
                vif.tvalid <= 1'b0;

                if (en_display)
                    $display("[driver_cls] Item sended...");
            end

        endtask : run

    endclass : driver_cls

endpackage : driver_cls_pkg

`endif //DRIVER_CLS_PKG__SV

The driver_cls class is used to send a transaction to the device under test. The transaction is an instance of the operand_item_cls class. In addition to the DATA_WIDTH parameter for specifying the width of the source operand field, the class has an AXIS_WIDTH parameter for specifying the width of the AXI-Stream interface used to interact with the device under test. When creating an instance of the class in the constructore (the new function) is passed pointers to the mailbox containing the transaction and the virtual interface connected to the device under test.

To generate switching of individual signals in the interface from the class instance, the run method is used, which takes the number of transactions as input. The method cyclically generates signals. A transaction is retrieved from the mailbox, a random time is expected before the transaction starts, the contents of the m_data field of the operand_item_cls class are set to the tdata bus of the interface, and tvalid is set to 1. The appearance of the tready signal equal to 1 is expected, after which tvalid goes to state 0, the transaction is considered transferred

4.6 monitor_cls

`ifndef MONITOR_CLS_PKG__SV
`define MONITOR_CLS_PKG__SV

package monitor_cls_pkg ;

    import result_item_cls_pkg::*;

    class monitor_cls #(parameter int DATA_WIDTH = 5, parameter int AXIS_WIDTH = 8);

        virtual AXIS_Bus    #(.DW(AXIS_WIDTH))                      vif;
        mailbox             #(result_item_cls #(.DW(DATA_WIDTH)))   mbx;

        function new (input mailbox #(result_item_cls #(.DW(DATA_WIDTH))) mbx, input virtual AXIS_Bus #(.DW(AXIS_WIDTH)) vif);
            this.vif = vif;
            this.mbx = mbx;
        endfunction : new

        task run (input int count, input int min_delay, input int max_delay, input bit en_display = 0);
            if (en_display)
                $display("[monitor_cls] Starting...");

            vif.tready <= 1'b0;

            repeat (count) begin
                result_item_cls #(.DW(DATA_WIDTH)) item = new;

                repeat ($urandom_range(min_delay, max_delay)) begin
                    @(posedge vif.clk);
                end

                vif.tready <= 1'b1;
                @(posedge vif.clk);
                while (!vif.tvalid) begin
                    @(posedge vif.clk);
                end
                item.m_data = vif.tdata;
                vif.tready <= 1'b0;

                if (en_display)
                    item.print("monitor_cls");

                mbx.put(item);
            end
        endtask : run

    endclass : monitor_cls

endpackage : monitor_cls_pkg

`endif //MONITOR_CLS_PKG__SV

The monitor_cls class is used to analyze the switching of the tdata, tvalid, tready signals and to form an object of the result_item_cls class, which stores the result of the adder's operation. In addition to the DATA_WIDTH parameter for setting the width of the result field, the class has an AXIS_WIDTH parameter for setting the width of the AXI-Stream interface used when interacting with the device under test. When creating an instance of the class, pointers to the mailbox where the transaction must be placed and to the virtual interface connected to the device under test are passed to the constructor (the new function).

To form a class instance from individual interface signals, the run method is used, which takes the number of transactions as input. The method cyclically generates a class instance. A random time is expected before the transaction begins. The tready signal of the interface is set to 1, and the tvalid signal equal to 1 is expected. After this, the value of the tdata signal of the interface is entered into the m_data field of the class instance, and the tready signal takes the value 0, the transaction is considered processed.

4.7 scoreboard_cls

`ifndef SCOREBOARD_CLS_PKG__SV
`define SCOREBOARD_CLS_PKG__SV

package scoreboard_cls_pkg;

    import operand_item_cls_pkg::*;
    import result_item_cls_pkg::*;

    class scoreboard_cls #(parameter int DW = 4);

        mailbox #(operand_item_cls  #(.DW(DW)))     mbx_op1, mbx_op2;
        mailbox #(result_item_cls   #(.DW(DW+1)))   mbx_res;

        function new ( input mailbox #(operand_item_cls #(.DW(DW))) mbx_op1, mbx_op2, input mailbox #(result_item_cls #(.DW(DW+1))) mbx_res);
            this.mbx_op1 = mbx_op1;
            this.mbx_op2 = mbx_op2;
            this.mbx_res = mbx_res;
        endfunction : new

        task run (int count, input bit en_display = 0);
            operand_item_cls    #(.DW(DW))      operand1, operand2;
            result_item_cls     #(.DW(DW+1))    result;

            if (en_display)
                $display("[scoreboard_cls] Starting... ");

            repeat (count) begin
                fork
                    mbx_op1.get(operand1);
                    mbx_op2.get(operand2);
                join
                result = new ();
                result.m_data = operand1.m_data + operand2.m_data;
                mbx_res.put(result);
            end

        endtask : run

    endclass : scoreboard_cls

endpackage : scoreboard_cls_pkg

`endif //SCOREBOARD_CLS_PKG__SV

The scoreboard_cls class is used to calculate the expected result of the adder based on the source operands. The source operands are represented by instances of the operand_item_cls class, the expected result is represented by an instance of the result_item_cls class. When creating an instance of the class, pointers to mailboxes are passed to the constructor (the new function), in which the operands are stored and in which the expected result of the adder must be placed.

The analysis of operands and calculation of the expected result is performed in the run method, which takes the number of transactions as input. In the loop, operands are extracted from mailboxes, an object is formed to save the result, and the expected result value is entered into the mailbox.

4.8 checker_cls

`ifndef CHECKER_CLS_PKG__SV
`define CHECKER_CLS_PKG__SV

package checker_cls_pkg;

    import result_item_cls_pkg::*;

    class checker_cls #(parameter int DW = 5);

        mailbox #(result_item_cls #(.DW(DW))) mbx_res_sc, mbx_res_mon;

        int count_good;
        int count_bad;

        function new ( input mailbox #(result_item_cls #(.DW(DW))) mbx_res_sc, mbx_res_mon);
            this.mbx_res_sc = mbx_res_sc;
            this.mbx_res_mon = mbx_res_mon;
            count_good = 0;
            count_bad = 0;
        endfunction : new

        task run (int count, input bit en_display = 0);
            result_item_cls     #(.DW(DW))    result_sc, result_mon;

            if (en_display)
                $display("[checker_cls] Starting... ");

            repeat (count) begin
                fork
                    mbx_res_sc.get(result_sc);
                    mbx_res_mon.get(result_mon);
                join

                if (result_sc.m_data == result_mon.m_data) begin
                    count_good++;
                end
                else begin
                    count_bad++;
                end
            end

            if (count_bad != 0) begin
                $display("[checker_cls] Fail!");
                $finish;
            end
            else if (count_good == count) begin
                $display("[checker_cls] Pass!");
                $finish;
            end

        endtask : run

    endclass : checker_cls

endpackage : checker_cls_pkg

`endif //CHECKER_CLS_PKG__SV

The checker_cls class is used to check whether the expected result of the adder's operation matches the actual result of the adder's operation. The results are represented by instances of the result_item_cls class. When creating an instance of the class, pointers to mailboxes are passed to the constructor (the new function), where the results of the adder's operation (expected and actual) are stored.

The analysis of the results is performed in the run method, which takes the number of transactions as input. In the loop, the results are extracted from the mailboxes and compared. If the expected result is equal to the actual result, the counter of correct transactions is increased, otherwise, the counter of incorrect transactions is increased. If an incorrect transaction was received, the test stops.

4.9 config_cls

`ifndef CONFIG_CLS_PKG_SV
`define CONFIG_CLS_PKG_SV

package config_cls_pkg;

    class config_cls;
        rand bit [31:0] run_for_n_trans;
        rand bit [ 7:0] min_delay;
        rand bit [ 7:0] max_delay;

        constraint reasonable {
            run_for_n_trans inside {[1:1000]};
            min_delay < max_delay;
        }
    endclass : config_cls

endpackage : config_cls_pkg

`endif //CONFIG_CLS_PKG_SV

The config_cls class is used to obtain random parameters for the test environment. The following fields are defined in the class:

The fields have rand identifiers, meaning that each time the randomize() function is called on an instance of the class, the fields will be filled with a random value. The run_for_n_trans field has a constraint that after calling the randomize() function, the value of the run_for_n_trans field must be in the range from 1 to 1000. The min_delay and max_delay fields have a constraint that min_delay must be less than max_delay.

4.10 environment_cls

`ifndef ENVIRONMENT_CLS_PKG__SV
`define ENVIRONMENT_CLS_PKG__SV

package environment_cls_pkg;

    import operand_item_cls_pkg::*;
    import result_item_cls_pkg::*;
    import generator_cls_pkg::*;
    import agent_cls_pkg::*;
    import driver_cls_pkg::*;
    import monitor_cls_pkg::*;
    import scoreboard_cls_pkg::*;
    import checker_cls_pkg::*;
    import config_cls_pkg::*;

    `include "rand_check.svh"

    parameter OPERAND_QTY = 2;

    class environment_cls #(parameter int DATA_WIDTH = 4, parameter int AXIS_IN_WIDTH = 8, parameter int AXIS_OUT_WIDTH = 8);

        generator_cls   #(.DW(DATA_WIDTH))                                              gen[OPERAND_QTY-1:0];
        agent_cls       #(.DW(DATA_WIDTH))                                              agt[OPERAND_QTY-1:0];
        driver_cls      #(.DATA_WIDTH(DATA_WIDTH),      .AXIS_WIDTH(AXIS_IN_WIDTH))     drv[OPERAND_QTY-1:0];
        monitor_cls     #(.DATA_WIDTH(DATA_WIDTH+1),    .AXIS_WIDTH(AXIS_OUT_WIDTH))    mon;
        scoreboard_cls  #(.DW(DATA_WIDTH))                                              scb;
        checker_cls     #(.DW(DATA_WIDTH+1))                                            chk;
        config_cls                                                                      cfg;

        mailbox #(operand_item_cls  #(.DW(DATA_WIDTH)))     mbx_gen2agt [OPERAND_QTY-1:0];
        mailbox #(operand_item_cls  #(.DW(DATA_WIDTH)))     mbx_agt2scb [OPERAND_QTY-1:0];
        mailbox #(operand_item_cls  #(.DW(DATA_WIDTH)))     mbx_agt2drv [OPERAND_QTY-1:0];
        mailbox #(result_item_cls   #(.DW(DATA_WIDTH+1)))   mbx_scb2chk;
        mailbox #(result_item_cls   #(.DW(DATA_WIDTH+1)))   mbx_mon2chk;

        virtual AXIS_Bus #(.DW(AXIS_IN_WIDTH))  operand1;
        virtual AXIS_Bus #(.DW(AXIS_IN_WIDTH))  operand2;
        virtual AXIS_Bus #(.DW(AXIS_OUT_WIDTH)) result;

        function new( input virtual AXIS_Bus #(.DW(AXIS_IN_WIDTH)) operand1, operand2, input virtual AXIS_Bus #(.DW(AXIS_OUT_WIDTH)) result);
            cfg             = new();
            this.operand1   = operand1;
            this.operand2   = operand2;
            this.result     = result;
        endfunction

        function void gen_cfg();
             `SV_RAND_CHECK(cfg.randomize());
        endfunction : gen_cfg

        function void build();
            for (int i = 0; i < OPERAND_QTY; i++) begin
                mbx_gen2agt[i] = new(1);
                mbx_agt2scb[i] = new(1);
                mbx_agt2drv[i] = new(1);
            end
            mbx_scb2chk = new(1);
            mbx_mon2chk = new(1);

            for (int i = 0; i < OPERAND_QTY; i++) begin
                gen[i] = new(.mbx(mbx_gen2agt[i]));
                agt[i] = new(.gen2agt(mbx_gen2agt[i]), .agt2drv(mbx_agt2drv[i]), .agt2scb(mbx_agt2scb[i]));
            end

            drv[0] = new(.mbx(mbx_agt2drv[0]), .vif(operand1));
            drv[1] = new(.mbx(mbx_agt2drv[1]), .vif(operand2));

            mon = new(.mbx(mbx_mon2chk), .vif(result));

            scb = new(.mbx_op1(mbx_agt2scb[0]), .mbx_op2(mbx_agt2scb[1]), .mbx_res(mbx_scb2chk));
            chk = new(.mbx_res_sc(mbx_scb2chk), .mbx_res_mon(mbx_mon2chk));
        endfunction : build

        task run();
            fork
                gen[0].run(cfg.run_for_n_trans);
                gen[1].run(cfg.run_for_n_trans);
                agt[0].run();
                agt[1].run();
                drv[0].run(cfg.run_for_n_trans, cfg.min_delay, cfg.max_delay);
                drv[1].run(cfg.run_for_n_trans, cfg.min_delay, cfg.max_delay);
                mon.run(cfg.run_for_n_trans, cfg.min_delay, cfg.max_delay);
                scb.run(cfg.run_for_n_trans);
                chk.run(cfg.run_for_n_trans);
            join
        endtask : run

    endclass : environment_cls

endpackage : environment_cls_pkg

`endif //ENVIRONMENT_CLS_PKG__SV

The environment_cls class is used to connect all the blocks involved in testing. The class has the following parameters: DATA_WIDTH (the width of the operands and the result), AXIS_IN_WIDTH (the width of the AXI-Stream interface for the operands), AXIS_OUT_WIDTH (the width of the AXI-Stream interface for the result). When creating an instance of the class, pointers to virtual interfaces connected to the device under test are passed to the constructor (the new function), and an object of the config_cls class is also created.

The gen_cfg function is used to set random parameters for the test environment.

The build function creates mailboxes for exchanging transactions between blocks, as well as instances of the generator_cls, agent_cls, driver_cls, monitor_cls, scoreboard_cls blocks.

The run function starts the creation of transactions, their transmission, reception and verification.

5 Connecting the test environment and the device under test

The adder testing classes are implemented. Now we need to connect the adder testing classes and the device under test to each other.

5.1 test_cls

`ifndef TEST_CLS_PKG__SV
`define TEST_CLS_PKG__SV

package test_cls_pkg;

    import environment_cls_pkg::*;

    class test_cls #(parameter int DATA_WIDTH = 4, parameter int AXIS_IN_WIDTH = 8, parameter int AXIS_OUT_WIDTH = 8);

        environment_cls #(.DATA_WIDTH(DATA_WIDTH), .AXIS_IN_WIDTH(AXIS_IN_WIDTH), .AXIS_OUT_WIDTH(AXIS_OUT_WIDTH)) env;

        function new( input virtual AXIS_Bus #(.DW(AXIS_IN_WIDTH)) operand1, operand2, input virtual AXIS_Bus #(.DW(AXIS_OUT_WIDTH)) result);
            env = new(operand1, operand2, result);
        endfunction

        task run ();
            env.gen_cfg();
            env.build();
            env.run();
        endtask

    endclass : test_cls

endpackage : test_cls_pkg

`endif //TEST_CLS_PKG__SV

The test_cls class is used to connect the test environment. The class has the parameters DATA_WIDTH (the width of the operands and the result), AXIS_IN_WIDTH (the width of the AXI-Stream interface for the operands), AXIS_OUT_WIDTH (the width of the AXI-Stream interface for the result). When creating an instance of the class, pointers to virtual interfaces connected to the device under test are passed to the constructor (the new function).

The run method sequentially sets random parameters for testing, creates all the necessary blocks, assembles them together, and starts testing.

5.2 tb

`timescale 1ns/1ps

module tb;

import test_cls_pkg::*;

localparam CLK_PERIOD = 10ns;
localparam CNT_RESET_CYCLE = 10;

logic clk;
logic rstn;

localparam integer ADDER_WIDTH      = 8;
localparam integer IN_AXIS_WIDTH    = $ceil($itor(ADDER_WIDTH) / 8) * 8;
localparam integer OUT_AXIS_WIDTH   = $ceil($itor(ADDER_WIDTH+1) / 8) * 8;

AXIS_Bus #(IN_AXIS_WIDTH)   operand1_if (clk);
AXIS_Bus #(IN_AXIS_WIDTH)   operand2_if (clk);
AXIS_Bus #(OUT_AXIS_WIDTH)  result_if   (clk);

initial begin : clk_gen
    clk <= 1'b0;
    forever begin
        #(CLK_PERIOD/2);
        clk <= ~clk;
    end
end : clk_gen

initial begin : rst_gen
    rstn <= 1'b0;
    repeat (CNT_RESET_CYCLE)
        @(posedge clk);
    rstn <= 1'b1;
end : rst_gen

test_cls #(.DATA_WIDTH(ADDER_WIDTH), .AXIS_IN_WIDTH(IN_AXIS_WIDTH), .AXIS_OUT_WIDTH(OUT_AXIS_WIDTH)) test;

initial begin : stim_gen
    if ($test$plusargs("SEED")) begin
        int seed;
        $value$plusargs("SEED=%d", seed);
        $display("Simalation run with random seed = %0d", seed);
        $urandom(seed);
    end
    else
        $display("Simulation run with default random seed");

    test = new(operand1_if, operand2_if, result_if);
    @(posedge rstn);
    test.run();
end : stim_gen

adder_axis_pipe_mp #(
    .ADDER_WIDTH    (ADDER_WIDTH)
)
u_adder_axis_pipe_mp(
    .ACLK_I             (clk),
    .ARST_N             (rstn),
    .AXIS_OPERAND1_IF   (operand1_if),
    .AXIS_OPERAND2_IF   (operand2_if),
    .AXIS_RESULT_IF     (result_if)
);

initial begin : watchdog
    @(posedge rstn);
    @(posedge clk);
    $display("Transaction quantity = %4d", test.env.cfg.run_for_n_trans);
    repeat(test.env.cfg.run_for_n_trans * test.env.cfg.max_delay) @(posedge clk);
    $display("ERROR! Watchdog error!");
    $finish;
end : watchdog

endmodule

The tb module connects the test environment and the device under test. The clk_gen block generates a clock signal for the device under test and the test environment. The rst_gen block generates a reset signal for the device under test and the test environment, the stim_gen block connects the ports of the adder_axis_pipe_mp block to the test environment and starts the test. The SEED variable, which is passed via command line parameters (PLUSARG), is used as the initial value for random.

6 Conclusion

# Simulation run with default random seed
# Transaction quantity =  568
# [checker_cls] Pass!
...
# Simalation run with random seed = 68
# Transaction quantity =  563
# [checker_cls] Pass!

A test environment for the adder with AXI-Stream interfaces was developed in the SystemVerilog language using classes. The result was a set of classes, each of which is functionally complete, which allows them to be reused использовать повторно in other test environments. Generating random values ​​has become easier when using the randomize() class method. Only driver_cls and monitor_cls classes interact with the physical interface. UVM testing technology is not considered, maybe later.

Source codes: https://github.com/Finnetrib/AXI-Stream-Adder/tree/axis-adder-v2

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *