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

AboutRoadmapCareersInternshipsFAQContactPrivacyTerms
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. Item vs Driver Boundary: What Belongs Where
On this page

    Part 5 of 14, lesson 1 of 21Part 5 of 14/Sequences/Lesson 1 of 21Intermediate

    Item vs Driver Boundary: What Belongs Where

    A sequence item carries intent; the driver owns the mechanics. The bench's apb_item and axis_item, response fields filled by the driver, control knobs such as idle, and the monitor-rebuild test.

    A register sequence works perfectly on the block bench. At subsystem level the APB clock runs four times slower, and the same sequence starts reading STATUS before the DMA has even seen the GO bit. Nobody changed the sequence. It had a #40ns between the write and the read, because on the block bench that was four clocks. At the new clock it is one.

    The bug is not the number. It is that a decision about time on the pins was made in the sequence, which cannot know what the pins are doing. This lesson draws the line between what a transaction says and how the driver makes it happen, using the two items of the DMA bench.

    The idea: an item is an order slip, not a recipe

    Think of a restaurant. The waiter writes an order slip: "steak, medium, no salt, after the starter". The slip says what is wanted and in what order. It never says how hot the grill is or how long to sear each side; that is the cook's job, and a different kitchen may do it differently and still serve the same dish.

    A sequence item (a class extending uvm_sequence_item) is that slip. It describes one operation on an interface: the address, the data, read or write. A driver is the cook. It owns every clock edge, every handshake wait and every pin. The split is what lets one sequence run unchanged on a block bench, a subsystem bench and an emulator, and what lets one driver serve every sequence that anyone writes for that interface.

    The line is not "no timing information in items, ever". The senior version is: intent may live in the item; mechanics live in the driver. "Wait three idle clocks before this beat" is intent, expressed in units of the protocol. "#30ns" or "@(posedge vif.clk)" is mechanics, and it belongs in the driver.

    The picture: who writes each field, and when

    Item

    Field

    rand?

    Written by

    Read by

    apb_item

    addr, write, data

    yes

    the sequence, by randomize() after the grant

    the driver, to drive PADDR, PWRITE, PWDATA

    apb_item

    rdata, slverr

    no

    the driver, in the cycle PREADY is high

    the sequence, after finish_item returns

    axis_item

    data, last

    yes

    the sequence

    the driver; the monitor fills them from the pins

    axis_item

    idle

    yes

    the sequence

    the driver only; no monitor can see it

    Read the last column. Every field that a driver reads came from the sequence; every field the sequence reads back came from the driver. Nothing in either item points at an interface, a component or a clock.

    Build it: the two items of the DMA bench

    apb_item: a request and its answer in one object

    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
    // snippet: from the course DMA bench
    class apb_item extends uvm_sequence_item;
      rand bit [7:0]  addr;
      rand bit        write;
      rand bit [31:0] data;     // write data
      bit      [31:0] rdata;    // filled by the driver on reads
      bit             slverr;   // filled by the driver
      constraint word_aligned_c { addr[1:0] == 2'b00; }
    
      `uvm_object_utils(apb_item)
      function new(string name = "apb_item");
        super.new(name);
      endfunction
    
      function string convert2string();
        return write ? $sformatf("WR addr=%02h data=%08h%s", addr, data, slverr ? " SLVERR" : "")
                     : $sformatf("RD addr=%02h rdata=%08h%s", addr, rdata, slverr ? " SLVERR" : "");
      endfunction
    
      function void do_copy(uvm_object rhs);
        apb_item r;
        super.do_copy(rhs);
        $cast(r, rhs);
        addr = r.addr; write = r.write; data = r.data; rdata = r.rdata; slverr = r.slverr;
      endfunction
    endclass

    Three fields describe the request and are rand, so a sequence can randomize them. Two fields carry the answer and are not rand: the driver writes rdata and slverr when the slave completes the access, and the sequence reads them once finish_item returns. Keeping the answer in the request object is the simplest response scheme there is, and it is why the bench's driver calls item_done() with no argument (the driver handshake lesson explains the consequence).

    word_aligned_c is a legality constraint: no access on this bus may be misaligned, so the rule belongs to the item and every randomize call obeys it. Test intent, such as "write KEY", does not belong here; it arrives as an in-line constraint from the sequence. The item also writes convert2string and do_copy by hand; Objects that copy, compare and print compares that with field macros.

    axis_item: a knob the driver turns into clocks

    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
    // snippet: from the course DMA bench
    class axis_item extends uvm_sequence_item;
      rand bit [31:0]   data;
      rand bit          last;
      rand int unsigned idle;        // clocks of TVALID=0 before this beat (driver only)
      constraint idle_c { idle dist { 0 := 6, [1:3] := 3, [4:8] := 1 }; }
    
      `uvm_object_utils(axis_item)
      function new(string name = "axis_item");
        super.new(name);
      endfunction
    
      function string convert2string();
        return $sformatf("data=%08h%s", data, last ? " LAST" : "");
      endfunction
    
      function void do_copy(uvm_object rhs);
        axis_item r;
        super.do_copy(rhs);
        $cast(r, rhs);
        data = r.data; last = r.last; idle = r.idle;
      endfunction
    endclass

    data and last are the payload of one stream beat. idle is different: it is a control knob, the number of clocks the driver holds TVALID low before offering this beat. It is randomized like everything else, with a distribution that makes most beats back-to-back and a few late, which is what shakes out a design that only works when data arrives smoothly. The driver implements it in two lines, counting edges of whatever clock its interface has:

    design.sv
    1
    2
    3
    4
    // snippet: inside axis_driver::run_phase
    repeat (beat.idle) @(posedge vif.clk);   // intent from the item, clocks from the interface
    @(posedge vif.clk);
    vif.tvalid <= 1'b1;

    Because idle counts clocks, it means the same thing at 100 MHz and at 25 MHz. A #30ns field would not.

    What never goes in an item

    • Virtual interface handles, events on pins, `@` or `#` statements. An item is data. Code that waits belongs in the driver.

    • References to components or sequencers. The item travels through whatever sequencer it is started on; it must not care which.

    • Handshake state, such as "waiting for PREADY". That is the driver's loop, not the transaction.

    • Absolute time. Express delays in protocol units (clocks, beats) and let the driver convert.

    The monitor test: can you rebuild the item from the pins?

    A quick review check sorts every field. Ask: could a passive monitor fill this field from the pins alone? The APB monitor can fill all five apb_item fields, so all five are payload and a scoreboard may compare them. The stream monitor fills data and last but never idle, because idle clocks leave no trace on TDATA. That makes idle a knob: useful to stimulus, invisible to checking.

    Knobs are legitimate, and most production VIP has them (delays, error-injection flags, retry counts). The rule is that they must not take part in comparisons. With field macros you add UVM_NOCOMPARE to the knob; with a hand-written do_compare you leave it out. The bench sidesteps the question entirely: its scoreboard compares observed words, not items, so a knob can never cause a false mismatch.

    Watch it run

    Run dma_smoke_test with +UVM_VERBOSITY=UVM_HIGH and the monitors print every item they rebuild. An excerpt from a Verilator run (times in ps; the data values depend on the simulator and seed):

    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(299) @ 205000: uvm_test_top.env.in_agt.mon [AXIS_MON] data=1364cd0b
    UVM_INFO dma_tb.sv(299) @ 255000: uvm_test_top.env.in_agt.mon [AXIS_MON] data=7980e079
    UVM_INFO dma_tb.sv(299) @ 275000: uvm_test_top.env.in_agt.mon [AXIS_MON] data=cbccc8de
    ...
    UVM_INFO dma_tb.sv(151) @ 475000: uvm_test_top.env.apb.mon [APB_MON] RD addr=04 rdata=00000002
    • The three APB writes (KEY, LEN, CTRL) are 40 ns apart: the driver's setup clock, the access clock, one wait state and the clock it waits before starting. The sequence asked for none of that timing.

    • The input beats arrive 50 ns, then 20 ns apart. Those gaps are the idle knob at work; the monitor's items carry only data and last, as the monitor test predicts.

    • The read of STATUS returns 00000002 (DONE). The same value travelled back to the test inside the request object.

    What breaks: the delay that was really a clock count

    Back to the opening. The sequence wrote CTRL with GO, waited #40ns, then read STATUS once and treated BUSY == 0 as "job finished". On the 100 MHz block bench the DMA had raised BUSY long before the read. On the subsystem bench, with a slower APB clock and the bus behind a bridge, the read overtook the GO: STATUS said idle, the sequence moved on, and the test checked a job that had not started. It passed for a week, because the scoreboard happened to see the data later in the run.

    The fix had two parts. The wait became a condition, not a duration: poll STATUS until DONE or ERR, with a bounded count. And where a real gap was needed, it became a knob in protocol units, like idle, that the driver turns into clocks. Rule of thumb for reviews: if a sequence contains # followed by a number, ask what condition it is standing in for.

    "What goes in a sequence item, and what goes in the driver?"

    A weak answer: "Data in the item, timing in the driver, never any delays in an item." The interviewer then points at the delay and error knobs in every commercial VIP, and the rule falls apart.

    A strong answer: "The item carries intent: payload fields a monitor can rebuild from the pins, response fields the driver fills in, and optionally knobs such as an idle count or an inject-error flag, expressed in protocol units. The driver owns mechanics: edges, handshakes and pins. Knobs are excluded from compare. I test each field by asking whether a passive monitor could fill it; if not, it is a knob or it does not belong."

    Follow-ups: Where do response fields go? (in the request for simple in-order buses; a separate response object when the driver is pipelined, covered in Responses and pipelined drivers). Would you ever put a virtual interface in an item? (no; the item would stop being portable and could not be recorded, copied or compared meaningfully).

    Check yourself A teammate adds rand int unsigned delay_ns to apb_item, and the driver does #(req.delay_ns * 1ns) before each transfer. What do you ask them to change?

    Express the knob in clocks (idle_clks) and have the driver count edges of its own interface clock, as axis_driver does with idle. A delay in nanoseconds means a different number of cycles on every clock frequency and lands between edges, so it can shift a drive off the edge the protocol expects. Also make sure the knob is excluded from any compare.

    Check yourself Why are rdata and slverr not declared rand in apb_item?

    They are answers, not requests. If they were rand, every randomize() would fill them with random values, and a driver bug that forgot to write them would hand the sequence a plausible-looking random read value instead of an obvious zero. Non-random response fields also keep the constraint solver from wasting effort on them.

    Check yourself A scoreboard calls compare() between the axis_item objects a sequence sent and the ones the input monitor saw. Half the compares fail although the data is right. Why, and what is the fix?

    The monitor never sets idle, so it stays 0 in observed items while the sent items carry random idle counts. idle is a knob, so it must be excluded from comparison (UVM_NOCOMPARE with field macros, or left out of do_compare). Better still, compare observed input against observed output, as the bench does, rather than trusting the sequence's items.

    Next: the item says what is legal; the sequence says what it wants this time. Constraints and randomize() with shows how the two combine, and the one-word mistake that turns a directed register write into a random one.

    Related topics

    • Your first UVM testbench

    • Objects that copy, compare and print

    • Agent architecture

    Previous

    73 / 280

    Next: Constraints and randomize() with: Legal Space, Defaults and Corners

    Up next

    Up nextConstraints and randomize() with: Legal Space, Defaults and Corners

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • 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