SV small project - simple verification environment construction of asynchronous fifo (full)

Posted by (RL)Ian on Thu, 27 Jan 2022 00:50:58 +0100

catalogue

0. Preface

1. Overall environment construction

1.1 interface construction

1.2 clk generator setup

1.3 rst generator setup

1.4 environment construction

1.5 # top construction

1.5.1 fifo_top build

1.5.2 top floor construction

2. Add remaining components

2.1 transaction component

2.2 generator components

2.3 driver assembly

2.4 monitor components

2.5 scoreboard components

2.6 update environment

2.7 update top

3. Simulation results

4. Conclusion

0. Preface

After learning the whole SV, in order to further consolidate the knowledge, preliminarily understand and build a simple verification environment, so build a verification environment for an asynchronous fifo module; The specific implementation and detailed introduction of the asynchronous fifo can be seen in this article: Asynchronous FIFO -- Verilog implementation.

Firstly, the architecture block diagram of the whole verification process is presented as follows, and the subsequent verification environment will be built based on this diagram:

For verifying an IP, first specify the verification strategy, extract the verification points and make the corresponding test plan. The asynchronous fifo function is single and simple, so it is specified as follows:

1. Overall environment construction

This part builds the most basic part of the whole verification architecture, including five files: interface, clk generator, rst generator, environment and top; This will generate clock excitation and reset excitation for the DUT. The correct construction of this part is the basis for adding other components in the future.

1.1 interface construction

It's a personal habit to build interface files first to facilitate subsequent connections

interface fifoPorts #(parameter DSIZE=8);
    logic wclk;//Declare all input / output interfaces
    logic rclk;
    logic [DSIZE-1:0]wdata;
    logic [DSIZE-1:0]rdata;
    logic wfull;
    logic rempty;
    logic winc,rinc;
    logic wrst_n,rrst_n;

    clocking wcb@(posedge wclk);//Synchronous drive winc and wdata
        output winc;
        output wdata;
    endclocking

    clocking rcb@(posedge wclk);//Synchronous drive rinc
        output rinc;
    endclocking

    modport TB(
                output wrst_n,rrst_n,wdata,winc,rinc,wclk,rclk,
                input rdata,wfull,rempty);//Specify signal direction for TB

    modport DUT(
                input wrst_n,rrst_n,wdata,winc,rinc,wclk,rclk,
                output rdata,wfull,rempty);//Specify the signal direction for the DUT
endinterface

1.2 clk generator setup

class clockGenerator;
    logic wclk,rclk;
    int period;//Clock half cycle

    virtual fifoPorts itf;//Define the virtual interface and send the signal in the software to the hardware

    function new(virtual fifoPorts itf);//
        this.itf=itf;
    endfunction
/*>==================clkActivate=============<*/  
    task automatic clkActivate(input string clkName);//wclk/rclk from external input determines what clk is generated
        if(clkName=="wclk")begin//wclk
            $display("%0t:Calling task wclkActivate",$time);
            this.itf.wclk=1'b0;
            forever begin
                #this.period this.itf.wclk++;
            end
        end
        else begin//rclk
            $display("%0t:Calling task rclkActivate",$time);
            this.itf.rclk=1'b0;
            forever begin
                #this.period this.itf.rclk++;// 0 1 0 1 clock reversal by self addition
            end
        end
    endtask
/*>==================endclkActivate=============<*/

/*>==================clkGenerator=============<*/  
    task automatic clkGenerator(input string clkName,input int clkPeriod);//The cycle and type of clk are determined by the wclk/rclk and cycle input by external call
        $display("%0t:Calling task clkGenerator for %s",$time,clkName);
        this.period=clkPeriod/2;
        fork
            clkActivate(clkName);
        join_none
    endtask
/*>==================endclkGenerator=============<*/  
endclass

1.3 rst generator setup

class resetGenerator;
    logic wrst,rrst;
    int period;//How many clock cycles are reset

    virtual fifoPorts itf;//Define the virtual interface and send the signal in the software to the hardware

    function new(virtual fifoPorts itf);//
        this.itf=itf;
    endfunction
/*>==================rstActivate=============<*/  
    task automatic rstActivate(input string rstName);//wrst/rrst is determined according to the external call name
        if(rstName=="rrst")begin
            $display("%0t:Calling task rrstActivate",$time);
            this.itf.rrst_n=1'b1;
            repeat(this.period) @(posedge this.itf.rclk);
            this.itf.rrst_n=1'b0;
            repeat(this.period) @(posedge this.itf.rclk);
            this.itf.rrst_n=1'b1;
        end
        else begin
            $display("%0t:Calling task wrstActivate",$time);
            this.itf.wrst_n=1'b1;
            repeat(this.period) @(posedge this.itf.wclk);
            this.itf.wrst_n=1'b0;
            repeat(this.period) @(posedge this.itf.wclk);
            this.itf.wrst_n=1'b1;
    endtask
