SystemVerilog Interfaces Tutorial
Interfaces are a major new construct in SystemVerilog, created
specifically to encapsulate the communication between blocks, allowing
a smooth refinement from abstract system-level through successive steps
down to lower RTL and structural levels of the design. Interfaces also
facilitate design re-use. Interfaces are hierarchical structures that
can contain other interfaces.
There are several advantages when using an Interface:
- They encapsulate connectivity: an interface can be passed as a single item through a port, thus replacing a group of names by a single one. This reduces the amount of code needed to model port connections and improves its maintainability as well as readability.
- They encapsulate functionality, isolated from the modules that
are connected via the interface. So, the level of abstraction and the
granularity of the communication protocol can be refined totally
independent of the modules.
- They can contain parameters, constants, variables, functions and
tasks, processes and continuous assignments, useful for both
system-level modelling and testbench applications.
- They can help build applications such as functional coverage recording and reporting, protocol checking and assertions.
- They can be used for port-less access: An interface can be instantiated directly as a static data object within a module. So, the methods used to access internal state information about the interface may be called from different points in the design to share information.
- Flexibility: An interface may be parameterised in the same way as a module. Also, a module header can be created with an unspecified interface instantiation, called a Generic Interface. This interface can be specified later on, when the module is instantiated.
At its simplest, an interface is a named bundle of wires, similar to
a struct, except that an interface is allowed as a module port, while a
struct is not.
The following example shows the definition and use of a very simple
interface:
// Interface definition
interface Bus;
logic [7:0] Addr, Data;
logic RWn;
endinterface
// Using the interface
module TestRAM;
Bus TheBus(); // Instance the interface
logic[7:0] mem[0:7];
RAM TheRAM (.MemBus(TheBus)); // Connect it
initial
begin
TheBus.RWn = 0; // Drive and monitor the bus
TheBus.Addr = 0;
for (int I=0; I<7; I++)
TheBus.Addr = TheBus.Addr + 1;
TheBus.RWn = 1;
TheBus.Data = mem[0];
end
endmodule
module RAM (Bus MemBus);
logic [7:0] mem[0:255];
always @*
if (MemBus.RWn)
MemBus.Data = mem[MemBus.Addr];
else
mem[MemBus.Addr] = MemBus.Data;
endmodule
Interface Ports
An interface can also have input, output or inout ports. Only the
variables or nets declared in the port list of an interface can be
connected externally by name or position when the interface is
instantiated, and therefore can be shared with other interfaces.
The ports are declared using the ANSI-style.
Here is an example showing an interface with a clock port:
interface ClockedBus (input Clk);
logic[7:0] Addr, Data;
logic RWn;
endinterface
module RAM (ClockedBus Bus);
always @(posedge Bus.Clk)
if (Bus.RWn)
Bus.Data = mem[Bus.Addr];
else
mem[Bus.Addr] = Bus.Data;
endmodule
// Using the interface
module Top;
reg Clock;
// Instance the interface with an input, using named connection
ClockedBus TheBus (.Clk(Clock));
RAM TheRAM (.Bus(TheBus));
...
endmodule
Parameterised Interface
This is a simple example showing a parameterised interface:
interface Channel #(parameter N = 0)
(input bit Clock, bit Ack, bit Sig);
bit Buff[N-1:0];
initial
for (int i = 0; i < N; i++)
Buff[i] = 0;
always @ (posedge Clock)
if(Ack = 1)
Sig = Buff[N-1];
else
Sig = 0;
endinterface
// Using the interface
module Top;
bit Clock, Ack, Sig;
// Instance the interface. The parameter N is set to 7using named
// connection while the ports are connected using implicit connection
Channel #(.N(7)) TheCh (.*);
TX TheTx (.Ch(TheCh));
...
endmodule
Modports in Interfaces
A new construct related to Interface is also added: Modport. This
provides direction information for module interface ports and controls
the use of tasks and functions within certain modules. The directions
of ports are those seen from the module.
This example includes modports, which are used to specify the
direction of the signals in the interface. The directions are the ones
seen from the module to which the modport is connected, in our case the
RAM:
interface MSBus (input Clk);
logic [7:0] Addr, Data;
logic RWn;
modport Slave (input Addr, inout Data);
endinterface
module TestRAM;
logic Clk;
MSBus TheBus(.Clk(Clk));
RAM TheRAM (.MemBus(TheBus.Slave));
...
endmodule
module RAM (MSBus.Slave MemBus);
// MemBus.Addr is an input of RAM
endmodule
Tasks in Interfaces
Tasks and functions can be defined in interfaces, to allow a more
abstract level of modelling.
The next example shows two tasks in an interface being used to model
bus functionality. The tasks are called inside the testRAM module:
interface MSBus (input Clk);
logic [7:0] Addr, Data;
logic RWn;
task MasterWrite (input logic [7:0] waddr,
input logic [7:0] wdata);
Addr = waddr;
Data = wdata;
RWn = 0;
#10ns RWn = 1;
Data = 'z;
endtask
task MasterRead (input logic [7:0] raddr,
output logic [7:0] rdata);
Addr = raddr;
RWn = 1;
#10ns rdata = Data;
endtask
endinterface
module TestRAM;
logic Clk;
logic [7:0] data;
MSBus TheBus(.Clk(Clk));
RAM TheRAM (.MemBus(TheBus));
initial
begin
// Write to the RAM
for (int i = 0; i<256; i++)
TheBus.MasterWrite(i[7:0],i[7:0]);
// Read from the RAM
for (int i = 0; i<256; i++)
begin
TheBus.MasterRead(i[7:0],data);
ReadCheck : assert (data === i[7:0])
else $error("memory read error");
end
end
endmodule


