SystemVerilog(3):interface、clocking、$root

1、interface

1.1 概念

  • 接口可以用作设计,也可以用作验证。在验证环境中,接口可以使得连接变得简洁而不易出错。
  • 接口 interface 和模块 module 的使用性质很像,可以定义端口也可以定义双向信号,可以使用 initial 和 always,也可以定义 function 和 task。
  • 接口可以在硬件环境和软件环境中传递,例如可以作为module的端口列表,也可以作为软件方法的形式参数。
    • 接口就像一个“排插”,DUT 和 TB 之间的数据驱动关系由 interface 这个排插来完成。

1.2 案例

//==============================================================
// 例1:top层
//==============================================================
module top
    bit clk;
    always #5 clk = ~clk;
    
    arb_if  u_arb_if  (clk      );  //例2接口的例化
    arb_dut u_arb_dut (u_arb_if );  //例3设计的例化
    arb_tb  u_arb_tb  (u_arb_if );  //例3测试的例化
    /* 也可以这样写,能自动的在当前级别自动连接模块实例的端口
       具体到信号,只要端口和信号的名字和数据类型相同。
       arb_if  u_arb_if  (.*);  //例2接口的例化
       arb_dut u_arb_dut (.*);  //例3设计的例化
       arb_tb  u_arb_tb  (.*);  //例3测试的例化
    */
endmodule

//==============================================================
// 例2:接口
//==============================================================
interface arb_if(input bit clk);
    logic [1:0] grant;
    logic [1:0] request;
    logic       rst;
endinterface

//==============================================================
// 例3:设计
//==============================================================
module arb_dut(arb_if u_arb_if);
    //...
    always @(posedge u_arb_if.clk or posedge u_arb_if.rst) begin
        if(u_arb_if.rst)
            u_arb_if.grant <= 2'b0;
        else
            u_arb_if.grant <= next_grant;
        //...
    end
endmodule

