zoukankan      html  css  js  c++  java
  • 如何编写testbench的总结(非常实用的总结)

    1.激励的设置

    相应于被测试模块的输入激励设置为reg型,输出相应设置为wire类型,双向端口inout在测试中需要进行处理。

    方法1:为双向端口设置中间变量inout_reg作为该inout的输出寄存,inout口在testbench中要定义为wire型变量,然后用输出使能控制传输方向。

    eg:

    inout [0:0] bi_dir_port;

    wire [0:0] bi_dir_port;

    reg [0:0] bi_dir_port_reg;

    reg bi_dir_port_oe;

    assign bi_dir_port=bi_dir_port_oe?bi_dir_port_reg:1'bz;

    用bi_dir_port_oe控制端口数据方向,并利用中间变量寄存器改变其值。等于两个模块之间用inout双向口互连。往端口写(就是往模块里面输入)

    方法2:使用force和release语句,这种方法不能准确反映双向端口的信号变化,但这种方法可以反映块内信号的变化。具体如示:

    module test();

    wire data_inout;

    reg data_reg;

    reg link;

    #xx; //延时

    force data_inout=1'bx; //强制作为输入端口

    ...............

    #xx;

    release data_inout; //释放输入端口

    endmodule

    从文本文件中读取和写入向量

    1)读取文本文件:用 $readmemb系统任务从文本文件中读取二进制向量(可以包含输入激励和输出期望值)。$readmemh 用于读取十六进制文件。例如:

    reg [7:0] mem[1:256] // a 8-bit, 256-word 定义存储器mem

    initial $readmemh ( "mem.data", mem ) // 将.dat文件读入寄存器mem中

    initial $readmemh ( "mem.data", mem, 128, 1 ) // 参数为寄存器加载数据的地址始终

    2)输出文本文件:打开输出文件用?$fopen 例如:

    integer out_file; // out_file 是一个文件描述,需要定义为 integer类型

    out_file = $fopen ( " cpu.data " ); // cpu.data 是需要打开的文件,也就是最终的输出文本

    设计中的信号值可以通过$fmonitor, $fdisplay,

    2. Verilog和Ncverilog命令使用库文件或库目录
    ex). ncverilog -f run.f -v lib/lib.v -y lib2 +libext+.v //一般编译文件在run.f中, 库文件在lib.v中,lib2目录中的.v文件系统自动搜索
    使用库文件或库目录,只编译需要的模块而不必全部编译

    3.Verilog Testbench信号记录的系统任务:

    1). SHM数据库可以记录在设计仿真过程中信号的变化. 它只在probes有效的时间内记录你set probe on的信号的变化.
    ex). $shm_open("waves.shm"); //打开波形数据库
    $shm_probe(top, "AS"); // set probe on "top",
    第二个参数: A -- signals of the specific scrope 
    S -- Ports of the specified scope and below, excluding library cells
    C -- Ports of the specified scope and below, including library cells
    AS -- Signals of the specified scope and below, excluding library cells
    AC -- Signals of the specified scope and below, including library cells
    还有一个 M ,表示当前scope的memories, 可以跟上面的结合使用, "AM" "AMS" "AMC"
    什么都不加表示当前scope的ports;
    $shm_close //关闭数据库
    2). VCD数据库也可以记录在设计仿真过程中信号的变化. 它只记录你选择的信号的变化.
    ex). $dumpfile("filename"); //打开数据库
    $dumpvars(1, top.u1); //scope = top.u1, depth = 1
    第一个参数表示深度, 为0时记录所有深度; 第二个参数表示scope,省略时表当前的scope.
    $dumpvars; //depth = all scope = all
    $dumpvars(0); //depth = all scope = current
    $dumpvars(1, top.u1); //depth = 1 scope = top.u1
    $dumpoff //暂停记录数据改变,信号变化不写入库文件中
    $dumpon //重新恢复记录
    3). Debussy fsdb数据库也可以记录信号的变化,它的优势是可以跟debussy结合,方便调试.
    如果要在ncverilog仿真时,记录信号, 首先要设置debussy:
    a. setenv LD_LIBRARY_PATH :$LD_LIBRARY_PATH
    (path for debpli.so file (/share/PLI/nc_xl//nc_loadpli1))
    b. while invoking ncverilog use the +ncloadpli1 option.
    ncverilog -f run.f +debug +ncloadpli1=debpli:deb_PLIPtr
    fsdb数据库文件的记录方法,是使用$fsdbDumpfile和$fsdbDumpvars系统函数,使用方法参见VCD
    注意: 在用ncverilog的时候,为了正确地记录波形,要使用参数: "+access+rw", 否则没有读写权限

    在记录信号或者波形时需要指出被记录信号的路径,如:tb.module.u1.clk.

    ………………………………………………………………………………………………………

    关于信号记录的系统任务的说明:

    在testbench中使用信号记录的系统任务,就可以将自己需要的部分的结果以及波形文件记录下来(可采用sigalscan工具查看),适用于对较大的系统进行仿真,速度快,优于全局仿真。使用简单,在testbench中添加:initial begin

    $shm_open("waves.shm");

    $shm_probe("要记录信号的路径“,”AS“);

    #10000

    $shm_close; 即可。

    4. ncverilog编译的顺序: ncverilog file1 file2 ....
    有时候这些文件存在依存关系,如在file2中要用到在file1中定义的变量,这时候就要注意其编译的顺序是
    从后到前,就先编译file2然后才是file2.

    5. 信号的强制赋值force
    首先, force语句只能在过程语句中出现,即要在initial 或者 always 中间. 去除force 用 release 语句.
    initial begin force sig1 = 1'b1; ... ; release sig1; end
    force可以对wire赋值,这时整个net都被赋值; 也可以对reg赋值.

    6.加载测试向量时,避免在时钟的上下沿变化

    为了模拟真实器件的行为,加载测试向量时,避免在时钟的上下沿变化,而是在时钟的上升沿延时一个时间单位后,加载的测试向量发生变化。如:

    assign #5 c=a^b

    ……

    @(posedge clk) #(0.1*`cycle) A=1;

    ******************************************************************************

    //testbench的波形输出

    module top;

    ...

    initial

    begin

    $dumpfile("./top.vcd"); //存储波形的文件名和路径,一般是.vcd格式.

    $dumpvars(1,top); //存储top这一层的所有信号数据

    $dumpvars(2,top.u1); //存储top.u1之下两层的所有数据信号(包含top.u1这一层)

    $dumpvars(3,top.u2); //存储top.u2之下三层的所有数据信号(包含top.u2这一层)

    $dumpvars(0,top.u3); //存储top.u3之下所有层的所有数据信号

    end

    endmodule

    //产生随机数,seed是种子

    $random(seed);

    ex: din <= $random(20);

    //仿真时间,为unsigned型的64位数据

    $time

    ex:

    ...

    time condition_happen_time;

    ...

    condition_happen_time = $time;

    ...

    $monitor($time,"data output = %d", dout);

    ...

    //参数

    parameter para1 = 10,

    para2 = 20,

    para3 = 30;

    //显示任务

    $display();

    //监视任务

    $monitor();

    //延迟模型

    specify

    ...

    //describ pin-to-pin delay

    endspecify

    ex:

    module nand_or(Y,A,B,C);

    input A,B,C;

    output Y;

    AND2 #0.2 (N,A,B);

    OR2 #0.1 (Y,C,N);

    specify

    (A*->Y) = 0.2;

    (B*->Y) = 0.3;

    (C*->Y) = 0.1;

    endspecify

    endmodule

    //时间刻度

    `timescale 单位时间/时间精确度

    //文件I/O

    1.打开文件

    integer file_id;

    file_id = fopen("file_path/file_name");

    2.写入文件

    //$fmonitor只要有变化就一直记录

    $fmonitor(file_id, "%format_char", parameter);

    eg:$fmonitor(file_id, "%m: %t in1=%d o1=%h", $time, in1, o1);

    //$fwrite需要触发条件才记录

    $fwrite(file_id, "%format_char", parameter);

    //$fdisplay需要触发条件才记录

    $fdisplay(file_id, "%format_char", parameter);

    $fstrobe();

    3.读取文件

    integer file_id;

    file_id = $fread("file_path/file_name", "r");

    4.关闭文件

    $fclose(fjile_id);

    5.由文件设定存储器初值

    $readmemh("file_name", memory_name"); //初始化数据为十六进制

    $readmemb("file_name", memory_name"); //初始化数据为二进制

    //仿真控制

    $finish(parameter); //parameter = 0,1,2

    $stop(parameter);

    //读入SDF文件

    $sdf_annotate("sdf_file_name", module_instance, "scale_factors");

    //module_instance: sdf文件所对应的instance名.

    //scale_factors:针对timming delay中的最小延时min,典型延迟typ,最大延时max调整延迟参数

    //generate语句,在Verilog-2001中定义.用于表达重复性动作

    //必须事先声明genvar类型变量作为generate循环的指标

    eg:

    genvar i;

    generate for(i = 0; i < 4; i = i + 1)

    begin

    assign = din[i] = i % 2;

    end

    endgenerate

    //资源共享

    always @(A or B or C or D)

    sum = sel ? (A+B):(C+D);

    //上面例子使用两个加法器和一个MUX,面积大

    //下面例子使用一个加法器和两个MUX,面积小

    always @(A or B or C or D)

    begin

    tmp1 = sel ? A:C;

    tmp2 = sel ? B:D;

    end

    always @(tmp1 or tmp2)

    sum = tmp1 + tmp2;

    ******************************************************************************

    模板:

    module testbench; //定义一个没有输入输出的module

    reg …… //将DUT的输入定义为reg类型

    ……

    wire…… //将DUT的输出定义为wire类型

    ……

    //在这里例化DUT

    initial

    begin

    …… //在这里添加激励(可以有多个这样的结构)

    end

    always…… //通常在这里定义时钟信号

    initial

    //在这里添加比较语句(可选)

    end

    initial

    //在这里添加输出语句(在屏幕上显示仿真结果)

    end

    endmodule

    一下介绍一些书写Testbench的技巧:

    1.如果激励中有一些重复的项目,可以考虑将这些语句编写成一个task,这样会给书写和仿真带来很大方便。例如,一个存储器的testbench的激励可以包含write,read等task。

    2.如果DUT中包含双向信号(inout),在编写testbench时要注意。需要一个reg变量来表示其输入,还需要一个wire变量表示其输出。

    3.如果initial块语句过于复杂,可以考虑将其分为互补相干的几个部分,用数个initial块来描述。在仿真时,这些initial块会并发运行。这样方便阅读和修改。

    4.每个testbench都最好包含$stop语句,用以指明仿真何时结束。

    最后提供一个简单的示例(转自Xilinx文档):

    DUT:

    module shift_reg (clock, reset, load, sel, data, shiftreg);

    input clock;

    input reset;

    input load;

    input [1:0] sel;

    input [4:0] data;

    output [4:0] shiftreg;

    reg [4:0] shiftreg;

    always @ (posedge clock)

    begin

    if (reset)

    shiftreg = 0;

    else if (load)

    shiftreg = data;

    else

    case (sel)

    2’b00 : shiftreg = shiftreg;

    2’b01 : shiftreg = shiftreg << 1;

    2’b10 : shiftreg = shiftreg >> 1;

    default : shiftreg = shiftreg;

    endcase

    end

    endmodule

    Testbench:

    module testbench; // declare testbench name

    reg clock;

    reg load;

    reg reset; // declaration of signals

    wire [4:0] shiftreg;

    reg [4:0] data;

    reg [1:0] sel;

    // instantiation of the shift_reg design below

    shift_reg dut(.clock (clock),

    .load (load),

    .reset (reset),

    .shiftreg (shiftreg),

    .data (data),

    .sel (sel));

    //this process block sets up the free running clock

    initial begin

    clock = 0;

    forever #50 clock = ~clock;

    end

    initial begin// this process block specifies the stimulus.

    reset = 1;

    data = 5’b00000;

    load = 0;

    sel = 2’b00;

    #200

    reset = 0;

    load = 1;

    #200

    data = 5’b00001;

    #100

    sel = 2’b01;

    load = 0;

    #200

    sel = 2’b10;

    #1000 $stop;

    end

    initial begin// this process block pipes the ASCII results to the

    //terminal or text editor

    $timeformat(-9,1,"ns",12);

    $display(" Time Clk Rst Ld SftRg Data Sel");

    $monitor("%t %b %b %b %b %b %b", $realtime,

    clock, reset, load, shiftreg, data, sel);

    end

    endmodule

  • 相关阅读:
    mybatis-01-简单概述基础点
    04-书城缺少方法
    03-书城bean类中的id缺少get属性
    02-书城传参类型异常
    执行Oracle存储过程报权限不足的解决方法
    创建表空间及用户的SQL
    Oracle instr函数与SqlServer charindex的区别
    利用ExtJS导出Excel
    Java循环日期
    Oracle给不同组数据添加顺序
  • 原文地址:https://www.cnblogs.com/lsjjob/p/6036000.html
Copyright © 2011-2022 走看看