第四章:连接设计和测试平台

验证一个设计需要经过几个步骤:生成输入激励,捕获输出相应,决定对错和衡量进度。要完成这个设计,首先第一步就是如何将DUT(Design Under Test)连接到测试平台。这一章的内容就来解决一下这个问题。

4.1将测试平台和设计分开

测试平台的代码独立于设计的代码,设计者需要编写满足规范的代码,而验证工程师需要创建使得设计不满足设计规范的场景。使用模块来保存测试平台经常会引起驱动和采样时的时序问题,在SystemVerilog引入程序块(program block),从逻辑和时间上来分开测试平台。

4.1.1测试平台和DUT之间的通信

随着设计复杂度的增加,模块之间的连接也变得复杂。在本章中,为了让大家更好地了解测试平台与DUT之间的连接关系,我们以搭建仲裁器的测试平台为例进行分析。
测试平台与DUT之间的端口连接.png

通常在我们Verilog的端口描述中是比较繁琐的,现在就这个例子,我们来看一下复杂的端口描述。
1、使用端口的仲裁器模型

      module arb_port (output logic[1:0] grant,
                  input logic[1:0] request,
                  input logic rst,
                  input logic clk );
       always@(posedge clk or posedge rst)
                 begin
                  if(rst)
                        grant<=2'b00;
                   else
                     ......
                  end
       endmodule

