What is interrupt?
The interrupts are signaled through which some unusual or special events acknowledge to users. Interrupts are either hardware or software or both generated. Sometimes system generates interrupts or the user also can generate interrupts, it totally depends on system design and its use case. Software interrupts usually used register to acknowledge and hardware interrupts used direct signals to acknowledge. In nutshell, interrupts are events that can be triggered parallel threads of processes.
Importance of Interrupts
For any SoC level or system-level where multiple blocks work together, prepare complex design, in that case, all components work together and are compatible with each other that's why to introduce some exceptions and acknowledge of it, its call interrupts. Sometimes SoC Design used a separate Interrupt controller to handle such requests. So interrupt controller helps to manage system or subsystem level interrupts via enabling and disabling interruptions based on their priority.
Concepts Recall
- uvm_sequence and uvm sequencer
- Concurrent sequence generate
- Sequencer arbitration methods
- uvm_events
- Lock() and grab() methods
uvm_sequence and uvm_sequencer
UVM sequence contains different data types and helps to create different scenarios by used of uvm_sequence_item. uvm_sequencer controls the flow of sequences and directs them to the driver. Also, help in control responding by a driver. Sequencer used as a bridge between sequence and driver. For more details refer uvm cookbook or user guide.
Sample sequence and sequencer code:
class base_seq extends uvm_sequence#(seq_item);
`uvm_object_utils(base_seq)
seq_item seq1;
function new(string name = "base_seq");
super.new(name);
endfunction: new
virtual task body();
`uvm_do(seq1)
//if required perform other operation in body task
endtask: body
endclass: int_seq
class base_seqr extends uvm_sequencer;
`uvm_component_utils(base_seqr);
function new(string name = "base_seqr", uvm_component parent);
super.new(name, parent);
endfunction
endclass: base_seqr
Generate concurrent sequence:
There is two way to generate concurrent sequence 1) Direct used fork...join in base sequence 2) Extends base sequences. In the later part, we discussed pros and cons of these two types.
1st method sample code:
class base_seq extends uvm_sequence #(seq_item);
`uvm_object_utils(base_seq);
seq_item seq1, seq2, seq3;
function new(string name = "base_seq", uvm_component parent);
super.new(name, parent);
endfunction
virtual task body();
//first create sequences and required handle
fork
begin: thread_1
`uvm_do(seq1)
end: thread_1
begin: thread_2
`uvm_do(seq2)
end: thread_2
begin: thread_3
`uvm_do(seq3)
end: thread_3
join
endtask: body
endclass: base_seq
In this case, all sequences start concurrently. But here note that we compromise controlling over sequences like in case we want to perform certain operations on particular sequence then it hard to write code and maybe the length of code becomes larger. so instead of being used, this method used 2 methods as described below.
2nd method sample code:
class base_seq extends uvm_sequence#(seq_item);
`uvm_object_utils(base_seq)
//if need add more functionality
function new(string name = "base_seq");
super.new(name);
endfunction: new
endclass: base_seq
class child_seq extends base_seq;
`uvm_onject_utils(child_seq)
seq_item pkt;
rand int count = 0;
//if need add more veriable and sequence
function new(string name = "child_seq");
super.new(name);
endfunction: new
virtual task body();
//if any operation then write here
for(int i=0; i<count*2; i++)
`uvm_do(pkt)
endtask: body
constraint count_c {count inside {[1:20]};}
endclass: child_seq
class concurrent_seq extends base_seq;
`uvm_onject_utils(concurrent_seq)
child_seq seq1, seq2, seq3;
//if need add more veriable and sequence
function new(string name = "concurrent_seq");
super.new(name);
endfunction: new
virtual task body();
super.body();
//first create sequences and required handle
fork
begin: thread_1
//pre-operation write here
`uvm_do(seq1)
//post-operation write here
end: thread_1
begin: thread_2
`uvm_do(seq2)
end: thread_2
begin: thread_3
`uvm_do(seq3)
end: thread_3
join
endtask: body
//if required used also some constriant or methods
// to control sequence values
endclass: concurrent_seq
By using child classes we can achieve more control, synchronization, and manipulation between sequences.
Handle sequence using sequencer:
Once the sequencer was set then we need to handle these sequences using a sequencer. Note here sequencer is two-way or duplex communication provide between sequence and driver. If we want to send or drive some value in the interface then used a sequencer as a sender and we want to receive something from a monitor or DUT then used it as a receiver. Now we only focus on the first case. As mentioned above uvm_sequencer also help to handle sequences by using build-in methods. Let's deep dive into uvm_sequencer methods with one example.
Let's consider we have 4 different sequences and all sequences also have some sub-sequence. Now, we know uvm_sequencer handles these sequences but it is half answered. Here some questions arise like in which order sequence execute? which sequence goes first and which sequence goes last? If interleaving happens then which order? and so many... Let's take one-by-one questions and figure out their relevant answers.
When multiple sequences try to access a single driver, sequencer schedules that sequences in predefined or random order. This process calls arbitrations. Arbitration comes in various flavors. We can use the arbitration method in a sequencer or directly through test cases or in sequences.
Sample code:
class concurrent_seq extends base_seq;
`uvm_onject_utils(concurrent_seq)
//sub-sequence handle declaration
child_seq seq1, seq2, seq3;
//if need add more veriable and sequence
function new(string name = "concurrent_seq");
super.new(name);
endfunction: new
virtual task body();
super.body();
seq1 = chlid_seq::type_id::create("seq1");
seq2 = chlid_seq::type_id::create("seq2");
seq3 = chlid_seq::type_id::create("seq3");
//write sequencer path m_sequencer.set_arbitration(UVM_SEQ_ARB_FIFO);
//arbitration having other types as well used insated of UVM_SEQ_ARB_FIFO
fork
begin: thread_1
//pre-operation write here
`uvm_do(seq1)
//post-operation write here
end: thread_1
begin: thread_2
`uvm_do(seq2)
end: thread_2
begin: thread_3
`uvm_do(seq3)
end: thread_3
join
endtask: body
endclass: concurrent_seq
Types of arbitrations:
Assume the order of arrival/execution is top-down.
1. UVM_SEQ_ARB_FIFO
UVM_SEQ_ARB_FIFO follows the FIFO mechanism. Therefore in the above case, first stream-1 comes so it will execute first (follow assumption). Then stream-2 and then stream-3 execute. For the next iterations, it will start with stream-1 and end with stream-3.
2. UVM_SEQ_ARB_WEIGHTED
Item priority is treated as distribution weight in random selection (dist keyword). When seeds change randomization changes and respective output also changes. Avoid starvation lower priority also get chance to execute according to its weight.
3. UVM_SEQ_ARB_RANDOM
4. UVM_SEQ_ARB_STRICT_RANDOM
One problem with this method, If high priority continues to send a request for execution then lower priorty is never executed and its going for starvation. So every time used this method for arbitration then make sure starvation not happened.
5. UVM_SEQ_ARB_STRICT_FIFO
This method contains FIFO and priority features. Therefore first stream-1 executes then stream-3 and last stream-2. This method also starvation happened.
6. UVM_SEQ_ARB_USER
It provides user define arbitration method. If none of the above mention models satisfied requirements then go for user define methods. For that create a new sequencer class that extends from uvm_sequencer. In that used user_priority_arbitration() function. Apart from that some properties also help to create their own arbitration which mentions below.
class uvm_sequence_request;
bit grant;
int sequence_id;
int request_id;
int item_priority;
process process_id;
uvm_sequencer_bas::seq_req_t request_t;
uvm_sequence_bas sequence_ptrl;
endclass: uvm_sequence_request
Example-1
class user_define_seqr extends uvm_sequencer#(seq_item);
`uvm_component_utils(user_define_seqr)
function new(string name = "user_define_seqr", uvm_component parent=null);
super.new(name, parent);
endfunction: new
//below method override the default method
function integer user_priority_arbitration(integer avail_sequences[$]);
//define algorithem for arbitration
//return must be integer
//this example for FILO -> first in last out
int index;
index = avail_sequences.size() - 1;
return (avail_sequences[index]);
endfunction: user_priority_arbitration
endclass: user_define_seqr
Example-2
class user_define_seqr extends uvm_sequencer#(seq_item);
`uvm_component_utils(user_define_seqr)
function new(string name = "user_define_seqr", uvm_component parent=null);
super.new(name, parent);
endfunction: new
//below method override the default method
function integer user_priority_arbitration(integer avail_sequences[$]);
//define algorithem for arbitration
//priority greter then 500 that items consider as first outputs
//other items/sequence follow FIFO only
int index;
foreach(avail_sequences[i]) begin
index = arb_sequence_q[avail_sequence[i]].item_priority;
if(index>500)
return(avail_sequence[i]);
end
return(avail_sequence[0]);
endfunction: user_priority_arbitration
endclass: user_define_seqr
Lock() and Grab() methods:
Lock() and grab() methods play a very important role in controlling and synchronization. If the sequencer executing another sequence and some event occurs due to the user wants to pause the current sequence and user can lock/grab the sequencer and start another sequence. Once the started sequence is complete sequencer can resume the last sequence. Ultimately, lock() and grab() methods used in interrupt.
Lock() and unlock():
- Lock sequence requests are treated the same as other arbitrate sequences before the lock() method encounter.
- One's Lock() encounter it will start executing sequences under lock method till unlock() not encounter.
- Lock() is granted after all earlier requests are completed and no other locks or grabs are blocking this sequence until unlock not encounter.
- If no argument is put in a lock(), it is considering the current default sequencer.
Grab() and ungrab():
- Grab() requests execute first in any arbitration.
- One's Grab() encounter starts executing sequences and releases the end of ungrab() encounter.
- Grab() is granted when no other grab or locks are blocking these sequences. The only thing that stops a sequence from grabbing a sequencer is a pre-existing lock() and grab().
- If no argument is put in a grab(), it is considering the current default sequencer.
class seq_item extends uvm_sequence_item;
rand int temp;
function new(string name = "seq_item");
super.new(name);
endfunction: new
`uvm_object_utils_begin(seq_item)
`uvm_field_int(temp, UVM_DEFAULT)
`uvm_object_utils_end
constraint temp_c {temp inside {[1:20]};}
endclass: seq_item
class sequencer extends uvm_sequencer#(seq_item);
`uvm_component_utils(sequencer)
function new(string name = "seq_item", uvm_component parent = null);
super.new(name, parent);
endfunction: new
endclass: sequencer
class driver extends uvm_driver #(seq_item);
`uvm_component_utils(driver)
function new (string name = "driver", uvm_component parent = null);
super.new(name, parent);
endfunction : new
task run_phase (uvm_phase phase);
forever begin
seq_item_port.get_next_item(req);
//`uvm_info(get_type_name(), $sformatf("Temp: %0d",req.temp),UVM_LOW)
#1;
seq_item_port.item_done();
end
endtask: run_phase
endclass: driver
class agent extends uvm_agent;
`uvm_component_utils(agent)
sequencer sqr;
driver drv;
function new (string name = "agent", uvm_component parent = null);
super.new(name, parent);
endfunction: new
function void build_phase (uvm_phase phase);
super.build_phase(phase);
sqr = sequencer::type_id::create("sqr", this);
drv = driver::type_id::create("drv", this);
endfunction: build_phase
function void connect_phase (uvm_phase phase);
super.connect_phase(phase);
drv.seq_item_port.connect(sqr.seq_item_export);
endfunction: connect_phase
endclass: agent
class base_seq extends uvm_sequence#(seq_item);
`uvm_object_utils(base_seq)
seq_item req;
function new(string name = "base_seq");
super.new(name);
endfunction: new
endclass: base_seq
class child_seq extends base_seq;
`uvm_object_utils(child_seq)
seq_item pkt;
rand int count = 0;
//if need add more veriable and sequence
function new(string name = "child_seq");
super.new(name);
endfunction: new
virtual task body();
//if any operation then write here
for(int i=0; i<count; i++) begin
`uvm_do(pkt)
`uvm_info(get_type_name(), $sformatf("SEQ count: %0d",i), UVM_LOW)
end
endtask: body
constraint count_c {soft count inside {[1:2]};}
endclass: child_seq
class concurrent_seq extends base_seq;
`uvm_object_utils(concurrent_seq)
child_seq seq1, seq2, seq3;
//if need add more veriable and sequence
function new(string name = "concurrent_seq");
super.new(name);
endfunction: new
virtual task body();
super.body();
seq1 = child_seq::type_id::create("seq1");
seq2 = child_seq::type_id::create("seq2");
seq3 = child_seq::type_id::create("seq3");
fork
`uvm_info(get_type_name(), "LOCK START", UVM_LOW);
begin: thread_1
//pre-operation write here
`uvm_do(seq1)
`uvm_info(get_type_name(), $sformatf("FOR SEQ1 count = %0d",seq1.count)
, UVM_LOW)
//post-operation write here
end: thread_1
begin: thread_2
`uvm_do_with(seq2, {count==3;})
`uvm_info(get_type_name(), $sformatf("FOR SEQ2 count = %0d",seq2.count)
, UVM_LOW)
end: thread_2
begin: thread_3
`uvm_do_with(seq3, {count inside {[5:10]};})
`uvm_info(get_type_name(), $sformatf("FOR SEQ3 count = %0d",seq3.count)
, UVM_LOW)
end: thread_3
join
endtask: body
endclass: concurrent_seq
class test extends uvm_test;
agent agt;
`uvm_component_utils(test)
function new (string name = "test", uvm_component parent = null);
super.new(name, parent);
endfunction : new
function void build_phase (uvm_phase phase);
super.build_phase(phase);
agt = agent::type_id::create("agt", this);
endfunction : build_phase
task run_phase(uvm_phase phase);
concurrent_seq c_seq;
c_seq = concurrent_seq::type_id::create("c_seq");
phase.raise_objection(this);
agt.sqr.set_arbitration(UVM_SEQ_ARB_RANDOM);
c_seq.start(agt.sqr);
phase.drop_objection(this);
endtask : run_phase
endclass : test
module top();
initial begin
run_test("test");
end
endmodule : top
Output: With UVM_ARB_RANDOM
For simulate lock() and grab() method:
Preform some changes to simulate lock and grab methods. For grab used grab() instead of using lock. This lock/grab method consider for 3rd sequence or 3rd thread in concurrent_seq.
class child_seq extends base_seq;
`uvm_object_utils(child_seq)
seq_item pkt;
rand int count = 0;
static int seq_no = 0;
//if need add more veriable and sequence
function new(string name = "child_seq");
super.new(name);
endfunction: new
virtual task body();
//if any operation then write here
if(seq_no==2) lock();
else seq_no++;
for(int i=0; i<count; i++) begin
`uvm_do(pkt)
`uvm_info(get_type_name(), $sformatf("SEQ count: %0d",i), UVM_LOW)
end
if(seq_no==2) unlock();
endtask: body
constraint count_c {soft count inside {[1:2]};}
endclass: child_seq
 |
| For lock() and unlock() |
 |
For grab() and ungrab() |
 |
| For thread-2 having grab() and thread-3 having lock() |
NOTE: - Once the child sequence issues lock() or grab() then parent sequences cant able start any parallel sequences until the child sequence has unlocked.
- There is no priority on locking. Also, allow to used multiple locks and grabs used in sequencer or sequence.
Now think about how above mention concepts and methods help in modeling interrupts. For more details please
click here. Refer to all subsections also.
Interrupt Modeling Method-1
(put one dig which contains arch. and two concurrent sequences one for regular and other for isr)
{also explain how to isr call through monitor replay or something else}
Usually, In any SoC or IP, the level test bench contains some special register or signals to drive through driver for interrupts and for detection used to monitor to encounter interrupts. So monitor give replay for some unusual happen and prepare interrupt service routine(ISR).
Sample code:
class monitor extends uvm_monitor;
//connect TLM ports and declear here required variables
uvm_event tr_event;
task wait_tr;
//event declear as globle so it can be used in sequence
//and base test or other component
tr_event = uvm_event_pool::get_global("uvm_tr");
@(posedge vif.interrupt);
tr_event.trigger();
// other opetration
//If flag are used the also perform operation for that
endtask: wait_tr
endclass: monitor
class interrupt_seq extends base_seq;
//connect TLM ports and declear here required variables
uvm_event tr_event;
task body();
tr_event = uvm_event_pool::get_global("uvm_tr");
//wait for triggering event in monitor
tr_event.wait_trigger();
//some operations
grab();
`uvm_do(int_seq)
ungreb();
endtask:body
endclass: interrupt_seq
This method works well in simpler one interrupt are theirs in the system. But it obviously has not happened in today's system. Multiple interrupts are in command nowadays. This method used grab() methods to grab a sequence as an when event trigger. Now let's assume high priority interrupt come then it is not able to execute due to grab() property. Resolve this problem by setting priorities.
class concurrent_seq extends base_seq;
//declear required variables
virtual task body();
fork
begin: normal_seqs
//normal operation
end: normal_seqs
begin: ISR_1
set_priority(500);
tr1_event = uvm_event_pool::get_global("uvm_tr1");
tr1_event_wait_trigger();
//other process for interrupt-1
end: ISR_1
begin: ISR_2
set_priority(600);
tr2_event = uvm_event_pool::get_global("uvm_tr2");
tr2_event_wait_trigger();
//other process for interrupt-2
end: ISR_2
join
endtask: body
endclass: concurrent_seq
Sometimes interrupts triggers through special registers and it's a dependency on the other events or some status register. In the case above mention, the method doesn't help much. For that need to or create a decoding interrupt sequence that helps to mimic this scenario. In that need to read the register to discover which interrupt needs executing. after that used grab() to grabbing sequence. And for multiple ISR sequences used the set_prority() method. ISR sequences priority makes sure the interrupts are serviced in correct order. Also handles scenario such as each new interrupt event triggers new register read. So keep tracking each and every sequence which triggers.
For register-based interrupts Upload in PART-2. Stay tuned.
Comments
Post a Comment