UVM Sequence and Sequencer: A Practical Guide

Every UVM testbench eventually needs to answer one question: who decides what stimulus the driver sends, and when? In raw SystemVerilog you would hard-code that logic inside the driver. UVM pulls it out into a separate, reusable object called a uvm sequence, and routes it to the driver through a sequencer. Once you understand how a sequence, a sequencer, and a driver talk to each other, most of UVM stimulus generation stops feeling like magic.

This guide builds the idea up step by step for ECE students and verification engineers in India who are starting with UVM. We cover uvm_sequence, the body() task, the start_item/finish_item handshake, the uvm_sequencer, and the two ways to actually start a sequence.

A note on running the code: the UVM examples below are written for a UVM-capable simulator (Mentor industry-standard simulation tools, or the free EDA Playground) because they depend on the UVM base-class library. They show canonical UVM structure, not something you can run on Icarus Verilog. The one plain-SystemVerilog block (the handshake model) is fully self-contained and runs on any simulator.

What is a sequence item?

A sequence does not send raw bits. It sends transactions — objects that extend uvm_sequence_item. Think of a transaction as one request to your DUT: an ALU operation, a bus read, one AXI beat. Here is a small ALU transaction.

SystemVerilog
class alu_txn extends uvm_sequence_item;
  rand bit [7:0] a, b;
  rand bit [1:0] op;

  `uvm_object_utils_begin(alu_txn)
    `uvm_field_int(a,  UVM_ALL_ON)
    `uvm_field_int(b,  UVM_ALL_ON)
    `uvm_field_int(op, UVM_ALL_ON)
  `uvm_object_utils_end

  function new(string name = "alu_txn");
    super.new(name);
  endfunction
endclass

The uvm_sequence and its body()

A sequence is a uvm_object (not a component — it is created and destroyed at run time, it is not part of the static hierarchy). It is parameterised with the item type it produces: uvm_sequence #(alu_txn). All of its behaviour lives in one task named body(). When you start the sequence, UVM calls body() for you; your job is to fill it with the stimulus you want.

SystemVerilog
class alu_seq extends uvm_sequence #(alu_txn);
  `uvm_object_utils(alu_seq)

  function new(string name = "alu_seq");
    super.new(name);
  endfunction

  task body();
    alu_txn tx;
    repeat (5) begin
      tx = alu_txn::type_id::create("tx");
      start_item(tx);            // ask the sequencer for a turn on the driver
      assert(tx.randomize());    // choose values while we hold the grant
      finish_item(tx);           // send it, and block until the driver is done
    end
  endtask
endclass

Notice the pattern inside body(): create an item, call start_item, randomize, call finish_item. That create-and-handshake loop is the heart of almost every sequence you will ever write.

start_item and finish_item: the handshake

Why are there two calls instead of one “send” call? Because the driver is shared. Many sequences may be running at once, and only one item can be driven at a time. The pair implements a blocking handshake:

  • start_item(tx) — the sequence requests access and blocks until the sequencer’s arbiter grants it a turn. Only after it returns do you own the driver, so this is the right place to randomize or set fields.
  • finish_item(tx) — the sequence hands the item over and blocks until the driver calls item_done(). When it returns, you know the item has been consumed and you can prepare the next one.

The two calls bracket the window during which this sequence, and no other, controls the driver. To make the ordering concrete, here is a plain-SystemVerilog model of the same handshake — no UVM library, so it runs anywhere.

SystemVerilog
module tb;
  // A tiny plain-SystemVerilog model of the sequence-driver handshake.
  // No UVM library is used; it runs on any SystemVerilog simulator.

  task automatic drive(input int d);
    $display("[DRIVER]   got item data=%0d, driving onto bus", d);
  endtask

  // start_item: sequence asks the sequencer for a turn and waits for the grant
  task automatic start_item;
    $display("[SEQ] start_item: requesting grant from sequencer");
    #1;
    $display("[SEQ] start_item: grant received, sequence now owns the item");
  endtask

  // finish_item: sequence hands the item to the driver and blocks until done
  task automatic finish_item(input int d);
    $display("[SEQ] finish_item: handing item data=%0d to driver", d);
    drive(d);
    #1;
    $display("[SEQ] finish_item: returns after driver signals item_done");
  endtask

  task automatic body;
    int payload;
    integer i;
    $display("[SEQ] body() begins");
    for (i = 0; i < 3; i = i + 1) begin
      start_item;
      payload = 10 * (i + 1);   // assign/randomize fields after the grant
      finish_item(payload);
    end
    $display("[SEQ] body() ends");
  endtask

  initial begin
    $display("=== plain-SV model of start_item/finish_item ordering ===");
    body;
    $display("=== all items sent ===");
    $finish;
  end
endmodule

=== plain-SV model of start_item/finish_item ordering ===
[SEQ] body() begins
[SEQ] start_item: requesting grant from sequencer
[SEQ] start_item: grant received, sequence now owns the item
[SEQ] finish_item: handing item data=10 to driver
[DRIVER] got item data=10, driving onto bus
[SEQ] finish_item: returns after driver signals item_done
[SEQ] start_item: requesting grant from sequencer
[SEQ] start_item: grant received, sequence now owns the item
[SEQ] finish_item: handing item data=20 to driver
[DRIVER] got item data=20, driving onto bus
[SEQ] finish_item: returns after driver signals item_done
[SEQ] start_item: requesting grant from sequencer
[SEQ] start_item: grant received, sequence now owns the item
[SEQ] finish_item: handing item data=30 to driver
[DRIVER] got item data=30, driving onto bus
[SEQ] finish_item: returns after driver signals item_done
[SEQ] body() ends
=== all items sent ===
/var/folders/l0/gs5hnkm56g54bmxg32r6nnwr0000gn/T/tmppk65gwuj/tb.sv:40: $finish called at 6 (1s)

