Bootstrap

Testbench

作者:桂。

时间:2017-08-15  07:11:50

链接:http://www.cnblogs.com/xingshansi/p/7363048.html 


前言

  Testbench主要用于module的测试,这里仅记录一般的操作流程。

 

〇、verilog与C的区别

  本段文字出处

  RTL级的verilog其实就是常说的verilog语言中可综合的那部分,它是verilog语言的一个子集。所谓的RTL级建模,其实也就是用verilog语言去描述实际电路的行为,比如用verilog语言去描述一个ram或者是一个移位寄存器。 
  对于这个层次上的建模,最重要的思想就是“硬件意识”,也就是说,我们在写代码的时候,大脑里要先知道我们设计的是什么样的电路,然后再用verilog语言将它描述出来。其实,在出现硬件描述语言之前,电路设计都是用原理图去搭的,这种设计过程也是我们心中先有了电路,然后用一种计算机能够识别的方式进行描述,现在的verilog HDL只是相当于我们有了更先进的描述方法,但是在RTL级的设计过程还是不变----心中先有电路,再将它翻译成verilog代码。 
刚才我们说了,RTL的设计其实就是用verilog去描述电路的行为,那么,我们要怎么去理解这个“电路的行为”呢? 
第一,电路在物理上是并行工作的。 
  我们知道,在写C程序的时候,由于CPU在任一时刻最多只能执行一条指令,程序都是顺序执行的,即使是我们平时说的多任务操作系统,所谓的并行任务也只是从宏观上来说的(人感觉不到而已),但从微观上来说它仍然是顺序执行的。所以我们设计C程序时思维是串行或者说是并行的。所以看语言不能从上往下看调用逻辑,而是要抓住数据的流向,各个In/out的调用顺序。
  但是数字电路设计则不然,数字电路芯片无非是由一些门电路和触发器组成,从物理上看,在任何时刻,芯片内部各部分电路都是同时工作的。所以在设计的时候,我们需要以一种并发的思维去思考。以下面例子为例: 
例3: 

