前言:SV中TB的构成主要包括Dut的例化、interface的例化、验证环境的例化三部分。接口类似于一条总线,把零碎的线包装在一起,给那些需要的模块。
Verilog通过模块间的端口来完成模块间的通信,SV在Verilog的基础上扩展了接口interface。引入interface可以简化模块儿之间的连接,将一组相关的信号可以封装到一起。interface就像—个"插排", DUT与TB之间的数据驱动关系都可以使用interface这个插排来完成。
interface特点
interface与module的区别:
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。(约束信号的方向,是输入到模块还是输出到模块)
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两边时,就会采样到不一样的值。
如何解决竞争问题呢?
以上几点在下面的例子中具体说明。
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