Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutContactPrivacyTerms
Skip to lesson

Course contents

  • Start Here4
  • UVM Foundations14
  • UVM Phases & Simulation Lifecycle26
  • Factory & Configuration15
  • Transaction-Level Modeling (TLM)13
  • UVM Sequences & Stimulus21
    • Sequence Items and the Driver Handshake
    • Sequence Mechanics: Lifecycle, Sequencer, Responses and Control
      • Sequence Lifecycle and Hooks: What start() Does, Step by Step
      • Inside the Sequencer: Arbitration, Priority and Relevance
      • Responses and Pipelined Drivers
      • Stopping Sequences and Reset
      • Sequences and Objections: Who Keeps the Phase Open
      • The uvm_do Macro Family: What It Expands To
    • Virtual Sequences and Coordination
  • UVM Agents & Protocol VIP21
  • Environment & Test Classes11
  • Checking & Coverage in UVM21
  • UVM Register Abstraction Layer (RAL)22
  • Advanced UVM in Practice13
  • Debug & Regression10
  • Coding Practice47
  • Verification in Industry Practice10
  • Senior Prep: Architecture, Leadership and Interviews32
Loading lesson

‹Sequence Mechanics: Lifecycle, Sequencer, Responses and Control
  1. Topics
  2. UVM Verification
  3. UVM Sequences & Stimulus
  4. Sequence Mechanics: Lifecycle, Sequencer, Responses and Control
  5. Inside the Sequencer: Arbitration, Priority and Relevance

Part 5 · Sequences · Intermediate

Inside the Sequencer: Arbitration, Priority and Relevance

The sequencer's arbitration queue, when grants happen, how priority is set and used, the six arbitration modes run side by side, user_priority_arbitration, and is_relevant / wait_for_relevant.

