Leaf level sequencer's stop_sequences() is terminating virtual sequence which is running on virtual sequencer

Hi,
I came across problem where stop_sequences function of leaf level sequencer is terminating virtual sequence which is running on virtual sequencer.
Let me more elaborate on this.

Here ,

  • my_seqr - leaf level sequencer
  • my_vir_seqr - virtual sequencer
  • my_vir_seq - virtual sequence
  • my_tr - leaf level transaction

Where my_tr transaction is sent from virtual sequence (i.e. my_vir_seq) by using `uvm_do_on() macro to leaf level sequencer and it has been driven successfully and driver has done item_done, then after certain delay , reset is being applied and leaf level sequencer (my_seqr) is calling stop_sequences function to kill sequence if any sequence is running on it. But at time of reset , there is no sequence running on leaf level sequencer. Only running sequence is virtual sequence which is running of virtual sequencer (my_vir_seqr) and it is not generating any kind of traffic on my_seqr (leaf level sequencer), though stop_sequences() is killing my_vir_seq.

Snippet:
Transaction Class:

class my_tr extends uvm_transaction;
  function new (string name = "my_tr");
    super.new(name);
  endfunction : new

  `uvm_object_utils(my_tr)
endclass : my_tr

Leaf level Sequencer:

class my_seqr extends uvm_sequencer#(my_tr);

  virtual my_if my_vif;

  function new (string name = "my_seqr" , uvm_component parent = null);
    super.new(name,parent);
  endfunction : new 

  function void build_phase(uvm_phase phase);
   // get virtual interface by using uvm_config_db
  endfunction : build_phase

  task run_phase (uvm_phase phase);
    forever
    begin
      wait (my_vif.reset == 0);// wait for reset to be asserted.
      stop_sequences();
      wait (my_vif.reset == 1);// wait for reset to be de-asserted
    end
  endtask : run_phase

  `uvm_component_utils(my_seqr)

endclass : my_seqr

Leaf level driver:

class my_drv extends uvm_driver#(my_tr);

  // ------- --- build_phase
  
  // ----- ---- connect phase

  task run_phase(uvm_phase phase);
    forever
    begin
      seq_item_port.get_next_item(req);
      #10; // some delay
      seq_item_port.item_done();
    end
  endtask : run_phase

endclass : my_drv

Virtual Sequencer (top level sequencer):

class my_vir_seqr extends uvm_sequencer;

  my_seqr my_seqr_h; // handle of leaf level sequencer

  function new (string name = "my_vir_seqr",uvm_component parent = null);
    super.new(name,parent);
  endfunction : new

  `uvm_component_utils(my_vir_seqr)

endclass : my_vir_seqr

Virtual sequence:

class my_vir_seq extends uvm_sequence;

  `uvm_object_utils(my_vir_seq)
  `uvm_declare_p_sequencer(my_vir_seq)
   
   process p_seq;

  function new (string name = "my_vir_seq" );
    super.new(name);
  endfunction : new 

  task body();
    my_tr tr;
    p_seq = process :: self();
    `uvm_info(get_name(),"sequence is running",UVM_LOW)
    `uvm_do_on( tr, p_sequencer.my_seqr_h); // send transaction on leaf level sequencer
      #1000;  // Added delay to execute this sequence longer time
    `uvm_info(get_name(),"sequence is completed",UVM_LOW)
  endtask : body

endclass : my_vir_seq

UVM Test:

class my_test extends uvm_test;

  task run_phase (uvm_phase phase);
    my_vir_seq  vir_seq = my_vir_seq :: type_id :: create("vir_seq");
    // raise objection Here
    //-------
    // start sequence on virtual sequencer
    vir_seq.start(env.my_vir_sqr_h); 
    `uvm_info("PROCESS_STATE",$sformatf("%0s",vir_seq.p_seq.state()),UVM_LOW)

    // -------
    // drop objection here
  endtask : run_phase
endclass : my_test

TOP file:

module top;

bit reset;

// interface declaration and connection

initial
begin
  reset = 1;
  #80; // adding delay
  reset = 0 ; // reset is applied
  #10; 
  reset = 1;
end

initial
  run_test();

endmodule : top

OUTPUT:

UVM_INFO@80[PROCESS_STATE]: KILLED

