The input and output signals of a module are the main way to communicate with other blocks in the design. There can be hundreds of signals for complex designs that involve multiple bus protocols, memory interfaces and connections to various other peripherals. From a testbench perspective, such designs have to be instantiated and connected for each signal and it becomes time consuming to debug, prone to errors and difficult to maintain for design changes.
What is SystemVerilog interface ?
interface is a named bundle of nets or variables created specifically to encapsulate communication between blocks, hence assuring a smoother migration between different projects. Moreover it brings an element of abstraction by hiding away the details. It also enables different blocks to be connected to the testbench more easily.
Traditional way of connecting design with testbench
module mydesign ( input clk, reset, enable, ... output gnt, irq, ... ); module tb; reg clk; reg tb_reset; ... mydesign top ( .clk (tb_clk), .reset (tb_reset) ... .gnt (tb_gnt), ... ); endmodule
As you can see, it becomes a little messy and hard to maintain as the port list grows. Now we'll see the power of an interface. It can also have functions, tasks, variables, and parameters making it more like a class template. Also it has the ability to define policies of directional information for different module ports via the
modport construct alongwith testbench synchronization capabilities with clocking blocks. Last but not the least, it can also contain
always procedures and continuous
assign statements. To make it a power punch, we can also put in assertions, coverage recording and other protocol checking elements.
Interface blocks are defined and described within
endinterface keywords. It can be instantiated like a module with or without ports.
interface [name] ([port_list]); ... // list of signals ... endinterface
In the example below an interface named myInterface with an empty port list is created and instantiated within the top level testbench module. It is also fine to omit the parenthesis for an empty port list and instead truncate the statement with a semicolon
// interface myInterface; interface myInterface (); reg gnt; reg ack; reg [7:0] irq; ... endinterface module tb; myInterface if0 (); myInterface wb_if [3:0] (); ... endmodule
In this case a single interface called if0 is created and signals within it can be accessed via this handle, and an array of interfaces are instantiated that goes by the name wb_if to wb_if. Note that a
module cannot be instantiated in an interface, but the other way is possible. Now let's create an interface called if0 and pass that as an argument to the design under verification which can be used to drive and sample signals. Look at how short this code became !
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); // Or connect by name // myDesign top (.dut_if(if0), .clk(clk)); endmodule
When an interface is referenced as a port, the variables and nets in it are assumed to have ref and inout access respectively. If same identifiers are used as interface instance name and port name in the design, then implicit port connections can also be used.
module tb; reg clk; myInterface dut_if(); // Can use implicit port connection when all port signals have same name myDesign top (.*); endmodule