打印

谁能推荐一些优秀的testbench源程序(verilog)?

[复制链接]
4696|24
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
zhushouxi|  楼主 | 2012-3-5 20:03 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
沙发
GoldSunMonkey| | 2012-3-5 21:21 | 只看该作者
分数太少了。

使用特权

评论回复
板凳
GoldSunMonkey| | 2012-3-5 21:21 | 只看该作者
但是我还是要回答~
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

使用特权

评论回复
评分
参与人数 1威望 +1 收起 理由
zhushouxi + 1
地板
zhushouxi|  楼主 | 2012-3-5 21:35 | 只看该作者
猴哥的回答真详细,总结很好 顶个!
现在发现仿真中系统函数功能很强大,抽象层次越高越好,能来个复杂的tb就好了:lol

使用特权

评论回复
5
GoldSunMonkey| | 2012-3-5 21:36 | 只看该作者
;P就是没有Verilog的。哈哈

自己摸索吧。这个东西,如果太复杂了都是从公司弄回来的。不太好拿出来。

有问题过来问吧。

使用特权

评论回复
6
zhushouxi|  楼主 | 2012-3-5 22:18 | 只看该作者
是的,还得摸索

使用特权

评论回复
7
GoldSunMonkey| | 2012-3-5 22:37 | 只看该作者
给分。哈哈

使用特权

评论回复
8
ifpga| | 2012-3-6 09:09 | 只看该作者
MARK, 学习之

使用特权

评论回复
9
nongfuxu| | 2012-3-6 14:07 | 只看该作者
:lol

使用特权

评论回复
10
nongfuxu| | 2012-3-6 17:42 | 只看该作者
GSM给的的例子真好,非常专业的testbench. 自叹不如!

使用特权

评论回复
11
nongfuxu| | 2012-3-6 17:46 | 只看该作者
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");

GSM,能否给一个详细使用例子?:)

使用特权

评论回复
12
GoldSunMonkey| | 2012-3-6 18:22 | 只看该作者
有问题过来问,我对VHDL的比较熟悉。

使用特权

评论回复
13
jlgcumt| | 2012-3-6 18:47 | 只看该作者
Mark

使用特权

评论回复
14
GoldSunMonkey| | 2012-3-6 20:25 | 只看该作者
Mark
jlgcumt 发表于 2012-3-6 18:47
歡迎

使用特权

评论回复
15
nongfuxu| | 2012-3-7 11:16 | 只看该作者
有问题过来问,我对VHDL的比较熟悉。

我对VHDL只晓得点皮之毛。

使用特权

评论回复
16
GoldSunMonkey| | 2012-3-7 21:39 | 只看该作者
我对VHDL只晓得点皮之毛。
nongfuxu 发表于 2012-3-7 11:16
不可能,你是全中国VHDL语言最好的人之一

使用特权

评论回复
17
zhushouxi|  楼主 | 2012-3-7 21:42 | 只看该作者
12# nongfuxu

一个fifo测试程序,测试xilinx fifo核
/********************************************
             读写时钟
********************************************/
initial
     begin
         wr_clk = 0;
          forever #5 wr_clk = ~wr_clk;  // f=100 MHz
      end
      
initial
     begin
         rd_clk = 0;
          forever #10.4 rd_clk = ~rd_clk; // f= 48MHz
      end
/********************************************
                测试程序主进程
********************************************/

initial
     begin
          rst    = 0;
          wr_en  = 0;
          rd_en  = 0;
          din    = 0;
          start  = 0;
          #20;
          reset;
          $display("reset case done!");
          case_underflow;
          $display("underflow case done!");
          case_singleRD_WR;
          $display("singleRD_WR case done!");
          case_overflow;
          $display("overflow case done!");
          reset;
          case_multiRD_WR;
          $display("multiRD_WR case done!");
          $finish;
      end
/********************************************
                  复位测试实例
********************************************/
task reset;
     begin
       rst   = 1;
         @(posedge wr_clk);
         rst   = 0;
         @(posedge wr_clk);
      end
endtask
//fifo单字节读写测试
task case_singleRD_WR;
     begin
       @(negedge wr_clk);
        wr_en = 1;                    //写使能信号维持一个写时钟周期,完成一次写操作
        din   = 16'h2a3b;
       @(negedge wr_clk);
        wr_en = 0;
       @(negedge empty);             //当fifo不为空时才能进行读操作,否则将造成下溢  
        rd_en = 1;
       repeat(3) @(posedge rd_clk);
       if(dout != 16'h2a3b)$display("single write_read error!");
       @(negedge rd_clk);            //读使能信号维持一个写时钟周期,完成一次读操作
       rd_en = 0;
       repeat(3) @(negedge rd_clk);
      end
endtask
/***************************
    fifo读下溢测试实例
***************************/
task  case_underflow;
      begin
           @(negedge rd_clk);
         rd_en = 1;
         @(negedge rd_clk);
         rd_en = 0;
         repeat(3) @(negedge rd_clk);   
      end
endtask
/***************************
fifo上溢测试实例
***************************/

task case_overflow;
      begin
         wr_en  =  1;
         for(i=0;i<515;i=i+1)
            begin
                @(posedge wr_clk);
                din  =  i;
                if(overflow)
                $display("fifo is overflow!!!");
            end
         @(posedge wr_clk);
         wr_en  =  0;
      end
endtask


/**************************
大量数据读写测试实例
**************************/
task  case_multiRD_WR;
      begin
         start = 0;
         wr_count = 1;  //地址初始化为1
         rd_count = 0;
         file1 = $fopen("wr.dat");
         for(i=0;i<2000;i=i+1)
            begin
                data2file = {$random}%16'hffff;
                $fdisplayh(file1,data2file);//将data2file写入file1
            end
         $readmemh("wr.dat",mem);
         start  = 1;
         #10000;
         start  = 0;
         $fclose(file1);
      end
endtask
//发送方控制字
always @(negedge wr_clk)
       begin:sender
           if(start)
             begin
                 if(!full)
                    begin
                      wr_en    <= 1;
                      din      <= mem[wr_count];
                      wr_count <= wr_count + 1;
                    end
                 else
                    begin
                      wr_en    <= 0;
                      wr_count <= wr_count;
                    end
              end
       end

always @ (negedge rd_clk)
       begin:reciever
           if(start)
              begin
                  if(!empty)
                    begin
                     rd_en    <= 1;
                     rd_count <= rd_count + 1;
                     @(posedge rd_clk);
                     if(dout!=mem[rd_count])
                         $display("read data error at %d ,rd=%h ,wr=%h",rd_count,dout,mem[rd_count]);
                     else
                         $display("read data correct at %d ,rd=%h ,wr=%h",rd_count,dout,mem[rd_count]);
                    end
                  else
                     begin
                      rd_en     <= 0;
                      rd_count  <= rd_count;
                     end
              end
       end

使用特权

评论回复
18
GoldSunMonkey| | 2012-3-7 21:44 | 只看该作者
这个太简单了。不过谢谢分享~

使用特权

评论回复
19
zhushouxi|  楼主 | 2012-3-7 21:46 | 只看该作者
19# GoldSunMonkey
对于初学者 还是不错的

使用特权

评论回复
20
GoldSunMonkey| | 2012-3-7 21:49 | 只看该作者
:)

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

8

主题

51

帖子

0

粉丝