I checked UVM code of uvm_sequencer and found that whenever **start_item** is being called (through `uvm_do_on()) that time , sequencer is storing parent sequence pointer inside reg_sequences[] associative array, Thus here my_vir_seq's pointer would be stored inside this array. But at time of **finish_item**, UVM is not un-registering that sequence pointer inside this array.

Thus I needed to unregister my virtual sequence pointer explicitly by calling below function after `uvm_do_on()

// m_unregister_sequence
 // ---------------------
  
   function void uvm_sequencer_base::m_unregister_sequence(int sequence_id);
     if (!reg_sequences.exists(sequence_id))
       return;
     reg_sequences.delete(sequence_id);
   endfunction


So i dont understand why finish_item is not un-registering sequence pointer of parent sequence. Is there aBUG in UVM?
Should i consider that we should not generate/send leaf level transaction by using virtual sequence?

In reply to kerulmodi:

Hi Kerul,

Following code is implemented inside leaf level sequencer’s run phase.

task run_phase (uvm_phase phase);
    forever
    begin
      wait (my_vif.reset == 0);// wait for reset to be asserted.
      stop_sequences();
      wait (my_vif.reset == 1);// wait for reset to be de-asserted
    end
  endtask : run_phase

Above logic can be easily taken care from test or virtual sequence.

Now coming to your question.

As per UVM reference manual,
stop_sequences method is exactly doing the thing for what it is meant for -

stop_sequences ::
Tells the sequencer to kill all sequences and child sequences currently operating on the
sequencer, and remove all requests, locks and responses that are currently queued.

In reply to kerulmodi:

If you are following the UVM User Manual you do not have to take care for register/unregister any object.
The key weakness I see in you code is you are using signals in a transaction level object (your sequencer). Furthermore you do not need any p_sequencer declaration.
Additionally it is unclear for me what are yxou doing with your process object.

In reply to chr_sue:

In reply to kerulmodi:
If you are following the UVM User Manual you do not have to take care for register/unregister any object. Furthermore you do not need any p_sequencer declaration.
Additionally it is unclear for me what are yxou doing with your process object.

I completely understand that we should not take care about register/unregister sequence inside sequencer. I have used this as patch to overcome sequence termination issue.

Process (p_seq) is just used to indicate that virtual sequence body method has been killed. you can see output in quote.

To access leaf level sequencer (p_sequencer.my_seqr_h), i had to declare virtual sequencer as p_sequencer.

In reply to DigvijayS:

stop_sequences ::
Tells the sequencer to kill all sequences and child sequences currently operating on the
sequencer, and remove all requests, locks and responses that are currently queued.

As you have used word “currently” , let me clear you that at time of reset(when stop_sequences()has been called) , no sequence is running on leaf level sequencer (my_seqr).
Thus it should not terminate any sequence.

In reply to kerulmodi:

1).Actual sequence gets executed on leaf sequencer using below statement
`uvm_do_on( tr, p_sequencer.my_seqr_h);

2).Inside leaf sequencer below statement gets executed at @80 time-stamp as inside
top module (reset=0) has been set after delay of #80 time-unit.

stop_sequences();

initial
begin
  reset = 1;
  #80; // adding delay
  reset = 0 ; // reset is applied
  #10; 
  reset = 1;
end

So,your sequence has been already started and executes for 80 time-units before stop_sequences() function is being called.

Once this function is called “currently” running sequence on leaf sequencer gets killed.

In reply to DigvijayS:

at time of reset , `uvm_do_on is already executed successfully so there is no transaction or sequence is going on into leaf level sequencer.

In reply to kerulmodi:

Hi kerulmodi,
I am having exactly the same problem. I do a reset and my virtual sequence is killed. I have seen that commenting stop_sequences solves the problem. Did you find any other solution?
Kind regards,
Jonathan

In reply to kerulmodi:

Hi Kerulmodi,
I think i have found the problem and a workaround to avoid commenting the stop_sequences function.

As already described above, stop_sequences will kill the sending sequence. However, as you noticed, it is not true that it will kill the sequence that is currently active in the sequencer. The stop_sequence function has a sequence registration mechanism. It will register the sequence used for a sequencer once the “uvm_send” has been sent from the sequence. No matter if the “uvm_send” and the transaction has already been processed and the sequencer is not doing anything, if the sequence did “uvm_send” over a sequencer, this sequence will be registered.

