接口与端口:
逻辑设计已经变得如此之复杂,即便是块之间的通信也必须分割成独立实体。sv使用接口为块之间的通信建模,接口可看做一捆智能的连线。接口包含了连接、同步、甚至两个或者更多块之间的通信功能,他们连接了测试平台。
最简单的接口仅仅是一组双向信号的组合。这些信号使用logic数据类型,可以用过程语句驱动。
//接口声明
interface arb_if(input bit clk);
logic [1:0] grant,request; //全部使用logic数据类型
logic grant_valid;
logic rst;
endinterface
接口与端口的对比区别:
//接口方式
module top;
bit clk;
always #5 clk = ~clk;
arb_if arbif(clk); //例化接口
arb a1(arbif); //例化DUT并与接口连接
test ti(arbif); //例化tb并与接口连接 实现了DUT-接口-TB的连接
endmodule
//端口方式
module top;
logic [1:0] grant,request;
bit clk,rst;
always #5 clk = ~clk;
arb_port a1(.grant(grant),
.rst(rst),
.clk(clk));
test t1(.grant(grant),
.rst(rst),
.clk(clk));
endmodule
从上可以看出使用interface不仅可以简化顶层的代码,使层次结构更加清晰明了,还减少了频繁修改端口连接时错误的产生。
使用modport将接口中的信号分组:
在接口中使用modport结构能够将信号分组并指定方向。
interface arb_if(input bit clk);
logic [1:0] grant,request; //全部使用logic数据类型
logic grant_valid;
logic rst;
modport TEST(output request,rst,
input grant,clk);
modport DUT (input request,rst,clk,
output grant);
endinterface
值得注意的是,顶层top只需要实例化接口即可,因为modport只需要在模块首部指明,而在模块例化时不需要指明。
//DUT
module arb(arb_if.DUT arbif)
....
endmodule
//TEST
module test(arb_if.TEST arbif)
....
endmodule
在设计中,可以通过两种方法来使用这些modport名,你可以在使用接口信号的程序和模块中使用modport名;也可以在顶层模块中使用modport名,然后把接口放到程序和模块的端口表里。这里推荐前一种方式,因为modport是实现的细节,不应该分散在顶层模块中。但是,如果需要多次实例化一个模块,他们分别连接到不同的modport,即有不同的接口信号分组(一个模块连接多个modport时)。在这种情况下,你需要在实例模块的时候指明modport而非在模块中指明。
interface arb_if(input bit clk);
logic [1:0] grant,request; //全部使用logic数据类型
logic grant_valid;
logic rst;
modport TEST(output request,rst,
input grant,clk);
modport DUT1 (input request,rst,clk,
output grant);
modport DUT2 (input request,rst,clk, //与DUT连接的第二组信号
output grant);
endinterface
//此时,DUT含有两组modport,我们在top实例化时就需要
module top;
bit clk;
always #5 clk = ~clk;
arb_if arbif(clk); //例化接口
arb a1(arbif.DUT1); //例化DUT并与接口连接
arb a1(arbif.DUT2);
test ti(arbif); //例化tb并与接口连接 实现了DUT-接口-TB的连接
endmodule
//在模块中不需要再指明modport
module arb(arb_if arbif)
....
endmodule
接口的优缺点:
在接口中不能例化模块,但是可以例化其他接口。
优势:
1) 接口便于设计重用,当两个块之间有两个以上的信号连接,并且使用特定的协议通信的时候,应当考虑使用接口。
2)接口可以用来代替原来需要在模块或者程序中反复声明并且位于代码内部的一系列信号,减少了连接错误的可能性。
3)要增加一个新的信号时,在接口中只需要声明一次,不需要再更高层的模块层声明,进一步减少了错误。
4)modport允许一个模块很方便的将接口中的一些列信号捆绑到一起,也可以为信号指定方向以方便工具自动检查。
缺点:
1)对于点对点的连接,使用modport的接口描述跟使用信号列表的端口一样冗长。接口带来的好处是所有声明集中在一个地方,减少出错的几率。
2)必须同时使用信号名和接口名,可能会使模块变得更加冗长。
3)连接两个不同的接口很困难。
4)如果要连接的两个模块使用的是一个不会被重用的专用协议,使用接口需要做比端口连线更多的工作。
5)连接两个不同的接口很困难。一个新的接口可能包含了现有接口的所有信号并新增了信号,你需要拆分出独立的信号并正确驱动他们。
激励时序:
测试平台和设计之间的时序必须紧密配合。
使用时钟块控制同步信号的时序:
首先,时钟块的使用应该有如下几个优点:
1)接口块可以使用时钟块来指定同步信号相对于时钟的时序。
2)一旦你定义了时钟块,测试平台就可以使用@itf.cb表达式等待时钟,而不需要描述确切的时钟信号和边沿。这样即使改变了时钟块中的时钟或者边沿,也不需要修改测试平台的代码。
3)你可以在时钟块中使用default语句指定一个时钟偏移,但是默认情况下输入信号仅在设计执行前被采样,并且设计的输出信号在当前时间片又被驱动回当前设计。如下代码:
clocking bus @(posedge clk);
default input #10ns output #2ns; //在时钟上升沿来临的前10ns进行采样,在事件的后2ns对其进行驱动
input data,ready,enable;
output negedge,ack;
input #1step addr; //采用了自身定义的采样事件,即clk上升沿前的1step。这里的step会使得采样发生在clk上升沿前的1step。这里的1step会使得采样发生在clk上升沿的上一个时间片采样区域,即可以保证采样到的数据是上一个时钟周期的数据。
endclocking
接口中的logic和wire对比:
如果测试平台在接口中使用过程赋值语句驱动一个异步信号,那么该信号必须是logic类型的。wire类型变量只能被连续赋值语句驱动。时钟块中的信号始终是同步的,可以定义为logic和wire。驱动一个wire类型变量需要使用额外的代码。接口中的信号使用logic类型的另一个原因是如果你无意中使用了多个元件的驱动源,编译器会报错(logic为单驱动)。
interface arb_if();
logic a;
wire b;
endinterface
module test(arb_if aif);
logic logic_wire;
assign aif.b = logic_wire;
initial begin
aif.a <= 0;
logic_wire <= 1;
end
endmodule
注:测试平台与设计之间充满了竞争状态,因为驱动总有先有后,采样也要时刻注意时钟上升沿时输出值是否改变的问题。
程序块和时序区域:
sv的主要调度区域:
区域名 | 行为 |
Active | 仿真模块中的设计代码 |
Observed | 执行sv断言 |
Reactive | 执行程序中的测试平台部分 |
Postponed | 为测试平台的输入采样信号(所有设计活动都结束后) |
这里需要注意的是,时间并不是单向地向前流动,Observed和Reactive区域的时间可以触发本时钟周期内Active区域中进一步的设计事件。