Day 08 / 25
← Day 07 Day 09 →
HomeVerificationDay 8 — UVM Sequences & seq_item
Track 2 — UVM

UVM Sequences and seq_item

Sequences are the stimulus generators of UVM — they define what transactions to send and in what order. The sequence item models a single transaction, while the sequence body() task orchestrates the flow. Together they decouple stimulus generation from the protocol-level driving.

⏱ 28 min read📖 Day 8 of 25🎯 Sequence · seq_item · Virtual Seq
Contents
  1. uvm_sequence_item
  2. uvm_sequence and body()
  3. Starting a Sequence
  4. p_sequencer
  5. Virtual Sequence
  6. Sequencer Arbitration
  7. Sequence Library
  8. Response Handling

1. uvm_sequence_item

The sequence item is a uvm_object subclass. It models a single stimulus transaction — one bus write, one packet, one AXI burst. Rand fields allow constrained-random generation:

class bus_item extends uvm_sequence_item;
  // Registration with field macros for auto copy/compare/print
  `uvm_object_utils_begin(bus_item)
    `uvm_field_int(addr,  UVM_ALL_ON)
    `uvm_field_int(data,  UVM_ALL_ON)
    `uvm_field_int(write, UVM_ALL_ON)
    `uvm_field_int(strobe,UVM_ALL_ON)
  `uvm_object_utils_end

  rand logic [31:0] addr;
  rand logic [31:0] data;
  rand logic        write;
  rand logic [3:0]  strobe;

  // Constraints
  constraint c_addr_aligned {
    addr[1:0] == 2'b00;    // word-aligned
  }
  constraint c_strobe {
    write -> (strobe != 4'h0);
    !write -> (strobe == 4'hF);
  }

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

  // convert2string — used by `uvm_info for debug printing
  function string convert2string();
    return $sformatf("addr=%0h data=%0h write=%0b strobe=%0b",
                     addr, data, write, strobe);
  endfunction
endclass
Field macros: `uvm_field_int, `uvm_field_enum, `uvm_field_string, etc. automatically generate do_copy, do_compare, do_print, do_pack/do_unpack for you. This is the standard way — only override them manually when you need custom behavior.

2. uvm_sequence and body()

class write_seq extends uvm_sequence #(bus_item);
  `uvm_object_utils(write_seq)

  int num_transactions = 10;   // configurable

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

  virtual task body();
    bus_item item;

    repeat(num_transactions) begin
      // `uvm_do — create, randomize, start_item, finish_item in one macro
      `uvm_do(item)
    end

    // Manual equivalent of `uvm_do — gives you control between steps
    item = bus_item::type_id::create("item");
    start_item(item);   // request grant from sequencer
    if (!item.randomize() with { item.addr inside {[32'h0:32'hFF]}; })
      `uvm_fatal("SEQ", "Randomize failed")
    finish_item(item);  // send to driver, wait for item_done
  endtask
endclass

// `uvm_do_with — randomize with inline constraints
class aligned_write_seq extends uvm_sequence #(bus_item);
  `uvm_object_utils(aligned_write_seq)

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

  virtual task body();
    bus_item item;
    // `uvm_do_with adds constraints to the randomization call
    `uvm_do_with(item, {
      item.write == 1;
      item.addr inside {[32'h1000:32'h1FFF]};
      item.strobe == 4'hF;
    })
  endtask
endclass

3. Starting a Sequence

// Method 1: from test run_phase
task run_phase(uvm_phase phase);
  write_seq seq;
  phase.raise_objection(this);

  seq = write_seq::type_id::create("seq");
  seq.num_transactions = 50;
  seq.start(env.agent.sqr);   // runs body() on this sequencer

  phase.drop_objection(this);
endtask

// Method 2: using default_sequence (runs automatically)
function void build_phase(uvm_phase phase);
  super.build_phase(phase);
  uvm_config_db #(uvm_object_wrapper)::set(
    this, "env.agent.sqr.main_phase",
    "default_sequence",
    write_seq::type_id::get()
  );
endfunction

// Run multiple sequences in sequence (serial)
task run_phase(uvm_phase phase);
  reset_seq  r_seq;
  write_seq  w_seq;
  read_seq   rd_seq;
  phase.raise_objection(this);

  r_seq  = reset_seq::type_id::create("r_seq");
  w_seq  = write_seq::type_id::create("w_seq");
  rd_seq = read_seq::type_id::create("rd_seq");

  r_seq.start(env.agent.sqr);
  w_seq.start(env.agent.sqr);
  rd_seq.start(env.agent.sqr);

  phase.drop_objection(this);
endtask

4. p_sequencer — Typed Sequencer Handle

p_sequencer is a typed handle to the sequencer the sequence is running on. Use it when the sequence needs to access configuration or other state stored in the sequencer:

// Custom sequencer with config information
class my_sequencer extends uvm_sequencer #(bus_item);
  `uvm_component_utils(my_sequencer)
  int max_outstanding = 4;    // config exposed to sequences
  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction
endclass

// Sequence using p_sequencer
class throttled_seq extends uvm_sequence #(bus_item);
  `uvm_object_utils(throttled_seq)
  // Declare typed p_sequencer — cast happens automatically
  `uvm_declare_p_sequencer(my_sequencer)

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

  virtual task body();
    bus_item item;
    // p_sequencer is now typed as my_sequencer — no cast needed
    `uvm_info("SEQ",
      $sformatf("max_outstanding=%0d", p_sequencer.max_outstanding),
      UVM_MEDIUM)
    `uvm_do(item)
  endtask
endclass

5. Virtual Sequence

A virtual sequence coordinates multiple agents. It holds handles to all sequencers and starts sub-sequences on each in a controlled order:

// Virtual sequencer — no driver attached, just handles
class sys_virtual_seqr extends uvm_sequencer;
  `uvm_component_utils(sys_virtual_seqr)
  axi_sequencer  axi_sqr;   // handle to real AXI sequencer
  apb_sequencer  apb_sqr;   // handle to real APB sequencer
  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction
endclass

// Virtual sequence — runs on the virtual sequencer
class sys_vseq extends uvm_sequence;
  `uvm_object_utils(sys_vseq)
  `uvm_declare_p_sequencer(sys_virtual_seqr)

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

  virtual task body();
    axi_write_seq  axi_seq;
    apb_config_seq apb_seq;

    axi_seq = axi_write_seq::type_id::create("axi_seq");
    apb_seq = apb_config_seq::type_id::create("apb_seq");

    // Run APB config first, then AXI traffic in parallel with more APB
    apb_seq.start(p_sequencer.apb_sqr);

    fork
      axi_seq.start(p_sequencer.axi_sqr);
      apb_seq.start(p_sequencer.apb_sqr);
    join
  endtask
endclass

6. Sequencer Arbitration

ModeBehavior
SEQ_ARB_FIFODefault — round-robin FIFO order
SEQ_ARB_RANDOMRandom selection among waiting sequences
SEQ_ARB_STRICT_FIFOStrict priority FIFO — lower priority waits
SEQ_ARB_STRICT_RANDOMStrict priority random within same priority
SEQ_ARB_WEIGHTEDWeighted random — higher weight = more grants
lock()Exclusive access — no other sequence gets grant
grab()Like lock() but preempts current sequence
// Set arbitration mode on a sequencer
env.agent.sqr.set_arbitration(SEQ_ARB_RANDOM);

// Lock/unlock for exclusive access
virtual task body();
  bus_item item;
  m_sequencer.lock(this);     // wait for exclusive access
  `uvm_do(item)               // no other sequence can interleave
  `uvm_do(item)
  m_sequencer.unlock(this);   // release exclusivity
endtask

7. Sequence Library

// Sequence library — randomly picks from registered sequences
class bus_seq_lib extends uvm_sequence_library #(bus_item);
  `uvm_object_utils(bus_seq_lib)
  `uvm_sequence_library_utils(bus_seq_lib)

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

// Register sequences to library with type_id macro
// In each sequence class body:
// `uvm_add_to_seq_lib(write_seq, bus_seq_lib)
// `uvm_add_to_seq_lib(read_seq,  bus_seq_lib)

// Library config: how many sequences to run, min/max count
function void build_phase(uvm_phase phase);
  bus_seq_lib lib = bus_seq_lib::type_id::create("lib");
  lib.selection_mode = UVM_SEQ_LIB_RAND;
  lib.min_random_count = 10;
  lib.max_random_count = 20;
  lib.start(env.agent.sqr);
endfunction

8. Response Handling

// Driver puts response when transaction completes
task run_phase(uvm_phase phase);
  bus_item req, rsp;
  forever begin
    seq_item_port.get_next_item(req);
    drive_bus(req);

    // Clone the item, fill response fields, put it back
    $cast(rsp, req.clone());
    rsp.set_id_info(req);         // match sequence ID for routing
    rsp.data = bus_rdata;         // fill read data from bus
    seq_item_port.item_done(rsp); // item_done with response
  end
endtask

// Sequence gets the response
virtual task body();
  bus_item req, rsp;
  req = bus_item::type_id::create("req");
  start_item(req);
  req.randomize() with { req.write == 0; };  // read
  finish_item(req);

  // Wait for driver response
  get_response(rsp);
  `uvm_info("SEQ", $sformatf("Read data: %0h", rsp.data), UVM_MEDIUM)
endtask

Key Takeaways — Day 8

Next → Day 09
UVM Driver and Monitor
get_next_item/item_done cycle, clocking block in driver, monitor run_phase, analysis port write, AXI-lite examples.