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. Field Macros and Item Ownership: Who May Change an Item

Part 5 · Sequences · Intermediate

Field Macros and Item Ownership: Who May Change an Item

Field macros versus hand-written methods for items, flags for knobs, and the ownership rules that keep items honest: the sequence owns, the driver borrows, the monitor creates fresh, and a change means a clone.

On this page

    The first version of the truncating driver was three lines long. On the beat where it wanted the packet to end early, it wrote it.last = 1 and drove the item. The DMA reported ERR as expected and the test passed. Two days later the coverage team asked why their sequence-side coverage claimed that the generator was producing four-beat packets, when every test asked for eight.

    Nothing was wrong with the generator. The driver had written into an object the sequence still owned. This lesson sets out who owns an item at each moment, how field macros and hand-written methods support copying it, and the one-line habit that fixed the driver.

    The idea: the sequence owns the item; everyone else borrows or copies

    An item is a class object, so every component that receives it receives a handle, a reference to the same memory. Nothing in SystemVerilog stops two pieces of code from writing the same object. The discipline has to come from rules, and UVM code follows four:

    • The sequence creates and owns the request. It may change it until finish_item hands it over, and may read it again after finish_item returns.

    • The driver borrows it between get_next_item and item_done. It writes only the response fields (rdata, slverr) and never changes the request fields.

    • A monitor creates a fresh object for every transfer it observes, because subscribers may keep the handle (Analysis broadcast explains why).

    • Anyone who needs a changed version clones first and changes the clone.

    Think of a signed contract passed around an office. Anyone may read it, the counterparty may fill in the boxes marked for them, but if you want to propose different terms you photocopy it and mark up the copy.

    The picture: ownership across one transfer

    Moment

    Who holds the handle

    Who may write which fields

    create to start_item

    The sequence

    The sequence: anything

    After start_item (granted)

    The sequence

    The sequence, usually through randomize()

    get_next_item to item_done

    Sequence and driver

    The driver: response fields only

    After finish_item returns

    The sequence (and anyone it shared it with)

    The sequence: reads responses; reuses only if nobody kept the handle

    A monitor sees the transfer on the pins

    A new object per transfer

    The monitor fills it, then treats it as read-only once written to its port

    Build it: copying support, then the clone

    Field macros or hand-written methods

    To clone an item, the item must know how to copy itself. The bench writes do_copy and convert2string by hand. The same axis_item with field macros, which generate print, copy, compare and pack code from a field list, looks like this:

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // snippet: the field-macro version of axis_item (the bench writes do_copy by hand)
    class axis_item extends uvm_sequence_item;
      rand bit [31:0]   data;
      rand bit          last;
      rand int unsigned idle;
      constraint idle_c { idle dist { 0 := 6, [1:3] := 3, [4:8] := 1 }; }
    
      `uvm_object_utils_begin(axis_item)
        `uvm_field_int(data, UVM_ALL_ON)
        `uvm_field_int(last, UVM_ALL_ON)
        `uvm_field_int(idle, UVM_ALL_ON | UVM_NOCOMPARE)   // a knob: copied, never compared
      `uvm_object_utils_end
    
      function new(string name = "axis_item");
        super.new(name);
      endfunction
    endclass

    The flag on idle is the one item-specific point: a knob is copied with the item, so a clone drives the same way, but it is excluded from compare, so a monitored item (which never has it) does not mismatch. The full trade-off between macros and hand-written do_* methods, including speed and debuggability, is in Objects that copy, compare and print.

    The driver hook that clones before it changes

    The bench's axis_driver calls a virtual hook, pre_drive, on every item before driving it. The base version returns the item unchanged. The truncating driver, installed by a factory override in dma_truncate_test, uses the hook to end packets early:

    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
    // snippet: from the course DMA bench
    // Fault injection without touching the sequence: cuts every packet after beat
    // `cut` by setting TLAST there and dropping the rest of the packet.
    class axis_truncating_driver extends axis_driver;
      `uvm_component_utils(axis_truncating_driver)
      int unsigned cut = 3;           // index of the beat that gets TLAST
      int unsigned beat_no;
    
      function new(string name, uvm_component parent);
        super.new(name, parent);
      endfunction
    
      function axis_item pre_drive(axis_item it);
        axis_item b;
        int unsigned i = beat_no;
        beat_no = it.last ? 0 : beat_no + 1;    // count beats per packet
        if (i > cut) return null;                // after the cut: drop the beat
        if (i < cut) return it;
        $cast(b, it.clone());                    // never edit the sequence's own item
        b.last = 1'b1;
        `uvm_info("TRUNC", $sformatf("TLAST forced on beat %0d", i), UVM_MEDIUM)
        return b;
      endfunction
    endclass
    • Before the cut it returns the sequence's own item: no change, no copy needed.

    • On the cut beat it clones, sets last on the clone, and returns the clone. The sequence's item still says what the sequence asked for.

    • After the cut it returns null, and the base driver skips the pins but still calls item_done, so the sequence is never left waiting.

    • clone() creates a new object of the same type with the class's create method, fills it with copy, and returns it as a uvm_object, so a $cast back to axis_item is needed, and do_copy (or field macros) must copy every field.

    Fresh item or reuse?

    The bench creates a new item for every start_item, which is the safe default. Reusing one item for a loop of transfers is legal: once finish_item returns, the driver has let go. It becomes a bug only when something kept the handle, such as a sequence that pushes each sent item onto a queue for later checking, or a response that points back at the request. If anything might keep the handle, create a new item. The cost of create is small next to an afternoon spent finding an item that changed under a scoreboard.

    Watch it run

    dma_truncate_test forces TLAST on beat 3 of an eight-beat packet. The DMA sees a short packet, ends the job with ERR, and the scoreboard matches the four words that were really sent:

    Diagram
    UVM_INFO dma_tb.sv(520) @ 275000: uvm_test_top.env.in_agt.drv [TRUNC] TLAST forced on beat 3
    UVM_INFO dma_tb.sv(761) @ 385000: uvm_test_top [TRUNC] DMA reported ERR for the short packet, as expected
    UVM_INFO dma_tb.sv(411) @ 485000: uvm_test_top.env.sb [SB] 4 words matched, 0 mismatched
    UVM_ERROR :    0

    The scoreboard predicts from the input monitor, which created its own items from the pins, so it saw exactly four beats with the last one marked. The sequence's eight items are untouched; it sent eight requests, and that is what any record of the sequence still says.

    What breaks: coverage of packets nobody asked for

    Back to the coverage puzzle. The coverage collector sampled each sequence's items when the sequence finished, to record what the generator had requested. With the three-line driver writing it.last = 1 into the sequence's item, the record showed a packet ending at beat 3, so coverage of "requested length" collapsed to four. Worse, a later version of the sequence retried failed packets by resending the same item handles, and the retries went out truncated too.

    The fix was the $cast(b, it.clone()) line you see in the bench, plus a review rule: a driver writes only response fields of the item it was given. When it needs a different request, it builds one. If a driver must change the request itself, for example to resolve an address through a memory map, it does so on a clone and returns the response separately.

    Interview

    "Your driver needs to modify a transaction before driving it, for error injection. How do you do it?"

    A weak answer: "Just change the field in req before driving it." It works in a demo and corrupts every record, retry and scoreboard that trusted the sequence's object.

    A strong answer: "Through a virtual hook in the driver, like the bench's pre_drive, overridden in a derived driver installed with a factory override. The hook clones the item, changes the clone and drives that. The sequence's request stays as requested; the driver only ever writes response fields into it. The item needs complete copy support, by field macros or do_copy, and knobs are flagged UVM_NOCOMPARE so monitored items compare cleanly."

    Follow-ups: clone versus copy? (copy fills an existing object; clone creates a new object of the same type and copies into it, returning uvm_object). What about nested objects? (field macros and do_copy copy a handle unless told to deep-copy; see Objects that copy, compare and print).

    Check yourself The base axis_driver drives beat, the value pre_drive returned, but calls item_done() for req. Why does that matter when pre_drive returns a clone or null?

    The handshake is about the item the sequencer handed out, not about what reached the pins. Whatever pre_drive returns, the driver must release req exactly once, or the sequence's finish_item never returns. The clone is the driver's private object; the sequencer never knew about it.

    Check yourself A sequence reuses one apb_item for 100 writes and also pushes each one onto a queue sent_q for an end-of-test check. What does the check see?

    100 handles to the same object, all showing the last write's values. Reuse after finish_item is fine only when nobody keeps the handle. Either create a new item per transfer or push a clone.

    Check yourself Your monitor reuses a single axis_item and calls ap.write(it) after filling it each beat, to save allocations. The scoreboard stores items in a queue. What goes wrong?

    Every queued entry is the same object, so each new beat overwrites all the stored ones and the scoreboard compares the latest beat against itself or against stale predictions. A monitor must create a new object per transfer; subscribers copying on receipt is only a second line of defence.

    Next: you have seen the driver borrow an item and give it back. The driver handshake draws exactly how that borrowing works, call by call, and what each side is blocked on.

    Related topics

    • Objects that copy, compare and print

    • Scoreboard architecture

    • Driver injection hooks

    Previous

    76 / 280

    Next: The Driver Handshake: start_item, finish_item, get_next_item, item_done

    Up next

    Up nextThe Driver Handshake: start_item, finish_item, get_next_item, item_done

    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
    • Stalled Sequences: Hang Triage