Why Learn Virtual Sequencers? Real SoC tests coordinate stimulus across several interfaces at once - configure the DUT over a register bus, stream data through another port, then wait for an interrupt. A virtual sequencer gives one sequence handles to every agent's sequencer, so that coordination lives in a single place.

A chip-level testbench rarely has just one bus. A register interface, a reset controller and a high-speed data link all have their own agents, and a test has to drive all of them in a controlled order. A virtual sequencer gives one test a single place from which to reach every sequencer in the environment.

A UVM virtual sequencer is a uvm_sequencer that is not connected to a driver and does not process sequence items itself. It holds handles to other sequencers so that a virtual sequence running on it can start sequences on all of them.

7 min read | Intermediate Level

What You'll Learn

  • What a virtual sequencer is and how it differs from a regular sequencer
  • Create a virtual sequencer with handles to real sequencers
  • Instantiate it in the environment and connect its handles in connect_phase
  • Use it safely from a virtual sequence, including null handle checks

What is a virtual sequencer ?

A regular sequencer sits inside an agent. It arbitrates between sequences and passes sequence items to its driver through the seq_item_export.

A virtual sequencer has no driver. Its only job is to hold references to the "sub-sequencers" it coordinates. These can be regular, driver-connected sequencers or other virtual sequencers, which makes a layered control structure possible.

This is useful whenever one test scenario must control stimulus on several interfaces at once, such as resetting the design over one bus, configuring it over another, and then sending data over a third.

uvm_virtual_sequencer

The environment shown above has an APB env, an AXI env and a UART env. Each one has its own sequences and the sequencer on which they run. A virtual sequencer called m_vseqr holds a reference to each of these sequencers, so a virtual sequence running on m_vseqr can reach every sequencer in the testbench.

UVM has no separate uvm_virtual_sequencer class. The standard uvm_sequencer class is used for both regular and virtual sequencers. The difference comes only from how it is used: a virtual sequencer is never connected to a driver.

Where are Virtual Sequencers Used?

Virtual sequencers appear in almost every SoC and subsystem-level UVM environment. A typical example is a boot-flow test where reset, register programming and data traffic each come from a different agent, and a single virtual sequence controls the order through one virtual sequencer.

Syntax


class VSEQR_NAME extends uvm_sequencer;       // No item type needed
  `uvm_component_utils (VSEQR_NAME)          // It is a component, not an object

  SEQR_TYPE_A    m_handle_a;                 // Handle to a real sequencer
  SEQR_TYPE_B    m_handle_b;                 // Handle to another sequencer

  function new (string name, uvm_component parent);
    super.new (name, parent);
  endfunction
endclass
  • uvm_sequencer - base class, used with its default parameter (required)
  • `uvm_component_utils - registers it with the factory as a component (required)
  • Sequencer handles - one per sequencer to be controlled, assigned later by the environment (required)

Steps to create a virtual sequencer

Creating a virtual sequencer is similar to creating a regular sequencer. The key differences are the handles it contains and how they are connected.

1. Derive from uvm_sequencer

Like any other sequencer, a custom virtual sequencer extends the uvm_sequencer base class.


class virtual_sequencer extends uvm_sequencer;

2. Implement Constructor and Factory Registration

A virtual sequencer is a uvm_component, so it is registered with `uvm_component_utils and its constructor takes both a name and a parent.


    `uvm_component_utils (virtual_sequencer)

	function new (string name = "my_virtual_sequencer", uvm_component parent);
		super.new (name, parent);
	endfunction

3. Declare Handles for Sub-Sequencers

This is the main difference from a regular sequencer. The class declares one handle for each sequencer it needs to control, using the exact sequencer type from the agent.


    // Declare handles to other sequencers here
	apb_sequencer       m_apb_seqr;
	reg_sequencer       m_reg_seqr;
	wb_sequencer        m_wb_seqr;
	pcie_sequencer      m_pcie_seqr;

endclass

Name each handle after the interface it drives, such as m_apb_seqr for the APB agent. Descriptive names make virtual sequences much easier to read than generic names like seqr1.

4. Instantiate and Connect

Because it is a component, the virtual sequencer is created in the build_phase of a higher-level component, usually the uvm_env. Its handles are assigned in connect_phase.


