【FPGA自学总结】Testbench测试代码推荐编写规范

Testbench介绍及其重作用

编写Testbench的主要目的是为了对使用硬件描述语言(HDL)设计的电路进行仿真验证,测试设计电路的功能、部分性能是否与预期的目标相符。
  博主在刚开始入门FPGA时把写RTL代码当成重点,不愿写Testbench,仅仅使用Quartus II自带的仿真产生几个激励,然后观察一下最后输出的波形就完事了。有段时间甚至直接忽视仿真,拿单片机在线调试那一套来对付FPGA,直接把代码下载到板子里看效果,若与预期不符,再修改代码,再次下载到板子,如此反复,效率、效果都很低。
  后来随着深入地了解,发现FPGA水很深,而且和单片机等有很大的不同。因此劝各位要管住自己、不去使用Quartus II自带的仿真。如果你立志从事FPGA行业,那么会写Testbench才你的敲门砖,才是去大厂的门票,别想着去公司写RTL代码了,公司一定是让你来写Testbench测试文件的。
  Testbench作为Modelsim的激励文件,也是自成体系的,同样具有规范化的设计,需要完美的风格及苛刻的结构
  本篇只讲Testbench测试代码的推荐编写规范,Modelsim仿真步骤请移步另一篇博客https://blog.csdn.net/zhaogoudan/article/details/111663758。

一个最基本的Testbench包含三个部分,信号定义、模块接口和功能代码。借用一下特权同学总结的编写Testbench的三个基本步骤:

  1. 对被测试设计的顶层接口进行例化;

  2. 给被测试设计的输入接口添加激励;

  3. 判断被测试设计的输出相应是否满足设计要求

    逐步解决编写Testbench的这三点:

对被测试设计的顶层接口进行例化

首先“对被测试设计的顶层接口进行例化”,这一步相对比较简单,就是简单的例化,但端口多时,也比较困难,而且要分wire、reg,有时会弄错。这里我推荐大家可以偷个懒,通过Quartus II自动生成一个Testbench的模板,里面会自动例化好顶层的接口。
  步骤:选择Processing -> Start -> Start Test Bench Template Writer,等待完成后打开刚才生成的Testbench,默认是保存在simulation\Modelsim文件夹下的.vt格式文件。这一步就不多讲了,偷懒就挺好。

给被测试设计的输入接口添加激励

其次“给被测试设计的输入接口添加激励”,一般时序设计必然涉及到最基本的两个信号——clk、rst_n(时钟、复位)。肯定有朋友会讲可以没有rst_n,是可以没有,但让代码更丰富、更严谨没有错。下面来看看激励clk、rst_n的写法:
首先先讲一下timescale,因为想要进行仿真首先要规定时间单位,而且最好在Testbench里面统一规定时间单位,而不要在工程代码里定义,因为不同的模块如果时间单位不同可能会为仿真带来一些问题,而timescale本身对综合也就是实际电路没有影响。 `timescale 1ns/ 1ps表示仿真的单位时间为1ns,精度为1ps。

系统时钟的三种写法 :

`timescale 1ns/1ps
initial
	clk = 0;
always
	#10 clk <= ~clk;
`timescale 1ns/1ps
initial begin
	clk = 0;
	forever
	#10 clk = ~clk;
end
`timescale 1ns/1ps
always begin
	#10 clk <= 0;
	#10 clk <= 1;
end

上述三种代码的目的就是产生系统时钟,给clk一个初值后,不断重复执行:每10ns翻转一次clk,从而生成一个周期为20ns,频率50MHz的方波信号。第一、二种基本类似,第三种比较简单,少了一个initial,放在了always里初始化。

三种方法都无一例外地给clk赋了初值,因为信号的缺省值为Z,如果不赋初值,则反相后还是Z,时钟就一直处于高阻Z状态。

复位信号rst_n的两种写法

根据复位方式的不同,rst_n一般有两种写法:

`timescale 1ns/1ps
initial begin
	rst_n = 1;
	#100 rst_n = 0;
	#500 rst_n = 1;
end

//同步复位
`timescale 1ns/1ps
initial begin
	rst_n = 1;
	@(negedge clk)	//等待时钟下降沿
	repeat(3) @(negedge clk);
	rst_n = 1;
end

上述两种代码的目的基本都是延时复位,但一个异步复位,一个同步复位,用途不同。

