Part 5 · Sequences · Intermediate

Base Sequence Helpers: axi_base_seq and send_beat

Centralize shared knobs, p_sequencer declaration, and the send_beat helper in a base class hierarchy.

What a base sequence owns

A base sequence is not a test — it is shared infrastructure. It declares the sequencer type with `uvm_declare_p_sequencer, exposes scenario knobs as public fields, and provides

task helpers like send_beat() that wrap the canonical start_item → randomize → finish_item pattern. Derived sequences override body() and call helpers — they do not duplicate handshake code.

diagram
[SEQ] base class responsibilities

  DECLARE     `uvm_declare_p_sequencer(axi_sequencer)
  KNOBS       num_bursts, allow_errors, addr_range — set from test/config
  HELPERS     send_beat(), send_idle(), pre_body()/post_body() hooks
  NOT body()  base body() is empty or uvm_fatal — derived classes run stimulus

axi_base_seq — complete example

Here is a realistic AXI base sequence with a shared beat helper and configurable burst count:

systemverilog
class axi_base_seq extends uvm_sequence #(axi_item);
  `uvm_declare_p_sequencer(axi_sequencer)

  // Scenario knobs — overridden from test via uvm_config_db or direct assign
  int unsigned num_bursts   = 1;
  bit          allow_errors = 0;
  bit [31:0]   addr_lo      = 32'h0000;
  bit [31:0]   addr_hi      = 32'hFFFF;

  `uvm_object_utils(axi_base_seq)

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

  // Shared helper — NOT a separate sequence class
  virtual task send_beat(axi_item req);
    `uvm_info(get_type_name(), "send_beat: pre start_item", UVM_HIGH)
    start_item(req);
    if (!req.randomize() with {
      addr inside {[addr_lo:addr_hi]};
      if (!allow_errors) err == 0;
    })
      `uvm_fatal("RAND", "axi_item randomize failed in send_beat")
    `uvm_info(get_type_name(), req.sprint(), UVM_MEDIUM)
    finish_item(req);
    `uvm_info(get_type_name(), "send_beat: post finish_item", UVM_HIGH)
  endtask

  // Optional hook — derived seq calls super.pre_body() for logging
  virtual task pre_body();
    `uvm_info(get_type_name(), $sformatf(
      "starting %s on %s, num_bursts=%0d",
      get_type_name(), p_sequencer.get_full_name(), num_bursts), UVM_LOW)
  endtask
endclass
  • virtual on send_beat allows derived classes to add beat-level checks without overriding body().

  • Randomize inline constraints in send_beat enforce legal traffic for all derived sequences.

  • pre_body() logs sequencer name — first place to look when debugging null p_sequencer.


Derived sequence — axi_rand_burst_seq

The derived class sets burst-specific behavior in body() and reuses send_beat for every handshake:

systemverilog
class axi_rand_burst_seq extends axi_base_seq;
  `uvm_object_utils(axi_rand_burst_seq)

  task body();
    axi_item req;
    pre_body();
    repeat (num_bursts) begin
      req = axi_item::type_id::create("req");
      `uvm_info(get_type_name(), $sformatf(
        "burst %0d, len=%0d", num_bursts, req.burst_len), UVM_MEDIUM)
      repeat (req.burst_len + 1)
        send_beat(req);   // reuse — no duplicated start_item block
    end
  endtask
endclass

Walkthrough — send_beat through the handshake stack

diagram
Legend: [SEQ] [DRV] [UVM]

  axi_rand_burst_seq.body()
       │
       │  send_beat(req)
       ▼
  [SEQ] start_item(req)  ──► sequencer FIFO ──► [DRV] get_next_item(req)
       │                                              │
       │  randomize(req)                                │ drive(req) on vif
       │                                              │
       │  finish_item(req)  ◄── blocks until ──────── item_done()
       ▼
  next beat iteration

send_beat encapsulates the blocking boundary. If finish_item never returns, the stall is in the driver handshake — not in burst loop logic. Strategic uvm_info at pre/post start_item localizes the hang (see Sequence Debugging topic).


Configuring knobs from the test

Tests set base sequence knobs before start() — either by direct assignment or through the config DB:

systemverilog
axi_rand_burst_seq seq = axi_rand_burst_seq::type_id::create("seq");
seq.num_bursts   = 10;
seq.allow_errors = 1;
seq.addr_lo      = 32'h1000;
seq.addr_hi      = 32'h1FFF;
seq.start(env.axi_agent.sqr);
  • Knobs on the sequence object survive randomize — set before start(), not inside body().

  • uvm_config_db can push defaults in env build_phase for all tests in a regression.

  • Do not put pin-level timing knobs in base seq — those belong in driver/agent config.

Key takeaways

  • Base sequence = declare p_sequencer + shared knobs + send_beat helper.

  • Derived sequences override body(); they call helpers, not start_item directly.

  • One send_beat fix propagates to every derived burst and flow sequence.

Common pitfalls

  • Base class with non-empty body() that runs stimulus — derived start() runs both bodies.

  • send_beat as a separate uvm_sequence — unnecessary arbitration overhead per beat.

  • Duplicating randomize() in every derived body() instead of centralizing in send_beat.

  • Missing virtual on send_beat — cannot override beat behavior in stress subclass.