Part 5 · Sequences · Intermediate
lock / grab / unlock / ungrab: Atomic Register Programming
Exclusive sequencer access, lock vs grab semantics, and atomic multi-beat register block programming.
When FIFO is not enough
FIFO arbitration interleaves items from concurrent sequences. That is correct for independent traffic streams but wrong when the DUT requires atomic multi-beat programming — for example, writing an entire register block before background traffic resumes, or completing a mode-switch sequence without a stress write in the middle.
UVM provides lock / grab on the sequencer for exclusive access : while held, no other sequence's start_item is granted until unlock/ungrab.
[SEQ] FIFO interleaving vs lock — register programming
WITHOUT lock (broken):
setup writes CTRL=0x01 → bg writes random addr → setup writes MODE=0x02
DUT sees partial config with bg traffic in the middle ✗
WITH lock (correct):
setup.lock(this)
setup writes CTRL=0x01 → setup writes MODE=0x02 → setup writes START=0x01
setup.unlock(this)
bg traffic resumes ✓lock / unlock — fair exclusive access
Call p_sequencer.lock(this) at the start of an atomic region. lock waits until no other sequence holds the lock and the current in-flight item completes — fair handoff. Pair every lock with unlock(this).
class prog_regs_seq extends uvm_sequence #(apb_item);
`uvm_declare_p_sequencer(apb_sequencer)
`uvm_object_utils(prog_regs_seq)
bit [31:0] reg_vals[$]; // values to program in order
task body();
apb_item req = apb_item::type_id::create("req");
// [SEQ] Acquire exclusive access — no other sequence interleaves
p_sequencer.lock(this);
foreach (reg_vals[i]) begin
start_item(req);
assert(req.randomize() with {
addr == REG_BASE + i * 4;
write == 1;
data == reg_vals[i];
});
finish_item(req);
end
// [SEQ] Release — background sequences resume arbitration
p_sequencer.unlock(this);
endtask
endclassgrab / ungrab — immediate seizure
Call grab(this) when a sequence must seize the sequencer immediately if free, with higher precedence than pending lock requests. ungrab(this) releases. Use grab sparingly — urgent reset or error injection that cannot wait behind a fair lock queue.
[UVM] lock vs grab semantics
lock(this):
• Waits for current driver item to finish
• Waits for any other lock holder to unlock
• Fair — queued lock requests served in order
• Use for: register programming, mode switches, calibration
grab(this):
• Seizes immediately if sequencer not locked
• Preempts pending lock requests
• Use for: urgent reset, fatal error injection, watchdog recovery
• Danger: starving lock holders if grab never releasedlock vs grab decision table
Register block programming → lock (fair, waits for current item).
Multi-beat config without interleaving → lock.
Emergency bus reset during stress → grab (preempt).
Never grab in background sequences — starves everyone else.
Atomic register programming — full example
A DMA engine requires four registers programmed in order before START is written. Background APB traffic runs concurrently. lock ensures the four writes execute back-to-back on the bus.
class dma_config_seq extends uvm_sequence #(apb_item);
`uvm_declare_p_sequencer(apb_sequencer)
rand bit [31:0] src, dst;
rand int unsigned len;
task body();
apb_item req = apb_item::type_id::create("req");
p_sequencer.lock(this);
// Atomic programming sequence — no bg interleave
write_reg(req, REG_SRC, src);
write_reg(req, REG_DST, dst);
write_reg(req, REG_LEN, len);
write_reg(req, REG_CTRL, CTRL_ENABLE);
p_sequencer.unlock(this);
endtask
task write_reg(apb_item req, bit [31:0] addr, bit [31:0] data);
start_item(req);
assert(req.randomize() with { this.addr == addr; this.data == data; write == 1; });
finish_item(req);
endtask
endclass[STIM] bus timeline — with lock
TIME ─────────────────────────────────────────────────────────────►
bg_seq: [item]──BLOCKED──────────────────────────[item]──[item]──...
dma_cfg: lock──SRC──DST──LEN──CTRL──unlock
DRIVER: bg0 dma0 dma1 dma2 dma3 ctrl bg1 ...
Between lock and unlock: ONLY dma_config_seq items on the busKey takeaways
lock/unlock — fair exclusive access for atomic multi-beat operations.
grab/ungrab — immediate seizure; use only for urgent preemption.
Always pair lock with unlock — forgotten unlock starves all other sequences.
Common pitfalls
Forgotten unlock after lock — every other sequence hangs at start_item forever.
lock inside a fork branch without unlock on all paths — use try/finally pattern.
Using lock for every sequence — serializes everything; defeats concurrent stimulus.
grab in a loop — permanent starvation of lower-priority sequences.