Callback 机制
在UVM 验证平台中,callback 机制的最大用处就是提高验证平台的可重用性。很多情况下,验证人员期望在一个项目中开发的验证平台能够用于另外一个项目。但是,通常来说,完全的重用是比较难实现的,两个不同的项目之间或多或少会有一些差异。如果把两个项目不同的地方使用 callback 函数来做,而把相同的地方写成一个完整的 env,这样重用时,只要改变相关的 callback 函数 env 可完全的重用。
除了提高可重用性外,callback 机制还用于构建异常的测试用例。只是在 UVM 中,构建异常的测试用例有很多种方式,如factory 机制的重载callback 机制只是其中的一种。
post_randomize 函数是 SystemVerilog 提供的广义的 callback 函数。UVM也为用户提供了广义的 callback函数/任务: pre_body 和post _body,除此之外还有pre_ do、mid_ do、post_do。
callback 机制制的使用
作为 VIP的开发者应该做的事情:
class A extends uvm_callback;
virtual task pre_tran (my_driver drv, ref my_transaction tr);
endtask
endclass
A类一定要从 uvm_callback 派生,另外还需要定义一个pre_tran 的任务,此任务的类型一定要是 virtual 的,因为从A 派生的类需要重载这个任务
接下来声明一个A_pool类:
typedef uvm_callbacks# (my_driver, A) A_pool;
A pool 的声明相当简单,只需要一个 typeder语句即可。另外,在这个声明中除了变指明这是一个A类型的池子外,还要指明这个池子将会被哪个类使用。
如果my.driver 使用这个池子,需要将此池子声明为 my_driver专用的。之后,在 my_driver中要做如下声明:
typedef class A;
class my_driver extends uvm_driverf (my_transaction);
'uvm_component_utils(my driver)
'uvm_register_cb(my_driver,A)
endclass
这个声明与A_pool的类似,要指明 my_driver和A。在my_driver的 main_phase 中调用pre_tran 时需调用一个宏来实现:
task my_driver::main_phase (uvm_phase phase);
while(1) begin
seq_item_port.get_next_item(req);
'uvm do callbacks (my_driver, A, pre_tran(this, reg))
drive_one_pkt(req);
seq_item_port.item done ();
endclass
uvm_do_callbacks宏的第一个参数是调用pre_tran 的类的名字,这里自然是my_driver,第二个参数是哪个类具有 pre tran,这里是A,第三个参数是调用的是函数/任务,这里是 pre_tran,在指明是 pre_tran 时,要顺便给出 pre_tran 的参数。
作为使用 VIP 的用户来说,需要做如下事情:
首先从 A 派生一个类:
class my_callback extends A;
'uvm_object_utils(my_callback)
virtual task pre_tran(my_driver drv, ref my_transaction tr);
'uvm_info("my_callback","this is pre_tran task",UVM_MEDIUM)
endtask
endclass
其次,在测试用例中将 my_callback 实例化,并将其加人 A_pool中:
function void my_case0::connect_phase (uvm_phase phase);
my_callback my_cb;
super.connect phase(phase);
my_cb = my_callback::type_id::create("my_cb");
A_pool::add(env.i_agt.drv, my_cb);
endfunction
my_callback 的实例化是在 connect_phase 中完成的,实例化完成后需要将 my_cb 加人A_pool中。同时,在加人时需要指定是给哪个my_driver 使用的。因为很可能整个 base_test中实例化了多个my_env,从而有多个my_driver 的实例,所以要将my_driver 的路径作为add函数的第一个参数。至此,一个简单的 callback 机制示例就完成了。这个示例几乎涵盖 UVM 中所有可能用到的 callback 机制的知识,大部分 callback 机制的使用都与这个例子相似。
总结一下,对于 VIP 的开发者来说,预留一个 callback 函数/任务接口时需要做以下几步:
1.定义一个A类。
2.声明一个A _pool类。
3.在要预留 callback 函数/任务接口的类中调用uvm_register_cb 宏。
4.在要调用 callback 函数/任务接口的函数/任务中,使用uvm _do _callbacks宏。
对于 VIP 的使用者来说,需要做如下几步:
1.从A派生一个类,在这个类中定义好 pre _tran。
2.在测试用例的 connect_phase 中将从 A类派生的类实例化。并将其加人入 A_pool中.
类继承父类的 callback 机制
由于一个 callback 池(即A_pool)在声明的时候指明了这个池子只能装用于 my_driver 的 callback,那么怎样才能使原来的 callback 函数/任务能够用于 new_driver 中呢?
这就涉及到了子类继承父类的 callback 函数/任务问题。
my_driver 使用上节中的定义,在此基础上派生新的类 new_driver.
class new_driver extends my_driver;
'uvm_component_utils(new driver)
'uvm_set_super_type(new_driver, my_driver)
endclass
task new_driver: :main_phase (uvm_phase phase) ;
'uvm_info("new_driver", "this is new driver", UVM_MEDIUM)
seq_item_port.get_next_item(req);
while(l) begin
'uvm_do_callbacks (my_driver, A, pre_tran(this, req))
drive_one_pkt(req);
seq_item_port.item_done();
end
endtask
这里使用了uvm_set_super_type宏,它把子类和父类关联在一起。其第一个参数是子类,第二个参数是父类。在 main phase 中调用uvm_do_callbacks 宏时,其第一个参数是my_driver,而不是new_driver,即调用方式与在 my_driver 中一样。
在my_agent 中实例化此 new_driver:
function void my_agent::build phase (uvm_phase phase);
super.build_phase (phase);
if (is_active == UVM_ACTIVE) begin
sqr = my_sequencer::type_id::create("sqr", this);
drv = new_driver::type_id::create("drv", this);
end
mon = my_monitor::type_id::create("mon", this);
endfunction
使用callback 函数/任务来实现所有的测试用例
其实完全可以不用sequence,只用 callback 函数/任务就可以实现所有的测试用例。假设A类定义如下:
class A extends uvm_callback;
my_transaction tr;
virtual function bit gen_tran();
......
endfunction
virtual task run (my_driver drv, uvm_phase phase);
phase.raise_objection (drv);
drv.vif.data <= 8'b0;
drv.vif.valid <= 1'b0;
while(!drv.vif.rst_n)
@(posedge drv.vif.clk);
while(gen_tran()) begin
drv.drive_one_pkt(tr);
end
phase.drop_objection(drv);
endtask
endclass
在my_driver 的 main_phase 中,去掉所有其他代码,只调用A的 run():
task my_driver: :main_phase (uvm_phase phase) ;
'uvm_do_callbacks (my_driver, A, run(this, phase))
endtask
在建立新的测试用例时,只需要从A 派生一个类,并重载 gen_tran 函数:
class my_callback extends A;
int pkt num = 0;
'uvm object_utils (my_callback)
virtual function bit gen_tran();
‘uvm_info("my_callback","gen_tran","UVM_MEDIUM")
tr = new("tr");
assert(tr.randomize());
pkt_num++;
return 1;
end
else
return 0;
endfunction
endclass
在这种情况下,新建测试用例相当于重载 gen_tran。如果不满足要求,还可以将A类run 任务重载。在这个示例中完全丢弃了 sequence 机制,在 A类的run任务中进行控制 objection,激励产生在gen_tran 中。