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
      • Item vs Driver Boundary: What Belongs Where
      • Constraints and randomize() with: Legal Space, Defaults and Corners
      • Derived Items: Narrower Stimulus Without Editing the Agent
      • Field Macros and Item Ownership: Who May Change an Item
      • The Driver Handshake: start_item, finish_item, get_next_item, item_done
      • Stalled Sequences: Hang Triage
    • Sequence Mechanics: Lifecycle, Sequencer, Responses and Control
    • 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 Items and the Driver Handshake
  1. Topics
  2. UVM Verification
  3. UVM Sequences & Stimulus
  4. Sequence Items and the Driver Handshake
  5. The Driver Handshake: start_item, finish_item, get_next_item, item_done

Part 5 · Sequences · Intermediate

The Driver Handshake: start_item, finish_item, get_next_item, item_done

The four-call handshake between sequence, sequencer and driver, drawn as one sequence diagram; the bench's APB and stream driver loops; item_done() with no argument; the exactly-once rule; try_next_item and get.

On this page

    An interviewer slides a marker across the table: "Draw what happens between start_item and item_done. Who blocks, and on what?" Most candidates draw a sequence, an arrow, and a driver. The follow-up questions, when is the sequence granted?, when does randomize run?, what if the driver calls item_done twice?, all live in the space that drawing leaves out.

    This lesson fills it in. It is the home of the section's key picture: the four calls, the three parties, and what each one waits for.

    The idea: a pass between the kitchen and the floor

    In a restaurant kitchen, the pass is the counter between the cooks and the waiters. Waiters queue their order slips there; the head chef picks the next one when a cook is free; the cook takes it, cooks, and puts the plate back on the pass, which tells the waiter it is ready. The sequencer is the pass. Sequences queue requests on it, the driver takes one when it is ready, and the driver's "done" is what releases the sequence.

    The crucial word is pull. The driver asks for work when it is free. A sequence that calls start_item is only asking for a turn; it is granted when the driver next asks for an item and the sequencer's arbitration picks that sequence. Everything that confuses people about the handshake follows from that one fact.

    The picture: one transfer, call by call

    Sequence diagram with three lifelines: the driver asks for an item, the sequence asks for a grant and is granted, randomizes, sends the item, the driver receives and drives it, calls item_done, and the sequence's finish_item returns.
    The handshake for one APB access. The grant happens only when the driver asks; finish_item returns only after item_done.
    • `start_item` blocks until the grant. If the driver is busy with another transfer, the sequence waits here. With several sequences on one sequencer, this is where they queue (see Inside the sequencer).

    • Randomize between the two calls. The values are chosen after the grant, at the last moment before the driver sees the item.

    • `finish_item` blocks until `item_done`. The sequence cannot run ahead of the driver, which is why it can read rdata the moment finish_item returns.

    • `get_next_item` blocks until an item is sent. A driver with nothing to do simply waits there.

    Build it: the bench's two driver loops

    The APB driver: one item, one transfer, one item_done

    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
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    // snippet: from the course DMA bench
    class apb_driver extends uvm_driver #(apb_item);
      `uvm_component_utils(apb_driver)
      virtual apb_if vif;
    
      function new(string name, uvm_component parent);
        super.new(name, parent);
      endfunction
    
      function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        if (!uvm_config_db#(virtual apb_if)::get(this, "", "vif", vif))
          `uvm_fatal("NOVIF", "apb_if not set for this driver")
      endfunction
    
      task run_phase(uvm_phase phase);
        vif.psel    <= 1'b0;
        vif.penable <= 1'b0;
        @(posedge vif.rst_n);
        forever begin
          seq_item_port.get_next_item(req);
          drive(req);
          seq_item_port.item_done();     // rdata travels back inside req itself
        end
      endtask
    
      // Setup phase: PSEL=1, PENABLE=0 for one clock. Access phase: PENABLE=1, held
      // until the slave answers PREADY=1; read data is valid in that cycle.
      // Every wait is on the same event, @(posedge vif.clk), and every drive is a
      // nonblocking assignment, so a value read right after the edge is the value
      // from before it: the one the DUT sampled.
      task drive(apb_item it);
        @(posedge vif.clk);
        vif.paddr   <= it.addr;
        vif.pwrite  <= it.write;
        vif.pwdata  <= it.data;
        vif.psel    <= 1'b1;
        vif.penable <= 1'b0;
        @(posedge vif.clk);
        vif.penable <= 1'b1;
        do @(posedge vif.clk); while (!vif.pready);
        it.rdata  = vif.prdata;
        it.slverr = vif.pslverr;
        vif.psel    <= 1'b0;
        vif.penable <= 1'b0;
      endtask
    endclass

    The loop is the canonical shape: get_next_item, drive, item_done, forever. Inside drive, the setup clock raises PSEL with PENABLE low; the access phase raises PENABLE and holds it until PREADY. The do @(posedge vif.clk); while (!vif.pready); form matters: it waits at least one edge before looking at PREADY, so with a zero-wait slave the read data is captured in the cycle the slave really completes the access, not one cycle early.

    Common pitfall

    The driver calls item_done() with no argument. The read data travels back inside the same req object the sequence created, and the sequence reads it after finish_item returns. Writing item_done(req) instead also pushes the item onto the sequence's response queue. A sequence that never calls get_response lets that queue fill: it holds 8 by default, and in UVM 1.2 the 9th response is dropped with a UVM_ERROR (response queue overflow). The bench's one-access apb_rw_seq would never notice, but a polling loop such as apb_poll_seq would. Responses and pipelined drivers covers the response queue properly.

    The sequence side: reading the answer

    The base test's reg_read helper shows the payoff of the in-place response. It starts one apb_rw_seq, and by the time start returns, finish_item has returned inside it, so s.rdata already holds what the slave sent:

    design.sv
    1
    2
    3
    4
    5
    6
    7
    // snippet: from dma_base_test
    task reg_read(bit [7:0] addr, output bit [31:0] data);
      apb_rw_seq s = apb_rw_seq::type_id::create("s");
      s.addr = addr; s.write = 0; s.data = 0;
      s.start(env.apb.sqr);
      data = s.rdata;                  // copied from the item after finish_item returned
    endtask

    The stream driver: a hook, and item_done on every path

    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
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    // snippet: from the course DMA bench
    class axis_driver extends uvm_driver #(axis_item);
      `uvm_component_utils(axis_driver)
      virtual axis_if vif;
    
      function new(string name, uvm_component parent);
        super.new(name, parent);
      endfunction
    
      function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        if (!uvm_config_db#(virtual axis_if)::get(this, "", "vif", vif))
          `uvm_fatal("NOVIF", "axis_if not set for this driver")
      endfunction
    
      task run_phase(uvm_phase phase);
        vif.tvalid <= 1'b0;
        @(posedge vif.rst_n);
        forever begin
          axis_item beat;
          seq_item_port.get_next_item(req);
          beat = pre_drive(req);
          if (beat != null) begin
            repeat (beat.idle) @(posedge vif.clk);
            @(posedge vif.clk);
            vif.tdata  <= beat.data;
            vif.tlast  <= beat.last;
            vif.tvalid <= 1'b1;
            do @(posedge vif.clk); while (!vif.tready);   // the beat moves on TVALID && TREADY
            vif.tvalid <= 1'b0;
          end
          seq_item_port.item_done();
        end
      endtask
    
      // Hook for derived drivers: return the beat to drive (the sequence's item, or a
      // modified copy), or null to drop it. The base driver drives every item as is.
      virtual function axis_item pre_drive(axis_item it);
        return it;
      endfunction
    endclass

    Two details are worth stealing. First, the virtual pre_drive hook lets a derived driver change or drop a beat without copying the loop, which is how dma_truncate_test works. Second, item_done() sits outside the if (beat != null): whether the beat was driven or dropped, the sequencer's item is released exactly once. That exactly-once rule is the whole contract:

    Driver mistake

    What UVM does

    Never calls item_done for an item

    The sequence stays blocked in finish_item. The test hangs until the timeout.

    Calls get_next_item again without an item_done

    UVM_ERROR: "Get_next_item called twice without item_done or get in between".

    Calls item_done twice for one item

    UVM_FATAL SQRBADITMDN: "Item_done() called with no outstanding requests".

    The other pull calls

    • `try_next_item(req)` returns without consuming simulation time, with null if no sequence has an item ready. A driver that must drive idle cycles on every clock when there is no work uses it: if req == null, drive one idle cycle and try again.

    • `get(req)` is get_next_item and item_done in one call: the sequence is released immediately, before anything is driven. Pipelined drivers use it and send responses later; see Responses and pipelined drivers.

    • `peek(req)` returns the next item without completing it; a later get completes it.

    Watch it run

    The APB monitor's lines from dma_smoke_test at UVM_HIGH show the handshake's rhythm (Verilator, times in ps):

    Diagram
    UVM_INFO dma_tb.sv(151) @ 75000: uvm_test_top.env.apb.mon [APB_MON] WR addr=0c data=a5a50f0f
    UVM_INFO dma_tb.sv(151) @ 115000: uvm_test_top.env.apb.mon [APB_MON] WR addr=08 data=00000008
    UVM_INFO dma_tb.sv(151) @ 155000: uvm_test_top.env.apb.mon [APB_MON] WR addr=00 data=00000003
    ...
    UVM_INFO dma_tb.sv(151) @ 475000: uvm_test_top.env.apb.mon [APB_MON] RD addr=04 rdata=00000002
    • One access completes every 40 ns: the driver's wait for the next edge, the setup clock, the access clock and the DMA's one wait state. The next reg_write cannot start sooner, because its sequence is granted only when the driver calls get_next_item again after item_done.

    • The read returns 00000002, DONE set. The test's check of status[1] passes because the driver wrote rdata into the request before item_done.

    What breaks: two clocking blocks, one edge

    While the bench was being built, a random test failed on two jobs out of three. The DMA reported STATUS.ERR because each packet arrived one word short. Yet the sequence had generated all N items, and the stream driver had called item_done N times. By the handshake's own accounting, every beat had been sent.

    The cause was in how the drivers waited. The APB driver finished the GO write on a clock edge. In the same timestep, the stream driver received its first item and waited on the stream interface's clocking block event. The two interfaces had separate clocking blocks on the same clock edge, and the order in which their events fire within a timestep is undefined. The stream driver caught the current edge instead of the next one. Its clocking-block drive and sample then disagreed with what the DUT saw: the driver believed TREADY was high on the first beat and called item_done, but the DUT never took that word.

    The fix is the timing rule the bench uses everywhere: drivers wait on @(posedge vif.clk), a single shared event, and drive with nonblocking assignments; monitors sample through a clocking block with #1step input skew. The lesson for interviews is that item_done reports what the driver believes happened. It is only as trustworthy as the driver's timing, which is why the scoreboard checks the monitored pins and never the driver's count.

    Interview

    "Walk me through start_item, finish_item, get_next_item and item_done."

    A weak answer: "The sequence sends the item with start_item and finish_item, and the driver gets it with get_next_item and says item_done." It names the calls and explains nothing about blocking.

    A strong answer: "start_item asks the sequencer for a grant and blocks until arbitration picks this sequence, which only happens when the driver asks for work. I randomize after it returns. finish_item sends the item and blocks until the driver calls item_done. The driver pulls with get_next_item, drives, fills response fields in place and calls item_done exactly once, so the sequence reads the response right after finish_item. If I passed the response to item_done(rsp), it would also go to the sequence's response queue, which holds 8 by default, so the sequence must call get_response."

    Follow-ups: What if the driver calls item_done twice? (SQRBADITMDN fatal). When would you use try_next_item? (a driver that must keep driving idle cycles when no item is ready).

    Check yourself axis_truncating_driver returns null for beats after the cut. Why does the base driver still call item_done for those beats?

    Each of those beats is an item the sequence is waiting on in finish_item. Skipping item_done would leave axis_pkt_seq blocked forever, and the next get_next_item would report that it was called twice without an item_done. Dropping a beat from the pins does not remove it from the handshake.

    Check yourself A sequence calls start_item at 100 ns, while the APB driver is in the middle of a transfer that ends at 130 ns. When does start_item return, and when are the item's random values chosen?

    Around 130 ns, when the driver calls item_done for the current transfer and then get_next_item, and the sequencer grants this sequence. The values are chosen after that, because the bench randomizes after start_item returns. That is late randomization: the values reflect the moment the item really goes out.

    Check yourself A teammate changes the APB driver to item_done(req). Which bench sequence is at risk, and why not apb_rw_seq?

    Each apb_rw_seq sends one item, so its response queue only ever holds one response, and the sequence ends with it unread. apb_poll_seq loops, sending a read every 50 ns, so after 8 polls its queue is full. From the 9th poll on, responses are dropped, with a UVM_ERROR in UVM 1.2. A long job turns a harmless refactor into a red regression.

    Next: when the handshake breaks, the symptom is silence. Stalled sequences turns that silence into a short list of places to look.

    Related topics

    • The driver run-phase loop

    • TLM ports and exports

    • Inside the sequencer

    Previous

    77 / 280

    Next: Stalled Sequences: Hang Triage

    Up next

    Up nextStalled Sequences: Hang Triage

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Item vs Driver Boundary: What Belongs Where
    • Constraints and randomize() with: Legal Space, Defaults and Corners
    • Derived Items: Narrower Stimulus Without Editing the Agent