当多个sequence在一个sequencer上同时启动时,每个sequence产生的transaction都需要参与sequencer的仲裁。考虑这样一种情况,某个sequence一旦要执行,那么它所有transaction必须连续地交给driver,如果中间夹杂着其他sequence的transaction就会发生错误。要解决这个问题,可以像上节一样对此sequence赋予较高的优先级。但假如有其他sequence有更高的优先级呢?所以这种解决方法并不科学。
在UVM中可使用lock操作来解决这个问题:lock就是sequence向sequencer发送一个请求,这个请求与其他sequence发送transaction的请求一同被放入sequencer的仲裁队列中。当前面所有的请求被处理完毕后,sequencer开始响应这个lock请求,此后sequencer会一直连续发送这个sequence的transaction直到unlock操作被调用。从效果上看,此sequencer的所有权并没有被所有的sequence共享,而是被申请lock操作的sequence独占。使用lock操作的sequence为:
class sequence1 extends uvm_sequence #(my_transaction);
…
virtual task body();
…
repeat (3) begin
`uvm_do_with(m_trans, {m_trans.pload.size < 500;})
`uvm_info("sequence1", "send one transaction", UVM_MEDIUM)
end
lock(); //notice
`uvm_info("sequence1", "locked the sequencer ", UVM_MEDIUM)
repeat (4) begin
`uvm_do_with(m_trans, {m_trans.pload.size < 500;})
`uvm_info("sequence1", "send one transaction", UVM_MEDIUM)
end
`uvm_info("sequence1", "unlocked the sequencer ", UVM_MEDIUM)
unlock(); //notice
repeat (3) begin
`uvm_do_with(m_trans, {m_trans.pload.size < 500;})
`uvm_info("sequence1", "send one transaction", UVM_MEDIUM)
end
…
endtask
…
endclass
class sequence0 extends uvm_sequence #(my_transaction);
…
virtual task body();
…
repeat (5) begin
`uvm_do(m_trans)
`uvm_info("sequence0", "send one transaction", UVM_MEDIUM)
end
#100;
…
endtask
`uvm_object_utils(sequence0)
endclass
在my_sequencer上同时启动了两个sequence: sequence1和sequence2:
task my_case0::main_phase(uvm_phase phase);
sequence0 seq0;
sequence1 seq1;
seq0 = new("seq0");
seq0.starting_phase = phase;
seq1 = new("seq1");
seq1.starting_phase = phase;
fork
seq0.start(env.i_agt.sqr);
seq1.start(env.i_agt.sqr);
join
endtask
将此sequence1与sequence0在env.i_agt.sqr上启动,会发现在lock语句前,sequence0和seuquence1会交替产生transaction;在lock语句后,一直发送sequence1的transaction直到unlock语句被调用,sequence0和seuquence1又开始交替产生transaction。
如果两个sequence都试图使用lock任务来获取sequencer的所有权会如何呢?答案是先获得所有权的sequence在执行完毕后才会将所有权交还给另外一个sequence。
class sequence0 extends uvm_sequence #(my_transaction);
…
virtual task body();
…
repeat (2) begin
`uvm_do(m_trans)
`uvm_info("sequence0", "send one transaction", UVM_MEDIUM)
end
lock();
repeat (5) begin
`uvm_do(m_trans)
`uvm_info("sequence0", "send one transaction", UVM_MEDIUM)
end
unlock();
repeat (2) begin
`uvm_do(m_trans)
`uvm_info("sequence0", "send one transaction", UVM_MEDIUM)
end
#100;
…
endtask
`uvm_object_utils(sequence0)
endclass
将sequence0与sequence1同时在env.i_agt.sqr上启动,会发现sequence0先获得sequencer所有权,在unlock函数被调用前一直发送sequence0的transaction。在unlock被调用后,sequence1获得sequencer的所有权,之后一直发送sequence1的transaction,直到unlock函数被调用。
与lock操作一样,grab操作也用于暂时拥有sequencer的所有权,只是grab操作比lock操作优先级更高。lock请求是被插入sequencer仲裁队列的最后面,等到它时其前面的仲裁请求都已经结束了。grab请求则被放入sequencer仲裁队列的最前面,它几乎是一发出就拥有了sequencer的所有权:
class sequence1 extends uvm_sequence #(my_transaction);
…
virtual task body();
…
repeat (3) begin
`uvm_do_with(m_trans, {m_trans.pload.size < 500;})
`uvm_info("sequence1", "send one transaction", UVM_MEDIUM)
end
grab(); //notice
`uvm_info("sequence1", "grab the sequencer ", UVM_MEDIUM)
repeat (4) begin
`uvm_do_with(m_trans, {m_trans.pload.size < 500;})
`uvm_info("sequence1", "send one transaction", UVM_MEDIUM)
end
`uvm_info("sequence1", "ungrab the sequencer ", UVM_MEDIUM)
ungrab(); //notice
repeat (3) begin
`uvm_do_with(m_trans, {m_trans.pload.size < 500;})
`uvm_info("sequence1", "send one transaction", UVM_MEDIUM)
end
…
endtask
`uvm_object_utils(sequence1)
endclass
如果两个sequence同时试图使用grab任务获取sequencer的所有权将会如何呢? 这种情况与两个sequence同时试图调用lock函数一样,先获得所有权的sequence执行完毕后才会将所有权交还给另外一个试图所有权的sequence。
如果一个sequence在使用grab任务获取sequencer的所有权前,另一个sequence已经使用lock任务获得了sequencer的所有权会如何呢?答案是grab任务会一直等待lock的释放。grab任务还是比较讲文明的,虽然它会插队,但绝不会打断别人正在进行的事情。
当有多个sequence同时在一个sequencer上启动时,所有的sequence都参与仲裁,根据算法决定哪个sequence发送transaction。仲裁算法是由sequencer决定的,sequence除了可以在优先级上进行设置外,对仲裁的结果无能为力。
通过lock任务和grab任务,sequence可以独占sequencer,强行使sequencer发送自己产生的transaction。UVM也提供措施使sequence可在一定时间内不参与仲裁,即令此sequence失效。
sequencer在仲裁时,会查看sequence的is_relevant函数的返回结果。如果为1,说明此sequence有效,否则无效。因此可通过重载is_relevant函数来使sequence失效:
class sequence0 extends uvm_sequence #(my_transaction);
my_transaction m_trans;
int num;
bit has_delayed;
…
virtual function bit is_relevant(); //notice
if((num >= 3)&&(!has_delayed)) return 0;
else return 1;
endfunction
virtual task body();
…
fork
repeat (10) begin
num++;
`uvm_do(m_trans)
`uvm_info("sequence0", "send one transaction", UVM_MEDIUM)
end
while(1) begin
if(!has_delayed) begin //发送3个transaction后变为无效
if(num >= 3) begin
`uvm_info("sequence0", "begin to delay", UVM_MEDIUM)
#500000;
has_delayed = 1'b1;
`uvm_info("sequence0", "end delay", UVM_MEDIUM)
break;
end
else
#1000;
end
end
join
…
endtask
…
endclass
这个sequence在发送了3个transaction后开始变为无效,延时500000时间单位后又开始有效。 将此sequence与sequence1同时启动,会发现在失效前sequence0和sequence1交替发送transaction;而在失效的500000时间单位内,只有sequence1发送transaction;当sequence0重新变有效后,sequence0和sequence1又开始交替发送transaction。某种程度上, is_relevant与grab任务和lock任务是完全相反的。通过设置is_relevant,可使sequence主动放弃sequencer的使用权,而grab任务和lock任务则强占sequencer的所有权。
除了is_relevant外,sequence中还有一个任务wait_for_relevant也与sequence的有效性相关:
class sequence0 extends uvm_sequence #(my_transaction);
…
virtual function bit is_relevant();
if((num >= 3)&&(!has_delayed)) return 0;
else return 1;
endfunction
virtual task wait_for_relevant();
#10000;
has_delayed = 1;
endtask
virtual task body();
…
repeat (10) begin
num++;
`uvm_do(m_trans)
`uvm_info("sequence0", "send one transaction", UVM_MEDIUM)
end
…
endtask
…
endclass
当sequencer发现在其上启动的所有sequence都无效时,此时会调用wait_for_relevant并等待sequence变有效。当此sequence与sequence1同时启动并发送3个transaction后,sequence0变为无效。此后sequencer一直发送sequence1的transaction直到全部transaction都发送完毕。此时sequencer发现sequence0无效,会调用其wait_for_relevant。换言之sequence0失效是自己控制,但重新变得有效却是受其他sequence控制。如果其他sequence永远不结束,sequence0将永远处于失效状态。这段sequence0代码与前面sequence0的区别是,前面代码中sequence0并不是等待sequence1的transaction全部发送完毕,而是主动控制着自己何时有效何时无效。
在wait_for_relevant中必须将使sequence无效的条件清除。上面代码中假如wait_for_relevant只是如下定义:
virtual task wait_for_relevant();
#10000;
endtask
那么当wait_for_relevant返回后,sequencer会继续调用sequence0的is_relevant,发现依然是无效状态,则继续调用wait_for_relevant。系统会处于死循环的状态。
在前例的sequence0中,通过控制延时(500000)单位时间使sequence0重新变得有效。假如这段时间内sequence1的transaction发送完毕后,而sequence0中又没有重载wait_for_relevant任务,那么将会给出如下错误提示:
UVM_FATAL @ 1166700: uvm_test_top.env.i_agt.sqr@@seq0 [RELMSM] is_relevant()
was implemented without defining wait_for_relevant()
因此,is_relevant与wait_for_relevant一般应成对重载,不能只重载其中的一个。 前例中的sequence0没有重载wait_for_relevant是因为设置了延时,可保证不会调用到wait_for_relevant。 在使用时应该重载wait_for_relevant这个任务。