Part 6 · Agents & Protocol IP · Intermediate
Active Build Internals: Driver, Sequencer, and Handshake Wiring
Detailed active-agent construction: component graph, connect_phase handshake, and disciplined ownership of virtual interfaces and sequence traffic.
Active-mode architecture
In active mode, an agent becomes a complete protocol endpoint: sequences produce transactions, sequencer arbitrates, driver converts transactions into pin activity, and monitor observes for checking.
The implementation goal is symmetry: active mode adds components and links but should not alter monitor publication behavior compared with passive mode.
[VIP][AGT] active-mode internals
sequence(s)
|
v
sequencer [UVM] --seq_item--> driver [DRV] --vif--> DUT
|
+--> response path (optional)
monitor [MON] --analysis_port--> scoreboard/coverage/predictor[UVM] component responsibilities
sequencer:
- arbitration across running sequences
- item grant ordering
driver:
- bus timing protocol implementation
- reset-awareness and handshake
monitor:
- passive sampling and reconstruction
- analysis publicationDriver and sequencer exist only in active mode.
Monitor and analysis publication remain mode-independent.
Driver owns the active signal-driving contract to DUT.
Build/connect implementation
Use strict phase placement: create components in build_phase and wire TLM in connect_phase. This avoids accidental null links and phase-order races.
class protocol_agent extends uvm_agent;
`uvm_component_utils(protocol_agent)
protocol_agent_cfg cfg;
protocol_driver drv;
protocol_sequencer sqr;
protocol_monitor mon;
function void build_phase(uvm_phase phase);
super.build_phase(phase);
if (!uvm_config_db#(protocol_agent_cfg)::get(this, "", "cfg", cfg))
`uvm_fatal("CFG", "cfg missing")
mon = protocol_monitor::type_id::create("mon", this);
mon.cfg = cfg;
if (cfg.is_active == UVM_ACTIVE) begin
sqr = protocol_sequencer::type_id::create("sqr", this);
drv = protocol_driver::type_id::create("drv", this);
drv.cfg = cfg;
end
endfunction
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
if (cfg.is_active == UVM_ACTIVE) begin
drv.seq_item_port.connect(sqr.seq_item_export);
end
endfunction
endclassclass protocol_driver extends uvm_driver #(protocol_item);
`uvm_component_utils(protocol_driver)
protocol_agent_cfg cfg;
virtual protocol_if vif;
function void build_phase(uvm_phase phase);
super.build_phase(phase);
vif = cfg.vif;
if (vif == null)
`uvm_fatal("VIF", "driver vif is null")
endfunction
endclass[AGT] active connect checklist
build:
create mon always
create drv/sqr only if active
pass cfg handles to children
connect:
driver.seq_item_port -> sequencer.seq_item_export
monitor.ap external connects are env-ownedKeep mode checks in both build and connect for null safety.
Fail fast on missing vif in driver build.
Do not connect seq_item ports in constructors or run_phase.
Run-phase handshake patterns
Active driver code should be protocol-accurate and robust to reset. Keep handshake loops explicit and always pair get_next_item with item_done exactly once.
task run_phase(uvm_phase phase);
protocol_item req;
forever begin
seq_item_port.get_next_item(req);
drive_one(req);
seq_item_port.item_done();
end
endtask
task drive_one(protocol_item req);
@(posedge vif.clk);
wait (vif.rst_n === 1'b1);
vif.valid <= 1'b1;
vif.addr <= req.addr;
vif.data <= req.data;
do @(posedge vif.clk); while (!vif.ready);
vif.valid <= 1'b0;
endtasktask reset_aware_idle();
vif.valid <= 1'b0;
vif.addr <= '0;
vif.data <= '0;
forever begin
@(posedge vif.clk);
if (!vif.rst_n) begin
vif.valid <= 1'b0;
end
end
endtask[DRV] handshake anti-bug notes
always:
get_next_item -> drive -> item_done
never:
call item_done without successful get_next_item
block forever waiting ready without timeout policy
drive during reset unless protocol explicitly requiresPairing handshake methods correctly prevents sequence deadlocks.
Reset-aware driving avoids random X-propagation and startup flakiness.
Protocol timing belongs in driver, not in sequence classes.
Active-mode assertions and diagnostics
Because active agents own pins, add strict assertions and diagnostics around ownership boundaries. This catches accidental multi-driver scenarios early.
function void check_active_contract();
if (cfg.is_active == UVM_ACTIVE) begin
if (drv == null || sqr == null)
`uvm_fatal("AGT_ACTIVE", "active mode missing drv/sqr")
end
else begin
if (drv != null || sqr != null)
`uvm_fatal("AGT_PASSIVE", "passive mode should not create drv/sqr")
end
endfunction[VIP][AGT] active debug trace points
1) mode resolution log
2) topology print (drv/sqr present)
3) first sequence start
4) first get_next_item grant
5) first driven transfer on vif
6) monitor observes same transfer[SOC] integration symptom map
symptom root cause zone
---------------------------------------------------------
sequence starts, no bus toggles driver vif not wired
bus toggles, scoreboard silent monitor/ap wiring missing
driver drives garbage after reset reset handling bug
hang at seq start get_next_item/item_done mismatchKey takeaways
Active mode adds a deterministic driver+sequencer pipeline on top of shared monitor logic.
Strict build/connect phase separation keeps active internals reliable.
Correct req handshake semantics are essential for stable sequence execution.
Ownership assertions prevent subtle multi-driver integration bugs.
Common pitfalls
Assigning vif independently in driver and monitor from different sources.
Conditional monitor creation by mode, which breaks observability symmetry.
Hiding mode mismatch behind UVM_WARNING instead of failing fast.
Putting protocol timing randomization in sequence rather than driver.