FPGA设计技巧与案例开发详解
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

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)同一功能的激励规划在一起,并且需要必要的注释。