绿皮书


验证设计的步骤:生成输入激励,捕获输出相应,决定对错和衡量进度。

测试平台的作用:1.包裹着设计,发送激励并且捕获设计的输出。2.测试平台组成了设计周围的"真实环境",模拟设计的整个运行环境。eg:1.一个处理器模型需要连接不同的总线和器件,这些总线和期间在测试平台中被建模成总线功能模型。2.一个网络设备连接多个输入和输出数据流,这些数据流根据标准的协议建模。3.一个视频芯片连接送入指令的总线,然后根据写入内存模型的数据重构图像。3.除了DUT(Design Under Test)的行为之外,测试平台(TestBench)还仿真了其他所有行为。

verilog的端口缺陷:1.描述繁琐,代码容易产生连接错误,所以测试平台需要一种更高层次的方法与设计建立通信(但是2005 连接 有所改进)。2.verilog 模型中常见的竞争状态。需要一种可靠描述时序的方法,可以在正确的时间点驱动和采样同步信号。(实际在UVM 也不采用program,????why)

1.将测试平台和设计分开

  原因:1.开发过程(设计小组:阅读最初的设计规范书(Sepc.),然后做出解释,编写满足规范的代码。设计小组:阅读最初的设计规范书(Sepc.),然后做出解释,创建使得设计不满足设计规范的场景(scenarios)),测试平台的代码与设计的代码独立,在不同的module中。但是使用module保存测试平台经常会引起驱动和采样的时序问题。2.随着设计复杂度的增加,模块之间的连接也变得复杂。两个RTL模块之间可能有几十个连接信号,这些信号必须按照正确的顺序排列使得它们能够正常通信,而且任何层次的错误就会导致设计无法正常工作。eg:1.错误地交换了两个电平偶尔才会翻转的管脚,你可能很长时间都找不到问题的根源。2.在两个模块中增加一个新信号时,不仅需要修改模块代码来增加新的宽端口,还需要编辑上一层次中连接器件的模块。

解决方案:

​ 1.sv引入程序块(program block),从逻辑上和时间上分开测试平台。见1.3;(实际不推荐使用)

​ 2.使用接口。代码一捆连线的结构,它可以描述时序(CB,Clock Block),信号方向(modport input output),甚至增加功能性代码。还可以向模块一样例化,也可以像信号那样连接到端口。(端口 VS 接口 :接口软件硬件交互接口 类似 JAVA的 接口类,端口是硬件)

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

例1:使用端口的仲裁器模型(DUT)

sv拓展了传统的reg类型,可以像wire一样连接块。引入:logic。

注意点:1.唯一不能使用logic变量 就是含有多个驱动的连线,必须使用连线类型。例如:wire。

2.绿皮书建议将接口信号声明为logic,但是验证方法学(UVM) 的建议是wire。区别是主要注重于logic的易用性,UVM注重代码的可重用性。但是UVM中采用的方式更具有远见性。考虑到如何将测试代码用于未来的项目。eg:如果接口信号全部都是logic,如果有一个信号有多个元件驱动,工程师不得不将logic改为wire,并且当该信号不穿过任意一个时钟块,需要修改过程赋值语句。这样就有了两个版本的接口,现有代码用于新的项目之前比如修改。这与UVM的原则不符。

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] grant,
             output logic[1:0] request,
             output logic rst,
             input logic clk);
    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       

顶层网单连接了测试平台和DUT,并且包含有一个简单地时钟发生器(clock generator)。

例3:没有接口的顶层模块

module top;
    logic[1:0] grant, request;
    bit clk,rst;
always #5 clk=~clk; //时钟发生器
    arb_port a1 (grant,request,rst,clk); //例1 使用端口的仲裁器模型
    test t1 (grant,request,rst,clk);    //例2 使用端口的测试平台
endmodule

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

2.接口 interface

定义:SystemVerilog使用接口为块之间的通信建模,接口看成一捆智能的连线。

