SystemVerilog接口允许我们将多个信号组合在一起,并将它们表示为单个端口。 所有这些信号都可以在一个地方声明和维护,并且易于维护。 接口内的信号由接口实例句柄访问。
Syntax
接口块在interface和endinterface关键字中定义和描述。可以像带有或不带有端口的模块一样实例化它。
interface [name] ([port_list]);
[list_of_signals]
endinterface
接口还可以具有函数,任务,变量和参数,使其更像类模板。 它还具有通过modport构造为不同模块端口定义方向信息策略的能力,以及带有时钟模块的测试台同步功能。 它还可以具有断言,覆盖率记录和其他协议检查元素。 最后但并非最不重要的一点,它还可以包含初始和始终过程以及连续的Assign语句。
无法在接口中实例化模块! 但是可以在模块中实例化接口。
SystemVerilog现在作为HDL流行起来,让我们看看两种在Verilog和SystemVerilog中使用具有相同设计的接口的情况。 为了使这个简单的例子简单,我们将创建一个简单的接口。
Verilog设计接口
让我们看看如何在测试平台中使用接口并通过端口列表将其连接到标准Verilog设计。 下面显示的代码是Verilog中up-down计数器的设计。 该模块接受一个参数来确定计数器的宽度。 它还接受仅在load_en为1时才加载到计数器的输入负载值load。
当输入down为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用于为设计提供时钟->这里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);
//初始化测试台变量
clk <= 0;
cnt_if0.rstn <= 0;
cnt_if0.load_en <= 0;
cnt_if0.load <= 0;
cnt_if0.down <= 0;
// 5个时钟后将驱动器设计取消复位
repeat (5) @(posedge clk);
cnt_if0.rstn <= 1;
//驱动仿真,重复5次
for (int i = 0; i < 5; i++) begin
// 随机延迟后驱动输入
int delay = $urandom_range (1,30);
#(delay);
// 随机输入要驱动的值
std::randomize(load, load_en, down);
This module accepts an interface object as the port list
//将tb值赋值给接口信号
cnt_if0.load <= load;
cnt_if0.load_en <= load_en;
cnt_if0.down <= down;
end
// 等待5个时钟并完成仿真
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 loadomize(load, load_en, down);
d_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
SystemVerilog设计使用接口
现在让我们看看如何在测试平台中使用接口并将其连接到SystemVerilog设计模块。 SystemVerilog允许模块接受接口作为端口列表,而不是单独的信号。 在下面显示的设计示例中,我们用用于定义设计功能的接口句柄替换了counter_ud的端口列表。
`timescale 1ns/1ns
// 该模块接受接口对象作为端口列表
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的接口句柄传递,并用于将测试平台的输入驱动到设计中。如果需要,可以使用相同的接口句柄来监视设计的输出。
// 接口定义与以前相同
module tb;
reg clk;
//TB Clock Generator用于为设计提供时钟->这里half_period = 10ns => 50 MHz
always #10 clk = ~clk;
cnt_if cnt_if0 (clk);
// 请注意,这里我们只需要将接口句柄传递给设计,而不是连接每个单独的信号
counter_ud c0 (cnt_if0);
// 激励与以前相同
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
与Verilog有何不同?
Verilog通过其模块端口在不同模块之间连接。 对于大型设计,这种连接方法会变得更加耗时且重复。 这些端口中的某些端口可能包含与总线协议(如AXI / AHB),时钟和复位引脚有关的信号,以及往返RAM /内存和其他外围设备的信号。
使用Verilog端口
这是Verilog中传统的端口连接方式。
module d_slave ( input clk,
reset,
enable,
//更多输入信号
output gnt,
irq,
// 更多的输出信号
);
// 一些设计功能
endmodule
module d_top ( [top_level_ports] );
reg [`NUM_SLAVES-1:0] clk; // 假设NUM_SLAVES是设置为2的宏
reg [`NUM_SLAVES-1:0] tb_reset;
// 其他声明
d_slave slave_0 (
.clk (d_clk[0]), // 对于所有模块实例化,必须重复这些连接
.reset (d_reset[0]) //
...
.gnt (d_gnt[0]),
... );
d_slave slave_1 ( ... );
d_slave slave_2 ( ... );
endmodule
让我们考虑一个方案,其中在上面显示的设计中有十二个需要例化的模块。如果在d_slave模块端口进行了更改,则该更改也必须反映在d_top中的所有十二个从属实例连接中。
使用Verilog端口方法进行连接的一些缺点是:
【1】繁琐的跟踪,调试和维护;
【2】太容易制作或破坏设计功能;
【3】设计要求的更改可能需要在多个模块中进行修改;
【4】在多个模块,通信协议和其他地方需要复制。
使用SystemVerilog接口
注意,模块d_top仅使用接口与从属实例进行连接,而不是像之前所示重复声明与从属块的每个信号的连接。
interface slave_if (input logic clk, reset);
reg clk;
reg reset;
reg enable;
reg gnt;
endinterface
module d_slave (slave_if s_if);
// 设计功能
always (s_if.enable & s_if.gnt) begin //接口信号由句柄“ s_if”访问
// Some behavior
end
endmodule
module d_top (input clk, reset);
// 创建从属接口的实例
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中,模块端口列表也可以具有带有接口类型的端口,而不是通常的输入,输出和输入输出。
接口数组
在下面的示例中,将创建一个名为myInterface的接口,该接口带有一个空端口列表,并在顶级testbench模块中实例化该接口。 也可以省略括号以表示空端口列表,而用分号截断语句;
// interface myInterface;
interface myInterface ();
reg gnt;
reg ack;
reg [7:0] irq;
...
endinterface
module tb;
//单接口句柄
myInterface if0 ();
// 接口数组
myInterface wb_if [3:0] ();
// 测试台的其余部分
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;
myInterface if0;
myDesign top (if0, clk);
// 或按名称连接
// myDesign top (.dut_if(if0), .clk(clk));
// 多个设计实例连接到适当的接口句柄
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
当将接口引用为端口时,假定其中的变量和nets分别具有ref和inout访问权限。 如果在设计中将相同的标识符用作接口实例名称和端口名称,则也可以使用隐式端口连接。
module tb;
reg clk;
myInterface dut_if();
// 当所有端口信号具有相同名称时,可以使用隐式端口连接
myDesign top (.*);
endmodule
参考文献:
【1】https://www.chipverify.com/systemverilog/systemverilog-interface-intro