System Verilog学习笔记—接口interface

目录

0.interface的直观理解

1.使用端口的TB与DUT通信

2.使用接口的TB与DUT通信

3.使用modport将interface中的信号分组

4.接口中的clocking block

   4.1为何要引入clocking block?

   4.2clocking block的作用

5.接口信号的驱动和采样

   5.1接口信号的采样

   5.2接口信号的驱动

   5.3输入/出偏差input/output skew


0.interface的直观理解

    若想将TB和DUT连接起来,按照最常规的方法,是通过映射的方法将各个端口的信号一一连接,如下图所示,当一个DUT的信号有几十个甚至上百个的时候,就产生了许多的连线,一一连接十分繁琐且容易出错。

      因此我们引入接口的方法,我们可以把接口类比成一根HDMI线,其内部包含了许多子线,使它们按照规定好的输入/出规则排列,因此使用的时候用的时候直接连接即可,而省去了一根一根线连接的繁琐操作。

    下面我们通过代码进一步理解其使用方法。 

1.使用端口的TB与DUT通信

     如下是一段仲裁器的RTL代码,可见其在起始部分一一描述了端口的类型和方向,这里需要注意的是以下代码用了SV的变量类型logic,一般来说,设计人员主要还是用verilog语言来写RTL代码,即在端口声明时用的不是logic而是reg/wire型。

module arb_port(output logic [1:0] grant,
                output logic grant_valid,
                input logic [1:0] request,
                input logic rst,
                input logic clk);//DUT的输入/出信号声明,相当于一根一根的线

  ······

endmodule

 以下代码是一个针对上述代码的简单的测试平台,其仍然使用端口来与DUT一一连接:

module test(input logic [1:0] grant,
            input logic grant_valid,
            output logic [1:0] request,
            input logic rst,
            input logic clk);//通过每根线一一连接

  initial begin//激励产生
    @(posedge clk) request <=2’b01;
    $display(“@%05: Drove req=01”,$time);
    repeat(2) @(posedge clk )
    if(grant_valid &&(grant!=2’b01))
    $display(“@%0t:a1:grant!=2’b01”,$time);
    ·······
    $finish;
  end
endmodule

再创建一个顶层控制文件,用来连接DUT和TB:

module top;
  logic[1:0]grant, request;
  logic grant_valid;
  bit clk,rst;

  always #5 clk=~clk;

//实例化DUT和TEST文件
  arb_port a1(grant, grant_valid, request, rst, clk);
  test t1(grant, grant_valid, request, rst, clk);
endmodule

       由上述可见,若一个DUT包含上百个端口信号,则在TB和TOP中去声明每一个端口是非常繁琐的事情,且还容易出错,因此我们引入接口interface的方法,来简化这种繁琐的声明。

2.使用接口的TB与DUT通信

     一般设计人员不会按照接口的方式声明端口,它他们仍然用端口的方式声明,因此此处RTL代码同上:

module arb_port(output logic [1:0] grant,
                output logic grant_valid,
                input logic [1:0] request,
                input logic rst,
                input logic clk);

  ······
endmodule

验证人员拿到RTL代码后,会先写一个接口文件来简化连接,如下:

interface arb_if(input bit clk);//外部时钟,一般clk和rst都由top生成,所以这里作为input输入
  logic[1:0] grant,request;//使用logic类型,就不用管信号是input还是output,但logic不支持inout信号!!
  logic grant_valid;
  logic rst;
endinterface

 此时再写TB时,端口信号声明会大大简化:

module test(arb_if arbif);//接口的例化,繁琐的端口声明被简化,相当于连接了一次“HDMI”线

  initial begin
    @(posedge arbif.clk) arbif.request <=2’b01;//这里要注意,信号要写成 arbif.信号的形式,表明是接口中的信号,并注意接口信号必须使用非阻塞赋值来驱动!!!
    $display(“@%05: Drove req=01”,$time);

    repeat(2) @(posedge arbif.clk )
    if(arbif.grant_valid &&(arbif.grant!=2’b01))
    $display(“@%0t:a1:grant!=2’b01”,$time);
    ·······
    $finish;
  end
endmodule

顶层TOP控制文件,用来连接DUT和TB:

module top;
  bit clk;
  always #5 clk=~clk;//生成时钟

  arb_if arbif(clk);//例化接口,不用再一一书写端口

  arb_port a1(.grant(arbif.grant),
              .request(arbif.request),
              .rst(arbif.rst),
              .clk(arbif.clk)); //连接DUT,由于RTL代码信号声明并未采用接口的方式,因此通过这种信号映射的方式连接

  test t1(arbif); //连接tb
