sequencer
sequencer产生transaction,而driver负责接收transaction。
class my_driver extends uvm_driver #(my_transaction);
这样定义的好处是可以直接使用uvm_driver中的某些预先定义好的成员变量,例如uvm_driver中有成员变量req,它的类型是传递给uvm_driver的参数,即my_transaction。
task my_driver::main_phase(uvm_phase phase);
phase.raise_objection(this);
phase.drop_objection(this);
endtask
每个sequence都应该派生自uvm_sequence,并且在定义时指定要产生的transaction类型。
每一个sequence都有一个body任务,当一个sequence启动之后,会自动执行body中的代码。
在下面代码中,用到了一个宏uvm_do。这个宏是UVM最常用的宏之一,它用于
1、创建一个my_transaction的实例m_trans;
2、将其随机化;
3、最终将其送给sequencer.
如果不使用uvm_do宏,也可以直接使用start_item和finish_item的方式产生transaction。
class my_sequence extends uvm_sequence #(my_transaction);
my_transaction m_trans;
function new(string name="my_sequence");
super.new(name);
endfunction
virtual task body();
repeat(10) begin
`uvm_do(m_trans)
end
#1000;
endtask
`uvm_object_utils(my_sequence)
endclass
一个sequence在向sequencer发送transaction前,要先向sequence发送一个请求,sequencer把这个请求放在一个仲裁队列中。
作为sequencer,它需要做两件事情:第一,检测仲裁队列里是否有某个sequence发送transaction的请求;第二,检测driver是否申请transaction。
1、如果仲裁队列里有发送请求,但是driver没有申请transaction,那么sequencer将会一直处于等待状态,直到driver申请新的transaction。
此时,sequencer同意sequence的发送请求,sequence在得到sequencer的批准后,产生一个transaction并交给sequencer,后者把这个transaction交给driver。
2、如果仲裁队列里没有发送请求,但是driver向sequence申请新的transaction,那么sequencer将会处于等待sequence的状态,一直到有sequence递交发送请求,sequencer马上同意这个请求,
sequence产生transaction并交给sequencer,最终driver获得这个transaction。
3、如果仲裁队列里有发送请求,同时driver也在向sequencer申请新的transaction,那么将会同意发送请求,sequence产生transaction并交给sequencer,最终driver获得这个transaction。
driver如何向sequencer申请transaction呢?
在uvm_driver中有成员变量seq_item_port,而在uvm_sequencer中有成员变量seq_item_export,这两者之间可以建立一个通道。通道中传递的transaction类型就是定义my_sequenr和my_driver
时指定的transaction类型。
当然,这里并不需要显示的指定“通道”类型,UVM已经做好了。在my_agent中,使用connect函数把两者联系在一起。
function void my_agent::connect_phase(uvm_phase phase);
super.connect_phase(phase);
if(is_active==UVM_ACTIVE)begin
drv.seq_item_port.connect(sqr.seq_item_export);
end
endfunction
当把两者连接好之后,就可以在driver中通过get_next_item任务向sequencer申请新的transaction。
task my_driver::main_phase(uvm_phase phase);
……
while(1)begin
seq_item_port.get_next_item(req);
drive_one_pkt(req);
seq_item_port.item_done();
end
endtask
在如上代码中,一个最显著的特征是使用了while(1)循环,因为driver只负责驱动transaction,而不负责产生,只要有transaction就驱动,所以必须做成一个无限循环的形式。
这与monitor、refmodel和scoreboard情况非常类似。
通过get_next_item任务来得到一个新的req,并且驱动它,驱动完成后调用item_done通知sequencer。这里为什么会有一个item_done?
当driver使用get_next_item得到一个transaction时,sequencer自己也保留一份刚刚发送出的transaction。当出现sequencer发出了transaction,而driver没有得到的情况时,sequencer会把保留的这份transaction再发送出去。
那么sequencer如何知道driver是否已经成功得到transaction呢?如果在下次调用get_next_item前,item_done被调用,那么sequence就认为driver已经得到了这个transaction,将会把这个transaction删除。换言之,这是一种握手机制。
在sequence中,向sequencer发送transaction使用的是uvm_do宏。这个宏什么时候会返回呢?
uvm_do宏产生了一个transaction并交给sequencer,driver取走这个transaction后,uvm_do并不会立刻返回执行下一次uvm_do宏,而是等待在那里,直到driver返回item_done信号。此时,uvm_do宏才算是执行完毕,返回后开始下一个uvm_do。