/*>==================endrstActivate=============<*/

/*>==================rstGenerator=============<*/  
    task automatic rstGenerator(input string rstName,input int rstPeriod);//The external call determines which signal to reset and how long to reset
        $display("%0t:Calling task rstGenerator for %s",$time,clkName);
        this.period=rstPeriod;
        rstActivate(rstName);
    endtask
/*>==================endrstGenerator=============<*/  
endclass

1.4 environment construction

class environment #(parameter DSIZE=8);
    virtual fifoPorts itf;
    
    clockGenerator w_clkGen;//Declaration handle
    clockGenerator r_clkGen;
    resetGenerator wrstGen;
    resetGenerator rrstGen;

    function new(virtual fifoPorts itf);
        this.itf=itf;
        w_clkGen=new(itf);//instantiation 
        r_clkGen=new(itf);
        wrstGen=new(itf);
        rrstGen=new(itf);
    endfunction

    extern task wcgu(string msg="wclk",int clk_p=10);
    extern task rcgu(string msg="rclk",int clk_p=10);
    extern task wrgu(string msg="wrst",int clk_p=10);
    extern task rrgu(string msg="rrst",int clk_p=10);
endclass


task environment::wcgu(string msg="wclk",int clk_p=10);
    w_clkGen.clkGenerator(msg,clk_p);
endtask

task environment::rcgu(string msg="rclk",int clk_p=10);
    r_clkGen.clkGenerator("rclk",10);
endtask

task environment::wrgu(string msg="wrst",int clk_p=10);
    wrstGen.rstGenerator("wrst",10);
endtask

task environment::rrgu(string msg="rrst",int clk_p=10);
    rrstGen.rstGenerator("rrst",10);
endtask

1.5 # top construction

1.5.1 fifo_top build

module fifo_top #(parameter DSIZE=8,parameter ADDRSIZE=4) (fifoPorts.DUT itf);
    reg wclk_tmp;

    always(*)begin
        #1 wclk_tmp=itf.wclk;// Wclk and rclk are phased differently by delay
    end

    fifo1 #(.DSIZE(DSIZE),.ASIZE(ADDRSIZE)) / / set the data bit width and depth I0 for asynchronous fifo(
                                                .rdata(itf.rdata),
                                                .wfull(itf.wfull),
                                                .rempty(itf.rempty),
                                                .wdata(itf.wdata),
                                                .winc(itf.winc),
                                                .wclk(wclk_tmp),
                                                .wrst_n(itf.wrst_n),
                                                .rinc(itf.rinc),
                                                .rclk(itf.rclk),
                                                .rrst_n(itf.rrst_n));//DUT and interface connection
endmodule

1.5.2 top floor construction

We can put the parameters used and all components to be compiled into the package to facilitate top calling and make the code more concise and understandable.

