Using Uvm Virtual Sequencers Virtual Sequences
Using Uvm Virtual Sequencers Virtual Sequences
Introduction: What are virtual sequencers and virtuals sequences and when should they be used?
Tests that require coordinated generation of stimulus using multiple driving agents need to use virtual sequences.
This paper will clarify important concepts and usage techniques related to virtual sequencers and virtual sequences
that are not well documented in existing UVM reference materials. This paper will also detail the m_sequencer and
p_sequencer handles and the macros and methods that are used with these handles. The objective of this paper is
to simplify the understanding of virtual sequencers, virtual sequences and how they work.
If you only have a single driving agent, you do not need a virtual sequencer.
If you have multiple driving agents but no stimulus coordination is required, you do not need a virtual sequencer.
If you have multiple driving agents and stimulus coordination IS required, you need a virtual sequencer.
It should be noted that if a testbench with multiple agents and non-coordinated stimulus is ever extended in the fu-
ture to require coordinated stimulus, then the environment will require updates to include one or more virtual se-
quencers. Those updates, performed later in the projet, could be quite painful as opposed to building in a virtual se-
quencer from the beginning and taking advantage of the virtual sequencer when needed. Engineers might want to
make a habit of adding the virtual sequencer in most of their UVM testbenches.
SystemVerilog has virtual classes, virtual methods and virtual interfaces and all three require the "virtual" keyword.
UVM has virtual sequencers and virtual sequences but neither one requires the "virtual" keyword. There are no
uvm_virtual_sequencer or uvm_virtual_sequence base classes in UVM. All sequencers and virtual se-
quencers are derivatives of the uvm_sequencer class and all sequences and virtual sequences are derivatives of the
uvm_sequence class.
A virtual sequence can run multiple transaction types on multiple real se-
quencers. The virtual sequence is typically just coordinating execution of
the other sequences on the appropriate subsequencers.
The UVM User Guide claims that "most users disable the subsequencers and invoke sequences only from the virtual
sequence," but our experience and the experience of many verification colleagues is that the most popular virtual
sequencer mode is parallel tragffic generation, also known as "business as usual." This is the mode that is described
in this paper.
How are virtual sequencers implemented?
A virtual sequencer is little more than a component providing a locus and scope to configure virtual sequences and
provide handles to the subsequencers that will be required by virtual sequences.
The code for a virtual sequencer is rather simple. The subsequencer handles declared in the virtual sequencer will be
specified, via the configuration database, after all components are built (after the build_phase()) and are typically
set by the environment in the connect_phase().
Example 1 is a typical structure for a virtual sequencer. The user-selected name for this example is vsequencer.
Virtual sequencers are extended from uvm_sequencer, NOT uvm_virtual_sequencer (which does not exist).
Unlike normal sequencers, the virtual sequencer of Example 1 is not user-parameterized to a transaction type be-
cause this sequencer will be able to execute multiple transaction types. Extending a virtual sequencer from the
uvm_sequencer base class without any parameters means that the virtual sequencer will use the default parameter-
ized values of uvm_sequence_item.
The virtual sequencer declares subsequencer handles. In Example 1, the subsequencer handles are called ahb_sqr
and eth_sqr respectively. These two subsequencer handles will be assigned from values specified in the configura-
tion database during the end_of_elaboration_phase().
Unlike Transaction Level Model (TLM) connections that are used to connect most components in a UVM testbench,
the subsequencer handles are not set using a TLM connect() method, but are specified by the environment using
the configuration database. It is then the job of the virtual sequencer to extract those handles from the configuration
database and assign them to the two handles declared in the virtual sequencer. The actual subsequencers will be cre-
ated in the build_phase(). Therefore, their handles will only be available to be put in the configuration database
by the environment in its connect_phase(). Thus, the virtual sequencer will have to retrieve them in the next
phase: end_of_elaboration_phase().
Finally, the vsequencer example includes the typical new() constructor that is common to all UVM components.
It can be seen from this example, that the vsequencer is just a container for the handles to subsequencers and other
configuration parameters. The virtual sequences assume the virtual sequencer has been properly configured before
the virtual sequences execute in the run_phase(). They can then access these configuration paratemers in the vir-
tual sequencer via their p_sequencer handle.
Sequence Details
Sequences are run on a sequencer and are parameterized to the transaction type that is processed by that sequencer.
Sequences are started on a sequencer using the built-in sequence start() method or by using the `uvm_do() mac-
ros.
Every sequence has a handle to the sequencer that is running that sequence. That handle is called the m_sequencer
handle.
Just like any other sequence, when a virtual sequence is started on a virtual sequencer, using either the start()
method or the `uvm_do macros, the virtual sequence will automatically have an m_sequencer handle that correctly
points to the virtual sequencer.
All sequences have an m_sequencer handle but sequences do not automatically have a p_sequencer handle. Fur-
thermore, the m_sequencer variable is an internal implementation variable that is poorly documented and should
not be used directly by verification engineers. It is an artifact of the SystemVerilog language, which lacks C++'s
concept of “friend” classes that this variable is public. Any variable or method with the “m_” prefix should similarly
not be used directly.
p_sequencer is not automatically declared and set, but can be declared and set by using the
`uvm_declare_p_sequencer macro. As will be shown later in this paper, the `uvm_declare_p_sequencer
macro and p_sequencer handle are user-conveniences.
Technically, the p_sequencer handle is never required but when used with the `uvm_declare_p_sequencer
macro, it is automatically (1) declared, (2) set and (3) checked when a virtual sequence is started, and properly
points to the virtual sequencer that is running the virtual sequence.
More about the p_sequencer handle and its usage is described below.
A closer look at this macro and what it does is instructive. This macro is typically placed in a sequence base class
that will be extended to create all of the sequences that use the designated sequencer, virtual or not.
On line 1, the user calls this macro and passes the type of the sequencer that will be used by the sequences. For vir-
tual sequences, this is the class name of the designated virtual sequencer they will execute on.
On line 2, the designated sequencer is declared with the handle name p_sequencer. For the remainder of the code
in this macro and everywhere else in the user-defined virtual sequence base and extended virtual sequence classes,
the virtual sequencer will be referenced by the name p_sequencer. From this point forward, there is no need to
reference the name of the virtual sequencer that is being used, the user can simply reference the p_sequencer (vir-
tual sequencer) handle. This is simply a convenience, not a requirement.
On line 3 is the start of a virtual void function declaration that continues through line 9. The void function is
called m_set_p_sequencer and this function is called whenever a sequence start() method is called on one of
the virtual sequences or when a `uvm_do_on() macro is used to start a virtual sequence.
Line 4 ensures that if the virtual sequence is an extension of another virtual sequence, the base virtual sequence will
also execute its own m_set_p_sequencer method .
Line 5 casts the internal m_sequencer handle, which should be the handle of the virtual sequencer to the local
p_sequencer handle declared on line 2. The if-test checks to see if the $cast operation failed (!$cast(...))
and if the $cast did fail, the fatal message on lines 6-8 will terminate the UVM simulation and report a consistent
message that there was a problem casting to the specified virtual sequencer type, i.e. the sequence is executing on a
sequencer of the wrong type. The if-test, $cast operation and corresponding consistent error message are also
shown in Figure 3.
The virtual sequencer for this testbench was shown in Example 1. All other testbench files will be described in the
remainder of this paper.
Since every virtual sequence needs to execute these steps, it is recommended to put this code into a virtual sequence
base class (vseq_base) and then create all virtual sequences by extending the vseq_base class.
Example vseq_base
For the virtual sequencer shown in Example 1, we can use the vseq_base definition shown in Example 2.
tb_ahb_sequencer ahb_sqr;
tb_eth_sequencer eth_sqr;
Note that the vseq_base class assumes that the handles were already set in the virtual sequencer. It is the job of the
enviroinment to ensure that the subsequencer handles are properly set.
Consider the two virtual sequence examples shown in Example 3 and Example 4. These sequences are examples of
virtual sequences that are extensions of the base virtual sequence shown in Example 2.
There are two accepted methods for executing sequences in UVM: (1) use the `uvm_do macros, which are generally
considered to be the easiest to use but may also be less simulation efficient (because the subsequences are always
allocated and randomized before being executed) and more difficult to understand if the user ever expands the
`uvm_do macro code, and (2) use explicit allocation, and direct assignments or calls to randomize() before using
the start() method to execute the sequences on the chosen subsequencer, which is generally considered to require
more user-coding effort but that are straightforward and allow the creation and execution of more directed sequenc-
es.
The Example 3 vitual sequence uses the `uvm_do macros to run a virtual sequence to randomly generate pseudo-
AHB packets followed by two sequences of pseudo-Ethernet packtes and concludes with another sequence of pseu-
do-AHB packets. The code for the pseudo-ethernet and AHB transactions, along with the Ethernet and AHB se-
quences, and the test that runs v_seq1 will be shown later.
class v_seq1 extends vseq_base;
`uvm_object_utils(v_seq1)
The Example 4 vitual sequence uses calls to the randomize() and start() methods to run a virtual sequence to
randomly generate pseudo-AHB packets followed by two sequences of pseudo-Ethernet packtes and concludes with
another sequence of pseudo-AHB packets. The code for the pseudo-ethernet and AHB packets, along with the
Ethernet and AHB sequences, and the test that runs v_seq2 will be shown later.
Consider the AHB sequence code shown Example 6. This simple AHB sequence randomly generates 2-5 AHB
packets. The virtual sequences in Example 3 and Example 4 send this randomly generated set of AHB packets to the
ahb_sqr handle declared in the vsequencer component.
Consider the pseudo Ethernet packet code shown Example 7. This is a "pseudo-Ethernet packet" because it only
contains two non-standard Ethernet fields and an example DUT will recognize when these fields have been sent to
the DUT and print that information to the screen for examination.
It is a good idea to create a test_base class with common declarations and methods that will be used by every
other test in the verification suite. The test_base class shown in Example 9, declares the environment handle and
creates the environment in the build_phase(). These actions will not need to be repeated in tests that extend this
test_base class. This test_base also includes a start_of_simulation_phase() to print the testbench struc-
ture and factory contents before the simulation executes in the run_phase(). It is useful to print the testbench
structure and factory contents in the start_of_simulation_phase() because troubles most often appear in the
run_phase() so these pre-run printouts can help diagnose if any components were incorrectly constructed or if
some of the testbench classes were omitted from the factory.
`timescale 1ns/1ns
class test_base extends uvm_test;
`uvm_component_utils(test_base)
env e;
Once a test_base class is coded, each of the tests can extend the test_base to create the individual tests. Exam-
ple 10 shows the test1 class definition that is extended from the test_base class. The test1 example defines the
run_phase() for this test, which declares the first virtual sequence ( v_seq1 ) handle vseq and creates the vseq
object. The test then calls the raise_objection() method, prints a message, calls the start() method on the
vseq sequence and passes the environment-virtual sequencer path ( e.v_sqr ) to the start() method. Once the
virtual sequence has completed, the test prints one more message and then calls the drop_objection() method
and finishs.
`timescale 1ns/1ns
class test1 extends test_base;
`uvm_component_utils(test1)
The test2 code in Example 11 does the same thing as the test1 code of Example 10 except that the test2 code
declares the vseq handle to be the second virtual sequence type, v_seq2.
`timescale 1ns/1ns
class test2 extends test_base;
`uvm_component_utils(test2)
tb_eth_agent eth_agnt;
tb_ahb_agent ahb_agnt;
vsequencer v_sqr;
Summary
This paper describes the necessary steps to create a working virtual sequencer environment and explains the purpose
of m_sequencer and p_sequencer handles and the `uvm_declare_p_sequencer macro. These are topics that
are often confusing to new and experienced UVM verification engineers.
This paper also includes all the code necessary to test the example described in this paper.
Acknowledgements
The authors would like to thank the following colleagues: JL Gray who helped Cliff put together the first version of
the Virtual Sequence / Sequencer example shown in this paper many years ago. Mark Litterick of Verilab for help-
ing to refine some of the techniques used in this example especially related to the use of the p_sequencer handle
and `uvm_declare_p_sequencer macro. Heath Chambers who has helped correct these materials for use in
UVM training. Multiple Verilab engineers who responded to Cliff's survey years ago regarding their preferred virtu-
al sequencer usage mode. Logie Ramachandran for providing valuable feedback and suggestions for this paper.
References:
[1] "IEEE Standard For SystemVerilog ‐ Unified Hardware Design, Specification and Verification Language,"
IEEE Computer Society and the IEEE Standards Association Corporate Advisory Group, IEEE, New York,
NY, IEEE Std 1800™‐2012
[2] Universal Verification Methodology (UVM) 1.2 Class Reference, June 2014, Accellera
[3] Universal Verification Methodology (UVM) 1.1 User's Guide, May 2011, Accellera
Appendix 1 m_sequencer handle creation ‐ details
If you trust that the m_sequencer handle is properly created in the UVM Base Class Library (BCL) and if you don't
care how the m_sequencer handle is created, then you do not need to read this section. This section is included for
those who wish to understand the details of how UVM creates the m_sequencer handle.
Finding the correct UVM base class routines to create the m_sequencer handle is a bit tricky. Assuming the use of
a sequence called tr_seq, that is started on the e (environment) agnt1 (agent) sqr (sequencer), here is how the
m_sequencer handle is created:
(2) uvm_sequence inherits the start() method from the uvm_sequence_base class.
Typically only the sequencer handle is passed to the start() method and then uvm_sequence_base calls
set_item_context(null, e.agnt1.sqr);
(4) The set_item_context() method is defined in the uvm_sequence_item class, which is inherited by the
uvm_sequence_base and uvm_sequence classes.
(7) The set_sequencer() method sets the m_sequencer handle, declared to be a protected
uvm_sequencer_base handle in the uvm_sequence_item class.
To summarize, the tr_seq.start() method passes a sequencer handle to the set_item_context() method,
which passes the handle to the set_sequencer() method, which sets the m_sequencer handle. The
m_sequencer handle is a handle to the sequencer that is running this sequence ( tr_seq in this example). The
m_sequencer handle can be retrieved by calling the get_sequencer() method.
Appendix 2 UVM virtual sequencer /sequence example code
The code that corresponds to the virtual sequencer testbench shown in Figure 4 is included in this appendix. Each of
the files for this example are listed in alphabetical order in the appendix subsections.
import uvm_pkg::*;
`include "uvm_macros.svh"
always @* begin
`uvm_info("DUT AHB", $sformatf("ahb_addr=%8h ahb_data=%16h",
ahb_addr, ahb_data), UVM_MEDIUM);
end
always @* begin
`uvm_info("DUT ETH", $sformatf("eth_src =%12h eth_dst =%12h",
eth_src, eth_dst), UVM_MEDIUM);
end
endmodule
Example 14 ‐ dut.sv code
tb_ahb_driver drv;
tb_ahb_sequencer sqr;
tb_eth_driver drv;
tb_eth_sequencer sqr;
`include "eth_pkt.sv"
`include "ahb_pkt.sv"
`include "ahb_transaction.sv"
`include "eth_seq1.sv"
`include "ahb_seq1.sv"
`include "tb_eth_driver.sv"
`include "tb_eth_sequencer.sv"
`include "tb_eth_agent.sv"
`include "tb_ahb_driver.sv"
`include "tb_ahb_sequencer.sv"
`include "tb_ahb_agent.sv"
`include "vsequencer.sv"
`include "vseq_base.sv"
`include "v_seq1.sv"
`include "v_seq2.sv"
`include "env.sv"
`include "test_base.sv"
`include "test1.sv"
`include "test2.sv"
endpackage
Example 23 ‐ tb_pkg.sv code
initial begin
uvm_config_db#(virtual ahb_if)::set(null, "*", "ahb_vif", ahb_if);
uvm_config_db#(virtual eth_if)::set(null, "*", "eth_vif", eth_if);
run_test();
end
endmodule
Example 24 ‐ top.sv code