A virtual sequence is a container to start multiple sequences on different sequencers in the environment. This virtual sequence is usually executed by a virtual sequencer which has handles to real sequencers. The need for a virtual sequence arises when you require different sequences to be run on different environments. For example, an SoC design might have multiple different interfaces that might need to be driven by a different set of sequences on individual sequencers. Hence the best way to start and control these different sequences would be from a virtual sequence. It becomes virtual because it is not associated with any particular data type.

Use of a virtual sequencer
class my_virtual_seq extends uvm_sequence;
`uvm_object_utils (my_virtual_seq)
`uvm_declare_p_sequencer (my_virtual_sequencer)
function new (string name = "my_virtual_seq");
super.new (name);
endfunction
apb_rd_wr_seq m_apb_rd_wr_seq;
wb_reset_seq m_wb_reset_seq;
pcie_gen_seq m_pcie_gen_seq;
task pre_body();
m_apb_rd_wr_seq = apb_rd_wr_seq::type_id::create ("m_apb_rd_wr_seq");
m_wb_reset_seq = wb_reset_seq::type_id::create ("m_wb_reset_seq");
m_pcie_gen_seq = pcie_gen_seq::type_id::create ("m_pcie_gen_seq");
endtask
task body();
...
m_apb_rd_wr_seq.start (p_sequencer.m_apb_seqr);
fork
m_wb_reset_seq.start (p_sequencer.m_wb_seqr);
m_pcie_gen_seq.start (p_sequencer.m_pcie_seqr);
join
...
endtask
endclass
Note the following from the example above.
- my_virtual_seq is derived from
uvm_sequence
just like any other sequence - A handle called
p_sequencer
is created within the sequence via macro`uvm_declare_p_sequencer
and assigned to be run with my_virtual_sequencer - Each sequence is started on its corresponding sequencer using the
start()
method - Each sequencer is referenced by p_sequencer handle which points to the virtual sequencer
Once a virtual sequence is defined, you can start this in your test as shown below.
class my_test extends uvm_test;
`uvm_component_utils (my_test)
my_env m_env;
...
task run_phase (uvm_phase phase);
my_virtual_seq m_vseq = my_virtual_seq::type_id::create ("m_vseq");
phase.raise_objection (this);
m_vseq.start (m_env.m_virtual_seqr);
phase.drop_objection (this);
endtask
endclass
Without a virtual sequencer
You can also put handles to individual sequencers within the virtual sequence, and then would not need a virtual sequencer.
class my_virtual_seq extends uvm_sequence;
`uvm_object_utils (my_virtual_seq)
...
apb_sequencer m_apb_seqr;
reg_sequencer m_reg_seqr;
wb_sequencer m_wb_seqr;
apb_rd_wr_seq m_apb_rd_wr_seq;
wb_reset_seq m_wb_reset_seq;
pcie_gen_seq m_pcie_gen_seq;
virtual task pre_body();
m_apb_rd_wr_seq = apb_rd_wr_seq::type_id::create ("m_apb_rd_wr_seq");
...
// Instantiate other sequences here
endtask
virtual task body ();
m_apb_rd_wr_seq.start (m_apb_seqr);
...
endtask
endclass
The handles to sequencers within the sequence needs to be assigned prior to starting this virtual sequence.
class my_test extends uvm_test;
...
virtual task run_phase (uvm_phase phase);
my_virtual_seq m_vseq = my_virtual_seq::type_id::create ("m_vseq");
phase.raise_objection (this);
// Assign all sequencer handles
m_vseq.m_apb_seqr = m_env.m_apb_agent.m_apb_seqr;
...
m_vseq.start (null);
phase.drop_objection (this);
endtask
endclass
If you would like, you can also split the creation and assignment of sequence into build_phase
and connect_phase
within my_test.