在编写FPGA的程序时,常常要在ModelSim软件上进行仿真,来验证功能
而要进行仿真,就要先编写Testbench

我们可以使用QuartusII总动生成一个Testbench的模板
选择Processing->Start->Start Test Bench Template Writer,等待完成后就可以打开生成的Testbench了

创建的模板默认存放在simulation\modelsim文件夹下的**.vt格式文件**,打开vt文件后可以看到Quartus已经为我们完成了一些基本工作,包括端口部分的代码和接口变量的声明,我们要做的就是在这个做好的Testbench的模板里添加我们需要的测试代码

Testbench的编写

一个最基本的Testbench包含三个部分:信号定义、模块接口和功能代码
下面我们就以生成的这个Testbench模板为例,来看一看Testbench的编写
首先最上面一行是:

timescale 1ns/ 1ps

这里表示仿真的单位时间为1ns精度为1ps
想要进行仿真首先要规定时间单位,而且最好在Testbench里面统一规定时间单位,而不要在工程代码里定义,因为不同的模块如果时间单位不同可能会为仿真带来一些问题,而timescale本身对综合也就是实际电路没有影响

Testbench从本质上而言可以看作一个模块和自己编写的模块进行通信,通过Testbench模块向待测模块输出信号作为激励,同时接收从待测模块输出的信号来查看结果\

Testbench的编写

基本的Testbench结构如下:
`timescale 仿真单位/仿真精度
module test_bench();
//通常testbench没有输入与输出端口
信号或变量定义声明
使用initial或always语句产生激励波形
例化设计模块
endmodule

声明仿真的单位和精度

激励文件的开头要声明仿真的单位和仿真的精度
声明的关键词为timescale,声明方法如下:

‘timescale 1ns/1ns

要注意,timescale声明仿真单位和精度时,不需要以分号结尾
在声明的语句中:
**/**之前的1ns代表时间单位——用于定义模块中仿真时间和延迟时间的基准单位
/之后的1ns代表精度单位——用于声明模块的仿真时间和延迟时间的精确程度
代码中出现的
#10
表示延时10ns(由于仿真的精度是1ns,所以最低的延时精度只能到1ns)

定义模块名

在声明好了仿真的精度和单位后就可以定义模块名,模块名的命名方式一般在被测模块名后面加上** tb 或者在被测模块名前面加上 tb **,表示为哪个模块提供激励测试文件,通常激励文件不需要定义输入和输出端口

信号或变量定义

代码中定义的常量有时需要频繁的修改,为了方便修改,可以把常量定义成参数的形式,定义参数的关键字为parameter
Verilog代码中,常用声明信号或变量的关键字为regwire

使用initial或always语句产生激励波形

产生时钟激励的代码如下:

always #10 sys_clk = ~sys_clk;

这段代码代表每10ns(假设仿真单位为1ns),sys_clk的电平状态翻转一次,一个时钟周期包括一个高电平和一个低电平,因此sys_clk的时钟周期为20ns,占空比为50%

也可以生成其他占空比时钟:

always begin
	#6 sys_clk = 0;
	#4 sys_clk = 1;
end

设置时钟的初始值

虽然在always语句中设置了sys_clk的时钟周期,但没有设置初始值,因此sys_clk需要在initial语句中进行初始化

initial begin
	sys_clk = 1'b0; // 时钟初始值
	sys_rst_n = 1'b0; // 复位初始值
	#20 sys_rst_n = 1'b1; // 在第21ns的时候复位信号信号拉高
end

例化设计模块

例化的设计模块是指被测模块,例化被测模块的目的是把被测模块和激励模块实例化起来,并且把被测模块的端口与激励模块的端口进行相应的连接,使得激励可以输入到被测模块。如果被测模块是由多个模块组成的,激励模块中只需要例化多个模块的顶层模块

例化设计模块实例:

flow_led u0_flow_led (
	.sys_clk (sys_clk ),
	.sys_rst_n (sys_rst_n),
	.led (led )
);

其中flow_led是被测模块的名字,而u0_flow_led是自己例化的模块名
左侧带**.** 的信号为flow_led模块定义的端口信号,右侧括号内的信号为激励模块中定义的信号,其信号名可以和被测模块中的信号名一致,也可以不一致,命名一致的好处是便于理解激励模块和被测模块信号之间的对应关系。在实例化被测模块后,以endmodule结束

一个完整的Testbench

`timescale 1ns/1ns // 定义仿真时间单位1ns和仿真时间精度为1ns

module flow_led_tb(); // 测试模块

//parameter define
parameter T = 20; // 时钟周期为20ns

//reg define
reg sys_clk; // 时钟信号
reg sys_rst_n; // 复位信号

//wire define
wire [3:0] led;
//*****************************************************
//** main code
//*****************************************************
//给输入信号初始值
initial begin
	sys_clk = 1'b0;
	sys_rst_n = 1'b0; // 复位
	#(T+1) sys_rst_n = 1'b1; // 在第21ns的时候复位信号信号拉高
end

//50Mhz的时钟,周期则为1/50Mhz=20ns,所以每10ns,电平取反一次
always #(T/2) sys_clk = ~sys_clk;

//例化flow_led模块
flow_led u0_flow_led (
	.sys_clk (sys_clk ),
	.sys_rst_n (sys_rst_n),
	.led (led )
);

endmodule

用于仿真的关键字比较多,有些是可以综合的(能生成实际的电路),有些只能用于仿真,这里只介绍testbench文件最基本和常用的关键字

更多推荐

【FPGA学习】Quartus II中Testbench编写