Verification

UVM sequence item

By CourseTron Team ·  8 min read ·  Updated Oct 2026 💬 𝕏 in f
Table of Contents
    ? Print / Save as PDF

    In short: A UVM sequence item is the randomizable transaction object — derived from uvm_sequence_item — that carries stimulus between sequences and drivers, and its power comes from rand fields plus constraints and the copy/compare/print infrastructure you wire up through the field automation macros or hand-written do_* hooks.

    If you have ever run a constrained-random testbench on VCS, Xcelium, or Questa, nearly every packet, bus burst, or instruction that moves through it started life as a UVM sequence item. Understanding this object well separates engineers who merely assemble a testbench from those who can debug a mismatch at 2 a.m. without guessing. This article walks through what the transaction object actually is, how its fields are declared and randomized, and what the four core data operations — copy, compare, print, and pack — really do under the hood.

    What a sequence item is

    A sequence item is a pure data object. It extends uvm_sequence_item (which in turn extends uvm_transaction and ultimately uvm_object), so it lives on the heap, is created with the factory, and has no ports, no clock, and no notion of time. It describes one atomic unit of stimulus or response: an AXI write beat, a SPI frame, an Ethernet packet, a floating-point operand pair. The driver's job is to translate that abstract description into pin wiggles; the item itself never touches a wire.

    Rule of thumb: if a field would help you describe a transaction on a whiteboard, it belongs in the sequence item. If it describes how the transaction is driven on pins (setup time, phase), it belongs in the driver or the config object — not the item.

    Sequence item: from sequence to driver Sequence randomize() seq_item rand addr rand data[] rand kind Driver drives pins start_item get_next_item The item is timeless data; only the driver touches pins. CourseTron · coursetron.com
    The sequence builds and randomizes the item; the driver pulls it over the sequencer and converts it to pin-level activity.
    Class hierarchy of a sequence item uvm_object factory, copy, compare, print uvm_transaction base class for transactions uvm_sequence_item sequence id and txn id handshake bus_txn rand addr, data, kind + constraints Arrows point from child class to parent class. No ports, no clock, no notion of time: pure data. CourseTron · coursetron.com
    A sequence item extends uvm_sequence_item, which extends uvm_transaction and ultimately uvm_object.

    Declaring fields and registering with the factory

    A minimal item declares its data as class properties, marks the random ones rand, and registers with the factory. Most teams use the field-automation block so the base class can synthesize the data operations:

    class bus_txn extends uvm_sequence_item;
      rand bit [31:0] addr;
      rand bit [31:0] data[];      // variable-length burst
      rand enum {READ, WRITE} kind;
           int unsigned id;        // non-random bookkeeping
    
      `uvm_object_utils_begin(bus_txn)
        `uvm_field_int(addr, UVM_ALL_ON)
        `uvm_field_array_int(data, UVM_ALL_ON)
        `uvm_field_enum(kind, kind, UVM_ALL_ON)
      `uvm_object_utils_end
    
      constraint c_burst { data.size() inside {[1:16]}; }
    
      function new(string name="bus_txn");
        super.new(name);
      endfunction
    endclass

    The `uvm_field_* macros are convenient but not free: they expand into generic, reflection-style loops that walk a field table at run time. On large items driven millions of times, that overhead is measurable. Many production shops (and the UVM 1.2/IEEE 1800.2 guidance) therefore prefer hand-written do_* methods for hot-path transactions and reserve the macros for occasional config objects.

    Randomization and constraints

    Marking a field rand only makes it a candidate; constraints decide the legal space. When a sequence calls item.randomize(), the SystemVerilog constraint solver (Synopsys, Cadence, and Siemens each ship their own) picks a value for every active rand field that satisfies all active constraints simultaneously. You layer additional constraints at the call site with randomize() with { ... }, and you can switch individual variables off using rand_mode(0) or whole constraints with constraint_mode(0).

    Always check the return value: if(!item.randomize()) `uvm_fatal(...). A silent randomization failure is one of the most common causes of a test that "passes" while driving all-zero stimulus.

    Functional coverage then closes the loop — covergroups sample these same fields so you can prove the random space you intended was actually exercised. For a deeper treatment of how items are produced and arbitrated, see our companion piece on the UVM sequence and sequencer.

    do_copy, do_compare, do_print, do_pack

    Every uvm_object exposes public wrappers — copy(), compare(), print(), pack()/unpack() — that call protected virtual hooks you override. This is the heart of transaction handling in a scoreboard.

    Public callVirtual hookWhat you implement
    copy(rhs)do_copyDeep-copy each field so the clone is independent (no shared dynamic arrays)
    compare(rhs)do_compareField-by-field equality via a uvm_comparer; return 1 only if all match
    print()do_printEmit each field through a uvm_printer for readable logs
    pack()do_packSerialize fields into a bitstream (register models, reference-model I/O)

    A hand-written pair looks like this — note that do_copy must duplicate the dynamic array, not alias it:

    function void do_copy(uvm_object rhs);
      bus_txn t;
      super.do_copy(rhs);
      $cast(t, rhs);
      addr = t.addr; kind = t.kind;
      data = new[t.data.size()];     // fresh storage
      foreach(data[i]) data[i] = t.data[i];
    endfunction
    
    function bit do_compare(uvm_object rhs, uvm_comparer comparer);
      bus_txn t;
      if(!$cast(t, rhs)) return 0;
      return super.do_compare(rhs, comparer)
           & comparer.compare_field_int("addr", addr, t.addr, 32)
           & comparer.compare_field("kind", kind, t.kind, 2);
    endfunction

    Why does this matter in practice? A shallow copy that aliases data is a classic bug: the scoreboard stores a "golden" clone, the driver later mutates the original, and both now point at the same array — so the comparison always matches and real mismatches vanish. Routing do_compare through the uvm_comparer (rather than ad-hoc if statements) also gives you miscompare logging, a configurable abort count, and a verbosity knob for free.

    Scoreboard: do_compare on every item expected A5 A5 actual (DUT) A5 A5 do_compare MATCH expected is a deep copy made with do_copy CourseTron · coursetron.com
    The scoreboard clones the expected item (do_copy) and runs do_compare against the DUT's actual item every transaction.

    Common pitfalls

    • Forgetting the deep copy for dynamic arrays, queues, or sub-objects — the single most frequent scoreboard defect.
    • Mixing stimulus and control — putting a vif handle or a wait inside the item. Items are timeless data.
    • Macro overuse — UVM_ALL_ON on every field inflates simulation time and log noise; turn off UVM_COMPARE or UVM_PRINT bits where they add nothing.
    • Not overriding convert2string() — a one-line, human-readable dump beats paging through uvm_printer output during triage.

    For teams building real silicon, clean transaction modelling is also the foundation for reuse across blocks — an approach we apply in production at Avecas VLSI design services. If you want to go further into reusable stimulus layers, the UVM virtual sequence article is the natural next step, and the full curriculum lives on our courses page.

    Ready to build a scoreboard that catches real bugs? Join the CourseTron UVM Verification track and code every one of these do_* methods on a live AXI testbench.

    Frequently asked questions

    What is the difference between uvm_sequence_item and uvm_transaction?

    uvm_sequence_item extends uvm_transaction and adds the handshaking fields the sequencer needs — sequence id, transaction id, and the use_response_handler plumbing. You always extend uvm_sequence_item for stimulus so items can flow through a sequencer.

    Should I use the field macros or write do_copy/do_compare by hand?

    Macros are fine for config objects and low-frequency items. For high-throughput transactions, hand-written do_* methods run faster and give you exact control, which is why most mature verification teams write them explicitly.

    Why does my randomization produce the same values every run?

    Usually the seed is fixed. Change the simulator seed (+ntb_random_seed on VCS, equivalents on Xcelium/Questa), confirm the fields are actually rand, and verify randomize() returned 1 rather than silently failing a constraint.

    Does a sequence item consume simulation time?

    No. It is a data object with no clock or ports. Only the driver advances time when it converts the item into pin activity.

    Advertisement