On this page

    The interrupt-service sequence on the DMA bench is started with priority 500; the polling sequence runs at the default of 100. Yet the log shows the interrupt handler's STATUS read waiting behind two more COUNT polls. Someone files a bug against UVM. It is not a bug: the sequencer was doing exactly what its default arbitration mode says.

    This lesson opens the sequencer. You will see what it stores, when it decides, what priority means in each of the six arbitration modes, and how to write your own rule.

    The idea: a deli counter, with an optional VIP lane

    A deli counter hands out numbered tickets and serves them in order. That is the sequencer's default, FIFO arbitration: whoever asked first is served first, and the ticket's colour (the priority) is ignored. Other modes change the rule: serve the most important customer first (strict), or draw tickets from a hat with more tickets for important customers (weighted).

    Two facts make the analogy exact. First, the counter only calls a number when the server is free: the sequencer arbitrates when the driver asks for an item, never when a sequence asks for a grant. Second, a customer who is not ready can be skipped without losing their place: that is is_relevant.

    The picture: from request to grant

    Three sequences put requests into the arbitration queue; the driver's get_next_item triggers a choice that skips locked and irrelevant requests and applies the arbitration mode, granting one sequence.
    Requests wait in one queue. When the driver asks, the sequencer drops blocked and irrelevant requests, applies the arbitration mode to the rest, and grants one.

    Build it

    What start_item and finish_item really call

    start_item(item, priority) calls the sequencer's wait_for_grant, which puts a request into the arbitration queue and blocks. When the driver calls get_next_item, the sequencer chooses one request and grants it, and start_item returns. finish_item then calls send_request, which puts the item into the sequencer's request FIFO where get_next_item picks it up, and wait_for_item_done, which blocks until the driver's item_done. A sequence can call those three methods directly, and the uvm_do macros once did, but start_item / finish_item is the readable form.

    Setting the priority, and the mode

    A request's priority comes from start_item(item, priority), or else from the sequence's own priority set by start(sqr, parent, priority) or set_priority(). The default is 100, and -1 means "inherit". The mode is set on the sequencer with set_arbitration. Call it once the sequencer exists, in connect_phase or later, or from a sequence through m_sequencer.set_arbitration(...). In an env's build_phase the agent has not built its sequencer yet, so the handle is still null.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // snippet: seq_lab_tb.sv, three sequences race for one sequencer
    function void end_of_elaboration_phase(uvm_phase phase);
      env.sqr.set_arbitration(mode);             // UVM_SEQ_ARB_FIFO, _STRICT_FIFO, ...
    endfunction
    
    task run_phase(uvm_phase phase);
      lab_burst_seq a = lab_burst_seq::type_id::create("A");
      lab_burst_seq b = lab_burst_seq::type_id::create("B");
      lab_burst_seq c = lab_burst_seq::type_id::create("C");
      phase.raise_objection(this);
      fork                                       // four items each
        a.start(env.sqr, null, 100);
        b.start(env.sqr, null, 200);
        c.start(env.sqr, null, 300);
      join
      `uvm_info("ARB", $sformatf("%s grant order: %s", mode.name(), env.drv.grants), UVM_LOW)
      phase.drop_objection(this);
    endtask

    The six modes

    Mode

    Rule

    Priority used?

    UVM_SEQ_ARB_FIFO (default)

    First come, first served

    No

    UVM_SEQ_ARB_WEIGHTED

    Random, weighted by priority

    Yes, as odds: nobody starves

    UVM_SEQ_ARB_RANDOM

    Random

    No

    UVM_SEQ_ARB_STRICT_FIFO

    Highest priority first, FIFO among equals

    Yes, absolutely: low priority can starve

    UVM_SEQ_ARB_STRICT_RANDOM

    Highest priority first, random among equals

    Yes, absolutely: low priority can starve

    UVM_SEQ_ARB_USER

    Your user_priority_arbitration()

    If your function uses it

    Writing your own rule

    UVM_SEQ_ARB_USER calls the virtual function user_priority_arbitration(int avail_sequences[$]) on the sequencer. It receives the indexes of the requests that passed the lock and relevance checks, and must return one of them. The default returns the first, which is FIFO. This one does the opposite of STRICT_FIFO, picking the lowest priority, to show the mechanism:

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // snippet: seq_lab_tb.sv, installed with a factory type override of the sequencer
    class lab_user_sqr extends lab_sqr;           // lab_sqr is uvm_sequencer #(lab_item)
      `uvm_component_utils(lab_user_sqr)
      function new(string name, uvm_component parent); super.new(name, parent); endfunction
      // Lowest priority first (the opposite of STRICT_FIFO), oldest among equals.
      function int user_priority_arbitration(int avail_sequences[$]);
        int best = avail_sequences[0];
        foreach (avail_sequences[i])
          if (arb_sequence_q[avail_sequences[i]].item_priority < arb_sequence_q[best].item_priority)
            best = avail_sequences[i];
        return best;
      endfunction
    endclass

    arb_sequence_q and its item_priority field are library internals rather than documented API, though both UVM 1.2 and the IEEE 1800.2 reference library have them. A real rule usually asks the sequence itself, through arb_sequence_q[i].sequence_ptr, for something like a deadline or a credit count.

    Relevance: a request that is not ready yet

    A sequence can stay in the queue but decline to be chosen by overriding is_relevant(). If every waiting sequence is irrelevant, the sequencer calls their wait_for_relevant() tasks and arbitrates again when one returns, so a sequence that overrides one must override both (otherwise UVM stops with a RELMSM fatal). A configuration sequence that must not write while a job is running might look like this:

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // snippet
    class dma_cfg_seq extends uvm_sequence #(apb_item);
      `uvm_object_utils(dma_cfg_seq)
      bit dma_busy;                     // kept up to date by the virtual sequence from STATUS reads
    
      function new(string name = "dma_cfg_seq"); super.new(name); endfunction
    
      function bit is_relevant();      // skip me while the DMA is busy
        return !dma_busy;
      endfunction
      task wait_for_relevant();        // tell the sequencer when to try again
        wait (!dma_busy);
      endtask
      // body() writes KEY and LEN as usual
    endclass

    Locks and grabs act one step earlier, removing requests from the choice entirely. How lock() and grab() differ, and how children of the lock holder still get through, is the subject of Lock vs grab. For debugging, is_grabbed(), current_grabber(), has_do_available() and get_arbitration() tell you the sequencer's state at any moment.

    Watch it run: one race, four rules

    The lab test above, run once per mode. Each letter is one item granted, in order:

    Diagram
    UVM_INFO seq_lab_tb.sv(386) @ 255000: uvm_test_top [ARB] UVM_SEQ_ARB_FIFO grant order: ABCABCABCABC
    UVM_INFO seq_lab_tb.sv(386) @ 255000: uvm_test_top [ARB] UVM_SEQ_ARB_STRICT_FIFO grant order: CCCCBBBBAAAA
    UVM_INFO seq_lab_tb.sv(386) @ 255000: uvm_test_top [ARB] UVM_SEQ_ARB_WEIGHTED grant order: BCAACBBACCBA
    UVM_INFO seq_lab_tb.sv(386) @ 255000: uvm_test_top [ARB] UVM_SEQ_ARB_USER grant order: AAAABBBBCCCC
    • FIFO interleaves perfectly. Priorities 100, 200 and 300 make no difference: after each grant, the sequence that just finished re-queues behind the other two.

    • STRICT_FIFO serves C to completion, then B, then A. If C never ran out of items, A and B would never be served.

    • WEIGHTED is a random mix that favours C over the long run; with only twelve grants and this seed it happens to look even. The order changes with the seed.

    • USER with the lowest-first rule is STRICT_FIFO in reverse, which proves the function is in charge.

    What breaks: the error injector that starved the bench

    A team added a background sequence that injected random APB errors and gave it priority 300 so that its accesses would land promptly. They also switched the sequencer to UVM_SEQ_ARB_STRICT_FIFO, because priority "did nothing" in the default mode. The injector looped with no delay between items. From that moment every lower-priority request, including the test's register writes, waited forever: each time the driver asked, the injector already had a request in the queue, and strict arbitration always picked it. The test hung and ended in PH_TIMEOUT.

    Two fixes were possible, and they used both. The injector waited a random number of clocks between items, so other requests had moments when it was absent. And the sequencer went back to WEIGHTED, which favours high priority without ever reducing anyone else's odds to zero. The general rule: strict modes are for short, bounded bursts of urgent traffic, such as an interrupt handler, not for anything that loops.

    Interview

    "How does a sequencer decide which sequence gets the next grant?"

    A weak answer: "The one with the highest priority." That is true in two of the six modes and wrong in the default.

    A strong answer: "Sequences queue requests in start_item. Arbitration runs only when the driver asks for an item. The sequencer drops requests blocked by another sequence's lock or grab and those whose is_relevant returns 0, then applies the mode: FIFO by default, which ignores priority; WEIGHTED, which uses priority as odds; RANDOM; STRICT_FIFO and STRICT_RANDOM, which always pick the highest priority and can starve the rest; or USER, which calls user_priority_arbitration. Priority comes from start or start_item and defaults to 100."

    Follow-ups: Which modes can starve a sequence? (only the STRICT modes). Where do you call set_arbitration? (once the sequencer exists: connect_phase or later, or from a sequence via m_sequencer).

    Check yourself Two sequences run on the APB sequencer in the default mode: a poller at priority 100 and an interrupt handler started at priority 500. Which is granted first when both are waiting?

    Whichever requested first. UVM_SEQ_ARB_FIFO ignores priority. To make priority count, switch to WEIGHTED (a strong bias) or STRICT_FIFO (always first), or have the handler take the sequencer with grab(), which is what the bench's dma_irq_seq does.

    Check yourself An env calls apb.sqr.set_arbitration(UVM_SEQ_ARB_STRICT_FIFO) at the end of its build_phase, right after creating the agent. What happens?

    A null-handle error. The env's build_phase creates the agent, but the agent builds its own sequencer in its own build_phase, which runs after the env's returns. Move the call to connect_phase or end_of_elaboration_phase.

    Check yourself A sequence overrides is_relevant() but not wait_for_relevant(). When does it fail?

    Only when the sequencer finds that every waiting sequence is irrelevant and calls wait_for_relevant() to find out when to try again. The default implementation detects that is_relevant was overridden alone and stops with a RELMSM fatal. Until that moment the bug is invisible.

    Next: the grant gets the item to the driver. Responses and pipelined drivers is about the way back: how a driver that answers later, or out of order, returns results to the right sequence.

    Related topics

    • Lock vs grab

    • The driver handshake

    • Virtual sequences

    Previous

    80 / 280

    Next: Responses and Pipelined Drivers

    Up next

    Up nextResponses and Pipelined Drivers

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Stopping Sequences and Reset
    • Sequences and Objections: Who Keeps the Phase Open
    • The uvm_do Macro Family: What It Expands To