一种推荐的时钟发生器编写规范

推荐!

//clock generate moudle
initial                                                
begin                                                  
	clk = 0;
	forever #(PERIOD/2)
	clk = ~clk;
end                                                    

//将复位信号封装在Task(任务)当中
task task_reset;
begin
	rst_n = 0;
	repeat(2) @(negedge clk); //等待clk出现两个下降沿之后,才继续执行后续语句
	rst_n = 1;
end
endtask

判断被测试设计的输出相应是否满足设计要求

最后“判断被测试设计的输出相应是否满足设计要求”。首先介绍最常用的两个系统任务函数 $ stop和$ finish。$ stop代表暂停仿真后返回软件操作主窗口,将控制权交给user;$ finish代表终止仿真后关闭软件操作主窗口。其他任务函数如$ monitor、$ display 、$ time、$ fwrite等也比较重要,用到的时候再一一介绍。
RTL代码:

  * All rights Reserved, Designed By https://blog.csdn.net/zhaogoudan
  * @projectName		:led
  * @title     		:    
  * @description    	:LED自加  
  * @author 			:zhaodongliang
  * @date   			:21/01/07  
  * @version 		:V1.0.0
  * @copyright ${YEAR} https://blog.csdn.net/zhaogoudan
  */
module led(
	input				clk,
	input				rst_n,
	
	output reg [3:0]	led_data
);
 
//---------------------------
//Delay for 0.3s
localparam DELAY_TOP = 24'd15_000_000;
//localparam DELAY_TOP = 24'd4;			//just for test
 
reg	[23:0]	delay_cnt;
always @(posedge clk or negedge rst_n) begin
	if(!rst_n)
		delay_cnt <= 24'd0;
	else if(delay_cnt < DELAY_TOP) 
		delay_cnt <= delay_cnt + 1'b1;
	else
		delay_cnt <= 24'd0;
end

wire delay_done = (delay_cnt == DELAY_TOP) ? 1'b1 : 1'b0;

//----------------------------
always @(posedge clk or negedge rst_n) begin
	if(!rst_n)
		led_data <= 4'd0;
	else if(delay_done) begin
		if(led_data < 4'd15)
			led_data <= led_data + 1'b1;
		else
			led_data <= 4'd0;
	end
end
endmodule 

testbench代码

`timescale 1 ns/ 1 ns
module led_vlg_tst();

localparam PERIOD = 20;

reg clk;
reg rst_n;
// wires                                               
wire [3:0]  led_data;

// assign statements (if any)                          
led i1 (
// port map - connection between master ports and signals/registers   
	.clk(clk),
	.led_data(led_data),
	.rst_n(rst_n)
);

initial                                                
begin                                                  
	clk = 0;
	forever #(PERIOD/2)
	clk = ~clk;
end                                                    

task task_reset;
begin
	rst_n = 1'b0;
	repeat(2) @(negedge clk)
	rst_n = 1'b1;
end
endtask

//-------------------
//systerm initialization
task task_sysinit;
begin
end
endtask

//--------------------
//testbench of the RTL
initial
begin
	task_sysinit;		//必须先进行系统初始化
	task_reset;			//再进行复位
							//然后才能进行其他的激励
end
                      
endmodule

Testbench可以通过设计Task功能来实现任务的模块化编写及调用,而这些部分的目的就是为了在最后生成待测试的测试激励。
在测试激励生成的代码编写规范中,要遵守以下几点:

  1. 采用initial设计,生成现成的激励
  2. 为了可移植性,激励都通过Task编写调用
  3. 必须先进行系统初始化,接着进行复位,然后才能进行其他的激励
    如下所示:
//testbench of the RTL

initial
begin
	task_sysinit;		//必须先进行系统初始化
	task_reset;			//再进行复位
							//然后才能进行其他的激励
end

输入
必须为reg,因为对于testbench来说输入其实是要输出给测试模块的,也就是相当于需要一个东西来存储,所以是register类型

输出
必须为wire,因为对于testbench来说输出其实是测试模块的输入,也就是需要通过wire来进行连接

—————————————————————————————————
【FPGA自学总结】Testbench测试代码推荐编写规范_第1张图片

你可能感兴趣的:(FPGA知识点,FPGA自学总结,fpga,fpga/cpld)