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.
[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 stimulusaxi_base_seq — complete example
Here is a realistic AXI base sequence with a shared beat helper and configurable burst count:
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
endclassvirtual 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:
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
endclassWalkthrough — send_beat through the handshake stack
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 iterationsend_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:
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.