Sequence Libraries & Arbitration
Real regressions rarely run just one sequence class over and over — a healthy testbench accumulates a whole family of them (fifo_write_read_seq from earlier in this section, plus variations like an all-writes sequence, a burst sequence, an error-injection sequence) and needs a way to pick among them without a test hand-picking one every time. Separately, once more than one sequence can be active on the same sequencer at once, something has to decide whose item actually goes to the driver next. This page covers both: sequence libraries, and the arbitration modes that resolve exactly that kind of conflict.
Sequence libraries: a named, selectable group of sequences
class fifo_seq_library extends uvm_sequence_library #(fifo_txn);
`uvm_object_utils(fifo_seq_library)
`uvm_sequence_library_utils(fifo_seq_library)
function new(string name = "fifo_seq_library");
super.new(name);
init_sequence_library();
endfunction
endclass
class fifo_write_read_seq extends uvm_sequence #(fifo_txn);
`uvm_object_utils(fifo_write_read_seq)
`uvm_add_to_seq_lib(fifo_write_read_seq, fifo_seq_library)
// ... body() as in The Sequencer & Sequence ...
endclass
- Two registration macros, not one —
`uvm_object_utils(ordinary factory registration, from Utility & Field Macros) plus`uvm_sequence_library_utils(the library-specific machinery). Both are required together; the library macro alone doesn't register the class with the factory. init_sequence_library(), called in the constructor, activates every sequence statically registered to this library via`uvm_add_to_seq_libelsewhere in the codebase — this is what actually populates the library at construction time.`uvm_add_to_seq_lib(fifo_write_read_seq, fifo_seq_library), placed inside (or alongside) each individual sequence's own definition, is the static registration side — declarative, requiring no changes to the library class itself when a new sequence joins it. Sequences can also be added dynamically instead, viaadd_typewide_sequence()/add_sequence()calls, typically from a test, for cases where the membership needs to vary per test rather than being fixed at compile time.
Selection modes
A library picks which registered sequence to run each time it executes, according to a selection mode:
| Mode | Behavior |
|---|---|
UVM_SEQ_LIB_RAND | Randomly picks among registered sequences each time |
UVM_SEQ_LIB_RANDC | Randomly picks, but never repeats the same sequence twice in a row |
UVM_SEQ_LIB_ITEM | Runs items directly rather than whole sequences |
UVM_SEQ_LIB_USER | Custom selection, via overriding select_sequence() |
min_random_count/max_random_count bound how many sequences the library runs in one invocation — a library configured this way, started once from a test, behaves like a small randomized test generator on its own, rather than requiring the test to hand-pick and start each individual sequence.
Arbitration: who goes next, when more than one sequence is active
Everything so far in this section has had exactly one sequence active on a sequencer at a time. The moment more than one can be — a virtual sequence's sub-sequences running concurrently via fork/join, from the previous page, is one way this happens — the sequencer needs a policy for whose item is handed to the driver next:
sequencer.set_arbitration(UVM_SEQ_ARB_STRICT_FIFO);
| Mode | Behavior |
|---|---|
UVM_SEQ_ARB_FIFO | Default. First-come, first-served; ignores priority entirely |
UVM_SEQ_ARB_RANDOM | Random selection among ready sequences; also ignores priority |
UVM_SEQ_ARB_STRICT_FIFO | Respects priority; FIFO order among sequences that share the same priority |
UVM_SEQ_ARB_STRICT_RANDOM | Respects priority; random order among sequences that share the same priority |
UVM_SEQ_ARB_WEIGHTED | Selection weighted by priority value |
UVM_SEQ_ARB_USER | Fully custom — override user_priority_arbitration() on a uvm_sequencer subclass |
The detail worth being precise about: FIFO and RANDOM ignore priority completely — the this_priority argument to start(), covered on the previous page, only has any effect at all under the STRICT_* or WEIGHTED modes. Setting a priority under the default FIFO mode is silently a no-op, which is a real source of confusion the first time someone expects priority to matter without having changed the arbitration mode first.
On the driver side, none of this needs new code — seq_item_port.get_next_item(), exactly as written throughout this section, is what triggers arbitration resolution internally; item_done() is what releases the sequencer to resolve the next one. Arbitration is entirely a sequencer-side policy, invisible to the driver.
Overriding arbitration entirely: lock()/unlock() and grab()/ungrab()
Every mode above still lets the sequencer pick among all ready sequences. Sometimes a sequence needs the opposite — exclusive access to the driver, with every other sequence held off — for example, a sequence modeling an interrupt response that must not be interleaved with anything else mid-transaction. uvm_sequence_base provides two request-a-sequencer methods for exactly this, called from within the sequence itself:
task body();
seqr.lock(this); // wait in line, then take exclusive access once granted
// ... drive a burst of items with nothing else interleaved ...
seqr.unlock(this); // release exclusive access
endtask
| Method pair | Behavior |
|---|---|
lock() / unlock() | Requests exclusive access politely — the calling sequence still waits its turn through normal arbitration for the next available slot, but once granted, holds the sequencer exclusively (no other sequence's items pass through) until unlock() is called |
grab() / ungrab() | Requests exclusive access immediately — jumps straight to the front, ahead of every other sequence's priority, and takes over as soon as the current item (if any) completes. ungrab() is simply an alias for unlock() |
grab()overrideslock(), not the other way around — the only thing that can block agrab()request from taking immediate effect is a sequencer already held by an existinglock()orgrab(). Ordinary priority, and the arbitration mode itself, are both bypassed.- Both calls take the requesting sequence's own handle (
this) as their argument, so the sequencer knows which sequence currently holds exclusive access. - A locking/grabbing sequence must call
unlock()/ungrab()before it finishes — if it doesn't, the sequencer stays exclusively held forever and every other sequence starves, a common source of a testbench that silently stops making progress mid-regression. - This mechanism sits above the arbitration mode configured via
set_arbitration()—UVM_SEQ_ARB_FIFO,UVM_SEQ_ARB_WEIGHTED, etc. still govern ordering among sequences that are not locking/grabbing;lock()/grab()is an escape hatch layered on top, not an arbitration mode itself.
What's next
This closes out the deep dive into sequences and sequencers. The next section returns to a mechanism first introduced in Reporting & Messaging — printer and comparer policy objects, and the callback/event mechanisms that round out UVM's utility classes.