class my_env extends uvm_env;
  `uvm_component_utils (my_env)

  apb_env              m_apb_env;
  reg_env              m_reg_env;
  wb_env               m_wb_env;
  virtual_sequencer    m_vseqr;

  virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);

    // Create the virtual sequencer
    m_vseqr = virtual_sequencer::type_id::create("m_vseqr", this);
  endfunction

  virtual function void connect_phase(uvm_phase phase);
    super.connect_phase(phase);

    // Connect handles of real sequencers to the virtual sequencer
    m_vseqr.m_apb_seqr = m_apb_env.m_apb_seqr;
    m_vseqr.m_reg_seqr = m_reg_env.m_reg_seqr;
    // ...
  endfunction

endclass

The handles must be assigned in connect_phase and not in build_phase. The build phase runs top-down, so when the environment's build_phase executes, the agents below it have not created their sequencers yet.

The uvm_config_db can also be used to pass sequencer handles. In large environments, take care that each lookup key is unique.

Quick Check

Q: Does a virtual sequencer need a driver connected to its seq_item_export?

Show Answer

No. The export still exists because it is inherited from uvm_sequencer, but it stays unconnected. Sequence items are always sent through the real sequencers to their own drivers.

Guidelines for Usage

  • No driver connection: A virtual sequencer is never connected to a driver. It coordinates stimulus but does not provide it.
  • Runs virtual sequences: Virtual sequences run on the virtual sequencer and use its handles to start sub-sequences on the real sequencers.
  • Prefer start() over macros: Macros like `uvm_do_on and `uvm_do_on_with can start a sequence on a sub-sequencer, but the start() method is recommended because it is explicit and easier to debug. Item macros such as `uvm_do do not apply, since a virtual sequencer has no items.
  • Check for null handles: A virtual sequence should check each sub-sequencer handle before using it. A handle stays null if its agent is passive (a passive agent has no sequencer) or if the connection was missed.

The snippet below shows such a null check inside a virtual sequence. It reaches the sub-sequencers through p_sequencer, which is declared with `uvm_declare_p_sequencer.


  class my_system_vseq extends uvm_sequence;
    `uvm_object_utils(my_system_vseq)
    `uvm_declare_p_sequencer(my_virtual_sequencer)   // Typed handle to the virtual sequencer

    apb_write_read_seq  apb_sub_seq;

     virtual task body();
        ...

        // Now, check the handles to the subsequencers contained within the virtual sequencer.
        // Access these through the 'p_sequencer' handle.
        if (p_sequencer.m_apb_seqr == null) begin
           // If the APB sequencer handle is null, it indicates a configuration issue
           // (e.g., APB agent was passive, or connection in env::connect_phase failed).
           `uvm_fatal(get_full_name(), "APB Sequencer handle (p_sequencer.m_apb_seqr) is null! Check agent configuration or env::connect_phase.")

        end else begin
           `uvm_info(get_full_name(), "APB Sequencer handle is valid.", UVM_LOW)
           apb_sub_seq = apb_write_read_seq::type_id::create("apb_sub_seq");
           apb_sub_seq.start(p_sequencer.m_apb_seqr, this);
        end

       ...
     endtask
  endclass
  

Virtual sequencers can also be layered. A higher-level virtual sequencer can hold handles to lower-level virtual sequencers, which in turn hold handles to driver-connected sequencers. This lets block-level virtual sequences be reused unchanged at the SoC level.

Virtual Sequencer Example

The complete virtual sequencer class combines the steps above into a single definition.


	class my_virtual_sequencer extends uvm_sequencer;
		`uvm_component_utils (my_virtual_sequencer)

		function new (string name = "my_virtual_sequencer", uvm_component parent);
			super.new (name, parent);
		endfunction

		// Declare handles to other sequencers here
		apb_sequencer       m_apb_seqr;
		reg_sequencer       m_reg_seqr;
		wb_sequencer        m_wb_seqr;
		pcie_sequencer      m_pcie_seqr;
	endclass

The top environment creates an instance of the virtual sequencer and connects each real sequencer to its handle.


	class top_env extends uvm_env;
		...

		my_virtual_sequencer 	m_virt_seqr;

		virtual function void build_phase (uvm_phase phase);
			...
			m_virt_seqr = my_virtual_sequencer::type_id::create ("m_virt_seqr", this);
			...
		endfunction

		// Connect virtual sequencer handles to actual sequencers
		virtual function void connect_phase (uvm_phase phase);
			...
			m_virt_seqr.m_apb_seqr   = m_apb_agent.m_apb_seqr;
			m_virt_seqr.m_reg_seqr   = m_reg_env.m_reg_seqr;
			m_virt_seqr.m_pcie_seqr  = m_pcie_env.m_pcie_agent.m_pcie_seqr;
			...
		endfunction
	endclass

