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
In simple terms it's a UVM sequencer that contain handles to other sequencers. Why do we need this ? Because we plan to use virtual sequences and want to have control over all sequencers from a central place. A request type is not required here because this sequencer is generic and not limited to handle only one particular data type.
Click to refresh the concept of a virtual sequence.

The environment shown above has an APB agent, Wishbone agent, PCIE environment and a register layer environment. Each of these components have their own sequences and the respective sequencers on which they are launched. A virtual sequencer called m_virt_seqr is instantiated to hold references to each individual sequencer. Hence a virtual sequence executing on this virtual sequencer will have access to all the sequencers in the testbench.
Example
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
What is a UVM sequence ?
UVM sequences are made up of several data items which can be put together in different ways to create interesting scenarios. They are executed by an assigned sequencer which then sends data items to the driver. Hence, sequences make up the core stimuli of any verification plan.
Class Hierarchy
Steps to create a UVM sequence
1. Create a user-defined class inherited fromuvm_sequence
, register with factory and call new
// my_sequence is user-given name for this class that has been derived from "uvm_sequence"
class my_sequence extends uvm_sequence;
// [Recommended] Makes this sequence reusable. Note that we are calling
// `uvm_object_utils instead of `uvm_component_utils because sequence is a uvm_transaction object
`uvm_object_utils (my_sequence)
// This is standard code for all components
function new (string name = "my_sequence");
super.new (name);
endfunction
endclass
2. Declare the default sequencer to execute this sequence
// [Optional] my_sequencer is a pre-defined custom sequencer before this sequence definition
`uvm_declare_p_sequencer (my_sequencer)
3. Define the body method
// [Recommended] Make this task "virtual" so that child classes can override this task definition
virtual task body ();
// Stimulus for this sequence comes here
endtask
Subscribers are basically listeners of an analysis port. They subscribe to a broadcaster and receive objects whenever an item is broadcasted via the connected analysis port. A uvm_component
class does not have an in-built analysis port, while a uvm_subscriber
is an extended version with an analysis port named analysis_export
.
Class definition
virtual class uvm_subscriber #(type T=int) extends uvm_component;
typedef uvm_subscriber #(T) this_type;
uvm_analysis_imp #(T, this_type) analysis_export;
function new (string name, uvm_component parent);
super.new (name, parent);
analysis_export = new ("analysis_imp", this);
endfunction
pure virtual function void write (T, t);
endclass
Design
module jk_ff ( input j,
input k,
input clk,
output q);
reg q;
always @ (posedge clk)
case ({j,k})
2'b00 : q <= q;
2'b01 : q <= 0;
2'b10 : q <= 1;
2'b11 : q <= ~q;
endcase
endmodule
Hardware Schematic

Testbench
module tb_jk;
reg j;
reg k;
reg clk;
always #5 clk = ~clk;
jk_ff jk0 ( .j(j),
.k(k),
.clk(clk),
.q(q));
initial begin
j <= 0;
k <= 0;
#5 j <= 0;
k <= 1;
#20 j <= 1;
k <= 0;
#20 j <= 1;
k <= 1;
#20 $finish;
end
initial
$monitor ("j=%0d k=%0d q=%0d", j, k, q);
endmodule