always @(posedge clk or negedge rst_n) 
begin 
if ( rst_n == 1'b0 ) 
begin 
wren <= 1’b0; 
rden <= 1’b0; 
end 
else 
begin 
wren <= a&b; 
rden <= c&d; 
end 
end 

  在综合成实际电路之后,wren和rden的赋值是同时进行的,并不会因为wren在rden前面就先对wren赋值(其实将赋值理解高低电平的传递会更好一些)。事实上,当综合成网表文件后,这种赋值的先后顺序也就不存在了。 

第二,电路行为的先后顺序是通过时钟节拍去完成的。 
  在一些时候,我们需要完成这样的功能:在STATE0状态下,当事件A来了后,去触发状态STATE1;在状态STATE1下,外部总线上读取数据;在处理完后,又进入到状态STATE2,将数据突发传送到背板总线上,并回到STATE0。 
  这就有了一种逻辑上的先后关系,但是由于我们的电路都是并行工作的,我们不能像C语言的方式将代码在一个进程中按先后顺序排放后就可以完成功能。那我们要怎么去实现呢?我们知道,我们人类去判别先后顺序是按通过时间完成的,也就是说,如果我们是基于时钟节拍去设计的话----即在节拍n事件A到来,在节拍B进入状态STATE0,在n+1到n+k节拍中每x个节拍从外部总线上取一个数,在n+k节拍后,每一个节拍将缓存在FIFO的数据放到背板总线上,就可以实现这种逻辑的先后顺序。总结起来,就是电路行为的逻辑先后顺序是通过时钟节拍去体现的,设计时可以通过将事件安排在不同的时钟节拍去完成。

  二、Testbench的基本操作

  A-基本操作

  对于testbench而言,端口与被测module一一对应,input声明为reg,output声明为wire,如果是systemveriog,则可以统一定义为logic。

1-直接赋值

initial操作:

module exam();
reg rst_n;
reg clk;
reg data;

initial
begin
       clk=1'b0;
       rst=1'b1;
       #10
       rst=1'b0;
       #500
       rst=1'b1;
end

always
begin
       #10
            clk=~clk;
end

  注意到有个#符号,该符号的意思是指延迟相应的时间单位。该时间单位由timscale决定.一般在testbench的开头定义时间单位和仿真 精度,比如`timescale 1ns/1ps,前面一个是代表时间单位,后面一个代表仿真时间精度。以上面的例子而言,一个时钟周期是20个单位,也就是20ns。而仿真时间精度的概 念就是,你能看到1.001ns时对应的信号值,而假如timescale 1ns/1ns,1.001ns时候的值就无法看到。对于一个设计而言,时间刻度应该统一,如果设计文件和testbench里面的时间刻度不一致,仿真 器默认以testbench为准。一个较好的办法是写一个global.v文件,然后用include的办法,可以防止这个问题。

2-重复操作

例如写某一个字符串,需要重复操作且字符串内容有变更,这个时候可以借助task来完成。

task load_count;
       input [3:0] load_value;
       begin
            @(negedge clk_50);
                     $display($time, " << Loading the counter with %h >>", load_value);
            load_l = 1’b0;
            count_in = load_value;
            @(negedge clk_50);
            load_l = 1’b1;
       end
endtask //of load_count

initial 
begin
   load_count(4’hA);   // 调用task
end

  其他像forever,for,function等等语句用法类似,虽然不一定都能综合,但是用在testbench里面即使是偏高级的语言也不要紧,写testbench与写普通的module思路略有差异。

3-文件输入

有时候,需要大量的数据输入,直接赋值的话比较繁琐,可以先生成数据,再将数据读入到寄存器中,需要时取出即可。用 $readmemb系统任务从文本文件中读取二进制向量(可以包含输入激励和输出期望值)。$readmemh 用于读取十六进制文件。例如:

reg [7:0]   mem[1:256]   //   a 8-bit, 256-word 定义存储器mem
initial   $readmemh ( "E:/readhex/mem.dat", mem ) // 将.dat文件读入寄存器mem中
initial   $readmemh ( "E:/readhex/mem.dat", mem, 128, 1 ) // 参数为寄存器加载数据的地址始终

  B-查看仿真结果

  对于简单的module来说,要在modelsim的仿真窗口里面看波形,就用add wave ..命令,比如,testbench的顶层module名叫tb,要看时钟信号,就用add wave tb.clk。要查看所有信号的时候,就用 add wave /*。当然,也可以在workspace下的sim窗口里面右键单击instance来添加波形。
  对于复杂的仿真,免不了要记录波形和数据到文件里面去。 这一块的资料可搜索关键词:$dumpvar

  C-testbench编写技巧

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

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

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

4).每个testbench都最好包含$stop语句,用以指明仿真何时结束。
5).加载测试向量时,避免在时钟的上下沿变化,比如数据最好在时钟上升沿之前变化,这也符合建立时间的要求。

  三、常用的时钟/复位

时钟clk以及复位rst是除了测试模块外,必须要考虑的信息。

  A-产生时钟的几种方式
(a)使用 initial方式产生占空比50%的时钟
initial
begin
Clk = 0 ;
# delay ;
forever
# (period/2)   Clk = ~ Clk ;
end

  注意一定要给时钟赋初始值,因为信号的缺省值为z,如果不赋初值,则反相后还是z,时钟就一直处于高阻z状态。产生的时钟信号如下

testbench <wbr>产生激励的一些描述方式
(b)使用 always方式
initial
Clk= 0 ;
always
# (period/2)  Clk = ~ Clk ;
(c)使用 repeat产生确定数目的时钟脉冲
initial
begin
Clk = 0 ;
repeat ( 6 ) 
# (period/2)   Clk = ~ Clk ;
end

  该例使用repeat产生 3个时钟脉冲,产生的波形如下:

testbench <wbr>产生激励的一些描述方式
(d)产生占空比非50%的时钟
initial
Clk = 0 ;
always
begin
# 3 Clk=~Clk;
#2 Clk=~Clk;
end

testbench <wbr>产生激励的一些描述方式

  B-产生复位信号的几种形式

(a)异步复位
initial
begin
Rst = 1 ;
# 100 ;
Rst = 0 ;
# 500 ;
Rst = 1 ;
end
testbench <wbr>产生激励的一些描述方式
(b)同步复位信号
initial
begin
Rst = 1 ;
@( negedge Clk) ; //等待时钟下降沿
Rst = 0 ;
# 30 ;
repeat(3)@( negedge Clk) ; //等待3个时钟下降沿
Rst = 1 ;
end
testbench <wbr>产生激励的一些描述方式
 
参考
  • http://blog.csdn.net/wordwarwordwar/article/details/53885209
;