Notice that each handle can come from a different depth of the hierarchy. The APB sequencer is one level down in an agent, while the PCIe sequencer is two levels down inside its own env. The virtual sequencer hides these paths, so a virtual sequence only needs p_sequencer.m_pcie_seqr.

The output below comes from a version of this testbench with APB, SPI and Wishbone agents.

Output
----------------------------------------------------------------
CDNS-UVM-1.1d
(C) 2007-2013 Mentor Graphics Corporation
(C) 2007-2013 Cadence Design Systems, Inc.
(C) 2006-2013 Synopsys, Inc.
(C) 2011-2013 Cypress Semiconductor Corp.
----------------------------------------------------------------
UVM_INFO @ 0: reporter [RNTST] Running test base_test...
UVM_INFO @ 0: reporter [UVMTOP] UVM testbench topology:
--------------------------------------------------------------
Name                       Type                    Size  Value
--------------------------------------------------------------
uvm_test_top               base_test               -     @2667
  m_top_env                top_env                 -     @2733
    m_apb_agent            apb_agent               -     @2780
      m_apb_drv            apb_driver              -     @4089
        rsp_port           uvm_analysis_port       -     @4237
        seq_item_port      uvm_seq_item_pull_port  -     @4188
      m_apb_mon            apb_monitor             -     @4217
      m_apb_seqr           uvm_sequencer           -     @3512
        rsp_export         uvm_analysis_export     -     @3569
        seq_item_export    uvm_seq_item_pull_imp   -     @4109
        arbitration_queue  array                   0     -
        lock_queue         array                   0     -
        num_last_reqs      integral                32    'd1
        num_last_rsps      integral                32    'd1
    m_spi_agent            spi_agent               -     @2840
      m_spi_drv            spi_driver              -     @4897
        rsp_port           uvm_analysis_port       -     @5043
        seq_item_port      uvm_seq_item_pull_port  -     @4995
      m_spi_mon            spi_monitor             -     @5024
      m_spi_seqr           uvm_sequencer           -     @4321
        rsp_export         uvm_analysis_export     -     @4377
        seq_item_export    uvm_seq_item_pull_imp   -     @4917
        arbitration_queue  array                   0     -
        lock_queue         array                   0     -
        num_last_reqs      integral                32    'd1
        num_last_rsps      integral                32    'd1
    m_virt_seq             virtual_sequencer       -     @2870
      rsp_export           uvm_analysis_export     -     @2928
      seq_item_export      uvm_seq_item_pull_imp   -     @3478
      arbitration_queue    array                   0     -
      lock_queue           array                   0     -
      num_last_reqs        integral                32    'd1
      num_last_rsps        integral                32    'd1
    m_wb_agent             wb_agent                -     @2810
      m_wb_drv             wb_driver               -     @5712
        rsp_port           uvm_analysis_port       -     @5858
        seq_item_port      uvm_seq_item_pull_port  -     @5810
      m_wb_mon             wb_monitor              -     @5839
      m_wb_seqr            uvm_sequencer           -     @5136
        rsp_export         uvm_analysis_export     -     @5192
        seq_item_export    uvm_seq_item_pull_imp   -     @5732
        arbitration_queue  array                   0     -
        lock_queue         array                   0     -
        num_last_reqs      integral                32    'd1
        num_last_rsps      integral                32    'd1
--------------------------------------------------------------

UVM_INFO ./tb/my_pkg.sv(68) @ 0: uvm_test_top.m_top_env.m_virt_seq@@m_virt_seq [VSEQ] Start of virtual sequence
UVM_INFO ./tb/wb_agent.sv(63) @ 0: uvm_test_top.m_top_env.m_wb_agent.m_wb_seqr@@m_wb_reset_seq [RESET_SEQ] Starting wb_reset_seq
UVM_INFO ./tb/apb_agent.sv(52) @ 20000: uvm_test_top.m_top_env.m_apb_agent.m_apb_seqr@@m_apb_rw_seq [RW_SEQ] Starting apb_rw_seq
UVM_INFO ./tb/spi_agent.sv(74) @ 30000: uvm_test_top.m_top_env.m_spi_agent.m_spi_seqr@@m_spi_tx_seq [tx_SEQ] Starting spi_tx_seq
UVM_INFO ./tb/my_pkg.sv(75) @ 30000: uvm_test_top.m_top_env.m_virt_seq@@m_virt_seq [VSEQ] End of virtual sequence
UVM_INFO ./tb/my_pkg.sv(108) @ 30000: uvm_test_top [SHUT] Shutting down test ...

