开篇:润墨网以专业的文秘视角,为您筛选了一篇Testbench设计技巧研究范文,如需获取更多写作素材,在线客服老师一对一协助。欢迎您的阅读与分享!
摘要:testbench的设计是仿真和验证数字逻辑设计的标准方法也是设计过程中的必要环节,简单高效的Testbench可以帮助设计者完成复杂工作。该文主要从Testbench的功能结构入手,研究了如何将文件化分为功能模块和实现模块的设计技巧,并通过一个FIFO的实例将这些功能模块组合成一个完整的Testbench。
关键词:Testbench;仿真;验证;功能模块
中图分类号:TP311文献标识码:A文章编号:1009-3044(2009)13-3560-04
1 引言
Testbench是数字设计的仿真验证的主要手段。编写Testbench的主要目的是为了对使用硬件描述语言设计的电路仿真验证,测试设计电路的功能、部分性能是否与预期的目标相符,从而对设计的正确性进行验证。由于设计的规模越来越大也越来越复杂,数字设计的验证已经成为一个日益困难和繁琐的任务。
本文结合设计工作实践,研究了Testbench的功能结构、常用功能模块的实现方法以及一些测试技巧以提高验证效率,期望能对广大设计者的仿真和验证工作有所帮助。
2 Testbench的功能结构
Testbench是由HDL硬件描述语言编写的文件与一般的设计文件类似,从图1总可以看出Testbench应该是待测试设计(Design Under Test)的顶层设计。
下面以示例性质的Testbench.v来说明文件的一般功能结构。
// filename: Testbench.v
`define MACRO 20 // 宏定义
`include "dut.v"//包含待测试模块,如果在同一目录下省略
`timescale 1n/10ps//指定仿真时间精度
module testbench(); //定义仿真模块名
reg a; //声明线型和寄存器型变量
wire b;
parameter test_parameter =10; //定义相关测试参数
Dut dut(.a(a),.b(b)); //实例化DUT
initial begin //仿真激励信号
a = 0 ;
#30 a = 1 ;
end
#MAX $display("At time %d a is %b", $time, a);//利用系统任务显示调试信息
`undef MACRO //取消宏定义,防止冲突发生。
endmodule //模块结束
从Testbench.v文件可以看出Testbench完成了以下任务:在测试中实例化设计Design Under Test(DUT);仿真测试的设计(DUT)激励信号;通过使用仿真激励信号测试DUT;仿真结果输出到终端或波形窗口以观察结果。
2.1 仿真激励信号
首先我们知道Testbench是一个自封闭顶层设计,没有输入输出。所以为了实现测试模块的正常工作,Testbench就必须仿真激励信号。激励信号一般可以分为:系统时钟信号;复位信号;具体DUT仿真信号。
2.2 系统时钟信号
使用系统时钟控制逻辑工作的时序逻辑设计必然需要一个时钟。 重复的时钟信号可以很容易地用Veilog代码实现。
■
除了这种常用的系统时钟信号之外,有时候我们在设计中会用到占空比不是50%的时钟,比如用always语句实现占空比40%的时钟;如果需要产生固定数目的时钟脉冲,可以在initial语句中使用repeat语句来实现,代码如下:
■
2.3 复位信号
除了时钟信号,通用的激励信号就是复位信号了。由于复位信号不是周期信号,我们可以用intial来产生。
■
2.4 DUT激励信号
要得到测试验证的结构,除了要为测试的设计模块提供时钟和复位信号,我们还要编写一系列DUT的信号仿真。一般的写法就是在initial语句模块中根据时间顺序向仿真向量赋值,因为在每一个进程块或初始块中事件是按照代码书写的顺序依次执行的,就是说各个模块是在仿真的零时刻同时启动的。
我们可以将复杂的激励序列分解为多个模块编写以保障代码的可读性和可维护性,比如将一些常用操作封装成任务或者函数。但需要注意的是,激励信号在不同的模块的赋值写法只可以用于仿真,无法进行综合。
2.5 系统任务
在我们编写Testbench时,一些系统任务可以帮助我们显示调试信息,例如使用 $display()可以显示文字信号;使用$monitor()监控参数的变化。为了仿真数据保存下来,可以将数据写入文件,代码如下:
integer WriteFile;//定义文件指针
WriteFile = $fopen(" WriteFile.txt ");//打开文件
$fdisplay(WriteFile, " @%d\n%h ", addr, data ); //将数据写入文件
$fclose(WriteFile); //关闭文件指针
需要测试激励时,我们可以利用$readmemh()和$readmemb()从文件中读取数据或者用$random()产生测试数据。代码如下:
reg [7:0] DataSource [0:31];//定义一个二维数组
$readmemh("Data_File.txt", DataSource); //将Data_File.txt文件中的数据读取到DataSource中Data_File.txt文件的数据格式如下:
@01
12 2A 1F
@0A
0C 01 1B
$readmemh()读取16进制的数据而$readmemb()读取2进制的数据;@表示地址采用16进制,不同地址的数据可以用空格分开,数据内容可以是x或z而未定义的数据采用默认值x。上例中读取文件后DataSource[0]=xx,DataSource[1]=12,DataSource[2]=2A。
用$random()产生测试激励数据。例如:
DataOut = {$random}%256; //产生0~255的随即数据
能够用于 testbench 中的系统任务和函数有很多,它们的使用方法大同小异,非常简单。
2.6 仿真实例
前面已经介绍了实现仿真测试的功能模块,下面是如何运用这些模块组成一个完整的Testbench。我们就以简单的FIFO为实例,说明testbench的编写。FIFO
`define DATA_WIDTH_8 8//定义一些常用宏
`define DELAY_PERIOD 80
`define FIFO_SIZE 16
`include"fifo.v"//包含测试文件
`timescale 1ns/10ps //指定时间精度
module testbench();//定义仿真模块名
reg clk;//时钟信号功能模块
parameter clk_period = 20;
initial
clk = 0;//初始化时钟
always #(clk_period / 2) clk = ~clk;
reg [(`DATA_WIDTH_8 -1): 0]data_in; //FIFO数据输入
regpush,pop ; //FIFO 控制信号
wire[(`DATA_WIDTH_8- 1): 0] data_out; //FIFO数据输出
wirefull, empty; //FIFO 状态信号
reg [(`DATA_WIDTH_8- 1): 0] data_buf [(`FIFO_SIZE-1): 0]; //数据缓存
integer index;
reg rst; //复位信号
task SystemReset//复位任务
begin
rst = 1;
#(`DELAY_PERIOD *0.2)rst = 0;
#(`DELAY_PERIOD *0.6)rst =1;
end
endtask
task WriteFIFO;
input [(`DATA_WIDTH_8- 1): 0] data;
begin
data_in=data;
#40 push=1; //
#200push=0;
#40; end
endtask
task ReadFIFOCompare;
input [(`DATA_WIDTH_8 -1): 0] data;
begin
#40 pop=1; //
#200pop=0;
if (data_out != data)
$display($time,"Error: Data retrieved (%h) not match the one stored (%h). \n", data_out, data);
#40;
end
endtask
task ResetFIFO;
begin
#40 rst=0;
#40 rst=1;
end
endtask
fifo dut(.clk( clk ), .rst( rst ), .data_in( data_in ), .data_out( data_out ), .push( push ),.pop( pop ),
.full( full ), .empty( empty ));
initial begin //仿真开始
$display($time, ">");
push = 0;
pop =0;
SystemReset;
//连续写FIFO仿真
index = 0;
repeat(`FIFO_SIZE) begin
data_buf[index]=$random;
WriteFIFO(data_buf[index]);
index = index + 1;
end
if (full) $display($time,"Error: FIFO full, full should be low.\n");
repeat(2) WriteFIFO($random);
#200
//连续读 FIFO 仿真
index=0;
ReadFIFOCompare(data_buf[index]);
if (~full) $display($time,"Error: FIFO not full, full should be high.\n");
repeat(`FIFO_SIZE) begin
index = index + 1;
ReadFIFOCompare(data_buf[index]);
end
if (empty) $display($time,"Error: FIFO be empty, empty should be low.\n");
repeat(2) ReadFIFOCompare(8'bx);
ResetFIFO;
//写后读 FIFO 仿真
repeat(`FIFO_SIZE*2)
begin
data_buf[0] = $random;
WriteFIFO(data_buf[0]);
ReadFIFOCompare(data_buf[0]);
end
ResetFIFO; //异常操作 仿真
ReadFIFOCompare(8'bx);
WriteFIFO(data_buf[0]);
ReadFIFOCompare(data_buf[0]);
$stop;
$display($time, ">");
end
initial begin
$timeformat(-9,1,"ns",12);
end
`undef DATAWIDTH_8//取消宏定义
`undef DELAY_PERIOD//
`undef FIFO_SIZE
endmodule
以上的测试中实例化了设计,系统时钟采用了独立的逻辑模块编写,系统复位使用了task封装,然后是激励信号。在编写测试激励时,除了注意对实际可能存在的各种情况的覆盖外,还要有意针对非常情况下的操作进行测试。以上实例中,进行了FIFO读空后继续读取、FIFO写满后继续写入、FIFO复位后马上读取等操作的测试。测试激励通常会有一些复杂操作需要反复进行,如对FIFO的读写操作,可以将这些重复操作用task封装,提高测试的效率并使程序更易于维护。$stop命令使仿真器停止测试的仿真(所有测试设计中都应该包含一个停止命令)。
3 总结
由于现在设计的规模日趋庞大,设计的验证工作也变得十分艰巨,Testbench就是设计者提高验证效率的有利工具。在编写Testbench时使用Verilog HDL语法中所有initial, always, assign等并行执行的语法结构将不同的功能划分开,而在需要反复操作的测试由task进行封装,有利于设计维护和复用;同时利用系统任务显示调试信息或仿真激励信号。
参考文献:
[1] 吴继华,王诚.Verilog设计与验证[M].北京:人民邮电出版社,2006.
[2] Hamid M.Writing Efficient Testbenches[Z].Xilinx.2001.
[3] 李瑛,张盛兵,高德远.Verilog Testbench 设计技巧和策略[J].计算机工程与应用,2003(10):128-130.