When multiple sequences run on the same sequencer, they compete for a single driver. The sequencer decides which sequence's item goes to the driver next through a process called sequence arbitration, and the rule it follows is set by its arbitration mode.

9 min read | Intermediate Level

What You'll Learn

  • How a sequencer arbitrates between sequences that request the driver
  • The six UVM arbitration modes and when priorities matter
  • Set the arbitration mode and start sequences with a priority
  • Write a custom arbitration scheme with UVM_SEQ_ARB_USER

How Sequence Arbitration Works

Arbitration does not happen when a sequence starts. It happens every time a sequence calls start_item() to send an item. That call places a request in the sequencer's arbitration queue and blocks until the request is granted.

When the driver calls get_next_item(), the sequencer looks at all pending requests and grants exactly one, based on the current mode. The granted sequence then completes finish_item(), and the driver's item_done() frees the driver for the next grant.

A sequence that never sends items, such as a virtual sequence that only starts other sequences, never enters the queue. If several such sequences start on one sequencer, their bodies simply run in parallel.

UVM sequencer arbitration queue granting sequence requests to the driver

Syntax


// Set or read the mode on the sequencer (before or while sequences run)
m_seqr.set_arbitration (UVM_SEQ_ARB_STRICT_FIFO);
cur_mode = m_seqr.get_arbitration ();          // Returns UVM_SEQ_ARB_TYPE

// Give a sequence a priority when it is started
m_seq.start (m_seqr, .this_priority(200));     // Higher number = higher priority
  • set_arbitration() - selects one of the six UVM_SEQ_ARB_TYPE values (default UVM_SEQ_ARB_FIFO)
  • this_priority - optional argument of start(). The default of -1 means "use the parent sequence's priority", or 100 for a sequence started with no parent
ModeUses priority?How the next request is chosen
UVM_SEQ_ARB_FIFONoOldest request first (default)
UVM_SEQ_ARB_RANDOMNoAny pending request, chosen at random
UVM_SEQ_ARB_STRICT_FIFOYesHighest priority first; ties in request order
UVM_SEQ_ARB_STRICT_RANDOMYesHighest priority first; ties chosen at random
UVM_SEQ_ARB_WEIGHTEDYesRandom, with each request's chance proportional to its priority
UVM_SEQ_ARB_USERYour choiceYour user_priority_arbitration() function decides

Example

To compare the modes, the examples below use one small testbench: an environment with a sequencer and a driver, and six instances of the same sequence competing for that driver.

1. Data class

The class base_pkt is the data object sent to the driver, which would convert it into pin activity at the DUT interface. It holds just an address, which is enough to show which sequence sent each packet.


class base_pkt extends uvm_sequence_item;
  `uvm_object_utils(base_pkt)
  function new(string name = "base_pkt");
    super.new(name);
  endfunction
  
  rand bit[7:0] addr;
endclass

2. Driver

The driver accepts a base_pkt, takes 10 time units to "drive" it, and then calls item_done(). Only then can the sequencer grant the next request.


class base_driver extends uvm_driver #(base_pkt);
  `uvm_component_utils(base_driver)
  function new(string name = "base_driver", uvm_component parent=null);
    super.new(name, parent);
  endfunction
  
  // Assume that the packet is driven across to the DUT within 10ns
  // after which the driver calls item_done()
  virtual task run_phase(uvm_phase phase);
    base_pkt m_pkt;
    forever begin
      seq_item_port.get_next_item(m_pkt);
      #10 `uvm_info ("DRV", $sformatf("Drive packet with addr=0x%0h", m_pkt.addr), UVM_LOW)
      seq_item_port.item_done();
    end
  endtask
endclass

3. Environment

The environment creates the sequencer and driver and connects them.


class base_env extends uvm_env;
  `uvm_component_utils(base_env)
  function new(string name="base_env", uvm_component parent=null);
    super.new(name, parent);
  endfunction
  
  base_driver  		 		m_drv;
  uvm_sequencer#(base_pkt)  m_seqr;
  
  virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    m_drv = base_driver::type_id::create("m_drv", this);
    m_seqr = uvm_sequencer#(base_pkt)::type_id::create("m_seqr", this);
  endfunction
  
  virtual function void connect_phase(uvm_phase phase);
    super.connect_phase(phase);
    m_drv.seq_item_port.connect(m_seqr.seq_item_export);
  endfunction
endclass

4. Sequence

The sequence base_seq creates one packet and sends it. It blocks in start_item() until its request is granted, and in finish_item() until the driver calls item_done().

Because several instances run on the same sequencer, the variable seq_num identifies each one. The packet address is set to seq_num, so the driver's log shows which sequence won each grant.


class base_seq extends uvm_sequence;
  `uvm_object_utils(base_seq)
  function new(string name="base_seq");
    super.new(name);
  endfunction
  
  int seq_num;
  
  virtual task body();
    base_pkt m_pkt = base_pkt::type_id::create("m_pkt");
    `uvm_info(get_name(), $sformatf("Started body"), UVM_LOW)
    start_item(m_pkt);
    m_pkt.randomize() with { addr == seq_num; };
    finish_item(m_pkt);
    #10 `uvm_info(get_name(), $sformatf("Ended body"), UVM_LOW)
  endtask
