Part 5 · Sequences · Intermediate
Why Arbitration Exists: Multiple Sequences, One Driver
The one-driver constraint, concurrent sequence threads, and why the sequencer must serialize start_item access.
The fundamental constraint
A uvm_driver drives one physical interface — one set of virtual interface signals sampled and driven on each clock edge. It can execute one transaction at a time . The driver pulls items via get_next_item from its connected sequencer. If two sequences could deliver items simultaneously, the driver would see conflicting requests with no defined merge semantics.
The uvm_sequencer exists partly to solve this: it accepts start_item calls from many sequences but grants driver access to exactly one pending item at a time. That grant decision is arbitration .
[UVM] why one driver cannot serve two items at once
WRONG mental model:
seq_A.start_item(req_a) ──┐
├──► driver drives BOTH? ✗ undefined
seq_B.start_item(req_b) ──┘
CORRECT model:
seq_A.start_item(req_a) ──► sequencer queue slot A
seq_B.start_item(req_b) ──► sequencer queue slot B
│
▼
arbiter picks ONE
│
▼
driver executes item
│
▼
item_done → next slotConcurrent sequences are normal
Real tests rarely run a single sequence in isolation. Typical chip-level run_phase starts several sequences on the same sequencer:
[STIM] Background traffic generator — continuous randomized writes for stress.
[STIM] Directed setup sequence — programs control registers before the scenario.
[STIM] Error injection sequence — inserts protocol violations mid-run.
[SEQ] Test or virtual sequence forks all three with fork/join — they run in parallel threads.
task run_phase(uvm_phase phase);
apb_bg_seq bg = apb_bg_seq::type_id::create("bg");
apb_setup_seq setup = apb_setup_seq::type_id::create("setup");
apb_stress_seq stress = apb_stress_seq::type_id::create("stress");
phase.raise_objection(this);
fork
bg.start(env.apb_agent.sqr); // long-running background
setup.start(env.apb_agent.sqr); // short directed burst
stress.start(env.apb_agent.sqr); // main scenario
join
phase.drop_objection(this);
endtaskAll three sequences share one sequencer and one driver. Without arbitration, there is no defined rule for which sequence's start_item wins when multiple are pending.
What arbitration serializes — and what it does not
Arbitration operates at start_item boundaries — the point where a sequence requests the next driver slot. It does not serialize entire sequence body() execution. Two sequences can both be 'running' — one blocked at start_item while the other holds the driver.
[SEQ] two sequences, one driver — thread vs item view
TIME ─────────────────────────────────────────────────────────────►
seq_A thread: [body running]──start_item──BLOCKED──start_item──BLOCKED──...
seq_B thread: [body running]──start_item──[DRIVING]──finish_item──start_item──...
DRIVER: idle item_B1 idle item_A1 item_B2 ...
Thread parallelism: BOTH sequences active (fork/join)
Driver serialization: ONE item at a time (arbitration)When arbitration surprises you
Register programming interleaved with background traffic — partial config visible to DUT.
Directed read inserted between two writes of a burst — breaks burst semantics.
Reset sequence must preempt stress traffic — needs priority or lock, not hope.
Assuming seq.start() completion order matches fork order — items interleave instead.
The sequencer as arbiter — not just a FIFO pipe
Early testbenches sometimes used a single sequence per interface and never hit arbitration. As soon as you add background generators, layered sequences, or virtual sequences that fork sub-sequences on the same agent, arbitration becomes unavoidable. The sequencer is the only place where multi-sequence access is mediated before the driver.
[UVM] sequencer responsibilities
┌──────────────────────────────────────────────────────────┐
│ uvm_sequencer │
│ │
│ 1. Accept start_item from N concurrent sequences │
│ 2. Queue pending requests per arbitration mode │
│ 3. Grant ONE item to driver via get_next_item │
│ 4. Block other sequences at start_item until their turn │
│ 5. Honor lock/grab — exclusive access override │
│ 6. Apply priority weighting when mode requires it │
└──────────────────────────────────────────────────────────┘
▲ │
│ start_item │ get_next_item / item_done
[SEQ] sequences ▼
[STIM] driverKey takeaways
One driver = one item at a time; many sequences need an arbiter.
Arbitration serializes start_item grants, not entire sequence threads.
Background + directed + stress on one sequencer is the normal case — plan for interleaving.
Common pitfalls
Creating a second driver to avoid arbitration — breaks agent encapsulation and protocol checking.
Thinking start() on different sequences guarantees ordering — only items are ordered by arbitration.
Ignoring arbitration until a register programming test fails intermittently.