本文转自:http://www.eetop.cn/blog/html/28/1561828-2331500.html
拷贝
在之前的例码中,读者初步认识到一旦声明了域的自动化,那么自动拷贝时可以省去不少麻烦。在这里,我们额外需要讲解的是,如果域的成员类型是对象,那么在自动拷贝时,是否会对该对象的内容也全部拷贝下来呢?通过上面的数据操作方法默认类型可以看到,当拷贝对象时,默认进行的是深拷贝,即会执行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_end
function new(string name="ball");
super.new(name);
endfunction
function void do_copy(uvm_object rhs);
ball b;
$cast(b, rhs);
$display("ball::do_copy entered..");
if(b.diameter <= 20) begin
diameter = 20;
end
endfunction
endclass
class 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_end
function new(string name="box");
super.new(name);
this.name = name;
b = new();
endfunction
endclass
box b1, b2;
initial begin
b1 = 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());
end
endmodule
输出结果:
ball::do_copy entered..
-----------------------------------
Name Type Size Value
----------------------------------
box1 box - @336
volume integral 32 'h50
color color_t 32 BLACK
name string 4 box1
b ball - @337
diameter integral 32 'ha
color color_t 32 WHITE
----------------------------------
-----------------------------------
Name Type Size Value
-----------------------------------
box box - @338
volume integral 32 'h50
color color_t 32 BLACK
name string 4 box2
b ball - @340
diameter integral 32 'h14
color 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()有相似的地方,也有不同的地方。相似的地方在于,这些方法也有各自的回调函数,供用户在默认的自动数据操作无法满足需要时,额外实现并覆盖之前的操作。不同的地方在于,这些方法需要额外用来对数据操作做配置的对象。因此,接下来主要介绍的方法有:
比较
对于比较方法,默认情况下,如果不对比较的情况作出额外地配置,用户可以在调用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_end
function new(string name="box");
super.new(name);
this.name = name;
endfunction
endclass
box b1, b2;
initial begin
b1 = 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)
end
else begin
`uvm_info("COMPARE", "b2 comapred with b1 succes", UVM_LOW)
end
end
endmodule
输出结果:
UVM_INFO @ 0: reporter [MISCMP] Miscompare for box2.volume: lhs = 'h5a : rhs = 'h50
UVM_INFO @ 0: reporter [MISCMP] 1 Miscompare(s) for object box1@336 vs. box2@337
UVM_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_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_end
function new(string name="box");
super.new(name);
this.name = name;
endfunction
endclass
box b1, b2;
uvm_comparer cmpr;
initial begin
b1 = 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)
end
else begin
`uvm_info("COMPARE", "b2 comapred with b1 succes", UVM_LOW)
end
end
endmodule
输出结果:
UVM_INFO @ 0: reporter [MISCMP] Miscompare for box2.volume: lhs = 'h5a : rhs = 'h50
UVM_INFO @ 0: reporter [MISCMP] Miscompare for box2.color: lhs = WHITE : rhs = BLACK
UVM_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@337
UVM_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_end
function new(string name="box");
super.new(name);
this.name = name;
endfunction
endclass
box b1;
uvm_table_printer local_printer;
initial begin
b1 = 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);
end
endmodule
打印结果:
default table printer format
-------------------------------
Name Type Size Value
-------------------------------
box1 box - @336
volume integral 32 'h78
color color_t 32 WHITE
name string 4 box1
-------------------------------
default line printer format
box1: (box@336) { volume: 'h78 color: WHITE name: box1 }
default tree printer format
box1: (box@336) {
volume: 'h78
color: WHITE
name: box1
}
customized printer format
------------------------------------
Name Type Size Value
------------------------------------
box1 box - @336
box1.volume integral 32 'h78
box1.color color_t 32 WHITE
box1.name string 4 box1
-----------------------------------
从上面这段例码中,读者可以发现,只要被在field automation中声明过的域,在稍后的print()函数打印室,都将打印出它们的类型、大小和数值。如果用户不对打印的格式做出修改,那么在打印时,UVM会按照uvm_default_printer规定的格式来打印。在上面“比较”一节中,读者已经知道uvm_pkg中在仿真一开始的时候就会例化不少全局的对象,这其中就包括了uvm_default_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_end
function new(string name="box");
super.new(name);
endfunction
endclass
box b1, b2;
bit packed_bits[];
initial begin
b1 = 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();
end
endmodule
输出结果:
-------------------------------
Name Type Size Value
-------------------------------
box1 box - @336
volume integral 32 'h64
height integral 32 'h28
color color_t 32 RED
-------------------------------
packed bits stream size is 96
-------------------------------
Name Type Size Value
-------------------------------
box2 box - @337
volume integral 32 'h64
height integral 32 'h28
color 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机制》.