//==============================================================
// 例4:测试
//==============================================================
module arb_tb(arb_if u_arb_if);
    //...
    initial begin
        //...
        @(posedge u_arb_if.clk);
        u_arb_if.request <= 2'b01;
        $display("@%0t: drive request = 01", $time);
        //2个时钟后,检测grant状态
        repeat(2) @(posedge u_arb_if.clk);
        if(u_arb_if.grant != 2'b01)
            $display("@%0t: u_arb_dut: grant != 2'b01", $time);
        $finish;
    end
endmodule

即便在这个小设计中,也可以马上看到使用接口的好处,连接变得更加简洁而不易出错。如果希望在一个接口中放入一个新的信号,只需要在接口定义和实际使用该接口的模块中做修改,而不需要改变其他任何模块,这样极大地降低了连线出错的机率。

注意事项:

  • 接口的端口列表中可以只定义时钟、复位等公共信号;
  • 接口的端口信号并非都要连接,使用时可以选择部分;
  • 使用接口时需要确保在模块和程序块之外声明接口变量;
  • 模块可以例化模块和接口,但是接口里面只能例化接口,不能例化模块。

1.3 使用modport对接口信号分组

上面例3中使用了点对点的、无信号方向的连接方式,编译器依照原始网单的方向信息进行连线检查。为此我们也可以在接口内增加 modport 结构将接口中的信号进行分组,并指明方向。

//==============================================================
// 例1:top层
//==============================================================
module top
    bit clk;
    always #5 clk = ~clk;
    
    arb_if  u_arb_if  (clk      );  //例2接口的例化
    arb_dut u_arb_dut (u_arb_if );  //例3设计的例化
    arb_tb  u_arb_tb  (u_arb_if );  //例3测试的例化
endmodule

//==============================================================
// 例2:接口,增加modport对信号分组并指明方向
//==============================================================
interface arb_if(input bit clk);
    logic [1:0] grant;
    logic [1:0] request;
    logic       rst;
    
    modport DUT(input  clk, rst, request,
                output grant);
    
    modport TB(input  clk,  grant,
               output rst, request);
endinterface

//==============================================================
// 例3:设计
//==============================================================
module arb_dut(arb_if.DUT u_arb_if); //注意端口多了.DUT
    //...
    always @(posedge u_arb_if.clk or posedge u_arb_if.rst) begin
        if(u_arb_if.rst)
            u_arb_if.grant <= 2'b0;
        else
            u_arb_if.grant <= next_grant;
        //...
    end
endmodule

//==============================================================
// 例4:测试
//==============================================================
module arb_tb(arb_if.TB u_arb_if); //注意端口多了.TB
    //...
    initial begin
        //...
        @(posedge u_arb_if.clk);
        u_arb_if.request <= 2'b01;
        $display("@%0t: drive request = 01", $time);
        //2个时钟后,检测grant状态
        repeat(2) @(posedge u_arb_if.clk);
        if(u_arb_if.grant != 2'b01)
            $display("@%0t: u_arb_dut: grant != 2'b01", $time);
        $finish;
    end
endmodule

这个例子可以看到,在接口中增加了 modport 结构,而在具体使用该接口时就可以加上需要的 modport 了。

2、clocking

时钟块 clocking 的使用可以有效的避免竞争冒险现象,其使用如下所示:

//==============================================================
// 例2:接口,增加clocking
//==============================================================
interface arb_if(input bit clk);
    logic [1:0] grant;
    logic [1:0] request;
    logic       ack;
    logic [7:0] addr;
    logic       rst;
    
    //声明u_clocking
    clocking u_clocking @(posedge clk);
        default input #10ns output #2ns; //设置默认的采样和驱动时间
        input  grant;                    //采样时间:clk上升沿的前10ns
        input  #1step addr               //采样时间:clk上升沿的前一个时间片,覆盖了default设置
        output request;                  //驱动时间:clk上升沿的后2ns
        output negedge ack;              //驱动时间:clk下降沿,覆盖了default设置
    endclocking
    
    modport DUT(input  rst, request, ack, //没有了clk
                output grant, addr);
    
    modport TB(clocking u_clocking,  //使用u_clocking
               output rst);          //仅剩了rst
endinterface
        
//==============================================================
// 例4:测试,注意clocking的引用
//==============================================================
module arb_tb(arb_if.TB u_arb_if)
    initial begin
        u_arb_if.u_clocking.request <= 0;
        @u_arb_if.u_clocking;
        $display("@%0t: grant = %b", $time, u_arb_if.u_clocking.grant);
    end
endmodule

注意:

  • clocking 可以定义在 interface 中,也可以定义在 module、program 中;
  • clocking 列举的信号不是自己定义的,必须是由其他声明 clocking 的模块定义的,如该 interface;
  • 如果没有设置 default 的采样和驱动时间,则会默认的采样时间为 clk 的前 1step,默认的驱动时间为 0;
    • 用了 clocking,如果刚好时钟上升沿时给出下一个值,那么会立即驱动出去,DUT 会立即变为该值,而不是等到下一个上升沿才改变。

3、$root

SystemVeriog 增加了一个被称为 $root 的隐含的顶级层次。任何在模块边界之外的声明和语句都存在于 $root 空间中。所有的模块,无论它处于哪一个设计层次都可以引用 $root 中声明的名字。这样如果某些变量、函数或其它信息被设计中的所有模块共享,那么我们就可以将它们作为全局声明和语句。其实我们常常在 testbench 的顶层写的 `timescale 就可以看作是写在 $root 空间。

//================================================= root层
`timescale 1ns/1ns

parameter TIMEOUT = 1_000_000;

top u_top() //显示例化,如果不写这行则为隐式例化

//================================================= top层
module top;
    bit clk;
    test u_test(.*);
endmodule

//================================================= test层
module test;
    ...
    initial begin
        //绝对引用
        $display("clk = %b", $root.top.clk);
        //相对引用
        $display("clk = %b", top.clk);
    end
endmodule

 

参考资料:

[1] 路科验证V2教程

[2] 绿皮书:《SystemVerilog验证 测试平台编写指南》第2版

posted @ 2022-07-09 19:05  咸鱼IC  阅读(1491)  评论(0编辑  收藏  举报