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实现

(这里只是通过这篇文章理解一下异步fifo的实现原理,这篇文章验的原RTL代码并非这篇文章中的)

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

对于验证一个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=============<*/  
    task automatic clkActivate(input string clkName);//通过外部输入的wclk/rclk决定生成什么clk
        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时钟反转
            end
        end
    endtask
/*>==================endclkActivate=============<*/

/*>==================clkGenerator=============<*/  
    task automatic clkGenerator(input string clkName,input int clkPeriod);//通过外部调用输入的wclk/rclk和周期决定clk的周期和类型
        $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搭建

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

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

    function new(virtual fifoPorts itf);//
        this.itf=itf;
    endfunction
/*>==================rstActivate=============<*/  
    task automatic rstActivate(input string rstName);//根据外部调用名称决定是wrst/rrst
        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);//外部调用决定复位哪个信号,复位多长时间
        $display("%0t:Calling task rstGenerator for %s",$time,clkName);
        this.period=rstPeriod;
        rstActivate(rstName);
    endtask
/*>==================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加强理解。

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

  • 45
    点赞
  • 368
    收藏
    觉得还不错? 一键收藏
  • 12
    评论
### 回答1: 要搭建异步FIFO简单验证环境,我们需要以下步骤。 首先,我们需要确定项目的需求和目标。异步FIFO是一种用于数据传输的时序元件,用于在不同频率的两个电路间传输数据。因此,我们需要明确验证环境中需要使用的异步FIFO的规格和功能。 接下来,我们需要选择合适的验证工具。一般来说,我们可以使用Verilog或SystemVerilog语言来编写FIFO的模型和测试环境。对于模拟器,我们可以选择常用的商业工具,如ModelSim或VCS,也可以选择开源工具,如Icarus Verilog或Verilator。 然后,我们需要编写FIFO的模型和测试环境。根据项目需求,我们可以设计一个简单的异步FIFO的模型,并在测试环境中生成数据并进行传输。我们需要编写测试用例来验证FIFO的各种功能和性能特性,如写入和读取的正确性、数据丢失和溢出的处理等。 接下来,我们需要运行验证环境并进行仿真。使用选定的模拟器,我们可以加载FIFO模型和测试环境,并运行测试用例。我们需要检查仿真结果是否符合我们设计的预期,并根据需要进行调试和修改。 最后,我们需要分析仿真结果,并进行评估和报告。通过分析仿真波形和日志,我们可以评估异步FIFO的性能和可靠性。根据评估结果,我们可以优化FIFO的设计,或者确定它符合预期的性能和功能。 总结起来,搭建异步FIFO简单验证环境需要明确需求、选择合适的验证工具、编写FIFO的模型和测试环境、运行仿真、分析结果并进行评估报告。这个过程需要综合考虑验证目标和项目需求,并进行多次迭代和优化。 ### 回答2: 搭建异步FIFO简单验证环境,首先需要准备好测试工具和相关的硬件设备。测试工具包括仿真器、信号发生器、逻辑分析仪和示波器等。 步骤如下: 1. 硬件准备:准备好需要进行验证FIFO模块,包括读取和写入接口。连接FIFO模块到适当的时钟源,并确保时钟信号稳定。 2. 搭建验证环境:使用仿真器连接到FIFO模块的读取和写入接口,通过仿真器对FIFO进行控制和观察。将信号发生器连接到FIFO模块,模拟数据输入。同时,连接逻辑分析仪和示波器,用于监测和分析FIFO模块的读取和写入过程中的信号波形。 3. 配置测试环境:设置仿真器的工作参数,包括时钟频率、输入数据量以及FIFO的深度等。根据需要,还可以设置FIFO的读取和写入优先级、空状态和满状态等。 4. 设置测试用例:根据设计需求和功能特性,编写测试用例。测试用例应包含各种边界情况以及正常情况,以验证FIFO的性能和功能是否满足设计要求。 5. 运行测试:启动仿真器,根据测试用例的设定,对FIFO进行读取和写入操作。观察信号波形,并使用逻辑分析仪进行信号分析,确保FIFO的读取和写入过程正常进行。 6. 分析测试结果:根据测试结果,对FIFO的性能和功能进行评估。如果发现问题或不符合设计要求,需要进行调试和修改。 通过搭建这样的简单验证环境,可以有效地验证异步FIFO的功能和性能,确保其在实际应用中的可靠性。同时,也可以通过这个验证过程,发现潜在的问题并进行改进,提高FIFO的性能和可靠性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值