保姆级超硬核包会,​System Verilog SV接口(interface )

发布于:2022-11-29 ⋅ 阅读:(600) ⋅ 点赞:(0)

前言:SV中TB的构成主要包括Dut的例化、interface的例化、验证环境的例化三部分。接口类似于一条总线,把零碎的线包装在一起,给那些需要的模块。

1. interface

1.1 interface 是干什么的?

Verilog通过模块间的端口来完成模块间的通信,SV在Verilog的基础上扩展了接口interface。引入interface可以简化模块儿之间的连接,将一组相关的信号可以封装到一起。interface就像—个"插排", DUT与TB之间的数据驱动关系都可以使用interface这个插排来完成。

未使用interface:在这里插入图片描述 使用interface:使用

1.2 interface概念

interface特点

  • 接口可以用作设计,也可以用作验证
  • 与module的使用性质很像,可以定义端口,也可以定义双向信号;可以使用 initial和always,也可以定义function和task
  • TB和DUT是相互独立的,通过interface连接,下图的红线
  • 在interface的端口列表中只需要定义时钟、复位等公共信号,或者不定义任何端口信号 interface中可以包含多个modport,
  • 定义interface的不同视图view(DUT、Test program)

interface与module的区别:

  • interface不能例化module的,module可以例化interface
  • 不能将module里面portlist含有另一个module的,可以含有interface
  • 如果function、task的端口声明中声明为ref,那么function、task必须是automatic
  • 在program或者module中,function、task默认都是static

1.3 interface的使用

interface的使用类似于module,声明示例如下:

interface module_if(input clk);//声明了clk的方向,其余信号没有方向
    logic port_a_0 ;
    logic port_a_1 ;
    logic port_b_0 ;
    logic port_b_1 ;
endinterface

索引interface中的某个信号可以通过module_if.port_a_0来实现。
假设有两个模块module_a与module_b,且在顶层分别例化与连接,示例如下:

module module_a(
    input clk,
    input rst_n,
    input port_a_0 ,
    input port_a_1 ,
    output port_b_0 ,
    output port_b_1
);
    ......
endmodule

module top();
    logic clk ;
    logic rst_n ;
    logic port_a_0 ;
    logic port_a_1 ;
    logic port_b_0 ;
    logic port_b_1 ;
    
    always #10 clk = ~clk ;
    initial begin
        rst_n = 0 ;
        #50;
        rst_n = 1 ;
    end
//例化模块A
    module_a U_A(
        .clk(clk),
        .rst_n(rst_n),
        .port_a_0(port_a_0),
        .port_a_1(port_a_1),
        .port_b_0(port_b_0),
        .port_b_1(port_b_1)
    );
endmodule

虽然代码非常简单,当需要根据设计需要增加模块接口信号时,就需要修改很多地方。比如模块A增加了一组交互信号port_c_0,此时需要修改module_a的声明位置, 例化U_A位置的代码。

interface module_if(input clk);//声明接口
    logic rst_n,
    logic port_a_0 ;
    logic port_a_1 ;
    logic port_b_0 ;
    logic port_b_1 ;
endinterface

module module_b( module_if U_IF);
    ......
endmodule

module top();
    logic clk ;
    always #10 clk = ~clk ;
    module_if U_IF(clk);//例化接口,并且抽象的clk与具体的lk实现了连接
    initial begin
        U_IF.rst_n = 0 ;
        #50;
        U_IF.rst_n = 1 ;
    end
 //例化模块
    module_b U_B(U_IF);//例化模块b,顶层U_B与U_IF相连
endmodule

这样一来,数据的连接简单了不少,而且当需要更改模块的接口设计时,仅需要在interface内部一处修改。
虽然解决了线数量很多的问题,但是现在出现了新的问题:
1.把数据完成了打包,但不是每一个模块都可以用到全部的信号。
2.假设有两个模块通过interface来连接,A模块输出信号到interface,interface作为输入送给模块B,目前的interface是无法完成的,因为接口中的信号是没有方向的。
这时候就需要用到modport。(约束信号的方向,是输入到模块还是输出到模块)

1.4 modport

modpot是module port的缩写,表示不同模块看到同一组信号时的视角(连接方向)。在接口声明modport,需要指明modport中各个信号的方向。

