A raw interface has no notion of direction: req is just a variable, and both the DUT and the testbench could legally drive it. Direction is the essence of a protocol, so each side needs its own view.
A modport (“module port”) declares a named directional view of the signals. The same physical net appears as input in one modport and output in another, because direction is always relative to the component looking at the bus.
Modports add no hardware and no new nets. They are compile-time lenses that let the compiler reject a module driving a signal its view declares as input.
The next section's listing starts with an interface that declares two opposite views of the same four signals: dut and tb.
A module selects its view by qualifying the port type: bus_if.dut. The port-declaration style is preferred, because the module then documents and enforces its own role.
Once a view is in force, a procedural drive of an input-view signal is a compile error. The wrong-direction bug fails before simulation starts instead of surfacing as a runtime fight.
The listing declares the interface with both views, a DUT and a driver that each choose their view in the port, and a top that checks one request. Its drives are still raw signals; the clocking lesson upgrades them.
interface bus_if (input logic clk);
logic req, gnt;
logic [31:0] addr;
logic [63:0] rdata;
// the SAME nets, two opposite views:
modport dut (
input req, addr, // DUT receives requests
output gnt, rdata, // DUT produces grant + read data
input clk
);
modport tb (
output req, addr, // testbench produces requests
input gnt, rdata, // testbench samples responses
input clk
);
endinterface
// view chosen in the port declaration (preferred)
module dut (bus_if.dut bus);
always_ff @(posedge bus.clk) begin
bus.gnt <= bus.req; // OK: gnt is output in the dut view
bus.rdata <= {32'h0, bus.addr}; // OK: rdata is output too
// bus.req <= 1'b0; // COMPILE ERROR: req is input here
end
endmodule
PASS: the dut view granted the tb view's request and returned rdata 000000a5The commented drive above is the whole check in one line. Uncommented, it is the next block: a deliberately broken illustration.
dut_bad below drives bus.req through the dut view, where req is an input. When is the mistake caught: at compile time, while simulating, or never?At compile time. The view says req is an input, so the compiler refuses the drive before any simulation starts.
// bad-example: req is an input in the dut view, so this drive is illegal
module dut_bad (bus_if.dut bus);
always_ff @(posedge bus.clk)
bus.req <= 1'b0; // illegal drive through an input view
endmoduleCompiled together with the interface above, the build stops here:
%Error: bad_drive.sv:21:9: Attempt to drive input-only modport: 'req'
: ... note: In instance 'dut_bad'
%Error: Exiting due to 1 error(s)Modports restrict access through the modport only. Code holding the whole interface, or a hierarchical reference such as tb_top.bus.req, bypasses the view entirely.
Enforcement strength varies across tools for continuous assignments and virtual-interface handles. Treat modports as documentation plus best-effort checking, never as a boundary.
Modports create no drivers and resolve no contention. Two output views of one logic variable remain a multiple-driver problem.
A modport can also export a clocking block (modport tb (clocking cb)) or a method (import task read). Both appear in the following lessons.
req disagree. Is that a bug?No. Direction is relative to the viewer: output in tb, input in dut, one net underneath.
tb_top.bus.req directly instead of through its modport. Does the modport stop it?No. The modport checks only access through itself. Hierarchical references bypass it.
Run with Verilator 5.048 (--binary --timing) on 2026-10-18; every bench printed the PASS line shown, with no warnings. Icarus 13 cannot run these benches: it rejects interface ports.
The error shown is Verilator 5.048's for the bad drive, recorded on 2026-10-18.