endclass

5. Test

The base test builds the environment and six sequences, m_seq_0 to m_seq_5. The function show_arb_cfg() prints the sequencer's current mode, so each log shows which mode was active.


class base_test extends uvm_test;
  `uvm_component_utils(base_test)
  function new(string name="base_test", uvm_component parent=null);
    super.new(name, parent);
  endfunction
  
  base_seq 		m_seq[6];
  base_env 		m_env;
  
  virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);
   foreach(m_seq[i]) begin
      m_seq[i] = base_seq::type_id::create($sformatf("m_seq_%0d", i));
      m_seq[i].seq_num = i;
    end
    m_env = base_env::type_id::create("m_env", this);    
  endfunction
  
  virtual function void show_arb_cfg();
	UVM_SEQ_ARB_TYPE  cur_arb;
        
    cur_arb = m_env.m_seqr.get_arbitration();
    `uvm_info("TEST", $sformatf("Seqr set to %s", cur_arb.name()), UVM_LOW)
  endfunction
endclass

In every log below, all sequences print "Started body" at time 0. That shows the bodies start together, and the competition only begins at start_item(). The order of the driver's "Drive packet" lines is the grant order.

UVM_SEQ_ARB_FIFO

This is the default mode. The sequencer grants requests strictly in the order they arrived and ignores priorities.

Without priority


class arb_fifo_test extends base_test;
  `uvm_component_utils(arb_fifo_test)
  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction
  
  virtual task run_phase(uvm_phase phase);
    super.run_phase(phase);
    phase.raise_objection(this);

    m_env.m_seqr.set_arbitration(UVM_SEQ_ARB_FIFO);
    show_arb_cfg();
    
    fork
      m_seq[0].start(m_env.m_seqr);
      m_seq[1].start(m_env.m_seqr);
      m_seq[2].start(m_env.m_seqr);
      m_seq[3].start(m_env.m_seqr);
    join
    phase.drop_objection(this);
  endtask
endclass
Output
UVM_INFO @ 0: reporter [RNTST] Running test arb_fifo_test...
UVM_INFO testbench.sv(90) @ 0: uvm_test_top [TEST] Seqr set to UVM_SEQ_ARB_FIFO
UVM_INFO testbench.sv(23) @ 0: uvm_test_top.m_env.m_seqr@@m_seq_0 [m_seq_0] Started body
UVM_INFO testbench.sv(23) @ 0: uvm_test_top.m_env.m_seqr@@m_seq_1 [m_seq_1] Started body
UVM_INFO testbench.sv(23) @ 0: uvm_test_top.m_env.m_seqr@@m_seq_2 [m_seq_2] Started body
UVM_INFO testbench.sv(23) @ 0: uvm_test_top.m_env.m_seqr@@m_seq_3 [m_seq_3] Started body
UVM_INFO testbench.sv(41) @ 10: uvm_test_top.m_env.m_drv [DRV] Drive packet with addr=0x0
UVM_INFO testbench.sv(27) @ 20: uvm_test_top.m_env.m_seqr@@m_seq_0 [m_seq_0] Ended body
UVM_INFO testbench.sv(41) @ 20: uvm_test_top.m_env.m_drv [DRV] Drive packet with addr=0x1
UVM_INFO testbench.sv(27) @ 30: uvm_test_top.m_env.m_seqr@@m_seq_1 [m_seq_1] Ended body
UVM_INFO testbench.sv(41) @ 30: uvm_test_top.m_env.m_drv [DRV] Drive packet with addr=0x2
UVM_INFO testbench.sv(27) @ 40: uvm_test_top.m_env.m_seqr@@m_seq_2 [m_seq_2] Ended body
UVM_INFO testbench.sv(41) @ 40: uvm_test_top.m_env.m_drv [DRV] Drive packet with addr=0x3
UVM_INFO testbench.sv(27) @ 50: uvm_test_top.m_env.m_seqr@@m_seq_3 [m_seq_3] Ended body
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_objection.svh(1271) @ 50: reporter [TEST_DONE] 'run' phase is ready to proceed to the 'extract' phase
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_report_server.svh(847) @ 50: reporter [UVM/REPORT/SERVER] 
--- UVM Report Summary ---

The packets go out as 0x0, 0x1, 0x2, 0x3, the same order in which the forked sequences made their requests.

With priority

The same test now gives each sequence a different priority when it is started.


class arb_fifo_test extends base_test;
  `uvm_component_utils(arb_fifo_test)
  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction
  
  virtual task run_phase(uvm_phase phase);
    super.run_phase(phase);
    phase.raise_objection(this);

    m_env.m_seqr.set_arbitration(UVM_SEQ_ARB_FIFO);
    show_arb_cfg();

    fork
      m_seq[0].start(m_env.m_seqr, .this_priority(100));
      m_seq[1].start(m_env.m_seqr, .this_priority(200));
      m_seq[2].start(m_env.m_seqr, .this_priority(300));
      m_seq[3].start(m_env.m_seqr, .this_priority(400));
    join
    phase.drop_objection(this);
  endtask