示例中modport把interface中的信号约束了方向命名为A,在下面例子的顶层模块中通过U_if.A 来索引。

interface module_if(input clk);
    logic rst_n,
    logic port_a_0 ;
    logic port_a_1 ;
    logic port_b_0 ;
    logic port_b_1 ;//声明信号
    
    modport A(
        input rst_n ,
        input port_a_0 ,
        input port_a_1 ,
        output port_b_0 ,
        output port_b_1
    );//A这一组信号的方向,只能声明在interface里的信号,modport只声明你需要规定方向的信号
endinterface

module module_a( module_if U_IF);
    ......
endmodule

module top();
    logic clk ;
    always #10 clk = ~clk ;
    module_if U_IF(clk);//例化接口
    initial begin
        U_IF.rst_n = 0 ;
        #50;
        U_IF.rst_n = 1 ;
    end
    module_a U_A(U_IF.A);  //例化模块a并与A组信号连接
endmodule

此时看起来仿真结构已经构建差不多了,但其实仿真时采样信号还存在着竞争的问题。下面这道题如果a显示=1,b应该显示多少呢?

always@(posedge clk or negedge rstn)begin
	if(rstn == 0) begin
		a = 0;
		b = 0;
		end
		elsebegin
			a = a + 1 ;
			b = a ;
			$display ( "@%0t a=%0d, b=%0d",$time,a ,b) ;
		end
	end

很明显上述代码中出现了竞争问题,在RTL仿真行中解决竞争问题一般采用非阻塞赋值或特定的信号延迟来解决同步的问题。这样的时序竞争问题在SV仿真行为中也同样存在。

SV仿真中的竞争问题:
在仿真中,同一组数据仿真两次可能会采样到不一样的值,这是为什么呢?现实生活中信号与信号之间的传输会存在一定的延迟,而在仿真中这个延迟无法做到那么精确,所以在仿真中规定了delta-cycle最小延迟。当两次采样发生在这个无限小的delta-cycle两边时,就会采样到不一样的值。

  • 时钟对于组合电路的驱动会添加一个无限小的延时(delta-cycle)的延迟,而该延迟无法用绝对时间单位衡量,它要比最小的时间单位精度还要小小小小的多而采样数据中的竞争问题会成为潜在影响仿真准确性的问题。

如何解决竞争问题呢?

  • 在驱动时,可以添加相应的人为延迟来模拟真实的延迟。或者在采样时间前的某段时刻中进行采样。
  • clocking(时序块)基于时钟周期对信号进行驱动或者采样,使得testbench不再考虑竞争的问题。

1.5 clocking时钟块

  • clocking可以用在module,也可以用在interface,一般用在interface中。
  • clocking中列举的信号由interface声明不可以自己定义。
  • clocking声明玩名字之后,伴随着定义默认的采样事件,‘default input/out event’。如果没有定义,默认在clocking采样事件前1step对输入进行采样在采样时间后的#0对输出驱动。(#0是一个时间片包括无穷个delta-cycle)
  • 除了定义默认的采样和驱动事件,还可以自己添加新的时间来覆盖默认事件。

以上几点在下面的例子中具体说明。

clocking bus @ ( posedge clock1) ;
	default input #10ns output #2ns;
	input data , ready , enable;
	output negedge ack ;
	input  #1step addr;
endclocking

第一行定义clocking块名字是bus,有clock1上升沿来驱动和采样。
第二行支出clocking中所有的信号在clock1上升沿的前10ns来对其进行输入采样,在时间的后2ns进行输出驱动。(模拟建立保持时间)
第三行声明了要对其采样的三个输入信号,data,ready和enable信号,这三个信号作为输入,默认输入时间为clock1上升沿的前10ns。
第四行声明了要驱动的ack信号,由时钟clock1的下降沿驱动,clock1上升沿后的2ns。
接下来的addr,采用自身定义的采样事件,clock1上升沿的1step。采样发生在clock1上升沿的上一个时间片采样区域,保证采样到的数据是上一个时钟周期的数据。

参考原文链接:https://blog.csdn.net/baidu_38317135/article/details/126174822

本文含有隐藏内容,请 开通VIP 后查看