At chip level the CPU programs the DMA, the interconnect delivers its input stream, and every one of your agents is passive. A design reviewer asks two questions. Is your block scoreboard still checking anything? And: the subsystem lesson keeps block scoreboards on, but your chip plan turns some off. Which is it? Both are fair, and the answer is neither "always on" nor "always off". It is a rule you can state in one sentence and a record of where you applied it.
A passive agent builds only its monitor. It stops driving because something else, a DUT or a CPU, now owns the bus. Everything that depends only on observation keeps working: the monitor publishes transactions, protocol checks in the monitor or bound to the interface keep firing, and coverage collected from monitored traffic keeps counting. What a passive agent cannot do is create traffic, respond as a slave, or know anything that only a driver knew.
A football referee is a passive agent. They never touch the ball, but they see every play, apply every rule and keep the record. A referee who could only judge plays they had started themselves would be useless in a real match. A monitor that only works when its own driver is present is that referee.
Whether a scoreboard keeps running is a separate question with a simple rule: keep a block scoreboard wherever every one of its inputs is still observable by a monitor; turn it off only when some input is not. If the predictor's inputs are on buses you can still watch, the scoreboard is free localisation. If an input disappears (inside a hard macro, folded into another block, or not visible on an emulator), the scoreboard would only produce noise, and a higher-level check must cover that block instead.
The agent side is one if: the monitor is built unconditionally, the driver and sequencer only when active.
// snippet: from the course DMA bench
class axis_agent extends uvm_agent;
`uvm_component_utils(axis_agent)
axis_sequencer sqr;
axis_driver drv;
axis_monitor mon;
uvm_analysis_port #(axis_item) ap;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
mon = axis_monitor::type_id::create("mon", this);
if (get_is_active() == UVM_ACTIVE) begin
sqr = axis_sequencer::type_id::create("sqr", this);
drv = axis_driver::type_id::create("drv", this);
end
endfunction
function void connect_phase(uvm_phase phase);
ap = mon.ap;
if (get_is_active() == UVM_ACTIVE)
drv.seq_item_port.connect(sqr.seq_item_export);
endfunction
endclassThat is necessary, not sufficient. A monitor that will be passive somewhere must be written as if its driver never existed:
No driver state. It must not read the driver's variables, wait on events the driver triggers, or assume the sequence's timing. It reconstructs each transfer from the pins alone: for AXI-Stream, a beat is the clock edge where TVALID and TREADY are both high.
Its own reset handling. At chip level resets come from a reset controller, not the test. The monitor must drop a half-seen transfer when reset asserts, on its own.
No assumptions about traffic shape. The block driver never produced back-to-back beats (the coverage hole gap=0); a real upstream block will. Coverage and checks must handle traffic the block bench never generated.
A fresh object per transfer, because more subscribers attach at higher levels, and they store what they receive.
This is the table a reviewer should be able to read for any level. It applies the observability rule to each DMA component:
Component | Block | Subsystem | Chip | Reason |
|---|---|---|---|---|
APB agent | Active | Active | Passive | The CPU owns the register bus at chip level; the monitor still sees every KEY write |
| Active | Active | Passive | At chip level the interconnect feeds dma0 |
| Active | Passive | Passive | dut0 drives the middle bus |
| Passive | Passive | Passive | The DUT drives it at every level |
| On | On | On | KEY, input and output are all observable by monitors |
| None | On | On, or replaced by a memory-model check | Covers the chain as a whole |
Coverage | On | On | On, merged per instance | Monitored traffic is real traffic |
Block scoreboard on an emulator | n/a | n/a | Off | Input bus not captured in the emulator's transactor; chip-level check instead |
This also settles the reviewer's second question. The subsystem and chip lessons agree: block scoreboards stay on, as in vertical reuse, unless an input becomes unobservable, and the record says where that happens and what replaces the check.
+UVM_TESTNAME=dma_subsys_test (run.sh dma_subsys_tb.sv)
UVM_INFO dma_tb.sv(411) @ 875000: uvm_test_top.env.dma1.sb [SB] 10 words matched, 0 mismatched
UVM_INFO dma_tb.sv(1182) @ 875000: uvm_test_top.env.e2e [E2E] 10 words matched end to end, 0 mismatched`dma1.sb` matched 10 words with no driver anywhere on its input. Its predictions come from the passive dma1.in_agt watching the middle bus, and its KEY from dma1's APB monitor.
The same monitor feeds two checkers: dma0's output monitor and dma1's input monitor both watch mid. Two monitors on one bus cost nothing; two drivers would be a bug.
A team once moved a block bench to chip level and switched its stream agent to passive. Every chip test passed. Weeks later someone noticed the block scoreboard's line said zero items matched. The monitor had been written to sample on an event the driver triggered after each handshake, because that was easy and it worked in every block test. With the driver gone the event never fired, the monitor published nothing, and the scoreboard compared nothing, silently, because that scoreboard did not fail on an empty run.
Two habits catch this. First, the course bench's own guard: dma_scoreboard raises SB_EMPTY in check_phase when nothing was compared, and the end-to-end checker raises E2E_EMPTY, so a silent monitor turns into a red test the first night. Second, keep one block-level test that runs every agent passive against a replayed or DUT-driven stream; if the monitor depends on its driver, that test shows it at block level, where it is cheap to fix.
dma0.sb, and what replaces it?Its input monitor sees nothing, so its predictions are missing and it would report leftovers or nothing at all. Turn it off with has_scoreboard = 0 and record why. Replace it with a check at the macro boundary: a memory-model scoreboard that predicts the DMA's output from the memory contents and the programmed KEY, or end-to-end checking from the observable source to the sink.
gap=0 hit thousands of times, though the block regression never hit it. Is that good news?It is good news about coverage and a warning about checking. The DMA has now seen back-to-back input that its block regression never generated. Confirm the block scoreboard was on and matched those words, and consider adding a block-level test that reproduces the traffic, because a chip-level failure there would be slower to debug.
Next: much of what goes passive at chip level is protocol VIP. Buy vs Build VIP is the decision behind it.