| ||
拷贝在之前的例码中,读者初步认识到一旦声明了域的自动化,那么自动拷贝时可以省去不少麻烦。在这里,我们额外需要讲解的是,如果域的成员类型是对象,那么在自动拷贝时,是否会对该对象的内容也全部拷贝下来呢?通过上面的数据操作方法默认类型可以看到,当拷贝对象时,默认进行的是深拷贝,即会执行copy()和do_copy()。读者可以首先看看下面这个例子:module object_copy2;import uvm_pkg::*;`include "uvm_macros.svh"typedef enum {RED, WHITE, BLACK} color_t;class ball extends uvm_object;int diameter = 10;color_t color = RED;`uvm_object_utils_begin(ball)`uvm_field_int(diameter, UVM_DEFAULT)`uvm_field_enum(color_t, color, UVM_NOCOPY)`uvm_object_utils_endfunction new(string name="ball");super.new(name);endfunctionfunction void do_copy(uvm_object rhs);ball b;$cast(b, rhs);$display("ball::do_copy entered..");if(b.diameter <= 20) begindiameter = 20;endendfunctionendclassclass box extends uvm_object;int volume = 120;color_t color = WHITE;string name = "box";ball b;`uvm_object_utils_begin(box)`uvm_field_int(volume, UVM_ALL_ON)`uvm_field_enum(color_t, color, UVM_ALL_ON)`uvm_field_string(name, UVM_ALL_ON)`uvm_field_object(b, UVM_ALL_ON)`uvm_object_utils_endfunction new(string name="box");super.new(name);this.name = name;b = new();endfunctionendclassbox b1, b2;initial beginb1 = new("box1");b1.volume = 80;b1.color = BLACK;b1.b.color = WHITE;b2 = new();b2.copy(b1);b2.name = "box2";$display("%s", b1.sprint());$display("%s", b2.sprint());endendmodule输出结果:ball::do_copy entered..-----------------------------------Name Type Size Value-----------------------------------box1 box - @336volume integral 32 'h50color color_t 32 BLACKname string 4 box1b ball - @337diameter integral 32 'hacolor color_t 32 WHITE----------------------------------------------------------------------Name Type Size Value-----------------------------------box box - @338volume integral 32 'h50color color_t 32 BLACKname string 4 box2b ball - @340diameter integral 32 'h14color color_t 32 RED-----------------------------------这段例码新添加了一个类ball,并且在box中例化了一个对象。而在拷贝过程中,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的成员数值不同。在介绍完copy()方法之后,接下来的三个方法compare()、print()和pack(),与copy()有相似的地方,也有不同的地方。相似的地方在于,这些方法也有各自的回调函数,供用户在默认的自动数据操作无法满足需要时,额外实现并覆盖之前的操作。不同的地方在于,这些方法需要额外用来对数据操作做配置的对象。因此,接下来主要介绍的方法有:
- function bit compare (uvm_object rhs, uvm_comparer comparer=null)
- function void print (uvm_printer printer=null)
- function int pack (ref bit bitstream[], input uvm_packer packer=null)
比较对于比较方法,默认情况下,如果不对比较的情况作出额外地配置,用户可以在调用compare()方法时,省略第二项参数,即采用默认的比较配置。首先来看一个简单的例子:module object_compare1;import uvm_pkg::*;`include "uvm_macros.svh"typedef enum {RED, WHITE, BLACK} color_t;class box extends uvm_object;int volume = 120;color_t color = WHITE;string name = "box";`uvm_object_utils_begin(box)`uvm_field_int(volume, UVM_ALL_ON)`uvm_field_enum(color_t, color, UVM_ALL_ON)`uvm_field_string(name, UVM_ALL_ON)`uvm_object_utils_endfunction new(string name="box");super.new(name);this.name = name;endfunctionendclassbox b1, b2;initial beginb1 = new("box1");b1.volume = 80;b1.color = BLACK;b2 = new("box2");b2.volume = 90;if(!b2.compare(b1)) begin`uvm_info("COMPARE", "b2 comapred with b1 failure", UVM_LOW)endelse begin`uvm_info("COMPARE", "b2 comapred with b1 succes", UVM_LOW)endendendmodule输出结果:UVM_INFO @ 0: reporter [MISCMP] Miscompare for box2.volume: lhs = 'h5a : rhs = 'h50UVM_INFO @ 0: reporter [MISCMP] 1 Miscompare(s) for object box1@336 vs. box2@337UVM_INFO @ 0: reporter [COMPARE] b2 comapred with b1 failure在上面的两个对象的比较中,会将每一个自动化的域进行比较,所以在执行compare()函数时,内置的比较方法也会将比较错误输出。从上面的结果来看,比较发生了错误,返回了0值。那么,b1.color和b2.color虽然不相同,为什么没有比较错误的信息呢?原因在于,默认的比较器,即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对象。下面的这段例码,采取了第一种方法:module object_compare2;import uvm_pkg::*;`include "uvm_macros.svh"typedef enum {RED, WHITE, BLACK} color_t;class box extends uvm_object;int volume = 120;color_t color = WHITE;string name = "box";`uvm_object_utils_begin(box)`uvm_field_int(volume, UVM_ALL_ON)`uvm_field_enum(color_t, color, UVM_ALL_ON)`uvm_field_string(name, UVM_ALL_ON)`uvm_object_utils_endfunction new(string name="box");super.new(name);this.name = name;endfunctionendclassbox b1, b2;uvm_comparer cmpr;initial beginb1 = new("box1");b1.volume = 80;b1.color = BLACK;b2 = new("box2");b2.volume = 90;cmpr = new();cmpr.show_max = 10;if(!b2.compare(b1, cmpr)) begin`uvm_info("COMPARE", "b2 comapred with b1 failure", UVM_LOW)endelse begin`uvm_info("COMPARE", "b2 comapred with b1 succes", UVM_LOW)endendendmodule输出结果:UVM_INFO @ 0: reporter [MISCMP] Miscompare for box2.volume: lhs = 'h5a : rhs = 'h50UVM_INFO @ 0: reporter [MISCMP] Miscompare for box2.color: lhs = WHITE : rhs = BLACKUVM_INFO @ 0: reporter [MISCMP] Miscompare for box2.name: lhs = "box2" : rhs = "box1"UVM_INFO @ 0: reporter [MISCMP] 3 Miscompare(s) for object box1@336 vs. box2@337UVM_INFO @ 0: reporter [COMPARE] b2 comapred with b1 failure在这段例码中,额外创建了一个比较配置cmpr。这个对象是uvm_comparer类,而此类并不继承与任何其他的UVM类,只是单纯的一个用于存放比较配置信息的类。在设定了最大的比较错误次数之后,将b1与b2进行比较后的结果信息给得更加全面,这一次则将全部的比较错误信息都输出了。关于uvm_comparer的其它比较设置,用户可以阅读UVM类参考文档。打印打印方法是核心基类提供的另外一种便于开发和调试的功能。通过field automation,使得声明之后的各个成员域会在调用uvm_object::print()函数时自动打印出来。下面是一段例码:module object_print;import uvm_pkg::*;`include "uvm_macros.svh"typedef enum {RED, WHITE, BLACK} color_t;class box extends uvm_object;int volume = 120;color_t color = WHITE;string name = "box";`uvm_object_utils_begin(box)`uvm_field_int(volume, UVM_ALL_ON)`uvm_field_enum(color_t, color, UVM_ALL_ON)`uvm_field_string(name, UVM_ALL_ON)`uvm_object_utils_endfunction new(string name="box");super.new(name);this.name = name;endfunctionendclassbox b1;uvm_table_printer local_printer;initial beginb1 = new("box1");local_printer = new();$display("default table printer format");b1.print();$display("default line printer format");uvm_default_printer = uvm_default_line_printer;b1.print();$display("default tree printer format");uvm_default_printer = uvm_default_tree_printer;b1.print();$display("customized printer format");local_printer.knobs.full_name = 1;b1.print(local_printer);endendmodule打印结果:default table printer format-------------------------------Name Type Size Value-------------------------------box1 box - @336volume integral 32 'h78color color_t 32 WHITEname string 4 box1-------------------------------default line printer formatbox1: (box@336) { volume: 'h78 color: WHITE name: box1 }default tree printer formatbox1: (box@336) {volume: 'h78color: WHITEname: box1}customized printer format------------------------------------Name Type Size Value------------------------------------box1 box - @336box1.volume integral 32 'h78box1.color color_t 32 WHITEbox1.name string 4 box1------------------------------------从上面这段例码中,读者可以发现,只要被在field automation中声明过的域,在稍后的print()函数打印室,都将打印出它们的类型、大小和数值。如果用户不对打印的格式做出修改,那么在打印时,UVM会按照uvm_default_printer规定的格式来打印。在上面“比较”一节中,读者已经知道uvm_pkg中在仿真一开始的时候就会例化不少全局的对象,这其中就包括了uvm_default_printer和其它几个用于打印的对象,它们分别是:
- uvm_default_tree_printer:可以将对象按照数状结构打印。
- uvm_default_line_printer : 可以将对象打印到一行上面。
- uvm_default_table_printer : 可以将对象按照表格的方式打印。
- uvm_default_printer : UVM环境默认的打印设置,该句柄默认指向了uvm_default_table_printer。
所以通过给全局打印机uvm_default_printer赋予不同的打印机句柄,就可以在调用任何uvm_object的print()方法时,得到不同的打印格式。如果用户需要自定义一些打印的属性,用户可以自己创建一个打印机,进而通过修改其属性uvm_printer::knobs中的成员,来输出自己的打印格式。每一台打印机中,都有自己的打印属性,用户可以通过查看UVM类的参考手册,查找关于详细的打印属性类uvm_printer_knobs。除了简单的print()函数,用户还可以通过uvm_object::sprint()将对象的信息作为字符串返回,或者自定义do_print()函数来定制一些额外的打印输出。打包和解包最后,来看看另外一个核心功能,打包和解包。类似于之前的拷贝和打印,uvm_object也分别提供了通过field automation自动实现的打包和解包方法:function int pack (ref bit bitstream[], input uvm_packer packer=null);function int unpack (ref bit bitstream[], input uvm_packer packer=null);以及用户可以自定义的回调函数:virtual function void do_pack (uvm_packer packer);virtual function void do_unpack (uvm_packer packer);首先来看一段说明打包和解包的例码:module object_pack_unpack;import uvm_pkg::*;`include "uvm_macros.svh"typedef enum {RED, WHITE, BLACK} color_t;class box extends uvm_object;int volume = 120;int height = 20;color_t color = WHITE;`uvm_object_utils_begin(box)`uvm_field_int(volume, UVM_ALL_ON)`uvm_field_int(height, UVM_ALL_ON)`uvm_field_enum(color_t, color, UVM_ALL_ON)`uvm_object_utils_endfunction new(string name="box");super.new(name);endfunctionendclassbox b1, b2;bit packed_bits[];initial beginb1 = new("box1");b2 = new("box2");b1.volume = 100;b1.height = 40;b1.color = RED;b1.print();b1.pack(packed_bits);$display("packed bits stream size is %d \n", packed_bits.size());b2.unpack(packed_bits);b2.print();endendmodule输出结果:-------------------------------Name Type Size Value-------------------------------box1 box - @336volume integral 32 'h64height integral 32 'h28color color_t 32 RED-------------------------------packed bits stream size is 96-------------------------------Name Type Size Value-------------------------------box2 box - @337volume integral 32 'h64height integral 32 'h28color color_t 32 RED-------------------------------上面的例码中b1将自己内部已经声明过的域,首先进行打包,打包好的数据存入到一个比特数组中packed_bits,这个数组中存放着所有经过field aumation的域值,接下来b2又从packed_bits中解包,将数据存入到自己的各个域中。这么看起来,这个例子是完成一次对象数值的拷贝。如果是这样,那么为什么不适用uvm_object::copy()函数,而是费这么大周折,来将b1内的域值先打包,然后再通过b2解包完成数值的完整传递呢?实际上,打包和解包的方式并不是主要为软件对象之间的数值拷贝服务的,而是真正地在为从软件对象到硬件接口的赋值服务的。在硬件接口中,所有的接口都是按照一定的比特宽度来放置的,并不像软件对象内的各个域那样声明。因此,要完成一次从软件对象到硬件接口的赋值,一种方法就是利用uvm_object::pack()来实现。同样地,如果要完成对硬件信号的采样,也可以将排列好的硬件信号数值保存,继而通过uvm_object::unpack()来完成到软件对象的数值拷贝。pack()和unpack()函数的参数中,有一个是可以缺省的参数uvm_packer。如果用户不做特别指定,那么打包和解包的uvm_packer将会使用uvm_pkg中例化的全局对象uvm_default_packer。此外,用户如果需要自行打包,例如规定将b1.volume打包为什么长度的比特数组,来匹配硬件信号的位宽,则需要自己完成do_pack()的自定义函数。关于如何通过uvm_packer来完成自定义的打包方式和解包方式,用户可以阅读UVM类的参考手册,查看uvm_packer提供的公共方法。至此,我们已经将UVM和核心类uvm_object在注册时伴随的field automation以及因此带来的福利:拷贝、比较、打印和打包解包为读者们介绍完毕。有了这样完善的基础方法,接下来要进一步搭建房屋,为读者们介绍uvm_component时,我们将讨论UVM的phase机制,带领读者们思考,为什么需要phase机制,以及在实际使用过程中需要注意哪些地方。下一节,我们将为大家带来《phase机制》。谢谢你对路科验证的关注,也欢迎你分享和转发真正的技术价值,你的支持是我们保持前行的动力