Interface simplifies hooking up modules/ programs.
There are 2 methods of hooking up stuff using interface.
Hardwire method:
RTL: portname is interface_name.modport_name choose_a_name.
all outputs are assigned as choose_a_name = value.
At top level: create a instance of interface_name.
interface_name instance_of_interface_name (.clk(clk));
and hook up choose_a_name with instance_of_interface_name.
An example will make it clear. I am using an example from my current assignment at the UCSC extension coursework.
module memory_core (memory_interface.core_port deepika_coreif);
always @ (posedge deepika_coreif.clk)
....
endmodule
module memory_ctrl(memory_interface.ctrl_port deepika_ctrlif);
....
endmodule
program testcase(memory_interface.testcase_port deepika_tcif,
input call_finish);
............
endprogram
module top();
bit clk;
memory_interface imif (.clk (clk));
..........
initial begin
clk = 0;
end
always #5 clk = !clk;
....
memory_core icore (imif);
memory_ctrl ictrl (imif);
testcase itest (.deepika_tcif (imif),
.call_finish (call_finish) );
endmodule
Generic method:
Here at the RTL end, the porttype is simply interface. What gets hooked up is instance_of_interface.specific_modport.
module memory_core (interface deepika_coreif);
..
endmodule
module memory_ctrl(interface deepika_ctrlif);
...
endmodule
program testcase(interface deepika_tcif,
input call_finish);
..
endprogram
module top();
bit clk;
....
memory_interface imif (.clk (clk));
initial begin
clk = 0;
end
always #5 clk = !clk;
memory_core icore (imif.core_port);
memory_ctrl ictrl (imif.ctrl_port);
testcase itest (.deepika_tcif (imif.testcase_port),
.call_finish (call_finish) );
endmodule
The code for interface itself remains unaffected whichever way you to connect stuff.
interface memory_interface (input bit clk);
logic reset;
.....
//modports
modport core_port(
input clk, reset,...,
output ... );
modport ctrl_port(
input clk, reset, ...
output ... );
);
modport testcase_port(
input clk, reset...,
output ...,
import init_mem,
import write_mem,
import read_mem
);
task init_mem();
reset = 1; ....
#100 reset = 0;
endtask
task write_mem(ref int passed_addr,
ref bit [7:0] passed_data );
...
endtask
task read_mem( ref int passed_addr);
....
endtask
endinterface
Advantages of using tasks:
In my example, I could use the tasks and fork-join to test what happens when write and read occurs simultaneously (This could never happen because there is a single read/writebar pin but its fun to check it anyways.. Pretty cool stuff.)
Note that task only has whatever is "passed" as the inputs/outputs. Task is actively driving the outputs but enumerating that is already taken care of in the modport. A good way of simplifying the code.
Interface basically gives you a overview of stuff. Since you consolidate all wires in one file, all worry about data_width mismatch, or whether you connected little/ big endianess correctly is taken care of.
There are 2 methods of hooking up stuff using interface.
Hardwire method:
RTL: portname is interface_name.modport_name choose_a_name.
all outputs are assigned as choose_a_name = value.
At top level: create a instance of interface_name.
interface_name instance_of_interface_name (.clk(clk));
and hook up choose_a_name with instance_of_interface_name.
An example will make it clear. I am using an example from my current assignment at the UCSC extension coursework.
module memory_core (memory_interface.core_port deepika_coreif);
always @ (posedge deepika_coreif.clk)
....
endmodule
module memory_ctrl(memory_interface.ctrl_port deepika_ctrlif);
....
endmodule
program testcase(memory_interface.testcase_port deepika_tcif,
input call_finish);
............
endprogram
module top();
bit clk;
memory_interface imif (.clk (clk));
..........
initial begin
clk = 0;
end
always #5 clk = !clk;
....
memory_core icore (imif);
memory_ctrl ictrl (imif);
testcase itest (.deepika_tcif (imif),
.call_finish (call_finish) );
endmodule
Generic method:
Here at the RTL end, the porttype is simply interface. What gets hooked up is instance_of_interface.specific_modport.
module memory_core (interface deepika_coreif);
..
endmodule
module memory_ctrl(interface deepika_ctrlif);
...
endmodule
program testcase(interface deepika_tcif,
input call_finish);
..
endprogram
module top();
bit clk;
....
memory_interface imif (.clk (clk));
initial begin
clk = 0;
end
always #5 clk = !clk;
memory_core icore (imif.core_port);
memory_ctrl ictrl (imif.ctrl_port);
testcase itest (.deepika_tcif (imif.testcase_port),
.call_finish (call_finish) );
endmodule
interface memory_interface (input bit clk);
logic reset;
.....
//modports
modport core_port(
input clk, reset,...,
output ... );
modport ctrl_port(
input clk, reset, ...
output ... );
);
modport testcase_port(
input clk, reset...,
output ...,
import init_mem,
import write_mem,
import read_mem
);
task init_mem();
reset = 1; ....
#100 reset = 0;
endtask
task write_mem(ref int passed_addr,
ref bit [7:0] passed_data );
...
endtask
task read_mem( ref int passed_addr);
....
endtask
endinterface
Advantages of using tasks:
In my example, I could use the tasks and fork-join to test what happens when write and read occurs simultaneously (This could never happen because there is a single read/writebar pin but its fun to check it anyways.. Pretty cool stuff.)
Note that task only has whatever is "passed" as the inputs/outputs. Task is actively driving the outputs but enumerating that is already taken care of in the modport. A good way of simplifying the code.
Interface basically gives you a overview of stuff. Since you consolidate all wires in one file, all worry about data_width mismatch, or whether you connected little/ big endianess correctly is taken care of.