CummingsSNUG2024SV M Sequencer P Sequencer
CummingsSNUG2024SV M Sequencer P Sequencer
Silicon Valley, CA
Voted Best Presentation
3rd Place
Clifford E. Cummings
Paradigm Works, Inc.
Provo, Utah, USA
www.sunburst-design.com
ABSTRACT
There is significant confusion amongst verification engineers about the UVM
m_sequencer and p_sequencer handles and the use of the `uvm_declare_p_sequencer
macro. Every sequence has an m_sequencer handle but sequences only have access
to a p_sequencer handle if the sequence properly declares and sets it, or if the
sequence calls the `uvm_declare_p_sequencer macro.
This paper describes the m_sequencer handle and how it is defined and used by
sequences. This paper also describes the p_sequencer handle, how it is optionally
defined and used, and how it is typically defined using the `uvm_declare_p_sequencer
macro.
This paper explains why the p_sequencer handle is typically required if engineers use
the inferior uvm_config_db resources API. A commonly used virtual sequencer example
is shown to demonstrate the typical usage of the p_sequencer handle by verification
engineers.
This paper describes how to generally avoid usage of the confusing p_sequencer
handle if engineers use the more powerful uvm_resource_db resources API.
SNUG Silicon Valley 2024
Table of Contents
1. Sequences are started on sequencers ............................................................................................ 4
1.1 Sequence.start() ................................................................................................................... 4
1.2 Manually Setting the Sequencer Handle............................................................................... 4
1.3 `uvm_do macros ................................................................................................................... 5
2. UVM Base Classes for Sequencers and Sequences ...................................................................... 6
2.1 Where is the m_sequencer variable defined? ....................................................................... 6
2.2 set_sequencer() details ........................................................................................................ 7
3. What does the sequence start() method do? .................................................................................. 8
4. Starting a sequence on null ............................................................................................................. 9
5. What does the `uvm_declare_p_sequencer macro do? ................................................................ 10
6. sequence.start(null) example ........................................................................................................ 12
6.1.1 seq_base ................................................................................................................... 12
6.1.2 sequence ................................................................................................................... 12
6.1.3 test_base ................................................................................................................... 13
6.1.4 test ............................................................................................................................ 13
7. Typical Virtual Sequence / Virtual Sequencer Technique.............................................................. 14
8. Sequences can access resources directly .................................................................................... 18
9. Summary & Conclusions ............................................................................................................... 19
10. Acknowledgements ..................................................................................................................... 19
11. References .................................................................................................................................. 19
12. Author & Contact Information ...................................................................................................... 19
Table of Figures
Figure 1 - Tests start top-level sequences on sequencers .................................................................. 4
Figure 2 - Base & user-defined sequencer and sequence class hierarchies ....................................... 6
Figure 3 - m_sequencer handle declared in the uvm_sequence_item base class – inherited by
transaction and sequence classes ...................................................................................................... 7
Figure 4 - p_sequencer handle in user sequence classes by calling the `uvm_declare_p_sequencer
macro ................................................................................................................................................ 10
Figure 5 - Virtual sequencer example – block diagram ..................................................................... 14
Figure 6 - env_top block diagram – connect_phase() used to copy subsequencer handles to
vsequncer handles ............................................................................................................................ 15
Table of Examples
Example 1 - Sequence.start() on full path to sequencer ..................................................................... 4
Example 2 - seq.start() to environment . agent . sequencer from top-level test .................................. 4
Example 3 - set_sequencer command ................................................................................................ 5
Example 4 - Sequence started on null ................................................................................................ 5
Example 5 - Common set_sequencer() mistake ................................................................................. 5
Example 6 - VCS Fatal UVM null-sequencer error message .............................................................. 5
Example 7 - Default activity of set_sequencer() method ..................................................................... 7
Example 8 - Default m_set_p_sequencer() method definition – empty - returns nothing .................... 7
Example 9 - Default activity of the get_sequencer() method ............................................................... 8
Example 10 - virtual start task prototype defined in the uvm_sequence_base class........................... 8
Example 11 - set_item_context method prototype defined in the uvm_sequence_item base class .... 8
Example 12 - Failing set_sequencer() / seq.start(null) ........................................................................ 9
Example 13 - UVM source code for the uvm_declare_p_sequencer macro definition ...................... 10
Example 14 - Full uvm_declare_p_sequencer error message .......................................................... 11
Example 15 - p-sequencer handle declaration made by the `uvm_declare_p_sequencer ................ 11
Example 16 - seq.start(null) – seq_base example ............................................................................ 12
Example 17 - seq.start(null) – sequence extended from seq_base example .................................... 12
Example 18 - seq.start(null) – test_base example ............................................................................ 13
Example 19 - seq.start(null) – test1 extended from test_base example ............................................ 13
Example 20 - vsequencer (virtual sequencer) class example ........................................................... 14
Example 21 - env1 block diagram and env1 class example .............................................................. 15
Example 22 - env2 block diagram and env2 class example .............................................................. 15
Example 23 - env_top class that sets all the subsequencer handles in the vsequencer ................... 16
Example 24 - vseq_base (virtual sequence base) class using p_sequencer handles ....................... 16
Example 25 - Virtual sequence example #1 extended from vseq_base class (vseq_A1_B_A2_A1) 17
Example 26 - Virtual sequence example #2 extended from vseq_base class (vseq_A1_B_C)......... 17
1.1 Sequence.start()
Perhaps the most common way to start a sequence is to use the sequence.start() method. This
method can be invoked from a top-level test as well as from other sequences.
sequence.start(full_path_to_sequencer)
Example 1 - Sequence.start() on full path to sequencer
In the block level diagram shown in Figure 1, test1 starts the sequence seq on the env (with
handle name e), the tb_agent (with handle name agnt), and the tb_sequencer (with handle
name sqr). The command that test1 executes is shown in Example 2.
seq.start(e.agnt.sqr)
Example 2 - seq.start() to environment . agent . sequencer from top-level test
This will cause an m_sequencer handle in the started sequence to be set to e.agnt.sqr.
The m_sequencer handle is described in Section 2.
Inside of a sequence base class or the sequence itself, the sequencer handle is set using the
command shown in Example 3.
sqr_handle.set_sequencer(full_path_to_sequencer)
Example 3 - set_sequencer command
The test then just starts a sequence and since the sequence already has a handle to the sequencer,
the sequence can be started on null as shown in Example 4
sequence.start(null)
Example 4 - Sequence started on null
Using this technique can be tricky because the sqr_handle in the started sequence typically
cannot be the m_sequencer handle. Engineers who try to use this technique often make the
mistake of trying to run the set of commands shown in Example 5.
In Example 5:
Line 1: A write_read sequence object is created.
Line 3: The m_sequencer handle in the write_read sequence is set to the path
"e.agnt.sqr"
Line 4: The m_sequencer handle in the write_read sequence is overwritten and set to null.
Line 4 will now try to start a write_read sequence on a null sequencer handle, which will
produce a runtime error as shown in Example 6.
UVM_FATAL @0: reporter@@seq [SEQ] neither the item's sequencer nor dedicated
sequencer has been supplied to start the item in seq
Example 6 - VCS Fatal UVM null-sequencer error message
The seq.start(null) starts a sequence and sets the m_sequencer handle to null. Before
doing seq.start(null), a user-test typically must manually set a sequencer handle that is
frequently inherited from a sequence_base class.
An example using this seq.start(null) technique is shown in Section 6.
The `uvm_do macros can determine if the item to be executed is a transaction or a sequence. If the
item is a transaction, commonly referred to as a sequence_item, the `uvm_do macro will execute
start_item(), randomize(), finish_item(). If the item to be executed is a sequence, the
`uvm_do macro will execute seq.start(m_sequencer).
Figure 3 - m_sequencer handle declared in the uvm_sequence_item base class – inherited by transaction and sequence
classes
By default, the m_set_p_sequencer() method is empty and does nothing. The default
m_set_p_sequencer() method is shown in Example 8.
// Internal method
virtual function void m_set_p_sequencer();
return;
endfunction
Example 8 - Default m_set_p_sequencer() method definition – empty - returns nothing
The get_sequencer() method is an accessor method that is used to retrieve the m_sequencer
handle of the current sequence. In theory, UVM users should not reference the m_sequencer
handle directly but are encouraged to use the get_sequencer() method to access the
m_sequencer handle; however, it is not uncommon for engineers to access the m_sequencer
handle directly from user-defined sequences.
The reason behind the invisible internal-variable theory (which is a common software coding
practice) is that the m_sequencer handle is an internal variable that should be unknown to the user,
and as an internal variable, UVM library developers might decide to change the name. A quick
search for any reference to the m_sequencer handle in the UVM 1.2 Class Reference manual
turns up empty.
The empty m_set_p_sequencer() is defined in the src/seq/uvm_sequence_item.svh base
class file. This method is overridden by the `uvm_declare_p_sequencer() macro, and since
the uvm_sequence_base and uvm_sequence classes are both derivatives of the
uvm_sequence_item class, they both inherit the m_set_p_sequencer empty method.
When a user-sequence base class or user-sequence class calls the
`uvm_declare_p_sequencer macro, it overrides the m_set_p_sequencer method and
populates it as described in Section 5. and is shown in Example 13.
Three of the arguments on the start() method have default values and are not discussed in this
paper. The full path to the sequencer in the testbench is typically passed as the first argument to the
start() method. The first argument is sometimes called with the value null if the started-
sequence already has subsequencer handles declared and set inside of the calling sequence, and if
the subsequences are started on the defined subsequencer handles and not on the m_sequencer
handle. More about starting sequences on null is described in Section 6.
The start() task calls the set_item_context() method defined in the
src/seq/uvm_sequence_item.svh base class file. The prototype for the
set_item_context() method is shown in Example 11.
When the test or another sequence makes a call to the start() method, (for example:
sequence.start(e.agnt.sqr)), the start() method will call
set_item_context(e.agnt.sqr), which will set the m_sequencer handle in the started
sequence to the value of e.agnt.sqr. The sequence will then execute its code on the sequencer
whose handle is stored in the m_sequencer variable.
phase.raise_objection(this);
if (!seq.randomize())
`uvm_error("RAND", "Failed randomization");
seq.set_sequencer(e.agnt.sqr); // No compilation error
seq.start(null); // but does not work
init_seq(seq); // Works!
seq.start(null); // Works!
phase.drop_objection(this);
endtask
endclass
Example 12 - Failing set_sequencer() / seq.start(null)
Figure 4 - p_sequencer handle in user sequence classes by calling the `uvm_declare_p_sequencer macro
The UVM base classes do not define a p_sequencer handle. A user could declare a
p_sequencer handle manually, but the p_sequencer handle is declared most often by calling the
`uvm_declare_p_sequencer macro.
If a p_sequencer handle is not created by the user, any attempt to start a sequence on the
p_sequencer handle, or to reference the p_sequencer handle will fail with a null pointer error
message.
The `uvm_declare_p_sequencer(SEQUENCER) macro is defined as shown in Example 13. This
`uvm_declare_p_sequencer macro definition can be found in the
src/macros/uvm_sequence_defines.svh base class file:
`define uvm_declare_p_sequencer(SEQUENCER) \
SEQUENCER p_sequencer;\
virtual function void m_set_p_sequencer();\
super.m_set_p_sequencer(); \
if( !$cast(p_sequencer, m_sequencer)) \
`uvm_fatal("DCLPSQ", \
$sformatf("%m %s Error casting p_sequencer, …",
get_full_name())) \
endfunction
Example 13 - UVM source code for the uvm_declare_p_sequencer macro definition
The purpose of this macro is to declare and qualify a consistent p_sequencer handle of the user-
specified SEQUENCER TYPE that is passed to the macro as an input argument:
SEQUENCER p_sequencer;
Example 15 - p-sequencer handle declaration made by the `uvm_declare_p_sequencer
After declaring the p_sequencer handle (initially null), the macro calls the
m_set_p_sequencer() method to cast the current sequence's m_sequencer handle to the newly
declared p_sequencer handle to ensure that the p_sequencer handle is properly vetted and set
to exactly match the active m_sequencer handle.
Why cast m_sequencer to p_sequencer? Why not just use the m_sequencer handle directly?
Technically, there is no need to create the p_sequencer handle if the verification engineer properly
uses the m_sequencer handle and never makes a mistake. Unfortunately, erroneous handle usage
leads to runtime null-handle reference errors that abort the simulation and can be difficult to debug.
Part of the action performed by the m_set_p_sequencer() cast operation is to trap any illegal
m_sequencer/p_sequencer assignments and report a consistent error message to help identify
the problematic p_sequencer type declaration.
6. sequence.start(null) example
In multiple sections, we mentioned that a sequence could be started on null under the right
conditions. In this section, we show how to properly setup sequences so that a top-level test can
start a top-level sequence on null.
The verification academy description of virtual sequences [3] uses this basic technique. This
technique is not recommended because it requires a test_base class to create an init_seq()
method that uses fixed, constant paths to sequencers, which is not portable, and often missed when
modifying the testbench environment.
6.1.1 seq_base
The seq_base class shown in Example 16 has declared a sequencer handle name SQR. All user
sequences will extend from this seq_base class and inherit the SQR handle, which initially is not set
and is null.
tb_sequencer SQR;
6.1.2 sequence
The write_read sequence shown in Example 17 will start the write (wseq) and read (rseq)
sequences on the inherited SQR handle.
task body;
write_sequence wseq = write_sequence::type_id::create("wseq");
read_sequence rseq = read_sequence::type_id::create("rseq");
//----------------------------------------------
repeat (cnt) begin
wseq.start(SQR);
rseq.start(SQR);
end
endtask
endclass
Example 17 - seq.start(null) – sequence extended from seq_base example
Each sequence that extends the seq_base class shown in Example 16 will inherit the
tb_sequencer SQR handle. But where is this SQR handle being set?
6.1.3 test_base
The test_base class shown in Example 18 defines an init_seq() method that takes as an
input, a sequencer handle. The init_seq() method then sets the SQR handle to e.agnt.sqr
env e;
6.1.4 test
The test1 class shown in Example 19 extends the test_base class from Example 18, and
calls the init_seq() method on the write_read seq handle, which will set the SEQ handle in
all derivative sequences extended from the seq_base class to the sequencer located at
e.agnt.sqr.
Now all subsequences started on the SQR handle will run on the e.agnt.sqr tb_sequencer.
After calling init_seq(seq), test1 calls seq.start(null), which sets the m_sequencer
handle in the write_read sequence to null, but the write_read sequence executes its
subsequences on the SQR-handle and not on the m_sequencer (null) handle.
The virtual sequencer technique needs to access subsequencer handles from a central location.
UVM stored variables and handles are typically kept in a config object, but sequences cannot be
started on a config object, so the subsequencer handles are stored in a sequencer-type container;
the virtual sequencer, as shown in Example 20.
class vsequencer extends uvm_sequencer;
`uvm_component_utils(vsequencer)
a_sequencer a1_sqr;
a_sequencer a2_sqr;
b_sequencer b_sqr;
c_sequencer c_sqr;
Using this technique, virtual sequences are started on the virtual sequencer, which gives them
access to the stored subsequencer handles. In essence, the virtual sequencer takes the place of a
config object to hold accessible subsequencer handles that can be accessed from a single location.
In this example, there are two sub-environments coded as shown in Example 21 and Example 22.
The sub-environments include two agents each and both include their own copy of an a_agent
component.
Figure 6 - env_top block diagram – connect_phase() used to copy subsequencer handles to vsequncer handles
The env_top class is responsible for building the env1, env2 and vsequencer components. After
the components are all created during the build_phase(), the env_top uses the
connect_phase() to copy the component subsequencer handles to the subsequencer handles
declared in the vsequencer, as shown in Example 23.
env1 e1;
env2 e2;
vsequencer vsqr;
The environments, all testbench subcomponents, and the virtual sequencer make up the structural
portions of the virtual sequencer environment. Now the question becomes, how to pass the
subsequencer handles to the virtual sequences themselves?
This is where the `uvm_declare_p_sequencer macro and p_sequencer handles are used.
class vseq_base extends uvm_sequence #(uvm_sequence_item);
`uvm_object_utils(vseq_base)
`uvm_declare_p_sequencer(vsequencer)
a_sequencer A1;
a_sequencer A2;
b_sequencer B;
c_sequencer C;
task body;
A1 = p_sequencer.a1_sqr;
A2 = p_sequencer.a2_sqr;
B = p_sequencer.b_sqr;
C = p_sequencer.c_sqr;
endtask
endclass
Example 24 - vseq_base (virtual sequence base) class using p_sequencer handles
In Example 24, the subsequencer handles are declared. Declaring subsequencer handles is easy!
Now how are the handles properly set to point to the actual subsequencers? Since virtual sequences
will be started on this virtual sequencer, the virtual sequence base class will have an m_sequencer
handle that points to the e_top.vsqr. Using the `uvm_declare_p_sequencer macro, the
vseq_base class has declared vsequencer p_sequencer and has cast m_sequencer to
p_sequencer so that the p_sequencer handle now points to the same vsequencer as the
m_sequencer handle.
The body() task of the vseq_base class, now uses the p_sequencer handle to copy the stored
subsequencer handles to the locally declare subsequencer handles. That answers the question,
"how are the handles properly set so that they point to the actual subsequencers?"
task body();
a_seq a = a_seq::type_id::create("a");
b_seq b = b_seq::type_id::create("b");
a_seq a2 = a_seq::type_id::create("a2");
super.body();
a.start(A1);
fork
b.start(B);
a2.start(A2);
join
a.start(A1);
endtask
endclass
Example 25 - Virtual sequence example #1 extended from vseq_base class (vseq_A1_B_A2_A1)
task body();
a_seq a = a_seq::type_id::create("a");
b_seq b = b_seq::type_id::create("b");
c_seq c = c_seq::type_id::create("c");
super.body();
a.start(A1);
fork
b.start(B);
c.start(C);
join
endtask
endclass
Example 26 - Virtual sequence example #2 extended from vseq_base class (vseq_A1_B_C)
The vseq_base class had done the hard work of retrieving the actual subsequencer handles. Now
the virtual sequences1 can extend the vseq_base class, inherit the declared subsequencer
handles, and call the super.body() method to set the inherited handles in each virtual sequence
class, as shown in Example 25 and Example 26.
This is a common technique that shows that a sequence base class can use the
`uvm_declare_p_sequencer macro and p_sequencer handle to retrieve required testbench
component hierarchy information to be used for coordinated virtual sequence testing.
All of these p_sequencer coding gymnastics are required because handles stored as resources in
the uvm_resource_pool cannot be retrieved directly into the virtual sequence base class because
the uvm_config_db API requires get commands to be called using a two-argument cntxt
(component class handle), "inst_name" (string) that when combined must point to an actual
component in the testbench hierarchy, and sequences to do not have testbench hierarchy-paths. Is
there a better way?
1
"virtual sequence: A conceptual term for a sequence that controls the execution of sequences on other
sequencers" [4]. A virtual sequence coordinates the execution of other sequences on multiple subsequencers.
10. Acknowledgements
I would like to thank my friend and colleague Jeff Montesano for his review and valuable feedback
on both this paper and the accompanying presentation slides.
11. References
[1] Clifford E. Cummings, Janick Bergeron, "Using UVM Virtual Sequencers & Virtual Sequences,"
DVCon 2016 Proceedings, also available at:
www.sunburst-design.com/papers/CummingsDVCon2016_Vsequencers.pdf
[2] Clifford E. Cummings, Mark Glasser, Heath Chambers, "The Untapped Power of UVM Resources
and Why Engineers Should Use the uvm_resource_db API," DVCon 2023 Proceedings, also
available at: www.sunburst-design.com/papers/CummingsDVCon2023_uvm_resource_db_API.pdf
[3] Tom Fitzpatrick, "Advanced UVM Layered Sequences," Verification Academy –
https://verificationacademy.com
[4] Universal Verification Methodology (UVM) 1.2 Class Reference – June 2014
Cliff Cummings is Vice President of Training at Paradigm Works and Founder of Sunburst Design.
Paradigm Works and Sunburst Design merged in February of 2020. Mr. Cummings has 42 years of
ASIC, FPGA and system design experience and 32 years of combined Verilog, SystemVerilog, UVM
verification, synthesis, and methodology training experience.
Mr. Cummings has presented more than 100 SystemVerilog seminars and training classes in the
past 20 years and was the featured speaker at the world-wide SystemVerilog NOW! seminars.
Mr. Cummings participated on every IEEE & Accellera SystemVerilog, SystemVerilog Synthesis,
SystemVerilog committee from 1994-2012, and has presented more than 50 papers on
SystemVerilog & SystemVerilog related design, synthesis, and verification techniques.
Mr. Cummings holds a BSEE from Brigham Young University and an MSEE from Oregon State
University.
Email address: [email protected]
Last Updated: April 2024