SystemVerilog 2.2 interface实例
SystemVerilog接口允许我们将多个信号组合在一起,并将它们表示为一个端口。所有这些信号都可以在一个地方声明和维护,并且易于维护。接口内的信号由接口实例句柄访问。
2.2.1 语法
接口模块的定义以interface和endinterface关键词开始和结束,可以和module一样被例化。
interface [name] ([port_list]);
[list_of_signals]
endinterface
接口还可以包含函数、任务、变量和参数,使其更像类模板。它还能够通过modport构造以及带有时钟块的测试台同步功能为不同模块端口定义方向信息的策略。它还可以具有断言、覆盖记录和其他协议检查元素。最后但并非最不重要的是,它还可以包含初始和始终过程以及连续赋值语句。
注意:interface中不能例化module,但是在module中可以例化interface。
SystemVerilog是当前非常流行的硬件描述语言,接下来我们将以两个例子来看interface的定义和使用,其中会列出Verilog和SystemVerilog两种语言来描述相同的设计。
2.2.2 Verilog接口实例
让我们看看如何在测试台中使用接口,并通过端口列表连接到标准Verilog设计。下面显示的代码是Verilog中上下计数器的设计。此模块接受一个参数来决定计数器的宽度。它还接受仅当load_en为1时才加载到计数器中的输入负载值load。
当向下输入为1时,计数器开始向下计数,否则计数器向上计数。滚动输出指示计数器何时从max_value转换为0或从0转换为max_value。
module counter_ud
#(parameter WIDTH = 4)
(
input clk,
input rstn,
input wire [WIDTH-1:0] load,
input load_en,
input down,
output rollover,
output reg [WIDTH-1:0] count
);
always @ (posedge clk or negedge rstn) begin
if (!rstn)
count <= 0;
else
if (load_en)
count <= load;
else begin
if (down)
count <= count - 1;
else
count <= count + 1;
end
end
assign rollover = &count;
endmodule
下面声明了一个名为cnt_if的接口,其可参数化值为计数器信号的宽度。此任务还有一个任务init()来分配值。
interface cnt_if #(parameter WIDTH = 4) (input bit clk);
logic rstn;
logic load_en;
logic [WIDTH-1:0] load;
logic [WIDTH-1:0] count;
logic down;
logic rollover;
endinterface
module tb;
reg clk;
// TB Clock Generator used to provide the design
// with a clock -> here half_period = 10ns => 50 MHz
always #10 clk = ~clk;
cnt_if cnt_if0 (clk);
counter_ud c0 ( .clk (cnt_if0.clk),
.rstn (cnt_if0.rstn),
.load (cnt_if0.load),
.load_en (cnt_if0.load_en),
.down (cnt_if0.down),
.rollover (cnt_if0.rollover),
.count (cnt_if0.count));
initial begin
bit load_en, down;
bit [3:0] load;
$monitor("[%0t] down=%0b load_en=%0b load=0x%0h count=0x%0h rollover=%0b",
$time, cnt_if0.down, cnt_if0.load_en, cnt_if0.load, cnt_if0.count, cnt_if0.rollover);
// Initialize testbench variables
clk <= 0;
cnt_if0.rstn <= 0;
cnt_if0.load_en <= 0;
cnt_if0.load <= 0;
cnt_if0.down <= 0;
// Drive design out of reset after 5 clocks
repeat (5) @(posedge clk);
cnt_if0.rstn <= 1; // Drive stimulus -> repeat 5 times
for (int i = 0; i < 5; i++) begin
// Drive inputs after some random delay
int delay = $urandom_range (1,30);
#(delay);
// Randomize input values to be driven
std::randomize(load, load_en, down);
// Assign tb values to interface signals
cnt_if0.load <= load;
cnt_if0.load_en <= load_en;
cnt_if0.down <= down;
end
// Wait for 5 clocks and finish simulation
repeat(5) @ (posedge clk);
$finish;
end
endmodule
Simulation Log
> ncsim> run
[0] down=0 load_en=0 load=0x0 count=0x0 rollover=0
[96] down=1 load_en=1 load=0x1 count=0x0 rollover=0
[102] down=0 load_en=0 load=0x9 count=0x0 rollover=0
[108] down=1 load_en=1 load=0x1 count=0x0 rollover=0
[110] down=1 load_en=1 load=0x1 count=0x1 rollover=0
[114] down=1 load_en=0 load=0xc count=0x1 rollover=0
[120] down=1 load_en=0 load=0x7 count=0x1 rollover=0
[130] down=1 load_en=0 load=0x7 count=0x0 rollover=0
[150] down=1 load_en=0 load=0x7 count=0xf rollover=1
[170] down=1 load_en=0 load=0x7 count=0xe rollover=0
[190] down=1 load_en=0 load=0x7 count=0xd rollover=0
Simulation complete via $finish(1) at time 210 NS + 0
2.2.3 SystemVerilog接口实例
现在让我们看看如何在测试台中使用接口并将其连接到SystemVerilog设计模块。SystemVerilog允许模块接受接口作为端口列表,而不是单个信号。在下面所示的设计示例中,我们用一个用于定义设计功能的接口句柄替换了counter_ud的portlist。
`timescale 1ns/1ns
// This module accepts an interface object as the port list
module counter_ud #(parameter WIDTH = 4) (cnt_if _if);
always @ (posedge _if.clk or negedge _if.rstn) begin
if (!_if.rstn)
_if.count <= 0;
else
if (_if.load_en)
_if.count <= _if.load;
else begin
if (_if.down)
_if.count <= _if.count - 1;
else
_if.count <= _if.count + 1;
end
end
assign _if.rollover = &_if.count;
endmodule
设计实例被传递一个名为cnt_if的接口句柄,用于从测试台驱动设计输入。如果需要,可以使用相同的接口句柄来监控设计的输出。
// Interface definition is the same as before
module tb;
reg clk;
// TB Clock Generator used to provide the design
// with a clock -> here half_period = 10ns => 50 MHz
always #10 clk = ~clk;
cnt_if cnt_if0 (clk);
// Note that here we just have to pass the interface handle
// to the design instead of connecting each individual signal
counter_ud c0 (cnt_if0);
// Stimulus remains the same as before
Simulation Log
*> ncsim> run
[0] down=0 load_en=0 load=0x0 count=0x0 rollover=0
[96] down=1 load_en=1 load=0x1 count=0x0 rollover=0
[102] down=0 load_en=0 load=0x9 count=0x0 rollover=0
[108] down=1 load_en=1 load=0x1 count=0x0 rollover=0
[110] down=1 load_en=1 load=0x1 count=0x1 rollover=0
[114] down=1 load_en=0 load=0xc count=0x1 rollover=0
[120] down=1 load_en=0 load=0x7 count=0x1 rollover=0
[130] down=1 load_en=0 load=0x7 count=0x0 rollover=0
[150] down=1 load_en=0 load=0x7 count=0xf rollover=1
[170] down=1 load_en=0 load=0x7 count=0xe rollover=0
[190] down=1 load_en=0 load=0x7 count=0xd rollover=0
Simulation complete via $finish(1) at time 210 NS + 0*
2.2.4 为什么二者不同?
Verilog通过其模块端口连接不同模块。对于大型设计,这种连接方法可能会变得更加耗时和重复。这些端口中的一些可能包括与总线协议相关的信号,如AXI/AHB、时钟和复位引脚、往返于RAM/存储器和其他外围设备的信号。
2.2.4.1 使用verilog 端口
这是Verilog中传统的端口连接方式。
module d_slave ( input clk,
reset,
enable,
// Many more input signals
output gnt,
irq,
// Many more output signals);
// Some design functionality
endmodule
module d_top ( [top_level_ports] );
reg [`NUM_SLAVES-1:0] clk; // Assume `NUM_SLAVES is a macro set to 2
reg [`NUM_SLAVES-1:0] tb_reset;
// Other declarations
d_slave slave_0 ( .clk (d_clk[0]), // These connections have to be
.reset (d_reset[0]) // repeated for all other slave instances
...
.gnt (d_gnt[0]),
... );
d_slave slave_1 ( ... );
d_slave slave_2 ( ... );
endmodule
让我们考虑一个场景,上面所示的设计中有12个从设备。如果d_slave模块端口发生了更改,则该更改也必须反映在d_top中的所有12个从属实例连接中。
2.2.4.2 缺点
使用Verilog端口方法进行连接的一些缺点是:
- 易于跟踪、调试和维护
- 太容易创建或破坏设计功能
- 设计要求的变更可能需要对多个模块进行修改
- 在多个模块、通信协议和其他地方需要复制
2.2.4.3 使用SystemVerilog 端口
注意,模块d_top简单地使用接口与从实例连接,而不是如前所示重复声明与从块的每个信号的连接。
interface slave_if (input logic clk, reset);
reg clk;
reg reset;
reg enable;
reg gnt;
// Declarations for other signals follow
endinterface
module d_slave (slave_if s_if);
// Design functionality
always (s_if.enable & s_if.gnt) begin // interface signals are accessed by the handle "s_if"
// Some behavior
end
endmodule
module d_top (input clk, reset);
// Create an instance of the slave interface
slave_if slave_if_inst ( .clk (clk),
.reset (reset));
d_slave slave_0 (.s_if (slave_if_inst));
d_slave slave_1 (.s_if (slave_if_inst));
d_slave slave_2 (.s_if (slave_if_inst));
endmodule
现在,如果从属接口中的一个信号发生了变化,它将自动应用于所有实例。在SystemVerilog中,模块端口列表也可以有一个具有接口类型的端口,而不是通常的输入、输出和inout。
2.2.5 接口阵列
在下面的示例中,在顶级测试台模块中创建并实例化了一个名为myInterface的带有空端口列表的接口。也可以省略空端口列表的括号,而用分号截断语句
// interface myInterface;
interface myInterface ();
reg gnt;
reg ack;
reg [7:0] irq;
...
endinterface
module tb;
// Single interface handle
myInterface if0 ();
// An array of interfaces
myInterface wb_if [3:0] ();
// Rest of the testbench
endmodule
可以实例化一个名为if0的接口,并且应该通过引用该句柄来访问该接口中的信号。然后,这可以用来驱动和采样去往DUT的信号。我们还可以有一系列接口。在这里,这个数组被称为wb_if,它有4个接口实例。
module myDesign ( myInterface dut_if,
input logic clk);
always @(posedge clk)
if (dut_if.ack)
dut_if.gnt <= 1;
endmodule
module tb;
reg clk;
// Single interface handle connection
myInterface if0;
myDesign top (if0, clk);
// Or connect by name
// myDesign top (.dut_if(if0), .clk(clk));
// Multiple design instances connected to the appropriate
// interface handle
myDesign md0 (wb_if[0], clk);
myDesign md1 (wb_if[1], clk);
myDesign md2 (wb_if[2], clk);
myDesign md3 (wb_if[3], clk);
endmodule
当一个接口被引用为端口时,其中的变量和网络分别具有ref和inout访问权限。如果在设计中使用相同的标识符作为接口实例名和端口名,那么也可以使用隐式端口连接。
module tb;
reg clk;
myInterface dut_if();
// Can use implicit port connection when all port signals have same name
myDesign top (.*);
endmodule