— UVM 源码精读系列 - Day 3 —
uvm_object 核心架构:中心类与七大操作体系
UVM 源码精读 Day3:核心对象体系 uvm_object
`uvm_object` 是 UVM 中所有可见对象的根基,包括 `uvm_component`、`uvm_sequence_item`、`uvm_sequence` 乃至寄存器模型中的 `uvm_reg` 都直接或间接继承自它。理解 `uvm_object` 的设计,等于掌握了 UVM 整个对象体系的 DNA。
一、类层次与核心属性
virtual class uvm_object extends uvm_void;`uvm_object` 直接继承自 `uvm_void`(一个空基类,作为整个 UVM 类树的根)。这种设计使得 UVM 可以在最顶层统一处理所有对象的公共行为。
核心属性
| 属性 | 类型 | 作用 |
|---|---|---|
| `m_leaf_name` | `string` | 对象实例名,可通过 `get_name()`/`set_name()` 访问 |
| `m_inst_id` | `int` | 实例唯一 ID,通过静态计数器递增分配 |
| `m_inst_count` | `static int` | 全局实例计数器 |
| `use_uvm_seeding` | `static bit` | 是否使用 UVM 全局种子机制(默认 1) |
设计意图每个对象都自带名称和唯一 ID,这是调试、打印、比较的基础。`m_inst_id` 保证了在大量对象实例化时,日志中总能精确定位到具体对象。
二、工厂模式创建 create()
static function uvm_object create (string name="");`create()` 是 UVM 工厂模式的核心入口。与直接调用 `new()` 不同,`create()` 通过 `uvm_factory` 在运行时查找对应的 `uvm_object_wrapper`,由工厂负责实例化正确的类型。
function uvm_object uvm_object::create (string name="");
uvm_coreservice_t cs = uvm_coreservice_t::get();
uvm_factory factory = cs.get_factory();
return factory.create_object_by_type(get_type(), get_full_name(), name);
endfunction关键设计思想
- 运行时多态`create()` 返回的是 `uvm_object` 基类句柄,但实际类型由工厂根据注册信息决定。这使得在测试平台中可以通过配置覆盖(override)将 `my_driver` 替换为 `my_err_driver`,而无需修改源代码。
- 与 `new()` 的区别`new()` 是编译时绑定,而 `create()` 是运行时绑定。UVM 推荐所有可复用组件都通过 `create()` 创建。
三、数据复制体系 copy / clone
clone 完整复制
function uvm_object uvm_object::clone();
uvm_object tmp;
tmp = this.create(get_name());
if(tmp == null) begin
uvm_report_warning("CRFLD", ...);
end
else begin
tmp.copy(this);
end
return(tmp);
endfunction`clone()` 的精髓是"先创建,再复制"利用 `create()` 确保工厂覆盖生效,然后调用 `copy()` 完成数据深拷贝。
copy 与 do_copy 钩子
function void uvm_object::copy (uvm_object rhs, uvm_copier copier=null);
uvm_coreservice_t coreservice = uvm_coreservice_t::get();
uvm_copier m_copier;
if(copier == null) begin
m_copier = coreservice.get_default_copier();
end else begin
m_copier = copier;
end
if(m_copier.get_active_object_depth() == 0) begin
m_copier.flush();
end
m_copier.copy_object(this, rhs);
endfunction钩子模式`copy()` 是公共接口,负责初始化 `uvm_copier` 并委托给 `m_copier.copy_object()`。最终底层会回调 `do_copy()`,子类通过覆盖 `do_copy()` 实现自定义字段复制。
function void uvm_object::do_copy (uvm_object rhs);
return;
endfunction设计意图将"框架流程"与"业务逻辑"分离。框架负责递归遍历、深度控制、循环检测,用户只需在 `do_copy()` 中处理自己新增的字段。
四、数据比较体系 compare
function bit uvm_object::compare (uvm_object rhs, uvm_comparer comparer=null);
if(comparer == null) begin
comparer = uvm_comparer::get_default();
end
if(comparer.get_active_object_depth() == 0) begin
comparer.flush();
end
compare = comparer.compare_object(get_name(), this, rhs);
endfunction与 `copy` 完全对称的设计:初始化 `uvm_comparer` -> 委托给 `comparer.compare_object()` -> 底层回调 `do_compare()`。
function bit uvm_object::do_compare (uvm_object rhs, uvm_comparer comparer);
return 1;
endfunction默认返回 `1`(相等),子类覆盖后添加自定义比较逻辑。这种设计让比较操作可以递归进行——`uvm_comparer` 会自动处理嵌套对象的深度比较。
五、打印体系 print / sprint / convert2string
三种打印接口
| 方法 | 返回值 | 用途 |
|---|---|---|
| `print(printer)` | `void` | 直接输出到文件/屏幕 |
| `sprint(printer)` | `string` | 返回格式化字符串,用于拼接日志 |
| `convert2string()` | `string` | 自定义简短描述,子类覆盖 |
function void uvm_object::print(uvm_printer printer=null);
if(printer==null) begin
printer = uvm_printer::get_default();
end
$fwrite(printer.get_file(), sprint(printer));
endfunction
function string uvm_object::sprint(uvm_printer printer=null);
if(printer==null) begin
printer = uvm_printer::get_default();
end
if(printer.get_active_object_depth() == 0) begin
printer.flush();
name = printer.get_root_enabled() ? get_full_name() : get_name();
end else begin
name = get_name();
end
printer.print_object(name, this);
return printer.emit();
endfunction关键细节
- `get_active_object_depth() == 0` 判断当前是否为顶层对象,顶层才刷新 printer 缓冲区,避免中间对象干扰格式化。
- `get_root_enabled()` 控制是否打印完整层次名(`env.agent.driver`)还是仅打印局部名(`driver`)。
- 最终通过 `printer.print_object()` 递归打印所有字段,底层回调 `do_print()`。
六、序列化体系 pack / unpack
pack 族方法
function int uvm_object::pack (ref bit bitstream[], input uvm_packer packer=null);
m_pack(packer);
packer.get_packed_bits(bitstream);
return packer.get_packed_size();
endfunction| 方法 | 输出格式 |
|---|---|
| `pack(bitstream[])` | `bit` 数组 |
| `pack_bytes(bytestream[])` | `byte unsigned` 数组 |
| `pack_ints(intstream[])` | `int unsigned` 数组 |
三种接口共享同一个 `m_pack()` 核心,只是最终获取二进制数据的格式不同。
function void uvm_object::m_pack (inout uvm_packer packer);
if(packer == null) begin
packer = uvm_packer::get_default();
end
if(packer.get_active_object_depth() == 0) begin
packer.flush();
end
packer.pack_object(this);
endfunction设计意图序列化体系是 UVM 跨进程通信和事务记录的基础。`uvm_packer` 负责将对象字段按位/字节/int 打包成连续的二进制流,支持大小端转换和深度控制。
unpack 反向恢复
与 pack 完全对称,通过 `uvm_unpacker` 将二进制流还原为对象字段。同样是"框架处理递归,用户覆盖 `do_unpack()` 处理自定义字段"的模式。
七、记录体系 record
function void uvm_object::record (uvm_recorder recorder=null);
if(recorder == null) begin
recorder = uvm_recorder::get_default();
end
if(recorder.get_active_object_depth() == 0) begin
recorder.flush();
end
recorder.record_object(get_name(), this);
endfunction`record()` 用于将对象状态持久化到波形数据库(如 FSDB、TRN 等)。与 print/pack 相同的设计范式,底层回调 `do_record()`。
八、field automation 标志位
`uvm_object_globals.svh` 中定义了 `uvm_field_flag_t` 类型和一系列标志位,用于在 `uvm_field_*` 宏中声明字段需要支持哪些操作。
typedef bit [31:0] uvm_field_flag_t;
parameter uvm_field_flag_t UVM_COPY = 32'b00000000000000000000000000000001;
parameter uvm_field_flag_t UVM_COMPARE = 32'b00000000000000000000000000000010;
parameter uvm_field_flag_t UVM_PRINT = 32'b00000000000000000000000000000100;
parameter uvm_field_flag_t UVM_RECORD = 32'b00000000000000000000000000001000;
parameter uvm_field_flag_t UVM_PACK = 32'b00000000000000000000000000010000;
parameter uvm_field_flag_t UVM_UNPACK = 32'b00000000000000000000000000100000;使用方式
`uvm_object_utils_begin(my_item)
`uvm_field_int(data, UVM_ALL_ON | UVM_DEC)
`uvm_field_string(name, UVM_DEFAULT)
`uvm_object_utils_end`UVM_ALL_ON` 等价于 `UVM_COPY | UVM_COMPARE | UVM_PRINT | UVM_RECORD | UVM_PACK | UVM_UNPACK`,表示该字段参与所有自动化操作。也可以通过按位或组合精确控制,例如 `UVM_COPY | UVM_COMPARE` 表示只参与复制和比较,不参与打印和序列化。
设计意图用位掩码实现编译时/运行时的行为控制,既灵活又高效。这种设计使得一个字段可以在不同场景下有不同的处理方式——例如密码字段可以设置 `UVM_NOCOMPARE` 避免在比较时暴露。
九、随机化事件回调
function void uvm_object::pre_randomize();
m_field_automation(null, UVM_PRE_RANDOMIZE, "");
endfunction
function void uvm_object::post_randomize();
m_field_automation(null, UVM_POST_RANDOMIZE, "");
endfunction`pre_randomize()` 和 `post_randomize()` 是 SystemVerilog 内建随机化机制的标准回调。UVM 在这里增加了 `m_field_automation()` 调用,使得 `uvm_field_*` 宏注册的字段可以在随机化前后自动执行额外的约束处理。
十、今日总结
| 体系 | 公共接口 | 钩子方法 | 辅助类 | 核心设计模式 |
|---|---|---|---|---|
| 创建 | `create()` | — | `uvm_factory` | 工厂模式 |
| 复制 | `copy()` / `clone()` | `do_copy()` | `uvm_copier` | 模板方法模式 |
| 比较 | `compare()` | `do_compare()` | `uvm_comparer` | 模板方法模式 |
| 打印 | `print()` / `sprint()` | `do_print()` | `uvm_printer` | 模板方法模式 |
| 序列化 | `pack()` / `unpack()` | `do_pack()` / `do_unpack()` | `uvm_packer` / `uvm_unpacker` | 模板方法模式 |
| 记录 | `record()` | `do_record()` | `uvm_recorder` | 模板方法模式 |
| 自动化 | `m_field_automation()` | — | — | 位掩码标志控制 |
`uvm_object` 的设计堪称 SystemVerilog 中模板方法模式(Template Method Pattern)的教科书级实现:公共接口定义算法骨架,钩子方法(`do_*`)留待子类扩展。这种设计带来的好处是
1. 一致性所有数据操作方法(copy/compare/print/pack/record)遵循相同的"初始化工具类 -> 委托给工具类 -> 回调钩子"流程。 2. 可扩展性子类只需覆盖对应的 `do_*` 方法,无需关心递归深度、循环引用检测等复杂逻辑。 3. 可组合性通过 `uvm_field_*` 宏和标志位,可以在不修改类定义的情况下精细控制字段行为。
夜雨聆风