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.

diagram
[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
diagram
[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 publication
  • Driver 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.

systemverilog
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
endclass
systemverilog
class 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
diagram
[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-owned
  • Keep 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.

systemverilog
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;
endtask
systemverilog
task 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
diagram
[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 requires
  • Pairing 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.

systemverilog
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
diagram
[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
diagram
[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 mismatch

Key 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.