verilog_test

来源:互联网 发布:怎样才能当淘宝客服 编辑:程序博客网 时间:2024/06/01 09:20

文章来源:http://wenku.baidu.com/view/c0e1470bbb68a98271fefa1a.html

 

Writing Efficient Testbenches
原文作者:Mujtaba Hamid
翻译:phixcoco@freecity.cn(浙江大学飘渺水云间论坛)
[请阅读文档最后的说明]
摘要:
本应用笔记是专门为没有测试编写经验并对HDL 验证流程陌生的逻辑设计者而编写的。
编写测试代码是验证HDL 设计的主要手段。本应用笔记为创建或构建有效的测试设计提供了准则。同时给
出了一个为任何设计开发自检测测试的算法。
涉及的所有设计文件可以从以下的站点获得:
PC: ftp://ftp.xilinx.com/pub/applications/xapp/xapp199.zip
UNIX: ftp://ftp.xilinx.com/pub/applications/xapp/xapp199.tar.gz
1 简介
由于设计的规模越来越大和越来越复杂,数字设计的验证已经成为一个日益困难和繁琐的任
务。验证工程师们运用多种验证工具和方法来应对挑战。对于大的系统,如几百万门的设计,
工程师们通常使用一系列完善的验证工具。当然,对于一些小的设计,设计工程师常常发现
能编写测试设计的hdl 仿真器就可以做得很好。
测试设计已经成为一个验证高级语言HLL (High-Level Language)描述的设计的标准方法。
典型的,测试设计完成以下任务:
· 在测试中实例化设计模块Design Under Test(DUT);
· 向要进行测试的模块(DUT)输入测试向量进行仿真;
· 仿真通过使用模块的测试向量来仿真测试设计;
· 仿真结果输出到终端或波形窗口以观察结果;
· 将实际结果和预期结果进行比较(可选步骤)。
一般测试使用工业标准的VHDL 或Verilog 硬件描述语言来编写。测试中调用功能设计,然
后仿真。复杂的测试设计完成一些附加的功能――如它们包含逻辑模块来为设计产生适当的
激励或者将实际结果与预期结果做比较。
后续的章节说明了一个优良的测试设计的结构,并提供了一个自检测测试的例子――用以自
动化地比较实际结果和测试设计的预期结果。
图1 给出了符合上述步骤的一个标准的HDL 验证流程。由于测试设计使用VHDL 或
VerilogHDL 编写,因此测试设计的验证流程可以在各平台或各公司提供的软件工具间移植。
另外,由于VHDL 或VerilogHDL 是公开化的标准语言,用VHDL 或VerilogHDL 编写的验
证测试包可以方便地以后的设计中重用。
图1 使用测试设计的HDL 测试验证流程
2 构建测试设计
测试设计可以用VHDL 或VerilogHDL 编写。因为测试设计只用来进行仿真,不需要受综合
中仅能使用RTL 语言子集这样的语法约束。因此它可以使用所有的行为级结构。从而测试
可以编写地更为通用,从而方便维护。
所有的测试设计都包含了如表1 的基本程序段。正如上面所提到的,测试设计一般也可以包
含更多的附加功能,如在终端上显示可视化的结果以及内建的错误检测。
表1 测试设计的基本程序段
下面的例子给出了常用的测试设计的模块。
2.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 准备激励信号
要得到测试验证的结果,就要为测试的设计模块提供激励。必要时,需要在测试设计中使用
并行的激励程序块。通常采用两种方式给出激励:绝对时间激励和相对时间激励。第一种方
式,仿真激励的数值是以对应于仿真时刻零点的绝对时间的形式列出。相比而言,相对时间
激励首先给出激励的初始值,然后等待事件触发然后赋予激励新的数值。根据设计者的需要,
两种方式可以在测试设计中组合使用。
表2 和表3 用VHDL 和Verilog 分别给出了一段绝对时间激励和相对时间激励的代码。
表2 绝对时间激励
表3 相对时间激励
VHDL 进程块和Verilog 初始块与设计文件中的其他的进程块或初始块并行执行。然而,在
每一个进程块或初始块中,事件是按照代码书写的顺序依次执行的。就是说各个模块是在仿
真的零时刻同时启动的。我们应该将复杂的激励序列分解为多个模块来编写以保障代码的可
读性和易于维护性。
2.2 显示结果
在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 仿真结果返回结果
3 简单的测试设计
简单的测试设计就是实例化用户设计,然后向它提供激励。测试的输出可以图形化地显示在
仿真器的波形窗口中或者以文本的方式显示在用户终端或者是管道重定向输出到文本中。
以下是一个简单的用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 描述的移位寄存器的设计中。
VHDL 示例
library IEEE;
use IEEE.std_logic_1164.all;
entity testbench is
end entity testbench;
architecture test_reg of testbench is
component shift_reg is
port ( clock : in std_logic;
reset : in std_logic;
load : in std_logic;
sel : in std_logic_vector(1 downto 0);
data : in std_logic_vector(4 downto 0);
shiftreg : out std_logic_vector(4 downto 0));
end component;
signal clock, reset, load: std_logic;
signal shiftreg, data: std_logic_vector(4 downto 0);
signal sel: std_logic_vector(1 downto 0);
constant ClockPeriod : TIME := 50 ns;
begin
UUT : shift_reg port map (clock => clock, reset => reset,
load => load, data => data,
shiftreg => shiftreg);
process begin
clock <= not clock after (ClockPeriod / 2);
end process;
process begin
reset <= ’1’;
data <= "00000";
load <= ’0’;
set <= "00";
wait for 200 ns;
reset <= ’0’;
load <= ’1’;
wait for 200 ns;
data <= "00001";
wait for 100 ns;
sel <= "01";
load <= ’0’;
wait for 200 ns;
sel <= "10";
wait for 1000 ns;
end process;
end architecture test_reg;
上述VHDL 测试设计与之前提到的Verilog 测试设计在功能上是相似的,除了将结果输出到
终端上的命令以外。在VHDL 中,std_textio 程序包被用于在终端上显示信息,它将在下一
节说明。
4 自动验证
我们推荐实现自动化的测试结果的验证,特别对于较大的设计。自动化减少了检查设计正确
性所需的时间,并将人为错误减到最少。
一般有以下几种常用的自动测试验证的方法:
1、数据库比较。首先,要创建一个包含预期输出(一个“黄金向量”文件)的数据库文件。
然后,捕获仿真输出并与黄金向量文件中参考的向量比较(在UNIX 中的diff 工具可以用
来比较ASCII 格式的数据库文件)。然而,因为缺少从输出到输入文件的指针,所以这种方
法的一个缺点在于难以跟踪得到导致一个错误输出的错误源。
2、波形比较。波形比较可以自动或是手动的运行。自动的方法使用一个测试比较器来比较
黄金波形与测试输出的波形。Xilinx 的HDL Bencher 工具可以用于执行一个自动波形比较(关
于HDL Bencher 的相关信息,请参看:
http://www.xilinx.com/products/software/statecad/index.htm
3、自检测测试。一个自检测测试实时检查预期的结果与实际的结果,而不是在仿真结束时。
因为可以在一个测试设计中内建有用的错误跟踪信息用来说明哪些地方设计有误,从而大大
缩短了调试时间。更多的关于自检测测试的信息会在下一节说明。
5 自检测测试
自检测测试设计将一系列的预期向量放置在测试文件中。这些向量间隔地与实际仿真结果比
较。如果实际结果与预期结果匹配,仿真成功。如果结果不匹配,测试报告两者的差异。
为同步设计实现自检测测试显得更简单一些,因为预期结果与真实结果相比较可以在一个时
钟边沿或任何一个整数倍的时钟周期后进行。比较的方法还与设计本身的特性相关。比如一
个用于内存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 示例验证
VHDL 示例
在VHDL 中,预期的结果放在一个向量文件中。VHDL 中的textio 程序包用于从向量文件
中读取数据,以及显示错误信息。这个测试用VHDL 描述了秒表设计。
LIBRARY IEEE;
USE IEEE.std_logic_1164.all;
LIBRARY ieee;
USE IEEE.STD_LOGIC_TEXTIO.ALL;
USE STD.TEXTIO.ALL;
ENTITY testbench IS
END testbench;
ARCHITECTURE testbench_arch OF testbench IS
COMPONENT stopwatch
PORT (
CLK : in STD_LOGIC;
RESET : in STD_LOGIC;
STRTSTOP : in STD_LOGIC;
TENTHSOUT : out STD_LOGIC_VECTOR (9 DOWNTO 0);
Figure 3: Verilog Example Verification
ONESOUT : out STD_LOGIC_VECTOR (6 DOWNTO 0);
TENSOUT : out STD_LOGIC_VECTOR (6 DOWNTO 0)
);
END COMPONENT;
SIGNAL CLK : STD_LOGIC;
SIGNAL RESET : STD_LOGIC;
SIGNAL STRTSTOP : STD_LOGIC;
SIGNAL TENTHSOUT : STD_LOGIC_VECTOR (9 DOWNTO 0);
SIGNAL ONESOUT : STD_LOGIC_VECTOR (6 DOWNTO 0);
SIGNAL TENSOUT : STD_LOGIC_VECTOR (6 DOWNTO 0);
constant ClockPeriod : Time := 60 ns;
FILE RESULTS: TEXT IS OUT "results.txt";
signal i: std_logic;
BEGIN
UUT : stopwatch
PORT MAP (
CLK => CLK,
RESET => RESET,
STRTSTOP => STRTSTOP,
TENTHSOUT => TENTHSOUT,
ONESOUT => ONESOUT,
TENSOUT => TENSOUT
);
stimulus: PROCESS
begin
reset <= ’1’;
strtstop <= ’1’;
wait for 240 ns;
reset <= ’0’;
strtstop <= ’0’;
wait for 5000 ns;
strtstop <= ’1’;
wait for 8125 ns;
strtstop <= ’0’;
wait for 500 ns;
strtstop <= ’1’;
wait for 875 ns;
reset <= ’1’;
wait for 375 ns;
reset <= ’0’;
wait for 700 ns;
strtstop <= ’0’;
wait for 550 ns;
strtstop <= ’1’;
end process stimulus;
clock: process
begin
clk <= ’1’;
wait for 100 ns;
loop
wait for (ClockPeriod / 2);
CLK <= not CLK;
end loop;
end process clock;
check_results : process
variable tmptenthsout: std_logic_vector(9 downto 0);
variable l: line;
variable good_val, good_number, errordet: boolean;
variable r : real;
variable vector_time: time;
variable space: character;
file vector_file: text is in "values.txt";
begin
while not endfile(vector_file) loop
readline(vector_file, l);
read(l, r, good => good_number);
next when not good_number;
vector_time := r * 1 ns;
if (now < vector_time) then
wait for vector_time - now;
end if;
read(l, space);
read(l, tmptenthsout, good_val);
assert good_val REPORT "bad tenthsoutvalue";
wait for 10 ns;
if (tmptenthsout /= tenthsout) then
assert errordet REPORT "vector mismatch";
end if;
end loop;
wait;
end process check_results;
end testbench_arch;
library XilinxCoreLib;
CONFIGURATION stopwatch_cfg OF testbench IS
FOR testbench_arch
FOR ALL : stopwatch use configuration work.cfg_tenths;
END FOR;
END FOR;
END stopwatch_cfg;
以下向量文件用于上述的测试。它包含了预期的仿真值。
-- Vector file containing expected results
0 1111111110
340 1111111110
400 1111111101
460 1111111011
520 1111110111
580 1111101111
640 1111011111
700 1110111111
760 1101111111
820 1011111111
880 0111111111
940 1111111110
1000 1111111110
1060 1111111101
1120 1111111011
1180 1111110111
1240 1111101111
1300 1111011111
1360 1110111111
1420 1101111111
1480 1011111111
1540 0111111111
1600 1111111110
1660 1111111110
1720 1111111101
1780 1111111011
如果检测到错误,它会显示在一个仿真提示器中。图4 展示了MTI 脚本窗口中的错误信息:
图4 仿真命令错误报告
6 编写测试的准则
本节提供编写测试设计的准则。正如计划一个电路设计可以帮助得到更好的电路设计性能,
计划好测试方案可以提高仿真验证的结果。
· 在编写测试设计前要了解仿真器
虽然通用仿真工具遵从HDL 工业标准,但标准并没有说明一些关于仿真的重要问题。
不同的仿真器有不同的功能,兼容性,和性能特性,从而会得到不同的仿真结果。
- 基于事件vs 基于周期的仿真
仿真器采用基于事件或者基于周期的方法运行仿真。基于事件的仿真器,当输入,信号,
或逻辑门改变数值时触发仿真事件。在一个基于事件的仿真器中,一个延时值可以关联
到逻辑门或是连线上来得到最佳的时序仿真。基于周期的仿真器面向同步设计。他们基
于时钟周期来优化组合逻辑和分析结果。这使得基于周期的仿真器比基于事件的仿真器
更快更有效。然而,由于基于周期的仿真器不允许详细的时序说明,所以并不如基于事
件的仿真器准确。关于两者差异的更多的信息请看“Digital Logic Simulation:
Event-Driven, Cycle-Based, and Home-Brewed”,参见:
http://www.ednmag.com/ednmag/reg/1996/070496/14df4.htm
- 事件调度
基于事件的仿真器提供商使用不同的运算法则来调度仿真事件。所以,根据仿真器使用
的算法的不同,同一个仿真时间的事件可能被确定为不同的次序(各个事件之间插入一
定延时)。为避免对算法依赖性和确保正确的结果,一个事件驱动测试应该详细描述明
确的激励次序。
· 避免使用无限循环
当基于事件的仿真器中添加了一个事件,cpu 和内存的使用就增加,仿真过程就会变慢。
除非测试中特别的需要,否则无限循环不应该用来设计激励。通常,时钟用无限循环定
义(如Verilog 中的'forever'循环),但是其他信号事件不能。
· 分解激励到逻辑模块
在测试中,所有初始块(Verilog)或进程块(VHDL)并行地运行。如果不相关的激励被
分离到各自独立的块中,测试激励序列将变得更简洁实现并易于检查。因为每个并行的
块均相对于仿真零点运行,为分离的块提供激励变得更容易。分离的激励块使得测试的
建立,维护和升级更加简单(参看后面的高级测试技术,以及该技术的示例)
· 避免显示并不重要的数据
大型设计的测试可能包含10 万以上的事件或繁多的信号。显示大量的仿真数据极大地
降低了仿真的速度。最好只是每整数时钟周期采样相关的信号以确保足够的仿真速度。
7 Xilinx 仿真流程要决
7.1 配置语句(VHDL)
一个VHDL 配置语句允许一个设计链接到一个面向综合或者仿真的特点结构。在Xilinx
CORE Generator VHDL 功能仿真流程中,配置语句用于调用一个设计的核心仿真模块。如
果核心仿真模块没有被调用到设计中,仿真将不能正确运行。关于配置语句使用的例子,参
看前面的用VHDL 描述的自检测测试程序代码。关于Xilinx CORE Generator 中使用配置语
句的详细信息可以在Modelsim VHDL 仿真入门指南中找到,参见:
http://support.xilinx.com/support/techsup/tutorials/tutorials31i.htm#Modelsim
7.2 为仿真初始化块RAM
Xilinx VirtexTM 块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;
8 高级测试技术
8.1 根据任务和过程细分激励模块
在创建一个大的测试设计时,应该分割激励以使代码清晰和易于修改。任务(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
……
将激励分解为一系列独立的任务,使得添加激励更加容易,也使得代码的可读性更好。
8.2 在仿真时控制双向信号
多数设计使用双向信号,在测试设计中必须区别对待双向信号和单向信号。
VHDL 示例
以下是一个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;
双向总线由测试台控制,双向总线的值可以通过data_top 信号来访问。
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 信号读取数据。
8.3 为仿真初始化内存
请参考前面的Xilinx 仿真流程要决章节。
9 有用的语言结构
9.1 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 以适应9ns
的延时。
读取存储器初始化文件
Verilog 提供$readmemb 和 $readmemh 命令来读取ASCII 文件来初始化存储器的内容。这个
命令可以在仿真中用来初始化Xilinx BlockRAM 或者SelectRAM 器件。句法如下:
$readmemb (“<design.mif>”, design_instance);
MIF 是由coregen 生成的存储器初始化文件。由使用者指定MIF 的内容。
9.2VHDL
除了前文曾经叙述过的VHDL 命令以外(assert,wait,report),以下的结构也用于辅助VHDL
测试文件的创建:
meminitfile
VHDL 提供一个meminitfile 记录用来输入存储模块的内容。以下是它的句法:
FILE meminitfile: TEXT IS IN “<design.mif>”;
MIF 是由coregen 生成的存储器初始化文件。由使用者指定MIF 的内容。
10 编码风格准则
以下编码准则帮助您创建易于阅读和维护的代码:
10.1 缩进
缩进使代码更容易阅读。推荐使用3 或4 个空格间距缩进代码。5 个以上宽度的缩进常会使
得右边缺少空间,宽度小于3 则显得缩进过小。
10.2 文件名
总是在源文件名中保持".v"(Verilog)或".vhd"(VHDL)文件扩展名。如果这些标准的扩展名被
改变了,一些编辑器或文件过滤器就无法识别这些源文件。
10.3 信号命令
使用同样的大小写格式――推荐小写字符――来表示所有的用户信号。Verilog 是大小写敏
感的,错位的大小写可能引起设计综合和仿真失败。并且,一致的信号名称格式风格能方便
在源文件中定位信号。使用简短的描述含义的信号名。短的名称更容易输入,而有含义的名
称会帮助表明信号的功能。
10.4 注释
可以自由地注释测试设计文件代码。注释对于那些要继承或重用代码的人是非常重要的。队
此以外,Verilog 和VHDL 代码语法结构不能明确地显示含义――详细地注释代码极大地增
加了源代码的清晰性和可重用性。
10.5 设计结构
为每一个模块或实体保持一个物理文件。为独立的模块或实体创建独立的文件使得设计更易
于维护。
更多的信息,请参考HDL 参考手册。其中许多包含全面的代码书写准则。参考FPGA 设计
重用指南,在以下站点可以找到:
http://www.xilinx.com/ipcenter/designreuse/xrfg.htm
11 结语
Testbenches 为工程师们提供了可移植与升级的验证流程。使用混合语言传真器使得设计者
可以自由地选择他们熟悉的HDL 语言来验证VHDL 和Verilog 描述的设计。高层次行为语
言使得能用最小的代码量和简单的结构创建测试。设计得益于自检测测试,它在仿真过程自
动验证结果的正确性。
Xilinx FoundationTM ISE v3.1i 被设计来提供一个集成HDL 设计工作流程。Synplicity 的
Synplify, Synopsys 的FPGA Express, 和 Xilinx Synthesis Technology (XST) , Xilinx
Foundation,能和Xilinx Foundation 工具一起融合地工作以综合代码。Foundation ISE 可以集
成Modelsim(XE,PE,SE)来仿真设计,Xilinx HDL Bencher 可以用来自动创建测试,Xilinx
StateCad 用来创建状态机的代码。
关于完整的 Foundation ISE 及其集成套件的信息请链接:
http://www.xilinx.com/xlnx/xil_prodcat_landingpage.jsp
12 版本历史
下表给出了本文档的版本信息
[说明]
感谢冷血剑网友最初的工作,我在他的博客上看到了“Writing Efficient Testbenches”的中文翻译,我在他
的译文的基础上,参照Xilinx Application Note 的原文进行了不少修改,并重新进行了排版。错误再所难免,
欢迎各位网友指出,联系phixcoco@sina.com 或者在浙江大学飘渺水云间论坛中致信phixcoco,在此一并谢
过:P
参考译文见链接:http://www.fpga.com.cn/blog/blog.asp?name=冷血剑
版权归“Writing Efficient Testbenches”原作者Mujtaba Hamid 以及Xilinx 公司所有。
原文见连接:http://direct.xilinx.com/bvdocs/appnotes/xapp199.pdf
以下是参考译者在翻译稿中的抬头注释:
一个设计的测试验证是非常重要的。有效的测试可以助我们快速的完成或改善设计。Testbenches 建议编写
有效的测试代码来通过软件实现可靠的验证。无意中发现,顺手译为中文,以备将来方便。也贴给没有找
到更好中文版本的同道人。
Testbenches 本意应该是测试平台更合理,但是在中文中阅读起来很不舒服。所以本文中有时译为“测试设
计”,“测试代码”,有时干脆是“测试”。