endclass

Priorities have no effect in UVM_SEQ_ARB_FIFO mode. The output is identical to the run without priorities.

Output
UVM_INFO @ 0: reporter [RNTST] Running test arb_fifo_test...
UVM_INFO testbench.sv(90) @ 0: uvm_test_top [TEST] Seqr set to UVM_SEQ_ARB_FIFO
UVM_INFO testbench.sv(23) @ 0: uvm_test_top.m_env.m_seqr@@m_seq_0 [m_seq_0] Started body
UVM_INFO testbench.sv(23) @ 0: uvm_test_top.m_env.m_seqr@@m_seq_1 [m_seq_1] Started body
UVM_INFO testbench.sv(23) @ 0: uvm_test_top.m_env.m_seqr@@m_seq_2 [m_seq_2] Started body
UVM_INFO testbench.sv(23) @ 0: uvm_test_top.m_env.m_seqr@@m_seq_3 [m_seq_3] Started body
UVM_INFO testbench.sv(41) @ 10: uvm_test_top.m_env.m_drv [DRV] Drive packet with addr=0x0
UVM_INFO testbench.sv(27) @ 20: uvm_test_top.m_env.m_seqr@@m_seq_0 [m_seq_0] Ended body
UVM_INFO testbench.sv(41) @ 20: uvm_test_top.m_env.m_drv [DRV] Drive packet with addr=0x1
UVM_INFO testbench.sv(27) @ 30: uvm_test_top.m_env.m_seqr@@m_seq_1 [m_seq_1] Ended body
UVM_INFO testbench.sv(41) @ 30: uvm_test_top.m_env.m_drv [DRV] Drive packet with addr=0x2
UVM_INFO testbench.sv(27) @ 40: uvm_test_top.m_env.m_seqr@@m_seq_2 [m_seq_2] Ended body
UVM_INFO testbench.sv(41) @ 40: uvm_test_top.m_env.m_drv [DRV] Drive packet with addr=0x3
UVM_INFO testbench.sv(27) @ 50: uvm_test_top.m_env.m_seqr@@m_seq_3 [m_seq_3] Ended body
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_objection.svh(1271) @ 50: reporter [TEST_DONE] 'run' phase is ready to proceed to the 'extract' phase
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_report_server.svh(847) @ 50: reporter [UVM/REPORT/SERVER] 
--- UVM Report Summary ---

UVM_SEQ_ARB_RANDOM

In this mode, the sequencer picks one of the pending requests at random for each grant and ignores priorities.

Without priority


class arb_random_test extends base_test;
  `uvm_component_utils(arb_random_test)
  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction
  
  virtual task run_phase(uvm_phase phase);
    super.run_phase(phase);
    phase.raise_objection(this);

    m_env.m_seqr.set_arbitration(UVM_SEQ_ARB_RANDOM);
    show_arb_cfg();
    
    fork
      m_seq[0].start(m_env.m_seqr);
      m_seq[1].start(m_env.m_seqr);
      m_seq[2].start(m_env.m_seqr);
      m_seq[3].start(m_env.m_seqr);
    join
    phase.drop_objection(this);
  endtask
endclass
Output
UVM_INFO @ 0: reporter [RNTST] Running test arb_random_test...
UVM_INFO testbench.sv(90) @ 0: uvm_test_top [TEST] Seqr set to UVM_SEQ_ARB_RANDOM
UVM_INFO testbench.sv(23) @ 0: uvm_test_top.m_env.m_seqr@@m_seq_0 [m_seq_0] Started body
UVM_INFO testbench.sv(23) @ 0: uvm_test_top.m_env.m_seqr@@m_seq_1 [m_seq_1] Started body
UVM_INFO testbench.sv(23) @ 0: uvm_test_top.m_env.m_seqr@@m_seq_2 [m_seq_2] Started body
UVM_INFO testbench.sv(23) @ 0: uvm_test_top.m_env.m_seqr@@m_seq_3 [m_seq_3] Started body
UVM_INFO testbench.sv(41) @ 10: uvm_test_top.m_env.m_drv [DRV] Drive packet with addr=0x0
UVM_INFO testbench.sv(27) @ 20: uvm_test_top.m_env.m_seqr@@m_seq_0 [m_seq_0] Ended body
UVM_INFO testbench.sv(41) @ 20: uvm_test_top.m_env.m_drv [DRV] Drive packet with addr=0x3
UVM_INFO testbench.sv(27) @ 30: uvm_test_top.m_env.m_seqr@@m_seq_3 [m_seq_3] Ended body
UVM_INFO testbench.sv(41) @ 30: uvm_test_top.m_env.m_drv [DRV] Drive packet with addr=0x1
UVM_INFO testbench.sv(27) @ 40: uvm_test_top.m_env.m_seqr@@m_seq_1 [m_seq_1] Ended body
UVM_INFO testbench.sv(41) @ 40: uvm_test_top.m_env.m_drv [DRV] Drive packet with addr=0x2
UVM_INFO testbench.sv(27) @ 50: uvm_test_top.m_env.m_seqr@@m_seq_2 [m_seq_2] Ended body
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_objection.svh(1271) @ 50: reporter [TEST_DONE] 'run' phase is ready to proceed to the 'extract' phase
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_report_server.svh(847) @ 50: reporter [UVM/REPORT/SERVER] 
--- UVM Report Summary ---

The grant order is now 0x0, 0x3, 0x1, 0x2 instead of the request order. A different random seed would give a different order.

With priority

The same test now starts the sequences with different priorities.


class arb_random_test extends base_test;
  `uvm_component_utils(arb_random_test)
  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction
  
  virtual task run_phase(uvm_phase phase);
    super.run_phase(phase);
    phase.raise_objection(this);

    m_env.m_seqr.set_arbitration(UVM_SEQ_ARB_RANDOM);
    show_arb_cfg();
    
    fork
      m_seq[0].start(m_env.m_seqr, .this_priority(100));
      m_seq[1].start(m_env.m_seqr, .this_priority(200));
      m_seq[2].start(m_env.m_seqr, .this_priority(300));
      m_seq[3].start(m_env.m_seqr, .this_priority(400));
    join
    phase.drop_objection(this);
  endtask
