一、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_object类一步步继承而来的。
uvm_object的核心方法主要提供与数据操作的相关服务,包括copy、clone、compare、print、pack/unpack。
二、域的自动化
UVM通过域的自动化,使得用户在注册UVM类的同时也可以声明今后会参与到对象拷贝、克隆、打印等操作的成员变量。
域的自动化使得在使用uvm_object提供的一些预定义方法时,非常便捷,无需再实现自定义方法。
熟悉域的自动化常用的宏之后,还需要考虑哪些成员变量在注册UVM类(`uvm_{component,object}_utils)的时候,也一并将它们归置到对应的域列表中,以便为稍后的域方法提供可以自动实现的基础。
class box extends uvm_object;
int volume = 120; // 声明一个整型成员变量volume并赋初值为120
color_t color = WHITE; // 声明一个color_t类型的成员变量color并赋初值为WHITE
string name = "box"; // 声明一个字符串类型的成员变量name并赋初值为"box"
`uvm_object_utils_begin(box)
//域的自动化的声明
`uvm_field_int(volume, UVM_ALL_ON) // 使用宏定义自动声明一个整型成员变量volume的域
`uvm_field_enum(color_t, color, UVM_ALL_ON) // 使用宏定义自动声明一个color_t类型的成员变量color的域
`uvm_field_string(name, UVM_ALL_ON) // 使用宏定义自动声明一个字符串类型的成员变量name的域
`uvm_object_utils_end
...
endclass
box b1, b2;
initial begin
b1 = new("box1"); // 创建一个名为b1的box对象,并将名字设为"box1"
b1.volume = 80; // 设置b1的volume成员变量的值为80
b1.color = BLACK; // 设置b1的color成员变量的值为BLACK
b2 = new(); // 创建一个名为b2的box对象
b2.copy(b1); // 将b1的成员变量拷贝给b2,包括volume和color(前提是做了域的自动化声明)
b1.name = "box2"; // 设置b1的name成员变量的值为"box2"
end
三、拷贝copy
在UVM的数据操作中,copy默认已经创建好了对象,只需要对数据进行拷贝,而clone则会自动创建对象并对source object进行数据拷贝,再返回target object句柄。无论是copy还是clone都需要对数据进行赋值。在进行copy时,默认进行的是深拷贝,即会执行copy()和do_copy()。
class ball extends uvm_object;
int diameter = 10; // 球的直径,默认值为 10
color_t color = RED; // 球的颜色,默认值为 RED
// 使用 `uvm_object_utils_begin` 和 `uvm_object_utils_end` 宏定义成员变量的拷贝和打印方法
`uvm_object_utils_begin(ball)
// 定义 `diameter` 成员变量的拷贝方式,使用默认的拷贝方式 (UVM_DEFAULT)
`uvm_field_int(diameter, UVM_DEFAULT)
// 定义 `color` 成员变量的拷贝方式,使用 UVM_NOCOPY,表示不拷贝
`uvm_field_enum(color_t, color, UVM_NOCOPY)
`uvm_object_utils_end
// 重写 `do_copy` 方法,实现自定义的拷贝逻辑
function void do_copy(uvm_object rhs);
ball b; // 创建一个 `ball` 类型的对象
$cast(b, rhs); // 将 `rhs` 对象转换为 `ball` 类型,并赋值给 `b`
$display("ball::do_copy entered..."); // 打印提示信息
// 如果 `b` 的直径小于等于 20,则将 `diameter` 设置为 20
if(b.diameter <= 20) begin
diameter = 20;
end
endfunction
endclass
class box extends uvm_object;
int volume = 120; // 盒子的体积,默认值为 120
color_t color = WHITE; // 盒子的颜色,默认值为 WHITE
string name = "box"; // 盒子的名字,默认值为 "box"
ball b; // 盒子中的球
// 使用 `uvm_object_utils_begin` 和 `uvm_object_utils_end` 宏定义成员变量的拷贝和打印方法
`uvm_object_utils_begin(box)
// 定义 `volume` 成员变量的拷贝方式,使用 UVM_ALL_ON,表示全拷贝
`uvm_field_int(volume, UVM_ALL_ON)
// 定义 `color` 成员变量的拷贝方式,使用 UVM_ALL_ON,表示全拷贝
`uvm_field_enum(color_t, color, UVM_ALL_ON)
// 定义 `name` 成员变量的拷贝方式,使用 UVM_ALL_ON,表示全拷贝
`uvm_field_string(name, UVM_ALL_ON)
// 定义 `b` 成员变量的拷贝方式,使用 UVM_ALL_ON,表示深拷贝
`uvm_field_object(b, UVM_ALL_ON)
`uvm_object_utils_end
// ...
endclass
box b1, b2; // 创建两个 `box` 类型的对象
initial begin
b1 = new("box1"); // 创建 `box1` 对象
b1.volume = 80; // 设置 `box1` 的体积为 80
b1.color = BLACK; // 设置 `box1` 的颜色为 BLACK
b2 = new(); // 创建 `b2` 对象
b2.copy(b1); // 将 `b1` 对象的内容拷贝到 `b2` 对象中
b1.name = "box2"; // 设置 `b1` 的名字为 "box2"
$display("%s", b1.sprint()); // 打印 `b1` 对象的字符串表示
$display("%s", b2.sprint()); // 打印 `b2` 对象的字符串表示
end
代码解释:
ball 类:
定义了一个名为 ball 的类,继承自 uvm_object。
包含两个成员变量:diameter 和 color,分别表示球的直径和颜色。
使用 uvm_object_utils_begin 和 uvm_object_utils_end 宏定义了成员变量的拷贝和打印方法。
重写了 do_copy 方法,实现自定义的拷贝逻辑,当被拷贝的球的直径小于等于 20 时,将自身的直径设置为 20。
box 类:
定义了一个名为 box 的类,继承自 uvm_object。
包含四个成员变量:volume、color、name 和 b,分别表示盒子的体积、颜色、名字和盒子里的球。
使用 uvm_object_utils_begin 和 uvm_object_utils_end 宏定义了成员变量的拷贝和打印方法。
使用 uvm_field_object 宏定义了 b 成员变量的拷贝方式为 UVM_ALL_ON,表示深拷贝,即当 box 对象被拷贝时,b 成员变量也会被拷贝,并且会创建一个新的 ball 对象。
initial 块:
创建了两个 box 类型的对象 b1 和 b2。
初始化 b1 对象,设置其体积、颜色和名字。
使用 b2.copy(b1) 将 b1 对象的内容拷贝到 b2 对象中,由于 b 成员变量被定义为深拷贝,因此 b2 对象中也会创建一个新的 ball 对象,并且 b2 中的球和 b1 中的球是两个不同的对象。
修改 b1 对象的名字为 “box2”。
使用 sprint() 方法打印 b1 和 b2 对象的字符串表示,可以观察到 b1 和 b2 的成员变量值和对象内容。
代码总结:
这段代码定义了两个类:ball 和 box,并展示了如何使用 uvm_object_utils_begin 和 uvm_object_utils_end 宏定义成员变量的拷贝和打印方法,以及如何使用 uvm_field_object 宏定义深拷贝。同时,代码还演示了如何使用 copy() 方法进行对象拷贝,以及如何使用 sprint() 方法打印对象的字符串表示。
四、比较compare
function bit compare(uvm_object rhs, uvm_comparer comparer=null);
默认情况下,如果不对比较的情况作出额外配置,可以在调用compare()方法时,省略第二项参数,即采用默认的比较配置。比较方法经常会在两个数据类中进行,例如从generator产生的一个transaction(数据类),和在设计输出上捕捉的transaction(数据类),如果它们为同一种类型,除了可以自定义数据比较之外,也可以直接使用uvm_object::compare()函数来实现数据比较和消息打印。
class box extends uvm_object;
int volume = 120; // 盒子的体积,默认值为 120
color_t color = WHITE; // 盒子的颜色,默认值为 WHITE
string name = "box"; // 盒子的名字,默认值为 "box"
ball b; // 盒子中的球
// 使用 `uvm_object_utils_begin` 和 `uvm_object_utils_end` 宏定义成员变量的拷贝和打印方法
`uvm_object_utils_begin(box)
// 定义 `volume` 成员变量的拷贝方式,使用 UVM_ALL_ON,表示全拷贝
`uvm_field_int(volume, UVM_ALL_ON)
// 定义 `color` 成员变量的拷贝方式,使用 UVM_ALL_ON,表示全拷贝
`uvm_field_enum(color_t, color, UVM_ALL_ON)
// 定义 `name` 成员变量的拷贝方式,使用 UVM_ALL_ON,表示全拷贝
`uvm_field_string(name, UVM_ALL_ON)
`uvm_object_utils_end
// ...
endclass
box b1, b2; // 创建两个 `box` 类型的对象
initial begin
b1 = new("box1"); // 创建 `box1` 对象
b1.volume = 80; // 设置 `box1` 的体积为 80
b1.color = BLACK; // 设置 `box1` 的颜色为 BLACK
b2 = new("box2"); // 创建 `b2` 对象
b2.volume = 90; // 设置 `b2` 的体积为 90
// 使用 `compare` 方法比较 `b2` 和 `b1` 对象
if(!b2.compare(b1)) begin
// 如果比较失败,则打印信息
`uvm_info("COMPARE", "b2 compared with b1 failure", UVM_LOW)
end
else begin
// 如果比较成功,则打印信息
`uvm_info("COMPARE", "b2 compared with b1 success", UVM_LOW)
end
end
代码解释:
box 类:
定义了一个名为 box 的类,继承自 uvm_object。
包含三个成员变量:volume、color 和 name,分别表示盒子的体积、颜色和名字。
使用 uvm_object_utils_begin 和 uvm_object_utils_end 宏定义了成员变量的拷贝和打印方法。
由于 box 类没有重写 compare 方法,因此会使用 uvm_object 类提供的默认 compare 方法,默认 compare 方法会比较所有成员变量的值,如果所有成员变量的值都相等,则返回 true,否则返回 false。
initial 块:
创建了两个 box 类型的对象 b1 和 b2。
初始化 b1 对象,设置其体积和颜色。
初始化 b2 对象,设置其体积。
使用 b2.compare(b1) 比较 b2 和 b1 对象,由于 b1 的体积为 80,b2 的体积为 90,因此 compare 方法会返回 false,表示比较失败。
根据比较结果打印不同的信息。
代码总结:
这段代码定义了 box 类,并演示了如何使用 compare 方法比较两个 box 对象。代码中使用 uvm_object_utils_begin 和 uvm_object_utils_end 宏定义了成员变量的拷贝和打印方法,并使用 uvm_info 宏打印信息。
在上面的两个对象比较中,会将每一个自动化的域进行比较,所以在执行compare()函数时,内置的比较方法也会将比较错误输出。默认的比较器,即uvm_package::UVM_default_comparer最大输出的错误比较信息是1,也就是说当比较错误发生时,不会再进行后续的比较。实际上,在uvm_object使用到的方法compare()、print()和pack(),如果没有指定数据操作配置对象作为参数时,会使用在uvm_pkg中例化的全局数据操作配置成员。
五、打印print
打印方法是核心基类提供的一种便于开发和调试的功能,通过field automation,使得声明之后的各个成员域会在调用uvm_object::print()函数时自动打印出来。相比于在仿真中设置断点,逐步调试,打印是另一种调试方式,好处在于可以让仿真继续进行,会在最终回顾执行过程中,从全局理解执行的轨迹和逻辑。
class box extends uvm_object;
int volume = 120; // 盒子的体积,默认值为 120
color_t color = WHITE; // 盒子的颜色,默认值为 WHITE
string name = "box"; // 盒子的名字,默认值为 "box"
ball b; // 盒子中的球
// 使用 `uvm_object_utils_begin` 和 `uvm_object_utils_end` 宏定义成员变量的拷贝和打印方法
`uvm_object_utils_begin(box)
// 定义 `volume` 成员变量的拷贝方式,使用 UVM_ALL_ON,表示全拷贝
`uvm_field_int(volume, UVM_ALL_ON)
// 定义 `color` 成员变量的拷贝方式,使用 UVM_ALL_ON,表示全拷贝
`uvm_field_enum(color_t, color, UVM_ALL_ON)
// 定义 `name` 成员变量的拷贝方式,使用 UVM_ALL_ON,表示全拷贝
`uvm_field_string(name, UVM_ALL_ON)
`uvm_object_utils_end
// ...
endclass
box b1; // 创建一个 `box` 类型的对象
uvm_table_printer local_printer; // 创建一个 `uvm_table_printer` 类型的对象
initial begin
b1 = new("box1"); // 创建 `box1` 对象
local_printer = new(); // 创建 `local_printer` 对象
$display("default table printer format"); // 打印提示信息
b1.print(); // 使用默认的 `uvm_table_printer` 打印 `b1` 对象
$display("default line printer format"); // 打印提示信息
uvm_default_printer = uvm_default_line_printer; // 将默认打印器设置为 `uvm_default_line_printer`
b1.print(); // 使用 `uvm_default_line_printer` 打印 `b1` 对象
$display("default tree printer format"); // 打印提示信息
uvm_default_printer = uvm_default_tree_printer; // 将默认打印器设置为 `uvm_default_tree_printer`
b1.print(); // 使用 `uvm_default_tree_printer` 打印 `b1` 对象
$display("customized printer format"); // 打印提示信息
local_printer.knobs.full_name = 1; // 设置 `local_printer` 的 `full_name` 属性为 1,表示打印对象的完整路径
b1.print(local_printer); // 使用 `local_printer` 打印 `b1` 对象
end
代码解释:
box 类:
定义了一个名为 box 的类,继承自 uvm_object。
包含三个成员变量:volume、color 和 name,分别表示盒子的体积、颜色和名字。
使用 uvm_object_utils_begin 和 uvm_object_utils_end 宏定义了成员变量的拷贝和打印方法。
initial 块:
创建了一个 box 类型的对象 b1 和一个 uvm_table_printer 类型的对象 local_printer。
初始化 b1 对象。
使用默认的 uvm_table_printer 打印 b1 对象,默认的 uvm_table_printer 使用表格形式打印对象信息。
将默认打印器设置为 uvm_default_line_printer,使用 uvm_default_line_printer 打印 b1 对象,uvm_default_line_printer 使用一行文本形式打印对象信息。
将默认打印器设置为 uvm_default_tree_printer,使用 uvm_default_tree_printer 打印 b1 对象,uvm_default_tree_printer 使用树形结构打印对象信息。
设置 local_printer 的 full_name 属性为 1,表示打印对象的完整路径,并使用 local_printer 打印 b1 对象。
代码总结:
这段代码展示了 UVM 中使用不同打印器打印 uvm_object 对象的方法。代码中使用了 uvm_table_printer、uvm_default_line_printer 和 uvm_default_tree_printer 三种打印器,并演示了如何设置打印器的属性以改变打印方式。
六、打包和解包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数据串中,类似于struct packed的整理方式,但又能充分利用数据空间,也更容易与硬件之间进行数据传递和比对。
unpack则是将串行数据解包变为原有的各自域,该操作适用于从硬件一侧接受串行数据,进行校验之后,还原为软件一侧对象中各自对应的成员变量。
box b1, b2; // 创建两个 `box` 类型的对象
bit packed_bits[]; // 创建一个 `bit` 类型的数组,用于存储打包后的数据
initial begin
b1 = new("box1"); // 创建 `box1` 对象
b2 = new("box2"); // 创建 `box2` 对象
b1.volume = 100; // 设置 `box1` 的体积为 100
b1.height = 40; // 设置 `box1` 的高度为 40
b1.color = RED; // 设置 `box1` 的颜色为 RED
b1.print(); // 打印 `box1` 对象
b1.pack(packed_bits); // 将 `box1` 对象打包成 `packed_bits` 数组
$display("packed bits stream size is %d\n", packed_bits.size()); // 打印 `packed_bits` 数组的大小
b2.unpack(packed_bits); // 将 `packed_bits` 数组解包到 `b2` 对象
b2.print(); // 打印 `b2` 对象
end
代码解释:
box 类:
定义了一个名为 box 的类,继承自 uvm_object。
包含三个成员变量:volume、height 和 color,分别表示盒子的体积、高度和颜色。
使用 uvm_object_utils_begin 和 uvm_object_utils_end 宏定义了成员变量的拷贝和打印方法。
该类还包含 pack() 和 unpack() 方法,用于将对象打包成位流和将位流解包到对象。
initial 块:
创建了两个 box 类型的对象 b1 和 b2。
创建了一个 bit 类型的数组 packed_bits,用于存储打包后的数据。
初始化 b1 对象,设置其体积、高度和颜色。
打印 b1 对象。
使用 b1.pack(packed_bits) 将 b1 对象打包成 packed_bits 数组,pack() 方法将 b1 对象的成员变量值转换成位流,并存储到 packed_bits 数组中。
打印 packed_bits 数组的大小。
使用 b2.unpack(packed_bits) 将 packed_bits 数组解包到 b2 对象,unpack() 方法将 packed_bits 数组中的位流还原成 b2 对象的成员变量值。
打印 b2 对象。
代码总结:
这段代码展示了 UVM 中使用 pack() 和 unpack() 方法将 uvm_object 对象打包成位流和将位流解包到对象的方法。代码中使用了一个 bit 类型的数组 packed_bits 存储打包后的位流,并演示了如何将对象打包成位流,以及如何将位流解包到对象。