作用:1.接口包含了连接、同步、甚至两个或者更多块之间的通信功能,错误检查等;2.它们连接了设计块和测试平台。

2.1 使用接口来简化连接

我们将端口捆绑成一个接口。接口扩展到测试平台和DUT的驱动和接收功能模块。时钟可以是接口的一部分或者是一个独立的端口。

最简单的接口仅仅是一组双向信号的组合。这些信号使用logic数据类型,可以使用过程语句驱动。

例4:仲裁器的简单接口

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

例5 使用了简单接口的仲裁器模型

module arb (arb_if arbif); //DUT使用接口,接口实例名arbif
    ......
    always@(posedge arbif.clk or posedge arbif.rst)
        begin
        if(arbif.rst)
            arbif.grant<=2'b00;
        else
        ......
        end
endmodule

可以通过使用实例名arbif.request来引用接口的信号。接口信号必须使用非阻塞赋值来驱动。

例6 使用简单仲裁器接口的测试平台

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

建议:所有的这些块都在top模块中例化和连接。

例7 使用简单仲裁器接口的top模块

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

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

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

2.2 连接接口和端口

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

例9 直接将接口的信号连接到端口上

module  top;
    bit clk;
    always #5 clk=~clk;
    arb_if arbif(clk);
    arb a1(.grant(arbif.grant), //.port(ifc.signal)
           .request(arbif.request),
           .rst(arbif.rst),
           .clk(arbif.clk));
    test t1 (arbif);
endmodule:top

2.3 使用modport将接口中的信号分组

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

下例代码中的monitor modport语句使测试平台能够连接到一个新增加的monitor模块。

例10 带有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

下面是相应的仲裁器模型和测试平台,它们都在各自的端口连接表中使用了modport。应当指出的是你需要将modport名即DUT或TEST放在接口名即arb_if的后面。

例11 接口中使用modport的仲裁器模型

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

例12 接口中使用modport 的测试平台

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

顶层模块与例7一致,modport只在模块首部指明,而在模块例化时不需指明。

注意:在上面定义接口时,我们使用了modport MONITOR(例10),它能够将测试平台连接到一个新增加的monitor模块。

例13 接口使用modport的仲裁器模型

注:modport 是在接口内部定义的,在模块的二端口列表描述相应的modport,而不是在信号名称描述。arb_if.TEST.grant 这样的名称是不合法的!

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

3.激励时序

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

3.1 使用时钟块cb控制同步信号的时序

  • 接口块可以使用时钟块来指定同步信号相对于时钟的时序。时钟块大都在测试平台中使用。

  • 时钟块中的任何信号都将同步地驱动或采样,这就保证了测试平台在正确的时间点与信号交互。

  • 一个接口可以包含多个时钟块,因为每个块中都只有一个时钟表达式,所以对应一个时钟域。eg:1. @(posedge clk)单时钟沿 2.@(clk) 定义了DRR时钟(双数据率)

    注:1.可以在时钟块中使用default语句指定时钟偏移,但是默认情况下输入信号仅在设计执行前被采样,并且设计的输入信号在当前时间片又被驱动会当前设计。 2.一旦定义了时钟块,测试平台就能用@arbif.cb表达式来等待时钟,不需要准切的时钟信号和边沿。即使改变时钟和边沿,也不用修改测试平台的代码。

例14 带时钟块的接口

