概念
接口(interface)是SV引入的很重要的特性,目前在绝大多数验证环境或者设计中都会出现。接口最直接的作用就是将一组相关的信号封装到一起,特别是一些标准协议的接口信号,比如常见的AMBA AXI/AHB/APB等等。接口的定义不仅可以方便信号在验证环境组件中的连接,还可以将其复用至其他芯片设计中,降低连接出错的概率。
接口并不只是提供一组信号这么简单,它还是可综合的,它还可以包含modport、clocking block、parameter、过程语句块、断言、覆盖组(covergroup)、函数和任务等。
interface特点
-
接口便于设计重用,当两个块之间有两个以上的信号需要连接,并且使用特定的协议进行通信时应该使用接口
-
接口可以用于替代原来需要在模块或者程序中反复声明并且位于代码内部的一系列信号,减少连接错误的可能性
-
要增加新的信号时,只需要在接口中声明一次,不需要再更高层的模块中共声明,减少错误
-
modport允许一个模块很方便的将接口的一系列信号捆绑在一起,也可以为信号指定方向以便工具自动检查
-
作为SV中唯一的硬件与软件环境的媒介交互,既可以在硬件世界(module)中使用,也可以在软件世界(class)中使用
module和interface区别
- interface不能例化module的,module可以例化interface
- 不能将module里面portlist含有另一个module的,可以含有interface
- 如果function、task的端口声明中声明为ref,那么function、task必须是automatic
- 在program或者module中,function、task默认都是static
interface的声明与使用
interface通过关键词interface来声明,声明方式与module非常类似,示例如下:
interface module_if(input clk);
logic port_a_0 ;
logic port_a_1 ;
logic port_b_0 ;
logic port_b_1 ;
endinterface
下面我们来看使用interface与不使用interface的区别。
假设我们有两个模块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 module_b(
input clk,
input rst_n,
input port_b_0 ,
input port_b_1 ,
output port_a_0 ,
output port_a_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
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)
);
module_b U_B(
.clk(clk),
.rst_n(rst_n),
.port_b_0(port_b_0),
.port_b_1(port_b_1),
.port_a_0(port_a_0),
.port_a_1(port_a_1)
);
endmodule
显然,虽然代码非常简单,但是各种net还是非常凌乱,而且如果我们需要根据设计需要增加模块接口信号时,我们会增加超级多的工作,比如模块U_A和U_B均增加了一组交互信号port_c_0和port_c_1时,我们需要修改module_a的声明位置,module_b的声明位置, 以及例化U_A和U_B等4个位置的代码。
以上仅仅是简单的增加了两个信号的交互,如果增加更多的信号修改,或者某些信号修改位宽…简直不敢想象会有多少工时的加班。但是当有了interface这种结构,一切都变得简单了很多:
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_a( module_if U_IF);
......
endmodule
module module_b( 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);
module_b U_B(U_IF);
endmodule
这样一来,不但代码少了很多,而且当我们需要更改模块的接口设计时,仅需要在interface内部一处修改。
modport
但是如果向上面一样简单的使用interface可能还是会有点不太方便:
(1)interface中的信号可能有很多,并不是所有的模块都会用得到interface中声明的全部信号。
(2)不使用interface是,模块接口的input/output属性可以帮助我们检查连线方向的正确性,所以最好在interface中也可以规定好每一个信号在某个模块的方向。
其实在sv中,这两个问题已经得到了解决,那就是使用modport将interface中的信号进行分组打包。比如针对上面的例子,可以做一个修改:
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
);
modport B(
input rst_n ,
output port_a_0 ,
output port_a_1 ,
input port_b_0 ,
input port_b_1
);
endinterface
module module_a( module_if.A U_IF);
......
endmodule
module module_b( module_if.B 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); //注意此处不需要再声明modport的名字了
module_b U_B(U_IF); //注意此处不需要再声明modport的名字了
endmodule
clocking时钟块
- 可以在interface中声明clocking和采样的时钟信号用来做信号的同步和采样。
- clocking块基于时钟周期对信号进行驱动或者采样的方式,使得testbench不再苦恼于如何准确及时地对信号驱动或者采样,消除了信号竞争问题。
//在clock1的上升沿来驱动和采样
clocking bus@(posedge clock1);
//在clock1上升沿前10ns对其进行输入采样,
//在后2ns对其进行输出驱动。
default input #10ns output #2ns
input ready, margin;
output data, valid;
endclocking
clocking的使用
- clocking块不但可以定义在interface中,也可以定义在module和program中。
- clocking中列举的信号不是自己定义的,而是应该由interface或者其他声明clocking的模块定义的。
- clocking在声明完名字之后,应该伴随着定义默认的采样事件,即"default input/output
event"。如果没有定义,则会默认地在clocking采样事件前的1step对输入进行采样,在采样事件后的#0对输出进行驱动。 - 除了定义默认的采样和驱动事件,也可以在定义信号方向时,用新的采样事件对默认事件做覆盖。