Run it and read the interleaving: every start_item prints before its payload is chosen, and every finish_item only returns after the driver has acted. That is exactly the discipline the real UVM calls enforce — the difference is that real UVM blocks on a sequencer arbiter and a TLM port instead of a #1 delay.

The uvm_sequencer

The sequencer is the traffic controller between sequences and the driver. You rarely subclass it; a typedef of the built-in uvm_sequencer parameterised with your item type is enough.

SystemVerilog
typedef uvm_sequencer #(alu_txn) alu_sequencer;

The sequencer does three things. It accepts sequences that want to run, it arbitrates when several sequences ask for a turn at the same time, and it forwards the granted item to the driver. The driver pulls that item through its seq_item_port. The driver side mirrors the sequence side: get_next_item corresponds to finish_item, and item_done is what unblocks it.

SystemVerilog
class alu_driver extends uvm_driver #(alu_txn);
  `uvm_component_utils(alu_driver)

  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction

  task run_phase(uvm_phase phase);
    alu_txn tx;
    forever begin
      seq_item_port.get_next_item(tx);   // receives what finish_item sent
      // ... drive tx onto the DUT interface here ...
      `uvm_info("DRV", $sformatf("op=%0d a=%0d b=%0d", tx.op, tx.a, tx.b), UVM_LOW)
      seq_item_port.item_done();         // unblocks the sequence's finish_item
    end
  endtask
endclass

In the agent’s connect_phase you wire them together with driver.seq_item_port.connect(sequencer.seq_item_export). That single connection is what makes the whole handshake flow.

Starting a sequence on a sequencer

A sequence does nothing until it is started on a sequencer. There are two common ways.

SystemVerilog
// Option 1: start it explicitly from the test's run_phase
task run_phase(uvm_phase phase);
  alu_seq seq;
  phase.raise_objection(this);
  seq = alu_seq::type_id::create("seq");
  seq.start(env.agent.sequencer);   // blocks here until body() finishes
  phase.drop_objection(this);
endtask

// Option 2: register it as the sequencer's default sequence
function void build_phase(uvm_phase phase);
  super.build_phase(phase);
  uvm_config_db #(uvm_object_wrapper)::set(this,
     "env.agent.sequencer.main_phase",
     "default_sequence", alu_seq::type_id::get());
endfunction

Option 1 is the one to learn first: seq.start(sequencer) hands the sequence to that sequencer and blocks until body() returns. The objection keeps the phase alive while stimulus is running. Option 2 lets UVM start the sequence for you at the named phase, which is handy when a base test sets a default and derived tests override it. Both end up calling the same body() through the same sequencer.

Common beginner mistakes

SymptomLikely cause
Simulation ends immediately, no stimulusForgot to raise_objection, or never called seq.start()
Driver hangs foreverMissing item_done(), so finish_item never returns
All items have the same valueRandomized before start_item, or forgot randomize()
Null handle when drivingUsed new() instead of type_id::create() for the item

Keep the mental model simple: a uvm sequence is the what, the sequencer is the router, and the driver is the how. The start_item/finish_item pair is the contract that keeps them in step.

Quick check

Q1. Is a uvm_sequence a component or an object, and which task holds its behaviour?

It extends uvm_object (dynamic, created at run time), not uvm_component. All of its stimulus behaviour lives in the body() task, which UVM calls when the sequence is started.

Q2. Why does a sequence call start_item and finish_item instead of a single send?

They form a blocking handshake around a shared driver. start_item blocks until the sequencer grants a turn (the right moment to randomize), and finish_item blocks until the driver calls item_done(), confirming the item was consumed before the next one is prepared.

Q3. What is the sequencer’s role in the flow?

It receives sequences, arbitrates between sequences that request a turn at the same time, and forwards the granted item to the driver through the TLM connection between seq_item_port and seq_item_export.

Q4. What is the difference between seq.start() and a default sequence?

seq.start(sequencer) runs the sequence explicitly and blocks until body() finishes. A default sequence is registered with uvm_config_db for a named phase, and UVM starts it for you — useful for base-test defaults that derived tests can override. Both ultimately run the same body().

Have questions about this topic?
Share your question in comments or talk to our mentor team for batch guidance.
▶ Try it in the ChipXpert Lab

Compile and simulate this in the browser — no install. Edit the code and re-run instantly.

Open in Verification Lab

Learn Verilog with mentors, labs and placement support

This free series is maintained by the ChipXpert training team in Hyderabad & Bengaluru. Go deeper with instructor-led courses on real EDA tools.

Explore VLSI Courses
Next batch starts 18th November 2026
Start your VLSI career with ChipXpert

Live online and classroom batches in Hyderabad & Bengaluru. Fill in your details and a counsellor will call you back.

  • Real EDA tools in your browser: industry-standard EDAReal EDA tools in your browser
  • Recorded sessions on the elearn portalRecorded sessions
  • Placement assistance: resume, mock interviewsPlacement assistance
  • Merit scholarship up to 60% · EMI optionsScholarship up to 60%
Hi! Ask me about courses, fees, batches or discounts. ×
BENGALURU
HYDERABAD