interface arb_if (input bit clk);
    logic[1:0] grant,request;
    logic rst;
    clocking cb @(posedge clk); //声明cb
        output request;
        input grant;
    endclocking
    modport TEST(clocking cb, //使用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。接口信号采用的是非阻塞赋值。

3.2 verilog的时序问题

  • 测试平台不仅在逻辑上独立于设计,在时序上也独立于设计。DUT中存储单元在时钟有效沿所存输入信号。从上一个存储单元的输入到下一个存储单元的输入,在下一个存储单元的输入延时必须要小于一个时钟周期。测试平台需要模拟这种行为。

  • 解决方案:1.给系统加一点小延时 比如#0.问题:大型设计中,往往不和避免的多个线程都在想在最后执行。2.较大的延时 #1。RTL代码除了在时钟沿会有时序信息,所以逻辑电路在时钟沿之后1个时间单位后就会稳定。但是,如果一个用1ns的时间精度,另一个模块使用10ps的时间精度。

3.3 程序块(Program Block)和时序区域(Timing Region)

  测试平台应该不仅在逻辑上而且在时序方面独立于设计。通常测试平台和设计之间会存在竞争状态。会出现这种问题的根源在于设计和测试平台的事件混在同一个事件片内。如果存在一种可以在时间轴上分开这些事件的方法,确保能够在所有事件执行完毕后,测试平台开始下一个动作。那么将会很好地解决时序上的问题。

SystemVerilog是如何将测试平台的事件与设计的事件分开调度呢?

在SystemVerilog中,测试平台的代码在一个程序块中,这个与模块非常类似,但是,程序块不能有任何的层次级别,例如模块的实例、接口或者其他程序。

SystemVerilog引入一种新的时间片的划分方式。

  • Active:仿真模块中设计代码
  • Observed:执行SystemVerilog断言
  • Reactive:执行程序中测试平台部分
  • Postponed:为测试平台的输入采样,所有设计活动都结束的只读时间段采样信号。

例17 使用带有时钟块接口的测试平台

program automatic test(arbif.TEST arbif);
...
    initial begin
        arbif.cb.request<=2'b01;
        repeat(2) @arbif.cb; //@arbif.cb语句将等待时钟块给出有效沿@(posedge clk)
        if(arbif.cb.grant!=2'b01);
            $display ("@%0t:a1:grant!=2'b01",$time);
    end
endprogram:test

建议:1.测试代码应当包含在一个单个的程序块中。应当使用OOP通过对象而非模块来创建一个动态、分层的测试平台。

​ 2.应将程序块声明为automatic类型

注:1.程序块直接来自Synopsys向SystemVerilog捐赠的Vera语言,并试图模仿PLI应用程序与Verilog模拟器交互的调度语义。 因此,来自 Vera 背景的程序块非常有意义,并且确实可以帮助人们从 Vera 过渡到 SV。但是从头开始看SV,它们只是额外的语言包袱。

2.OVM的示例不支持程序块,程序块只会给systemverilog增加不必要的复杂性。实际不推荐使用。

不推荐使用原因:

1.在OVM中,测试代码与设计分离,因为测试代码由包中定义的类的层次结构组成。实际处理信号和驱动采样相关的时序问题,可以通过结构中的时钟块的正确使用来实现。OVM环境中成功地使用这一方法。除此之外,时钟块还能处理由门级传播引入的非零延时偏差所引起的竞争。时钟块通过使用输入歪斜来进行采样和输出歪斜来进行驱动。如果程序块能够解决,这样就造成时钟块的冗余。

2.不理解为什么这些在DUT中产生的竞争,将会在测试平台中会有相同的竞争,并且没有任何程序块能够防止测试平台中的竞争。这是一种错误的安全感。

3.如果有遗留的verilog tb代码,有时希望通过类来共享遗留的BFM任务。基于tb来调用遗留的这些BFM任务。如果该任务被设计为在Active 活动区域调度,而现在该任务在re-active 活动区域调度,就会遇到验证的时间问题。采样将会被关闭一个时钟周期。此外,如果某些任务从程序块中调用的,另一些任务依然在module中调用,那么问题会更糟糕。

4.一个人设计的IP是另一个人的验证IP。在系统及,与测试平台相比,为表示设计的更高层次抽象而编写的的model 之间的区别更小。不能因为一次是从program中调度,另一次,就从module调用,所以调度也存在差异。此外从program或者module 调用c 代码也有相同的问题。

5.除非是一个经验丰富的vera用户,否则会有出人意料的结果。在program块的线程结束后,仿真会立即退出。这又是一个混合遗留测试平台(v,sv)或者混合语言测试平台(SV,c)。

6.此外及时不适用program块,大多数高级用户也很难理解systemverilo的调度语义。为什么要引入不必要的复杂性。许多其他测试环境,像systemC,VHDL,已经使用多年,而不需要程序块来引入调度语义。

3.4 仿真的结束

第一种:遇到$finish结束

第二种:如果仅有一个程序块,那么当完成所有initial块中的最后一个语句时,仿真就结束了。如果存在多个程序块,仿真在最后一个程序块结束时结束。

第三种:执行 $exit可以提前终断任何一个程序块。

4 .1接口的驱动和采样

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

4.2 接口信号采样

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

例19 模块中同步接口的采样和驱动

'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的形式。

4.3 接口信号驱动

时钟块的默认时序是在#1step延时之后采样输入信号,在#0延时之后驱动输出信号

#1step延时规定了信号在前一个时间片的Postponed区域,在设计有任何新的动作之前被采样。

#0,因为时钟模块的原因,测试平台的输出信号是同步的,所以他们直接送入设计中。

例4.22 驱动一个同步接口

program test(arb_if.TEST arbif);
    initial begin
        #7 arbif.cb.request<=3; //@7ns
        #10 arbif.cb.request<=2; //@17ns
        #8 arbif.cb.request<=1; //@25ns
        #15 finish;
    end
endprogram
module arb(arb_if.DUT arbif);
    initial
        $monitor("@%0t:req=%h", $time,arbif.request);
endmodule

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

例23 接口信号驱动

##2 arbif.cb,request<=0; //等待两个时钟周期然后赋值
##3;                    //非法——必须跟赋值语句同时使用

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

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

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

如果在program块中加入always块,它将永远不会结束,这样必须调用$exit来发出程序块结束的信号。

如果你确实需要一个always块,你可以使用"initial forever"来完成相同的事情。

4.7 时钟发生器

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

例25 位于程序块中错误的时钟发生器

program bad_generator (output bit clk, out_sig);
    initial
        forever # 5clk=~clk;
    initial
        forever@(posedge clk)
            out_sig<=~out_sig;
endprogram

clk和out_sig信号会引起竞争状态。

例26 模块中正确的时钟发生器

module clock_generator(output bit clk);
    initial
        always #5 clk=~clk; //在时间0之后生成时钟沿
endmodule

5.顶层作用域

  在Verilog中,只有宏定义可以跨越模块的边界,而且经常被用来创建全局变量。SystemVerilog引入了编译单元(compilation unit),它是一起编译的源文件的一个组合。任何module,interface,program,package等边界之外的作用域被称为编译单元作用域,也成为$unit。

  这个作用域的任何成员,比如parameter,都类似于全局成员,因为他们可以被所有低一级的块访问,但是又不同于真正的全局成员,比如parameter在编译时其他源文件不可见。下面的例子就显示了编译单元parameter、const的用法。

区别:1. 宏定义的变量 都类似于全局变量,但在编译时不能被其他源文件不可见。

例31 仲裁器设计的顶层作用域

//root.sv
`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.top

'top.clk

相对引用:top.clk

例32 使用 $root的跨模块引用

`timescale 1ns/1ns
parameter int TIMEOUT=1_000_000;
top t1(); //顶层模块的显示例化
    
module top;
    bit clk;
    test t1(.*);
endmodule
    
'define TOP $root.top
program automatic test;
    ......
    initial 
    begin
        //绝对引用
        $display("clk=%b", $root.top.clk);
        $display("clk=%b", 'top.clk); //使用宏
        //相对引用
        $display("clk=%b", top.clk);
endprogram
posted @ 2024-10-08 22:45  LeslieQ  阅读(296)  评论(0)    收藏  举报