精华内容
下载资源
问答
  • 如何写testbench文件
    千次阅读 多人点赞
    2020-04-29 17:25:08

    欲观原文,请君移步微信

    testbench作用是什么?

    testbench就是对写的FPGA文件进行测试的文件。任何设计都是有输入输出的,testbench的作用就是给这个设计输入,然后观察输出是否符合我们的预期,这就是testbench的功能。运行环境一般是ise或者vivado自带的仿真工具,或者如modelsim一样的第三方仿真工具。
    如下图所示,仿真模型就好比是"一道菜"(Verilog design file),而输入是厨师给的"各种调料"(Stimulus),输出是这道菜的口味是否符合顾客的"预期口感"(Response)。

    testbench是如何运行的?

    首先要记住一点就是所有testbench本质上都是串行执行,因为在CPU环境下,所有的语句都是串行的。所有并行的语句,比如两个always模块,fork join语句块,都是软件模拟并行执行的。所以早先的编译器,信号定义要在initial语句前面,initial的信号要先有初始值后面的语句才能从给定初值开始执行。所以写testbench的时候,要注意最好先定义信号再写initial语句后面的语句交换顺序不影响,软件可以识别并按照IEEE标准的顺序去执行。
    如果一个模块里面想用并行执行语句用fork join语句,顺序执行用begin end语句。initial语句可以写多个,都是并行执行的,当两个信号在initial冲突的时候,会先执行前面的initial的值。

    常用testbench语法

    1.精度问题
    编译器指令用以控制编译和预处理verilog代码,他们通过重音符号[``]来指明。重音符号常位于键盘的左上角。与时间有关的指令是`timescale指令

    `timescale [time_unit] / [time_precision]
    

    time_unit指定计时和延时的测量单位,time_precision则是指定仿真器的精度。
    比方说,指令

    `timescale 1ns/10ps
    

    则说明仿真单位为1ns,精度为10ps。
    2.延迟问题
    延时语句——#n; 代表延时n个时间轴单位。比如之前定义了timescale 1ns / 10ps;当指定如下代码中的延时,
    #5 y = a & b;
    表明实际上的延时为5ns(即5*1ns)。
    3.initial
    一般用 initial 块给信号赋初值,initial 块执行一次。inital 块里面是顺序执行的。如下

    initial 
    begin 
    a=0; 
    #100; 
    a=1; 
    end。
    

    4.always
    always表示由事件激发反复执行。比如下面产生的语句:

    always #5 clk= !clk;
    

    5.forever
    forever 表示由事件激发反复执行,,重复执行其主体直至仿真结束位置。循环体内常包括一定的时序控制结构,以致周期性推迟执行。比方说,我们换一种方式来描述时钟信号,该信号每10个时间单位翻转一次,且永远运行下去。

    initial
    begin
    clk=1'b0;
      forever
        #10 clk=~clk;
    end
    

    6.repeat
    repeat循环的简单语法如下,循环体内的语句被重复执行指定数次,该数可通过[number]来指定。

    integer i;
    ...
    repeat(16)
    begin
      [procedural_statements;]
    end
    

    7.wait
    wait语句用以等待指定条件。其简单语法如下

    wait[boolean_expression]
    

    直到[boolean_expression]被计算为真,后面语句才可跳过延迟,继续执行。比方说,

    wait(state==READ && mem_ready==1'b1) [statement_to_get_data];
    

    8.task
    任务就是一段封装在“task-endtask”之间的程序。任务是通过调用来执行的,而且只有在调用时才执行,如果定义了任务,但是在整个过程中都没有调用它,那么这个任务是不会执行的。调用某个任务时可能需要它处理某些数据并返回操作结果,所以任务应当有接收数据的输入端和返回数据的输出端。另外,任务可以彼此调用,而且任务内还可以调用函数。?
    任务定义的形式如下:

    task task_id; 
        [declaration] 
        procedural_statement 
    endtask 
    

    其中,关键词 task 和 endtask 将它们之间的内容标志成一个任务定义,task 标志着一个任务的开始;task_id 是任务名;可选项declaration 是端口声明语句和变量声明语句,任务接收输入值和返回输出值就是通过此处声明的端口进行的;procedural_statement是一段用来完成这个任务操作的过程语句,如果过程语句多于一条,应将其放在语句块内;endtask 为任务定义结构体结束标志。下面给出一个的实例:

    task task_demo;                //任务定义结构开头,命名为 task_demo 
        input  [7:0] x,y;           //输入端口说明 
        output [7:0] tmp;           //输出端口说明  
        if(x>y)                  //给出任务定义的描述语句 
          tmp = x; 
      else 
        tmp = y;
    endtask 
    

    虽然任务中不能出现 initial 语句和 always 语句语句, 但任务调用语句可以在 initial 语句
    和 always 语句中使用,其语法形式如下:

    task_id[(端口1,  端口 2, ........,  端口 N)]; 
    

    9.function
    函数定义是嵌入在关键字function和endfunction之间的,其中关键词function标志着一个函数定义结构的开端,endfunction标志着一个函数定义结构的结束。
    定义函数的语法:

    function <返回值的类型或范围> (函数名);
    <端口说明语句>
    <变量类型说明语句>
    
    begin
    <语句>
    ........
    end
    endfunction
    

    如下例:

    function [7:0] getbyte;
    input [15:0] address;
    begin
        <说明语句> //从地址字中提取低字节的程序
        getbyte = result_expression; //把结果赋予函数的返回字节
    end
    endfunction
    

    (1)函数只能与主模块共用同一个仿真时间单位,而任务可以定义自己的仿真时间单位。
    (2)函数不能启动任务,而任务能启动其它任务和函数。
    (3)函数至少要有一个输入变量,而任务可以没有或有多个任何类型的变量。
    (4)函数返回一个值,而任务则不返回值
    10.数据类型转换函数
    $unsigned和$signed函数执行介于无符号数和有符号数类型之间的转换。
    11.系统函数
    Verilog有一组预定义的系统函数,以$打头,执行与系统相关的操作,如仿真控制、文件读取等。下面我们讲一下一些常用的函数和任务。
    1.$finish和$stop。其中,$finish任务用于终止仿真并跳出仿真器;$stop任务则用于中止仿真。
    2.在Modelsim中,仿真的结果可以以波形的形式显示,也可以以文本的形式显示。四种主要的显示任务有$display、$write、$strobe和$monitor,它们语法类似。在Modelsim中,文本是在控制面板显示的。$display的语法与C语言中的打印函数类似。其简单语法为:

    $display([format_string], [argument], [argument], ...);
    

    3.Veirlog提供一组用于访问外部数据文件的函数和任务。文件可以通过 f o p e n 和 fopen和 fopenfclose函数来打开和关闭。$fopen的语法为:

    [mcd_names] = `$`fopen("[file_name]"); 
    

    至此,testbench文件的语法部分就告一段落,但是小编提醒:学verilog要知道verilog语句的执行顺序和机制,生成的对应时序,哪些语句可综合哪些不可综合。这是最基础的要求。

    更多相关内容
  • 试谈FPGA设计仿真激励文件Testbench的编写方法.pdf
  • 适合新手学习verilog HDL语言。并附有testbench文件,共新手学习使用。 适合新手学习verilog HDL语言。并附有testbench文件,共新手学习使用。
  • 运行:Testbench生成testbench模板 运行:VerilogInstance生成组件实例 运行:VerilogInterface生成接口(SystemVerilog)模板 运行:VerilogClass生成类(SystemVerilog)模板您可以使用p粘贴它。 推荐模块(端口)声明...
  • Testbench编写详解

    千次阅读 2022-05-18 07:36:02
    之前有朋友私信留言谈到想系统学习下Testbench,今早五点早起下这篇博客,算是一个Testbench编写专题的开场白吧,整个其实说到底透过现象看本质,不同于功能模块的编写,Testbench核心任务在于验证功能模块的设计...

             Testbench的编写说难也难,说易也易。之前有朋友私信留言谈到想系统学习下 Testbench,今天特意撰写这篇博客,其实说到底透过现象看本质,不同于功能模块的编写,Testbench核心任务在于验证功能模块的设计是否符合预期,所以围绕着这个目标,为了更方便理解,笔者将其简单地归纳为3个步骤:1.对被测试功能模块的顶层接口进行信号例化;2.向被测试功能模块的输入接口添加激励;3.判断被测试功能模块的输出是否满足设计预期。所以说到这里大家回头去思考下在上面3个步骤中,其中步骤1是通用性的书写规则,那么Testbench的核心设计就在步骤2编写测试激励和步骤3判断输出是否满足预期,所以按照这个思路我们再把步骤2和3的设计方法归纳总结清楚。

            我们首先来聊一聊如何编写测试激励,这也是一个非常有意思的话题,可惜的是往往在学习工作中却经常被人们忽视,就直接导致即使工作几年如果不注重总结,这方面也依然让人感到模里模糊、似懂非懂。实际项目工程当中,需要具体问题具体分析,笔者结合项目工程经验,认真总结出编写测试激励的一般性方法有:1.直接输入激励信号;2.封装测试的子程序;3.循环轮询产生激励。下面我们就对此逐一展开说明,不同的方法适用于不同的应用场景,这里也没有统一的标准答案,测试文件的编写服务于功能文件的验证。

    一、直接输入激励信号

           直接输入激励信号,这是一个最简单快捷的Testbench编写方法,但是同样适用于大多数情况,通常FPGA工程师们会列举出几种典型的输入激励并和功能模块一起代入Modelsim去仿真,然后观察在这些典型的输入情况下,功能模块的输出是否符合预期。

           我们也用一个简单的功能模块举例来说明,这样更加直观明了也方便理解,比如在一个计数器练习,要求为dout初值为8'h0,当收到en1后,dout输出4个周期的8'h55;当收到en2后,dout输出8个周期的8'haa,同时en1和en2不会同时到来且间隔大于10个周期,设计目标如图1所示,表1为练习的信号列表,其中图2练习中各个信号的波形图,图3练习的代码设计。

            我们按照为已经设计好模块的功能模块编写测试文件,去验证输出是否符合预期,Testbench的编写也非常简单,只需要依次输入en1和en2两个使能信号即可,请大家打开Gvim编译器,在编译器下输入“Test”,调出Testbench的模板,我们先将功能模块例化,为了方便大家观察信号,笔者在此把练习中的flag_add和flag_sel信号也都输出并例化来了,而后再去添加en1和en2两个信号的激励即可,图4是练习的输入信号激励设计,这里就不赘述了,如图5请大家打开Vivado环境,去联合Modelsim进行练习的仿真,具体操作步骤在之前有详细介绍过,所以忘记的话可以去回顾下前面的知识,设置操作成功后即可看到如图6所示的在Modelsim下练习的仿真结果,通过这个简单的例子,也帮助大家熟悉了Gvim编辑器下的Testbench的模板,以及回顾了Vivado环境下如何联合Modelsim仿真的操作流程。

    信号列表

    信号名

    I/O

    位宽

    clk

    I

    1

    rst_n

    I

    1

    en1

    I

    1

    en2

    I

    1

    dout

    O

    8

    表1 练习信号列表

    图1 练习示意图

    图2 练习中各个信号的波形图

    图3 练习的代码设计

    图4 练习的输入信号激励设计

    ​​​​​图5 Vivado联合Modelsim进行练习的仿真

    图6 在Modelsim下练习的仿真结果

    二、封装测试的子程序

             在C语言编写过程中,比如单片机STM32程序开发,有经验的软件工程师一定会做好软件架构的规划,对于经常使用到的代码封装成函数,通过调用时,传入不同的形参达到了对代码复用的效果,同时也把相同类型的数据结构统一组织成结构体,方便不同设计层对数据进行有效的交互,很大程度上规范了程序设计。

            同样的道理,封装的思想也完全可以应用在Verilog程序设计当中,不同功能模块的信号之间进行例化,从本质上说就是一种封装思想的具体应用,类似的在测试模块也可以灵活应用task关键字,对Testbench测试文件进行代码层面上的有效封装,有效提高Testbench的可读性。在这里我们就用上一节的按键消抖功能模块来举例,编写其对应的测试文件,从而进一步说明在Testbench中应该怎么封装调用测试任务集。

              按键消抖功能模块的设计,大家如果有忘记的话,请回过头看下20个例程的内容,这里主要是说明Testbench测试模块的代码编写。因为对应有4个按键,为了模拟不同按键按下的情况,所以这里我们就用到了task关键字去封装一个任务模块:task_key_scan,在该任务模块中,我们去模拟了按键的闭合抖动和断开抖动,同时为了方便观看波形,我们把按键消抖功能模块中的20ms消抖时间改成20us。如图7所示是按键消抖功能模块的输入信号激励设计,图8所示是在Modelsim下按键消抖功能模块的仿真结果,大家可以通过查看仿真波形,发现功能模块的设计是符合预期的。

    图7 按键消抖功能模块的输入信号激励设计

    图8 在Modelsim下按键消抖功能模块的仿真结果

    三、循环轮询产生激励

          在上面的“封装测试的子程序”中,我们封装成任务task_key_scan,然后通过调用task_key_scan成功仿真模拟了不同按键按下后,功能模块是否可以准确置位key_vld,返回对应的按键键值key_value,这样最大程度上简化了测试文件的代码数量和代码结构,这里可能细心的同学会发现,对于“按键消抖功能模块”,板子上一共只有4个按键,每次按下一个按键最多会有4种可能的情况,调用task_key_scan任务最多4次即可模拟出所有的情况,非常简单方便。

            然而更多情况下,会有很多种激励产生的可能,即使我们都封装好了task模块,想方设法简化测试文件的代码结构,也很难通过人为反复调用各类task任务去列举出所有可能的情况,使得Testbench的验证结果更有具有说服力(虽然在大部分情况下也不必代入所有可能的激励组合,选取典型的激励作为仿真文件的输入即可),下面我们来介绍“循环轮询产生激励”的方法,去循环产生所有可能的输入激励组合,从而满足一些要求苛刻的应用场景仿真需求。

          笔者想通过“串口自发自收”这个工作当中随处可见的例子,进一步说明测试文件编写方面“循环轮询产生激励”的方法。下面我们先来简单介绍下串口的底层实现,上学读书的时候,大家都会接触到TTL电平和串口等等这样的名词概念,毕业以后真正进入工作岗位成为一名嵌入式工程师,更是几乎天天都和串口打交道。串口本身也是最常用、实用的通信方式:一款产品对内可能有多块PCB板,而它们之间基本都是通过串口TTL电平通信交互数据;一款产品对外大多数都会预留RS232串口和上位机之间进行通信;一块PCB板需要和几块PCB板之间建立通信,或者一台机器需要对外和多台机器进行数据通信时候,使用RS485串口协议几乎成为大家默认的选择方式。

          这里我们从底层说起,其中串口的时序主要包括:空闲位、起始位、数据位、校验位、停止位,如图9所示是串口通信底层实现方式。

            在空闲状态时,串口的数据线会一直保持在高电平状态;当主机要发送数据时,会将数据线拉低一个波特率的时间,从而告诉从机有数据要传输了,要做好准备;起始位之后是数据位,数据位的位数由双方之前约定好即可,双方约定后才能正确地传输和解析包文。每个数据位传输时都会占用一个波特率的时间,请注意在这里是从低位到高位进行传输,比如要传输数据4'b0101,在串口传输时是先传输最低位的“1”,数据传输完成后,发送检验位:奇偶校验是一种非常简单常用的数据校验方式,又细分为奇校验和偶校验,但是一般在实际项目工程中使用的不多,所以不做详细介绍,在校验位后即为最后一位停止位,主机必须保证有停止位,即把数据线拉高一个波特率的时间。因为数据在传输线上传输,硬件上可能会有一定的干扰,每一个设备内部又有自己的时钟,很可能在通信中两台设备间出现了一些细微的不同步,停止位的到来表示整个包文传输的结束,也使得从机可以正确地识别下一轮包文数据的起始位。

            在串口传输当中,有一个非常重要的概率即波特率,串口中常用到的波特率有9600、19200、57600、115200,波特率表示每个数据在传输线上的传输速率,比如在9600的波特率下,每位数据需要传输1/9600s=104166ns的时间,所以在两个设备之间串口通信之前,都需要事先约定相同的波特率、是否有校验位、包文的长度或者包文的结束字节等等。

    图9 串口通信底层实现方式

          然后我们再去编写功能模块,即串口发送模块uart_transfer,串口接收模块uart_receive以及顶层模块uart_loop,也顺便带着大家去复习下前面功能文件编写的知识。

          我们首先来看串口接收模块uart_receive,这里我们提前约定每包包文是1字节即8bit,只需要把图9所示的串口通信底层实现方式还原成verilog代码即可,我们用两个计数器cnt0和cnt1去分别计数一个波特率的时间即接收1bit时间和计数9bit时间,因为这里我们把起始位1bit和停止位1bit也算在了接收数据当中,所以cnt1需要计数10次,rxd_data在cnt1计数在1-9的时刻,使用数据拼接的老办法即可得到,但是需要大家注意一下,为了防止偏差一般串口都采用计数到一半波特率的时间去做赋值操作,在数完end_cnt1即接收完8bit数据和1bit停止位后,拉高rxd_data_vld一个时钟周期即可,uart_receive模块的信号列表如表2所示,串口接收模块uart_receive的代码设计如图10所示。

           然后同样的道理,我们再去编写串口发送模块uart_transfer模块,和uart_receive模块思路基本一致,只需要事先打包好需要发送了包文,即1bit的起始位,8bit的数据位,1bit的停止位,再按照顺序逐一送到外部引脚txd_uart即可,所以不再赘述了,大家直接看代码很好理解这两个模块的实现和功能,如果有不太明白的地方,请再对照9的示意图多思考几遍肯定可以搞明白了,没有太多绕脑的东西,uart_transfer模块的信号列表如表3所示,串口发送模块uart_transfer的代码设计如图11所示。

    信号列表

    信号名

    I/O

    位宽

    clk

    I

    1

    rst_n

    I

    1

    rxd_uart

    I

    1

    rxd_data

    O

    8

    rxd_data_vld

    O

    1

    表2 uart_receive模块信号列表

    信号列表

    信号名

    I/O

    位宽

    clk

    I

    1

    rst_n

    I

    1

    txd_data

    I

    8

    txd_data_vld

    I

    1

    txd_uart

    O

    1

    表3 uart_transfer模块信号列表

           接着我们再把uart_receive模块和uart_transfer模块通过uart_loop顶层模块例化即可,这里我们选择先收后发的方式,所以把uart_transfer模块中的txd_data和txd_data_vld信号例化为rxd_data和rxd_data_vld,图13 串口自发自收顶层模块的代码设计。

    图10 串口接收模块的代码设计

      ​​​​图11 串口发送模块的代码设计

    图12 串口自发自收顶层模块的代码设计

            最后我们来详细介绍串口自发自收模块的输入信号激励设计,这里就使用了“循环轮询产生激励“的方式来产生了8bit数据256种所有可能的组合,并通过测试文件的自动对比打印输出结果,节约人工观察波形的时间,极大地提高仿真测试的效率。

           为了简化代码结构,我们也把串口接收封装成一个task任务方便Testbench的灵活调用,按照串口接收的时序逻辑编写好了task_rxd_uart任务,因为在串口自发自收顶层模块的设计中,是按照先收后发的模式进行的,所以直接调用task_rxd_uart任务即可仿真模拟任何8bit串口接收的情况,在接收到串口8bit数据后,会将收到的数据再发送出去,顺其自然地再去设计好串口发送逻辑即可。

            注意到串口不论收或发,都是起始位是0,而停止位是1,空闲位是1,所以在Testbench编写中,我们用一个always块去实现串口发送逻辑,当txd_uart在下降沿的时候,即收到起始位0的时刻,整个always块内模拟实现了串口发送的过程,设置一个rs232_flag标志位,当rs232_flag被置为1时表示开始接收一包8bit包文,当rs232_flag被置为0时表示接收完成一包8bit包文。

           在Testbench的initial块内,我们遍历了0-255作为串口发送数据作为激励信号代入功能模块,当rs232_flag在下降沿的时候,即刚接收完成一包8bit包文,然后对接收到的8bit数据进行判断,如果等于发送的8bit数据,即仿真结果正确满足自发自收的测试,通过系统任务$display打印到Modelsim窗口下,如图13 是串口自发自收模块的输入信号激励设计的详细代码设计,在Vivado下添加好功能文件和测试文件,联合Modelsim仿真即可得到图14 串口自发自收模块的仿真结果,我们可以清晰地看到遍历了256种可能的8bit输入激励,Modelsim的窗口下成功打印出了仿真结果,仿真结果完全符合预期,通过上面介绍的方法整个仿真得到的结果更加具有说服力,并且省去了人工观察波形的工作,提高了整个Testbench的工作效率。

    ​​​​​ ​​​​​​​ ​​​​​​​ 图13 串口自发自收模块的输入信号激励设计

    ​​​​​​图14 串口自发自收模块的仿真结果

    展开全文
  • 如何编写一个高效的Testbench

    千次阅读 多人点赞 2022-06-16 18:55:33
    如何编写一个高效的Testbench

    文章目录

    写在前面

    概述

    介绍

    构建testbenches

    产生时钟信号

    提供激励

    显示结果        

    简单的testbenches

    自动化验证

    编写testbenches的指导方针

    高级testbenches技术

    用任务(Tasks)分解测试激励

    控制仿真中的双向信号

    有用的Verilog概念

    force和release

    assign和deassign

    timescale

    读取Memory初始化文件

    编码风格

    缩进

    文件命令

    信号命名

    注释

    设计架构

    结论


    写在前面

            在FPGA的开发过程中,验证是一个必不可少的环节,其所占时间有时候甚至还要大于RTL级的开发时间。对于大型设计,设计公司通常都有一整套完整且规范的设计流程。而对于小型设计,我们设计者则可以自己编写一个testbench对设计进行一个简单的功能验证。本文将告诉你:对于小型设计,要如何编写一个高效率的testbench。

            本文主要翻译自Xilinx《XAPP199,Writing Efficient Testbenches》,浅蓝色字体为本人理解


    概述

            本文是为刚接触HDL验证流程和没有丰富的Testbenches编写经验的逻辑设计师编写的。Testbenches是验证HDL设计的主要手段。本文提供了布局和构建高效Testbenches的指导大纲。它还提供了一种算法来为设计开发一个自检的Testbenches。


    介绍

            由于设计规模和复杂性的增加,数字化设计验证已经成为一项越来越困难和艰巨的任务。为了解决这一挑战,验证工程师使用了一些验证工具和方法。对于大型的、数百万门电路级的设计,工程师通常使用一套正式的验证工具。然而,对于较小的设计,设计工程师通常发现带有testbenches的HDL仿真工具是一种最有效的测试模式。

            Testbenches已经成为验证HLL(High-Level Language,高级语言)设计的标准方法。通常,Testbenches会实现以下任务:

    • 实例化被测模块(DUT)
    • 通过对模型应用测试向量来激励DUT
    • 输出结果到终端或波形窗口进行目视检查
    • 可以选择将实际结果与预期结果进行比较

           通常,testbenches是用行业标准的VHDL或Verilog硬件描述语言编写的。Testbenches调用被测模块,然后编写测试激励来激励它以观察期输出。复杂的testbenches会实现一些额外的功能----例如,它们包含用于确定设计的适当设计刺激或将实际结果与预期结果进行比较的逻辑。

            本文描述了一个高效的testbenches架构,并提供了一个自检testbenches的示例----它自动地比较实际输出的结果和预期输出的结果。

            图1显示了遵循上述步骤的标准HDL验证流程。

             由于testbenches是用VHDL或Verilog编写的,因此testbenches验证过程可以跨平台和工具进行移植。此外,由于VHDL和Verilog是标准的非专有语言,所以在未来的设计中,用VHDL或Verilog编写的验证套件可以轻松地被复用。

            testbenches通常包含一下部分:被测模块DUT,测试激励,测试结果比较。DUT是我们编写的RTL代码,也就是我们的功能模块;测试激励是要对DUT进行的测试输入,比如你的功能模块是一个加法器,那么你总得写个测试激励----要给2个加数赋值吧?结果比较模块的实现比较多样化:你可以直接观察波形,看看是不是自己想要的结果;也可以将结果与预期正确的结果自动做比较,如果无误就输出低电平,如果有误就输出高电平;也可以把所有的结果都保存下来,再用脚本自动比对,等等。


    构建testbenches

            testbenches可以用VHDL或Verilog编写。由于testbenches仅用于仿真,因此它们不受可综合过程中使用的RTL语言子集的语义约束的限制。因此,可以更通用地编写测试testbenches,这会使得它们更具可维护性。

            所有的testbenches都包含表1中所示的基本部分。如上所述,testbenches通常还包含额外的功能,例如在终端上显示结果和错误检测功能。

            下面的例子展示了一些在testbenches中经常使用的结构。

            上面的结构是一个testbenches一定会具备的基本结构,testbenches的编写比较灵活,因为其内容不需要综合,也就是说不需要被映射到具体的电路实现,说白了,就是设计者随心所欲的写激励来作为输入,观察被测的功能模块能否在对应测试激励下,实现符合预期条件的输出。


    产生时钟信号

            使用系统时钟来实现时序逻辑的设计必须生成一个时钟。时钟可以很容易地在Verilog源代码中实现。以下是时钟生成的例子:

    // Declare a clock period constant.
    Parameter ClockPeriod = 10;
    
    // Clock Generation method 1:
    initial begin
        forever Clock = #(ClockPeriod / 2) ~ Clock;
    end
    
    // Clock Generation method 2:
    initial begin
        always #(ClockPeriod / 2) Clock = ~Clock;
    end

            时钟信号一般使用always和Foever这两个循环来构建,方法很简单:每半个时钟周期,时钟信号翻转一次,这样就能实现周期性的占空比为50%的方波。 


    提供激励

            为了获得testbenches的验证结果,必须向被测模块提供测试刺激。在testbenches上使用并行块来提供必要的测试激励。可以采用两种方法:绝对时间激励和相对时间激励。在第一种方法中,仿真数值相对于仿真时间零指定。相比之下,相对时间激励提供初始值,然后等待事件再重新触发激励。根据设计人员的需要,这两种方法可以在一个testbenches中实现。

            下面分别提供了绝对时间激励和相对时间激励的例子:

    (1)绝对时间激励

    initial begin
    Reset = 1;
    Load = 0;
    Count_UpDn = 0;
    #100 Reset = 0;
    #20 Load = 1;
    #20 Count_UpDn = 1;
    end

            绝对时间激励使用#+时间来定义,比如#20就代表从仿真开始20个时间单位。

    (2)相对时间激励

    always @ (posedge clock)
        TB_Count <= TB_Count + 1;
    
    initial begin
    if (TB_Count <= 5) 
     begin 
     Reset = 1;
     Load = 0;
     Count _UpDn = 0;
     end
    else
     begin
     Reset = 0;
     Load = 1;
     Count_UpDn = 1;
     end
    end
    
    initial begin
     if (Count == 1100) begin
     Count_UpDn <= 0;
     $display("Terminal Count 
    Reached, now counting down.");
     end 
    end

            相对时间激励则使用某些时间来进行控制,比如时钟的上升沿啦,某个信号条变成具体的指定的值的时间啦,等等。

            Verilog中的所有initial块是一起并发执行的。但是,在每个initial块中,事件是按照写入的顺序执行的。这意味着测试激励在每个并发块中会从仿真的零时刻开始执行。设计者应该使用多个initial块将复杂的测试激励分解成多个简单的部分,从而使代码更具可读性和可维护性。


    显示结果        

            在Verilog中,通过$display和$monitor关键字可以方便地显示结果。

            以下是在终端屏幕上显示仿真结果的例子:

    // pipes the ASCII results to the terminal or text editor
    
    initial begin
     $timeformat(-9,1,"ns",12);
     $display(" Time Clk Rst Ld SftRg Data Sel");
     $monitor("%t %b %b %b %b %b %b", $realtime,
     clock, reset, load, shiftreg, data, sel);
    end

            $timeformat关键字指定时间变量的格式。$display关键字将带引号的文本("…")输出到终端窗口。$monitor关键字的工作方式不同,因为它的输出是事件驱动的。在本例中,$realtime变量(由用户分配给当前仿真时间)用于触发信号列表中值的显示。信号列表以$realtime变量开始,后面跟着要显示其值的其他信号的名称(时钟、重置、加载等)。开头的“%”关键字包含一个格式说明符列表,用于控制如何格式化信号列表中的每个信号值以便显示。格式列表是位置对应的——每个格式说明符顺序地与信号列表中的一个连续的信号名称相关联。例如,%t指示符将显示的$realtime值格式化为时间格式,而第一个%指定符将时钟值格式化为二进制格式。Verilog提供了额外的格式说明符,例如,%h用于十六进制格式,%d用于十进制格式,%o用于八进制格式。

            格式化的显示结果如图2所示。

             $display是直接显示括号里的一句话,而$monitor则是对括号内的变量进行监控,每当监控变量中的任意一个发生改变,就会在终端打印所有的变量值。


    简单的testbenches

            简单的testbenches例化被测模块,然后为其提供测试激励。Testbench输出以图形方式显示在仿真工具的波形窗口上,或者作为文本发送到用户的终端或文件。下面是一个表示移位寄存器的简单Verilog设计:

    module shift_reg (clock, reset, load, sel, data, shiftreg);
    input clock;
    input reset;
    input load;
    input [1:0] sel;
    input [4:0] data;
    output [4:0] shiftreg;
    reg [4:0] shiftreg;
    always @ (posedge clock)
    begin
     if (reset)
     shiftreg = 0;
     else if (load)
     shiftreg = data;
     else 
     case (sel)
     2'b00 : shiftreg = shiftreg;
     2'b01 : shiftreg = shiftreg << 1;
     2'b10 : shiftreg = shiftreg >> 1;
     default : shiftreg = shiftreg;
     endcase
    end
    endmodule

            以下的testbenches例化了上面的移位寄存器模块:

    module testbench; // declare testbench name
    
     reg clock;
     reg load;
     reg reset; // declaration of signals
     wire [4:0] shiftreg;
     reg [4:0] data;
     reg [1:0] sel;
    
     // instantiation of the shift_reg design below
     shift_reg dut(.clock (clock), 
        .load (load), 
        .reset (reset), 
        .shiftreg (shiftreg),
        .data (data), 
        .sel (sel));
    
     //this process block sets up the free running clock
     initial begin
         clock = 0;
         forever #50 clock = ~clock;
     end
    
     initial begin// this process block specifies the stimulus. 
         reset = 1;
         data = 5'b00000;
         load = 0;
         sel = 2'b00;
         #200
         reset = 0;
         load = 1;
         #200
         data = 5'b00001;
         #100
         sel = 2'b01;
         load = 0;
         #200
         sel = 2'b10;
         #1000 $stop;
     end
    
     initial begin// this process block pipes the ASCII results to the 
    //terminal or text editor
         $timeformat(-9,1,"ns",12);
         $display(" Time Clk Rst Ld SftRg Data Sel");
         $monitor("%t %b %b %b %b %b %b", $realtime,
         clock, reset, load, shiftreg, data, sel);
     end
    
     endmodule

            上面的testbenches例化了被测设计,设置了时钟,并提供了测试刺激。所有过程块从仿真的零时开始,并且是并发的。井号(#)指定应用下一个刺激之前的仿真时间延迟。$stop命令指示仿真工具停止testbenches仿真(所有testbenches都应该包含一个停止命令)。最后,$monitor语句将ASCII格式的结果回显给屏幕或文本文件。

            上面这个testbench算是一个比较经典且简单的测试结构,被测模块、测试激励、时钟和结果监控都有了,可以作为一个testbench的一般模板来使用。


    自动化验证

            建议自动化验证测试结果,特别是对于较大型的设计。自动化验证减少了检查设计正确性所需的时间,并将人为出错的可能性降至最低。自动化testbenches验证常用的方法有:

    • 数据库比较。首先,创建一个包含预期输出的数据库文件。然后,获取仿真输出,并与文件中的参考值与实际结果进行比较(可借助脚本工具)。但是,由于没有提供从输出到输入文件的指针,这种方法的缺点是很难跟踪错误来源。
    • 波形的比较。波形比较可以自动进行,也可以手动进行。该方法使用testbenches比较器将预期波形与testbenches的输出波形进行比较。Xilinx HDL bench工具可以实现这一自动化功能。
    • 自检Testbenches。自检Testbenches在运行时,将实际结果和预期结果进行实时比对。因为可以在测试台中构建有用的错误跟踪信息来显示设计失败的地方,所以调试时间被显著缩短。

            自检Testbenches是通过在测试台文件中放置一系列期望的向量来实现的。这些向量在定义的运行时间隔与实际模拟结果进行比较。如果实际结果与预期结果匹配,则仿真成功。如果结果没有匹配预期,则Testbenches会报告差异的地方。

            实现自检Testbenches对于同步设计来说更简单,因为预期和实际结果可以在一个时钟边缘或每“n”个时钟周期之后进行比较。比较方法也取决于设计的性质。例如,内存I/O的Testbenches应该在每次向内存位置写入新数据或从内存位置读取新数据时检查结果。如果一个设计使用了大量的组合块,在指定预期结果时则必须考虑组合延迟。

            在自检Testbenches中,预期输出将与定期运行时的实际输出进行比较,以提供自动错误检查。这种技术适用于中小型设计。然而,由于可能的输出组合随着设计的复杂性呈指数级增长,为大型设计编写一个自检Testbenches就变得更加困难和耗时。

            下面是用Verilog编写的简单的自检Testbenches的例子:

            在例化被测模块后,将设定预期的结果。在后面的代码中,将预期结果和实际结果进行比较,并将结果反馈给终端。如果每一次结构都匹配,就会显示“end of good simulation”消息。如果发生不匹配,则报告一个错误以及不匹配的期望值和实际值。

    `timescale 1 ns / 1 ps
    module test_sc;
     reg tbreset, tbstrtstop;
     reg tbclk;
     wire [6:0] onesout, tensout;
     wire [9:0] tbtenthsout;
    parameter cycles = 25;
    reg [9:0] Data_in_t [0:cycles];
    // /
    // Instantiation of the Design
    // /
    stopwatch UUT (.CLK (tbclk), .RESET (tbreset), .STRTSTOP (tbstrtstop),
     .ONESOUT (onesout), .TENSOUT (tensout), .TENTHSOUT (tbtenthsout));
     wire [4:0] tbonesout, tbtensout;
     assign tbtensout = led2hex(tensout);
     assign tbonesout = led2hex(onesout);
    ///
    //EXPECTED RESULTS
    ///
    initial begin
     Data_in_t[1] =10'b1111111110; 
     Data_in_t[2] =10'b1111111101; 
     Data_in_t[3] =10'b1111111011; 
     Data_in_t[4] =10'b1111110111;
     Data_in_t[5] =10'b1111101111; 
     Data_in_t[6] =10'b1111011111;
     Data_in_t[7] =10'b1110111111; 
     Data_in_t[8] =10'b1101111111; 
     Data_in_t[9] =10'b1011111111; 
     Data_in_t[10]=10'b0111111111;
     Data_in_t[11]=10'b1111111110; 
     Data_in_t[12]=10'b1111111110; 
     Data_in_t[13]=10'b1111111101; 
     Data_in_t[14]=10'b1111111011;
     Data_in_t[15]=10'b1111110111; 
     Data_in_t[16]=10'b1111101111;
     Data_in_t[17]=10'b1111011111; 
     Data_in_t[18]=10'b1110111111; 
     Data_in_t[19]=10'b1101111111; 
     Data_in_t[20]=10'b1011111111; 
     Data_in_t[21]=10'b0111111111; 
     Data_in_t[22]=10'b1111111110; 
     Data_in_t[23]=10'b1111111110; 
     Data_in_t[24]=10'b1111111101;
     Data_in_t[25]=10'b1111111011;
    end
    reg GSR;
    assign glbl.GSR = GSR;
    initial begin
     GSR = 1;
     // ///
     // Wait till Global Reset Finished
     // ///
     #100 GSR = 0;
    end
     
    // 
    // Create the clock
    // 
    initial begin
     tbclk = 0;
     // Wait till Global Reset Finished, then cycle clock
     #100 forever #60 tbclk = ~tbclk;
    end
    initial begin
     // //
     // Initialize All Input Ports
     // //
     tbreset = 1;
     tbstrtstop = 1;
     // /
     // Apply Design Stimulus
     // /
     #240 tbreset = 0;
     tbstrtstop = 0;
     #5000 tbstrtstop = 1;
     #8125 tbstrtstop = 0;
     #500 tbstrtstop = 1;
     #875 tbreset = 1;
     #375 tbreset = 0;
     #700 tbstrtstop = 0;
     #550 tbstrtstop = 1;
     // /
     // simulation must be halted inside an initial statement
     // /
    // #100000 $stop;
    end
    integer i,errors;
    ///
    ///
    // Block below compares the expected vs. actual results
    // at every negative clock edge.
    ///
    ///
    always @ (posedge tbclk)
    begin
     if (tbstrtstop)
     begin
     i = 0;
     errors = 0;
     end
     else 
     begin
     for (i = 1; i <= cycles; i = i + 1)
     begin
     @(negedge tbclk) 
     // check result at negedge
     $display("Time%d ns; TBSTRTSTOP=%b; Reset=%h; Expected 
    TenthsOut=%b; Actual TenthsOut=%b", $stime, tbstrtstop, tbreset, 
    Data_in_t[i], tbtenthsout);
     if ( tbtenthsout !== Data_in_t[i] )
     begin
     $display(" ------ERROR. A mismatch has occurred-----");
     errors = errors + 1;
     end
     end
     if (errors == 0)
     $display("Simulation finished Successfully.");
     else if (errors > 1)
     $display("%0d ERROR! See log above for details.",errors);
     else
     $display("ERROR! See log above for details."); 
    #100 $stop; 
     end
    end
    endmodule
    

            这种简单的自检Testbenches设计可以移植到任何测试用例中——当然,预期输出值和信号名称必须修改才能重用。如果不需要在每个时钟边缘进行检查,则可以根据需要修改for循环。

            如果仿真成功,终端屏幕会显示如下信息:

            自动化验证听起来挺唬人的,实际上简单的很:就是我先把所有预期的结果穷举出来(如果有必要的话,比如其没有规律性),然后再把得到的测试结果与其一一进行比较,如果比较结果是对的,那么我就输出高电平或者低电平;如果不对,那么我就输出相反的电平。这样,我们只需要监控这个信号,就可以知道我们的设计是否是正确的。 


    编写testbenches的指导方针

            本节提供了编写测试工作台的指导方针。正如规划一个电路设计的布局有助于实现更好的电路性能一样,规划一个testbenches的布局同样可以改善仿真验证过程。

    • (1)在编写testbenches前多了解仿真工具

            尽管常用的仿真工具符合HDL行业标准,但这些标准没有解决几个重要的特定仿真问题。不同的仿真工具具有不同的特性、性能和性能特征,则会导致其产生不同的仿真结果。

            ····基于事件VS基于循环的仿真

                    基于事件的仿真在输入、信号或门电路改变值时调度一个仿真事件。在基于事件的仿真中,延迟值可以与门电路和线网相关联,以实现最佳的定时仿真。

                    基于循环的仿真以同步设计为目标。它们优化组合逻辑,并在时钟周期分析结果。这个特性使得基于循环的仿真比基于事件的仿真更快,内存效率更高。

            ----调度事件

                    基于事件的仿真工具供应商使用不同的算法来调度仿真事件。因此,根据仿真工具使用的调度算法,在相同的仿真时间发生的事件可以以不同的顺序被调度(在每个事件之间插入                  delta延迟)。为了避免算法依赖和确保正确的结果,事件驱动的testbenches应该指定一个显式的刺激序列。

    (2)避免使用无限循环

            当事件添加到基于事件的仿真工具时,CPU和内存使用量会增加,处理会变慢。除非对testbenches至关重要,否则不应该使用无限循环来提供测试刺激。通常,时钟是在一个无限循环中指定的(例如,Verilog中的“forever”循环),而不是其他信号事件。

    (3)将测试激励分解成不同的逻辑块

            所有初始块(Verilog)都并行运行。如果不相关的激励被划分成单独的块,testbenches激励序列将变得更容易执行和检查。由于每个并发块都是相对于零仿真时间运行的,因此使用单独的块传递激励更容易。使用单独的测试激励块会产生更容易创建、维护和升级的testbenches。

    (4)不要显示不重要的数据

            大型设计的testbenches可能包含超过100,000个事件和大量信号。显示大量的仿真数据会大大降低仿真速度。最好每“n”个时钟周期只采样相关信号,以保证足够的仿真速度。


    高级testbenches技术

    用任务(Tasks)分解测试激励

            当创建大型testbenches时,测试激励应该被划分成不同的子模块,以使得代码清晰和方便修改。任务(task)可用于对信号进行划分。在下面的例子中,测试台模拟了SDRAM控制器的设计。该设计包括重复激励块,因此testbench通过声明单独的任务对激励进行划分,这些任务稍后在testbench中调用,以实现单独的设计功能。

    task addr_wr;
     input [31 : 0] address;
     begin
     data_addr_n = 0;
     we_rn = 1;
     ad = address;
     end
     endtask
    task data_wr;
     input [31 : 0] data_in;
     begin
     data_addr_n = 1;
     we_rn = 1;
     ad = data_in;
     end
     endtask
     
    task addr_rd;
     input [31 : 0] address;
     begin
     data_addr_n = 0;
     we_rn = 0;
     ad = address;
     end
     endtask
     
    task data_rd;
     input [31 : 0] data_in;
     begin
     data_addr_n = 1;
     we_rn = 0;
     ad = data_in;
     end
     endtask
    
    task nop;
     begin
     data_addr_n = 1;
     we_rn = 0;
     ad = hi_z;
     end
     endtask

            这些任务指定了设计功能的某些独立单元—地址读和地址写、数据读和数据写或nop(无操作)。一旦指定,这些任务可以在激励过程中被调用。如下:

    Initial begin
    nop ; // Nop
    #( 86* `CYCLE +1); addr_wr (32'h20340400); // Precharge, load 
    Controller MR
    #(`CYCLE); data_wr (32'h0704a076); // value for Controller MR
    #(`CYCLE); nop ; // Nop
    #(5 * `CYCLE); addr_wr (32'h38000000); // Auto Refresh
    #(`CYCLE); data_wr (32'h00000000); //
    #(`CYCLE); nop ; // Nop
    …
    …
    end

            将测试激励分解成不同的任务以使激励更容易实现,并使代码更具可读性。

            这一方法类似于C语言的子函数封装,我们可以把测试过程中要实现的激励封装到不同的任务里,这样在编写测试激励时,只需要调用对应的task即可,这样的代码看起来简洁、直观,而且这一方法实际上能有效的节约开发时间。


    控制仿真中的双向信号

            大多数设计使用了双向信号,这使得在testbenches中必须与单向信号区别对待。

            下面是使用了双向信号的设计:

    module bidir_infer (DATA, READ_WRITE);
    input READ_WRITE ;
    inout [1:0] DATA ;
    reg [1:0] LATCH_OUT ;
    always @ (READ_WRITE or DATA)
    begin
     if (READ_WRITE == 1)
     LATCH_OUT <= DATA;
    end
    assign DATA = (READ_WRITE == 1) ? 2'bZ : LATCH_OUT;
    endmodule
    

            对应的testbenches:

    module test_bidir_ver;
    reg read_writet;
    reg [1:0] data_in;
    wire [1:0] datat, data_out;
    bidir_infer uut (datat, read_writet);
    assign datat = (read_writet == 1) ? data_in : 2'bZ;
    assign data_out = (read_writet == 0) ? datat : 2'bZ;
    initial begin
     read_writet = 1;
     data_in = 11;
     #50 read_writet = 0;
    end
    endmodule

            在上面的testbenches中,data_in信号为设计中的双向信号DATA提供激励,而data_out信号则读取DATA的返回值。

            关于双向信号,建议参考:如何规范地使用双向(inout)信号?


    有用的Verilog概念

            有用的Verilog语言概念,如$monitor、$display和$time,在上面的Verilog测试示例中已经进行了讨论。本节将讨论可以在测试平台中使用的其他Verilog概念。

    force和release

            force和release语句可用于重写对寄存器或网进行的过程赋值。这些概念通常用于强制特定的设计行为。一旦释放强制值,该信号将保持其状态,直到通过过程赋值传递新值。下面是force和release语句的用法示例:

    module testbench;
    ..
    ..
    initial begin
    reset = 1;
    force DataOut = 101;
    #25 reset = 0;
    #25 release DataOut;
    ..
    ..
    end
    endmodule

    assign和deassign

            assign和deassign语句类似于force和release语句,但是assign和deassign仅适用于设计中的寄存器。它们通常用于设置输入值。与强制语句类似,assign语句覆盖过程语句传递的值。下面是assign和deassign语句的使用示例:

    module testbench;
    ..
    ..
    initial begin
    reset = 1;
    DataOut = 101;
    #25 reset = 0;
    release DataOut;
    ..
    ..
    end
    initial begin
    #20 assign reset = 1;// this assign statement overrides the earlier 
    statement #25 reset = 0;
    #50 release reset;
    endmodule

    timescale

            timescale指令用于指定testbenches的单位时间步长。这也会影响仿真工具的精度。这个指令的语法是:`timescale reference_time/precision

            Reference_time是仿真的单位时间。Precision则决定了仿真时间的精度。下面是一个使用“timescale”的例子:

    `timescale 1 ns / 1 ps
    // this sets the reference time to 1 ns and precision to 1 ps.
    module testbench;
    ..
    ..
    initial begin
    #5 reset = 1; // 5 unit time steps correspond to 5 * 1ns = 5ns in 
    simulation time
    #10 reset = 0;
    ..
    end
    initial begin
    $display (“%d , Reset = %b”, $time, reset); // this display 
    // statement will get executed 
    // on every simulator step, ie, 1 ps.
    end
    endmodule

            如果仿真使用时间延迟值,则模拟必须运行在比最小延迟更大的精度。例如,如果在仿真库中使用9ps延迟,仿真的精度必须为1ps以适应9ps延迟。

            关于`timescale指令,建议参考:你真的会用`timescale吗?


    读取Memory初始化文件

            Verilog提供了 $readmemb 和 $readmemh 命令来读取一个ASCIl文件,以便初始化Memory 。语法如下:$readmemb (“<design.mif>”, design_instance);

            MIF是创建的Memory初始化文件。用户可以指定MIF文件中的内容。


    编码风格

            下面是一些有助于创建更好可阅读性和可维护性RTL的编码风格。

    缩进

            缩进代码以使其更易读。建议缩进宽度为三到四个空格。缩进宽度为5个或5个以上的空格通常在右边边缘留下的空间很小,而缩进宽度小于3个空格会导致缩进过小。

    文件命令

            保持".v“(Verilog)或”.vhd" (VHDL)的扩展文件名。如果更改了这些标准扩展名,一些编辑器可能无法识别源文件。

    信号命名

            所有用户信号使用相同的小写字母。Verilog是大小写敏感的,错误的大写可能导致设计无法进行综合和模拟。此外,使用一致的信号名称格式样式会使信号名称更容易在源文件中定位。使用简短、描述性的信号名称。简短的名称更容易输入,描述性的名称有助于识别信号功能。

    注释

            大量编写注释。注释对于接手和重用代码的其他人来说是无价的。大量注释代码,填充了重要的细节,大大提高了源代码的清晰度和可重用性。

    设计架构

            一个文件对弈一个module。单独的模块对应单独的文件会使设计更容易维护。


    结论

            testbenches为工程师提供了一个便携的、可升级的验证流程。随着混合语言仿真器的使用,设计人员可以自由地使用他们选择的HDL语言来验证VHDL和Verilog设计。高级行为语言有助于创建使用简单构造的testbenches,并且只需要最少的源代码。在仿真过程中自动验证正确设计结果的自检testbenches有助于RTL的设计开发。

    展开全文
  • 博客【Verilog实战】AMBA 3 APB接口设计的RTL、Test bench、makefile和tcl文件
  • 目录序言变量定义时钟设计设计输入模块例化实战演练 序言 由于入门的测试文件很简单...TestBench文件,又称为TB文件,是用来对设计文件进行测试的程序,它与设计文件不同的地方在于,它使用的语法可以是不可综合的...

    序言

    由于入门的测试文件很简单,所以一直以来也都是直接给出测试文件,直到今天才想着去总结一个测试文件的写法。这篇博客将根据HDLBits的题目来总结如何书写Testbench文件,肯定有不完善的地方,仅仅作为一次总结吧。
    TestBench文件,又称为TB文件,是用来对设计文件进行测试的程序,它与设计文件不同的地方在于,它使用的语法可以是不可综合的,比较灵活,不仅仅可以使用Verilog来写Tb文件,还可以使用其他语言,例如sv等。
    写好一个基本的测试文件比较简单,但是写好一个比较完善的测试文件还是比较具有挑战性的。
    Tb文件包括那些组件呢?

    1. 变量定义;
    2. 时钟生成;
    3. 待测试模块输入设计;
    4. 例化待设计模块。
      那我们就开始吧。

    变量定义

    这个比较简单,所谓的变量定义,就是将待测试设计的输入在Tb文件中进行定义,方便后面对其数值进行设计。
    待测试模块的输入定义为reg类型,因为我们要对其进行设计,输出定义为wire类型,它作为例化模块的输出,所以必须定义为wire类型。
    例如,我们要测试一个D触发器:

    module dff(
    input clk,
    input d,
    input areset,
    output q;
    );
    reg q_mid;
    always@(posedge clk or posedge areset ) begin
    	if(areset) q_mid <= 0;
    	else q_mid <= d;
    end
    assign q = q_mid;
    endmodule
    

    我们在写测试文件的时候,对其输入输出进行定义,如下:

    module test_dff();
    // input define
    reg clk;
    reg d;
    reg areset;
    // output define
    wire q;
    
    //other parts
    //......
    endmodule
    

    好了,这部分讲完了,进入下一部分,时钟生成。

    时钟设计

    对于时钟的设计,我们有两种写法,都是通过循环的方式来实现。

    • forever
    • always
      第一种方式,也是我比较喜欢的方式,使用forever进行时钟的设计:
      还是以上一个例子为例,待测试模块为dff:
    module test_dff();
    // input define
    reg clk;
    reg d;
    reg areset;
    // output define
    wire q;
    
    localparam PERIOD = 4;
    // clk generate
    initial begin
    	clk = 0;
    	forever 
    		# (PERIOD/2) clk = ~clk; 
    end
    //other parts
    //......
    endmodule
    

    可以看到,时钟生成部分很容易,需要注意的是必须在initial的内容进行输入的设计(时钟也属于输入),可以这么说,测试文件的reg(输入)几乎都是在initial内部完成的,这是一种有时间顺序的系列行为。
    另一种方式是使用always的方式来设计时钟,如下:

    module test_dff();
    // input define
    reg clk;
    reg d;
    reg areset;
    // output define
    wire q;
    
    localparam PERIOD = 4;
    // clk generate
    initial begin
    	clk = 0;
    end
    always begin
    	#(PERIOD/2) clk = ~clk;
    end
    
    //other parts
    //......
    endmodule
    

    可以看出区别,这种方式是在initial里面首先将clk初始化为0,之后在initial外部进行clk循环反转设计。
    这是规则,需要严格遵守。
    介绍完这两种方式之后,我们来看看HDLBits中对应这一部分的例题:
    Tb/clock
    原题复现:
    You are provided a module with the following declaration:

    module dut ( input clk ) ;

    Write a testbench that creates one instance of module dut (with any instance name), and create a clock signal to drive the module’s clk input. The clock has a period of 10 ps. The clock should be initialized to zero with its first transition being 0 to 1.
    在这里插入图片描述
    好吧,给出我的设计:

    module top_module ( );
        reg clk;
        initial begin
           clk = 0;
            forever 
            #5 clk = ~clk;
        end
        
        dut inst_dut(
            .clk(clk)
        );
    
    endmodule
    

    在这里插入图片描述

    设计输入

    其实时钟也是输入,输入的设计和时钟的设计方式没有什么差别,只不过更加的多样化,我们可以使用task,function等等,当然也可以直接设计。
    这里就不那么多废话了,突然感觉前面说了很多多余的东西,这里就怎么简单怎么来吧。
    看这个例子:
    input design
    原题复现:
    Create a Verilog testbench that will produce the following waveform for outputs A and B:
    在这里插入图片描述
    我的设计:

    module top_module ( output reg A, output reg B );//
    
        // generate input patterns here
        initial begin
            A = 0;
            B = 0;
            #10
            A = 1;
            #5
            B = 1;
            #5
            A = 0;
            #20
            B = 0;
    
        end
    
    endmodule
    
    

    当然,在实际写tb文件时候,肯定不是这样的整体格式,这里是为了适应做相应的题目需要。
    实际中的格式是:

    module top_module (  );//
    
        // generate input patterns here
        initial begin
            A = 0;
            B = 0;
            #10
            A = 1;
            #5
            B = 1;
            #5
            A = 0;
            #20
            B = 0;
    
        end
    
    endmodule
    
    

    模块例化

    模块例化是测试文件中必须的,因为我们写测试文件的目的就是测试待测试模块。
    举个例子,我们下面来测试一个与门:
    题目链接
    You are given the following AND gate you wish to test:

    module andgate (
        input [1:0] in,
        output out
    );
    

    Write a testbench that instantiates this AND gate and tests all 4 input combinations, by generating the following timing diagram:
    在这里插入图片描述
    我的设计:

    module top_module();
        reg [1:0] in;
        wire out;
        
        initial begin
           in = 2'b00;
            #10
            in = 2'b01;
            #10
            in = 2'b10;
            #10
            in = 2'b11;
        end
        
        andgate inst(
            .in(in),
            .out(out)
        );
    
    endmodule
    
    

    实战演练

    题目链接
    The waveform below sets clk, in, and s:
    在这里插入图片描述
    Module q7 has the following declaration:

    module q7 (
        input clk,
        input in,
        input [2:0] s,
        output out
    );
    

    Write a testbench that instantiates module q7 and generates these input signals exactly as shown in the waveform above.
    我的设计:

    module top_module();
        
        reg clk;
        reg in;
        reg [2:0] s;
        wire out;
        
        initial begin
           clk = 0;
           forever begin
              #5 clk = ~clk; 
           end
        end
        
        initial begin
        	in = 0;
            s = 2;
            #10
            s = 6;
            #10
            in = 1;
            s = 2;
            #10
            in = 0;
            s = 7;
            #10
            in = 1;
            s = 0;
            #30
            in = 0;
        end
        
        q7 inst(
            .clk(clk),
            .in(in),
            .s(s),
            .out(out)
        
        );
        
        
        
    
    endmodule
    
    

    在加上一个题目:
    Tff testbench
    You are given a T flip-flop module with the following declaration:

    module tff (
        input clk,
        input reset,   // active-high synchronous reset
        input t,       // toggle
        output q
    );
    

    Write a testbench that instantiates one tff and will reset the T flip-flop then toggle it to the “1” state.
    我的设计为:

    module top_module ();
        
        reg clk;
        reg reset;
        reg t;
        wire q;
        
        initial begin
           clk = 0;
           forever 
               #2 clk = ~clk;
        end
        
        initial begin
           reset = 1;
           t = 0;
           #4
           reset = 0;
            t = 1;
           
        end
        
        tff inst(
            .clk(clk),
            .reset(reset),
            .t(t),
            .q(q)
        );
        
    
    endmodule
    
    

    这篇博文就到这里吧,这是最基础,最简单的测试文件的写法,当然,基础很重要,在此基础上,才能够在上一层楼。

    展开全文
  • TestBench基本写法与语法详解

    千次阅读 多人点赞 2021-10-13 14:12:49
    而 RTL 逻辑设计中,学会根据硬件逻辑来测试程序,即Testbench 是尤其重要的。 Verilog 测试平台是一个例化的待测(MUT) 模块,重要的是给它施加激励并观测其输出。逻辑模块与其对应的测试平台共同组成仿真模型,...
  • testbench读写文件

    2022-02-16 19:55:17
    文件: $readmemh("/home/gaojiahua/Downloads/8K-colorbar/0-1_SF.dat",...写文件: reg [95:0] wr_txt; integer w_file; initial w_file = $fopen("/home/gaojiahua/data_d.txt"); $fwrite(w_file,"%h\n",wr_txt);
  • 手动仿真在项目开发中是比较常用的,此时需要手动编写testbench文件。对于初学者来说,可能觉得编写testbench文件比较困难,但其实并没有想象的那么复杂,我们只需要按照testbench的结构,编写基本的激励文件还是...
  • 用vhdl写testbench文件的简单方法

    万次阅读 多人点赞 2018-03-30 10:12:05
    Vhdl -- 写Testbench 1 六进制计数器的代码 Library ieee; use ieee.std_logic_1164.all; use ieee.std_logic_arith.all; use ieee.std_logic_unsigned.all; entity cnt6is port (clr,en,clk ...
  • testbench读取txtx文件进行仿真。
  • 借鉴UVM的测试方法,在tb中将初始数据和结果数据写入txt文件,处理完成后,读出来,进行逐一对比,并打印结果。无需肉眼一一对比,将结果用计算机自动对比,减少人工工作量,提高了准确率。
  • 这是个串口通信的Verilog代码,代码简单明了。在顶层收到PC一个字节然后再发给PC。适合初学者使用
  • 使用IVerilog进行编译—使用icarus verilog编译verilog文件修改testbench文件tb.v以更新模块DUT名称,用于读取输入激励数据的文件名称以及用于存储生成的输出的文件名称 iverilog -o output module.v tb.v vvp ...
  • 博文【异步FIFO的设计和功能验证】的源码,包含异步FIFO模块的RTL代码文件Testbench 代码文件、tcl和makefile脚本文件
  • 添加testbench文件、仿真
  • verilog testbench.rar

    2021-10-31 11:39:03
    verilog testbench编写指南,此文档个人整理的经典资料,内容全,入门容易
  • 2、测试文件的变量只需要定义,而不需要成端口,因为不需要绑定管脚。 3、测试文件的内容包括变量定义、变量初始化、变量赋值、实例化。 3、变量定义都在模块的开头,初始化只在上电后执行一次,初始化、变量...
  • 通过SPI协议,对ADC中的寄存器进行配置。适用于大部分spi接口的ADC配置,例如高速ad hmcad1511,hmcad1520,ADS8694系列等等。也可以参考其中的spi时序的...总之,实现了一个三线spi的master工程,带testbench文件
  • 弄了好长时间vhdl,一直对testbench很迷惑。前几天静下心来好好看了下资料,终于会简单的testbench了。
  • ----------------------------------------------------------------------------------- --文件写 ----------------------------------------------------------------------------------- process ( clk _ in ) ...
  • 这里要仿真的模块是example,所以就例化上去,注意看这个例化的模块输入时clk所以把testbench内部变量进去,就算是模拟的输入。 同样需要一个wire线来接到example的输出。 好了最后加上endmodule就可以了。 -----...
  • vivado中FFT核的调用配置及Verilog HDL 版本的testbench.v文件
  • 花了两天时间才基本弄清楚Modelsim独立仿真的流程及testbench文件的编写,在这里分享一下供大家参考。这里以我编写的分频器工程div为例。(一)Modelsim仿真流程一、首先打开Modelsim,创建工程创建工程如图1,先取一...
  • 流程:先用$fopen打开文件,获取文件句柄,再$fdisplay写入数据,再用$fclose关闭文件 代码示例: file_1 = $fopen("D:/xilinx_project/comp/comp.srcs/sim_1/new/test1.txt","wb"); if(!file_1) begin $fclose(file...
  • 二、DUT的输入由单独的一个文件产生,在testbench中实例化两上entity,可以复杂输入,简单输出的模块。模型如图2所示三、DUT的输入与测试输出各由单独文件产生,在testbench由三个实例化模块产生,用于具有复杂输入...
  • 时序控制的串并转换模块,其中包括并行转串行子模块和串行转并行子模块,主时钟24Mhz;在安装了modelsim之后,直接运行testbench文件可以获得仿真结果。

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 15,925
精华内容 6,370
关键字:

如何写testbench文件