UVM clone 与 create 区别:对象复制和工厂创建的语义边界
更新时间:2026-09-28。上一篇 SystemVerilog 对象拷贝:深拷贝与浅拷贝 讲了裸 SV 层面
new赋值和手动 copy 的深浅语义;这一篇进入 UVM 语境:库里自带的clone()/copy()与工厂create()到底差在哪、什么时候 clone 是捷径、什么时候必须回到 create。工厂机制本身见 UVM factory 与 env 派生,本篇不重复注册与 override 的完整流程。
本文要回答的问题
clone()和copy()内部做了什么?为什么 transaction 里常见$cast(rsp, req.clone())这种写法?- factory
create()比new()多出来的东西是什么?为什么 override 对 clone 完全无效? - 哪些场景下必须用 create,clone 会悄悄埋雷?
一、两条造对象的路径:从无到有 vs 从有到有
先把语义钉死:create 是构造,clone 是复制。构造不依赖任何既有对象——你给一个类型名,工厂查注册表、可能命中 override,然后 new 出一个全新实例。复制必须有一个源对象在手——它先 new 一个同类型空壳,再把源对象的字段内容灌进去。
// 路径一:factory create——从无到有
class my_driver extends uvm_driver #(my_txn);
`uvm_component_utils(my_driver)
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
req = my_txn::type_id::create("req"); // 查注册表,可被子类 override
endfunction
endclass
// 路径二:clone——从有到有
virtual task drive_and_respond(my_txn req);
my_txn rsp;
$cast(rsp, req.clone()); // 以 req 为模板复制一份当响应
rsp.addr = req.addr + 4;
seq_item_port.put(rsp);
endtask两条路径的起点不同,决定了后面一切差异:create 经过工厂注册表,clone 完全不经过。下表把核心维度摆在一起:
| 对比维度 | factory create | clone / copy |
|---|---|---|
| 本质 | 构造新对象 | 复制已有对象 |
| 是否需要源对象 | 否,只要类型句柄 | 是,必须有非 null 源 |
| 经过工厂注册表 | 是 | 否 |
| 受 type/instance override 影响 | 是 | 否,结果类型恒等于源类型 |
| 字段初始值 | 类声明默认值 | 源对象当前字段值(经 do_copy) |
name / 层级归属 | 可指定,component 挂父节点 | clone 出的对象 name 需手工再设 |
| 典型场景 | build 阶段建组件、造可替换的事务类型 | driver 复制请求做响应、monitor 复制观测事务广播 |
二、clone 的内部解剖:copy 加一次 new
很多人把 clone() 当黑盒,其实它就是两行逻辑的封装,看懂了就明白它的所有限制:

读图抓两点。第一,clone 的返回类型是基类 uvm_object,所以 $cast(rsp, req.clone()) 里的 $cast 不可省——不做向下转换,编译器只认基类接口。第二,字段灌入走的是 copy(),而 copy() 的默认实现是浅赋值加钩子回调:凡是用 `uvm_field_* 宏注册的字段自动处理,没走宏的字段要自己在 do_copy() 里补。
class my_txn extends uvm_sequence_item;
rand bit [31:0] addr;
rand bit [31:0] data;
my_payload payload; // 嵌套对象,宏管不到细节
`uvm_object_utils_begin(my_txn)
`uvm_field_int(addr, UVM_ALL_ON)
`uvm_field_int(data, UVM_ALL_ON)
`uvm_field_object(payload, UVM_ALL_ON)
`uvm_object_utils_end
// 需要定制时的钩子
virtual function void do_copy(uvm_object rhs);
my_txn rhs_;
super.do_copy(rhs);
if (!$cast(rhs_, rhs)) `uvm_fatal("CAST", "type mismatch")
payload = new; // 手工深拷贝嵌套对象
payload.copy(rhs_.payload);
endfunction
endclass这里和 SystemVerilog 对象拷贝:深拷贝与浅拷贝 的知识直接衔接:UVM 的 copy 默认仍是逐字段赋值,嵌套句柄字段若只赋句柄不 new 新对象,两份对象就共享同一份 payload——这正是 do_copy 存在的原因。`uvm_field_object 宏带 UVM_ALL_ON 时会自动递归调用子对象的 copy,但前提是子对象类也正确实现了 copy/do_copy,链条断一环就退回共享引用。
三、create 的语义核心:override 只在这一条路生效
为什么 UVM 组件一律用 type_id::create() 而禁止裸 new?因为 create 里多了一步「查表」:

图里的分叉点就是 create 与 clone 的全部差别。clone 第一步的 new 用的是源对象的动态类型——源是 base 就造 base,源是 ext 就造 ext,它根本不经过工厂那层查表。后果很实际:你在 test 里设了 type override,指望某个 sequence 产出的事务被替换成扩展类,结果 sequence 内部用的是 req.clone() 派生后续事务——override 完全失效,而且仿真不报错,只是行为静静回到基类。
还有一种等价的隐蔽形式:有些团队封装了自己的 new_transaction() 帮助函数,里面直接用 new 而不是 type_id::create,效果与 clone 一样绕开注册表。排查 override 失效时,除了搜 .clone(),也要搜裸 new——凡是不经过工厂的构造点,都是 override 链条上的断点。
这正是「必须 create 不能 clone」的第一类场景:对象类型需要被 test 层替换的地方。sequence item 的原型、组件、任何挂在 override 体系里的类型,首次造实例一律走 create;clone 只用于「我已经拿到正确类型的实例,想再要一份同型同内容的副本」的派生环节。
四、何时 clone 是对的,何时必须 create
把判断收敛成一句话:决定「造什么类型」用 create,决定「再造一份一样的」用 clone。落到具体场景:
| 场景 | 正确选择 | 理由 |
|---|---|---|
| env/agent/driver/monitor 等组件建树 | create | 组件必须经工厂才可能被 override 派生,且 create 负责挂父节点、进 UVM 层级 |
| sequence 里造首个 sequence item 原型 | create | test 可能 override item 类型;clone 会锁死在源类型 |
| driver 把请求复制成响应(rsp 协议) | clone | 类型已确定,只要字段副本;`uvm_field_* 保证内容一致 |
| monitor 观测到事务后广播给多个 subscriber | clone | 一份观测多处消费,各 subscriber 拿到的必须是独立副本,否则下游改字段互相污染 |
| scoreboard 暂存 expected 事务供稍后比对 | clone | 入队时 clone 一份快照,防止上游句柄复用导致队列里内容被后续写覆盖 |
| 需要按 config 字符串决定造哪个子类 | create(配合 registry) | clone 没有「按名字选类型」的能力 |
两类隐藏雷区值得单独点名。第一类是组件绝不能用 clone:uvm_component 的层级(parent、phase 参与、config_db 路径)在 create 时由工厂和构造函数共同建立,clone 一个 component 不会重建层级,得到的只是个游离对象。第二类是 sequence item 池化复用:driver 的 req 句柄在 item_done() 后会被 sequencer 回收复用,如果你在 scoreboard 或 reference model 里只存句柄不 clone,下一次随机化就把你存的那份「历史」改掉了——这类 bug 的表象是「队列里的事务莫名其妙变了」,定位成本远高于当初写一行 $cast(q.push_back(req.clone()))。
还有一个工程习惯值得养成:团队里约定「凡是跨组件传递的事务句柄,接收方若需要保留就 clone」,比事后逐处排查共享污染省事得多。reference model 比对失败、coverage 数据莫名漂移,追到最后经常就是某个环节少了一次 clone——把这条写进编码规范,比依赖每个人的自觉可靠。
性能上 clone 也比想象便宜:它是单次 new 加字段拷贝,没有注册表查询;但反过来 create 的查表开销在仿真尺度上也可忽略,所以选型永远按语义而不是按性能——该 override 的地方用 clone 省下的纳秒,会在调试失效的 override 时以小时计还回去。
五、常见坑速查
| 现象 | 根因 | 处理 |
|---|---|---|
| override 设了不生效 | 派生环节用了 clone,类型被锁死 | 首个实例走 create;clone 只用于同型副本 |
| clone 后嵌套对象改一个动两个 | 子对象未实现 do_copy,浅共享句柄 | 检查字段宏链;必要时手工 do_copy 深拷贝 |
| 编译报类型不匹配 | 省略 $cast 直接接 clone 返回值 | clone 返回 uvm_object,必须 $cast |
| clone 出的 component 不跑 phase | component 被 clone,层级未建立 | 组件永远 create;clone 只用于 object 系 |
| 队列里的历史事务内容变了 | 存的是句柄不是副本 | 入队/存档处 $cast(t, item.clone()) |
补一个直观判据:clone 出来的对象与原对象字段完全独立,改一边不会影响另一边,这正是它能安全放进队列缓存的原因;但句柄字段拷贝的是句柄本身,两边仍指向同一个子对象,需要深拷贝语义时必须在 do_copy 里显式 new 子对象。
这一篇在知识体系里的位置
- 上游:SystemVerilog 对象拷贝:深拷贝与浅拷贝 是 do_copy 设计的语言层基础;OOP 多态与动态类型概念见 SystemVerilog OOP:类、继承与多态。
- 工厂机制:UVM factory 与 env 派生 讲注册、override 的完整用法,本篇只站在「选 create 还是选 clone」的决策角度。
- 组件与 phase 背景:UVM 组件与 phase 机制 解释为什么组件必须 create 进层级树。
总结
UVM clone 与 create 区别就一条主线:create 经工厂注册表从无到有造对象、受 override 支配、是组件和可替换类型的唯一合法出生方式;clone 以既有对象为模板做字段级复制(内部是 new 加 copy/do_copy)、类型锁死在源的动态类型上、从不查 override 表——所以「定类型」用 create、「要副本」用 clone,组件永远 create,响应派生、广播快照、队列存档这些同型复制场景才是 clone 的地盘。