Module 9: SystemVerilog for Design & Verification ยท 9.1 SystemVerilog Design Enhancements

Interfaces and Modports

The previous article bundled data into structs. This one bundles a whole protocol.

Look back at the ready/valid signals in Section 7.3, or the FIFO ports in Section 7.4: wdata, wr_en, full, rdata, rd_en, empty. Every module that talks to a FIFO needs all of them, always together, always in the same relationship. Classic Verilog has no way to say these signals are a package deal. You list them, separately, in every port list and every instantiation, and hope nobody miscounts or reorders one. A SystemVerilog interface makes the grouping a declared, checkable thing.

The problem: repetitive, error-prone port lists

// Classic Verilog: every module using this FIFO protocol re-declares
// all the signals, separately, every time
module producer (
    output wire [7:0] wdata,
    output wire        wr_en,
    input  wire        full,
    ...
);

module consumer (
    input  wire [7:0] rdata,
    output wire        rd_en,
    input  wire        empty,
    ...
);

Every declaration is independent. Nothing enforces that producer’s wdata is the same width as the FIFO it eventually connects to, or that a hurried top-level instantiation did not swap wr_en and full.

The fix: an interface

interface fifo_if #(parameter DATA_WIDTH = 8);
    logic [DATA_WIDTH-1:0] wdata, rdata;
    logic                  wr_en, rd_en;
    logic                  full, empty;
endinterface

This single declaration is a reusable, named bundle that every module needing FIFO signals can reference:

module producer (fifo_if.producer_port fifo);
    // fifo.wdata, fifo.wr_en, fifo.full, accessed through the interface instance
endmodule

module consumer (fifo_if.consumer_port fifo);
    // fifo.rdata, fifo.rd_en, fifo.empty
endmodule

The port lists got much shorter, one interface reference instead of five signal declarations, and every module referencing fifo_if is guaranteed by the type system to agree on which signals exist and how wide they are, since they all read one declaration rather than five hand-typed copies.

modport: direction per side

This is the part that makes interfaces safer, not only shorter. The producer side and the consumer side need opposite directions: the producer drives wdata and wr_en and reads full; the consumer drives rd_en and reads rdata and empty. A modport declares this per role, inside the interface:

interface fifo_if #(parameter DATA_WIDTH = 8);
    logic [DATA_WIDTH-1:0] wdata, rdata;
    logic                  wr_en, rd_en;
    logic                  full, empty;

    modport producer_port (
        output wdata, wr_en,
        input  full
    );

    modport consumer_port (
        input  rdata,
        output rd_en,
        input  empty
    );
endinterface

A module declared with fifo_if.producer_port can only drive wdata and wr_en and only read full. Trying to drive full from inside it is a compile-time violation rather than a silent multi-driver risk (the Section 8.4 bug class) that would otherwise surface only when two modules’ hand-written port directions disagreed at the top level.

One interface instance, two modports with opposite directions
Figure 1: Modports give each side its own view of signal direction

Instantiating and connecting

At the top level, connecting two modules through an interface is a single instantiation rather than a long list of individual connections:

module top;
    fifo_if #(.DATA_WIDTH(16)) my_fifo_bus();

    producer u_producer (.fifo(my_fifo_bus.producer_port));
    consumer u_consumer (.fifo(my_fifo_bus.consumer_port));
endmodule

The classic equivalent needs five wire declarations at the top and five named connections per instance, ten connection points for what the interface reduces to two, each of the ten being an opportunity for a swapped name or a width mismatch. Standard buses (AXI, AXI-Lite, which you will meet as a project in Module 10, Avalon, Wishbone) are almost universally modelled as interfaces in modern RTL, because they involve a dozen or more signals that always travel together with strict directions between the manager and subordinate roles.

Practice Exercise

Extend fifo_if with a third modport, monitor_port, for a verification component that needs to observe every signal but must never drive any. Give its direction list.

Solution
modport monitor_port (
    input wdata, rdata, wr_en, rd_en, full, empty
);

Every signal is input from the monitor’s point of view. It can read all six for observation, and driving any of them from a module connected through monitor_port is a compile-time violation. A read-only observer modport alongside the driving modports is very common in verification environments, where a monitor’s whole job is to watch a bus without influencing it.

An interface bundles a multi-signal protocol into one reusable declaration, and modport declares what each connected role may drive versus only observe, turning a silent directional mismatch into a compile error. That closes Section 9.1; every construct in it has been a cleaner, more checkable way to express design ideas from earlier modules. In the next article, Section 9.2 moves into new territory: verification built from SystemVerilog classes.

โ† typedef, struct, union and enum Classes and Objects for Verification โ†’