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:
systemverilogclass 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:
systemverilogclass 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
systemverilogclass 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:
systemverilogtask 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
systemverilogclass 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
| Scenario | Regular Sequence | Virtual Sequence |
|---|---|---|
| Single-interface stimulus | ✅ | Overkill |
| Multi-interface coordination | ❌ | ✅ |
| Reset → Configure → Test | ❌ | ✅ |
| Protocol-specific directed test | ✅ | ❌ |