如上图所示,VIP的开发一般都会提供env的层次,或者最少是agent的层次,原则上用户是不可以修改vip本身的代码的,因此针对不同用户的需求,需要留一些勾子,可以在不修改源码的前提下又可以完成不同的代码需求,这就是callback。
下面来举例说明:
以monitor为例,我希望它在每次完成数据包采集后,都告诉我一下,采到了一个什么类型的数据包,假如这个monitor是我自己写的呢,一个显而易见的做法就是我在退出task之前调一个函数,就叫fun_print()好了,在这个function里完成打印。
class monitor;
virtual task fun_sample();
...
fun_print();
endtask
virtual function fun_print();
$display("this is a read trans");
endfunction
endclass
然后在需要用到这个monitor的地方比如agent中例化一个monitor A, 调用A.func_sample()时就能自然而然地调用fun_print()了。假如我下次想让fun_print() 打印的是 "this is a crazy trans", 我该怎么做呢?我想起来以前讲virtual的功能时提到过,父类句柄是可以调用子类实例的function的,那我可以从monitor extend一个new monitor,并且修改它的fun_print函数,就能达到目的了。 但是,父类句柄调用子类实例function的一个重要前提,就是完成了一个句柄转换的操作, A=new monitor inst, 又要改agent的代码了。。。显然也和VIP不改代码的需求冲突。(*这里还需要补充一些说明,放在最后)
假如我不把fun_print写在monitor里面会不会好一点呢:
class monitor;
classC c_father;
virtual task fun_sample();
...
c_father.fun_print();
endtask
endclass
class classC;
virtual function fun_print();
$display("this is a crazy trans");
endfunction
endclass
这样的话,我就扩展 classC 就好了,好像比较轻量级, 但是仔细想想,根本原则和上面是一样的,我还是要利用virtual的特性,利用父类句柄调用子类实例的function,只不过改代码的地方从agent移到monitor了。
为了解决如何告知调用方的问题,源码开发的大神们提供了一个厉害的思路:
class monitor;
classC c_father;
virtual task fun_sample();
...
classC的所有子类.fun_print();//将单一句柄的调用改成了调用其所有子类的fun_print
endtask
endclass
class classC;
virtual function fun_print();
$display("this is a crazy trans");
endfunction
endclass
这样用户就可以不用改monitor源码了,但是如何实现查找并调用所有子类函数的目的呢-------------创建一个篮子,让用户可以把所有新建的子类实例丢进去,monitor实例则将所有子类实例都拿起来看一下是不是自己的。(敲完这段,突然觉得好像去拿快递啊。。。,快递员把所有快递都丢在前台,然后我去把所有快递都翻一遍,看看有没有我的。。。里面装了东西的快递盒子岂不就是我的子类实例,没装东西的快递盒子就是我上面的classC)。源码开发大佬们将这种专门为了回调函数而定义的类称为uvm_callback,指定它为开山鼻祖。
于是上述classC就应该直接从uvm_callback扩展。
class classC extends uvm_callback;
virtual function fun_print();
endfunction
endclass
新建子类的动作则是:
class classC1 extends classC;
virtual function fun_print():
$display("this is a crazy trans");
endfunction
endclass
class classC2 extends classC;
virtual function fun_print():
$display("this is a mad trans");
endfunction
endclass
往篮子里丢实例的动作则由参数化类uvm_callback#(component,uvm_callback)里的add函数来完成:
classC1 c1;
classC2 c2;
c1=classC1::type_id::create("c1");
c2=classC2::type_id::create("c2");
uvm_callback#(monitor,classC)::add("xxxx.xxx.monitor_inst1",c1);
uvm_callback#(monitor,classC)::add("xxxx.xxx.monitor_inst2",c2);
实际上这个过程也创建了monitor实例和callback子类实例的联系,让验证平台中的不同monitor实例可以找到属于自己的fun_print();
让monitor启动找子类fun_print()则用一个宏来进行:
`uvm_do_callbacks(monitor,classC,fun_print())
为了这个宏能顺利执行,还需要在monitor类中注册一下callback
`uvm_register_cb(monitor,classC)
至此可以重新整理一下这个过程了。
公司前台告诉快递员我们只收寄到公司的快递(VIP 定义了一个classC),快递员将快递盒子放到公司前台(用户将扩展的子类定义好,并实例化,add到某个约定的地方), 我去翻一遍前台的所有快递,找我的名字(VIP monitor查看一遍所有的子类实例,然后找出与自己匹配的那个执行),我有资格翻找的前提是,我是这个公司的员工(VIP monitor代码中需要将callback注册好)。
至此,VIP的使用者就是一个快递员的角色,动作非常简单,就是extend 一个类,重载函数,然后调用add。其余都是VIP开发者的事情。
*这里是特殊说明:其实有很多其他办法能实现相同的目的,比如我把VIP的monitor 扩展一下,产生一个new monitor,然后用工厂模式,将monitor override整个类替换掉,或者用实例替换也是可以的,能支持不同的monitor实例做不同的事情。callback和重载两者的优劣我也没有比对过。希望以后能带来一些使用感受。
*我上面举的例子非常傻,只是打印而已,有一些稍微正经一点的应用,比如在AHB VIP的monitor中,在每拍结束的位置做一下覆盖率采样,由于使用者无法触及VIP内部代码,想要知道每拍结束的位置,不使用VIP留的接口是很难实现的,除非自己实现一个monitor。
**自己看一遍书总以为自己懂了,写一遍分享出来发现原来有更简单地理解方式,这就是分享的意义,希望有疑问也能留言,一起探讨学习~