One of the most confusing UVM stuff is about m_sequencer and p_sequencer and the difference between the two. In reality, its just a game of polymorphism.
Referring to some forum answer,
m_sequencer is a generic sequencer pointer of type uvm_sequencer_base. It will always exist for a uvm_sequence and is initialized when the sequence is started.
p_sequencer is a type specific sequencer pointer, created by registering the sequence to a sequencer using the
`uvm_declare_p_sequencer macros. Being type specific, you will be able to access anything added to the sequencer (i.e. pointers to other sequencers, etc.).
p_sequencer will not exist if the
`uvm_declare_p_sequencer macros isn’t used.
Lets have a look at the UVM sequence and sequencer hierarchy:
As we can see sequence is ultimately extended from
uvm_sequence_item and in turn from
uvm_object. While sequencer has a grandparent of
uvm_component. This is one of the reason why sequencer has phases and sequence does not. But that’s an entire different part of discussion.
What we are interested in is that
m_sequencer is a handle of type
p_sequencer is a handle of type
user_defined_sequencer is a grandchild of
When we start a sequence, we provide an object handle of our
user_defined_sequencer. Internally, in start method, this child class object is assigned into parent handle called
m_sequencer. So, a static casting occurs such that a parent class handle points to child class object (
m_sequencer = user_defined_sequencer_object).
Now, when referring to sequence, if a
p_sequencer is defined, the macro
`uvm_declare_p_sequencer expands to a function that declares a
user_defined_sequencer handle known as
p_sequencer. This function then casts the
m_sequencer (parent class handle) back to
p_sequencer (child class handle) using dynamic casting (
Referring to a simple code as below, the
class child extends base;
//... some other class
// Just like m_sequencer
// user created sequencer
// Just like p_sequencer
// user sequencer object in env/agent
child1 = new;
// internal in start method, m_sequencer = user_sequencer
b = child1;
// internal in declare_p_sequencer macro casting back from m_sequencer to p_sequencer
A more elaborate example is done at Mimic m_seqr p_seqr link.
Finally, referring to UVM source code, following snippets can give you an intuition.
virtual task start (uvm_sequencer_base sequencer,
uvm_sequence_base parent_sequence = null,
int this_priority = -1,
bit call_pre_post = 1);
// ... some other stuff
// ... some other stuff...
virtual function void set_sequencer(uvm_sequencer_base sequencer);
m_sequencer = sequencer;
virtual function void m_set_p_sequencer();
In user sequence:
`define uvm_declare_p_sequencer(SEQUENCER) \
virtual function void m_set_p_sequencer();\
if( !$cast(p_sequencer, m_sequencer)) \
$sformatf("%m %s Error casting p_sequencer, please verify that this sequence/sequence item is intended to execute on this type of sequencer", get_full_name())) \
user_sequencer is passed to start method which invokes
set_sequencer method of
uvm_sequence_item. This method in turn calls
m_set_p_sequencer (empty by default) method. If a
p_sequencer is declared during macro expansion, then
p_sequencer handle to
Thus, basically it is just a matter of static and dynamic casting between a base class and extended class. Hope this gives a basic idea about
p_sequencer help and avoiding confusion between the two.