对象在哪里创建,句柄在哪里可见?
在 UVM 验证环境中,我们经常看到这样的现象:reg_model对象在env的build_phase中创建,但在test层可以直接通过env.reg_model访问它;而vsequencer中也持有各个 real sequencer 的句柄,但它自身并没有创建那些 sequencer。这一切都源于 SystemVerilog 的句柄(handle)与对象(object)分离机制。
很多初学者会混淆:为什么在 env 里 new 出来的东西,test 里也能用?为什么 vsequencer 可以调度别的 sequencer?答案很简单:组件变量是引用(句柄),不是值本身。理解了这一点,UVM 中 99% 的“神秘现象”都会豁然开朗。
句柄是指向对象的引用
句柄与对象:一对多的关系
在 SystemVerilog 中,类类型变量存储的是对象的内存地址,而不是对象本身。这个变量就是“句柄”。对象的实际内存是在new()或create()时分配的。一个对象可以有多个句柄指向它,也可以没有句柄(此时对象被垃圾回收)。
重点:create()实际上执行了两步:分配内存并调用构造函数(new),然后返回该对象的句柄。我们把这个句柄赋值给某个变量(如m_reg_model),这只是让该变量指向对象,并不复制对象。
例如:
reg_model rm = reg_model::type_id::create("rm", this);这里rm是一个句柄,指向工厂新创建的那个reg_model对象。我们可以把这个句柄复制给其他变量,它们都指向同一个对象。
父子组件间的句柄传递
在 UVM 组件树中,父组件在build_phase中创建子组件,并通常将子组件的句柄保存在自己的成员变量中。例如env创建了agent,并保存了agent的句柄。其他组件如果需要访问该agent,可以通过父组件的层次路径找到它,或者通过config_db传递句柄。
重点:父组件保留子组件句柄,是为了方便管理和访问。test 层能够通过env.agent访问,正是因为 env 的成员变量中保存了 agent 的句柄,而 test 持有 env 的句柄。
config_db注入等价于句柄传递
uvm_config_db::set()和get()实际上存储和获取的是句柄值,而不是对象拷贝。当你set一个对象句柄后,其他组件get到的是同一个对象的句柄,因此它们共享同一个对象。这就是为什么 reg_model 在 env 中创建,test 中还能通过config_db拿到同一个 reg_model。
重点:config_db传递的是句柄(引用),不是深拷贝。所有通过get获取该对象的组件,操作的都是同一个对象。
对象创建与句柄共享
以下代码展示了在 env 中创建 reg_model 和 vsequencer,并通过 config_db 将 reg_model 句柄传递给 test 或其他组件。
class my_env extends uvm_env; reg_model m_reg_model; my_vseqr m_vseqr; `uvm_component_utils(my_env) function new(string name, uvm_component parent); super.new(name, parent); endfunction function void build_phase(uvm_phase phase); super.build_phase(phase); // 创建 reg_model 对象,并将句柄存到 m_reg_model m_reg_model = reg_model::type_id::create("m_reg_model", this); m_reg_model.build(); // 构建寄存器模型 // 创建 vsequencer 对象,并将句柄存到 m_vseqr m_vseqr = my_vseqr::type_id::create("m_vseqr", this); // 通过 config_db 将 reg_model 句柄共享给 test 层或其他组件 uvm_config_db #(reg_model)::set(this, "*", "reg_model", m_reg_model); endfunction endclass在 test 中获取 reg_model:
class my_test extends uvm_test; `uvm_component_utils(my_test) function void build_phase(uvm_phase phase); super.build_phase(phase); my_env env; // 假设 env 已经作为 test 的子组件创建 env = my_env::type_id::create("env", this); // 通过 config_db 获取同一个 reg_model 对象 reg_model rm; if (!uvm_config_db #(reg_model)::get(this, "", "reg_model", rm)) begin `uvm_fatal("NOREG", "reg_model not found") end // 现在 rm 和 env.m_reg_model 指向同一个对象 endfunction endclass解析:
m_reg_model = reg_model::type_id::create(...)中,create()返回一个句柄,该句柄指向新创建的对象,同时赋值给m_reg_model。uvm_config_db::set(this, "*", "reg_model", m_reg_model)将这个句柄放入配置数据库。注意,这里m_reg_model是句柄,存储的是对象的地址,不是对象本身。- 在 test 中
get时,获得的是同一个句柄,因此rm和env.m_reg_model指向同一个对象,对rm的任何修改都会影响env.m_reg_model。
跨组件共享同一对象
跨 env 共享 reg_model
在大型 SoC 验证中,可能有多个 env 或子 env 需要访问同一个寄存器模型。例如 CPU env 和 DMA env 都需要读写系统寄存器。此时只需在顶层 env 或 test 中创建一次 reg_model,然后通过 config_db 将句柄传递给所有需要的组件。这样既避免了重复创建,也保证了数据一致性。
virtual sequencer 挂载 real sequencer 句柄
在 SOC 验证中,virtual sequencer需要调度多个real sequencer。这些 real sequencer 实际上是由各自的 agent 创建的,vsequencer 并不创建它们,而是通过句柄引用它们。通常在 env 的connect_phase中,将 real sequencer 的句柄赋值给 vsequencer 的成员:
function void connect_phase(uvm_phase phase); vseqr.m_cpu_sqr = cpu_agt.sqr; // 只是句柄赋值,没有新对象 vseqr.m_dma_sqr = dma_agt.sqr; endfunction这样 vsequencer 就拥有了这些 sequencer 的“通讯录”,可以指挥它们,但并没有拥有这些 sequencer 的所有权(对象仍然由各自的 agent 管理)。
共享配置对象
很多时候我们需要在多个组件间共享一个配置对象(例如接口配置、测试参数)。利用句柄传递,我们只需创建一个配置对象,通过 config_db 或直接赋值传递句柄,所有组件看到的是同一份配置,修改时也能实时同步。
句柄使用中的常见错误
句柄未初始化(null)就使用
如果在new()或create()之前就使用句柄,或者create()失败(例如工厂未注册),句柄为 null,调用其成员会引发运行时错误。务必在对象创建后检查句柄是否非空,或用$isunknown等确认。句柄拷贝 ≠ 对象拷贝
很多人误以为a = b会复制对象。实际上,a = b只是让两个句柄指向同一对象。如果希望复制对象,需要使用copy()方法或new后手动复制字段。理解引用语义是避免逻辑错误的关键。父子句柄关系混淆
子组件句柄由父组件保存,但子组件内部也可以通过get_parent()获取父组件句柄。如果错误地释放或覆盖父组件句柄,可能导致无法访问子组件。保持组件树清晰,避免手动销毁由 UVM 管理的组件。句柄 null 调用 fatal
在 UVM 中,访问 null 句柄会导致仿真中止(fatal error)。通常表现为Null pointer dereference。在使用前必须检查,尤其是从config_db获取的对象,必须判断返回值是否为 1(成功)。build_phase 后才 set 句柄失败
如果通过config_db传递句柄,set必须在目标组件的get之前完成。由于组件build_phase顺序自上而下,通常应在父组件或 test 的build_phase中尽早set。如果在connect_phase或之后才set,子组件已经get失败,导致句柄为 null。务必在正确 phase 完成配置传递。
6句柄理解透,UVM 99% 错误都能自解
在 UVM 学习和使用中,句柄与对象的关系是最基础也最重要的概念。很多看似玄乎的问题(为什么这个变量改了那个也变?为什么这里报 null?)根源都在于对句柄的误解。
记住以下要点:
- 类类型变量是句柄,存储地址,不是数据本身。
new()/create()分配对象内存,返回句柄。- 多个句柄可以指向同一对象;一个对象可以被多个组件共享。
config_db传递的是句柄,不是对象拷贝,所以是高效的共享方式。- 使用前检查句柄是否非空,是避免 90% 运行时错误的好习惯。
当你彻底理解“句柄是指向对象的引用”后,UVM 环境中的对象创建、组件连接、配置共享都会变得清晰透明。这也是区分新手和老手的一道分水岭。掌握它,你在调试 UVM 平台时就能快速定位问题,不再被“奇怪”的现象迷惑。