endmodule:top

此时相当于TB和DUT的连接如下图所示,将对应的端口一一连接,但并没有指明每个信号相对于TB和DUT的相对方向,所以又引入了modport。

Δ注意接口一定要在模块儿和程序块儿外部声明,因为接口是对测试平台和DUT都可见的,如果在模块儿内部声明,则只对内部可见,对外部不可见,如下就是错误的:

module bad_tset(arb_if itf);
    `include "arb_if.sv"//错误
endmodule

 3.使用modport将interface中的信号分组

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

  modport DUT(input request, rst, clk, output grant,grant_valid);//信号相对于DUT是输入/出?

  modport TEST(output request, rst, input grant,grant_valid,clk);//信号相对于TB是输入/出?

endinterface

此时相当于将各个信号相对于TB和DUT是输入/出都规定好,使代码信号方向更清晰明了,如下图:

一旦使用modport,则DUT(如果用接口方式声明)/TB中的接口信号声明就要这么写:

module arb_port(arb_if.DUT arbif);//相当于声明是interface中定义的DUT的modport中的信号
......

endmodule

其实设计人员不会管接口的,他们仍然按照Verilog的方式写 

module test(arb_if.TEST arbif);//相当于声明是interface中定义的TEST的modport中的信号
......

endmodule

     顶层TOP模块不变,仍然按照原来的写。

4.接口中的clocking block

4.1为何要引入clocking block?

      在SystemVerilog中引入时钟块是为了解决在写testbench时对于特定时序和同步处理的要求而设计的,但对其深入的了解,由于初学还未能理解透彻,因此本部分会在后续补上。

4.2clocking block的作用

      在interface中定义clocking block可以指定信号相对于时钟的时序,被定义在时钟块儿的信号都将被同步的驱动或者采样。示例代码如下:

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

  clocking cb @(posedge clk); // 声明一个时钟模块cb,里面的信号根据相对于TB方向指定是输入/出?
    output request;
    input grant,grant_valid;
  endclocking

  modport TEST(clocking cb,output rst);//为TB声明信号方向,使其更明了,这里可直接调用clocking,表明这些信号是同步的驱动或者采样;单独声明的rst表明这个信号不受clocking约束,是一个异步信号

  modport DUT(input request,rst,output grant);//为DUT声明信号方向

endinterface

 需要注意的是,根据https://www.cnblogs.com/xh13dream/p/9016356.html和一些示例代码可以发现:clocking block中的信号,被驱动时采用非阻塞赋值,被采样时采用阻塞赋值。如下所示:

5.接口信号的驱动和采样

5.1接口信号的采样

       对于一个DUT的信号输出grant,clock block的cb.grant输出如波形所示:

可见在30ns处,在时钟上升沿,采样的到的信号不是后一时刻的3而是前一时刻的2;这是因为DUT是硬件,其信号需要建立时间和保持时间,但在30ns处直接发生了跳变,因此采样值保持前一时刻的2。

5.2接口信号的驱动

       TEST将arb.cb.request驱动给DUT,DUT接收到的request的波形如图所示:

可见在30ns处,DUT接收到的值并不像5.1那样保持前面的值2,而是接收到后面的值1;这是因为TEST是软件,没有建立时间保持时间的概念,只要值有变化,立马反映到输出上,因此在时钟跳变沿直接发生跳转。

如果想在驱动一个信号前等待2个时钟周期,可以使用如下第一种方式或者第二种方式,但第二种方式必须在时钟块儿里的信号作为驱动信号赋值的同时使用,因为##必须知道是按照哪个时钟来做延时:

repeat(2) @arbif.cb;//等待2个时钟周期
##2 arbif.cb.request<=0;//等待2个时钟周期后赋值
##3;非法,必须和clocking块儿内的信号赋值时同时使用

5.3输入/出偏差input/output skew

       为了解决5.1和5.2的问题,SV中引入了输入/输出偏差的方法使得TB采样到的值和DUT接收到的值恒为clk跳变沿之后的值。代码如下所示:

interface master_if(input bit ck)
  logic[7:0]data;
  clocking cb@(posedge clk);
    default:input #1step ouput #0;//定义输入/出偏差
    input data; 
  endclocking
  modport TEST(clocking cb);
endinterface

 关于default的介绍和实际用途可见https://blog.csdn.net/qq_41337361/article/details/122156036介绍。

  • 14
    点赞
  • 79
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值