Sequences & Sequencers
Learn how to generate stimulus with UVM sequences and control flow through sequencers.
Sequences & Sequencers
Sequences are the primary mechanism for generating stimulus in UVM. They create sequence items (transactions) and send them to the driver through a sequencer.
The Pipeline
text┌────────────┐ ┌─────────────┐ ┌──────────┐ │ Sequence │────▶│ Sequencer │────▶│ Driver │──▶ DUT │ (generates │ │ (arbitrates │ │ (drives │ │ items) │◀────│ items) │◀────│ pins) │ └────────────┘ └─────────────┘ └──────────┘
Writing a Basic Sequence
systemverilogclass write_sequence extends uvm_sequence #(apb_txn); `uvm_object_utils(write_sequence) function new(string name = "write_sequence"); super.new(name); endfunction task body(); apb_txn txn; repeat(10) begin txn = apb_txn::type_id::create("txn"); start_item(txn); // Request grant from sequencer if (!txn.randomize() with { write == 1; addr inside {[32'h0 : 32'hFF]}; }) `uvm_fatal("RAND", "Randomization failed") finish_item(txn); // Send to driver, wait for completion end endtask endclass
Understanding start_item / finish_item
| API Call | What Happens |
|---|---|
start_item(txn) | Request grant from sequencer (blocks until ready) |
txn.randomize() | Randomize between start and finish |
finish_item(txn) | Send item to driver; block until item_done() |
Why randomize between start and finish? The sequencer grants access to one sequence at a time. Randomizing after the grant ensures you have the latest context.
The Driver Side
The driver pulls items from the sequencer and drives them:
systemverilogclass apb_driver extends uvm_driver #(apb_txn); `uvm_component_utils(apb_driver) virtual apb_if vif; function new(string name, uvm_component parent); super.new(name, parent); endfunction task run_phase(uvm_phase phase); apb_txn req; forever begin seq_item_port.get_next_item(req); // Pull from sequencer drive_to_pins(req); // Drive on interface seq_item_port.item_done(); // Tell sequencer we're done end endtask task drive_to_pins(apb_txn txn); @(posedge vif.PCLK); vif.PSEL <= 1; vif.PADDR <= txn.addr; vif.PWRITE <= txn.write; if (txn.write) vif.PWDATA <= txn.data; @(posedge vif.PCLK); vif.PENABLE <= 1; do @(posedge vif.PCLK); while (!vif.PREADY); if (!txn.write) txn.data = vif.PRDATA; vif.PSEL <= 0; vif.PENABLE <= 0; endtask endclass
Running a Sequence from the Test
systemverilogtask run_phase(uvm_phase phase); write_sequence seq; phase.raise_objection(this); seq = write_sequence::type_id::create("seq"); seq.start(env.agent.sequencer); // Blocks until body() completes phase.drop_objection(this); endtask
Sequence Composition
Sequences can start other sequences for complex scenarios:
systemverilogclass read_after_write_seq extends uvm_sequence #(apb_txn); `uvm_object_utils(read_after_write_seq) function new(string name = "read_after_write_seq"); super.new(name); endfunction task body(); write_sequence wr_seq; read_sequence rd_seq; // Run writes first, then reads wr_seq = write_sequence::type_id::create("wr_seq"); wr_seq.start(m_sequencer); rd_seq = read_sequence::type_id::create("rd_seq"); rd_seq.start(m_sequencer); endtask endclass
Parallel Sequences
Run multiple sequences concurrently using fork-join:
systemverilogtask body(); fork write_seq.start(m_sequencer); // Writes read_seq.start(m_sequencer); // Reads (concurrent) join endtask
The sequencer uses its arbitration scheme (default: FIFO) to interleave items.
Key Points
| Concept | Detail |
|---|---|
| Sequences are objects | Extend uvm_sequence, not uvm_component |
| Sequences are transient | Created, run, destroyed |
body() task | Contains the stimulus logic |
start_item/finish_item | Per-transaction handshake |
| Composition | Sequences can start other sequences |
| Parallelism | Use fork-join for concurrent sequences |