I understand, as you did, that this is not 100% OK but UVM do so. In my opinion, UVM should un-register that sequence once the driver of the sequencer has all objections dropped or is waiting for an item of the sequence. In other words, it should not kill an idle sequence.

OK. Now to the solutions.
I guess that you want to use stop_sequence to kill all unfinished sequence items from the sequence when a reset is monitored. This is the best reset handling approach i have seen in UVM, see this https://www.embedded.com/print/4396934 for more information of this approach. Therefore, i assume that you want to keep your stop_sequence() function in your code.

The workaround is simple, you just avoid using any UVM_DO or any “uvm_send” inside your virtual sequence. I explain. If you want to send your stimuli, then you need to create a sub-sequence (inside your virtual sequence) that wrap all the stimuli (“uvm_send” to any sequencer that uses stop_sequence). Doing that the registered and killed sequence (by stop_sequences) will be the sub-sequence and not your virtual sequence. (For completation, if you use the UVM register model functions (read/write/mirror/update) each time you write to a register of the UVM, the register model creates a subsequence for you so you don’t have to worry about wrapping those read/write/mirror/update register logic functions, they do already right)

For your code, the following need to be done (approximately):

Go from your current Virtual sequence:


class my_vir_seq extends uvm_sequence;
 
  `uvm_object_utils(my_vir_seq)
  `uvm_declare_p_sequencer(my_vir_seq)
 
   process p_seq;
 
  function new (string name = "my_vir_seq" );
    super.new(name);
  endfunction : new 
 
  task body();
    my_tr tr;
    p_seq = process :: self();
    `uvm_info(get_name(),"sequence is running",UVM_LOW)
    `uvm_do_on( tr, p_sequencer.my_seqr_h); // send transaction on leaf level sequencer
      #1000;  // Added delay to execute this sequence longer time
    `uvm_info(get_name(),"sequence is completed",UVM_LOW)
  endtask : body
 
endclass : my_vir_seq

To the new Virtual sequence:



typedef class my_sub_vir_seq;//forward declaration to be able to use this type inside my_vir_seq 
class my_vir_seq extends uvm_sequence;
 
  `uvm_object_utils(my_vir_seq)
  `uvm_declare_p_sequencer(my_vir_seq)
 
   process p_seq;
 
  function new (string name = "my_vir_seq" );
    super.new(name);
  endfunction : new 
 
  task body();    
    p_seq = process :: self(); 
    my_sub_vir_seq   this_seq_has_all_stimuli_ready2bekilled;
    this_seq_has_all_stimuli_ready2bekilled =  my_sub_vir_seq::type_id::create("this_seq_has_all_stimuli_ready2bekilled");
    this_seq_has_all_stimuli_ready2bekilled.start(p_sequencer.my_seqr_h);//the subsequence is started 
  endtask : body 
endclass : my_vir_seq

//sub sequence with stimuli definition that is used inside the virtual sequence
class my_sub_vir_seq extends uvm_sequence; 
  `uvm_object_utils(my_sub_vir_seq )
  `uvm_declare_p_sequencer(my_vir_seq)
 
   process p_seq;
 
  function new (string name = "my_sub_vir_seq " );
    super.new(name);
  endfunction : new 
 
  task body();
    my_tr tr;    
    `uvm_info(get_name(),"sequence is running",UVM_LOW)
    `uvm_do_on( tr, p_sequencer.my_seqr_h); // send transaction on leaf level sequencer
      #1000;  // Added delay to execute this sequence longer time
    `uvm_info(get_name(),"sequence is completed",UVM_LOW)
  endtask : body
 
endclass : my_sub_vir_seq 


Of course, you can have in your virtual sequence the reset handling. In other words, you can for example detect a reset in your virtual sequence and restart your sub-sequence (the one with the stimuli) after reset or other stimuli.

I hope that this helps.
Best regards,
Jonathan

In reply to Jonathan_Alvarez:

I appreciate that you went so deep for this problem and found an approach for solution.
Whatever you understood is correct and that is aligned with my understanding.

Finally , i had to create another wrapper sequence which can send transactions to leaf level sequencer (legacy flow) to avoid this problem as you suggested.

Thanks again for sharing reset handling mechanism information.

Regards,
Kerul Modi