SystemVerilog introduces interface as a first-class abstraction for modeling communication channels between design blocks. Unlike Verilog’s flat port-list approach, interfaces unify signal declarations, directionality, protocol logic, and verification constructs into a single, reusable unit—enabling cleaner hierarchy, safer connectivity, and scalable verification.
Why Interfaces Replace Redundant Port Lists
In large designs, replicating bus signals across dozens of modules leads to brittle code. Consider a 16-bit data/address bus shared by five components: each module redeclares data, address, mem_read, mem_write, and handshaking signals. A change—e.g., widening address from 16 to 32 bits—requires edits in every module and top-level instantiation. Worse, mismatched declarations (e.g., inout vs. wire) cause subtle simulation mismatches or synthesis failures.
Interfaces eliminate this redundancy. All shared signals are declared once:
interface axi_lite_bus (
input logic clk,
input logic rst_n
);
logic [31:0] awaddr, wdata, rdata;
logic [ 2:0] awprot, wstrb;
logic awvalid, wvalid, bready, rready;
logic awready, wready, bvalid, rvalid;
// Modports define per-component views
modport master (
output awaddr, awprot, awvalid, wdata, wstrb, wvalid, bready, rready,
input awready, wready, bvalid, rdata, rvalid
);
modport slave (
input awaddr, awprot, awvalid, wdata, wstrb, wvalid, bready, rready,
output awready, wready, bvalid, rdata, rvalid
);
endinterface
This single definition replaces 40+ lines of duplicated port declarations—and enforces consistency across the entire design.
Modports: Context-Aware Signal Visibility
A single interface can present different signal views to different components. In a AXI-Lite bus:
- A
masterseesawaddrasoutputandawreadyasinput. - A
slaveseesawaddrasinputandawreadyasoutput.
Modports enforce this at compile time. Attempting to assign to awaddr inside a slave using the master modport triggers an error.
Connection syntax is explicit and hierarchical:
module top;
axi_lite_bus bus (.clk(clk), .rst_n(rst_n));
axi_master m0 (.bus(bus.master)); // Uses master view
axi_slave s0 (.bus(bus.slave)); // Uses slave view
endmodule
Methods: Protocol Logic Inside the Interface
Interfaces host tasks and functions that encapsulate communication behavior—eliminating copy-paste protocol code across modules. For example, a burst-read task:
interface memory_bus;
logic [31:0] addr, data;
logic rd_en, rd_valid;
task automatic read_burst(
input logic [31:0] start_addr,
input int unsigned num_words,
output logic [31:0] words[$]
);
addr = start_addr;
rd_en = 1;
repeat (num_words) begin
@(posedge rd_valid); // Wait for valid response
words.push_back(data);
addr += 4;
end
rd_en = 0;
endtask
endinterface
Modules call it directly via their interface port:
module processor (memory_bus.bus);
logic [31:0] insts[$];
initial begin
bus.read_burst(32'h1000, 8, insts); // Clean, reusable call
end
endmodule
Parameterized and Hierarchical Interfaces
Interfaces support parameters for reusability across configurations:
interface generic_bus #(
parameter int ADDR_WIDTH = 16,
parameter int DATA_WIDTH = 32
)(
input logic clk
);
logic [ADDR_WIDTH-1:0] address;
logic [DATA_WIDTH-1:0] data;
logic valid, ready;
modport device (input address, data, valid, output ready);
modport host (output address, data, valid, input ready);
endinterface
// Instantiate with custom widths
generic_bus #(24, 64) bus24_64 (.clk(clk));
They also support nesting: a system_bus interface may contain sub-interfaces like periph_bus and dma_bus as ports—modeling hierarchical interconnects without flattening signal names.
Verification Integration
Procedurla blocks inside interfaces enable built-in protocol checking. An assertion block can moniter handshake timing:
interface apb_bus;
logic paddr, psel, penable, pwrite, prdata, pwdata;
// Protocol checker: PSEL must stay high during PENABLE cycle
assert property (@(posedge clk) psel |-> $stable(psel))
else $error("APB: PSEL deasserted during transfer");
endinterface
Such checks activate automatically whenever the interface is used—no manual insertion into each component.
Synthesis Compatibility
Modern synthesis tools fully support interfaces when used with modports. During synthesis, an interface port expands into discrete ports matching the modport’s directional declarations. Tasks/functions imported via modports are inlined into the module’s netlist. However, exported tasks (from modules into interfaces) and extern forkjoin constructs remain simulation-only features.
Best Practices
- Name interface ports concisely: Use
bus,ctrl, orcfginstead ofmain_bus_interface_instance—since access is alwaysbus.data, brevity improves readability. - Prefer explicit modports over generic
interfaceports: They prevent accidental misconnections and self-document intent. - Declare clocks/resets in the interface: When shared globally, they reduce top-level wiring clutter and ensure consistent timing domains.
- Use
.*sparingly: While convenient for rapid prototyping, it obscures signal flow and hinders debugging. Prefer named connections in production code.