SystemVerilog(3):interface、clocking、$root
1、interface
1.1 概念
- 接口可以用作设计,也可以用作验证。在验证环境中,接口可以使得连接变得简洁而不易出错。
- 接口 interface 和模块 module 的使用性质很像,可以定义端口也可以定义双向信号,可以使用 initial 和 always,也可以定义 function 和 task。
- 接口可以在硬件环境和软件环境中传递,例如可以作为module的端口列表,也可以作为软件方法的形式参数。
- 接口就像一个“排插”,DUT 和 TB 之间的数据驱动关系由 interface 这个排插来完成。
1.2 案例
//==============================================================
// 例1:top层
//==============================================================
module top
bit clk;
always #5 clk = ~clk;
arb_if u_arb_if (clk ); //例2接口的例化
arb_dut u_arb_dut (u_arb_if ); //例3设计的例化
arb_tb u_arb_tb (u_arb_if ); //例3测试的例化
/* 也可以这样写,能自动的在当前级别自动连接模块实例的端口
具体到信号,只要端口和信号的名字和数据类型相同。
arb_if u_arb_if (.*); //例2接口的例化
arb_dut u_arb_dut (.*); //例3设计的例化
arb_tb u_arb_tb (.*); //例3测试的例化
*/
endmodule
//==============================================================
// 例2:接口
//==============================================================
interface arb_if(input bit clk);
logic [1:0] grant;
logic [1:0] request;
logic rst;
endinterface
//==============================================================
// 例3:设计
//==============================================================
module arb_dut(arb_if u_arb_if);
//...
always @(posedge u_arb_if.clk or posedge u_arb_if.rst) begin
if(u_arb_if.rst)
u_arb_if.grant <= 2'b0;
else
u_arb_if.grant <= next_grant;
//...
end
endmodule
//==============================================================
// 例4:测试
//==============================================================
module arb_tb(arb_if u_arb_if);
//...
initial begin
//...
@(posedge u_arb_if.clk);
u_arb_if.request <= 2'b01;
$display("@%0t: drive request = 01", $time);
//2个时钟后,检测grant状态
repeat(2) @(posedge u_arb_if.clk);
if(u_arb_if.grant != 2'b01)
$display("@%0t: u_arb_dut: grant != 2'b01", $time);
$finish;
end
endmodule
即便在这个小设计中,也可以马上看到使用接口的好处,连接变得更加简洁而不易出错。如果希望在一个接口中放入一个新的信号,只需要在接口定义和实际使用该接口的模块中做修改,而不需要改变其他任何模块,这样极大地降低了连线出错的机率。
注意事项:
- 接口的端口列表中可以只定义时钟、复位等公共信号;
- 接口的端口信号并非都要连接,使用时可以选择部分;
- 使用接口时需要确保在模块和程序块之外声明接口变量;
- 模块可以例化模块和接口,但是接口里面只能例化接口,不能例化模块。
1.3 使用modport对接口信号分组
上面例3中使用了点对点的、无信号方向的连接方式,编译器依照原始网单的方向信息进行连线检查。为此我们也可以在接口内增加 modport 结构将接口中的信号进行分组,并指明方向。
//==============================================================
// 例1:top层
//==============================================================
module top
bit clk;
always #5 clk = ~clk;
arb_if u_arb_if (clk ); //例2接口的例化
arb_dut u_arb_dut (u_arb_if ); //例3设计的例化
arb_tb u_arb_tb (u_arb_if ); //例3测试的例化
endmodule
//==============================================================
// 例2:接口,增加modport对信号分组并指明方向
//==============================================================
interface arb_if(input bit clk);
logic [1:0] grant;
logic [1:0] request;
logic rst;
modport DUT(input clk, rst, request,
output grant);
modport TB(input clk, grant,
output rst, request);
endinterface
//==============================================================
// 例3:设计
//==============================================================
module arb_dut(arb_if.DUT u_arb_if); //注意端口多了.DUT
//...
always @(posedge u_arb_if.clk or posedge u_arb_if.rst) begin
if(u_arb_if.rst)
u_arb_if.grant <= 2'b0;
else
u_arb_if.grant <= next_grant;
//...
end
endmodule
//==============================================================
// 例4:测试
//==============================================================
module arb_tb(arb_if.TB u_arb_if); //注意端口多了.TB
//...
initial begin
//...
@(posedge u_arb_if.clk);
u_arb_if.request <= 2'b01;
$display("@%0t: drive request = 01", $time);
//2个时钟后,检测grant状态
repeat(2) @(posedge u_arb_if.clk);
if(u_arb_if.grant != 2'b01)
$display("@%0t: u_arb_dut: grant != 2'b01", $time);
$finish;
end
endmodule
这个例子可以看到,在接口中增加了 modport 结构,而在具体使用该接口时就可以加上需要的 modport 了。
2、clocking
时钟块 clocking 的使用可以有效的避免竞争冒险现象,其使用如下所示:
//==============================================================
// 例2:接口,增加clocking
//==============================================================
interface arb_if(input bit clk);
logic [1:0] grant;
logic [1:0] request;
logic ack;
logic [7:0] addr;
logic rst;
//声明u_clocking
clocking u_clocking @(posedge clk);
default input #10ns output #2ns; //设置默认的采样和驱动时间
input grant; //采样时间:clk上升沿的前10ns
input #1step addr //采样时间:clk上升沿的前一个时间片,覆盖了default设置
output request; //驱动时间:clk上升沿的后2ns
output negedge ack; //驱动时间:clk下降沿,覆盖了default设置
endclocking
modport DUT(input rst, request, ack, //没有了clk
output grant, addr);
modport TB(clocking u_clocking, //使用u_clocking
output rst); //仅剩了rst
endinterface
//==============================================================
// 例4:测试,注意clocking的引用
//==============================================================
module arb_tb(arb_if.TB u_arb_if)
initial begin
u_arb_if.u_clocking.request <= 0;
@u_arb_if.u_clocking;
$display("@%0t: grant = %b", $time, u_arb_if.u_clocking.grant);
end
endmodule
注意:
- clocking 可以定义在 interface 中,也可以定义在 module、program 中;
- clocking 列举的信号不是自己定义的,必须是由其他声明 clocking 的模块定义的,如该 interface;
- 如果没有设置 default 的采样和驱动时间,则会默认的采样时间为 clk 的前 1step,默认的驱动时间为 0;
- 用了 clocking,如果刚好时钟上升沿时给出下一个值,那么会立即驱动出去,DUT 会立即变为该值,而不是等到下一个上升沿才改变。
3、$root
SystemVeriog 增加了一个被称为 $root 的隐含的顶级层次。任何在模块边界之外的声明和语句都存在于 $root 空间中。所有的模块,无论它处于哪一个设计层次都可以引用 $root 中声明的名字。这样如果某些变量、函数或其它信息被设计中的所有模块共享,那么我们就可以将它们作为全局声明和语句。其实我们常常在 testbench 的顶层写的 `timescale 就可以看作是写在 $root 空间。
//================================================= root层
`timescale 1ns/1ns
parameter TIMEOUT = 1_000_000;
top u_top() //显示例化,如果不写这行则为隐式例化
//================================================= top层
module top;
bit clk;
test u_test(.*);
endmodule
//================================================= test层
module test;
...
initial begin
//绝对引用
$display("clk = %b", $root.top.clk);
//相对引用
$display("clk = %b", top.clk);
end
endmodule
参考资料:
[1] 路科验证V2教程
[2] 绿皮书:《SystemVerilog验证 测试平台编写指南》第2版