endclass

Priorities have no effect in UVM_SEQ_ARB_RANDOM mode. This run used the same seed as the one without priorities, so it produced the same random order.

Output
UVM_INFO @ 0: reporter [RNTST] Running test arb_random_test...
UVM_INFO testbench.sv(90) @ 0: uvm_test_top [TEST] Seqr set to UVM_SEQ_ARB_RANDOM
UVM_INFO testbench.sv(23) @ 0: uvm_test_top.m_env.m_seqr@@m_seq_0 [m_seq_0] Started body
UVM_INFO testbench.sv(23) @ 0: uvm_test_top.m_env.m_seqr@@m_seq_1 [m_seq_1] Started body
UVM_INFO testbench.sv(23) @ 0: uvm_test_top.m_env.m_seqr@@m_seq_2 [m_seq_2] Started body
UVM_INFO testbench.sv(23) @ 0: uvm_test_top.m_env.m_seqr@@m_seq_3 [m_seq_3] Started body
UVM_INFO testbench.sv(41) @ 10: uvm_test_top.m_env.m_drv [DRV] Drive packet with addr=0x0
UVM_INFO testbench.sv(27) @ 20: uvm_test_top.m_env.m_seqr@@m_seq_0 [m_seq_0] Ended body
UVM_INFO testbench.sv(41) @ 20: uvm_test_top.m_env.m_drv [DRV] Drive packet with addr=0x3
UVM_INFO testbench.sv(27) @ 30: uvm_test_top.m_env.m_seqr@@m_seq_3 [m_seq_3] Ended body
UVM_INFO testbench.sv(41) @ 30: uvm_test_top.m_env.m_drv [DRV] Drive packet with addr=0x1
UVM_INFO testbench.sv(27) @ 40: uvm_test_top.m_env.m_seqr@@m_seq_1 [m_seq_1] Ended body
UVM_INFO testbench.sv(41) @ 40: uvm_test_top.m_env.m_drv [DRV] Drive packet with addr=0x2
UVM_INFO testbench.sv(27) @ 50: uvm_test_top.m_env.m_seqr@@m_seq_2 [m_seq_2] Ended body
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_objection.svh(1271) @ 50: reporter [TEST_DONE] 'run' phase is ready to proceed to the 'extract' phase
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_report_server.svh(847) @ 50: reporter [UVM/REPORT/SERVER] 
--- UVM Report Summary ---

Where is Random Arbitration Used?

Random modes are useful when several traffic streams share one bus, such as multiple DMA channels or CPU masters behind a single interface. Letting the sequencer interleave their transactions randomly exposes ordering bugs in the DUT that a fixed FIFO order would never hit.

UVM_SEQ_ARB_STRICT_FIFO

This mode always grants the highest-priority request first. Requests with the same priority are granted in the order they arrived, like a FIFO. The tests below use all six sequences so that several share the same priority.

Without priority


