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.
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 sixUVM_SEQ_ARB_TYPEvalues (defaultUVM_SEQ_ARB_FIFO)this_priority- optional argument ofstart(). The default of -1 means "use the parent sequence's priority", or 100 for a sequence started with no parent
| Mode | Uses priority? | How the next request is chosen |
|---|---|---|
UVM_SEQ_ARB_FIFO | No | Oldest request first (default) |
UVM_SEQ_ARB_RANDOM | No | Any pending request, chosen at random |
UVM_SEQ_ARB_STRICT_FIFO | Yes | Highest priority first; ties in request order |
UVM_SEQ_ARB_STRICT_RANDOM | Yes | Highest priority first; ties chosen at random |
UVM_SEQ_ARB_WEIGHTED | Yes | Random, with each request's chance proportional to its priority |
UVM_SEQ_ARB_USER | Your choice | Your 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
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.
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
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.
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.
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.
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.
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.
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_FIFOis the default mode, and it ignores priorities, as doesUVM_SEQ_ARB_RANDOM- The strict modes always grant the highest priority first and break ties in FIFO or random order
UVM_SEQ_ARB_WEIGHTEDchooses at random with probability proportional to priority- Priority is set with
.this_priority()instart(), and defaults to 100 for a top-level sequence UVM_SEQ_ARB_USERcalls youruser_priority_arbitration()override to pick the next request