一.端口的缺点
数字系统模块之间的通信是至关重要的领域。在Verilog中,使用模块端口连接模块。对于大型模块效率低下:
【1】手动连接数百个端口可能会导致错误;
【2】需要所有端口的详细信息;
【3】设计更改,难以更改;
【4】更加耗时;
【5】大多数端口声明工作在许多模块中都是重复的。
让我们看一个verilog示例:
module Dut (input clk, read, enable,
Input [7:0] addr,
output [7:0] data);
....
assign data = temp1 ? temp2 : temp3 ;
always @(posedge clk)
....
endmodule
module Testbench(input clk,
Output read, enable,
output [7:0] addr,
input [7:0] data );
endmodule
将以上两个模块集成到顶部模块中。
1 module top();
2 reg clk;
3 wire read, enable;
4 wire [7:0] addr;
5 wire [7:0] data;
6
7 Dut D (clk,read,enable,Addr,data);
8
9 Testbench TB(clk,read,enable,Addr,data);
10
11 endmodule
所有的连接clk,读取,启用,地址,数据都是手动完成的。 第7行和第9行具有相同的代码结构,因此重复工作。 如果添加了新端口,则需要更改DUT端口,TestBench端口以及顶部模块的7、9行。 这很耗时,并且随着端口列表的增加,维护起来很复杂。
二.接口
为了解决上述问题,SystemVerilog添加了一个新的强大功能,称为接口。接口封装了块之间的互连和通信。
上例的接口声明:
interface intf #(parameter BW = 8)(input clk);如:#(100)代表接口中第一个参数;
logic read, enable;
logic [BW -1 :0] addr,data;
endinterface :intf
此处,read,enable,addr,data的信号被分组为“ intf”。 接口也可以将方向指定为输入,输出和输入。 在上面的示例中,clk信号用作接口的输入。 接口也可以具有模块之类的参数。 接口声明就像模块声明一样。 使用关键字interface,endinterface进行定义。 在模块内部,对接口中的信号使用层次名称。
提示:如果有多个驱动器,请使用wire类型。如果是单个驱动程序,请使用logic类型。
让我们使用上面声明的接口查看DUT和Testbench模块。
module Dut (intf dut_if); // 声明接口
always @(posedge dut_if.clk)
if(dut_if.read) // 采样信号
$display(" Read is asserted");
endmodule
module Testbench(intf tb_if);
initial
begin
tb_if.read = 0;
repeat(3) #20 tb_if.read = ~tb_if.read;// 驱动信号
$finish;
end
endmodule
将以上两个模块集成到顶部模块中。
module top();
bit clk;
initial
forever #5 clk = ~clk;
intf bus_if(clk); //接口实例化
Dut d(bus_if); //Dut模块实例化d,使用接口连接D和TB
Testbench TB (bus_if);
endmodule
三.使用接口的优点:
【1】接口可以作为单个信号传递;
【2】它允许结构化信息在块之间流动;
【3】它可以包含模块中可能包含的任何内容,但其他模块定义或实例除外;
【4】接口定义独立于模块;
【5】提高可重用性;
【6】它的Interface可以在一个单独的文件中声明,也可以单独编译;
【7】接口可以包含任务(tasks)和功能(functions)。通过这种方法,所有连接到此信息的模块可以共享一个方法;
【8】接口可以包含使用断言(assertions)和功能(functional)覆盖块的协议检查;
【9】减少了在模块连接期间可能引起的错误;
【10】易于添加或删除信号。易于维护。
四.接口端口
在前面的示例中,信号clk被声明为接口的端口。 接口端口的工作方式类似于模块端口。 实例化接口时,可以按名称或位置在外部连接端口列表的成员,如模块顶部代码的第3行所示。
intf bus_if(clk);
五.Modports
在上面的示例中,我们没有提到信号的方向。Dut和Testbench模块clk信号方向为输入。但是对于其余信号,方向并不相同。在接口中使用modport结构,能够将信号分组,并指定方向。让我们在前面的示例中了解modport的用法。需要2个mod端口定义,一个用于DUT,另一个用于TestBench。
上例的接口声明:
interface intf (input clk);
logic read, enable,
logic [7:0] addr,data;
modport dut (input read,enable,addr,output data);
modport tb (output read,enable,addr,input data);
endinterface :intf
Modport选择可以通过两种方式完成。一个在模块声明中,另一个在实例化中。
5.1Modport选择任务模块定义
module Dut (intf.dut dut_if); // 调用接口intf中dut部分,并实例化名为dut_if
....
assign dut_if.data = temp1 ? temp2 : temp3 ;
always @(posedge intf.clk)
....
endmodule
module Testbench(intf.tb tb_if);
.....
.....
endmodule
module top();
logic clk;
intf bus_if(clk); //接口实例化
Dut d(bus_if);
Testbench TB (bus_if);
endmodule
5.2Modport选择任务模块实例化完成
module Dut (intf dut_if);
....
assign dut_if.data = temp1 ? temp2 : temp3 ;
always @(posedge intf.clk)
....
endmodule
module Testbench(intf tb_if);
.....
.....
endmodule
1 module top();
2 logic clk;
3 intf bus_if(clk);
4 Dut d(bus_if.dut); // 将modport传递到模块中
5 Testbench TB (bus_if.tb);
6 endmodule
Mod端口还可以定义表达式。他们还可以定义自己的名称。模块可以使用modport声明的名称。例如:
modport dut (input read,enable,.addr(2),output .d(data[1:5]);
module dut(intf.dut dut_if);
assign dut_if.d = temp; // 使用modport声明的信号名。
六.接口中的方法
接口可以包括任务(tasks)和功能(function)定义。这允许更抽象的建模级别。
interface intf (input clk);
logic read, enable,
logic [7:0] addr,data;
task masterRead(input logic [7:0] raddr); // masterRead
...
endtask: masterRead
task slaveRead; // slaveRead method
...
endtask: slaveRead
endinterface :intf
参考文献:
【1】http://www.testbench.in/IF_03_INTERFACE_METHODS.html