Sequencer Arbitration & Control - Managing Concurrent Sequences
When multiple sequences compete for a single driver, who wins? Sequencer arbitration answers this question. Understanding arbitration modes, lock/grab mechanisms, and priority scheduling is essential for interrupt handling, resource sharing, and deterministic test scenarios.
The Arbitration Problem
Consider a CPU interface where normal traffic, interrupt service routines, and debug sequences all need access:
flowchart LR
subgraph "Competing Sequences"
S1[Normal Traffic
Priority: Low]
S2[ISR Sequence
Priority: High]
S3[Debug Sequence
Priority: Medium]
end
S1 --> ARB[Arbitration
Queue]
S2 --> ARB
S3 --> ARB
ARB --> D[Driver]
Without proper arbitration, your ISR might wait behind 100 normal transactions - not realistic behavior.
Arbitration Modes: Strategy Pattern in Action
UVM's arbitration modes are essentially the Strategy Pattern - interchangeable algorithms for the same problem:
| Mode | Algorithm | Respects Priority? | Use Case |
|---|---|---|---|
UVM_SEQ_ARB_FIFO | First In, First Out | No | Default, simple ordering |
UVM_SEQ_ARB_RANDOM | Random selection | No | Stress testing, randomized scheduling |
UVM_SEQ_ARB_STRICT_FIFO | Highest priority first, FIFO within | Yes | Priority-based with deterministic tiebreaker |
UVM_SEQ_ARB_STRICT_RANDOM | Highest priority first, random within | Yes | Priority-based with randomized tiebreaker |
UVM_SEQ_ARB_WEIGHTED | Probabilistic by priority weight | Soft | Weighted traffic mix |
UVM_SEQ_ARB_USER | Custom algorithm | Custom | Special scheduling requirements |
Setting Arbitration Mode
class my_env extends uvm_env;
cpu_sequencer cpu_sqr;
function void build_phase(uvm_phase phase);
super.build_phase(phase);
cpu_sqr = cpu_sequencer::type_id::create("cpu_sqr", this);
endfunction
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
// Set priority-aware arbitration
cpu_sqr.set_arbitration(UVM_SEQ_ARB_STRICT_FIFO);
endfunction
endclass
Setting Sequence Priority
Priority is passed to the start() method (default is 100, higher = more important):
class interrupt_test extends uvm_test;
task run_phase(uvm_phase phase);
phase.raise_objection(this);
fork
// Low priority: Normal traffic (priority 100 - default)
normal_seq.start(cpu_sqr);
// High priority: ISR (priority 200)
isr_seq.start(cpu_sqr, null, 200);
// Medium priority: Debug (priority 150)
debug_seq.start(cpu_sqr, null, 150);
join
phase.drop_objection(this);
endtask
endclass
Lock and Grab: Exclusive Access
Sometimes a sequence needs uninterrupted access - like an atomic operation or critical section. UVM provides two mechanisms:
| Method | Behavior | Queue Position | Use Case |
|---|---|---|---|
lock() | Request exclusive access | Back of queue | Planned exclusive sections |
grab() | Force exclusive access | Front of queue | Urgent interruption (ISR) |
Lock: Polite Exclusive Access
class atomic_write_seq extends uvm_sequence #(bus_txn);
`uvm_object_utils(atomic_write_seq)
task body();
// Request exclusive access (waits in queue)
lock(m_sequencer);
`uvm_info("SEQ", "Lock acquired - starting atomic write", UVM_MEDIUM)
// Critical section: No other sequence can interrupt
repeat(4) begin
`uvm_do_with(req, { addr[1:0] == 2'b00; }) // Word-aligned burst
end
// Release
unlock(m_sequencer);
`uvm_info("SEQ", "Lock released", UVM_MEDIUM)
endtask
endclass
Grab: Urgent Exclusive Access
class isr_sequence extends uvm_sequence #(bus_txn);
`uvm_object_utils(isr_sequence)
task body();
// IMMEDIATELY take control (cuts in line)
grab(m_sequencer);
`uvm_info("ISR", "Interrupt handler active", UVM_LOW)
// Handle interrupt
read_status_register();
clear_interrupt();
service_interrupt();
// Release control
ungrab(m_sequencer);
`uvm_info("ISR", "Interrupt handler complete", UVM_LOW)
endtask
task read_status_register();
`uvm_do_with(req, { op == READ; addr == ISR_ADDR; })
endtask
task clear_interrupt();
`uvm_do_with(req, { op == WRITE; addr == ICR_ADDR; data == 32'hFFFF; })
endtask
endclass
Interrupt Handling Pattern
A complete interrupt handling implementation combines grab/ungrab with event-driven triggering:
class interrupt_handler_vseq extends uvm_sequence #(uvm_sequence_item);
`uvm_object_utils(interrupt_handler_vseq)
`uvm_declare_p_sequencer(soc_virtual_sequencer)
// Configuration
int num_irq_lines = 4;
// Sequences
main_traffic_seq main_seq;
isr_sequence isr_seq[4];
task body();
// Create ISR sequences for each IRQ line
foreach(isr_seq[i])
isr_seq[i] = isr_sequence::type_id::create($sformatf("isr_seq_%0d", i));
main_seq = main_traffic_seq::type_id::create("main_seq");
fork
// Main traffic (runs continuously)
main_seq.start(p_sequencer.cpu_sqr);
// IRQ monitors (one per line)
monitor_irq(0);
monitor_irq(1);
monitor_irq(2);
monitor_irq(3);
join_any
disable fork; // Cleanup
endtask
task monitor_irq(int irq_num);
forever begin
// Wait for interrupt assertion
@(posedge p_sequencer.vif.irq[irq_num]);
`uvm_info("IRQ", $sformatf("IRQ %0d asserted", irq_num), UVM_LOW)
// Service with appropriate ISR
isr_seq[irq_num].irq_num = irq_num;
isr_seq[irq_num].start(p_sequencer.cpu_sqr, this, 300); // High priority
`uvm_info("IRQ", $sformatf("IRQ %0d serviced", irq_num), UVM_LOW)
end
endtask
endclass
ISR with Grab for Immediate Response
class isr_sequence extends uvm_sequence #(bus_txn);
`uvm_object_utils(isr_sequence)
int irq_num;
task body();
// Grab immediately - interrupt latency matters!
grab(m_sequencer);
// Standard ISR flow
read_isr_status();
case(irq_status)
IRQ_TIMER: handle_timer();
IRQ_DMA: handle_dma_complete();
IRQ_ERROR: handle_error();
default: handle_unknown();
endcase
clear_interrupt();
ungrab(m_sequencer);
endtask
endclass
Custom Arbitration: User-Defined Strategy
For complex scheduling requirements, implement custom arbitration:
class fair_arbitration_sequencer extends uvm_sequencer #(bus_txn);
`uvm_component_utils(fair_arbitration_sequencer)
// Track grants per sequence for fairness
int grant_count[uvm_sequence_base];
int max_consecutive = 3; // Max grants before yielding
function new(string name, uvm_component parent);
super.new(name, parent);
set_arbitration(UVM_SEQ_ARB_USER);
endfunction
// Override: Custom arbitration algorithm
function integer user_priority_arbitration(integer avail_sequences[$]);
int best_idx = -1;
int min_grants = 999999;
// Find sequence with fewest recent grants (fairness)
foreach(avail_sequences[i]) begin
uvm_sequence_base seq;
seq = arb_sequence_q[avail_sequences[i]].sequence_ptr;
// Skip if exceeded consecutive limit
if (grant_count.exists(seq) && grant_count[seq] >= max_consecutive)
continue;
// Prefer least-served sequence
if (!grant_count.exists(seq) || grant_count[seq] < min_grants) begin
min_grants = grant_count.exists(seq) ? grant_count[seq] : 0;
best_idx = avail_sequences[i];
end
end
// Update tracking
if (best_idx >= 0) begin
uvm_sequence_base seq = arb_sequence_q[best_idx].sequence_ptr;
grant_count[seq]++;
end
return best_idx;
endfunction
endclass
Real-World Example: CPU Bus Arbiter
class cpu_bus_test extends uvm_test;
`uvm_component_utils(cpu_bus_test)
cpu_env env;
task run_phase(uvm_phase phase);
// Use strict priority for realistic behavior
env.cpu_sqr.set_arbitration(UVM_SEQ_ARB_STRICT_FIFO);
phase.raise_objection(this);
fork
// Background: Normal memory traffic (lowest priority)
begin
memory_traffic_seq mem_seq = memory_traffic_seq::type_id::create("mem_seq");
mem_seq.num_transactions = 1000;
mem_seq.start(env.cpu_sqr, null, 50); // Priority 50
end
// Periodic: DMA transfers (medium priority)
begin
repeat(10) begin
#($urandom_range(100, 500) * 1ns);
dma_burst_seq dma_seq = dma_burst_seq::type_id::create("dma_seq");
dma_seq.start(env.cpu_sqr, null, 100); // Priority 100
end
end
// Sporadic: Debug probe (high priority)
begin
repeat(5) begin
#($urandom_range(200, 800) * 1ns);
debug_read_seq dbg_seq = debug_read_seq::type_id::create("dbg_seq");
dbg_seq.start(env.cpu_sqr, null, 150); // Priority 150
end
end
// Rare: Error handler (highest priority + grab)
begin
@(env.error_event);
error_handler_seq err_seq = error_handler_seq::type_id::create("err_seq");
// Uses grab() internally for immediate response
err_seq.start(env.cpu_sqr, null, 200);
end
join
phase.drop_objection(this);
endtask
endclass
Best Practices
1. Always Release Locks
task body();
lock(m_sequencer);
// Use try-finally pattern (SystemVerilog doesn't have try-finally,
// so structure code carefully)
begin
do_critical_work();
end
// ALWAYS unlock - even if error occurs
unlock(m_sequencer);
endtask
2. Prefer Priority Over Grab
// PREFER: Let arbitration handle it
isr_seq.start(sqr, null, 500); // Very high priority
// AVOID: Grab unless truly necessary
// grab() bypasses priority - use only for true emergencies
3. Document Arbitration Assumptions
class my_test extends uvm_test;
// ARBITRATION REQUIREMENTS:
// - Must use STRICT_FIFO for deterministic interrupt latency
// - ISR sequences use priority 200+
// - Normal traffic uses priority 50-100
// - Debug uses priority 150
endclass
4. Test Arbitration Behavior
class arbitration_stress_test extends uvm_test;
task run_phase(uvm_phase phase);
// Launch many sequences simultaneously
fork
repeat(10) low_priority_seq.start(sqr, null, 50);
repeat(5) med_priority_seq.start(sqr, null, 100);
repeat(2) high_priority_seq.start(sqr, null, 200);
join
// Verify high priority completed first
check_completion_order();
endtask
endclass
Common Pitfalls
| Pitfall | Symptom | Solution |
|---|---|---|
Forgetting unlock() | Sequencer deadlocks | Always pair lock/unlock |
| Using priority with FIFO mode | Priority ignored | Use STRICT_FIFO or STRICT_RANDOM |
| Grab without ungrab | Permanent exclusive access | Always ungrab after grab |
| Priority inversion | High-priority waits for low | Use grab for true urgency |
| Non-deterministic tests | Flaky regressions | Use STRICT_FIFO, not RANDOM |
Key Takeaways
- Arbitration modes = Strategy Pattern: Choose algorithm matching your needs
- STRICT_FIFO for priority: Default FIFO ignores priorities
- Lock for planned exclusion: Politely waits in queue
- Grab for emergencies: Cuts to front - use sparingly
- Custom arbitration for special cases: Fair scheduling, weighted access
- Always release locks: Deadlocks are hard to debug
Previous: Part 3 - Advanced Architectures
This completes the UVM Sequences series. Return to the UVM Hub for more topics.
Sources consulted: ChipVerify, VLSI Verify, Art of Verification
Comments (0)
Leave a Comment