简单的testbenches
简单的testbenches例化被测模块,然后为其提供测试激励。Testbench输出以图形方式显示在仿真工具的波形窗口上,或者作为文本发送到用户的终端或文件。下面是一个表示移位寄存器的简单Verilog设计:
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
以下的testbenches例化了上面的移位寄存器模块:
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
上面的testbenches例化了被测设计,设置了时钟,并提供了测试刺激。所有过程块从仿真的零时开始,并且是并发的。井号(#)指定应用下一个刺激之前的仿真时间延迟。 s t o p 命令指示仿真工具停止 t e s t b e n c h e s 仿真 ( 所有 t e s t b e n c h e s 都应该包含一个停止命令 ) 。最后, stop命令指示仿真工具停止testbenches仿真(所有testbenches都应该包含一个停止命令)。最后, stop命令指示仿真工具停止testbenches仿真(所有testbenches都应该包含一个停止命令)。最后,monitor语句将ASCII格式的结果回显给屏幕或文本文件。
上面这个testbench算是一个比较经典且简单的测试结构,被测模块、测试激励、时钟和结果监控都有了,可以作为一个testbench的一般模板来使用。
自动化验证
建议自动化验证测试结果,特别是对于较大型的设计。自动化验证减少了检查设计正确性所需的时间,并将人为出错的可能性降至最低。自动化testbenches验证常用的方法有:
- 数据库比较。首先,创建一个包含预期输出的数据库文件。然后,获取仿真输出,并与文件中的参考值与实际结果进行比较(可借助脚本工具)。但是,由于没有提供从输出到输入文件的指针,这种方法的缺点是很难跟踪错误来源。
- 波形的比较。波形比较可以自动进行,也可以手动进行。该方法使用testbenches比较器将预期波形与testbenches的输出波形进行比较。Xilinx HDL bench工具可以实现这一自动化功能。
- 自检Testbenches。自检Testbenches在运行时,将实际结果和预期结果进行实时比对。因为可以在测试台中构建有用的错误跟踪信息来显示设计失败的地方,所以调试时间被显著缩短。
自检Testbenches是通过在测试台文件中放置一系列期望的向量来实现的。这些向量在定义的运行时间隔与实际模拟结果进行比较。如果实际结果与预期结果匹配,则仿真成功。如果结果没有匹配预期,则Testbenches会报告差异的地方。
实现自检Testbenches对于同步设计来说更简单,因为预期和实际结果可以在一个时钟边缘或每“n”个时钟周期之后进行比较。比较方法也取决于设计的性质。例如,内存I/O的Testbenches应该在每次向内存位置写入新数据或从内存位置读取新数据时检查结果。如果一个设计使用了大量的组合块,在指定预期结果时则必须考虑组合延迟。
在自检Testbenches中,预期输出将与定期运行时的实际输出进行比较,以提供自动错误检查。这种技术适用于中小型设计。然而,由于可能的输出组合随着设计的复杂性呈指数级增长,为大型设计编写一个自检Testbenches就变得更加困难和耗时。
下面是用Verilog编写的简单的自检Testbenches的例子:
在例化被测模块后,将设定预期的结果。在后面的代码中,将预期结果和实际结果进行比较,并将结果反馈给终端。如果每一次结构都匹配,就会显示“end of good simulation”消息。如果发生不匹配,则报告一个错误以及不匹配的期望值和实际值。
`timescale 1 ns / 1 ps
module test_sc;
reg tbreset, tbstrtstop;
reg tbclk;
wire [6:0] onesout, tensout;
wire [9:0] tbtenthsout;
parameter cycles = 25;
reg [9:0] Data_in_t [0:cycles];
// /
// Instantiation of the Design
// /
stopwatch UUT (.CLK (tbclk), .RESET (tbreset), .STRTSTOP (tbstrtstop),
.ONESOUT (onesout), .TENSOUT (tensout), .TENTHSOUT (tbtenthsout));
wire [4:0] tbonesout, tbtensout;
assign tbtensout = led2hex(tensout);
assign tbonesout = led2hex(onesout);
///
//EXPECTED RESULTS
///
initial begin
Data_in_t[1] =10'b1111111110;
Data_in_t[2] =10'b1111111101;
Data_in_t[3] =10'b1111111011;
Data_in_t[4] =10'b1111110111;
Data_in_t[5] =10'b1111101111;
Data_in_t[6] =10'b1111011111;
Data_in_t[7] =10'b1110111111;
Data_in_t[8] =10'b1101111111;
Data_in_t[9] =10'b1011111111;
Data_in_t[10]=10'b0111111111;
Data_in_t[11]=10'b1111111110;
Data_in_t[12]=10'b1111111110;
Data_in_t[13]=10'b1111111101;
Data_in_t[14]=10'b1111111011;
Data_in_t[15]=10'b1111110111;
Data_in_t[16]=10'b1111101111;
Data_in_t[17]=10'b1111011111;
Data_in_t[18]=10'b1110111111;
Data_in_t[19]=10'b1101111111;
Data_in_t[20]=10'b1011111111;
Data_in_t[21]=10'b0111111111;
Data_in_t[22]=10'b1111111110;
Data_in_t[23]=10'b1111111110;
Data_in_t[24]=10'b1111111101;
Data_in_t[25]=10'b1111111011;
end
reg GSR;
assign glbl.GSR = GSR;
initial begin
GSR = 1;
// ///
// Wait till Global Reset Finished
// ///
#100 GSR = 0;
end
//
// Create the clock
//
initial begin
tbclk = 0;
// Wait till Global Reset Finished, then cycle clock
#100 forever #60 tbclk = ~tbclk;
end
initial begin
// //
// Initialize All Input Ports
// //
tbreset = 1;
tbstrtstop = 1;
// /
// Apply Design Stimulus
// /
#240 tbreset = 0;
tbstrtstop = 0;
#5000 tbstrtstop = 1;
#8125 tbstrtstop = 0;
#500 tbstrtstop = 1;
#875 tbreset = 1;
#375 tbreset = 0;
#700 tbstrtstop = 0;
#550 tbstrtstop = 1;
// /
// simulation must be halted inside an initial statement
// /
// #100000 $stop;
end
integer i,errors;
///
///
// Block below compares the expected vs. actual results
// at every negative clock edge.
///
///
always @ (posedge tbclk)
begin
if (tbstrtstop)
begin
i = 0;
errors = 0;
end
else
begin
for (i = 1; i <= cycles; i = i + 1)
begin
@(negedge tbclk)
// check result at negedge
$display("Time%d ns; TBSTRTSTOP=%b; Reset=%h; Expected
TenthsOut=%b; Actual TenthsOut=%b", $stime, tbstrtstop, tbreset,
Data_in_t[i], tbtenthsout);
if ( tbtenthsout !== Data_in_t[i] )
begin
$display(" ------ERROR. A mismatch has occurred-----");
errors = errors + 1;
end
end
if (errors == 0)
$display("Simulation finished Successfully.");
else if (errors > 1)
$display("%0d ERROR! See log above for details.",errors);
else
$display("ERROR! See log above for details.");
#100 $stop;
end
end
endmodule
这种简单的自检Testbenches设计可以移植到任何测试用例中——当然,预期输出值和信号名称必须修改才能重用。如果不需要在每个时钟边缘进行检查,则可以根据需要修改for循环。
如果仿真成功,终端屏幕会显示如下信息:
自动化验证听起来挺唬人的,实际上简单的很:就是我先把所有预期的结果穷举出来(如果有必要的话,比如其没有规律性),然后再把得到的测试结果与其一一进行比较,如果比较结果是对的,那么我就输出高电平或者低电平;如果不对,那么我就输出相反的电平。这样,我们只需要监控这个信号,就可以知道我们的设计是否是正确的。
编写testbenches的指导方针
本节提供了编写测试工作台的指导方针。正如规划一个电路设计的布局有助于实现更好的电路性能一样,规划一个testbenches的布局同样可以改善仿真验证过程。
- (1)在编写testbenches前多了解仿真工具
尽管常用的仿真工具符合HDL行业标准,但这些标准没有解决几个重要的特定仿真问题。不同的仿真工具具有不同的特性、性能和性能特征,则会导致其产生不同的仿真结果。
····基于事件VS基于循环的仿真
基于事件的仿真在输入、信号或门电路改变值时调度一个仿真事件。在基于事件的仿真中,延迟值可以与门电路和线网相关联,以实现最佳的定时仿真。
基于循环的仿真以同步设计为目标。它们优化组合逻辑,并在时钟周期分析结果。这个特性使得基于循环的仿真比基于事件的仿真更快,内存效率更高。
----调度事件
基于事件的仿真工具供应商使用不同的算法来调度仿真事件。因此,根据仿真工具使用的调度算法,在相同的仿真时间发生的事件可以以不同的顺序被调度(在每个事件之间插入 delta延迟)。为了避免算法依赖和确保正确的结果,事件驱动的testbenches应该指定一个显式的刺激序列。
(2)避免使用无限循环
当事件添加到基于事件的仿真工具时,CPU和内存使用量会增加,处理会变慢。除非对testbenches至关重要,否则不应该使用无限循环来提供测试刺激。通常,时钟是在一个无限循环中指定的(例如,Verilog中的“forever”循环),而不是其他信号事件。
(3)将测试激励分解成不同的逻辑块
所有初始块(Verilog)都并行运行。如果不相关的激励被划分成单独的块,testbenches激励序列将变得更容易执行和检查。由于每个并发块都是相对于零仿真时间运行的,因此使用单独的块传递激励更容易。使用单独的测试激励块会产生更容易创建、维护和升级的testbenches。
(4)不要显示不重要的数据
大型设计的testbenches可能包含超过100,000个事件和大量信号。显示大量的仿真数据会大大降低仿真速度。最好每“n”个时钟周期只采样相关信号,以保证足够的仿真速度。
高级testbenches技术
用任务(Tasks)分解测试激励
当创建大型testbenches时,测试激励应该被划分成不同的子模块,以使得代码清晰和方便修改。任务(task)可用于对信号进行划分。在下面的例子中,测试台模拟了SDRAM控制器的设计。该设计包括重复激励块,因此testbench通过声明单独的任务对激励进行划分,这些任务稍后在testbench中调用,以实现单独的设计功能。
task addr_wr;
input [31 : 0] address;
begin
data_addr_n = 0;
we_rn = 1;
ad = address;
end
endtask
task data_wr;
input [31 : 0] data_in;
begin
data_addr_n = 1;
we_rn = 1;
ad = data_in;
end
endtask
task addr_rd;
input [31 : 0] address;
begin
data_addr_n = 0;
we_rn = 0;
ad = address;
end
endtask
task data_rd;
input [31 : 0] data_in;
begin
data_addr_n = 1;
we_rn = 0;
ad = data_in;
end
endtask
task nop;
begin
data_addr_n = 1;
we_rn = 0;
ad = hi_z;
end
endtask
这些任务指定了设计功能的某些独立单元—地址读和地址写、数据读和数据写或nop(无操作)。一旦指定,这些任务可以在激励过程中被调用。如下:
Initial begin
nop ; // Nop
#( 86* `CYCLE +1); addr_wr (32'h20340400); // Precharge, load
Controller MR
#(`CYCLE); data_wr (32'h0704a076); // value for Controller MR
#(`CYCLE); nop ; // Nop
#(5 * `CYCLE); addr_wr (32'h38000000); // Auto Refresh
#(`CYCLE); data_wr (32'h00000000); //
#(`CYCLE); nop ; // Nop
…
…
end
将测试激励分解成不同的任务以使激励更容易实现,并使代码更具可读性。
这一方法类似于C语言的子函数封装,我们可以把测试过程中要实现的激励封装到不同的任务里,这样在编写测试激励时,只需要调用对应的task即可,这样的代码看起来简洁、直观,而且这一方法实际上能有效的节约开发时间。
控制仿真中的双向信号
大多数设计使用了双向信号,这使得在testbenches中必须与单向信号区别对待。
下面是使用了双向信号的设计:
module bidir_infer (DATA, READ_WRITE);
input READ_WRITE ;
inout [1:0] DATA ;
reg [1:0] LATCH_OUT ;
always @ (READ_WRITE or DATA)
begin
if (READ_WRITE == 1)
LATCH_OUT <= DATA;
end
assign DATA = (READ_WRITE == 1) ? 2'bZ : LATCH_OUT;
endmodule
对应的testbenches:
module test_bidir_ver;
reg read_writet;
reg [1:0] data_in;
wire [1:0] datat, data_out;
bidir_infer uut (datat, read_writet);
assign datat = (read_writet == 1) ? data_in : 2'bZ;
assign data_out = (read_writet == 0) ? datat : 2'bZ;
initial begin
read_writet = 1;
data_in = 11;
#50 read_writet = 0;
end
endmodule
在上面的testbenches中,data_in信号为设计中的双向信号DATA提供激励,而data_out信号则读取DATA的返回值。
关于双向信号,建议参考:如何规范地使用双向(inout)信号?
有用的Verilog概念
有用的Verilog语言概念,如 m o n i t o r 、 monitor、 monitor、display和$time,在上面的Verilog测试示例中已经进行了讨论。本节将讨论可以在测试平台中使用的其他Verilog概念。
force和release
force和release语句可用于重写对寄存器或网进行的过程赋值。这些概念通常用于强制特定的设计行为。一旦释放强制值,该信号将保持其状态,直到通过过程赋值传递新值。下面是force和release语句的用法示例:
module testbench;
..
..
initial begin
reset = 1;
force DataOut = 101;
#25 reset = 0;
#25 release DataOut;
..
..
end
endmodule
assign和deassign
assign和deassign语句类似于force和release语句,但是assign和deassign仅适用于设计中的寄存器。它们通常用于设置输入值。与强制语句类似,assign语句覆盖过程语句传递的值。下面是assign和deassign语句的使用示例:
module testbench;
..
..
initial begin
reset = 1;
DataOut = 101;
#25 reset = 0;
release DataOut;
..
..
end
initial begin
#20 assign reset = 1;// this assign statement overrides the earlier
statement #25 reset = 0;
#50 release reset;
endmodule
timescale
timescale指令用于指定testbenches的单位时间步长。这也会影响仿真工具的精度。这个指令的语法是:`timescale reference_time/precision
Reference_time是仿真的单位时间。Precision则决定了仿真时间的精度。下面是一个使用“timescale”的例子:
`timescale 1 ns / 1 ps
// this sets the reference time to 1 ns and precision to 1 ps.
module testbench;
..
..
initial begin
#5 reset = 1; // 5 unit time steps correspond to 5 * 1ns = 5ns in
simulation time
#10 reset = 0;
..
end
initial begin
$display (“%d , Reset = %b”, $time, reset); // this display
// statement will get executed
// on every simulator step, ie, 1 ps.
end
endmodule
如果仿真使用时间延迟值,则模拟必须运行在比最小延迟更大的精度。例如,如果在仿真库中使用9ps延迟,仿真的精度必须为1ps以适应9ps延迟。
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
initial begin
$display (“%d , Reset = %b”, $time, reset); // this display
// statement will get executed
// on every simulator step, ie, 1 ps.
end
endmodule
如果仿真使用时间延迟值,则模拟必须运行在比最小延迟更大的精度。例如,如果在仿真库中使用9ps延迟,仿真的精度必须为1ps以适应9ps延迟。
[外链图片转存中...(img-OrPz8rne-1714690155607)]
[外链图片转存中...(img-wS4mtK9V-1714690155608)]
**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**
**[需要这份系统化资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618545628)**
**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**