Part 5 · Sequences · Intermediate

Virtual Sequencer Wiring: soc_virtual_sequencer & connect_phase

Virtual sequencer class structure, env integration, connect_phase handle assignment, and null-handle prevention.

Virtual sequencer — handle bundle, no driver

A virtual sequencer extends uvm_sequencer but is never connected to a driver. It exists solely to hold typed handles to real sequencers built inside agents. Virtual sequences run on this component and access sub-sequencers via p_sequencer.

systemverilog
class soc_virtual_sequencer extends uvm_sequencer;
  `uvm_component_utils(soc_virtual_sequencer)

  // [UVM] Handles to REAL sequencers — populated in connect_phase
  apb_sequencer  apb_sqr;
  axi_sequencer  axi_sqr;
  dma_sequencer  dma_sqr;

  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction
endclass
diagram
[UVM] virtual sequencer vs agent sequencer

  apb_agent.sequencer:
    seq_item_port connected to apb_driver.seq_item_export  [STIM]
    Produces items  drives DUT pins

  soc_virtual_sequencer (env.v_sqr):
    NO driver connection
    apb_sqr / axi_sqr / dma_sqr = pointers to agent sequencers
    Virtual sequences start HERE; delegate via handles

Env structure — v_sqr as sibling to agents

The virtual sequencer lives in the env, sibling to agents — not inside any agent. The env builds agents first, then the virtual sequencer, then assigns handles once agents exist.

systemverilog
class soc_env extends uvm_env;
  `uvm_component_utils(soc_env)

  apb_agent            apb_agent;
  axi_agent            axi_agent;
  dma_agent            dma_agent;
  soc_virtual_sequencer v_sqr;

  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    apb_agent = apb_agent::type_id::create("apb_agent", this);
    axi_agent = axi_agent::type_id::create("axi_agent", this);
    dma_agent = dma_agent::type_id::create("dma_agent", this);
    v_sqr     = soc_virtual_sequencer::type_id::create("v_sqr", this);
  endfunction

  function void connect_phase(uvm_phase phase);
    super.connect_phase(phase);
    // [UVM] Wire handles AFTER agents built — sequencers exist now
    v_sqr.apb_sqr = apb_agent.sqr;
    v_sqr.axi_sqr = axi_agent.sqr;
    v_sqr.dma_sqr = dma_agent.sqr;
  endfunction
endclass

connect_phase wiring diagram

diagram
[UVM] connect_phase — handle assignment

  soc_env
  ├── apb_agent
  │     └── sqr ◄──────────────────── v_sqr.apb_sqr
  ├── axi_agent
  │     └── sqr ◄──────────────────── v_sqr.axi_sqr
  ├── dma_agent
  │     └── sqr ◄──────────────────── v_sqr.dma_sqr
  └── v_sqr (soc_virtual_sequencer)
        apb_sqr  ──pointer──► apb_agent.sqr
        axi_sqr  ──pointer──► axi_agent.sqr
        dma_sqr  ──pointer──► dma_agent.sqr

  Assignment in connect_phase — NOT build_phase
  (agent.sqr must exist; build order within env is guaranteed)

Use connect_phase, not build_phase, for handle assignment. While build_phase creates children in order, connect_phase is the conventional place for TLM connections and cross-component references — consistent with monitor-to-scoreboard wiring.


Null handle prevention

The most common virtual sequence failure is null object access on p_sequencer.apb_sqr — connect_phase never assigned the handle, or the vseq was started on the wrong sequencer (agent.sqr instead of env.v_sqr).

systemverilog
function void connect_phase(uvm_phase phase);
  super.connect_phase(phase);
  v_sqr.apb_sqr = apb_agent.sqr;
  v_sqr.axi_sqr = axi_agent.sqr;
  v_sqr.dma_sqr = dma_agent.sqr;

  // Defensive check — catch wiring bugs early
  if (v_sqr.apb_sqr == null)
    `uvm_fatal("CONNECT", "v_sqr.apb_sqr not assigned")
  if (v_sqr.axi_sqr == null)
    `uvm_fatal("CONNECT", "v_sqr.axi_sqr not assigned")
endfunction
diagram
[VSEQ] null p_sequencer debug

  Error: NULL pointer dereference at prog.start(p_sequencer.apb_sqr)

  Checklist:
    1. vseq.start(env.v_sqr) — NOT env.apb_agent.sqr
    2. uvm_declare_p_sequencer(soc_virtual_sequencer) in vseq class
    3. connect_phase assigns v_sqr.apb_sqr = apb_agent.sqr
    4. apb_agent builds sqr in its own build_phase

Key takeaways

  • Virtual sequencer holds handles to real sequencers — no driver.

  • Build v_sqr in env build_phase; assign handles in connect_phase.

  • v_sqr is env sibling to agents — never inside an agent.

  • Add null checks in connect_phase to catch wiring bugs early.

Common pitfalls

  • Assigning handles in build_phase before agent sub-components exist.

  • Starting vseq on agent.sqr — p_sequencer type mismatch or null handles.

  • Typo: v_sqr.apb_sqr = apb_agent.sequencer (wrong member name).