2、测试平台,测试平台在另一个模块中,与设计所在的模块相互独立。

        module test (input logic[1:0],
                        input logic clk,
                        output logic[1:0] request,
                        output logic rst);
         initial 
          begin
              @(posedge clk) request<=2'b01;
               $display("@%0t:Drove req=01",$time);
              repeat(2) @(posedge clk);
               if(grant!=2'b01)
                     $diaplay("@%0t:a1:grant!=2'b01",$time);
               ......
              $finish;
          end
        endmodule

3、顶层网单连接测试平台和DUT,并包含一个简单的时钟发生器

         module  top;
         logic[1:0] grant, request;
         bit clk,rst;
          always #5 clk=~clk;
          arb_port a1 (grant,request,rst,clk);
          test t1 (grant,request,rst,clk);
         endmodule

虽然通过端口连接,上面的例子看上去并不是太复杂,但是在实际的真实设计中往往含有数百个端口信号,需要数页代码来声明信号和端口。所有的这些连接都是极易出错的。如果你想添加一个新的信号,它必须在多个文件中定义和连接。针对以上问题,SystemVerilog都有相应的解决方案。

4.2接口

SystemVerilog使用接口为块之间的通信建模,接口看成一捆智能的连线。接口包含了连接、同步、甚至两个或者更多块之间的通信,它们连接了设计块和测试平台。

4.2.1使用接口来简化连接

我们将端口捆绑成一个接口。接口扩展到测试平台和DUT的驱动和接收功能模块。时钟可以是接口的一部分或者是一个独立的端口。
横跨两个模块的接口.png
1、仲裁器的简单接口

      interface arb_if (input bit clk);
                     logic[1:0] grant,request;
                     logic rst;
      endinterface

2、使用接口的仲裁器模型

     module arb (arb_if arbif);
     always@(posedge arbif.clk or posedge arbif.rst)
                  begin
                  if(arb.rst)
                        arb.grant<=2'b00;
                   else
                     ......
                  end
      endmodule

3、使用接口的测试平台

      module test (arb_if arbif);
       initial 
          begin
              @(posedge arbif.clk) arbif.request<=2'b01;
               $display("@%0t:Drove req=01",$time);
              repeat(2) @(posedge arbif.clk);
               if(arbif.grant!=2'b01)
                     $diaplay("@%0t:a1:grant!=2'b01",$time);
               ......
              $finish;
          end
        endmodule

4、顶层模块的例化

        module  top;
         bit clk;
          always #5 clk=~clk;
          arb_if arbif(clk);
          arb a1(arbif);
          test t1 (arbif);
         endmodule:top

       module  top;
         bit clk;
          always #5 clk=~clk;
          arb_if arbif(.*);
          arb a1(.*);
          test t1 (.*);
         endmodule:top

*顶层模块将1、模块里描述的设计2、程序块中的测试平台3、接口连接起来;快捷符号 .**(隐士端口连接),能自动在当前级别自动连接模块实例的端口到具体信号,只要端口和信号的名字和数据类型相同。
需要注意一下几点:

  • 在搭建测试平台时,接口信号必须使用非阻塞赋值来驱动。
  • 使用接口时需要确保在你的模块和程序块之外声明接口变量。

    4.2.2 连接接口和端口

    可以直接将接口的信号连接到端口上。

     module  top;
         bit clk;
          always #5 clk=~clk;
          arb_if arbif(clk);
          arb a1(.grant(arbif.grant),
                 .request(arbif.request),
                 .rst(arbif.rst),
                 .clk(arbif.clk));
          test t1 (arbif);
         endmodule:top
    4.2.3 使用modport将接口中的信号分组

    前面的接口定义中,并没有提及信号的方向,而是使用了点对点的无信号方向的连接方式。在接口中使用modport结构能够将信号分组并指定方向。
    1、带有modport的接口

      interface arb_if (input bit clk);
                     logic[1:0] grant,request;
                     logic rst;
      modport TEST(output request,rst,
                   input grant, clk);
      modport DUT (input request, rst, clk,
                 output grant);
      modport MONITOR(input request,grant,rst,clk);
      endinterface

    2、使用modport接口的仲裁器模型

       module arb (arb_if.DUT arbif);
                     ......
       endmodule

    3、使用modport接口的测试平台

    module test (arb_if.TEST arbif);
    ......
     endmodule

    4、顶层模块
    顶层模块与不带modport是一样的,只在DUT和测试平台上有微小的差距。尽管代码没有多大的变化,这个接口更加确切地代表了一个真实的设计,尤其是信号方向。
    注意:在上面定义接口时,我们使用了modport MONITOR(参考1),它能够将测试平台连接到一个新增加的monitor模块。

     module monitor (arb_if.MONITOR arbif);
      always@(posedge arbif.request[0])
       ......
     endmodule

    4.3 激励时序

    在时钟周期级的测试平台,你需要在相对于时钟信号的合适的时间点驱动和接收同步信号。驱动得太晚或者采样得太早,测试平台的动作就会错过一个时钟周期。SystemVerilog有几种结构可以帮助你控制通信中的时序问题。

    4.3.1使用时钟块控制同步信号的时序
    接口块可以使用时钟块来指定同步信号相对于时钟的时序。时钟块大都在测试平台中使用。
  • 时钟块中的任何信号都将同步地驱动或采样,这就保证了测试平台在正确的时间点与信号交互。
  • 一个接口可以包含多个时钟块,因为每个块中都只有一个时钟表达式,所以对应一个时钟域。

     interface arb_if (input bit clk);
                       logic[1:0] grant,request;
                     logic rst;
       clocking cb @(posedge clk);
                   output request;
                    input grant;
       endclocking
       modport TEST(clocking cb,
                  output rst);
      modport DUT (input request, rst,
                 output grant);
      endinterface
  • 从上面的例子中,我们看出TEST modport将request和grant视为同步信号。
  • 当在时钟块中使用modport时,任何同步接口信号的前面都必须加上接口名和时钟块名。arbif.cb.request是合法的,而arbif.request是不合法的。

       module test (arb_if.TEST arbif);
       initial 
       begin
         arbif.cb.request<=0;
         @arbif.cb;
          $diaplay("@%0t:grant!=%0b",$time,arbif.cb.grant);
        end
      endmodule
  • arbif.cb表示时钟的有效沿,而不需要描述确切的时钟信号和边沿。arb.cb.request<=0,表示在时钟的有效沿将0的值赋给request。

  • 接口信号采用的是非阻塞赋值。

    4.3.2接口中的logic和wire对比

    我们建议在接口中将信号定义为logic类型。logic类型的语句可以在过程语句中进行赋值也可以直接驱动,wire类型只能采用连续赋值assign。

    4.3.3程序块(program block)和时序区域(timing region)

    测试平台应该不仅在逻辑上而且在时序方面独立于设计。通常测试平台和设计之间会存在竞争状态。会出现这种问题的根源在于设计和测试平台的事件混在同一个事件片内。
    如果存在一种可以在时间轴上分开这些事件的方法,确保能够在所有事件执行完毕后,测试平台开始下一个动作。那么将会很好地解决时序上的问题。
  • SystemVerilog是如何将测试平台的事件与设计的事件分开调度呢?
    在SystemVerilog中,测试平台的代码在一个程序块中,这个与模块非常类似,但是,程序块不能有任何的层次级别,例如模块的实例、接口或者其他程序。
  • SystemVerilog引入一种新的时间片的划分方式。
    SystemVerilog时间步长内的主要区域.png
    Active:仿真模块中设计代码
    Observed:执行SystemVerilog断言
    Reactive:执行程序中测试平台部分
    Postponed:为测试平台的输入采样,所有设计活动都结束的只读时间段采样信号。

      program automatic test (arbif.TEST arbif);
      ...
       initial 
           begin
                arbif.cb.request<=2'b01;
                repeat(2) @arbif.cb;
                if(arbif.cb.grant!=2'b01);
            end
        endprogram:test

    上面的测试代码应当包含在一个单个的程序块中。

    4.3.4 仿真的结束
  • 第一种:遇到$finish结束
  • 第二种:如果仅有一个程序块,那么当完成所有initial块中的最后一个语句时,仿真就结束了。如果存在多个程序块,仿真在最后一个程序块结束时结束。
  • 第三种:执行$exit可以提前终断任何一个程序块。

    4.4 接口的驱动和采样

    测试平台需要驱动和采样设计的信号,这主要是通过带有时钟块的接口做到的。

    4.4.1接口信号采样

    接口要采样的信息对设计的输出信号进行采样。当你从时钟块中读取一个信号的时候,你是在时钟之前得到采样值。

      ·timescale 1ns/1ns;
       program test (arcif.TEST arbif);
              initial 
                   begin
                         $monitor("@%0t:grant=%h",$time,arbif.cb.grant);
                            #50 $display("End of test");
                     end
          endprogram

        module arb(arb_if.DUT arbif);
                  initial
                      begin
                            #7 arbif.grant=1;
                            #10 arbif.grant=2;                        
                            #18 arbif.grant=3;
                        end
         endmodule
  • arbif.grant由一个模块驱动,可以使用阻塞赋值。
  • 在测试平台中的program块中,当在时钟块中使用modport的时候,任何同步的接口信号都必须是arbif.cb.grant的形式。
    同步接口的采样.png

    4.4.2接口信号驱动
    时钟块的默认时序是在#1step延时之后采样输入信号,在#0延时之后驱动输出信号
  • #1step延时规定了信号在前一个时间片的Postponed区域,在设计有任何新的动作之前被采样。
  • #0,因为时钟模块的原因,测试平台的输出信号是同步的,所以他们直接送入设计中。

          program test(arb_if.TEST arbif);
                  initial
                      begin
                            #7 arbif.cb.request<=3;
                            #10 arbif.cb.request<=3;                       
                            #18 arbif.cb.request<=3;
                            #15
                        end
           endprogram

       module arb(arb_if.DUT arbif);
                  initial
                     $monitor("@%0t:req=%h",$time,arbif.request);
       endmodule

驱动一个同步时钟接口.png
从上面的例子我们可以看出,如果测试平台在时钟的有效沿驱动同步接口信号,那么其值将会立即传递到设计中。

    ##2 arbif.cb,request<=0;
    ##2;//非法

表示等待两个时钟周期以后再驱动信号,后面必须跟着赋值语句否则是非法的。

4.4.3为什么在程序块(program)中不允许使用always块?

在SystemVerilog中,你可以在program中使用initial块,但是不能使用always块。这是因为在一个设计中,always块可能从仿真开始就在每一个时钟的上升沿进行触发执行。但是一个测试平台的执行过程是经过初始化、驱动和相应设计行为等步骤后结束仿真的。在这里,一个连续执行的always块不能正常工作。

  • 如果在program块中加入always块,它将永远不会结束,这样必须调用$exit来发出程序块结束的信号。
  • 如果你确实需要一个always块,你可以使用“initial forever”来完成相同的事情。

    4.4.4时钟发生器

    时钟与其说跟测试平台结合得比较紧密,倒不如说它跟设计结合的更加紧密,所以时钟发生器应当定义成一个模块,而不是一个程序块,如果把时钟发生器放在program中,在Reactive中开始传递,到达Active中信号的先后顺序有可能引起0时刻的竞争。所以时钟边沿使用阻塞赋值生成,它们将在Active区域触发事件的生成。

      module clock_generator(output bit clk);
        initial
            always #5 clk=~clk;
      endmodule

    4.5 顶层作用域

    在Verilog中,只有宏定义可以跨越模块的边界,而且经常被用来创建全局变量。SystemVerilog引入了编译单元(compilation unit),它是一起编译的源文件的一个组合。任何module,interface,program,package等边界之外的作用域被称为编译单元作用域,也成为$unit。
    这个作用域的任何成员,比如parameter,都类似于全局成员,因为他们可以被所有低一级的块访问,但是又不同于真正的全局成员,比如parameter在编译时其他源文件不可见。下面的例子就显示了编译单元parameter、const的用法。

    `timescale 1ns/1ns
     parameter int TIMEOUT=1_000_000;
     const string time_out_msg="ERROR";
    
     module top;
          test t1();
     endmodule
    
     program automatic test;
      ......
      initial 
        begin
          # TIMEOUT;
          $display("%s",time_out_msg);
          $finish;
          end
     endprogram
    实例名$root允许你从顶层作用域开始明确地引用系统中的成员名。在这一点上它类似于Unix文件系统中的“/”。你可以通过$root指定绝对路径明确地指引跨模块的变量。下面给出几种跨模块引用的用法。在program块中,引用module top中的clk信号。
    绝对引用
  • $root.top.clk
  • 利用宏定义

    define TOP $root.toptop.clk
  • 相对引用 top.clk

    4.6 程序——模块交互

    程序块可以读写模块中的所有信号,可以调用模块中所有历程,这些历程可以改变内部信号的值,但是模块却看不到程序块。这是因为测试平台需要访问和控制设计,但是设计却又独立于测试平台的任何东西。
    在测试平台中使用DUT中的函数获取信息,模块中的函数可以封装两者之间的通信,并使得测试平台更便捷地跟设计保持同步。

    4.7 SystemVerilog断言

    使用SystemVerilog断言(SVA)在你的设计中创建时序断言。仿真器会跟踪哪些断言被激活,这样就可以在此基础之上收集功能覆盖率的数据。
  • 立即断言:Immediate Assertion

    bus.cb.request<=1;
    repeat(2) @bus.cb;
    a1:assert (bus.cb.grant==2'b01);
    我们期望assert里面的内容是真的,否则输出错误信息。类似于if语句,但是等价的过程代码可能远比这些断言要复杂和冗长。
  • 定制断言

    a1:assert (bus.cb.grant==2'b01)
    else $error("Grant not asserted");
    assert断言可选then和else分句来改变默认信息,可以添加你自己想要的输出信息。
    SystemVerilog有四个输出消息的函数:$info $warning $error $fatal。这些函数仅限于在断言内部使用。
  • 并发断言

     interface arb_if(input bit clk);
       logic[1:0] grant,request;
       logic rst;
    
      property request_2state;
              @(posedge clk) disable iff (rst);
              $isunknown(request)==0;
       endproperty
       assert_request_2state:assert property(request_2state);
     endinterface

    request信号除了在复位期间,其他任何时候都不是X或Z。
    1、它是一个连续运行的模块,它为整个仿真过程检查信号的值。
    2、在断言内指定了一个采样时钟。

    4.8 ref端口

    SystemVerilog引入了一种新的端口方向:ref。在Verilog中,我们熟悉的端口方向有:input、output、inout。
    如果使用多个inout端口驱动一个信号,SystemVerilog将会根据所有驱动器的值、驱动强度来计算最终的信号值。
    如果是多个ref端口驱动一个变量,就有可能产生竞争,因为多个模块的端口都可能更新同一变量。ref端口其实是对变量(不能是net)的引用,它的值是该变量最后一次赋的值。
    小结:使用到目前为止所学的知识,你可以为一个比较大的设计中的某一模块创建一个简单的测试。在接下来的学习中,我们将给大家介绍一下怎样创建随机测试,以及如何使用OOP来组织测试代码。

猜你喜欢

转载自www.cnblogs.com/xuqing125/p/9098515.html