The chip team pulls your stream VIP into the SoC bench and sets every instance passive, because at chip level the real RTL drives every stream. The build is clean. Then the coverage report arrives: the stream agents report zero coverage of idle gaps between beats on every port, a hole the chip verification lead now wants closed. Nothing is wrong with the chip's traffic. Something about your agent only worked because it was the one driving. What exactly does a VIP have to guarantee before "passive" is safe in someone else's bench?
Setting is_active to UVM_PASSIVE is the integrator's decision, and the mechanics are small: the agent skips its sequencer and driver (Mode-gated build shows them). But the flag only removes components. It cannot remove assumptions. If the interface drives a default, if the monitor relies on something only the driver knows, if coverage samples a field the driver fills in, passive mode builds fine and then quietly misbehaves.
Think of a house built to be rented out. The owner who lives there can leave notes on the fridge and keys under the mat. A rental must work for a stranger who has none of that: everything it needs is in the house, and nothing the owner does is required for it to function. A VIP's passive mode is the rental. It must work for an integrator who never runs your driver.
Bus | Block bench | Two-DMA subsystem | Chip bench |
|---|---|---|---|
Stream into the first DMA | Agent drives (active) | Agent drives (dma0.in_agt) | RTL drives; agent watches |
Stream between the DMAs | (does not exist) | dut0 drives, dut1 answers; two agents watch | RTL; agent watches |
Stream out of the last DMA | DUT drives; TREADY default from the top | dut1 drives; TREADY default from the top | RTL on both sides; agent watches |
APB | Agent drives | Agents drive both DMAs | The CPU model or interconnect drives; agent watches |
Read down the last column. At chip level the testbench owns no wire at all. Every guarantee below follows from that.
An interface is shared by the driver and the monitor, so it is tempting to put conveniences in it: a default TREADY = 1, a pull-down on TVALID. In passive mode there is no driver to override them, and at chip level they become a second driver on a wire the RTL owns. The bench keeps every default in the top module, outside the agent. The block top sets initial m_ax.tready = 1'b1; for the DMA's output; the subsystem top sets it only for the final output, because the middle bus's TREADY is driven by dut1:
// snippet: from dma_subsys_tb.sv
axis_if s_ax (clk, rst_n); // into dma0
axis_if mid (clk, rst_n); // dma0 -> dma1, driven only by the DUTs
axis_if m_ax (clk, rst_n); // out of dma1
initial m_ax.tready = 1'b1; // a bench default for the last output onlyLook at axis_item: besides data and last it has idle, the number of clocks the driver waits before the beat. Sequences randomize it and the driver obeys it. The monitor never sets it, because it cannot know whether a gap was the driver's choice or the DUT's backpressure. Every item a monitor publishes has idle == 0. That is the zero coverage from the opening: the coverage collector sampled idle from monitor items. Coverage of stimulus intent belongs on the driver side and exists only in active mode; coverage of what the bus did (gaps measured in clocks between accepted beats) must be computed by the monitor from the pins.
// snippet: from the course DMA bench
class axis_item extends uvm_sequence_item;
rand bit [31:0] data;
rand bit last;
rand int unsigned idle; // clocks of TVALID=0 before this beat (driver only)
constraint idle_c { idle dist { 0 := 6, [1:3] := 3, [4:8] := 1 }; }
`uvm_object_utils(axis_item)
function new(string name = "axis_item");
super.new(name);
endfunction
function string convert2string();
return $sformatf("data=%08h%s", data, last ? " LAST" : "");
endfunction
function void do_copy(uvm_object rhs);
axis_item r;
super.do_copy(rhs);
$cast(r, rhs);
data = r.data; last = r.last; idle = r.idle;
endfunction
endclassAnything that must work at chip level lives in the monitor, the interface or subscribers to the agent's analysis port, never in the driver. Protocol assertions in the interface (TDATA stable while TVALID is high and TREADY low) fire in every mode. A driver-side check, such as "the slave answered within N clocks", disappears in passive mode, and the VIP's documentation must say so.
At chip level there is no top-level axis_if to hand out: the stream is a set of internal wires deep in the RTL. The usual way in is bind, which instantiates an interface inside an RTL module with its ports connected to that module's signals. A bound interface must be input-only, since it may not drive anything, so a VIP meant for chip reuse provides a monitor that can read through one:
// snippet: an input-only interface bound into each DMA instance
interface axis_mon_if (input logic clk, rst_n,
input logic [31:0] tdata, input logic tvalid, tlast, tready);
clocking mon_cb @(posedge clk);
default input #1step;
input tdata, tvalid, tlast, tready;
endclocking
endinterface
bind dma_dut axis_mon_if u_out_mon (.clk(clk), .rst_n(rst_n),
.tdata(m_tdata), .tvalid(m_tvalid), .tlast(m_tlast), .tready(m_tready));
// The chip top then sets each instance for its agent, for example
// uvm_config_db#(virtual axis_mon_if)::set(null, "uvm_test_top.env.dma0_out*", "vif", chip.u_dma0.u_out_mon);The catch is the type. The bench's axis_monitor holds a virtual axis_if, so it cannot read an axis_mon_if. A VIP that plans for chip reuse either gives its monitor an input-only interface from the start, or reaches the pins through an abstract BFM class so the interface type never appears in class code. The trade-offs are in Interface abstraction.
In the subsystem test, dma1.in_agt is passive: dma_subsys_env sets it so because its config says dma1's input is driven by dma0. Yet dma1's own block scoreboard still checks every word, fed by that passive agent's monitor:
UVM_INFO dma_tb.sv(411) @ 875000: uvm_test_top.env.dma0.sb [SB] 10 words matched, 0 mismatched
UVM_INFO dma_tb.sv(411) @ 875000: uvm_test_top.env.dma1.sb [SB] 10 words matched, 0 mismatched
UVM_INFO dma_tb.sv(1178) @ 875000: uvm_test_top.env.e2e [E2E] 10 words matched end to end, 0 mismatched
UVM_ERROR : 0
UVM_FATAL : 0dma1.sb's predictions come from words its input agent observed on the middle bus, not words any sequence sent. That is the promise kept: the block env was written for an active input, and it works unchanged with a passive one.
A chip team spent two weeks writing directed tests to close a stream coverage hole, "no beats with idle gaps of 4 to 8 clocks", on ports where the RTL produced exactly such gaps all the time. The tests made no difference. The covergroup sampled item.idle, a field only the driver ever sets; at chip level every agent was passive, so every sampled item said idle == 0.
The fix was in the VIP, not the tests: the monitor started measuring the gap between accepted beats from the pins and published it in a separate field, gap_clocks, and the covergroup sampled that. The driver-side idle stayed, documented as stimulus intent with active-mode-only coverage. The general rule is worth writing into every VIP's user guide: list which fields the monitor fills in, and never cover or check a field it does not.
initial tready = 1'b1; so that block tests without a slave work out of the box. What happens when an integrator uses the agent passively on a bus whose TREADY comes from RTL?Two things write the same signal: the interface's default and the RTL. Depending on how the signal is declared and connected that is a compile or elaboration error, a contention X, or a silent fight where whichever writes last wins. None of them is acceptable for a passive agent. Defaults belong in the block top, which the integrator does not reuse.
latency field that the driver fills in when it sees the response. Which deployments get a correct value?Only active ones, where the driver exists and fills it in. In passive mode the field is always its default. Either the monitor measures latency from the pins itself, or the documentation marks the field as active-only and coverage must not sample it.
Next: the subsystem bench puts two passive monitors on the very same wires. Two passive monitors, one bus explains why that is right.