class arb_strict_fifo_test extends base_test;
  `uvm_component_utils(arb_strict_fifo_test)
  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction
  
  virtual task run_phase(uvm_phase phase);
    super.run_phase(phase);
    phase.raise_objection(this);

    m_env.m_seqr.set_arbitration(UVM_SEQ_ARB_STRICT_FIFO);
    show_arb_cfg();

    fork
      m_seq[0].start(m_env.m_seqr);
      m_seq[3].start(m_env.m_seqr);
	  m_seq[1].start(m_env.m_seqr);
      m_seq[2].start(m_env.m_seqr);
      m_seq[4].start(m_env.m_seqr);
      m_seq[5].start(m_env.m_seqr);
    join
    phase.drop_objection(this);
  endtask
endclass

With no priorities given, every sequence gets the default priority of 100. All requests tie, so the output matches UVM_SEQ_ARB_FIFO. Note that the grant order follows the fork order (0, 3, 1, 2, 4, 5), not the sequence number.

Output
UVM_INFO @ 0: reporter [RNTST] Running test arb_strict_fifo_test...
UVM_INFO testbench.sv(90) @ 0: uvm_test_top [TEST] Seqr set to UVM_SEQ_ARB_STRICT_FIFO
UVM_INFO testbench.sv(23) @ 0: uvm_test_top.m_env.m_seqr@@m_seq_0 [m_seq_0] Started body
UVM_INFO testbench.sv(23) @ 0: uvm_test_top.m_env.m_seqr@@m_seq_3 [m_seq_3] Started body
UVM_INFO testbench.sv(23) @ 0: uvm_test_top.m_env.m_seqr@@m_seq_1 [m_seq_1] Started body
UVM_INFO testbench.sv(23) @ 0: uvm_test_top.m_env.m_seqr@@m_seq_2 [m_seq_2] Started body
UVM_INFO testbench.sv(23) @ 0: uvm_test_top.m_env.m_seqr@@m_seq_4 [m_seq_4] Started body
UVM_INFO testbench.sv(23) @ 0: uvm_test_top.m_env.m_seqr@@m_seq_5 [m_seq_5] Started body
UVM_INFO testbench.sv(41) @ 10: uvm_test_top.m_env.m_drv [DRV] Drive packet with addr=0x0
UVM_INFO testbench.sv(27) @ 20: uvm_test_top.m_env.m_seqr@@m_seq_0 [m_seq_0] Ended body
UVM_INFO testbench.sv(41) @ 20: uvm_test_top.m_env.m_drv [DRV] Drive packet with addr=0x3
UVM_INFO testbench.sv(27) @ 30: uvm_test_top.m_env.m_seqr@@m_seq_3 [m_seq_3] Ended body
UVM_INFO testbench.sv(41) @ 30: uvm_test_top.m_env.m_drv [DRV] Drive packet with addr=0x1
UVM_INFO testbench.sv(27) @ 40: uvm_test_top.m_env.m_seqr@@m_seq_1 [m_seq_1] Ended body
UVM_INFO testbench.sv(41) @ 40: uvm_test_top.m_env.m_drv [DRV] Drive packet with addr=0x2
UVM_INFO testbench.sv(27) @ 50: uvm_test_top.m_env.m_seqr@@m_seq_2 [m_seq_2] Ended body
UVM_INFO testbench.sv(41) @ 50: uvm_test_top.m_env.m_drv [DRV] Drive packet with addr=0x4
UVM_INFO testbench.sv(27) @ 60: uvm_test_top.m_env.m_seqr@@m_seq_4 [m_seq_4] Ended body
UVM_INFO testbench.sv(41) @ 60: uvm_test_top.m_env.m_drv [DRV] Drive packet with addr=0x5
UVM_INFO testbench.sv(27) @ 70: uvm_test_top.m_env.m_seqr@@m_seq_5 [m_seq_5] Ended body
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_objection.svh(1271) @ 70: reporter [TEST_DONE] 'run' phase is ready to proceed to the 'extract' phase
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_report_server.svh(847) @ 70: reporter [UVM/REPORT/SERVER] 
--- UVM Report Summary ---

With priority


class arb_strict_fifo_test extends base_test;
  `uvm_component_utils(arb_strict_fifo_test)
  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction
  
  virtual task run_phase(uvm_phase phase);
    super.run_phase(phase);
    phase.raise_objection(this);

    m_env.m_seqr.set_arbitration(UVM_SEQ_ARB_STRICT_FIFO);
    show_arb_cfg();

    fork
      m_seq[0].start(m_env.m_seqr, .this_priority(100));
      m_seq[1].start(m_env.m_seqr, .this_priority(200));
      m_seq[2].start(m_env.m_seqr, .this_priority(200));
      m_seq[3].start(m_env.m_seqr, .this_priority(200));
	  m_seq[4].start(m_env.m_seqr, .this_priority(200));
      m_seq[5].start(m_env.m_seqr, .this_priority(400));      
    join

    phase.drop_objection(this);
  endtask
endclass

Priorities do affect the output in UVM_SEQ_ARB_STRICT_FIFO mode.

