在接口中定义带有方向的modport
列表,以对模块内的接口访问施加某些限制。关键字指示方向的声明方式与模块内部一样。
Syntax
modport [identifer] (
input [port_list],
output [port_list]
);
下面显示的是接口myInterface
的定义,它有几个信号和两个声明。modportdut0
本质上声明信号ack
和sel
是输入,gnt
和irq0
是使用此特定modport的任何模块的输出。
类似地,声明了另一个名为dut1
的modport,它声明gnt
和irq0
是输入,另外两个是任何使用modportdut1
的模块的输出。
interface myInterface;
logic ack;
logic gnt;
logic sel;
logic irq0;
// ack and sel are inputs to the dut0, while gnt and irq0 are outputs
modport dut0 (
input ack, sel,
output gnt, irq0
);
// ack and sel are outputs from dut1, while gnt and irq0 are inputs
modport dut1 (
input gnt, irq0,
output ack, sel
);
endinterface
Example of named port bundle
在这种风格中,设计将从接口对象中获取所需的正确modport定义,如其端口列表中所述。tb只需要为设计提供整个接口对象。
module dut0 (myInterface.dut0 _if);
...
endmodule
module dut1 (myInterface.dut1 _if);
...
endmodule
module tb;
myInterface _if;
dut0 d0 ( .* );
dut1 d1 ( .* );
endmodule
Example of connecting port bundle
在这种风格中,设计只是接受任何方向信息。因此,tb负责为设计提供正确的modport值。
module dut0 (myInterface _if);
...
endmodule
module dut1 (myInterface _if);
...
endmodule
module tb;
myInterface _if;
dut0 d0 (._if(_if.dut0));
dut1 d1 (._if(_if.dut1));
endmodule
What is the need for a modport ?
默认情况下,简单接口中声明的是net,因此连接到同一net的任何模块都可以驱动或从中获取值。简单来说,对数据传播的方向没有限制。你最终可能会在net上得到一个x,因为tb和设计都将两个不同的值驱动到一个接口net。tb编写者应特别小心,以确保不会发生这种情况。这可以通过使用modport从本质上避免。inout
Example of connecting to generic interface
A还可以将通用接口用作端口列表。通用句柄可以接受从上面的层次结构传递给它的任何modport。
module dut0 (interface _if);
...
endmodule
module dut1 (interface _if);
...
endmodule
module tb;
myInterface _if;
dut0 d0 (._if (_if.dut0));
dut1 d1 (._if (_if.dut1));
endmodule
Design Example
让我们考虑两个模块主从模块
通过一个非常简单的总线结构连接。假设总线能够发送一个地址和数据,slave
应该捕获并更新其内部寄存器中的信息。因此,master
始终必须启动传输,并且从站能够通过sready
信号向master
指示它是否准备好接受数据。
Interface
下面显示的是master和slave之间共享的定义。
interface ms_if (input clk);
logic sready;
logic rstn;
logic [1:0] addr;
logic [7:0] data;
modport slave (input addr, data, rstn, clk,
output sready);
modport master (output addr, data,
input clk, sready, rstn);
endinterface
RTL Design
假设主节点只是将地址从0迭代到3,并发送等于地址乘以4的数据。master
应该只在slave准备好接受并由sready
信号指示时发送。
// This module accepts an interface with modport "master"
// master sends transactions in a pipelined format
// CLK 1 2 3 4 5 6
// ADDR A0 A1 A2 A3 A0 A1
// DATA D0 D1 D2 D3 D4
module master (ms_if.master mif);
always @(posedge mif.clk) begin
// If reset is applied, set addr and data to default values
if (!mif.rst) begin
mif.addr <= 0;
mif.data <= 0;
// Else increment addr, and assign data accordingly if slave is ready
end else begin
// Send new addr and data only if slave is ready
if (mif.sready) begin
mif.addr <= mif.addr + 1;
mif.data <= mif.addr * 4;
// Else maintain curent addr and data
end else begin
mif.addr <= mid.addr;
mif.data <= mif.data;
end
end
end
endmodule
假设slave接受每个加法器的数据,并将它们分配给内存寄存器,当地址从3换行到0时,slave需要1个额外的时钟才能准备就绪。
module slave (ms_if.slave sif);
reg [7:0] reg_a;
reg [7:0] reg_b;
reg reg_c;
reg [3:0] reg_d;
reg dly;
reg [3:0] addr_dly;
always @(posedge sif.clk) begin
if (!sif.rstn) begin
addr_dly <= 0;
end else begin
addr_dly <= sif.addr;
end
end
always @(!sif.rstn) begin
if (!sif.rstn) begin
reg_a <= 0;
reg_b <= 0;
reg_c <= 0;
reg_d <= 0;
end else begin
case (addr_dly)
0 : reg_a <= sif.data;
1 : reg_b <= sif.data;
2 : reg_c <= sif.data;
3 : reg_d <= sif.data;
endcase
end
end
assign sif.sready = ~(sif.addr[1] & sif.addr[0]) | ~dly;
always @ (posedge sif.clk) begin
if (!sif.rstn)
dly <= 1;
else
dly <= sif.sready;
end
endmodule
这两个设计模块在顶层捆绑在一起。
module d_top (ms_if tif);
// Pass the "master" modport to master
master m0 (tif.master);
// Pass the "slave" modport to slave
slave s0 (tif.slave);
endmodule
Testbench
testbench会将接口句柄传递给设计,然后设计将master
和slave
分配给其子模块。
module tb;
reg clk;
always #10 clk = ~clk;
ms_if if0 (clk);
d_top d0 (if0);
initial begin
clk <= 0;
if0.rstn <= 0;
repeat (5) @ (posedge clk);
if0.rstn <= 1;
repeat (20) @ (posedge clk);
$finish;
end
endmoule
请记住,master发起总线事务,slave捕获数据并将其存储在相应地址的内部寄存器reg_*
中。