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.
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
`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.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
// 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
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
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
| Mode | Behavior |
|---|---|
SEQ_ARB_FIFO | Default — round-robin FIFO order |
SEQ_ARB_RANDOM | Random selection among waiting sequences |
SEQ_ARB_STRICT_FIFO | Strict priority FIFO — lower priority waits |
SEQ_ARB_STRICT_RANDOM | Strict priority random within same priority |
SEQ_ARB_WEIGHTED | Weighted 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
// 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
// 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