Testbench设计
一个完整的设计,除了好的功能描述代码,对于程序的仿真验证是必不可少的。学
会如何去验证自己所写的程序, 即如何调试自己的程序是一件非常重要的事情。 而 RTL
逻辑设计中,学会根据硬件逻辑来写测试程序,即 Testbench 是尤其重要的。Verilog 测
试平台是一个例化的待测(MUT)模块,重要的是给它施加激励并观测其输出。逻辑
模块与其对应的测试平台共同组成仿真模型, 应用这个模型可以测试该模块能否符合自
己的设计要求。
编写 TESTBENCH 的目的是为了对使用硬件描述语言设计的电路进行仿真验证,测试
设计电路的功能、性能与设计的预期是否相符。通常,编写测试文件的过程如下:
• 产生模拟激励(波形);
• 将产生的激励加入到被测试模块中并观察其响应;
• 将输出响应与期望值相比较。
通常,一个完整的测试文件其结构为
module Test_bench();//通常无输入无输出
信号或变量声明定义
逻辑设计中输入对应 reg 型
逻辑设计中输出对应 wire 型
使用 initial 或 always 语句产生激励
例化待测试模块
监控和比较输出响应
endmodule
下面列举出一些常用的封装子程序,这些是常用的写法,在很多应用中都能用到。
/*----------------------------------------------------------------
时钟激励产生方法一:50%占空比时钟
----------------------------------------------------------------*/
parameter clockperiod = 10;
initial
begin
clk_i = 0;
forever
#(clockperiod/2)clk_i = ~clk_i;
end
下面列举出一些常用的封装子程序,这些是常用的写法,在很多应用中都能用到。
/*----------------------------------------------------------------
时钟激励产生方法二:50%占空比时钟
----------------------------------------------------------------*/
parameter ClockPeriod=10;
initial
begin
clk_i = 0;
always#(clockperiod/2)clk_i = ~clk_i;
end
/*----------------------------------------------------------------
时钟激励产生方法三:产生固定数量的时钟脉冲
----------------------------------------------------------------*/
initial
begin
clk_i = 0;
repeat(6)
#(clockperiod)clk_i = ~clk_i;
end
/*----------------------------------------------------------------
时钟激励产生方法五:产生非占空比为 50%的时钟
----------------------------------------------------------------*/
initial
begin
clk_i = 0;
forever
begin
#((clockperiod/2)-2) clk_i = 0;
#((clockperiod/2)-2) clk_i = 1;
end
end
/*----------------------------------------------------------------
复位信号产生方法一:异步复位
----------------------------------------------------------------*/
initial
begin
rst_n_i = 1;
#100;
rst_n_i = 0;
#100;
rst_n_i = 1;
end
/*----------------------------------------------------------------
复位信号产生方法二:同步复位
----------------------------------------------------------------*/
initial
begin
rst_n_i = 1;
@(negedge clk_i)
rst_n_i = 0;
#100 //固定复位时间
repeat(10)@(negedge clk_i); //固定周期数复位
@(negedge clk_i)
rst_n_i = 1;
end
/*----------------------------------------------------------------
复位信号产生方法三:复位任务封装
----------------------------------------------------------------*/
task reset ;
input[31:0]reset_time;
RST_ING = 0;
begin
rst_n = RST_ING;
#reset_time;
rst_n_i = ~RST_ING;
end
endtask
/*----------------------------------------------------------------
特殊激励信号产生描述一:输入信号任务封装
----------------------------------------------------------------*/
task i_data;
input[7:0]dut_data;
begin
@(posedge data_en); send_data=0;
@(posedge data_en); send_data=dut_data[0];
@(posedge data_en); send_data=dut_data[1];
@(posedge data_en); send_data=dut_data[2];
@(posedge data_en); send_data=dut_data[3];
@(posedge data_en); send_data=dut_data[4];
@(posedge data_en); send_data=dut_data[5];
@(posedge data_en); send_data=dut_data[6];
@(posedge data_en); send_data=dut_data[7];
@(posedge data_en); send_data=1;
end
endtask
//调用方法:i_data(8'hXX)
/*----------------------------------------------------------------
特殊激励信号产生描述二:多输入信号任务封装
----------------------------------------------------------------*/
task more_input;
input[7:0]a;
input[7:0]b;
input[31:0]times;
output[8:0]c;
begin
repreat(times) //等待times个时钟上升沿
@(posedge clk_i)
c = a + b; //时钟上升沿a,b相加
end
//调用方法:more_input(x,y,t,z); //按声明顺序
/*----------------------------------------------------------------
双向信号描述一:inout 在 testbench 中定义为 wire 型变量
----------------------------------------------------------------*/
//为双向端口设置中间变量 inout_reg 作为 inout 的输出寄存,其中 inout 变
//量定义为 wire 型,使用输出使能控制传输方向
//inout bir_port;
wire bir_port;
reg bir_port_reg;
reg bi_port_oe;
assign bi_port = bi_port_oe? bir_port_reg:1'bz;
/*----------------------------------------------------------------
双向信号描述二:强制 force
----------------------------------------------------------------*/
//当双向端口作为输出口时,不需要对其进行初始化,而只需开通三态门
//当双向端口作为输入时,只需要对其初始化并关闭三态门,初始化赋值需
//使用 wire 型数据,通过 force 命令来对双向端口进行输入赋值
//assign dinout=(!en) din :16'hz; 完成双向赋值
initial
begin
force dinout = 20;
#200
force dinout = dinout - 1;
end
/*----------------------------------------------------------------
特殊激励信号产生描述三:输入信号产生,一次 SRAM 写信号产生
----------------------------------------------------------------*/
initial
begin
cs_n = 1; //片选无效
wr_n = 1; //写使能无效
rd_n = 1; //读使能无效
addr_8'hxx; //地址无效
data = 8'bzz; //数据无效
#100;
cs_n = 0; //片选无效
wr_n = 0; //写使能有效
addr_8'hzz; //写入地址
data = 8'h2c; //写入数据
#100;
cs_n = 0;
wr_n = 0;
addr = 8'bF1;
data = 8'h2c;
#100;
cs_n = 1;
wr_n = 1;
#10;
addr = 8'hxx;
data = 8'hzz;
end
/*----------------------------------------------------------------
Testbench 中@与 wait
----------------------------------------------------------------*/
//@使用沿触发
//wait 语句都是使用电平触发
initial
begin
start = 1'b1;
sait(en = 1'b1);
#10;
start = 1'b0;
end
/*----------------------------------------------------------------
仿真控制语句及系统任务描述
----------------------------------------------------------------*/
$stop //停止运行仿真,modelsim 中可继续仿真
$stop(n) //带参数系统任务,根据参数 0,1 或 2 不同,输出仿真信息
$finish //结束运行仿真,不可继续仿真
$finish(n) //带参数系统任务,根据参数 0,1 或 2 不同,输出仿真信息
//0:不输出任何信息
//1:输出当前仿真时刻和位置
//2:输出当前仿真时刻、 位置和仿真过程中用到的 memory 以及 CPU 时间的
统计
$random //产生随机数
$random % n //产生范围-n 到 n 之间的随机数
{$random} % n //产生范围 0 到 n 之间的随机数
/*----------------------------------------------------------------
仿真终端显示描述
----------------------------------------------------------------*/
$monitor //仿真打印输出,大印出仿真过程中的变量,使其终端显示
/*
$monitor($time,,,"clk=%d reset=%d out=%d",clk,reset,out);
*/
$display //终端打印字符串,显示仿真结果等
/*
$display(” Simulation start ! ");
$display(”At time %t,input is %b%b%b,output is %b",$time,a,b,en,z);
*/
$time //返回 64 位整型时间
$stime //返回 32 位整型时间
$realtime //实行实型模拟时间
/*----------------------------------------------------------------
文本输入方式:$readmemb/$readmemh
----------------------------------------------------------------*/
//激励具有复杂的数据结构
//verilog 提供了读入文本的系统函数
$readmemb/$readmemh("<数据文件名>",<存储器名>);
$readmemb/$readmemh("<数据文件名>",<存储器名>,<起始地址>);
$readmemb/$readmemh("<数据文件名>",<存储器名>,<起始地址>,<结束地址>);
$readmemb:/*读取二进制数据,读取文件内容只能包含:空白位置,注释行,二进制数
数据中不能包含位宽说明和格式说明,每个数字必须是二进制数字。*/
$readmemh:/*读取十六进制数据,读取文件内容只能包含:空白位置,注释行,十六进
制数
数据中不能包含位宽说明和格式说明,每个数字必须是十六进制数字。*/
/*当地址出现在数据文件中,格式为@hh...h,地址与数字之间不允许空白位置,
可出现多个地址*/
module
reg[7:0]memory[0:3];
integer i;
initial
begin
$readmemh("mem.data",memory);
//显示此时存储器内容
for(i = 0; i<4; i = i+1)
$display("Memory[%d] = %h" ,i,memory[i]);
end
endmodule
/*mem.dat 文件内容
@001
AB CD
@003
A1
*/
//仿真输出为
Memory[0] = xx;
Memory[1] =AB;
Memory[2] = CD;
Memory[3] =A1;
上面只例举了常用的 testbench 写法,在工程应用中基本能够满足我们需求,至于
其他更为复杂的 testbench 写法,大家可参考其他书籍或资料。
这里提出以下几点建议供大家参考:
• 封装有用且常用的 testbench,testbench 中可以使用 task 或 function 对代码
进行封装,下次利用时灵活调用即可;
• 如果待测试文件中存在双向信号(inout)需要注意,需要一个 reg 变量来表示输
入,一个 wire 变量表示输出;
• 单个 initial 语句不要太复杂, 可分开写成多个 initial 语句, 便于阅读和修改;
• Testbench 说到底是依赖 PC 软件平台,必须与自身设计的硬件功能想搭配。

浙公网安备 33010602011771号