子例程(任务和函数)可以在接口中定义,也可以在连接的一个或多个模块中定义。这允许更抽象的建模级别。例如,“读”和“写”可以定义为任务,而不需要引用任何连线,主模块只能调用这些任务。在modport中,这些任务被声明为导入任务( import tasks)。
函数原型指定参数的类型和方向,以及在其他地方定义的函数的返回值。类似地,任务原型指定了在其他地方定义的任务的参数的类型和方向。在modport中,导入和导出构造可以使用子例程原型,也可以只使用标识符。唯一的例外是当使用modport从另一个模块导入子例程时,以及当使用默认参数值或按名称绑定参数时,在这种情况下,应使用完整的原型。
原型中参数的数量和类型应与子程序声明中的参数类型相匹配。6.22.1中介绍了类型匹配的规则。如果子程序调用中需要默认参数值,则应在原型中指定。如果参数在原型和声明中都指定了默认值,则指定的值不必相同,但使用的默认值应为原型中指定的值。原型中的正式参数名称应是可选的,除非使用默认参数值或按名称绑定的参数,或者声明了额外的未封装维度。原型中的形式参数名称应与声明中的形式变量名称相同。
如果一个模块连接到包含导出子例程的modport,而该模块没有定义该子例程,则会发生elaboration错误。类似地,如果modport包含导出的子例程原型,而模块中定义的子例程与该原型不完全匹配,则会发生elaboration错误。
如果在模块中使用层次名称定义子例程,则它们也应在接口中声明为extern或在modport中声明为export。
任务(而非函数)可以在实例化两次的模块中定义,例如,由同一CPU驱动的两个存储器。接口中的extern forkjoin声明允许这样的多个任务定义。
interface simple_bus (input logic clk); // Define the interface
logic req, gnt;
logic [7:0] addr, data;
logic [1:0] mode;
logic start, rdy;
task masterRead(input logic [7:0] raddr); // masterRead method
// ...
endtask: masterRead
task slaveRead; // slaveRead method
// ...
endtask: slaveRead
endinterface: simple_bus
module memMod(interface a); // Uses any interface
logic avail;
always @(posedge a.clk) // the clk signal from the interface
a.gnt <= a.req & avail // the gnt and req signals in the interface
always @(a.start)
a.slaveRead;
endmodule
module cpuMod(interface b);
enum {read, write} instr;
logic [7:0] raddr;
always @(posedge b.clk)
if (instr == read)
b.masterRead(raddr); // call the Interface method
...
endmodule
module top;
logic clk = 0;
simple_bus sb_intf(clk); // Instantiate the interface
memMod mem(sb_intf);
cpuMod cpu(sb_intf);
endmodule
这个接口示例展示了如何在完整的读/写接口中使用modports来控制信号方向和任务访问。
interface simple_bus (input logic clk); // Define the interface
logic req, gnt;
logic [7:0] addr, data;
logic [1:0] mode;
logic start, rdy;
modport slave (input req, addr, mode, start, clk,
output gnt, rdy,
ref data,
import slaveRead,
slaveWrite);
// import into module that uses the modport
modport master(input gnt, rdy, clk,
output req, addr, mode, start,
ref data,
import masterRead,
masterWrite);
// import into module that uses the modport
task masterRead(input logic [7:0] raddr); // masterRead method
// ...
endtask
task slaveRead; // slaveRead method
// ...
endtask
task masterWrite(input logic [7:0] waddr);
//...
endtask
task slaveWrite;
//...
endtask
endinterface: simple_bus
module memMod(interface a); // Uses just the interface
logic avail;
always @(posedge a.clk) // the clk signal from the interface
a.gnt <= a.req & avail; // the gnt and req signals in the interface
always @(a.start)
if (a.mode[0] == 1'b0)
a.slaveRead;
else
a.slaveWrite;
endmodule
module cpuMod(interface b);
enum {read, write} instr;
logic [7:0] raddr = $random();
always @(posedge b.clk)
if (instr == read)
b.masterRead(raddr); // call the Interface method
// ...
else
b.masterWrite(raddr);
endmodule
module omniMod( interface b);
//...
endmodule: omniMod
module top;
logic clk = 0;
simple_bus sb_intf(clk); // Instantiate the interface
memMod mem(sb_intf.slave); // only has access to the slave tasks
cpuMod cpu(sb_intf.master); // only has access to the master tasks
omniMod omni(sb_intf); // has access to all master and slave tasks
endmodule
这个接口示例展示了如何在一个模块中定义任务,并在另一个模块调用它们,使用modports来控制任务访问。
interface simple_bus (input logic clk); // Define the interface
logic req, gnt;
logic [7:0] addr, data;
logic [1:0] mode;
logic start, rdy;
modport slave( input req, addr, mode, start, clk,
output gnt, rdy,
ref data,
export Read,
Write);
// export from module that uses the modport
modport master(input gnt, rdy, clk,
output req, addr, mode, start,
ref data,
import task Read(input logic [7:0] raddr),
task Write(input logic [7:0] waddr));
// import requires the full task prototype
endinterface: simple_bus
module memMod(interface a); // Uses just the interface keyword
logic avail;
task a.Read; // Read method
avail = 0;
...
avail = 1;
endtask
task a.Write;
avail = 0;
...
avail = 1;
endtask
endmodule
module cpuMod(interface b);
enum {read, write} instr;
logic [7:0] raddr;
always @(posedge b.clk)
if (instr == read)
b.Read(raddr); // call the slave method via the interface
...
else
b.Write(raddr);
endmodule
module top;
logic clk = 0;
simple_bus sb_intf(clk); // Instantiate the interface
memMod mem(sb_intf.slave); // exports the Read and Write tasks
cpuMod cpu(sb_intf.master); // imports the Read and Write tasks
endmodule
多个模块导出(export)相同的任务名称通常是错误的。然而,相同modport类型的几个实例可以连接到一个接口,例如前面示例中的内存模块。为了让它们仍然可以导出它们的读写任务,这些任务应该在接口中使用extern forkjoin关键字声明。对extern forkjoin任务countslaves()的调用;在以下示例中,行为如下:
fork
top.mem1.a.countslaves;
top.mem2.a.countslaves;
join
对于读取任务,只有一个模块应该主动响应任务调用,例如,包含适当地址的模块。其他模块中的任务应返回而不产生任何效果。只有这样,活动任务才能写入结果变量。与任务不同,不允许多次导出函数,因为它们总是写入结果。
禁用对外部forkjoin任务的影响如下:
--如果通过接口实例引用任务,则应禁用所有任务调用。
--如果任务是通过模块实例引用的,则只应禁用对该模块实例的任务调用。
--如果一个接口包含一个外部forkjoin任务,并且没有连接到该接口的模块定义该任务,那么对该任务的任何调用都将报告运行时错误,并立即返回而不产生任何影响。这个接口示例展示了如何在多个模块中定义任务,并使用extern forkjoin在另一个模块中调用它们。多任务导出机制还可以用于统计连接到每个接口实例的特定modport的实例。
interface simple_bus (input logic clk); // Define the interface
logic req, gnt;
logic [7:0] addr, data;
logic [1:0] mode;
logic start, rdy;
int slaves = 0;
// tasks executed concurrently as a fork-join block
extern forkjoin task countSlaves();
extern forkjoin task Read (input logic [7:0] raddr);
extern forkjoin task Write (input logic [7:0] waddr);
modport slave (input req,addr, mode, start, clk,
output gnt, rdy,
ref data, slaves,
export Read, Write, countSlaves);
// export from module that uses the modport
modport master ( input gnt, rdy, clk,
output req, addr, mode, start,
ref data,
import task Read(input logic [7:0] raddr),
task Write(input logic [7:0] waddr));
// import requires the full task prototype
initial begin
slaves = 0;
countSlaves;
$display ("number of slaves = %d", slaves);
end
endinterface: simple_bus
module memMod #(parameter int minaddr=0, maxaddr=0;) (interface a);
logic avail = 1;
logic [7:0] mem[255:0];
task a.countSlaves();
a.slaves++;
endtask
task a.Read(input logic [7:0] raddr); // Read method
if (raddr >= minaddr && raddr <= maxaddr) begin
avail = 0;
#10 a.data = mem[raddr];
avail = 1;
end
endtask
task a.Write(input logic [7:0] waddr); // Write method
if (waddr >= minaddr && waddr <= maxaddr) begin
avail = 0;
#10 mem[waddr] = a.data;
avail = 1;
end
endtask
endmodule
module cpuMod(interface b);
typedef enum {read, write} instr;
instr inst;
logic [7:0] raddr;
integer seed;
always @(posedge b.clk) begin
inst = instr'($dist_uniform(seed, 0, 1));
raddr = $dist_uniform(seed, 0, 3);
if (inst == read) begin
$display("%t begin read %h @ %h", $time, b.data, raddr);
callr:b.Read(raddr);
$display("%t end read %h @ %h", $time, b.data, raddr);
end
else begin
$display("%t begin write %h @ %h", $time, b.data, raddr);
b.data = raddr;
callw:b.Write(raddr);
$display("%t end write %h @ %h", $time, b.data, raddr);
end
end
endmodule
module top;
logic clk = 0;
function void interrupt();
disable mem1.a.Read; // task via module instance
disable sb_intf.Write; // task via interface instance
if (mem1.avail == 0) $display ("mem1 was interrupted");
if (mem2.avail == 0) $display ("mem2 was interrupted");
endfunction
always #5 clk++;
initial begin
#28 interrupt();
#10 interrupt();
#100 $finish;
end
simple_bus sb_intf(clk);
memMod #(0, 127) mem1(sb_intf.slave);
memMod #(128, 255) mem2(sb_intf.slave);
cpuMod cpu(sb_intf.master);
endmodule
接口定义可以以与模块定义相同的方式利用参数和参数重新定义。以下示例显示如何在接口定义中使用参数。
interface simple_bus #(AWIDTH = 8, DWIDTH = 8)
(input logic clk); // Define the interface
logic req, gnt;
logic [AWIDTH-1:0] addr;
logic [DWIDTH-1:0] data;
logic [1:0] mode;
logic start, rdy;
modport slave( input req, addr, mode, start, clk,
output gnt, rdy,
ref data,
import task slaveRead,
task slaveWrite);
// import into module that uses the modport
modport master(input gnt, rdy, clk,
output req, addr, mode, start,
ref data,
import task masterRead(input logic [AWIDTH-1:0] raddr),
task masterWrite(input logic [AWIDTH-1:0] waddr));
// import requires the full task prototype
task masterRead(input logic [AWIDTH-1:0] raddr); // masterRead method
...
endtask
task slaveRead; // slaveRead method
...
endtask
task masterWrite(input logic [AWIDTH-1:0] waddr);
...
endtask
task slaveWrite;
...
endtask
endinterface: simple_bus
module memMod(interface a); // Uses just the interface keyword
logic avail;
always @(posedge a.clk) // the clk signal from the interface
a.gnt <= a.req & avail; //the gnt and req signals in the interface
always @(a.start)
if (a.mode[0] == 1'b0)
a.slaveRead;
else
a.slaveWrite;
endmodule
module cpuMod(interface b);
enum {read, write} instr;
logic [7:0] raddr;
always @(posedge b.clk)
if (instr == read)
b.masterRead(raddr); // call the Interface method
// ...
else
b.masterWrite(raddr);
endmodule
module top;
logic clk = 0;
simple_bus sb_intf(clk); // Instantiate default interface
simple_bus #(.DWIDTH(16)) wide_intf(clk); // Interface with 16-bit data
initial repeat(10) #10 clk++;
memMod mem(sb_intf.slave); // only has access to the slaveRead task
cpuMod cpu(sb_intf.master); // only has access to the masterRead task
memMod memW(wide_intf.slave); // 16-bit wide memory
cpuMod cpuW(wide_intf.master); // 16-bit wide cpu
endmodule