IC验证学霸笔记4——UVM-- 核心基类
1 核心基类
UVM_object
UVM世界中的类最初都是由一个uvm_void根类继承过来的,但实际上该类没有实际的成员变量和方法。uvm_void只是一个虚类(virtual class),里边的内容等待继承于它的子类去开垦,在继承与uvm_void的子类中,有两类,一类为uvm_object,另外一类为uvm_port_base。在类库地图中,除了事务接口类继承于uvm_port_base,其他的所有类都是从uvm_object类中一步步继承过来的。从uvm_object提供的方法和相关的宏操作来看,它的核心方法主要提供与数据操作相关的主要事务:
copy、 clone、 compare、 print、pack/unpack
域的自动化
UVM通过域的自动化,使得用户在注册UVM类的同时也可以声明今后会参与到对象拷贝、克隆、打印等操作的成员变量。 域的自动化解放了verifier的双手,这使得在使用uvm_object提供的一些预定义方法时,非常便捷,而无需再实现自定义方法。在了解了域的自动化常用的宏之后,用户需要考虑哪些成员变量 、在注册UVM类( uvm_{component, object}_utils)的时候,也一并将它们归置到对应的域列表中,以便为稍后的域方法提供可以自动实现的基础。
从这个域的自动化宏的例子来看,在注册box的同时,也声明了将来会参与到 uvm_object数据操作的成员变量;
凡是声明了的成员变量,都将在数据操作时自动参与进来。
如果有一些数据没有通过域的自动化来声明的话,它们也将不会自动参与到数据的拷贝、打印等操作,除非用户自己去定义这些数据操作方法。
按照域的类型,将在域自动化自动化是声明的宏进行分类,如下:
这些用于域的自动化的宏声明应在 uvm_object 或 uvm_ component 注册时发生, 即在`uvm_object_utils_begin 和uvm_object_ utils_end 之间,或者在'uvm_ component_ utils_ begin 和 uvm_ component_ utils _ end 之间声明要自动化的域。在声明自动化的域时,除了要注意运用正确的宏来匹配域的成员类型 (ARG), 还应声明这些域在将来参与的数据操作 (FLAG), 这一声明由下表枚举类型来表示。
建议初学者只需要默认采取UVM_ALL_ON或者 UVM_DEFAULT, 即将所有的数据操仵方法都打开。
拷贝(copy)
• 在UVM的数据操作中, 需要对copy和clone加以区分。 前者默认已经创建好了对象,只需要对数据进行拷贝;后者则会自动创建对象并对source object进行数据拷贝, 再返回target object句柄。• 无论是copy或者clone, 都需要对数据进行复制。
• 在进行copy时, 默认进行的是深拷贝(deep copy) , 即会执行copy()和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的成员数值不同。
• 默认情况下, 如果不对比较的情况作出额外配置, 用户可以在调用compare()方法时, 省略第二项参数, 即采用默认的比较配置。
• 比较方法经常会在两个数据类中进行。 例如从generator产生的 一个transaction (数据类), 和在设计输出上捕捉的transaction(数据类), 如果它们为同一种类型, 除了可以自定义数据比较之外, 也可以直接使用uvm_object::compare()函数来实现数据比较和消息打印。
• 接下来,再执行do_copy()函数,这个函数是需要用户定义的回调函数(callback function) , 即在copy()执行完后会执行do_copy()。
• 如果用户没有定义该函数, 那么则不会执行额外的数据操作。
• 从ball::do_copy()函数可以看到, 如果被拷贝对象的diameter小于20, 那么则将自身的diameter设置为20。 囚此, 最后对象b2.b的 成员与b1.b的成员数值不同。
比较(compare)
function bit compare (uvm_object rhs, uvm_comparer comparer =null);• 默认情况下, 如果不对比较的情况作出额外配置, 用户可以在调用compare()方法时, 省略第二项参数, 即采用默认的比较配置。
• 比较方法经常会在两个数据类中进行。 例如从generator产生的 一个transaction (数据类), 和在设计输出上捕捉的transaction(数据类), 如果它们为同一种类型, 除了可以自定义数据比较之外, 也可以直接使用uvm_object::compare()函数来实现数据比较和消息打印。
在上面的两个对象比较中,会将每一个自动化的域进行比较,所以在执行compare()函数时,内置的比较方***将比较错误输出。• 从结果来看,比较发生了错误,返回0值。那么,b1.color和b2.color虽然不相同,却没有比较错误的信息。
• 原因在于, 默认的比较器, 即uvm_package::uvm_default_comparer最大输出的错误比较信息是1, 也就是说当比较错误发生时, 不会再进行后续的比较。
• 实际上, 在uvm_object使用到的方法compare()、print()和pack(), 如果没有指定数据操作配置对象作为参数时,会使用在uvm_pkg 中例化的全局数据操作配置成员。
如果用户不想使用默认的比较配置,而是想自己对比较 进行设定,可以考虑创建一个uvm_comparer对象,或者修改全局的uvm_comparer对象。
下表为uvm_pkg全局对象:
• 原因在于, 默认的比较器, 即uvm_package::uvm_default_comparer最大输出的错误比较信息是1, 也就是说当比较错误发生时, 不会再进行后续的比较。
• 实际上, 在uvm_object使用到的方法compare()、print()和pack(), 如果没有指定数据操作配置对象作为参数时,会使用在uvm_pkg 中例化的全局数据操作配置成员。
全局对象
在uvm_pkg中例化了不少全局对象,在本节中我们会使用到的全局配置对象包括有uvm_default_comparer, uvm_default_printer和uvm_default_packer。如果用户不想使用默认的比较配置,而是想自己对比较 进行设定,可以考虑创建一个uvm_comparer对象,或者修改全局的uvm_comparer对象。
下表为uvm_pkg全局对象:
打印(print)
• 打印方认是核心基类提供的另外才中便于“开发和调试的功能“;• 通过field automation, 使得声明之后的各个成员域会在调用 uvm_object::print()函数时自动打印出来。
• 相比于在仿真中设置断点、逐从调试,打印是另外一种调试方法,它的好处在于可以让仿真继续进行,会在最终回顾执行过程中, 从全局理解执行的轨迹和逻辑。
• uvm_pkg所包含的用于打印的全局对象, 它们分别是:
uvm_default_tree_printer: 可以将对象按照树状结构打印”;
uvm_default_line_printer : 可以将对象数据打印到一行上;
uvm_default_table_printer : 可以将对象按照表格的方式打印;
uvm_default_printer : UVM坏境默认的打印设置,该句柄默认指向了 uvm_default_table_printer
• 通过给全局打印机uvm_default_printe赋予不同的打印机句柄, 就可以在调用行何uvm_object的print()方法时, 得到不同的打印格式。
• 如果用户需要自定义一些打印属性, 可以自己创建一个打印机, 进而通过修改其属件uvm_printer:: knobs中的成员, 来定制打印格式。
打包和解包(pack/unpack)
function int pack (ref bit bitstream[ ], input uvm_packer packer=null); function int unpack (ref bit bitstream[ ], input uvm_packer packer=null);
• pack足为了将自动化声明后的域(标量)打包为比特流(bit stream)。即将各个散乱的数据,整理到bit数据串中,类似于 struct packed的整理方式,但又能充分利用数据空间,也更容易与硬件之间进行数据传递和比对。
• unpack与pack相反,即将串行数据解包变为原有的各自域。该操作适用于从硬件一侧接收串行数据,进行校验后,还原为软件一侧对象中各自对应的成员变量。
• pack与unpack在通常的UVM环境中使用较少,但是当与外界环境,例如System(发生大规模数据传递,该方法是首选,因为可以通过简单数据流实现精确的数据传输,另外,在UVM与FPGA emulator之间进行数据交换时,该方法也由于简便得到了青眯。

• unpack与pack相反,即将串行数据解包变为原有的各自域。该操作适用于从硬件一侧接收串行数据,进行校验后,还原为软件一侧对象中各自对应的成员变量。
• pack与unpack在通常的UVM环境中使用较少,但是当与外界环境,例如System(发生大规模数据传递,该方法是首选,因为可以通过简单数据流实现精确的数据传输,另外,在UVM与FPGA emulator之间进行数据交换时,该方法也由于简便得到了青眯。
输出结果:
• b1将声明过的域通过pack()进行打包,打包好的数据存入到一个比特数组packed_bits,这个数组存放着所有经过field automation 的域值。
• 接下来b2又从packed_bits中解包, 将数据存入到自己的各个域中。
• 这种操作即完成了大型标量数据的整形串发, 主要面向SV与硬件和其它语言接口直接的精确通信。
• 在pack()和unpack()的参数中,有一个是可以缺省的参数即 uvm_packer, 如果用户不做特别指定,那么打包和解包的uvm_packer将会使用uvm_pkg中例化的全局对象uvm_default_packer。
• 此外, 用户如果需要自行打包, 例如规定将b1.volume打包成多少长度的比特数组, 来匹配硬件信号的位宽, 则需要自己定义do_pack( )回调函数。
以下案例涵盖本小结的重点内容:
• 接下来b2又从packed_bits中解包, 将数据存入到自己的各个域中。
• 这种操作即完成了大型标量数据的整形串发, 主要面向SV与硬件和其它语言接口直接的精确通信。
• 在pack()和unpack()的参数中,有一个是可以缺省的参数即 uvm_packer, 如果用户不做特别指定,那么打包和解包的uvm_packer将会使用uvm_pkg中例化的全局对象uvm_default_packer。
• 此外, 用户如果需要自行打包, 例如规定将b1.volume打包成多少长度的比特数组, 来匹配硬件信号的位宽, 则需要自己定义do_pack( )回调函数。
以下案例涵盖本小结的重点内容:
注:优秀验证学员随堂笔记,已经征求到学生的同意,会持续给牛友们分享!
大家看完记得 一键三连!多多支持