Verilog 仿真激励

c 复制代码
module  data_consolidation
    (
        input           clk ,
        input           rstn ,
        input [1:0]     din ,          //data in
        input           din_en ,
        output [7:0]    dout ,
        output          dout_en        //data out
     );

   // data shift and counter
    reg [7:0]            data_r ;
    reg [1:0]            state_cnt ;
    always @(posedge clk or negedge rstn) begin
        if (!rstn) begin
            state_cnt     <= 'b0 ;
            data_r        <= 'b0 ;
        end
        else if (din_en) begin
            state_cnt     <= state_cnt + 1'b1 ;    //数据计数
            data_r        <= {data_r[5:0], din} ;  //数据拼接
        end
        else begin
            state_cnt <= 'b0 ;
        end
    end
    assign dout          = data_r ;

    // data output en
    reg                  dout_en_r ;
    always @(posedge clk or negedge rstn) begin
        if (!rstn) begin
            dout_en_r       <= 'b0 ;
        end
        //计数为 3 且第 4 个数据输入时,同步输出数据输出使能信号
        else if (state_cnt == 2'd3 & din_en) begin  
            dout_en_r       <= 1'b1 ;
        end
        else begin
            dout_en_r       <= 1'b0 ;
        end
    end
    //这里不直接声明dout_en为reg变量,而是用相关寄存器对其进行assign赋值
    assign dout_en       = dout_en_r;

endmodule
c 复制代码
`timescale 1ns/1ps

   //============== (1) ==================
   //signals declaration
module test ;
    reg          clk;
    reg          rstn ;
    reg [1:0]    din ;
    reg          din_en ;
    wire [7:0]   dout ;
    wire         dout_en ;

    //============== (2) ==================
    //clock generating
    real         CYCLE_200MHz = 5 ; //
    always begin
        clk = 0 ; #(CYCLE_200MHz/2) ;
        clk = 1 ; #(CYCLE_200MHz/2) ;
    end

    //============== (3) ==================
    //reset generating
    initial begin
        rstn      = 1'b0 ;
        #8 rstn      = 1'b1 ;
    end

    //============== (4) ==================
    //motivation
    int          fd_rd ;
    reg [7:0]    data_in_temp ;  //for self check
    reg [15:0]   read_temp ;     //8bit ascii data, 8bit \n
    initial begin
        din_en    = 1'b0 ;        //(4.1)
        din       = 'b0 ;
        open_file("../tb/data_in.dat", "r", fd_rd); //(4.2)
        wait (rstn) ;    //(4.3)
        # CYCLE_200MHz ;

        //read data from file
        while (! $feof(fd_rd) ) begin  //(4.4)
            @(negedge clk) ;
            $fread(read_temp, fd_rd);
            din    = read_temp[9:8] ;
            data_in_temp = {data_in_temp[5:0], din} ;
            din_en = 1'b1 ;
        end

        //stop data
        @(posedge clk) ;  //(4.5)
        #2 din_en = 1'b0 ;
    end

    //open task
    task open_file;
        input string      file_dir_name ;
        input string      rw ;
        output int        fd ;

        fd = $fopen(file_dir_name, rw);
        if (! fd) begin
            $display("--- iii --- Failed to open file: %s", file_dir_name);
        end
        else begin
            $display("--- iii --- %s has been opened successfully.", file_dir_name);
        end
    endtask

    //============== (5) ==================
    //module instantiation
    data_consolidation    u_data_process
    (
      .clk              (clk),
      .rstn             (rstn),
      .din              (din),
      .din_en           (din_en),
      .dout             (dout),
      .dout_en          (dout_en)
     );

    //============== (6) ==================
    //auto check
    reg  [7:0]           err_cnt ;
    int                  fd_wr ;

    initial begin
        err_cnt   = 'b0 ;
        open_file("../tb/data_out.dat", "w", fd_wr);
        forever begin
            @(negedge clk) ;
            if (dout_en) begin
                $fdisplay(fd_wr, "%h", dout);
            end
        end
    end

    always @(posedge clk) begin
        #1 ;
        if (dout_en) begin
            if (data_in_temp != dout) begin
                err_cnt = err_cnt + 1'b1 ;
            end
        end
    end

    //============== (7) ==================
    //simulation finish
    always begin
        #100;
        if ($time >= 10000)  begin
            if (!err_cnt) begin
                $display("-------------------------------------");
                $display("Data process is OK!!!");
                $display("-------------------------------------");
            end
            else begin
                $display("-------------------------------------");
                $display("Error occurs in data process!!!");
                $display("-------------------------------------");
            end
            #1 ;
            $finish ;
        end
    end

endmodule // test

testbench 具体分析

1)信号声明

testbench 模块声明时,一般不需要声明端口。因为激励信号一般都在 testbench 模块内部,没有外部信号。

声明的变量应该能全部对应被测试模块的端口。当然,变量不一定要与被测试模块端口名字一样。但是被测试模块输入端对应的变量应该声明为 reg 型,如 clk,rstn 等,输出端对应的变量应该声明为 wire 型,如 dout,dout_en。

2)时钟生成

生成时钟的方式有很多种,例如以下两种生成方式也可以借鉴。

c 复制代码
initial clk = 0 ;
always #(CYCLE_200MHz/2) clk = ~clk;

initial begin
    clk = 0 ;
    forever begin
        #(CYCLE_200MHz/2) clk = ~clk;
    end
end

需要注意的是,利用取反方法产生时钟时,一定要给 clk 寄存器赋初值。

利用参数的方法去指定时间延迟时,如果延时参数为浮点数,该参数不要声明为 parameter 类型。例如实例中变量 CYCLE_200MHz 的值为 2.5。如果其变量类型为 parameter,最后生成的时钟周期很可能就是 4ns。当然,timescale 的精度也需要提高,单位和精度不能一样,否则小数部分的时间延迟赋值也将不起作用。

3)复位生成

复位逻辑比较简单,一般赋初值为 0,再经过一段小延迟后,复位为 1 即可。

这里大多数的仿真都是用的低有效复位。

4)激励部分

激励部分该产生怎样的输入信号,是根据被测模块的需要来设计的。

本次实例中:

(4.1) 对被测模块的输入信号进行一个初始化,防止不确定值 X 的出现。激励数据的产生,我们需要从数据文件内读入。

(4.2) 处利用一个 task 去打开一个文件,只要指定文件存在,就可以得到一个不为 0 的句柄信号 fp_rd。fp_rd 指定了文件数据的起始地址。

(4.3) 的操作是为了等待复位后,系统有一个安全稳定的可测试状态。

(4.4) 开始循环读数据、给激励。在时钟下降沿送出数据,是为了被测试模块能更好的在上升沿采样数据。

利用系统任务 $fread ,通过句柄信号 fd_rd 将读取的 16bit 数据变量送入到 read_temp 缓存。

输入数据文件前几个数据截图如下。因为 $fread 只能读取 2 进制文件,所以输入文件的第一行对应的 ASCII 码应该是 330a,所以我们想要得到文件里的数据 3,应该取变量 read_temp 的第 9 到第 8bit 位的数据。

信号 data_in_temp 是对输入数据信号的一个紧随的整合,后面校验模块会以此为参考,来判断仿真是否正常,模块设计是否正确。

(4.5) 选择在时钟上升沿延迟 2 个周期后停止输入数据,是为了被测试模块能够正常的采样到最后一个数据使能信号,并对数据进行正常的整合。

当数据量相对较少时,可以利用 Verilog 中的系统任务 $readmemh 来按行直接读取 16 进制数据。保持文件 data_in.dat 内数据和格式不变,则该激励部分可以描述为:

c 复制代码
reg [1:0]    data_mem [39:0] ;
    reg [7:0]    data_in_temp ;  //for self check
    integer      k1 ;
    initial begin
        din_en    = 1'b0 ;
        din       = 'b0 ;
        $readmemh("../tb/data_in.dat", data_mem);
        wait (rstn) ;
        # CYCLE_200MHz ;

        //read data from file
        for(k1=0; k1<40; k1=k1+1)  begin
            @(negedge clk) ;
            din    = data_mem[k1] ;
            data_in_temp = {data_in_temp[5:0], din} ;
            din_en = 1'b1 ;
        end

        //stop data
        @(posedge clk) ;
        #2 din_en = 1'b0 ;
     end

5)模块例化

这里利用 testbench 开始声明的信号变量,对被测试模块进行例化连接。

6)自校验

如果设计比较简单,完全可以通过输入、输出信号的波形来确定设计是否正确,此部分完全可以删除。如果数据很多,有时候拿肉眼观察并不能对设计的正确性进行一个有效判定。此时加入一个自校验模块,会大大增加仿真的效率。

实例中,我们会在数据输出使能 dout_en 有效时,对输出数据 dout 与参考数据 read_temp(激励部分产生)做一个对比,并将对比结果置于信号 err_cnt 中。最后就可以通过观察 err_cnt 信号是否为 0 来直观的对设计的正确性进行判断。

当然如实例中所示,我们也可以将数据写入到对应文件中,利用其他方式做对比。

7)结束仿真

如果我们不加入结束仿真部分,仿真就会无限制的运行下去,波形太长有时候并不方便分析。Verilog 中提供了系统任务 $finish 来停止仿真。

停止仿真之前,可以将自校验的结果,通过系统任务 $display 在终端进行显示。

相关推荐
LabVIEW开发9 小时前
LabVIEW与FPGA超声探伤
fpga开发·labview·labview功能
cycf10 小时前
FPGA设计中的数据存储
fpga开发
FPGA之旅1 天前
FPGA从零到一实现FOC(一)之PWM模块设计
fpga开发·dubbo
XMAIPC_Robot2 天前
基于ARM+FPGA的光栅尺精密位移加速度测试解决方案
arm开发·人工智能·fpga开发·自动化·边缘计算
cycf2 天前
状态机的设计
fpga开发
szxinmai主板定制专家2 天前
【精密测量】基于ARM+FPGA的多路光栅信号采集方案
服务器·arm开发·人工智能·嵌入式硬件·fpga开发
千宇宙航2 天前
闲庭信步使用SV搭建图像测试平台:第三十二课——系列结篇语
fpga开发
千宇宙航2 天前
闲庭信步使用SV搭建图像测试平台:第三十一课——基于神经网络的手写数字识别
图像处理·人工智能·深度学习·神经网络·计算机视觉·fpga开发
小眼睛FPGA3 天前
【RK3568+PG2L50H开发板实验例程】FPGA部分/紫光同创 IP core 的使用及添加
科技·嵌入式硬件·ai·fpga开发·gpu算力
forgeda3 天前
如何将FPGA设计验证效率提升1000倍以上(2)
fpga开发·前沿技术·在线调试·硬件断点·时钟断点·事件断点