package fifo_params;
    parameter DSIZE=8;
    parameter ADDRSIZE=4;
    parameter DEPTH=1<<ADDRSIZE;
    typedef enum{WR,RD} opt_e;

    `include"Relevant components used"
    ······
    ······
endpackage
module top();
    import fifo_params::*;

    fifoPorts #(DSIZE) itf();// Instantiated interface
    fifo_top i0(itf.DUT);//DUT and interface connection

    environment #(DSIZE) env;    

    initial begin
        env=new(itf);Instantiate the verification environment to facilitate subsequent calls of tasks inside
        env.wcgu("wclk",10);
        env.rcgu("rclk",10);

        fork//Reset while reading and writing
            env.wrgu("wrst",10);
            env.rrgu("rrst",10);
        join

        repeat(10) @(posedge itf.rclk);
        $finish;
    end
endmodule

At this point, the whole framework of the verification environment is built. As shown in the figure below, the rest is to write the remaining components, then connect them in environment and invoke them in top.  

2. Add remaining components

2.1 transaction component

If the generator is a gun, the transaction is a bullet

class packet;
    rand bit[DSIZE-1:0] data;
    rand opt_e opt;
endclass

2.2 generator components

It is used to send the randomized transaction, which is equivalent to sending the incentive. Here, we send it to the mailbox for communication between the generator and the driver.

class generator #(parameter DSIZE=8);
    packet gpkt;
    mailbox GSMbx;

    function new(mailbox GSMbx);
        this.GSMbx=GSMbx;
    endfunction

    task send(int sendNumber);
        $display("%0t:Send task,Starting Send...sendNumber=%0d",$time,sendNumber);
        repeat(sendNumber)begin
            gpkt=new;
            assert(gpkt.randomize);
            $display(gpkt);
            GSMbx.put(gpkt);
        end
        $display("%0t:Send task,End Send",$time);
    endtask
endclass

2.3 driver assembly

Take the data from the mailbox between the generator and the driver and send it to the DUT through the virtual interface.

class driver #(parameter DSIZE=8);
    virtual fifoPorts itf;
    
    mailbox GSMbx;
    mailbox DWMbx;
    
    packet dpkt;

    function new(virtual fifoPorts itf,mailbox GSMbx,mailbox DWMbx);
        this.itf=itf;
        this.GSMbx=GSMbx;
        this.DWMbx=DWMbx;
    endfunction

    task Write(input int putInMbx,input int writeNumber);
        int i=0;
        $display("%0t:Driver.Write task,Starting write...writeNumber=%0d",$time,writeNumber);
      do begin
        this.itf.wcb.winc<=1'b1;

        GSMbx.get(dpkt);
        this.itf.wcb.wdata<=dpkt.data;
        pkt.opt=WR;

        if(putInMbx)begin
            this.DWMbx.put(dpkt);
        end
        @(posedge itf.wclk);
        i++;
      end while(i<writeNumber);
        $display("%0t:Driver.Write task,End write",$time);
    endtask
endclass

2.4 monitor components

Receive data from the DUT and send it to the mailbox of monitor and scoreboard.

class monitor #(parameter DSIZE=8);
    virtual fifoPorts itf;
    mailbox MRMbx;
    packet mpkt;
    
    function new(virtual fifoPorts itf,mailbox MRMbx);
        this.itf=itf;
        this.MRMbx=MRMbx;
    endfunction

    task Read(input int putInMbx,input int readNumber);
        int i=0;
        $display("%0t:Monitor.Read task,Start Reading...readNumber=%0d",$time,readNumber);

        do begin
            mpkt=new();
            this.itf.rinc=1'b1;
            @(posedge itf.rclk);
            if(putInMbx)begin
                this.mpkt.data=itf.rdata;
                this.mpkt.opt=RD;
                this.MRMbx.put(mpkt);
            end
            i++;
        end while(i<readNumber);
        this.itf.rinc=1'b0;
        $display("%0t:Monitor.Read task,End Reading...readNumber=%0d",$time);
    endtask
endclass

2.5 scoreboard components

Compare whether the data of Driver and monitor are consistent. Because fifo is only a data transfer station, the data is consistent. If it is a complex DUT, you should also create a ref model to compare the received data with the ref model.

class scoreboard #(parameter DSIZE=8);
    mailbox DWMbx,MRMbx;
    packet wpkt,rpkt;

    function new(mailbox DWMbx,MRMbx);
        this.DWMbx=DWMbx;
        this.MRMbx=MRMbx;
    endfunction

    task compareData;
        int loopw,loopr;
        printMbxContent(DWMbx,"Golden Data is:");
        printMbxContent(MRMbx,"Actual Data is:");

        $display("%0t:Scoreboard.Compare task,Starting Compare...",$time);
        loopw=DWMbx.num();
        loopr=MRMbx.num();
        
        if(loopw!=loopr)begin
            $display("%0t:FAILED for size-mismatch",$time);
        end
        else begin
            for(int i=0;i<loopw;i++)begin
                DWMbx.get(mpkt);
                MRMbx.get(rpkt);
                if(mpkt.data==rpkt.data)
                    $display("%0t:PASS:Read and Write are same:R%0h=W%0h",$time,this.rpkt.data,this.wpkt.data);
                else
                    $display("%0t:FAILED:Read and Write are different:R%0h=W%0h",$time,this.rpkt.data,this.wpkt.data);
            end
        end
    endtask


    task printMbxContent(input mailbox mbx,string message);
        int mbxElements;
        packet pkt;
        packet q[$];
    
        mbxElements=mbx.num();
        for(int i=0;i<mbxElements;i++)begin
            mbx.get(pkt);
            q.push_back(pkt);
            mbx.put(pkt);
        end
        foreach(q[i])begin
            $write("%0h",q[i].data);
        end
        $display(" ");
    endtask;
endclass

2.6 update environment

The above is just the definition of various components. We need to connect them together in the environment.

class environment #(parameter DSIZE=8);
    virtual fifoPorts itf;
    
    clockGenerator w_clkGen;//Declaration handle
    clockGenerator r_clkGen;
    resetGenerator wrstGen;
    resetGenerator rrstGen;

    generator #(DSIZE) gen;
    driver #(DSIZE) drv;
    monitor #(DSIZE) mon;
    scoreboard #(DSIZE) scb;

    mailbox wbox,rbox,sbox;

    function new(virtual fifoPorts itf);
        this.itf=itf;
        w_clkGen=new(itf);//instantiation 
        r_clkGen=new(itf);
        wrstGen=new(itf);
        rrstGen=new(itf);

        wbox=new();
        rbox=new();
        sbox=new();

        gen=new(sbox);
        drv=new(itf,sbox,wbox);
        mon=new(itf,rbox);
        scb=new(wbox,rbox);
    endfunction

    extern task wcgu(string msg="wclk",int clk_p=10);
    extern task rcgu(string msg="rclk",int clk_p=10);
    extern task wrgu(string msg="wrst",int clk_p=10);
    extern task rrgu(string msg="rrst",int clk_p=10);

    extern task gendata(int sendNumber=16);
    extern task fifoWrite(input int putInMbx=1,int writeNumber=16);
    extern task fifoRead(input int putInMbx=1,int readNumber=16);
    extern task compareResult;
endclass


task environment::wcgu(string msg="wclk",int clk_p=10);
    w_clkGen.clkGenerator(msg,clk_p);
endtask

task environment::rcgu(string msg="rclk",int clk_p=10);
    r_clkGen.clkGenerator("rclk",10);
endtask

task environment::wrgu(string msg="wrst",int clk_p=10);
    wrstGen.rstGenerator("wrst",10);
endtask

task environment::rrgu(string msg="rrst",int clk_p=10);
    rrstGen.rstGenerator("rrst",10);
endtask

task environment::gendata(int sendNumber=16);
    gen.send(sendNumber);
endtask

task environment::fifoWrite(input int putInMbx=1,int writeNumber=16);
    drv.write(putInMbx,writeNumber);
endtask

task environment::fifoRead(input int putInMbx=1,int readNumber=16);
    mon.read(putInMbx,readNumber);
endtask

task environment::compareResult;
    scb.compareResult;
endtask

2.7 update top

module top();
    import fifo_params::*;

    fifoPorts #(DSIZE) itf();// Instantiated interface
    fifo_top i0(itf.DUT);//DUT and interface connection

    environment #(DSIZE) env;    

    initial begin
        env=new(itf);Instantiate the verification environment to facilitate subsequent calls of tasks inside
        env.wcgu("wclk",10);
        env.rcgu("rclk",10);

        fork//Reset while reading and writing
            env.wrgu("wrst",10);
            env.rrgu("rrst",10);
        join

        repeat(10) @(posedge itf.wclk);
        env.gendata(26);
        env.fifoWrite(26);

        repeat(10) @(posedge itf.wclk);
        env.fifoRead(26);

        repeat(10) @(posedge itf.rclk);
        env.compareResult;
        $finish;
    end
endmodule

3. Simulation results

First, simply verify whether a reset function is normal and whether the read-write pointer returns to 0 after reset:

Then verify whether the data can be written and read normally; In the abnormal state, whether the corresponding signal is pulled up when writing full and reading empty:

When wclk and rclk are verified to be the same, whether the data can be written and read out at the same time is normal. It can be seen here that it may be due to the characteristics of DUT itself. rempty pulls down several clock cycles late, resulting in inaccurate data reading and loss of the following data:

Then verify whether the synchronous reading and writing under different clocks are normal. First, write data quickly and read data slowly. At the same time, in order to avoid the above rempty delay, write first and delay reading are adopted:

The following is fast reading and slow writing:

4. Conclusion

So far, this very simple verification project is over. Through this project, I have practiced the specific application of SV syntax, and built a simple verification environment myself; However, there are also some problems. For example, the understanding of DUT is not very good, so that the tested waveform does not know whether it is a DUT bug or it should be. Later, we will strengthen the understanding of asynchronous fifo.

It's a long way to change careers. I'm just a novice at present, so please criticize and correct the wrong places in the article! There are still too many things to learn, but as long as you stick to learning, study hard and practice more, I believe you will succeed!

Topics: systemverilog