Output
UVM_INFO @ 0: reporter [RNTST] Running test arb_strict_fifo_test...
UVM_INFO testbench.sv(90) @ 0: uvm_test_top [TEST] Seqr set to UVM_SEQ_ARB_STRICT_FIFO
UVM_INFO testbench.sv(23) @ 0: uvm_test_top.m_env.m_seqr@@m_seq_0 [m_seq_0] Started body
UVM_INFO testbench.sv(23) @ 0: uvm_test_top.m_env.m_seqr@@m_seq_1 [m_seq_1] Started body
UVM_INFO testbench.sv(23) @ 0: uvm_test_top.m_env.m_seqr@@m_seq_2 [m_seq_2] Started body
UVM_INFO testbench.sv(23) @ 0: uvm_test_top.m_env.m_seqr@@m_seq_3 [m_seq_3] Started body
UVM_INFO testbench.sv(23) @ 0: uvm_test_top.m_env.m_seqr@@m_seq_4 [m_seq_4] Started body
UVM_INFO testbench.sv(23) @ 0: uvm_test_top.m_env.m_seqr@@m_seq_5 [m_seq_5] Started body
UVM_INFO testbench.sv(41) @ 10: uvm_test_top.m_env.m_drv [DRV] Drive packet with addr=0x5
UVM_INFO testbench.sv(27) @ 20: uvm_test_top.m_env.m_seqr@@m_seq_5 [m_seq_5] Ended body
UVM_INFO testbench.sv(41) @ 20: uvm_test_top.m_env.m_drv [DRV] Drive packet with addr=0x1
UVM_INFO testbench.sv(27) @ 30: uvm_test_top.m_env.m_seqr@@m_seq_1 [m_seq_1] Ended body
UVM_INFO testbench.sv(41) @ 30: uvm_test_top.m_env.m_drv [DRV] Drive packet with addr=0x2
UVM_INFO testbench.sv(27) @ 40: uvm_test_top.m_env.m_seqr@@m_seq_2 [m_seq_2] Ended body
UVM_INFO testbench.sv(41) @ 40: uvm_test_top.m_env.m_drv [DRV] Drive packet with addr=0x3
UVM_INFO testbench.sv(27) @ 50: uvm_test_top.m_env.m_seqr@@m_seq_3 [m_seq_3] Ended body
UVM_INFO testbench.sv(41) @ 50: uvm_test_top.m_env.m_drv [DRV] Drive packet with addr=0x4
UVM_INFO testbench.sv(27) @ 60: uvm_test_top.m_env.m_seqr@@m_seq_4 [m_seq_4] Ended body
UVM_INFO testbench.sv(41) @ 60: uvm_test_top.m_env.m_drv [DRV] Drive packet with addr=0x0
UVM_INFO testbench.sv(27) @ 70: uvm_test_top.m_env.m_seqr@@m_seq_0 [m_seq_0] Ended body
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_objection.svh(1271) @ 70: reporter [TEST_DONE] 'run' phase is ready to proceed to the 'extract' phase
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_report_server.svh(847) @ 70: reporter [UVM/REPORT/SERVER] 
--- UVM Report Summary ---

m_seq_5 (priority 400) is granted first even though it requested last. The four sequences at priority 200 follow in request order, and m_seq_0 (priority 100) goes last.

Quick Check

Q: In UVM_SEQ_ARB_STRICT_FIFO mode, a low-priority sequence keeps requesting while a high-priority sequence sends a long stream of items. What happens to the low-priority sequence?

Show Answer

It waits until no higher-priority request is pending. Strict modes can starve low-priority sequences, which is why they suit "urgent traffic first" scenarios rather than fair sharing.

UVM_SEQ_ARB_STRICT_RANDOM

This mode also always grants the highest-priority request first, but requests with the same priority are picked at random. The tests below again use six sequences so that several share a priority.

Without priority


class arb_strict_random_test extends base_test;
  `uvm_component_utils(arb_strict_random_test)
  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction
  
  virtual task run_phase(uvm_phase phase);
    super.run_phase(phase);
    phase.raise_objection(this);

    m_env.m_seqr.set_arbitration(UVM_SEQ_ARB_STRICT_RANDOM);
    show_arb_cfg();

    fork
      m_seq[0].start(m_env.m_seqr);
      m_seq[3].start(m_env.m_seqr);
	  m_seq[1].start(m_env.m_seqr);
      m_seq[2].start(m_env.m_seqr);
      m_seq[4].start(m_env.m_seqr);
      m_seq[5].start(m_env.m_seqr);
    join

    phase.drop_objection(this);
  endtask
endclass

With no priorities, all sequences tie at the default of 100, so this mode behaves like UVM_SEQ_ARB_RANDOM.

Output
UVM_INFO @ 0: reporter [RNTST] Running test arb_strict_random_test...
UVM_INFO testbench.sv(90) @ 0: uvm_test_top [TEST] Seqr set to UVM_SEQ_ARB_STRICT_RANDOM
UVM_INFO testbench.sv(23) @ 0: uvm_test_top.m_env.m_seqr@@m_seq_0 [m_seq_0] Started body
UVM_INFO testbench.sv(23) @ 0: uvm_test_top.m_env.m_seqr@@m_seq_3 [m_seq_3] Started body
UVM_INFO testbench.sv(23) @ 0: uvm_test_top.m_env.m_seqr@@m_seq_1 [m_seq_1] Started body
UVM_INFO testbench.sv(23) @ 0: uvm_test_top.m_env.m_seqr@@m_seq_2 [m_seq_2] Started body
UVM_INFO testbench.sv(23) @ 0: uvm_test_top.m_env.m_seqr@@m_seq_4 [m_seq_4] Started body
UVM_INFO testbench.sv(23) @ 0: uvm_test_top.m_env.m_seqr@@m_seq_5 [m_seq_5] Started body
UVM_INFO testbench.sv(41) @ 10: uvm_test_top.m_env.m_drv [DRV] Drive packet with addr=0x3
UVM_INFO testbench.sv(27) @ 20: uvm_test_top.m_env.m_seqr@@m_seq_3 [m_seq_3] Ended body
UVM_INFO testbench.sv(41) @ 20: uvm_test_top.m_env.m_drv [DRV] Drive packet with addr=0x4
UVM_INFO testbench.sv(27) @ 30: uvm_test_top.m_env.m_seqr@@m_seq_4 [m_seq_4] Ended body
UVM_INFO testbench.sv(41) @ 30: uvm_test_top.m_env.m_drv [DRV] Drive packet with addr=0x0
UVM_INFO testbench.sv(27) @ 40: uvm_test_top.m_env.m_seqr@@m_seq_0 [m_seq_0] Ended body
UVM_INFO testbench.sv(41) @ 40: uvm_test_top.m_env.m_drv [DRV] Drive packet with addr=0x5
UVM_INFO testbench.sv(27) @ 50: uvm_test_top.m_env.m_seqr@@m_seq_5 [m_seq_5] Ended body
UVM_INFO testbench.sv(41) @ 50: uvm_test_top.m_env.m_drv [DRV] Drive packet with addr=0x2
UVM_INFO testbench.sv(27) @ 60: uvm_test_top.m_env.m_seqr@@m_seq_2 [m_seq_2] Ended body
UVM_INFO testbench.sv(41) @ 60: uvm_test_top.m_env.m_drv [DRV] Drive packet with addr=0x1
UVM_INFO testbench.sv(27) @ 70: uvm_test_top.m_env.m_seqr@@m_seq_1 [m_seq_1] Ended body
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_objection.svh(1271) @ 70: reporter [TEST_DONE] 'run' phase is ready to proceed to the 'extract' phase
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_report_server.svh(847) @ 70: reporter [UVM/REPORT/SERVER] 
--- UVM Report Summary ---

With priority


class arb_strict_random_test extends base_test;
  `uvm_component_utils(arb_strict_random_test)
  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction
  
  virtual task run_phase(uvm_phase phase);
    super.run_phase(phase);
    phase.raise_objection(this);

    m_env.m_seqr.set_arbitration(UVM_SEQ_ARB_STRICT_RANDOM);
    show_arb_cfg();

    fork
      m_seq[0].start(m_env.m_seqr, .this_priority(100));
      m_seq[1].start(m_env.m_seqr, .this_priority(200));
      m_seq[2].start(m_env.m_seqr, .this_priority(200));
      m_seq[3].start(m_env.m_seqr, .this_priority(200));
      m_seq[4].start(m_env.m_seqr, .this_priority(200));
      m_seq[5].start(m_env.m_seqr, .this_priority(400));
    join

    phase.drop_objection(this);
  endtask
