M_sequencer Vs P_sequencer

M_sequencer:

When a sequence is started, it is always associated with a sequencer on which it is started. The m_sequencer handle contains the reference to the sequencer on which sequence is running. Using this handle, the sequence can access any information and other resource handles in the UVM component hierarchy. 

P_sequencer and how it is different from m_sequencer: 

A UVM sequence is an object with limited life time unlike a sequencer or a driver or a monitor which are UVM components and are present throughout simulation time. So if you need to access any members or handles from the testbench hierarchy (component hierarchy), the sequence would need a handle to the sequencer on which it is running. m_sequencer is a handle of type uvm_sequencer_base which is available by-default in a uvm_sequence. However, to access the real sequencer on which sequence is running, we need to typecast the m_sequencer to the real sequencers, which is generally called p_sequencer (though you could really use any name and not just p_sequencer). 

Here is a simple example where a sequence wants to access a handle to a clock monitor component which is available as a handle in the sequencer.

class test_sequence_c extends uvm_sequence;
  test_sequencer_c p_sequencer;
  clock_monitor_c my_clock_monitor;
  
  task pre_body();
    if(!$cast(p_sequencer, m_sequencer)) begin
      `uvm_fatal(“Sequencer Type Mismatch:”, ” Wrong Sequencer”);
    end
    
    my_clock_monitor = p_sequencer.clk_monitor;
  endtask
endclass

class test_Sequencer_c extends uvm_sequencer;
  clock_monitor_c clk_monitor;
endclass