3.3 Testbench文件架构
3.3.1 Testbench的介绍
一个完整的设计,除要规范文档外,还要做仿真验证,即Testbench,而不是RTL电路设计。作为一个可靠性设计,Testbench不可或缺地成为一个验证高级语言(High-Level Language, HLL)设计的标准方法。而Testbench充当的角色便是设计的激励。将激励输入设计中,通过仿真工具观察输出波形,来验证时序设计的正确与否,这是最基本的设计流程,如图3.20所示。每个Verilog HDL模块的时序正确与否,都建立在“验证结果”是否符合预期的波形基础上。而本书所使用的仿真工具为业界最优秀的HDL语言仿真软件Modelsim,它提供了友好的仿真环境,是业界唯一的单内核支持VHDL和Verilog HDL混合仿真的仿真器。
图3.20 基本设计流程
设计是基于测试的,即DUT(Design Under Test),它是每一位逻辑工程师都必须掌握的方法。基于Quartus II和Modelsim的DUT,是本书设计最基本的思路。由于人为因素,所以不可避免地在设计中会出现错误,但是通过机器的辅助来实现时序逻辑的验证,不仅可以保证功能上的正确,而且还能减少重复的劳动,增强系统可靠性,这必然是每一个IC设计公司或逻辑设计部门要求的内容。
3.3.2 Testbench代码设计风格
Testbench作为Modelsim的激励文件,也是自成体系的,同样具有规范化设计,需要完美的风格及苛刻的结构。
1.Testbench文件头编写规范
与Verilog HDL完全一样,这部分只是Coding的基本规范,具体设计格式参照Verilog代码风格,此处不再做重复阐述。
2.Module列表编写规范
Module列表编写规范如下:
`timescale 1ns/1ns module Testbench_tb; wire [7:0] led_data; //LED test ... endmodule
如上所示,Testbench不同于被测试模块的地方是没有输入/输出类的信号定义。一般情况下,被测试模块的输入端口连接wire或reg信号,输出端口连接wire信号。这里定义了led_data信号,便于在Modelsim中添加并观察波形。此外,设计时还应遵循以下规范。
(1)module前必须写`timescale 1ns/1ns。
(2)module后紧跟Testbench需要输入/输出的信号。
(3)相关信号必须写在一起,并且有注释。
3.时钟发生器编写规范
时钟发生器编写规范如下:
//------------------------------------------ //clock generate module reg clk; reg rst_n; localparam PERIOD=20; //50MHz initial begin clk=0; forever #(PERIOD/2) clk=~clk; end task task_reset; begin rst_n=0; repeat(2) @(negedge clk); rst_n=1; end endtask
这一部分是每一个Testbench都必须包含的初始逻辑,主要用来生成待测模块的驱动时钟及复位信号。其中,通过forever循环翻转实现周期为PERIOD的clk信号,而rst_n采用最前面两个clk为低电平,后续持续为高电平的task来实现(这只是一个范例,用户可以按照自己的需求进行更好的设计)。可以按照CPU的线程工作思路理解Testbench,但所调用的模块又可以并行运算,即通过任务的调用来实现激励或相关逻辑的处理,同时各模块之间的工作又没有绝对的时间关系,可以在顶层调用的机制下并行开始工作。Testbench时钟复位模块生成的设计规范主要如下。
(1)时钟可通过PERIOD的宏定义去修改。
(2)类似时钟的生成可参考该部分的clk设计。
4.模块的例化编写规范
这部分与Verilog HDL的设计规范完全相同,都是直接调用需要测试的模块,通过例化完成线路的连接,作为Modelsim分析的目标电路,该模块如下:
//---------------------------------------------- //the target component instantiation reg spi_cs; reg spi_sck; reg spi_mosi; //----------------------------------- //SPI Data receive from MCU wire rxd_flag; wire [7:0] rxd_data; spi_receiver u_spi_receiver ( //global clock .clk (clk_ref), //100MHz clock .rst_n (sys_rst_n), //global reset //mcu spi interface .spi_cs (spi_cs), //Chip select enable, default:L .spi_sck (spi_sck), //Data transfer clock .spi_mosi (spi_mosi), //Master output and slave input // .spi_miso (spi_miso), //Master input and slave output //user interface .rxd_flag (rxd_flag), //SPI receive complete flag .rxd_data (rxd_data) //SPI received data );
5.系统的初始化编写规范
考虑电路上电时都有一个初始化阶段,Testbench为了实现完整的激励,也必要有一个初始化模块,该模块如下:
//--------------------------------------- //system initialization task task_sysinit; begin spi_cs=1; spi_sck=1; spi_mosi=0; end endtask
该模块简洁明了,唯一的规范便是保证所有输入信号都需要初始化,否则不能正常仿真。
6.生成系统的测试激励
Testbench可以通过设计Task功能来实现任务的模块化编写及调用,而这些部分的目的就是为了在最后生成待测试文件的测试激励,如下所示:
//---------------------------------------- //testbench of the RTL initial begin task_sysinit; task_reset; repeat (5) @(posedge clk); task_mcu_spi_txd(8'h95); repeat (5) @(posedge clk); task_mcu_spi_txd(8'hbe); // repeat(10)@(posedge clk); // test_writereg(8'hFF,16'h0123); //send led_data end
在测试激励生成的代码编写规范中,必须遵循以下几点。
(1)在initial的设计中,按照线程生成激励。
(2)必须首先进行系统初始化,接着进行复位,然后才能进行其他的激励。
(3)为了代码的可读性及可移植性,激励都通过Task编写及调用,以保证代码清晰,便于修改。
(4)同一功能的激励规划在一起,并且需要必要的注释。