Interfaces and modports
Bundle the read channel into an interface, give each connected block a modport that says which signals it may drive, and count only real transfers.
- Lesson
- 6.1
- Time
- 20 min
- Runs on
- Verilator
- Check
- 5 questions
A monitor module was copied from a driver and still contained assign rready = 1;. The monitor and the DUT were wired to the same rready net, so the monitor quietly held the channel ready even when the DUT wanted to stall. The backpressure tests passed for a month without a single stall reaching the source. With a modport that lists rready as an input for monitors, that line would not have compiled.
The DUT asked for stalls in cycles 1, 2 and 4, but the extra driver kept the channel ready.
An interface bundles a protocol
An interface is a named bundle of the signals that make up one protocol. The read data channel has rvalid, rready and rdata, and an interface called rd_if holds all three plus the clock.
You instantiate an interface once, as in rd_if bus (clk);, and connect it to each block through a single port. Adding a signal later, such as rlast, then means one edit in the interface instead of an edit in every port list.
An interface can take parameters like a module, so rd_if #(.W(32)) gives a 32-bit data bus from the same source. It can also hold clocking blocks, tasks and assertions, which the later lessons of this module add.
Modports state who drives what
A modport is a named view of the interface for one role. It lists each signal with a direction as seen from that role, so the same wire is an output for one block and an input for another.
| Modport | rvalid | rdata | rready | Used by |
|---|---|---|---|---|
source | output | output | input | the block that offers data |
sink | input | input | output | the block that accepts data, here the DUT |
monitor | input | input | input | monitors, scoreboards and checkers |
Read-channel modports. The monitor drives nothing.
A module port declared as rd_if.monitor bus gets the monitor view. If code in that module assigns bus.rready, compilation fails, because the monitor view lists rready as an input. Verilator 5.052, for example, reports Attempt to drive input-only modport.
What counts as a transfer
On a ready/valid channel, the source raises rvalid to offer data, and the sink raises rready to say it can accept. A transfer happens at a rising clock edge where both are 1.
A cycle with rvalid high and rready low is a stall: the offer stands, but nothing moves. Counting every cycle with rvalid high therefore counts stalls as data, and the scoreboard receives the same beat several times.
Count every offer
always @(posedge bus.clk)
if (bus.rvalid)
n_beats++;
A beat stalled for three cycles is counted three times.
Count transfers
always @(posedge bus.clk)
if (bus.rvalid && bus.rready)
n_beats++;
Each beat is counted once, at the edge where it moves.
What modports leave open
A modport controls who may drive a signal. It says nothing about when a signal is driven or sampled, so two blocks can still write and read the same signal at the same clock edge.
The example below follows one simple rule to stay safe. The testbench changes its signals only at the falling edge, half a cycle away from the rising edge where the DUT and the monitor look. The next two lessons explain why that rule works and replace it with clocking blocks.
Connecting a DUT with ordinary ports
Many DUTs have plain ports rather than an interface port. The testbench still owns the interface and connects each DUT port to the matching interface signal, as in .rvalid(bus.rvalid).
Nothing is lost by doing that. The DUT keeps its own port list, and every testbench block still reaches the channel through the one interface instance.
One interface per channel
An AXI-style bus has five channels, each with its own handshake. Giving each channel its own interface keeps the modports small and lets the same monitor class serve several channels.
The alternative, one large interface for the whole bus, is also common. In that case, define modports per role and keep the clocking blocks per channel, so that a read monitor never depends on write-channel signals.
Where the testbench connects
The DUT connects through the sink modport, or through plain ports wired to the interface signals. The monitor connects through the monitor modport, and a class-based driver reaches the source side through a virtual interface, which lesson 6.4 introduces.
One interface instance then carries the whole conversation. Every block sees the same three wires, and the modports record which block is responsible for each of them.
Keep the interface free of checking logic at first. It describes the pins; monitors, scoreboards and assertions decide what is correct, and they stay easy to change because they only read the bundle.
Try it
The DUT stalled on every other edge, so each beat was offered at two edges but moved at one. The monitor counted 6 offers and 3 transfers, and printed each transfer once.
In rd_monitor, add the line assign bus.rready = 1; and compile again. Read the error, then remove the line.
t=25 transfer rdata=a1 t=45 transfer rdata=b2 t=65 transfer rdata=c3 cycles with rvalid high: 6, transfers: 3Check yourself
Remember
- An interface bundles a protocol's signals so each block connects through one port.
- A modport gives one role its view, with a direction for each signal; driving an input is a compile error.
- A transfer happens at an edge where both
rvalidandrreadyare 1; otherrvalidcycles are stalls. - Modports control access, not timing: they do not stop two blocks touching a signal at the same edge.
- Until clocking blocks arrive, change testbench signals away from the edge where the DUT samples them.
Practice
- Count the real transfers Implement · Easy