接上一讲UVM(1),这讲开始学习核心基类、phase机制等内容。
目录
一、核心基类
1、uvm_object
UVM世界中的类最初都是从一个uvm_void根类 (root class)继承来的,而实际上这个类并没有成员变量和方法
uvm_void只是一个虚类 (virtual class) ,还在等待将来继承于它的子类去开垦。在继承于uvm_void的子类中,有两个类,一个为uvm_object类 ,另一个是uvm_port_base类
在UVM世界的类库地图中除过事务接口 (transaction interface)类继承于uvm_port_base,其它所有的类都是从uvm obiect类一步步继承而来的。
从uvm_obiect提供的方法和相关的宏操作来看,它的核心方法主要提供与数据操作的相关服务:
说明:与SV对比,无论是copy或者clone,都需要确保在操作的过程中,需要有source object和target object。
2、域的自动化(field automation)
首先对域进行一个解释:field就是在UVM类里面所有的成员变量都可以称为field(域)
从UVM通过域的自动化(如下),使得用户在注册UVM类的同时也可以面明今后会参与到对象拷贝、克隆、打印等操作的成员变量。
凡是声明了的成员变量,都将在数据操作时自动参与进来。
域的自动化解放了verifier的双手,这使得在使用uvm_obiect提供的一些预定义方法时,非常便捷,而无需再实现自定义方法。
如果有一些数据没有通过域的自动化来声明的话,它们也将不会自动参与到数据的拷贝、打印等操作,除非用户自己去定义这些数据操作方法。
对于新手来说,请养成以下习惯:
- 在注册component或者obiect的时候,使用`uvm_{component,object}_utils_begin和uvm_{component,obiect!utils_end来配对包裹接下来的域的自动化
- 域的自动化为了尽量识别多数的变量,以及做相应的处理,对应的宏的种类非常多,但是也不需要额外的担心,你可以使用路桑的红宝书来查各种域的自动化时所对应的宏
- 域的自动化的相关宏都是`uvm_field_{int,obiect,string, enum,event,real..}(ARG,FLAG)。ARG表示成员变量。 FLAG表示用来标记的数据操作。
- FLAG的具体表示可以参照红宝书的表10.3,初学者只需要默认采取UVM_ALL ON或者UVM_DEFAULT,即将所有的数据操作方法都打开。
- 常见的数据操作方法,包括之前所介绍的copy,compare,print,record,pack和其它。
3、拷贝(copy)
- 在UVM的数据操作中,需要对copy和clone如以区分。前者默认已经创建好了对象,只需要对数据进行拷贝;后备则会自动创建对象并对source obiect进行数据拷贝,再返回target object句柄
- 无论是copy或者clone,都需要对数据进行复制
- 但是如果数据成员包括句柄,那么拷贝的时候,拷贝该句柄指向的对象
- 在进行copy时,默认进行的是深拷贝 (deep copy) ,即会执行copy()和do_copy()。
说明:do_copy()是copy()的回调函数,定义好了do_copy()不需要自己去调用,调用copy()完后就会UVM会自动调用do_copy()!!!
举个例子:
这块要注意的是:
- 新添加了一个类ball,并且在box中例化了一个ball的对象。在拷贝过程中,box的其它成员都正常拷贝了,但对于box::b的拷贝则通过了ball的深拷贝方式进行。即先执行自动拷贝copy(),来拷贝允许拷贝的域,由于ball::color不允许拷贝,所以只拷贝了ball::diameter。
- 接下来再执行do_copy()函数,这个函数是需要用户定义的回调函数(callback function) ,即在copy()执行完后会执行do_copy()。如果用户没有定义该函数,那么则不会执行额外的数据操作;从ball::do_copy()函数可以看到,如果被拷贝对象的diameter小于20,那么则将自身的diameter设置为20。因此,最后对象b2.b的成员与b1.b的成员数值不同。
4、比较(compare)
比较方法经常会在两个数据类中进行。
说明:
在上面的两个对象比较中,会将每一个自动化的域进行比较,所以在执行compare()函数时,内置的比较方法也会将比较错误输出。
默认的比较器,即uvm_package::uvm_default_comparer最大输出的错误比较信息是1,也就是说当比较错误发生时,不会再进行后续的比较,即便后续有其他错误。
实际上,在uvm_obiect使用到的方法compare()、 print()和pack(),如果没有指定数据操作配置对象作为参数时,会使用在uvm_pkg中例化的全局数据操作配置成员
5、全局对象
6、打印
通过field automation,使得声明之后的各个成员域会在调用uvm_object::print函数时自动打印出来
相比于在仿真中设置断点,逐步调试,打印是另外一种调试方式。它的好处在于可以让仿真继续进行,会在最终回顾执行过程中,从全局理解执行的轨迹和逻辑。
通过给全局打印机uvm_default_printer赋予不同的打印机句柄,就可以在调用任何uvm_obiect的print()方法时,得到不同的打印格式。
如果用户需要自定义一些打印属性,可以自己创建一个打印机,进而通过修改其属性uvm_printer::knobs中的成员,来定制打印格式。
7、打包和解包(pack&unpack)
pack是为了将自动化声明后的域 (标量) 打包为比特流 (bit stream)。即将各个散乱的数据,整理到bit数据串中,类似于struct packed的整理方式,但又能充分利用数据空间,也更容易与硬件之间进行数据传递和比对
unpack与pack相反,即将串行数据解包变为原有的各自域。该操作适用于从硬件一侧接收串行数据,进行校验之后,还原为软件一侧对象中各自对应的成员变量。
pack与unpack在通常的UVM环境中使用较少但是当与外界环境,例如SystemC发生大规模数据传递,该方法是首选,因为可以通过简单数据流实现精确的数据传输,另外,在UVM与FPGA、emulator之间进行数据交换时,该方法也由于简便得到了青睐。
二、phase机制
phase机制是component独有的
首先说一下在SV中存在的问题:
- SV的验证环境构建中,传统的硬件设计模型在仿真开始前,已经完成例化和连接了;而SV的软件部分,对象例化则需要在仿真开始后执行。
- 虽然对象例化通过调用建函数new()来实现,但是单单通过new()函数无法解决一个重要问题,那就是验证环境在实现层次化时,如何保证例化的先后关系,以及各个组件在例化后的连接
- 此外如果需要实现高级功能,例如在顶层到底层的配置时,SV也无法在底层组件例化之前完成对底层的配置逻辑
UVM的优势:
- 因此UVM在验证环境构建时,引入了phase机制,通过该机制我们可以很清晰地将UVM仿真阶段层次化。
- 这里的层次化,不单单是各个phase的先后执行顺序,而且处于同一phase中的层次化组件之间的phase也有先后关系。
1、执行机制
说明:
- 不一定要定义这9个phase,关心那个定义那个就好了
- 只有run(task)可以添加时序延迟
test1里面有t1,t1里面有C1和C2
build和final是自顶向下,剩下的都是自底向上!!!
2、9个主要phase
上面的九个phase对于一个测试环境的生命周期而言是有固定执行也会的先后执行顺序的;同时对于同一个phase中的组件,按照层次的顺序或者自顶向下、或者自底向上来执行
常用的phase包括build、connect、 run和report,它们分别完成了组件的建立、连接、运行和报告。这些phase在uvm_component中通过_phase的后缀完成了虚方法的定义,比如build_phase可以定文些组件例花和配置的任务。
3、12个分支phase
在用户发送激励的一种简单方式是,在run_phase中完成上面所有的激励;
另外一种方式是,如果用户可以将上面几种典型序列划分到不同区间,让对应的激励按区间顺序发送的话,可以让测试更有层次。因此run_phase又可以分为下面12个phase:
start_of_simulation_phase任务执行以后,run_phase和reset_phase开始执行,而在shutdown_phase执行完成之后,需要等待run _phase执行完才可以进入extract_phase。
切记:不要把它们混合起来使用,因为这样容易导致执行关系的不明确!!!0
4、UVM编译和运行顺序
- 首先在加载硬件模型调用仿真器之前,需要完成编译和建模阶段调。
- (run 0)接下来在开始仿真之前,会分别执行硬件的always/initial语句以及UVM的调用测试方法run_test和几个phase,分别是build、connect、 end_of _elaboration和start_of_simulation。
- 在开始仿真后,将会执行run_phase或者对应的12个细分phase。
- 在仿真结束后将会执行剩余的phase,分别是extract、 check、report和final。
5、UVM仿真开始
- 要在仿真开始时建立验证环境,用户可以考虑选择下面几种方式:
- 可以通过全局函数 (由uvm_pkg提供)run_test()来选择性地指定要运行哪一个uvm_test。这里的test类均继承于uvm_test。这样的话,指定的test类将被例化并指定为顶层的组件。一般而言,run_test()函数可以在合适module/program中的initial进程块中调用。
- 如果没有任何参数传递给run_test(),那么用户可以在仿真时通过传递参数+UVM_TESTNAME=<test_name>,来指定仿真时调用的uvm_test。
- 当然,即便run_test()函数在调用时已经有test名称传递,在仿真时+UVM_TESTNAME=<test name>也可以从顶层覆盖已指定的test。这种方式使得仿真不需要通过再次修改run_test()调用的test名称和重复编译,就可以灵活选定test。
- 无论上面哪一种方式,都必须在顶层调用全局函数run_test(),用户可以考虑不传递test名称作为参数,而在仿真时通过传递参数+UVM_TESTNAME=<test name>来选择test。
- 全局函数run_test()的重要性,正是从uvm_root创建了一个UVM世界
(1)UVM世界的“诞生”
UVM顶层类uvm_root。 该类也继承于uvm_component,它也是UVM环境结构中的一员,而它可以作为顶层结构类。它提供了一些像run_test()的这种方法,来充当了UVM世界中的核心角色。在uvm_pkg中,有且只有一个顶层类uvm_root所例化的对象,即uvm_top。
uvm_top承担的核心职责包括:
- 作为隐形的UVM世界顶层,任何其它的组件实例都在它之下,通过创建组件时指定parent来构成层次
- 如果parent设定为null,那么它将作为uvm_top的子组件.
- phase控制。控制所有组件的phase顺序。
- 索引功能。通过层次名称来索引组件实例。
- 报告配置。通过uvm_top来全局配置报告的繁简度(verbosity)
- 全局报告设备。由于可以全局访问到uvm_top实例,因此UVM报告设备在组件内部和组件外部 (例如module和sequence) 都可以访问。
通过uvm_top调用方法run_test(test_name),uvm_top做了如下的初始化:
- 得到正确的test_name。
- 初始化objection机制。(用来控制仿真退出的)
- 创建uvm_test_top实例。
- 调用phase控制方法,安排所有组件的phase方法执行顺序。
- 等待所有phase执行结束,关闭phase控制进程
- 报告总结和结束仿真。
6、UVM仿真结束
- UVM-1.1之后,结束仿真的机制有且只有一种,那就是利用objection挂起机制来控制仿真结束。
- uvm_objection类提供了一种供所有component和sequence共享的计数器。如果有组件来挂起objection,那么它还应该记得落下objection;
- 参与到objection机制中的参与组件,可以独立的各自挂起objection,来防止run phase退出,但是只有这些组件都落下objection后,uvm objection共享的counter才会变为0,这意味run phase退出的条件满足,因此可以退出run phase。
(1)objection防止仿真退出
下一讲我们继续学习后面的知识config机制、消息管理