本文主要是介绍testbench tb仿真,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
Verilog功能模块HDL设计完成后,并不代表设计工作的结束,还需要对设计进行进一步的仿真验证。掌握验证的方法,即如何调试自己的程序非常重要。在RTL逻辑设计中,要学会根据硬件逻辑来写测试程序即写Testbench。Verilog测试平台是一个例化的待测(MUT)模块,重要的是给它施加激励并观测其输出。逻辑块与其对应的测试平台共同组成仿真模型,应用这个模型就可以测试该模块能否符合自己的设计要求。
编写TESTBENCH的目的就是为了测试使用HDL设计的电路,对其进行仿真验证、测试设计电路的功能、性能与设计的 预期是否相符。通常,编写测试文件的过程如下:
- 产生模拟激励(波形)
- 将产生的激励加入到被测试模块中并观察其响应;
- 将输出响应与期望值比较。
1. 完整的TESTBENCH文件结构
-
module Test_bench()//一般简单的测试文件无输入输出 -
信号或变量声明定义 -
逻辑设计中输入信号在这里对应reg型变量 -
逻辑设计中的输出信号在这里对应wire型 -
使用initial或always语句块产生激励 -
例化猜测是模块UT -
监控和比较输出响应 -
endmodule
2.时钟激励产生
下面列举一些常用的生成时钟激励的方法:
方法一: forever
-
//*======================================================== -
50%占空比时钟 -
==========================================================*// -
parameter ClockPeriod = 10 ; -
initial -
beign -
clk_i = 0; -
forever -
# (ClockPeriod/2) clk_i = ~clk_i ; -
end
方法2: always块
-
//========================================================= -
50%时钟占空比 -
==============================================================*/ -
Parameter ClockPeriod = 10 ; -
initial -
begin -
clk_i =0 ; -
always #(ClockPeriod/2) clk_i =~clk_i ; -
end
方法3:产生固定数量的时钟脉冲
-
parameter CloclPeriod = 10 ; -
initial -
begin -
clk_i = 0 ; -
repeat(6) -
#(ClockPeriod/2) clk_i =~ clk_i; -
end
方法4:产生占空比非 50%的时钟
-
parameter ClockPeriod = 10 ; -
initial -
begin -
clk_i = 0 ; -
forever -
begin -
#((ClockPeriod/2)-2) clk_i = 0 ; -
#((ClockPeriod/2)+2) clk_i = 1; -
end -
end
3.复位信号设计
方法1:异步复位
-
initial -
begin -
rst_n_i = 1 ; -
#100 ; -
rst_n_i = 0 ; -
#100 ; -
rst_n_i = 1; -
end
方法2:同步复位
-
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
方法3:对复位进行任务封装
-
task reset ; -
input[31:0] reset_timer ; //将复位的时间作为输入,达到复位时间可调的目的 -
RST_ING = 0 ;//复位的方式可调,低电平有效或高电平有效 -
begin -
rst_n = RST_ING ; //复位中 -
#reset_time //设置的复位时间 -
rst_n_i = ~ RST_ING ; -
end -
endtask
4.双向信号设计
双向信号的描述方式并不唯一,常用的方法如下:
描述方式1: inout在testbench中定义为wire型变量
-
//为双向端口设置中间变量inout_reg作为intou的输出寄存,其中inout变量定义为wire型,使用输出使能控制 -
//传输的方向 -
//inout bir_port; -
wire birport ; //将双向接口变量定义为wire型 -
reg bir_port_reg ; //定义一个reg型的中间变量,作为双向口的输出寄存 -
reg bi_port_oe ; //定义输出使能,用于控制传输的方向 -
assign birport = (bir_port_oe)?bir_port_reg:1'bz;
描述方式2:强制force
当双向端口作为输出端口时,不需要对其进行初始化,而只需开通三态门;当双向接口作为输入时,只需要对其初始化,并关闭三态门,初始化赋值需要使用wire数据,通过force命令来对双向端口进行输入赋值
-
//assign dinout = (!en)din: 16'hz ; 完成双向赋值 -
initial -
begin -
for dinout = 20 ; -
#200 -
force dinout = dinout -1 ; -
end
5. 特殊信号设计
1.输入信号任务的封装
方便产生激励数据。
-
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; -
#100 ; -
end -
endtask -
//调用该task的方法: i_data(8'hXX) ;
2.多输入信号任务封装
-
task more_input; -
input [7:0] a; -
input [7:0] b; -
input [31:0] times ; -
output[8:0] c; -
begin -
repeat(times) //等待times个时钟上升沿 -
@(posedge clk_i) -
c= a+b ;//时钟上升沿, a和b相加 -
end -
endtask -
//调用方法: more_input(x,y,t,z);
3.输入信号产生,一次SRAM写信号产生
-
initial -
begin -
cs_n = 1 ; //片选无效 -
wr_n = 1 ; //写使能无效 -
rd_n =1 ; //读使能无效 -
addr = 8'hxx; //地址无效 -
data = 8'hxx; //数据无效 -
#100 ; -
cs_n = 0 ; -
wr_n = 0 ; -
addr = 8'hF1 ; -
data = 8'h2C ; -
#100 ; -
cs_n = 1; -
wr_n = 1 ; -
#10 ; -
addr = 8'hxx; -
data = 8'hxx; -
end
Testbench中的 @ 和 wait
//wait都是使用电平触发
-
intial -
begin -
start = 1'b1 ; -
wait(en = 1'b1) ; -
#10; -
start = 1'b0 ; -
end
6.仿真控制语句以及系统任务描述
仿真控制语句以及系统能够任务描述:
-
$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位整型时间 -
$realtiime //实行实型模拟时间
文本输入方式:$readmemb /$readmemh
-
//激励具有复杂的数据结构 -
//verilog提供了读入文本的系统函数 -
$readmemb/$readmemh("<数据文件名>",<存储器名>); -
$readmemb/$readmemh("<数据文件名>",<存储器名>,<起始地址>); -
$readmemb/$readmemh("<数据文件名>",<存储器名>,<起始地址>,<结束地址>); -
$readmemb: -
/*读取二进制数据,读取文件内容只能包含:空白位置,注释行,二进制数 -
数据中不能包含位宽说明和格式说明,每个数字必须是二进制数字。*/ -
$readmemh: /*读取十六进制数据,读取文件内容只能包含:空白位置,注释行,十六进制数数据中不能包含位宽说明和格式说明,每个数字必须是十六进制数字。*/ -
/*当地址出现在数据文件中,格式为@hh...h,地址与数字之间不允许空白位置, 可出现多个地址*/
-
module ; -
reg [7:0] memory[0:3];//声明8个8位存储单元 -
integer i; -
initial -
begin -
$readmemh("mem.dat",memory);//读取系统文件到存储器中的给定地址 //显示此时存储器内容 -
for(i=0;i<4;i=i+1) -
$display("Memory[%d]=%h",i,memory[i]); -
end -
endmodule
mem.dat的文件内容格式:
-
//mem.dat文件内容 -
@001 -
AB CD -
@003 -
A1 -
//仿真输出为 -
// Memory[0] = xx; -
// Memory[1] = AB; -
//Memory[2] = CD; -
//Memory[3] = A1;
7.加法器的仿真测试文件编写
上面只例举了常用的 testbench 写法,在工程应用中基本能够满足我们需求,至于其他更为复杂的 testbench写法,大家可参考其他书籍或资料。
这里提出以下几点建议供大家参考:
- 封装有用且常用的 testbench,testbench 中可以使用 task 或 function 对代码进行封装,下次利用时灵活调用即可;
- 如果待测试文件中存在双向信号(inout)需要注意,需要一个 reg 变量来表示输入,一个 wire 变量表示输出;
- 单个 initial 语句不要太复杂,可分开写成多个 initial 语句,便于阅读和修改;
- Testbench 说到底是依赖 PC 软件平台,必须与自身设计的硬件功能相搭配。
下面具体看一段程序:
-
module add(a,b,c,d,e);// 模块接口 -
input [5:0] a; // 输入信号a -
input [5:0] b; // 输入信号b -
input [5:0] c; // 输入信号a -
input [5:0] d; // 输入信号b -
output[7:0] e; // 求和输出信号 -
wire [6:0]outa1,outa2; // 定义输出网线型 -
assign e = outa2+outa1; // 把两部分输出结果合并 -
/* -
通常,我们模块的调用写法如下: -
被调用的模块名字- 自定义的名字- 括号内信号 -
这里比如括号内的信号,.ina(ina1) -
这种写法最常用,信号的顺序可以调换 -
另外还有一种写法没可以直接这样写 -
adder u1 (ina1,inb1,outa1); -
这种写法必须确保信号的顺序一致,这种写法几乎没有人采用 */ -
adder u1 (.ina(a),.inb(b),.outa(outa1)); // 调用adder 模块,自定义名字为u1 -
adder u2 (.ina(c),.inb(d),.outa(outa2)); // 调用adder 模块,自定义名字为u2 -
endmodule
-
//adder 子模块 -
module adder(ina,inb,outa );// 模块接口 -
input [5:0] ina; // ina-输入信号 -
input [5:0] inb; // inb-输入信号 -
output [6:0] outa; // outa-输入信号 -
assign outa = ina + inb; // 求和 -
endmodule // 模块结束
仿真文件:
-
`timescale 1ns / 1ps -
module add_tb(); -
reg [5:0] a; -
reg [5:0] b; -
reg [5:0] c; -
reg [5:0] d; -
wire[7:0] e; -
reg [5:0] i; //中间变量 -
// 调用被仿真模块模块 -
add uut ( -
.a(a), -
.b(b), -
.c(c), -
.d(d), -
.e(e)); -
initial -
begin // initial 是仿真用的初始化关键词 -
a=0 ; // 必须初始化输入信号 -
b=0 ; -
c=0 ; -
d=0 -
for(i=1;i<31;i=i+1) -
begin -
#10 ; a = i; b = i; c = i; d = i; -
end -
end -
initial -
begin -
$monitor($time,,,"%d + %d + %d + %d ={%d}",a,b,c,d,e); // 信号打印输出 -
#500 -
$finish; -
end -
endmodule
仿真波形:

终端显示:

这篇关于testbench tb仿真的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!