endclass

m_seq_5 still goes first and m_seq_0 last, but m_seq_1 to m_seq_4, which share priority 200, come out in a random order (1, 4, 2, 3). Compare this with UVM_SEQ_ARB_STRICT_FIFO, where the same four came out in request order.

Output
UVM_INFO @ 0: reporter [RNTST] Running test arb_strict_random_test...
UVM_INFO testbench.sv(90) @ 0: uvm_test_top [TEST] Seqr set to UVM_SEQ_ARB_STRICT_RANDOM
UVM_INFO testbench.sv(23) @ 0: uvm_test_top.m_env.m_seqr@@m_seq_0 [m_seq_0] Started body
UVM_INFO testbench.sv(23) @ 0: uvm_test_top.m_env.m_seqr@@m_seq_1 [m_seq_1] Started body
UVM_INFO testbench.sv(23) @ 0: uvm_test_top.m_env.m_seqr@@m_seq_2 [m_seq_2] Started body
UVM_INFO testbench.sv(23) @ 0: uvm_test_top.m_env.m_seqr@@m_seq_3 [m_seq_3] Started body
UVM_INFO testbench.sv(23) @ 0: uvm_test_top.m_env.m_seqr@@m_seq_4 [m_seq_4] Started body
UVM_INFO testbench.sv(23) @ 0: uvm_test_top.m_env.m_seqr@@m_seq_5 [m_seq_5] Started body
UVM_INFO testbench.sv(41) @ 10: uvm_test_top.m_env.m_drv [DRV] Drive packet with addr=0x5
UVM_INFO testbench.sv(27) @ 20: uvm_test_top.m_env.m_seqr@@m_seq_5 [m_seq_5] Ended body
UVM_INFO testbench.sv(41) @ 20: uvm_test_top.m_env.m_drv [DRV] Drive packet with addr=0x1
UVM_INFO testbench.sv(27) @ 30: uvm_test_top.m_env.m_seqr@@m_seq_1 [m_seq_1] Ended body
UVM_INFO testbench.sv(41) @ 30: uvm_test_top.m_env.m_drv [DRV] Drive packet with addr=0x4
UVM_INFO testbench.sv(27) @ 40: uvm_test_top.m_env.m_seqr@@m_seq_4 [m_seq_4] Ended body
UVM_INFO testbench.sv(41) @ 40: uvm_test_top.m_env.m_drv [DRV] Drive packet with addr=0x2
UVM_INFO testbench.sv(27) @ 50: uvm_test_top.m_env.m_seqr@@m_seq_2 [m_seq_2] Ended body
UVM_INFO testbench.sv(41) @ 50: uvm_test_top.m_env.m_drv [DRV] Drive packet with addr=0x3
UVM_INFO testbench.sv(27) @ 60: uvm_test_top.m_env.m_seqr@@m_seq_3 [m_seq_3] Ended body
UVM_INFO testbench.sv(41) @ 60: uvm_test_top.m_env.m_drv [DRV] Drive packet with addr=0x0
UVM_INFO testbench.sv(27) @ 70: uvm_test_top.m_env.m_seqr@@m_seq_0 [m_seq_0] Ended body
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_objection.svh(1271) @ 70: reporter [TEST_DONE] 'run' phase is ready to proceed to the 'extract' phase
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_report_server.svh(847) @ 70: reporter [UVM/REPORT/SERVER] 
--- UVM Report Summary ---

