Writing Efficient Testbenches
编写高效的测试设计(testbenches)
原文作者:Mujtaba Hamid
注:
一个设计的测试验证是非常重要的。有效的测试可以助我们快速的完成或改善设计。Testbenches建议编写有效的测试代码来通过软件实现可靠的验证。无意中发现,顺手译为中文,以备将来方便。也贴给没有找到更好中文版本的同道人。
Testbenches本意应该是测试平台更合理,但是在中文中阅读起来很不舒服。所以本文中有时译为“测试设计”,“测试代码”,有时干脆是“测试”。
摘要:
应用笔记为HDL验证设计的新手,或者是没有丰富的测试设计经验的逻辑设计者而编写。
测试设计是验证HDL设计的主要手段。本应用笔记为创建或准备和构建有效的测试设计提供准则。它也提供一个为任何设计开发自较验测的测试设计的一个代数方法。
涉及的所有设计文件可以从以下的站点获得:
PC: ftp://ftp.xilinx.com/pub/applications/xapp/xapp199.zip
UNIX: ftp://ftp.xilinx.com/pub/applications/xapp/xapp199.tar.gz
简介:
由于设计的规模越来越大和越来越复杂,数字设计的验证已经成为一个日益困难和繁琐的事。面对挑战,验证工程师们依靠许多的验证工具和方法。对于大的系统,如几百万门的设计,工程师们一般使用一套可靠的验证工具。当然,对于一些小的设计,设计工程师常常发现带有测试的hdl仿真器就可以做得很好。
测试设计已经成为一个验证高级语言设计HLL (High-Level Language) 的标准方法。
典型的,测试设计完成以下任务:
实现测试设计;
仿真通过使用模块的测试向量来仿真测试设计;
输出结果到终端或波形窗口以检视;
可选择的将实际结果和预期结果进行比较。
一般测试设计使用工业标准的VHDL或verilog硬件描述语言来编写。测试设计调用功能设计,然后仿真。复杂的测试设计完成一些附加的功能----如它们包含逻辑来为设计决定适当的设计激励或比较实际结果和预期结果。
后续的章节说明了一个非常稳定的测试设计的结构,并且提供了一个自较验测例子----它将自动比较实际结果和测试设计的预期结果。
图1说明一个基于以上基本要求的标准的hdl验证流程。由于测试设计使用VHDL或verilogHDL来描述,测试设计的验证过程可以在不同的平台或不同公司的软件工具环境完成。另外,由于VHDL或verilogHDL是公开的通用标准语言,使用VHDL或verilogHDL来描述验证设计可以毫无困难的在将来重用。
图1使用测试设计的HDL测试验证流程
构建测试设计:
测试设计可以用VHDL或verilogHDL来描述.因为测试设计只用来进行仿真,它们没有那些适应综合中仅应用的rtl语言子集的语法约束的限制.而是所有的行为结构都可以使用。从而测试设计可以编写的更为通用,使得它们可以更容易维护。
所有的测试设计包含了如表1的基本程序段块。正如上面所提到的,测试设计一般包含更多的附加功能,如在终端上可视的结果和内建的错误检测。
表
1 测试设计的基本程序段
下面的例子说明经常使用的测试设计的结构。
产生时钟信号
使用系统时钟来的时序逻辑设计必须产生时钟。重复的时钟信号可以很容易的在vhdl或verilog源码中实现。以下是vhdl和verilog的时钟发生示例。
VHDL:
-- Declare a clock period constant.
Constant ClockPeriod : TIME := 10 ns;
-- Clock Generation method 1:
Clock <= not Clock after ClockPeriod / 2;
-- Clock Generation method 2:
GENERATE CLOCK: process
begin
wait for (ClockPeriod / 2)
Clock <= ’1’;
wait for (ClockPeriod / 2)
Clock <= ’0’;
end process;
Verilog:
// Declare a clock period constant.
Parameter ClockPeriod = 10;
// Clock Generation method 1:
initial begin
forever Clock = #(ClockPeriod / 2) ~ Clock;
end
// Clock Generation method 2:
initial begin
always #(ClockPeriod / 2) Clock = ~Clock;
end
准备激励信号
为了获得测试设计的验证结果,激励必须在测试设计中提供。在测试设计中使用的并行激励块提供必要的激励。两个方法被考虑:绝对时间激励和相对时间激励。在第一个方法里,仿真变量被详细描述为相对于仿真时间零点。通过比较,相对时间激励提供初始值,然后在重触发激励前等待一个事件。根据设计者的需要,两种方法可以在测试设计中组合使用。
表2绝对时间激励
表2和表3分别以vhdl和verilog提供了一个绝对时间激励和相对时间激励的源代码。
表3相对时间激励
VHDL进程块和Verilog初始块与设计文件中的其他的进程块或初始块同时执行。然而,在每一个进程块或初始块中,事件是按照书写的顺序有序的规划的。这说明在仿真时间零点并发的每一个块激励的顺序。多模块应该被用来将复杂的激励顺序分解为有更好的可读性和方便维护的代码。
显示结果
在verilog中推荐使用关键字$display 和 $monitor 显示结果。虽然vhdl没有等效的显示指令,它提供了std_textio标准文本输入输出程序包。它允许文件的i/o重定向到显示终端窗口(作为这个技术的示例,参看下面的自较验查验证设计)
下面是verilog示例,它将在终端屏幕上显示一些值。
// pipes the ASCII results to the terminal or text editor
initial begin
$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
关键字 $display在终端屏幕上输出引用的附加的说明文字(“。。。”).关键字$monitor操作不同。因为它的输出是事件驱动的。例中的变量$realtime(由用户赋值到当前的仿真时间)用于触发信号列表中值的显示。信号表由变量 $realtime开始,跟随其他将要显示的信号名(clock, reset, load等)。以%开始的关键字包含一个格式描述的表,用来控制如何格式化显示信号列表中的每个信号的值。格式列表是位置确定的。每个格式说明有序地与信号列表中的信号顺序相关。比如%t说明规定了$realtime的值是时间格式。并且第一个%b说明符格式化clock的值是二进制形式。verilog提供附加的格式说明,比如%h用于说明十六进制,%d说明十进制,%c说明显示为八进制。(参见verilog准则了解完整的关键字及格式描述符)
图2说明格式显示结果
图2仿真结果返回结果
简单的测试设计
简单的测试设计实例化用户设计,然后提供相应的激励。测试输出被图形化显示在仿真器的波形窗口里或者作为文本发送到用户的终端或者是管道输出文本。
以下是一个简单的用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
以下是简单的测试设计示例移位寄存器设计的例子,verilog描述。
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
以上的测试设计实例化设计,设置时钟,提供激励信号。所有的进程块在仿真时间零点开始。英镑标记(#)说明下一个激励作用前的延迟。$stop命令使仿真器停止测试仿真(所有测试设计中都应该包含一个停止命令)。最后,$monitor语句返回ascII格式的结果到屏幕或者管道输出到一个文本编辑器。接后的是一个vhdl描述的的测试设计,它实例化设计并提供激励到上述用verilog描述的移位寄存器.
自动验证
推荐自动实现测试结果的验证,尤其是对于较大的设计来说。自动化减少了检查设计是否正确所要求的时间,也使人可能的犯错最少。
一般有以下几种常用的自动测试验证的方法:
1、数据库比较。首先,要创建一个包含预期输出(一个黄金向量文件)的数据库文件。然后,仿真输出被捕获并与黄金向量文件中参考的向量比较(在unix中的diff 工具可以用来比较ascii数据文件)。然而,因为从输出到输入文件指针没有提供,是这种方法的一个缺点,使得跟踪一个导致错误输出的原因比较困难。
2、波形比较。波形比较可以自动或是手动的运行。自动的方法使用一个测试比较器来比较黄金波形与测试输出波形。xilinx的hdl bencher工具可以用于执行一个自动波形比较(关于hdl bencher的相关信息,请参看
http://www.xilinx.com/products/software/statecad/index.htm)
3、自较验测试。一个自较验测试检查预期的结果与运行时间的实际结果,并不是在仿真结束以后。因为有用的错误跟踪信息可以内建在一个测试设计中,用来说明哪些地方设计有误,调试时间可以非常明显地缩短。更多的关于自较验测试的信息在下一节说明。
自较验测试
自较验测试通过在一个测试文档中放置一系列的预期向量表来实现。运行时间时间间隔将这些向量与定义好的实际仿真结果进行比较。如果实际结果与预期结果匹配,仿真成功。如果结果不匹配,测试报告两者的差异。
为同步设计实现自较验测试更简单一些,因为与实现的结果相比较可以在一个时钟沿或任何一个整数倍的时钟周期后。比较的方法基于设计本身的特性。比如一个用于内存I/O的测试应该检查每一次更新数据时的结果或者从一个内存位置读取。类似的,如果一个设计用了一个显而易见的组合块的数字,在预期结果描述时,组合时延就必须要考虑。
在自较验测试中,预期输出与实际输出在一个特定的运行时间间隔比较以便提供自动的错误检查。这个技术在小到中型的设计中非常好。但是,因为当设计复杂后,可能的输出组合成指数倍的增长,为一个大型设计编写一个自较验测试设计是非常困难和非常费时的。
以下是一个用verilog和vhdl描述的自较验测试的简单的例子:
Verilog例子
下述的设计实例中,预期的结果被详细说明。后面的代码,两种结果被比较,比较的结果被返回终端。如果没有错误,一个“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
这种简单的自较验测试设计可以转换到任何测试场合----当然,预期的输出值和信号的名字在重用时是需要更改的。如果不需要每个时钟沿检查,需要的话可以修改for-loop结构。
如果仿真成功,下图的信息就会在显示终端上显示:
图3 verilog示例验证
编辑测试文件的准则
本节提供测试设计的编辑准则。正如计划一个电路设计可以帮助构建更好的电路性能,计划好测试方案可以提高仿真验证的结果。
在编写测试设计前要了解仿真器
虽然通用仿真工具遵从HDL工业标准,但标准并没有说明多少重要的仿真描述条项。不同的仿真器有不同的功能,兼容能力,和执行性能,形成不同的仿真结果。
--基于事件vs基于周期的仿真
仿真器使用基于事件或基于周期的仿真方法。基于事件的仿真器,当输入,信号,或是门改变了值,来确定仿真器事件的时间。在一个基于事件的仿真器中,一个延时值可以附加在门电路或是电路网络上来构建最适的时间仿真。基于周期的仿真器面向同步设计。他们最优化组合逻辑,在时钟周期内分析结果。这个功能使得基于周期的仿真器比基于事件的仿真器更快更有效。然而,由于基于周期的仿真器不允许详细的时间说明,他们并不如基于事件的仿真器准确。对于更多的关于两者的差异的信息,参看
http://www.ednmag.com/ednmag/reg/1996/070496/14df4.htm
上的"数字逻辑仿真:事件驱动,周期驱动,和Home-Brewed"(Digital Logic Simulation: Event-Driven, Cycle-Based, and Home-Brewed),
--确定事件时间
基于事件的仿真器提供商使用不同的运算法则来确定仿真事件。所以,根据仿真器用来确定的运算法则不同,同一个仿真时间的事件被确定为不同的次序(根据在每个事件之间插入的delta延时)。为避免对运算法则的依赖和确保正确的结果,一个事件驱动测试应该详细描述明确的激励次序。
--避免使用无限循环
当一个事件添加到基于事件的仿真器,cpu和内存的使用就增加了,仿真过程就会变慢。除非是评价测试设计,无限循环不应该使用来作为设计的激励。一般地,时钟被说明为一个内部的无限循环(如verilog中的'forever'循环),但是没有其他信号事件。
--细分激励到逻辑模块
在测试中,所有初始块(verilog)或进程块(VHDL)并列地运行。如果无关的激励被分离到独立的块,测试激励的次序会变得更容易实现和检视。因为每个并行的块相关于零点运行,对于分离的块传递激励更容易。分离激励块的使用使得测试设计建立,维护和升级(参看后面的高级测试技术,及该技术的示例)
--避免显示并不重要的数据
大型设计的测试可能包含10万以上的事件或匿名信号。显示大量的仿真数据会相当地降低仿真的速度。最好只是尝试每整数时钟周期时相应的信号来确保适当的仿真速度。
Xilinx 仿真流程要决
配置语句(VHDL)
一个VHDL配置语句允许一个实体链接到一个面向综合或者仿真的详细结构。在xilinx core generator vhdl功能仿真流程中,配置语句被用于一个设计的核心仿真模块的调用(切分)。如果核心仿真模块没有绑定在一个设计,仿真将不能正确的工作。关于配置语句使用的例子,参看前述的VHDL的自较验查测试代码。在xilinx CORE Generator设计中详细配置语句的使用信息可以在http://support.xilinx.com/support/techsup/tutorials/tutorials31i.htm#Modelsim页面中的Modelsim VHDL仿真教程中找到。
http://support.xilinx.com/support/techsup/tutorials/tutorials31i.htm#Modelsim.
为仿真初始化内存RAM块
根据默认值,Xilinx Virtex?块RAMs在所有数据位置都是初始为零,从零点开始的。对于一个post-NGDBuild, post-MAP, or Post-PAR的时序仿真,块RAMs初始为用户在约束文件(ucf)中指定的值或在输入设计文件到NGDBuild时通过init属性来指定值.对于一个pre-synthesis 或 post-synthesis (pre-NGDBuild)的功能仿真,一个配置语句必须用来给RAM块提供初值。下面是一个用来初始化RAM块的配置语句的例子。
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.std_logic_unsigned.all;
library UNISIM;
use UNISIM.vcomponents.all;
configuration cfg_ex_blkram_tb of ex_blkram_tb is
for tb
for uut : ex_blkram use entity work.ex_blkram(struct);
for struct
for INST_RAMB4_S4 : RAMB4_S4 use entity
unisim.RAMB4_S4(RAMB4_S4_V)
generic map (INIT_00 =>
X"1F1E1D1C1B1A191817161514131211100F0E0D0C0B0A09080706050403020100
",
INIT_01 =>
X"3F3E3D3C3B3A393837363534333231302F2E2D2C2B2A29282726252423222120
",
.
.
.
INIT_0F=>
X"FFFEFDFCFBFAF9F8F7F6F5F4F3F2F1F0EFEEEDECEBEAE9E8E7E6E5E4E3E2E1E0
");
end for;
end for;
end for;
end for;
end cfg_ex_blkram_tb;
高级测试技术
根据任务和过程细分激励模块
在创建一个大的测试设计时,激励将会被分割使得代码清晰而易于修改。任务(verilog)或过程(VHDL)可以被用来分割信号。在下面的例子中,测试激励用于一个SDRAM控制器的设计。设计包括重复的激励模块,以便测试设计中通过声明独立的任务分割激励,这些任务稍后被调用来进行独立块的功能的仿真执行。
Verilog示例
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
这些任务指定独立的设计功能元素----地址可读可写,数据可读可写,或者空操作。一量指定,这些任务可以在激励进程中被调用。如下所示:
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
VHDL例程
以下是相当设计的VHDL测试文件,分别细分到独立的过程。
Stimulus : process
procedure addr_wr (address: in std_logic_vector(31 downto 0)) is
begin
data_addr_n <= ‘0’;
we_rn <= ‘1’;
ad <= address;
end addr_wr;
procedure data_wr (data_in: in std_logic_vector(31 downto 0 )) is
begin
data_addr_n <= ‘1’;
we_rn <= ‘1’;
ad <= data_in;
end data_wr;
procedure addr_rd (address: in std_logic_vector(31 downto 0)) is
begin
data_addr_n <= ‘0’;
we_rn <= ‘0’;
ad <= address;
end addr_rd;
procedure data_rd (data_in: in std_logic_vector(31 downto 0)) is
begin
data_addr_n <= ‘1’;
we_rn <= ‘0’;
ad <= data_in;
end data_rd;
procedure nop is
begin
data_addr_n <= ‘1’;
we_rn = ‘0’;
ad = ‘Z’;
end nop;
begin
nop ; -- Nop
wait for 200 ns;
addr_wr (16#20340400#); -- Precharge, load Controller MR
wait for 8 ns;
data_wr (16#0704a076#); -- value for Controller MR
wait for 8 ns;
nop ; -- Nop
wait for 40 ns;
addr_wr (16#38000000#); -- Auto Refresh
wait for 8 ns;
data_wr (16#00000000#);
wait for 8 ns;
nop ; -- Nop
..
..
细分激励到独立的任务使得激励很容易实现,也使得代码的可读性更好。
在仿真时控制双向信号
多数设计使用双向信号,在测试设计中必须区别对待双向信号和单向信号。
VHDL示例
The following is a VHDL bi-directional signal example:
以下是一个vhdl描述的双向信号示例
Library IEEE;
use IEEE.STD_LOGIC_1164.all;
use IEEE.STD_LOGIC_UNSIGNED.all;
entity bidir_infer is
port (DATA : inout STD_LOGIC_VECTOR(1 downto 0);
READ_WRITE : in STD_LOGIC);
end bidir_infer;
architecture XILINX of bidir_infer is
signal LATCH_OUT : STD_LOGIC_VECTOR(1 downto 0);
begin
process(READ_WRITE, DATA)
begin
if (READ_WRITE = ’1’) then
LATCH_OUT <= DATA;
end if;
end process;
process(READ_WRITE, LATCH_OUT)
begin
if (READ_WRITE = ’0’) then
DATA(0) <= LATCH_OUT(0) and LATCH_OUT(1);
DATA(1) <= LATCH_OUT(0) or LATCH_OUT(1);
else
DATA(0) <= ’Z’;
DATA(1) <= ’Z’;
end if;
end process;
end XILINX;
为访问上例中的双向的DATA信号,一个测试可以设置如下:
library ieee;
use ieee.std_logic_1164.all;
Entity testbench is
End testbench;
Architecture test_bidir of testbench is
Component bidir_infer
port (DATA : inout STD_LOGIC_VECTOR(1 downto 0);
READ_WRITE : in STD_LOGIC);
end component;
signal read_writet: std_logic;
signal datat, data_top : std_logic_vector(1 downto 0);
begin
datat <= data_top when (Read_writet = ’1’) else (others => ’Z’);
data_top <= datat when (Read_writet = ’0’) else (others => ’Z’);
uut : bidir_infer port map (datat, read_writet);
process begin
read_writet <= ’1’;
data_top <= "10";
wait for 50 ns;
read_writet <= ’0’;
wait;
end process;
end test_bidir;
双向总线由测试台控制,双向总线的值可以通过数据顶层信号来访问。
Verilog示例
以下是verilog设计的可决断的双向总线示例。
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
Verilog测试设计可以如下设置:
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
在这些测试设计中,data_in信号提供激励到设计中的双向DATA数据信号,data_out信号读取该DATA数据信号.
为仿真初始化内存
请参考前段的xilinx仿真流程要决(Xilinx Simulation Flow Tips)
有用的语言结构
Verilog
有用的Verilog语言结构,如 $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只用于设计中的寄存器。他们一般用于设置输入值。就象一个force语句,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
timescales
timescale指示被用于为测试指定单位时间步。它也影响仿真器的精确度。表示符号为:‘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之间可调的范围。
只读储器初始化文件
verilog提供$readmemb和 $readmemh命令来读取ascii文件来初始化存储器的内容。这个命令可以在仿真中用来初始化Xilinx BlockRAM 或者SelectRAM器件。符号表达如下:
$readmemb (“<design.mif>”, design_instance);
MIF是由crorgenerator 生成的存储器初始化文件(Memory Initialization File)。使用者指定MIF的内容。
VHDL
除了前文曾经叙述过的vhdl命令以外(assert,wait,report),以下的结构也对vhdl测试台文件的创建有所帮助。
meminitfile
vhdl提供一个meminitfile记录用来输入存储模块的内容。以下是它的符号说明:
FILE meminitfile: TEXT IS IN “<design.mif>”;
MIF是由crorgenerator 生成的存储器初始化文件(Memory Initialization File)。使用者指定MIF的内容。
编码风格准则
以下编码准则帮助创建易于阅读和维护的代码
缩进
总是缩进代码来使它易读。推荐使用每个为三或四个英文字符的缩进宽度。5个以上字符宽度的缩进常会在右边的页边留下一个空白,当一个字符宽度小于3个字符时导致过小的缩进。
文件名
总是在源文件名中保持".v"(verilog)或".vhd"(VHDL)文件扩展名。如果这些标准的扩展名被改变了,一些编辑器或文件过滤器就会不能认出这些源文件。
信号命令
使用同样的大小写----推荐小写----来表示所有的用户信号。verilog是大小写敏感的,错位的大写可能引起设计综合和仿真失败。并且,一致的信号名称格式风格促使信号名字在源文件中易于定位。使用短的,描述含义的信号名。短的名称更容易输入,而有含义的名称会帮助表明信号的功能。
注释
可以自由地注释测试设计文件代码。注释对于那些要继承或重用代码的人是非常重要的。队此以外,verilog和vhdl代码语法结构是没有明确含义----注释代码填补了重要的细节说明,极大地增加了源代码的清晰性和可重用能力
设计结构
为每一个模块或实体保持一个物理文件。独立模块或实体的独立文件使得设计更易于维护。
更多的信息,请参考hdl准则的书。许多包含全面的代码编制准则。参考FPGA设计重用指南,在以下站点可以找到。
http://www.xilinx.com/ipcenter/designreuse/xrfg.htm
结语
Testbenches提供工程师以可移动,可升级的验证工具。使用混合语言传真器的有效性,设计者可以自由地使用他们选择的语言来验证vhdl和verilog两种设计。高层次行为语言推动了测试设计的发展,测试设计可以用简单的结构并只要求最小数量的源代码。设计得益于自较验测测试,它在仿真过程中自动实现合适的设计的验证。
Xilinx Foundation ise v3.1i被设计来提供一个无缝的,集成hdl设计工作流。Synplicity的Synplify, Synopsys FPGA Express, 和 Xilinx Synthesis Technology (XST),沿着Xilinx Foundation,非常融合地工作在一起来综合代码。Foundation ISE被集成与Modelsim(XE,PE,SE)一起来仿真我们的设计,与Xilinx HDL Bencher集成来自动实现测试台的创建,与Xilinx StateCad集成来创建状态机的编码。
关于完整的 Foundation ISE及其集成成员套件的信息请连结:
http://www.xilinx.com/xlnx/xil_prodcat_landingpage.jsp.
版本历史
下表说明本文档的版本
下面附件中为上文的Word文件,内容相同,根据需要下载
编写高效的测试设计testbenches.rar