--- UVM Report catcher Summary ---

Two details in this log confirm how a virtual sequencer works:

  • In the topology, the virtual sequencer instance m_virt_seq has a seq_item_export like the agent sequencers, but no driver is connected to it.
  • The virtual sequence reports from the virtual sequencer's path, while each child sequence reports from its own agent's sequencer path. The children really run on the real sequencers.

Virtual Sequencer vs Regular Sequencer

AspectRegular sequencerVirtual sequencer
Base classuvm_sequencer #(REQ)uvm_sequencer (default parameter)
LocationInside an agentInside an env, above the agents
Connected to a driverYesNo
RunsSequences that generate itemsVirtual sequences that start other sequences
ContainsArbitration logic and item exportThe same, plus handles to other sequencers

Where is Layering Used?

In large SoC projects, each subsystem team usually delivers its own environment with a virtual sequencer. The SoC environment then adds a top-level virtual sequencer whose handles point to these subsystem virtual sequencers, so subsystem test scenarios can be reused at the chip level.

Common Mistakes

Mistake #1: Assigning handles in build_phase instead of connect_phase

Wrong: m_vseqr.m_apb_seqr = m_apb_env.m_apb_seqr; inside the env's build_phase
Right: the same assignment inside the env's connect_phase

Why? The build phase runs top-down. When the environment's build_phase runs, the agents' sequencers have not been created yet, so the handle copied is null. By connect_phase, the whole hierarchy exists.

Mistake #2: Declaring a handle with the wrong sequencer type

If the agent declares its sequencer as uvm_sequencer #(apb_item) but the virtual sequencer declares the handle as apb_sequencer (a class derived from it), the direct assignment does not compile. A base-class handle cannot be assigned to a derived-class variable without $cast. Declare the handle with exactly the type the agent uses.

Mistake #3: Forgetting that passive agents have no sequencer

When an agent is configured as UVM_PASSIVE, it builds only its monitor. The virtual sequencer's handle for that agent stays null, and any virtual sequence that starts a sequence on it fails. Check the handle for null before calling start(), as shown in the guidelines above.

Practice Exercise

Challenge: Add an SPI Sequencer Handle

Extend my_virtual_sequencer with a handle m_spi_seqr of type spi_sequencer. Then connect it in top_env to the sequencer inside m_spi_agent, but only when that agent is active.

Hint

Every uvm_agent has a get_is_active() method that returns UVM_ACTIVE or UVM_PASSIVE. Use it in connect_phase before assigning the handle.

Solution

class my_virtual_sequencer extends uvm_sequencer;
  `uvm_component_utils (my_virtual_sequencer)

  apb_sequencer   m_apb_seqr;
  spi_sequencer   m_spi_seqr;    // New handle for the SPI agent

  function new (string name = "my_virtual_sequencer", uvm_component parent);
    super.new (name, parent);
  endfunction
endclass

class top_env extends uvm_env;
  // Rest of the code (agents, build_phase, etc.)

  virtual function void connect_phase (uvm_phase phase);
    super.connect_phase (phase);
    m_virt_seqr.m_apb_seqr = m_apb_agent.m_apb_seqr;

    // A passive agent has no sequencer, so connect only when active
    if (m_spi_agent.get_is_active() == UVM_ACTIVE)
      m_virt_seqr.m_spi_seqr = m_spi_agent.m_spi_seqr;
  endfunction
endclass

A virtual sequence that uses m_spi_seqr should still check it for null, so the same virtual sequence works in both active and passive configurations.

Summary

In this tutorial, you learned:

  • A virtual sequencer is a uvm_sequencer that has no driver and holds handles to other sequencers
  • UVM has no separate virtual sequencer class; the difference lies only in how the sequencer is used
  • It is registered with `uvm_component_utils and created in the environment's build_phase
  • Its handles must be assigned in connect_phase, after the agents have built their sequencers
  • Virtual sequences run on it and start child sequences on the real sequencers, preferably with start()
  • Handles should be checked for null, because passive agents have no sequencer
Did you find what you were looking for?

Quiz

Test Your Knowledge

1 of 5 Score: 0 / 5