HomeProblemsTheoryUVM
Advanced2 min readChapter 12

Virtual Sequences

Coordinate stimulus across multiple agents using virtual sequences and virtual sequencers.

Virtual Sequences

A virtual sequence orchestrates multiple sequences across different agents simultaneously. It doesn't generate items itself — it starts child sequences on specific sequencers.


The Problem

In a multi-agent environment, you need coordinated stimulus:

  • Configure registers via APB before sending data via AXI
  • Generate interrupts while data transfer is ongoing
  • Synchronize reset across all interfaces

A regular sequence only talks to one sequencer. Virtual sequences solve this.


Step 1: Virtual Sequencer

The virtual sequencer holds references to the real sequencers:

systemverilog
class virtual_sequencer extends uvm_sequencer; `uvm_component_utils(virtual_sequencer) apb_sequencer apb_sqr; // Handle to APB sequencer axi_sequencer axi_sqr; // Handle to AXI sequencer function new(string name, uvm_component parent); super.new(name, parent); endfunction endclass

Wire it up in the environment:

systemverilog
class soc_env extends uvm_env; virtual_sequencer v_sqr; apb_agent apb_agt; axi_agent axi_agt; function void build_phase(uvm_phase phase); super.build_phase(phase); v_sqr = virtual_sequencer::type_id::create("v_sqr", this); apb_agt = apb_agent::type_id::create("apb_agt", this); axi_agt = axi_agent::type_id::create("axi_agt", this); endfunction function void connect_phase(uvm_phase phase); // Point virtual sequencer to real sequencers v_sqr.apb_sqr = apb_agt.sequencer; v_sqr.axi_sqr = axi_agt.sequencer; endfunction endclass

Step 2: Virtual Sequence

systemverilog
class config_then_transfer extends uvm_sequence; `uvm_object_utils(config_then_transfer) `uvm_declare_p_sequencer(virtual_sequencer) function new(string name = "config_then_transfer"); super.new(name); endfunction task body(); apb_config_seq cfg_seq; axi_burst_seq burst_seq; // Step 1: Configure DUT via APB cfg_seq = apb_config_seq::type_id::create("cfg_seq"); cfg_seq.start(p_sequencer.apb_sqr); // Step 2: Transfer data via AXI burst_seq = axi_burst_seq::type_id::create("burst_seq"); burst_seq.start(p_sequencer.axi_sqr); endtask endclass

Parallel Coordination

Run traffic on both buses simultaneously:

systemverilog
task body(); fork begin // APB traffic apb_traffic_seq apb_seq; apb_seq = apb_traffic_seq::type_id::create("apb_seq"); apb_seq.start(p_sequencer.apb_sqr); end begin // AXI traffic (concurrent!) axi_traffic_seq axi_seq; axi_seq = axi_traffic_seq::type_id::create("axi_seq"); axi_seq.start(p_sequencer.axi_sqr); end join endtask

Running from the Test

systemverilog
class my_test extends uvm_test; `uvm_component_utils(my_test) soc_env env; function new(string name, uvm_component parent); super.new(name, parent); endfunction function void build_phase(uvm_phase phase); super.build_phase(phase); env = soc_env::type_id::create("env", this); endfunction task run_phase(uvm_phase phase); config_then_transfer vseq; phase.raise_objection(this); vseq = config_then_transfer::type_id::create("vseq"); vseq.start(env.v_sqr); phase.drop_objection(this); endtask endclass

When to Use What

ScenarioRegular SequenceVirtual Sequence
Single-interface stimulusOverkill
Multi-interface coordination
Reset → Configure → Test
Protocol-specific directed test