Lock and Grab Methods in UVM sequencer
The UVM sequencer provides the facility to have exclusive access for the sequence to a driver via a sequencer using a locking mechanism. This locking mechanism is implemented using lock and grab methods.
Example: In controller or microprocessor, internal core services interrupt handling along with other operations. Sometimes, if a particular interrupt is raised by the device which needs immediate attention and stops ongoing process execution. Once this high-priority interrupt is serviced by the core, the previous process can be resumed.
Lock method
On calling the lock method from a sequence, the sequencer will grant the sequence exclusive access to the driver when the sequence gets the next available slot via a sequencer arbitration mechanism. Until the sequence calls the unlock method, no other sequence will have access to the driver. On calling unlock method, the lock will be released. The lock is a blocking method and returns once the lock has been granted.
task lock (uvm_sequencer_base sequencer = null)
grab method
The grab method is similar to the lock method except it takes immediate control to grab the next sequencer arbitration slot itself by overriding any other sequence priorities.
The pre-existing lock or grab condition on the sequence will stop from grabbing the sequence for the current sequence.
task grab (uvm_sequencer_base sequencer = null)
unlock method
The unlock method of the sequencer is called from the sequence to release its lock or grab. It is mandatory to call unlock method in order to avoid sequencer locked conditions.
function void unlock (uvm_sequencer_base sequencer = null)
ungrab method
The ungrab method is an alias of the unlock method
function void ungrab( uvm_sequencer_base sequencer = null )
is_blocked method
The is_blocked method can be used to find a current sequence that is blocked due to the sequencer lock condition and returns 1. It returns 0 value, then the sequence is not blocked and available to get the next slot in sequencer arbitration. There is a possibility that the sequence may not get the slot if the sequencer is locked before the sequence issues a request.
function bit is_blocked()
has_lock method
It returns 1 if the current sequence is locked, else returns 0.
function bit has_lock()
kill method
The kill function is used to kill the sequence and removes all current locks and requests in the sequence’s default sequencer. This will not allow executing the post_body and post_start callback methods and the sequence state will change to UVM_STOPPED.
function void kill()
do_kill method
The do_kill is a user hook that is being called when a sequence is terminated by using sequence kill() or sequencer.stop_sequences().
Note: sequencer.stop_sequences() ultimately calls sequence.kill method.
virtual function void do_kill()
Note:
- The lock, grab, unlock and ungrab methods put requests on the specified sequencer. If no argument is passed, the default sequencer will be considered.
- The lock method puts a lock request at the back of the arbitration queue whereas the grab method puts a grab request in front of the arbitration queue. Hence, the grab request is serviced immediately as compared to the lock request if the arbitration queue has intermediate pending requests.
Lock and unlock methods example
There are three sequences seq_A, seq_B, and seq_C are forked at the same time. The only seq_B calls for lock and unlock methods.
class seq_A extends uvm_sequence #(seq_item);
seq_item req;
`uvm_object_utils(seq_A)
function new (string name = "seq_A");
super.new(name);
endfunction
task body();
req = seq_item::type_id::create("req");
wait_for_grant();
assert(req.randomize());
send_request(req);
`uvm_info(get_type_name(), $sformatf("%0t: seq_item is sent", $time), UVM_LOW);
wait_for_item_done();
endtask
endclass
class seq_B extends uvm_sequence #(seq_item);
seq_item req;
`uvm_object_utils(seq_B)
function new (string name = "seq_B");
super.new(name);
endfunction
task body();
lock(m_sequencer);
req = seq_item::type_id::create("req");
wait_for_grant();
assert(req.randomize());
send_request(req);
`uvm_info(get_type_name(), $sformatf("%0t: seq_item is sent", $time), UVM_LOW);
wait_for_item_done();
unlock(m_sequencer);
endtask
endclass
class seq_C extends uvm_sequence #(seq_item);
seq_item req;
`uvm_object_utils(seq_C)
function new (string name = "seq_C");
super.new(name);
endfunction
task body();
req = seq_item::type_id::create("req");
wait_for_grant();
assert(req.randomize());
send_request(req);
`uvm_info(get_type_name(), $sformatf("%0t: seq_item is sent", $time), UVM_LOW);
wait_for_item_done();
endtask
endclass
Output:
UVM_INFO sequences.sv(14) @ 0: uvm_test_top.env_o.agt.seqr@@seq_a [seq_A] 0: seq_item is sent
UVM_INFO sequences.sv(33) @ 50: uvm_test_top.env_o.agt.seqr@@seq_b [seq_B] 50: seq_item is sent
UVM_INFO sequences.sv(52) @ 100: uvm_test_top.env_o.agt.seqr@@seq_c [seq_C] 100: seq_item is sent
UVM_INFO sequences.sv(14) @ 150: uvm_test_top.env_o.agt.seqr@@seq_a [seq_A] 150: seq_item is sent
UVM_INFO sequences.sv(33) @ 200: uvm_test_top.env_o.agt.seqr@@seq_b [seq_B] 200: seq_item is sent
UVM_INFO sequences.sv(52) @ 250: uvm_test_top.env_o.agt.seqr@@seq_c [seq_C] 250: seq_item is sent
UVM_INFO sequences.sv(14) @ 300: uvm_test_top.env_o.agt.seqr@@seq_a [seq_A] 300: seq_item is sent
UVM_INFO sequences.sv(33) @ 350: uvm_test_top.env_o.agt.seqr@@seq_b [seq_B] 350: seq_item is sent
UVM_INFO sequences.sv(52) @ 400: uvm_test_top.env_o.agt.seqr@@seq_c [seq_C] 400: seq_item is sent
The log shows that a seq_B lock request is pushed at the back of the arbitration queue following default arbitration mode. Hence, all requests are serviced as per allotted slot with seq_B having exclusive access.
Grab and ungrab methods example
There are three sequences seq_A, seq_B, and seq_C are forked at the same time. The only seq_B calls for grab and ungrab methods.
class seq_A extends uvm_sequence #(seq_item);
seq_item req;
`uvm_object_utils(seq_A)
function new (string name = "seq_A");
super.new(name);
endfunction
task body();
req = seq_item::type_id::create("req");
wait_for_grant();
assert(req.randomize());
send_request(req);
`uvm_info(get_type_name(), $sformatf("%0t: seq_item is sent", $time), UVM_LOW);
wait_for_item_done();
endtask
endclass
class seq_B extends uvm_sequence #(seq_item);
seq_item req;
`uvm_object_utils(seq_B)
function new (string name = "seq_B");
super.new(name);
endfunction
task body();
grab(m_sequencer);
req = seq_item::type_id::create("req");
wait_for_grant();
assert(req.randomize());
send_request(req);
`uvm_info(get_type_name(), $sformatf("%0t: seq_item is sent", $time), UVM_LOW);
wait_for_item_done();
ungrab(m_sequencer);
endtask
endclass
class seq_C extends uvm_sequence #(seq_item);
seq_item req;
`uvm_object_utils(seq_C)
function new (string name = "seq_C");
super.new(name);
endfunction
task body();
req = seq_item::type_id::create("req");
wait_for_grant();
assert(req.randomize());
send_request(req);
`uvm_info(get_type_name(), $sformatf("%0t: seq_item is sent", $time), UVM_LOW);
wait_for_item_done();
endtask
endclass
Output:
UVM_INFO sequences.sv(33) @ 0: uvm_test_top.env_o.agt.seqr@@seq_b [seq_B] 0: seq_item is sent
UVM_INFO sequences.sv(33) @ 50: uvm_test_top.env_o.agt.seqr@@seq_b [seq_B] 50: seq_item is sent
UVM_INFO sequences.sv(33) @ 100: uvm_test_top.env_o.agt.seqr@@seq_b [seq_B] 100: seq_item is sent
UVM_INFO sequences.sv(14) @ 150: uvm_test_top.env_o.agt.seqr@@seq_a [seq_A] 150: seq_item is sent
UVM_INFO sequences.sv(52) @ 200: uvm_test_top.env_o.agt.seqr@@seq_c [seq_C] 200: seq_item is sent
UVM_INFO sequences.sv(14) @ 250: uvm_test_top.env_o.agt.seqr@@seq_a [seq_A] 250: seq_item is sent
UVM_INFO sequences.sv(52) @ 300: uvm_test_top.env_o.agt.seqr@@seq_c [seq_C] 300: seq_item is sent
UVM_INFO sequences.sv(14) @ 350: uvm_test_top.env_o.agt.seqr@@seq_a [seq_A] 350: seq_item is sent
UVM_INFO sequences.sv(52) @ 400: uvm_test_top.env_o.agt.seqr@@seq_c [seq_C] 400: seq_item is sent
The log shows that a seq_B grab request is pushed in the front of the arbitration queue. Hence, seq_B requests are serviced earlier and followed by other requests.
UVM Tutorials