UVM_SEQ_ARB_WEIGHTED

In this mode, each grant is random, but a request's chance of winning is proportional to its priority. Unlike the strict modes, a low-priority sequence is never starved. It just wins less often.

For example, with two sequences at priorities 100 and 300 both requesting, the second one wins each grant with a probability of about 300 / (100 + 300), or 75%.


m_env.m_seqr.set_arbitration (UVM_SEQ_ARB_WEIGHTED);
fork
  m_seq[0].start (m_env.m_seqr, .this_priority(100));   // ~25% of grants
  m_seq[1].start (m_env.m_seqr, .this_priority(300));   // ~75% of grants
join

UVM_SEQ_ARB_USER

If none of the built-in modes fits, you can define your own scheme. Extend uvm_sequencer, override its user_priority_arbitration() function, and set the mode to UVM_SEQ_ARB_USER.

The function receives a queue of indices of the requests that are ready to be granted, in arrival order, and returns the one to grant. The example below grants the most recent request first, giving last-in, first-out order.


class lifo_sequencer extends uvm_sequencer #(base_pkt);
  `uvm_component_utils (lifo_sequencer)

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

  // avail_sequences holds the indices of grantable requests, oldest first
  virtual function integer user_priority_arbitration (integer avail_sequences[$]);
    return avail_sequences[avail_sequences.size() - 1];   // Newest request wins
  endfunction
endclass

// In the test, after the env uses lifo_sequencer:
// m_env.m_seqr.set_arbitration (UVM_SEQ_ARB_USER);

Use a factory override to swap uvm_sequencer #(base_pkt) for your custom sequencer, so the environment code does not have to change.

Where is Priority Arbitration Used?

Strict and weighted modes model systems where some traffic is more important than the rest, for example interrupt or control messages that must overtake bulk data on a shared bus. A test can raise one sequence's priority to check that the DUT handles urgent requests correctly while background traffic continues.

Common Mistakes

Mistake #1: Expecting priorities to work in the default mode

Wrong: starting sequences with .this_priority() and leaving the sequencer in its default mode
Right: call set_arbitration() with UVM_SEQ_ARB_STRICT_FIFO, UVM_SEQ_ARB_STRICT_RANDOM or UVM_SEQ_ARB_WEIGHTED first

Why? The default mode is UVM_SEQ_ARB_FIFO, which ignores priorities completely, as do UVM_SEQ_ARB_RANDOM and the default user function.

Mistake #2: Thinking arbitration decides when a sequence starts

Arbitration only decides which item request is granted. Every sequence's body() starts as soon as start() is called, which is why all "Started body" messages above appear at time 0. Code before the first start_item() runs in parallel in every sequence.

Mistake #3: Overriding user_priority_arbitration() but not selecting UVM_SEQ_ARB_USER

The custom function is called only when the sequencer's mode is UVM_SEQ_ARB_USER. Without set_arbitration(UVM_SEQ_ARB_USER), the sequencer stays in FIFO mode and your function is never used.

Practice Exercise

Challenge: Predict the Grant Order

Using the testbench above in UVM_SEQ_ARB_STRICT_FIFO mode, the test forks these calls in this order:

  • m_seq[0] with priority 300
  • m_seq[1] with priority 100
  • m_seq[2] with priority 300
  • m_seq[3] with priority 200

In what order will the driver print the packet addresses? What could change if the mode were UVM_SEQ_ARB_STRICT_RANDOM?

Hint

All four requests are pending when the first grant happens. Sort by priority first, then use request order to break ties.

Solution

STRICT_FIFO: 0x0, 0x2, 0x3, 0x1. Sequences 0 and 2 tie at 300, so they go in request order. Sequence 3 (200) follows, and sequence 1 (100) is last.

STRICT_RANDOM: the first two grants could be 0x0 then 0x2, or 0x2 then 0x0, depending on the seed. 0x3 and then 0x1 always come last.

Summary

In this tutorial, you learned:

  • Arbitration happens at every start_item() call, when sequences compete for the driver, and not when they start
  • UVM_SEQ_ARB_FIFO is the default mode, and it ignores priorities, as does UVM_SEQ_ARB_RANDOM
  • The strict modes always grant the highest priority first and break ties in FIFO or random order
  • UVM_SEQ_ARB_WEIGHTED chooses at random with probability proportional to priority
  • Priority is set with .this_priority() in start(), and defaults to 100 for a top-level sequence
  • UVM_SEQ_ARB_USER calls your user_priority_arbitration() override to pick the next request
Did you find what you were looking for?

Quiz

Test Your Knowledge

1 of 5 Score: 0 / 5