Understand the Difference between UVM_SEQ_ARB_FIFO and UVM_SEQ_ARB_STRICT_FIFO

I am trying to understand the difference between UVM_SEQ_ARB_FIFO and UVM_SEQ_ARB_STRICT_FIFO.

My understanding is:

  • UVM_SEQ_ARB_FIFO grants requests in the order they arrive and ignores sequence priority.
  • UVM_SEQ_ARB_STRICT_FIFO first considers priority and then uses FIFO ordering among sequences that have the same priority.

To verify this, I created three sequence with different priorities:

task run_phase(uvm_phase phase);
  phase.raise_objection(this);

  seqa = seqA::type_id::create("seqa");
  seqb = seqB::type_id::create("seqb");
  seqc = seqC::type_id::create("seqc");

  envo.agt.seqr.set_arbitration(UVM_SEQ_ARB_STRICT_FIFO);

  seqa.set_priority(200);
  seqb.set_priority(300);
  seqc.set_priority(300);

  fork
    seqa.start(envo.agt.seqr);
    seqb.start(envo.agt.seqr);
    seqc.start(envo.agt.seqr);
  join

  phase.drop_objection(this);
endtask

Each sequence repeatedly does:

repeat (5) begin
  #2;
  wait_for_grant();
  `uvm_info(get_type_name(), "Grant received", UVM_HIGH)

  assert(req.randomize());
  send_request(req);

  wait_for_item_done();
end

The driver simply performs:

forever begin
  seq_item_port.get_next_item(req);
  #5;
  seq_item_port.item_done(req);
end

I expected seqB and seqC (priority 300) to be serviced before seqA (priority 200) when using UVM_SEQ_ARB_STRICT_FIFO.

However, the grant order appears similar to UVM_SEQ_ARB_FIFO, and I do not see the lower-priority sequence being deferred.

Here is the result for both cases:

Am I misunderstanding how UVM_SEQ_ARB_STRICT_FIFO works? Is there something about my test setup that prevents priority-based arbitration from becoming visible?

It would have helped to show a complete runnable example. This works for me

`include "uvm_macros.svh"
import uvm_pkg::*;

class my_item extends uvm_sequence_item;
 `uvm_object_utils(my_item)
  rand bit [7:0] data;
  function new(string name="my_item");
    super.new(name);
  endfunction
endclass
class my_driver extends uvm_driver #(my_item);
  `uvm_component_utils(my_driver)
  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction
  task run_phase(uvm_phase phase);
    forever begin
      seq_item_port.get_next_item(req);
      `uvm_info("DRIVER",
        $sformatf("Got item data=0x%0h from sequence=%s",
                  req.data,
                  req.get_sequencer().get_name()),
        UVM_MEDIUM)
      #5ns;
      seq_item_port.item_done();
    end
  endtask
endclass

class my_seq extends uvm_sequence #(my_item);
  rand bit [7:0] base_data;
  `uvm_object_utils(my_seq)
  function new(string name="my_seq");
    super.new(name);
  endfunction

  task body();
    #10ns;
    repeat (5) begin
      req = my_item::type_id::create("req");
      start_item(req);
      assert(req.randomize() with {
        data inside {[base_data : base_data+15]};
      });
      finish_item(req);
      `uvm_info(get_name(),
        $sformatf("Sent data=0x%0h priority=%0d",
                  req.data,
                  get_priority()),
        UVM_MEDIUM)
    end
  endtask
endclass
class my_agent extends uvm_agent;
  `uvm_component_utils(my_agent)
  uvm_sequencer #(my_item) seqr;
  my_driver                 drv;
  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction
  function void build_phase(uvm_phase phase);
    seqr = new("seqr", this);
    drv  = my_driver   ::type_id::create("drv",  this);
  endfunction
  function void connect_phase(uvm_phase phase);
    super.connect_phase(phase);
    drv.seq_item_port.connect(seqr.seq_item_export);
  endfunction
endclass
class seq_arb_test extends uvm_test;
  `uvm_component_utils(seq_arb_test)
  my_agent agent;
  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction
  function void build_phase(uvm_phase phase);
    agent = my_agent::type_id::create("agent", this);
  endfunction
  task run_phase(uvm_phase phase);
    my_seq sa,sb,sc;
    phase.raise_objection(this);
    sa = my_seq::type_id::create("sa");
    sb = my_seq::type_id::create("sb");
    sc = my_seq::type_id::create("sc");
    sa.base_data = 8'h10;
    sb.base_data = 8'h40;
    sc.base_data = 8'h80;
    agent.seqr.set_arbitration(UVM_SEQ_ARB_STRICT_FIFO);
    fork
      sa.start(agent.seqr, null, 200);
      sb.start(agent.seqr, null, 300);
      sc.start(agent.seqr, null, 300);
    join

    phase.drop_objection(this);

  endtask

endclass

module top;
  initial begin
    run_test("seq_arb_test");
  end
endmodule

While debugging my arbitration test, I noticed that the issue seems to be related to the set_priority() function.

When I set the priorities using set_priority() before starting the sequences, the priorities appear to revert to the default value of 100 once the sequences are started.

seqa.set_priority(200);
seqb.set_priority(300);
seqc.set_priority(300);
`uvm_info("TEST",$sformatf("BEFORE FORK...JOIN PRI : A = %0d B = %0d C = %0d",seqa.get_priority(),seqb.get_priority(),seqc.get_priority()),UVM_HIGH);
fork
  seqa.start(envo.agt.seqr);
  seqb.start(envo.agt.seqr);
  seqc.start(envo.agt.seqr);
join
phase.drop_objection(this);
`uvm_info("TEST",$sformatf("AFTER FORK...JOIN PRI : A = %0d B = %0d C = %0d",seqa.get_priority(),seqb.get_priority(),seqc.get_priority()),UVM_HIGH);

Output is like below:

From the log, I observe that the priorities become 100 after the sequences are started.

However, when I specify the priority directly in the start() method, the priorities are retained correctly:

seqa.set_priority(200);
seqb.set_priority(300);
seqc.set_priority(300);
`uvm_info("TEST",$sformatf("BEFORE FORK...JOIN PRI : A = %0d B = %0d C = %0d",seqa.get_priority(),seqb.get_priority(),seqc.get_priority()),UVM_HIGH);
fork
  seqa.start(envo.agt.seqr,null,400);
  seqb.start(envo.agt.seqr,null,500);
  seqc.start(envo.agt.seqr,null,600);
join
phase.drop_objection(this);
`uvm_info("TEST",$sformatf("AFTER FORK...JOIN PRI : A = %0d B = %0d C = %0d",seqa.get_priority(),seqb.get_priority(),seqc.get_priority()),UVM_HIGH);

The output is like below:

In this case, the priorities reported after start() are 400, 500, and 600 respectively, and the arbitration behavior matches those priorities.

My question is:

What is the intended use case of set_priority() if the priority passed to start() overrides it? Under what circumstances should set_priority() be used, and when is it preferable to provide the priority directly in the start() call?

Use set_priority() to adjust the priority after a sequence has started. This function primarily affects starting child sequences or sequence_items where the current sequence is passed as the parent.