zoukankan      html  css  js  c++  java
  • Testbench文件编写纪要(Verilog)

    之前在使用Verilog做FPGA项目中、以及其他一些不同的场合下,零散的写过一些练手性质的testbench文件,开始几次写的时候,每次都会因为一些基本的东西没记住、写的很不熟练,后面写的时候稍微熟练了一点、但是整体编写下来比较零碎不成体系,所以在这里简要记录一下一般情况下、针对小型的verilog模块进行测试时所需要使用到的testbench文件的编写要点。

    本文主要参考了在网上找到的Lattice公司的“A Verilog HDL Test Bench Primer”手册中的有关内容。谢谢!

    模块实例化、reg&wire声明、initial和always块的使用

    需要测试的模块(Verilog-module)被称为DUT(Design Under Test),在testbench中需要对一个或者多个DUT进行实例化。

    Testbench中的顶层module不需要定义输入和输出。

    Testbench中连接到DUT instance的输入的为reg类型、连接到DUT instance的输出的为wire类型。

    对于DUT的inout类型变量,在testbench中需要分别使用reg、wire类型的变量进行调用。

    例如,对于下面这样一个待测试module:

    module bidir_infer (DATA, READ_WRITE);
    input READ_WRITE ;
    inout [1:0] DATA ;
    reg [1:0] LATCH_OUT ;
    
    always @ (READ_WRITE or DATA) begin
        if (READ_WRITE == 1)
            LATCH_OUT <= DATA;
    end
    
    assign DATA = (READ_WRITE == 1) ? 2'bZ : LATCH_OUT;
    
    endmodule

    为其设计的testbench文件可以是:

    module test_bidir_ver;
    reg read_writet;
    reg [1:0] data_in;
    wire [1:0] datat, data_out;
    bidir_infer uut (datat, read_writet);
    
    assign datat = (read_writet == 1) ? data_in : 2'bZ;
    assign data_out = (read_writet == 0) ? datat : 2'bZ;
    
    initial begin
    read_writet = 1;
    data_in = 11;
    #50 read_writet = 0;
    end
    
    endmodule

    和普通的Verilog模块中一样、使用assign对wire类型的变量进行赋值。

    需要留意的一点是:对于没有在代码中赋初始值的变量,wire类型变量被初始化为Z、reg类型变量被初始化为X。

    always和initial是两种对reg变量进行操作的串行控制块。每个initial和always块都会在仿真开始时同时开始运行。

    常见的,可以利用它们生成模块所需的时钟和复位信号,如下:

    ‘timescale 1 ns / 100 ps
    
    reg clk_50, rst_l;
    
    initial
    begin
    $display($time, " << Starting the Simulation >>");
    clk_50 = 1’b0; // at time 0
    rst_l = 0; // reset is active
    #20 rst_l = 1’b1; // at time 20 release reset
    end
    
    always
    #10 clk_50 = ~clk_50; // every ten nanoseconds invert

    首行定义了时间单位/时间精度。时间单位为1ns,这样生成的clk_50时钟周期就是20ns、也就是频率为50MHz。

    复位信号rst_l在初始为0复位态、在20ns之后为1解除复位。

    仿真中的停止、变量监视和输出

    有两种仿真控制函数:$finish和$stop。其中,$finish任务用于终止仿真并跳出仿真器;$stop任务则用于中止仿真。在Modelsim中,$stop任务则是返回到交互模式。

    如果需要监视仿真中某个变量的变化情况,可以使用$monitor函数:

    $monitor($time, " clk_50=%b, rst_l=%b, enable_l=%b, load_l=%b, count_in=%h, cnt_out=%h, oe_l=%b, count_tri=%h", clk_50, rst_l, enable_l, load_l, count_in, cnt_out, oe_l, count_tri);

    每当变量列表中的任一变量发生变化,就会产生输出。

    如果需要在仿真控制台屏幕打印输出,可以使用$display函数:

    $display($time, "<< count = %d - Turning OFF count enable >>",cnt_out);

    任务Task的用法

    可以将一组重复性的或者相关的命令组合到一起构成一个任务。

    任务通常可以在initial或者always块中被调用。

    一个任务可以拥有输入、输出、以及inouts,也可以包含计时或延时元素。

    以一个在FPGA上实现的简单SPI接口为例。外部设备为主、FPGA为从,命令一共32bit,构成为“1位读写命令字(1读0写)+14位地址+1位NO CARE+16位数据”,片选信号拉低之后通信开始,时序如下图:

    数据流由外设到FPGA时(FPGA为接收),外设在SCLK的下降沿更新MOSI;FPGA在SCLK的上升沿将MOSI上的值抓取到移位寄存器。

    当FPGA为发送方时,FPGA在SCLK的下降沿更新MISO线上的输出,外设在SCLK的上升沿将MISO上的值抓取过来。

    外设可以通过该SPI接口访问FPGA内部生成的寄存器。

    当对FPGA上的spi模块进行读测试时,外设发给FPGA的读指令为:

    {1'b1,address,1'b0,data(读取到的16bit数据)}

    为此编写的任务spi_read可以是:

    task spi_read;    
    input[13:0]    address;
    output[15:0]    data;
    reg [31:0] output_register;
    reg [15:0]input_register;
    integer i;
        begin
           $display("time:%t----------------task spi_read",$time );
                #100;    
                spi_clk = 1'b0;
                spi_csn = 1'b1;
                spi_mosi =1'b0;
                output_register = {1'b1,address,1'b0,16'd0};
                
                $display("time:%t,testbench read output_register: %h,",$time,output_register );
                $display("time:%t,testbench read address: %h",$time,address );
                
                spi_csn = 1'b1; 
                for(i = 0 ; i < 16 ; i=i+1)
                    begin
                        spi_csn = 1'b0;
                        spi_clk = 1'b0;
                        spi_mosi = output_register[31-i];
                        #100;
                        spi_clk = 1'b1;
                        #100;
                    end
                    
                for(i = 0 ; i < 16 ; i=i+1)
                    begin
                        spi_csn = 1'b0;
                        spi_clk = 1'b0;
                        #100;
                        spi_clk = 1'b1;
                        input_register[15-i] = spi_miso;
                        #100;
                    end
                spi_csn = 1'b1;    
                
                
                data = input_register;    
                $display("time:%t,testbench spi_read read data: %h,",$time,input_register );            
                
                $display("time:%t----------------",$time );
                #100;
        end
    
    endtask
    View Code

    (其中仿真的时间单位为1ns,spi时钟频率为10MHz)

    示例及汇总

    根据前述内容,自我总结一般简单的testbench文件的结构形式可以是如下:

    `timescale 1 ns / 1 ns
    
    module testbench_module_top;
    reg 
    reg
    ……
    wire
    wire
    ……
    
    //reset and clock definition
    initial beginend
    initial beginend
    
    //actual testing flows
    initial 
    begin 
    //variables initialization
    a = 
    b = 
    …
    
    task_1(var_1… var_N)
    …
    task_N(var_1… var_N)
    $stop;
    …end
    
    
    //dut module instance
    module_top U1
    (
    .var1(),
    .var2(),
    …
    .varN()
    )
    
    //necessary control logic for testbench module test flow
    always@(...)
    
    //tasks definition
    task task_1;
    input …;
    output …;
    ……
    //action flow
    ……
    endtask
    
    ……
    
    task task_N;
    ……
    endtask
    
    endmodule
  • 相关阅读:
    POJ 1681 Painter's Problem(高斯消元法)
    HDU 3530 Subsequence(单调队列)
    HDU 4302 Holedox Eating(优先队列或者线段树)
    POJ 2947 Widget Factory(高斯消元法,解模线性方程组)
    HDU 3635 Dragon Balls(并查集)
    HDU 4301 Divide Chocolate(找规律,DP)
    POJ 1753 Flip Game(高斯消元)
    POJ 3185 The Water Bowls(高斯消元)
    克琳:http://liyu.eu5.org
    WinDbg使用
  • 原文地址:https://www.cnblogs.com/lazypigwhy/p/10599074.html
Copyright © 2011-2022 走看看