网络知识 娱乐 SV小项目—异步fifo的简单验证环境搭建(全)

SV小项目—异步fifo的简单验证环境搭建(全)

目录

0.前言

1.整体环境搭建

1.1 interface搭建

1.2 clk generator搭建

1.3 rst generator搭建

1.4 environment搭建 

1.5 top搭建

1.5.1 fifo_top搭建

1.5.2 顶层top搭建

2.添加剩余组件 

2.1 transaction组件

2.2 generator组件

2.3 driver组件

2.4 monitor组件

2.5 scoreboard组件

2.6 更新environment

2.7 更新top 

3.仿真结果

4.结语 


0.前言

整个SV学习完毕后,为了进一步巩固知识,初步了解并搭建出简单的验证环境,故为一个异步fifo模块儿搭建验证环境;该异步fifo的具体实现及详细介绍可见这篇文章:异步FIFO---Verilog实现。

首先奉上整个验证流程的架构框图如下,后面的验证环境将基于此图进行搭建:

对于验证一个IP,首先要指定验证策略并提取验证点,做出相应的test plan,该异步fifo功能单一简单,故为其指定如下:

1.整体环境搭建

该部分将整个验证架构最基本的部分搭建起来,包括interface、clk generator、rst generator、environment、top这5个文件;这将能给DUT生成时钟激励,及复位激励,该部分的正确搭建是后续添加其它组件的基础。

1.1 interface搭建

       个人习惯,上来先搭建interface文件方便后续连接

interface fifoPorts #(parameter DSIZE=8);
    logic wclk;//声明所有输入/出接口
    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);//同步驱动winc和wdata
        output winc;
        output wdata;
    endclocking

    clocking rcb@(posedge wclk);//同步驱动rinc
        output rinc;
    endclocking

    modport TB(
                output wrst_n,rrst_n,wdata,winc,rinc,wclk,rclk,
                input rdata,wfull,rempty);//为TB指定信号方向

    modport DUT(
                input wrst_n,rrst_n,wdata,winc,rinc,wclk,rclk,
                output rdata,wfull,rempty);//为DUT指定信号方向
endinterface

1.2 clk generator搭建

class clockGenerator;
    logic wclk,rclk;
    int period;//时钟半周期

    virtual fifoPorts itf;//定义虚接口把软件中的信号给硬件

    function new(virtual fifoPorts itf);//
        this.itf=itf;
    endfunction
/*>==================clkActivate===============================endclkActivate===============================clkGenerator===============================endclkGenerator=============<*/  
endclass

1.3 rst generator搭建

class resetGenerator;
    logic wrst,rrst;
    int period;//复位多少个时钟周期

    virtual fifoPorts itf;//定义虚接口把软件中的信号给硬件

    function new(virtual fifoPorts itf);//
        this.itf=itf;
    endfunction
/*>==================rstActivate===============================endrstActivate===============================rstGenerator===============================endrstGenerator=============<*/  
endclass

1.4 environment搭建 

class environment #(parameter DSIZE=8);
    virtual fifoPorts itf;
    
    clockGenerator w_clkGen;//声明句柄
    clockGenerator r_clkGen;
    resetGenerator wrstGen;
    resetGenerator rrstGen;

    function new(virtual fifoPorts itf);
        this.itf=itf;
        w_clkGen=new(itf);//实例化
        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搭建

1.5.1 fifo_top搭建

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

    always(*)begin
        #1 wclk_tmp=itf.wclk;//通过延迟来使wclk和rclk不同相位
    end

    fifo1 #(.DSIZE(DSIZE),.ASIZE(ADDRSIZE))//为异步fifo设置数据位宽和深度 i0(
                                                .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和interface连接
endmodule

1.5.2 顶层top搭建

我们可以将用到的参数和需要编译的所有组件放到包中,方便top调用,使代码更简洁易懂。

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

    `include"用到的相关组件"
    ······
    ······
endpackage
module top();
    import fifo_params::*;

    fifoPorts #(DSIZE) itf();//例化interface
    fifo_top i0(itf.DUT);//DUT和interface连接

    environment #(DSIZE) env;    

    initial begin
        env=new(itf);例化验证环境,方便后续调用里面的任务
        env.wcgu("wclk",10);
        env.rcgu("rclk",10);

        fork//读写同时复位
            env.wrgu("wrst",10);
            env.rrgu("rrst",10);
        join

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

至此整个验证环境的大框架就搭建好了,如下图所示,剩下的就是将剩余的每个组件写好,然后在environment中连接起来,并在top中调用即可。 

2.添加剩余组件 

2.1 transaction组件

如果generator是枪的话,transaction就是子弹

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

2.2 generator组件

用来将随机化后的transaction发送出去,相当于把激励发送出去,这里我们发送给generator和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组件

从generator和driver之间的mailbox中取出数据,通过虚接口发送给DUT。

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组件

从DUT中接收数据,给monitor和scoreboard的mailbox中。

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组件

比较Driver和monitor的数据是否一致,由于fifo只是当一个数据的中转站,所以数据一致。如果是复杂的DUT,还要创建ref model,将接收到的数据和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 更新environment

以上只是定义好了各个组件,在环境中我们要将他们相连到一起。

class environment #(parameter DSIZE=8);
    virtual fifoPorts itf;
    
    clockGenerator w_clkGen;//声明句柄
    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);//实例化
        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 更新top 

module top();
    import fifo_params::*;

    fifoPorts #(DSIZE) itf();//例化interface
    fifo_top i0(itf.DUT);//DUT和interface连接

    environment #(DSIZE) env;    

    initial begin
        env=new(itf);例化验证环境,方便后续调用里面的任务
        env.wcgu("wclk",10);
        env.rcgu("rclk",10);

        fork//读写同时复位
            env.wrgu("wrst",10);
            env.rrgu("rrst",10);
        join

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

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

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

3.仿真结果

先简单验证一个复位功能是否正常,读写指针是否在复位后回0了:

 然后验证数据是否能正常写入,正常读出;在异常状态,写满和读空时相应信号是否拉起:

 验证wclk和rclk相同时,数据同时写入和读出是否正常,这里可以看到可能是由于DUT本身特性,rempty晚拉低了几个时钟周期,导致数据读出不准确,且丢了后面几个数据:

 然后验证不同时钟下同步读写是否正常,先是写数据快,读数据慢,同时为了避免上述rempty延迟的问题,采取了先写延迟读的方式:

 下面是读快写慢:

4.结语 

至此这个非常简单的验证小项目就结束了,通过本项目实战操练了SV语法的具体运用,同时自己搭建了简单的验证环境;但也存在一些问题,如对DUT理解不是很到位,导致验出来的波形不知道是DUT的bug还是本该如此,后续会对异步fifo加强理解。

转行之路长漫漫,自己目前也只是初入茅庐,所以文章中不对的地方还敬请批评指正!需要学习的东西还是太多,但只要坚持学习,勤学多练,相信自己一定能够成功!