This is probably the most used and still most confusing for me in UVM.
In simple words, p_sequencer is just the dynamic casting of m_sequencer.
In testcase whenever we call start method to run the sequence,
------------------------------------------------------------------------------------------
This start method is the source code of UVM class uvm_sequence_base:
------------------------------------------------------------------------------------------
Inside uvm_sequence_item:
------------------------------------------------------------------------------------------
virtual function void m_set_p_sequencer();
return;
endfunction
endfunction
In simple words, p_sequencer is just the dynamic casting of m_sequencer.
In testcase whenever we call start method to run the sequence,
------------------------------------------------------------------------------------------
class test extends uvm_test;
...
seq.start(sequencer);
...
endclass: test
------------------------------------------------------------------------------------------This start method is the source code of UVM class uvm_sequence_base:
------------------------------------------------------------------------------------------
virtual task start (uvm_sequencer_base sequencer,
uvm_sequence_base parent_sequence = null,
int this_priority = -1,
bit call_pre_post = 1);
...
set_sequencer(sequnecer);
...
endtask
------------------------------------------------------------------------------------------
Inside uvm_sequence_item:
------------------------------------------------------------------------------------------
virtual function void set_sequencer(uvm_sequencer_base sequencer);
m_sequencer = sequencer;
m_set_p_sequencer();
endfunctionvirtual function void m_set_p_sequencer();
return;
endfunction
------------------------------------------------------------------------------------------
So, as we can see whenever we call start method of sequence it's m_sequencer(a variable of uvm_sequencer) is getting set. This variable stores any object derived from uvm_sequencer. And it can access all the properties of uvm_sequencer class.
Now what if user wants to access the property of it's own user defined sequencer, for that we have to have p_sequencer which points to the sequencer of our environment.
p_sequencer only exists if we call uvm_decalre_p_sequencer macro, which is defined inside the UVM source code:
------------------------------------------------------------------------------------------
`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, please verify that this sequence/sequence item is intended to execute on this type of sequencer", get_full_name())) \ |
------------------------------------------------------------------------------------------
This macro is called inside the user defined sequence and the sequencer passed when we call start method (from the testcase) is casted to p_sequencer.
As we can see in the above code, p_sequencer is just the dynamic casting of m_sequencer with the user defined type class. So ultimately both points to the same thing with different type of class.
Properties of user defined sequencer can be accessed through p_sequencer and that is the reason we required p_sequencer.