Testbench学习笔记(一)
书写testbench是数字电路设计中不可或缺的一项设计方法,主要是提供的是激励。尽管现在各种开发工具都通过绘制波形图的方法生成测试激励,测试书写的代码,但是其不可移植性,不可通用性,还有有些功能无法是实现,如监视变量的值的变化,显示数据的状态等。
一个完整的testbench包含下列几个部分:
(1)module的定义,一般无输入输出端口。
(2)信号的定义,定义哪些是你要输入,输入的定义为reg类型,输出的定义为wire型
(3)实例化待测试的模块
(4)提供测试激励
1.如何书写测试激励;
(1)时钟信号的产生
initial
begin
clk = 0;
forever
#clk_period/2 clk = ~clk;
end
或者是
always
begin
#clk_period/2 clk = 0;
#clk_period/2 clk = 1;
end
产生时钟的原理,是利用always或者是initial + forever产生不断重复的信号,构成时钟信号。
(2)复位信号的产生
复位信号就是在复位电平下延时一段时间,然后再将复位电平信号取反即可。如:
initial
begin
rst = 0;
# 100;
rst = 1;
end
在实际应用将其封装为task,使用时掉调用即可。
调用如下:
initial
begin
sys_time(100); //复位100个时间单位
.....
end
任务的定义如下:
task sys_rst ;
input [10:0] rst_time; //调用task的时候,将参数赋值给rst_time
begin
rst = 0;
#rst_time;
rst_time = 1;
end
endtask
(3)产生一种复杂的信号。如下面的实例产生一个vs和hs信号
//-------------------------------------------------------------------------------------
parameter n = 10;
//-------------------------------------------------------------------------------------
initial
begin
cmos_hs = 1'b0; // time 0 时刻
cmos_vs = 1'b0;
repeat(n) //重复n次
begin
# 200 cmos_vs = 1'b1; // time 200
repeat(10*n)
begin
#100 cmos_hs = 1'b1; //time 300
#100 cmos_hs = 1'b0; //time 700
end
#100 cmos_vs = 1'b0;
end
end
该程序段可以生成10个vs信号,每个vs信号下有100个hs信号。由于initial信号只能执行一次,所以为了得到有限的重复信号,可以采用repeat关键词得到。
这样基本上就可以完成一些简单的测试testbench了。
2.如何将我们的测试尽可能的简单明了化
用Modelsim对Verilog HDL进行仿真的人都会知道,看一大堆波形会很麻烦,如果代码变量很多,很复杂,出了问题都不知道问你在哪里,或者看了半天,发现图形是个错的。运用合适的方法将自己需要的变量以文本方式显示,监视变量的变化不很好嘛?
下面介绍几种常用的系统函数
(1)$time
作用:返回所在模块的仿真时间,可以查看信号的出现的时间,用来把握信号的时序。
如:$display(''the time is %t'',$time) ;//显示当时的时间
(2)$display
作用: 将需要显示的内容在命令栏显示出来
如: $display("the signal is % d",ad); //将ad信号以十进制的方式显示出来
(3)$monitor
作用:监视变量的变化,一旦变量变化,则将变量显示出来
如:$monitor ("at time is %t and the signal is %b\n",$time , signal) ;
(3) 文件操作类
$fopen
作用:打开一个文件面,对文件的操作
$fdisplay
作用:在打开的文件里,写入显示的内容
$fmonitor
作用:在打开的文件里,写入监视的变量变化时的内容
$fclose
作用:关闭当前的内容
如:initial
Begin :block //可以在内部声明局部变量
Integer out_file;
out_file = $fopen("data.out","w") ; //打开data.out这个文件后,从第一行开始写,如果该文件没有,则首先创建该文件,然后再写。打开文件后返回out_file这个文件整形指针
$fdisplay(out_file,"at the time is %t ",$time);
.....
$fmonitor(out_file,"at the time is %t and the signal is %b\n ",$time ,signal);
......
$fclose(out_file);
End
为什么要写testbench?
我们为什么要写testbench?
经常看到论坛里有人问我们为什么要写testbench,总是觉得不好回答,下面是整理出来的一些理由供大家参考。
与写testbench相对应的功能手段还有画波形图,两者相比,画波形图的方法更加直观和易于入门,那为什么我们还要写Testbench呢?原因有以下五点:
第一,画波形图只能提供极低的功能覆盖。
画波形无法产生复杂的激励,因此它只产生极其有限的输入,从而只能对电路的极少数功能进行测试;而testbench以语言的方式描述激励源,容易进行高层次的抽象,可以产生各种激励源,轻松地实现远高于画波形图所能提供的功能覆盖率。以PCI转以太网电路设计为例,该设计并不复杂,但却已经需要考虑多种情况:PCI的配置读写、存储器读写等操作;以太网的短包、长包等。如果这些激励都用画波形图完成,其工作量是难以想象的;用testbench则可以轻松完成这些工作。
第二,画波形无法实现验证自动化。
对于规模设计来说,仿真时间很长,如果一个需要仿真一天设计在检错时仅通过画波形图来观测,将几乎不能检查出任何错误;而testbench是以语言的方式进行描述的,能够很方便地实现对仿真结果的自动比较,并以文字的方式报告仿真结果。我们甚至可以在下班时开始仿真,然后第二天早上上班时再查看验证结果。
第三,画波形图难于定位错误。
用画波形图进行仿真是一种原始的墨盒验证法,无法使用新的验证技术;而testbench可以通过在内部设置观测点,或者使用断言等技术,快速地定位问题。
第四,画波形的可重用性和平台移植性极差。
如果将一个PCI转100Mb以太网的设计升级到PCI转1000Mb以太网,这时原来画的波形图将不得不重新设计,耗费大量的人力物力及时间;但若使用testbench,只需要进行一些小的修改就可以完成一个新的测试平台,极大地提高了验证效率。
第五,通过画波形的验证速度极慢。
Testbench的仿真速度比画波形图的方式快几个数量级,在Quartus下画波形需半个小时才能跑出来的仿真结果,在ModelSim下使用testbench可能只需几秒钟就可以完成。
所以,在设计中除了那些极简单的设计(如调用厂商提供的MegaCore),推荐通过写testbench的方法来做功能验证。
=================================概念=========================
testbench是一种验证的手段。
首先,任何设计都是会有输入输出的。但是在软环境中没有激励输入,也不会对你设计的输出正确性进行评估。那么此时便有一种,模拟实际环境的输入激励和输出校验的一种“虚拟平台”的产生。在这个平台上你可以对你的设计从软件层面上进行分析和校验,这个就是testbench的含义。
==============================================================
======================初步认识=================================
就初学而言,testbench更像一个激励的产生器。
举例:一个ram,可能有几个input和output。分别列在下面。
clk,时钟输入
addr,地址输入
wen,写使能
data,数据输入
然后还有一个dataout的数据输出。
那么你可以写一个文件,给clk,addr,wen,data送入你预想的一些信号,然后观察q的输出,看看ram是否工作正常。那么这个文件从一定意义上可以叫做"testbench"。
联想(帮助理解):从quartus里面你仿真,你可能对着那个画图一样的东西画上输入,然后编译以后看他的输出。对吧。那么在modelsim里面,我告诉你,可以不用画图了~,你只需要按照一定规则写一个.v或者.vhd的文件,这个文件可以给你的设计提供你预想的输入。这个就是testbench的文件。然后在modelsim这个特定的软件环境下,这个软件能根据你的代码给你的设计提供输入,又可以把你设计的输出在屏幕上显示出来给你debug。那么这个时候,一个在modelsim上的testbench就完成了。
狭义的总结一下:FPGA的testbench就是一个.v(verilog)或者.vhd(vhdl)的文件。这个文件能给你的设计提供激励,并能在一些专用的软件中提供良好的debug接口。这个就是一个testbench。
==============================================================
=====================高级应用================================
关于testbench的高级应用。
刚才说了初步的testbench。其实testbench是verification(验证)中的一个手段。
验证是什么呢?举例:做鱼了,你往里面加了调料,然后再尝尝味道,这个就是验证的过程。同样你可以分成几个部分,一条鱼,好比你的设计,然后你给他一定的激励,也就是调料啦。然后你再尝一尝,看看鱼是不是达到你想要的味道了。那就是一种验证的手段,如果淡了。那么加点盐,再尝尝,这个就是反复验证。
testbench里面包含了三个东西:
1、激励生成。也就是我们刚才初级时候说的所谓的“testbench”。英文么就是simulator,这个只用来生成输出,他自己没有输入,只是按照一定的规律去给你的设计激励,激励通过设计的输入端口送到你的设计中。其余的事情不管。这里的激励,都是预先设想好的,比如根据某个协议,或者某种通信方式传递。
2、你的设计。英文可以叫做DUT:design under testbench或者DUV:design under verification。当然咯。这个是你主要目标。
3、输出校验。校验你的输出。英文叫markerboard,他所管的事情就是,接收你设计的输入,然后通过校验,找出对应的问题。然后报错,或者统计错误。等等。通俗的讲,你设计它就是把你自己解脱出来,让他来帮你找错误。他输出给你的可能就是通过打印啊,通知啊,等等方法了解你设计的正确性。
那么你有可能问了,这个东西用verilog或者VHDL能写么,modelsim里能用么?的确是可以的,有写甚至可以用c的代码通过程序接口来转换到modelsim里面来帮助验证。
========================高级应用结束==========================
最后小说两句:testbench是一个平台,帮助你从软件方面验证的。对于这个概念不需强求,等你自己的验证写多了,自然而然就会了解其中深刻的含义。先开始慢慢的写一些激励,然后再写写校验。到时候你收获的东西自然而然的能帮助你理解testbench和verification 。
测试平台是个没有输入输出端口的模块。仿真在一个模块设计中是很关键的步骤,而testbench是仿真的很好工具。
与待测模块接口
与输入端口相连接的变量定义为reg
与输出端口相连的定义为wire
initial块中初始化变量,必须的。
用$stop或$finish暂停或结束仿真
wait(z==1’b1);//等待变量值改变,变量可以是待测试模块的输出或者内部变量
时钟产生:
always # 10 clk =~clk;产生时钟
initial repeat(13) #5 clk =~clk; //控制只产生13个时钟。
同步数据:
initial forever @ (posedge clk) #3 x = $random;
为了降低多个输入同时翻转的概率,对时序电路的输入一般采用素数作为时间间隔。
同步显示:
l initial$monitor (“%d is changed at %t”,MUT.current,$time);//
一般在initial中调用,采用$monitor显示模块MUT内部current的值以及发生变化的时间,$monitor是一个后台运行任务函数,多个模块下,任意时间只能有一个$monitor起作用,可用$monitoron $monitoroff来控制。
l always @(z) $display(“Output changed at %t to %b”,$time,z);当z发生变化输出z值以及变化时间,自动换行。
l always @(z) $strobe(“Output changed at %t to %b”,$time,z);//仿真结束后显示输出,查看非阻塞赋值变量的值。
随机数据
initial repeat(5) #7 x = $random;
a = $random`; //产生-59~59之间随机数
a = {$random}`; //产生0~59之间随机数
产生随机时间间隔
always begin
t= $random;
#(t) x = $random;
end
初学TestBench
`timescale 1ns/10ps //单位时间/精度
`include "adder.v"
module adder_testbench;
reg a,b;
wire sum,cout;
adder adder_t( //调用待测模块
.sum(sum),
.c(count),
.a(a),
.b(b)
);
initial begin
a = 0; //初始值a=0
forever #20 a = ~a; //每经过20个单位时间,a取反
end
initial begin
b = 0; //初始值b=0
forever #10 b = ~b; //每经过10个单位时间,b取反
end
initial begin
$monitor ($time,,,"%d + %d = {%d,%d}",a,b,cout,sum); //监控输出
#40 $stop;
end
小结:
$monitor 输出打印显示
$stop停止当前仿真
$finish结束仿真,询问是否退出ModelSim
时钟产生方法:
1.用intial语句
reg clock;
initial begin
clock = 0;
forever #10 clock = ~clock;
end
2.用always语句
reg clock;
initial
clock = 0;
always
#10 clock = ~clock;
以上写法产生的时钟如下:
如何编写testbench的总结(转)
如何编写testbench的总结
如何编写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命令使用库文件或库目录 3.Verilog Testbench信号记录的系统任务: 1). SHM数据库可以记录在设计仿真过程中信号的变化. 它只在probes有效的时间内记录你set probe on的信号的变化. 在记录信号或者波形时需要指出被记录信号的路径,如:tb.module.u1.clk. ……………………………………………………………………………………………………… 关于信号记录的系统任务的说明: 在testbench中使用信号记录的系统任务,就可以将自己需要的部分的结果以及波形文件记录下来(可采用sigalscan工具查看),适用于对较大的系统进行仿真,速度快,优于全局仿真。使用简单,在testbench中添加:initial begin $shm_open("waves.shm"); $shm_probe("要记录信号的路径“,”AS“); #10000 $shm_close; 即可。 4. ncverilog编译的顺序: ncverilog file1 file2 .... 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 |
task test_task;//task的定义,以task关键字开始,紧接后面是task的id,名字
input [1:0] a; //task的输入输出定义,用来和外面的变量进行交换,是通向外面的接口
output [1:0] b;
begin
b = ~a;
end
endtask
reg [1:0] task_a;
reg [1:0] task_b; //因为task必须在过程语句中调用,所以其实参必须为reg类型的
initial
begin
task_a = 2'b0;
test_task (task_a,task_b);//task的调用,直接调用,没有什么像module一样例化的东西
#10 task_a = 2'b10;
end
initial
begin
#20 task_a = 2'b11;
test_task (task_a,task_b);//同样可以改变task内部的值,和module不同,这是同一个task
#10 task_a = 2'b10;
end
reg [1:0] task1_a;
reg [1:0] task1_b;
task test_task1;
output [1:0] b;
assign b = ~task1_a;//可以使用全局变量,不一定是task内部的变量
endtask
initial
begin
task1_a = 2'd0;
#1 test_task1(task1_b);
#10 task1_a = 2'd1;
#1 test_task1(task1_b);
#10 task1_a = 2'd2;
#1 test_task1(task1_b);
#10 task1_a = 2'd3;
#1 test_task1(task1_b);
end
task和module有点类似,都可以通过输入输出来和外面发生关系,这个时候module可以例化多次,这样就是不同的单元,和task
还是有区别的。
1,task只能定义在module内部,不能单独在一个文件中,不能定义在module外面。
2,在task调用的是必须在过程性语句内部使用,initial,begin ... end
3,task可以没有参数,直接使用全局变量来实现功能。
4,可以使用延时控制,可以调用其他task和函数。
函数:可以调用函数,不能调用任务,不可有时序控制。
function [1:0] invert; //只有一个返回值,invert,寄存器类型的
//input [1:0] a; //可以有一个输入,或者多个,或者没有输入
//begin
invert = 2'd3;
//end
endfunction
reg [1:0] fun_a;
reg [1:0] fun_out;
initial
begin
fun_a = 2'd2;
fun_out = invert();//(fun_a);//调用函数,函数返回一个值,必须在过程性语句中调用。
#50 fun_a = 2'd1;
fun_out = invert();//(fun_a);
#50 fun_a = 2'd0;
fun_out = invert();//(fun_a);
#50 fun_a = 2'd3;
fun_out = invert();//(fun_a);
end
task test_task1; //任务调用函数
output [1:0] b;
b = ~add(task1_a);
endtask
function [1:0] invert;//函数调用函数
input [1:0] a;
invert = ~add(a);
endfunction
//显示和文件操作
integer i;
initial
begin
a1 = 0;
for(i=0;i<100;i=i+1)
begin
if(a1 == 8'd50)
begin
$write("a1 is %d",a1); //显示
$write("\n\n\n\n\n\n");
$fwrite(f_id,"a1 is %d",a1);//写入文件
$fwrite(f_id,"\n\n\n\n\n\n");
end
else if(a1 == 8'd70)
begin
$display("a1 is %d\n\n",a1);//显示
$fdisplay(f_id,"a1 is %d\n\n",a1);//写入文件
end
else if (a1 == 8'd90)
begin
$fclose(f_id);
end
#2 a1 = a1 +1;
end
end
initial
begin
$monitor("test %d\n",a1);//变量变化就会显示
end
integer f_id;
initial
begin
f_id = $fopen("./test.txt");//打开一个文件
end
initial
begin
$strobe("tes1t %d\n",a1); //在时间步0结束的时候显示这一句
end
integer cool;
initial
begin
cool = 4; //这是时间步0要执行的,
$strobe ("strobe cool1 is %d at time %t\n",cool,$time);//这是时间步0要执行的,在时间步的最后显示
$display("cool1 is %d at time %t\n",cool,$time); //这是时间步0要执行的,
#4 cool = 8; //这是时间步4要执行的,
$strobe ("strobe2 cool1 is %d at time %t\n",cool,$time);//这是时间步4要执行的,
$display("cool2 is %d at time %t\n",cool,$time); //这是时间步4要执行的,
标签: testbench 模型
testbench的一般模型
testbench的实现方法多样,而且还不断涌现出新方法,这些都是人们在为更好的验证设计做的努力。如VHDL,verilog,systemC,systemverilog均可以,但是真正的实际应用中绝对不是单独应用,而是将他们结合起来,使你的验证更方便,更全面。
由于系统验证的庞大,我们还是从最简单的,最熟悉的上手,就先单独用VHDL语言来写仿真语言,当然VHDL语言我们已经比较熟悉了,但是有个比较大的区别是,我们以前都是尽量学习能够综合的语言,但是在仿真中经常会用到一些行为级描述的语言,他们是不能被综合成逻辑的,但是却绝对可以让我们的验证更加高效方便,但是我们也不单独来讨论VHDL中哪些语言是能够综合,哪些语言是不能综合的,重点将会放在testbench的一般结构,编写testbench的一般思想,以及更多的是实际接触,以实际的例子与前面的可综合逻辑相结合达到完整的系统设计的上的!
testbench的几种思路:
一、只在testbench中实例化DUT(design under test),激励输入是在testbench中临时产生的,只能用于简单逻辑。优点:简单,易操作。缺点:复用性差 ,效率低 模型如图1所示
二、DUT的输入由单独的一个文件产生,在testbench中实例化两上entity,可以复杂输入,简单输出的模块。模型如图2所示
三、DUT的输入与测试输出各由单独文件产生,在testbench由三个实例化模块产生,用于具有复杂输入以及输出的模块,模型如图3所示。
四、可以根据仿真输出来修改输入激励的,可以自动通过输出来修改输入,使验证更加准确。如图4所示的模型
五、有文件做为testbench的输入,输出的模型。仿真中需要顺序的输入大量数据,以及接收相应的数据,可以通过从文件中读入数据,然后将产生的数据存入文件,使复杂系统验证更加方便。模型如图5所示。
六、可以将激励同时输入自己设计的模块和已经验证了相同模块,比较二者输出。模型如图6所示。
图1
图2
图3
图6
本文来源于:电子工程世界
描述测试信号的变化和测试过程的模块叫做测试平台(Testbench),它可以对电路模块进行动态的测试。通过观测被测试模块的输出信号是否符合要求,可以调试和验证逻辑系统的设计和结构是否正确,便于发现问题并修改。
Testbench用于测试模块的示意图如图所示:
由示意图可知,Testbench要对被测模块进行测试,需要产生被测模块所需的激励信号(比如时钟信号,复位信号等),这个就像我们用Quartus波形仿真时拖波形一样,只是Testbench里需要我们用代码来实现波形的变化。
产生的激励信号需要与被测模块对口(比如产生的时钟信号要送入时钟输入口,产生的复位信号要送入复位输入口等),如何实现对口,这就需要对被测试模块的例化来实现。例化的写法如下:
被测模块名 例化进Testbench后的模块名
(
.被测模块输入口 Testbench产生的激励信号,
.被测模块输出口 Testbench里用来显示输出的信号
);
上面的示意图对应的例化写法为:
被测模块名 例化进Testbench后的模块名
(
.Input_1 (In_1),
.Input_2 (In_2),
.Input_3 (In_3),
.Output_1 (Out_1),
.Output_2 (Out_2),
.Output_3 (Out_3)
);