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. Responses and Pipelined Drivers

Part 5 · Sequences · Intermediate

Responses and Pipelined Drivers

The response path: each sequence's response queue (depth 8, overflow), item_done(rsp) versus put_response, set_id_info, get_response by transaction ID, response handlers, and an out-of-order driver built on get() and put_response().

On this page

    A new AXI read driver lets four reads be outstanding, as the protocol allows. The first test passes. The second, with random slave latencies, fails with data mismatches on reads that the waveform shows returning the correct data. The sequence is pairing each response with the wrong request: it assumed responses come back in the order the requests went out.

    The bench's APB driver never has this problem, because it answers inside the request object before item_done. This lesson is about everything else: responses that come later, responses that come out of order, and the queue that holds them.

    The idea: a dry cleaner's ticket

    You hand in a shirt and get a ticket; you do not stand at the counter until it is clean. Later you come back and collect your shirt by ticket number, even if other people's were ready first. A pipelined driver works the same way. It takes the request and releases the sequence at once (the ticket), drives it, and much later sends a response that carries the request's IDs. The sequence collects responses from its response queue, either the next one or the one matching a given transaction ID.

    So a sequence has two separate channels with the driver. The request handshake (finish_item waits for item_done) says "the driver has taken this". The response path (put_response to get_response) says "here is the result". For the bench's APB driver they coincide; for a pipelined driver they are far apart.

    The picture: two requests, answered out of order

    Sequence diagram: the sequence sends two reads, each released at once by the driver's get; the driver later puts response 2 then response 1; the sequence's get_response for ID 1 waits for it, then gets ID 2 from the queue.
    The request handshake finishes immediately with get(); responses arrive later, in any order, and the sequence collects each one by its transaction ID.

    Build it

    The response queue, and its default depth of 8

    Every sequence owns a response queue. The sequencer routes each response to the sequence named in the response's sequence ID, and pushes it onto that queue, which holds 8 by default. get_response(rsp) pops the oldest; get_response(rsp, id) waits for the one with that transaction ID. If the sequence never collects, the 9th response is dropped, and in UVM 1.2 each drop is a UVM_ERROR ("Response queue overflow, response was dropped"). Three knobs control it:

    Call

    Effect

    set_response_queue_depth(n)

    Change the depth; -1 makes it unbounded (and makes a leak grow forever instead of failing)

    set_response_queue_error_report_disabled(1)

    Drop silently instead of erroring (UVM 1.2 name; still present in 1800.2)

    set_response_queue_error_report_enabled(1)

    The IEEE 1800.2 name for the opposite switch

    item_done(rsp) or put_response?

    item_done(rsp) is item_done() followed by put_response(rsp): it completes the request and queues a response in one call. It suits an in-order driver whose response is a separate object. seq_item_port.put_response(rsp) queues a response without touching the request handshake, for drivers that completed the request earlier, typically with get(). Either way the response must carry the request's IDs, set with `rsp.set_id_info(req)`, which copies the sequence ID and transaction ID. A response with no sequence ID is a SQRPUT fatal ("Driver put a response with null sequence_id"); one for a sequence that has already ended is dropped with a warning ("sequence not found").

    A pipelined, out-of-order driver

    This driver allows four outstanding reads. get() takes each request and releases the sequence immediately; a forked thread answers after a latency that depends on the address, so later requests can finish first:

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    // snippet: seq_lab_tb.sv (lab_driver supplies vif and mem_rd())
    class lab_pipe_driver extends lab_driver;
      `uvm_component_utils(lab_pipe_driver)
      semaphore slots = new(4);            // at most 4 outstanding
      function new(string name, uvm_component parent); super.new(name, parent); endfunction
    
      task run_phase(uvm_phase phase);
        wait (vif.rst_n === 1'b1);
        forever begin
          lab_item r;
          seq_item_port.get(r);            // get() = get_next_item() + item_done()
          slots.get(1);
          fork
            automatic lab_item t = r;
            respond(t);
          join_none
        end
      endtask
    
      task respond(lab_item t);
        lab_item rsp;
        repeat (2 + 3 * (3 - t.addr[3:2])) @(posedge vif.clk);   // higher addresses answer sooner
        rsp = lab_item::type_id::create("rsp");
        rsp.set_id_info(t);                // route back to t's sequence, with t's transaction ID
        rsp.addr  = t.addr;
        rsp.rdata = mem_rd(t.addr);
        `uvm_info("PIPE", $sformatf("response id=%0d addr=%02h", rsp.get_transaction_id(), rsp.addr), UVM_LOW)
        seq_item_port.put_response(rsp);
        slots.put(1);
      endtask
    endclass

    The automatic copy declared in the fork gives each forked thread its own handle, taken when the fork starts; it is the standard idiom for fork ... join_none inside a loop. The response is a new object, so the request the sequence holds is never changed behind its back.

    The sequence: send all, then collect by ID

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // snippet: seq_lab_tb.sv
    task body();
      int ids[$];
      for (int i = 0; i < 4; i++) begin
        lab_item it = lab_item::type_id::create($sformatf("rd%0d", i));
        start_item(it);
        if (!it.randomize() with { write == 0; addr == 8'(4 * i); }) `uvm_fatal("RAND", "randomize failed")
        finish_item(it);                      // returns at once: get() already did item_done
        `uvm_info("SEQ", $sformatf("sent id=%0d addr=%02h", it.get_transaction_id(), it.addr), UVM_LOW)
        ids.push_back(it.get_transaction_id());
      end
      foreach (ids[k]) begin
        lab_item rsp;
        get_response(rsp, ids[k]);            // wait for THIS id, whatever order they arrive in
        `uvm_info("SEQ", $sformatf("got  id=%0d rdata=%08h", rsp.get_transaction_id(), rsp.rdata), UVM_LOW)
      end
    endtask

    The transaction ID is assigned by the sequence when the item is sent, so read it after finish_item. And because finish_item returned before the driver did anything, the request's own rdata is meaningless here: the answer is only in rsp.

    Or let a handler take them

    If the sequence does not want to block, use_response_handler(1) sends every response to the virtual function response_handler() instead of the queue. The queue can then never overflow, but the handler is a function, so it cannot wait; it records and returns:

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // snippet: seq_lab_tb.sv
    function void response_handler(uvm_sequence_item response);
      lab_item rsp;
      $cast(rsp, response);
      got++;
      `uvm_info("HANDLER", $sformatf("id=%0d rdata=%08h", rsp.get_transaction_id(), rsp.rdata), UVM_LOW)
    endfunction
    
    task body();
      use_response_handler(1);
      // ... send four reads as above ...
      wait (got == 4);
    endtask

    Watch it run

    The pipelined sequence: all four reads go out at 22 ns, the responses come back in reverse order, and get_response still hands them over in request order:

    Diagram
    UVM_INFO seq_lab_tb.sv(235) @ 22000: uvm_test_top.env.sqr@@pipe [SEQ] sent id=1 addr=00
    UVM_INFO seq_lab_tb.sv(235) @ 22000: uvm_test_top.env.sqr@@pipe [SEQ] sent id=2 addr=04
    UVM_INFO seq_lab_tb.sv(235) @ 22000: uvm_test_top.env.sqr@@pipe [SEQ] sent id=3 addr=08
    UVM_INFO seq_lab_tb.sv(235) @ 22000: uvm_test_top.env.sqr@@pipe [SEQ] sent id=4 addr=0c
    UVM_INFO seq_lab_tb.sv(96) @ 35000: uvm_test_top.env.drv [PIPE] response id=4 addr=0c
    UVM_INFO seq_lab_tb.sv(96) @ 65000: uvm_test_top.env.drv [PIPE] response id=3 addr=08
    UVM_INFO seq_lab_tb.sv(96) @ 95000: uvm_test_top.env.drv [PIPE] response id=2 addr=04
    UVM_INFO seq_lab_tb.sv(96) @ 125000: uvm_test_top.env.drv [PIPE] response id=1 addr=00
    UVM_INFO seq_lab_tb.sv(241) @ 125000: uvm_test_top.env.sqr@@pipe [SEQ] got  id=1 rdata=a0000000
    UVM_INFO seq_lab_tb.sv(241) @ 125000: uvm_test_top.env.sqr@@pipe [SEQ] got  id=2 rdata=a0000004
    UVM_INFO seq_lab_tb.sv(241) @ 125000: uvm_test_top.env.sqr@@pipe [SEQ] got  id=3 rdata=a0000008
    UVM_INFO seq_lab_tb.sv(241) @ 125000: uvm_test_top.env.sqr@@pipe [SEQ] got  id=4 rdata=a000000c

    With the handler version, each response is processed the moment it arrives, in arrival order:

    Diagram
    UVM_INFO seq_lab_tb.sv(96) @ 35000: uvm_test_top.env.drv [PIPE] response id=4 addr=0c
    UVM_INFO seq_lab_tb.sv(254) @ 35000: uvm_test_top.env.sqr@@hseq [HANDLER] id=4 rdata=a000000c
    UVM_INFO seq_lab_tb.sv(96) @ 65000: uvm_test_top.env.drv [PIPE] response id=3 addr=08
    UVM_INFO seq_lab_tb.sv(254) @ 65000: uvm_test_top.env.sqr@@hseq [HANDLER] id=3 rdata=a0000008

    What breaks: the overflow you may never see

    A driver switched from item_done() to item_done(rsp) with a fresh response object, and a polling sequence sent ten reads without ever calling get_response. With the error report on, as UVM 1.2 has it by default, the 9th and 10th responses are dropped loudly:

    Diagram
    UVM_ERROR @ 195000: uvm_test_top.env.sqr@@flood [uvm_test_top.env.sqr.flood] Response queue overflow, response was dropped
    UVM_ERROR @ 215000: uvm_test_top.env.sqr@@flood [uvm_test_top.env.sqr.flood] Response queue overflow, response was dropped
    UVM_INFO seq_lab_tb.sv(212) @ 215000: uvm_test_top.env.sqr@@flood [FLOOD] 10 reads done, 8 responses waiting, depth 8

    Now the uncomfortable part. Run the same sequence on the IEEE 1800.2-2020 library used for this course's benches, without touching any knob, and the log is clean: 10 reads done, 8 responses waiting, depth 8 and UVM_ERROR : 0. In that library the report flag starts disabled, so the two responses vanish without a word. The two lines above come from the same run with set_response_queue_error_report_disabled(0) called first. Do not rely on the error to find this bug; rely on the rule instead: a sequence collects every response it causes, or the driver does not send one.

    Note

    In IEEE 1800.2: the switch is named set_response_queue_error_report_enabled(bit); the UVM 1.2 name set_response_queue_error_report_disabled(bit) is still provided. Check your library's default before trusting a clean log.

    Interview

    "How do you get read data back to a sequence from a pipelined driver?"

    A weak answer: "Call item_done(rsp) and the sequence reads req.rdata." A pipelined driver has already released the request, so req.rdata is never written, and nothing collects the queued responses.

    A strong answer: "The driver takes requests with get(), which releases the sequence at once, and later builds a separate response object, calls set_id_info(req) so the sequencer can route it, and sends it with put_response. The sequence records each request's transaction ID after finish_item and collects with get_response(rsp, id), or enables use_response_handler and handles responses in the callback. Every response must be collected: the queue holds 8 by default and the 9th is dropped, with an error in UVM 1.2."

    Follow-ups: What does set_id_info copy? (the sequence ID, which routes the response, and the transaction ID, which matches it). When is item_done(rsp) right? (an in-order driver whose response is a separate object and whose sequence calls get_response every time).

    Check yourself A driver calls item_done(rsp) where rsp is created with type_id::create but set_id_info is never called. What happens on the first response?

    The response's sequence ID is still -1, so the sequencer cannot tell which sequence it belongs to and stops with a SQRPUT fatal: "Driver put a response with null sequence_id". set_id_info(req) is what makes routing possible.

    Check yourself The pipelined sequence calls get_response(rsp) (no ID) four times instead of by ID. What does it get?

    The responses in arrival order: ID 4, 3, 2, 1 in the lab run. That is fine if the sequence matches each one to its request by the ID it carries, and wrong if it assumes the k-th response belongs to the k-th request, which is exactly the opening bug.

    Check yourself A sequence ends while two of its reads are still outstanding in the pipelined driver. What happens to their responses?

    When the driver puts them, the sequencer can no longer find the sequence and drops each one with a warning ("Dropping response for sequence N, sequence not found"). A sequence that causes responses must wait for all of them before its body returns.

    Next: sometimes a sequence must end before its work is done, because the DUT went into reset. Stopping sequences and reset shows how to stop one cleanly, and the one call order that ends in a fatal.

    Related topics

    • The driver handshake

    • The driver run-phase loop

    • Scoreboard architecture

    Previous

    81 / 280

    Next: Stopping Sequences and Reset

    Up next

    Up nextStopping Sequences and Reset

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Sequence Lifecycle and Hooks: What start() Does, Step by Step
    • Sequences and Objections: Who Keeps the Phase Open
    • The uvm_do Macro Family: What It Expands To