Verification

UVM sequence arbitration

By CourseTron Team ·  7 min read ·  Updated Oct 2026 💬 𝕏 in f
Table of Contents
    ? Print / Save as PDF

    In short: When several sequences run concurrently on one UVM sequencer, the sequencer's arbitration logic decides whose item wins the next grant — controlled by the SEQ_ARB_* mode, by per-sequence priority, and by explicit lock()/grab() hand-ownership calls.

    Put two sequences on the same sequencer and they immediately compete: both call start_item(), both block, and exactly one gets a grant per cycle of the driver's get_next_item() loop. How that winner is chosen is one of the most misunderstood corners of UVM — and a reliable interview filter. This article walks through the arbitration engine built into uvm_sequencer, the six SEQ_ARB modes, sequence priority, and the lock/grab mechanism that lets a sequence seize exclusive control.

    Why a sequencer needs arbitration at all

    A sequence does not drive pins. It generates uvm_sequence_item objects and pushes them, via start_item()/finish_item(), through the sequencer to the driver. The driver pulls with seq_item_port.get_next_item(). The crucial fact: a sequencer can host many sequences at once — a reset sequence, a config sequence, a traffic generator, an error-injector — all layered on the same sequencer. Each blocked start_item() registers a request in the sequencer's internal arbitration queue. The sequencer must pick one request to unblock whenever the driver asks for the next item.

    The arbitration decision happens per item, not per sequence. A sequence that sends 10 items re-enters the arbitration contest 10 separate times unless it holds a lock or grab.

    Three sequences, one grant per driver requestseq_reset (prio 300)seq_err (prio 200)seq_traffic (prio 100)uvm_sequencerarb queue + SEQ_ARB modelock/grab check1 grantdriverCourseTron · coursetron.com
    Three sequences contend on one sequencer; the arbitration engine unblocks exactly one per driver request.

    The six SEQ_ARB modes

    You set the policy with set_arbitration(UVM_SEQ_ARB_WEIGHTED) on the sequencer. The default is UVM_SEQ_ARB_FIFO. The modes:

    ModeBehaviourUses priority?
    UVM_SEQ_ARB_FIFODefault. Pure first-come-first-served order of arriving requests.No
    UVM_SEQ_ARB_WEIGHTEDRandom weighted by each sequence's priority — higher priority wins proportionally more often.Yes
    UVM_SEQ_ARB_RANDOMUniform random among all pending requests, priority ignored.No
    UVM_SEQ_ARB_STRICT_FIFOHighest priority first; ties broken by FIFO order.Yes
    UVM_SEQ_ARB_STRICT_RANDOMHighest priority first; ties broken randomly.Yes
    UVM_SEQ_ARB_USERCalls your overridden user_priority_arbitration() method — you return the winning request index.Custom

    The distinction people miss: STRICT_FIFO and STRICT_RANDOM are absolute — a priority-200 sequence will never lose to a priority-100 sequence as long as its request is pending. WEIGHTED is statistical — over, say, 1000 items a priority-200 and priority-100 sequence sharing the queue will win roughly in a 2:1 ratio, but the low-priority one is never fully starved. Choose STRICT_* when ordering is a correctness requirement (reset must precede traffic); choose WEIGHTED when you want a realistic blend of traffic classes without hard starvation.

    Setting sequence priority

    Priority is a positive integer; larger means more important. The default is 100. Two ways to set it:

    • At start: traffic_seq.start(sqr, null, 200); — the third argument is priority.
    • Dynamically: this.set_priority(500); from inside the running sequence.

    Only the WEIGHTED and STRICT_* modes read it. Under FIFO or RANDOM, setting priority does nothing — a common source of "my high-priority sequence isn't winning" confusion. Always confirm the arbitration mode first.

    Lock and grab: taking exclusive ownership

    Priority biases the contest but never guarantees a run of consecutive items. For an atomic burst — a read-modify-write, a protected register sequence, a cache-line transaction that must not be interleaved — you need exclusive access. UVM gives two calls:

    • lock(sqr) — requests exclusive access but waits its turn in the arbitration queue first. Once granted, no other sequence can send until this sequence calls unlock(sqr).
    • grab(sqr) — jumps to the front of the queue immediately, bypassing arbitration entirely (subject only to existing locks/grabs). Released with ungrab(sqr).

    Mnemonic: lock = polite (honours the queue), grab = rude (cuts the line). Both block every other sequence until released, so always pair them with an unlock/ungrab — ideally in a fork...join_none-safe or disable-safe structure so an exception cannot leave the sequencer locked forever.

    grab() seizes the sequencer, then ungrab() releases itGRAB burstBlue squares: normal items. Orange block: grab holds exclusive access.CourseTron · coursetron.com
    Normal items flow until a grab() slides the sequencer into exclusive mode; everything else stalls until ungrab().

    How the engine resolves a single grant

    On every driver request the sequencer evaluates, in order: (1) is there an active lock or grab? If so, only that owner's items are eligible. (2) Otherwise, apply the SEQ_ARB mode across all pending requests. (3) For USER mode, call user_priority_arbitration(avail_sequences) and use the returned index. This layering is why a grab always beats even a higher-priority sequence — ownership is checked before priority.

    How the engine resolves a single grant1. Active lockor grab?2. Apply SEQ_ARBmode to pendingrequests3. USER mode only:call user_priority_arbitration()Yes: only the owner'sitems are eligibleNo: policy picksone requestReturned indexis the winnerOwnership is checked before priority, so a grab beats a higher-priority sequence.CourseTron · coursetron.com
    Per grant, the sequencer checks lock/grab ownership first, then the SEQ_ARB mode, then the user hook in USER mode.

    Tool and methodology notes

    All three big simulators — Synopsys VCS, Cadence Xcelium, and Siemens Questa — ship the same reference UVM library, so arbitration semantics are identical across them; differences show up only in how each solver seeds WEIGHTED/RANDOM draws, which is why a weighted test can pass on VCS and expose an ordering bug on Xcelium. When debugging starvation, dump the sequencer's arbitration queue with +UVM_OBJECTION_TRACE alongside your own uvm_info on get_sequence(). For scoreboards that must know the true transaction order, never infer it from sequence code — read it from the driver's item stream, because arbitration, not your source order, decided it. Teams building rigorous verification environments at Avecas VLSI design services treat arbitration policy as a reviewed architectural choice, not a default left untouched.

    If you are building this muscle from scratch, pair arbitration with the broader UVM sequence and sequencer mechanics and with virtual sequencer layering, where arbitration across sub-sequencers becomes the main event. A structured path through all of it lives in our verification courses.

    Ready to master UVM from sequences to coverage closure? Enrol in CourseTron's Advanced UVM Verification track and build a complete, arbitration-aware testbench with guided labs on VCS, Xcelium and Questa.

    Frequently asked questions

    Does setting a higher priority guarantee my sequence runs first?

    No. Priority is only read in WEIGHTED, STRICT_FIFO and STRICT_RANDOM modes. In the default FIFO mode priority is ignored entirely. Only STRICT_* modes give an absolute guarantee; WEIGHTED is statistical.

    What is the difference between lock and grab?

    Both give exclusive access to the sequencer. lock() waits its turn in the arbitration queue before taking the lock; grab() jumps to the front immediately and takes control on the next grant, subject only to pre-existing locks. Grab is the more aggressive of the two.

    Can a lock cause a deadlock?

    Yes. If a locked sequence waits on something that itself needs the sequencer — or you forget to call unlock()/ungrab() after an early return or a disabled thread — the sequencer stays owned forever and every other sequence hangs. Always release in a finally-style structure.

    How do I implement fully custom arbitration?

    Set UVM_SEQ_ARB_USER and override user_priority_arbitration(queue) on your sequencer subclass. It receives the indices of pending requests and must return the index of the winner, letting you encode any policy — weighted round-robin, QoS classes, or deadline-driven selection.

    Advertisement