UVM override 静默失效:测试没报错,但你的错误注入根本没生效

摘要:UVM override 失效是静默的——不报错、不告警,测试照常跑完,错误注入却从未发生。本文剖析三种最常见的静默失效写法:用 new 建对象、override 写在 super.build_phase() 之后、set_inst_override 路径对不上,并给出正确写法与验证手段。

TL;DR 速览:

  • 用 new 建对象,绕过工厂,override 失效。详见「失效写法一」。
  • override 写在 super 之后,对象已建,失效。详见「失效写法二」。
  • 实例路径对不上,匹配不到,静默失效。详见「失效写法三」。
  • 正确做法:create 创建、先 override 后 super、路径传 this。

factory 是 UVM 复用性的基石。它解决的问题一句话能说完:在不动原始代码的前提下,把某个类替换成它的派生类。

但真正的坑不在“怎么用”,而在“为什么没生效”——override 失效是静默的:不报错、不告警,测试照常跑完,只是你注入的错误根本没出现。

工厂内部到底做了什么

UVM 工厂内部维护一张映射表:基类 → 实际要构造的类

正常情况下这张表是恒等映射(dma_item → dma_item)。当你调用 set_type_overrideset_inst_override,就把某一条映射改成了 dma_item → dma_err_item

之后创建对象时,工厂查表得到实际类型,构造出派生类对象,再交给基类句柄持有,配合虚方法完成多态——“替换”就是这么实现的

这也解释了为什么必须用 createnew 不查表。

四条前提,缺一不可

override 要生效,必须同时满足四条:

#前提漏掉的后果
1类型用 `uvm_component_utils / `uvm_object_utils 注册过工厂不认识这个类型,override 无从谈起
2对象用 type_id::create() 创建override 静默失效
3override 调用发生在目标被 create 之前override 静默失效
4set_inst_override 的路径能匹配到创建者override 静默失效

后三条,是绝大多数“明明写了 override 却没生效”的真凶。

失效写法一:用 new 建对象

// ✗ 错误:绕开工厂
dma_item tr = new("tr");
// ✓ 正确:经过工厂
dma_item tr = dma_item::type_id::create("tr");

new 是 SystemVerilog 原生的构造调用,直接构造你写的那个类,与工厂毫无关系。工厂里的映射表根本没被查询,override 自然不生效。

这个错误不会报任何警告——测试跑得好好的,只是错误注入没有发生。

失效写法二:override 写在 super.build_phase() 之后

// ✗ 错误:env 已经把 item 造出来了
function void build_phase(uvm_phase phase);
  super.build_phase(phase);                  // 这一步创建了 env 及其子组件
  dma_item::type_id::set_type_override(dma_err_item::get_type());   // 太晚了
endfunction

UVM 的 build_phase自顶向下执行的:先 test,再 env,再 agent……super.build_phase() 一旦调用,下面的组件树就已经构建完毕,该创建的对象都创建完了。此时再改映射表,对已存在的对象无效。

正确顺序是先 override,后 super

// ✓ 正确
function void build_phase(uvm_phase phase);
  dma_item::type_id::set_type_override(dma_err_item::get_type());
  super.build_phase(phase);                  // 现在创建的对象才会查新表
endfunction

失效写法三:set_inst_override 的路径对不上

路径这一条最容易踩,因为它有两个反直觉的地方。

第一,路径的起算点,取决于你有没有传第三个参数 parent

  • 传了 parentinst_path 被解释为相对该组件的层级路径
  • 不传 parentinst_path 被解释为绝对实例路径(从 uvm_top 起算)

这一点在 UVM 参考手册里写得很明确。所以下面这种写法里,"env.agent.*" 会被当成从根算起的绝对路径,根本匹配不到 uvm_test_top.env.agent.*

// ✗ 想当然:以为 "env.agent.*" 是相对于 test 的
dma_item::type_id::set_inst_override(dma_err_item::get_type(), "env.agent.*");

两种改法都对:

// ✓ 写法一:第三个参数传 this,路径就变成相对的
dma_item::type_id::set_inst_override(dma_err_item::get_type(), "env.agent.*", this);
// ✓ 写法二:不传 parent,那就老老实实写全路径
dma_item::type_id::set_inst_override(dma_err_item::get_type(), "uvm_test_top.env.agent.*");

第二,多条实例覆盖同时匹配时,先注册的那条赢。

UVM 参考手册的原话是:工厂处理实例覆盖时,实例队列按注册顺序处理,第一条匹配的生效;因此更具体的覆盖应当注册,更通用的注册。

注意这和 type override 的规则正好相反——type override 是后设的覆盖先设的(replace = 1 时)。所以下面这样写,具体那条永远轮不到:

// ✗ 顺序反了:通配写在前面,把后面更具体的那条吃掉了
dma_item::type_id::set_inst_override(dma_err_item::get_type(),   "env.agent.*",   this);
dma_item::type_id::set_inst_override(dma_delay_item::get_type(), "env.agent.sqr", this);

把两行顺序调过来即可——具体的先写,通用的后写

这两条规则合在一起,解释了一个常见现象:为什么在子类 test 里加的实例覆盖,有时会“盖不过”父类 test 里的那条。

原因是父类通常也在 build_phase / end_of_elaboration_phase 里注册自己的覆盖,而这些方法的 super 调用由你决定放在哪里。如果子类把自己的覆盖写在 super.build_phase() 之后,它不仅要面对“晚于创建”的老问题,在注册顺序上也输给了父类(父类先注册,先匹配者生效)。

所以子类的实例覆盖应当写在 super 调用之前。这也是为什么很多验证环境的 test 里,override 语句总是 build_phase 的第一行。

一个不会静默的情况:替换类型没有继承关系

set_type_override 要求替换类型是原类型的派生类。道理很朴素:调用方手里拿的是基类句柄,装配进去的对象必须能赋值给它,也就是里氏替换。

// ✓ dma_err_item 必须 extends dma_item
class dma_err_item extends dma_item;
  ...
endclass

这不是静默失效——不同工具的表现形式不同,有的给告警、有的直接报错,总之跑一次就会报出来,不需要靠翻日志去发现。真正难查的,是上面那三种不声不响的情况。

type override 与 inst override

方法作用范围用法
set_type_override全局替换某类型dma_item::type_id::set_type_override(dma_err_item::get_type())
set_inst_override层次路径替换某实例dma_item::type_id::set_inst_override(dma_err_item::get_type(), "env.agent.*", this)

优先级规则:实例覆盖优先于类型覆盖,与两者的设置顺序无关。至于多条实例覆盖彼此之间的关系,见上一节——它反直觉。

一个完整例子——只对 env.agent 下的事务做错误注入:

class dma_err_test extends dma_smoke_test;
  `uvm_component_utils(dma_err_test)
function void build_phase(uvm_phase phase);
// 第三个参数传 this,路径才是相对本组件的;
// 且必须早于 super.build_phase
dma_item::type_id::set_inst_override(
dma_err_item::get_type(),
"env.agent.*",
this);
super.build_phase(phase);
endfunction
endclass

第三个参数 this 不要省。省掉它,同一串 "env.agent.*" 会被当成绝对路径——匹配不上,而且悄无声息。

怎么确认 override 真的生效

因为失效是静默的,必须主动验证。三个手段:

一、打印工厂配置。 这是最直接的一个,能把每条 override 登记的路径原样打出来——路径写错时一眼就能认出来:

uvm_factory factory = uvm_factory::get();
factory.print();                    // 输出当前所有 type / inst override 记录
uvm_top.print_topology();           // 顺带看实际建出来的类型对不对

二、打印实际类型。end_of_elaboration_phase 里打印关键对象的 get_type_name(),确认拿到的已经是派生类名。

三、在派生类里加一条 uvm_info 既然错误注入是故意的,就让它“出声”——跑一次就知道有没有生效。

另外,override 其实还能从命令行给,不必改代码:

+uvm_set_type_override=dma_item,dma_err_item
+uvm_set_inst_override=dma_item,dma_err_item,uvm_test_top.env.agent.*

下面给一个完整的回归脚本示例,把两个参数一起用上,并说明各自的格式与注意事项:

#!/bin/bash
# run_dma_err_regression.sh —— 用命令行 override 跑错误注入回归
1) 类型覆盖:把 dma_item 全局替换成 dma_err_item
格式:+uvm_set_type_override=<原类型>,<替换类型>[,replace]
- 原类型、替换类型都要用注册过的类型名(`uvm_object_utils 等)
- 可选第三段 replace:1 表示后设覆盖先设的,0 表示保留先设的
- 不写第三段时默认 replace=1
TYPE_OVERRIDE="+uvm_set_type_override=dma_item,dma_err_item"
2) 实例覆盖:只替换 uvm_test_top.env.agent 下的 dma_item
格式:+uvm_set_inst_override=<原类型>,<替换类型>,<实例路径>[,replace]
- 实例路径必须是绝对路径,从 uvm_top 起算,支持通配符 *
- 路径写错不会报错,override 会静默失效,务必用 factory.print() 核对
INST_OVERRIDE="+uvm_set_inst_override=dma_item,dma_err_item,uvm_test_top.env.agent.*"
3) 跑回归:把两个参数一起传给仿真器
vsim -c -do "run -all; quit" 
+UVM_TESTNAME=dma_smoke_test
$TYPE_OVERRIDE
$INST_OVERRIDE
top_tb

注意事项:

  • 类型名必须已注册:命令行里写的 dma_itemdma_err_item 必须和代码里 `uvm_object_utils / `uvm_component_utils 注册的名字完全一致,否则工厂不认识,override 无从谈起。
  • 实例路径是绝对路径+uvm_set_inst_override 的路径从 uvm_top 起算,要写全 uvm_test_top.env.agent.*,不能写相对路径 env.agent.*——写错不报错,只会静默失效。
  • 通配符用法* 匹配任意一层或多层实例名,env.agent.* 会匹配 agent 下所有子组件里创建的 dma_item
  • 多条实例覆盖按注册顺序匹配:命令行参数按出现顺序注册,先匹配的那条生效;更具体的路径要写在更通用的前面。
  • 回归后务必验证:命令行 override 不写进代码,光看代码看不出来。跑完用 factory.print()uvm_top.print_topology() 确认实际建出来的类型,再配合派生类里的 uvm_info 确认错误注入真的发生。

回归里换变体时很方便,代价是不看代码看不出来——所以线上环境慎用。

小结

factory 能做到“不改代码就替换”,靠的是查表 + 多态;而 override 生效需要四条前提同时成立:

注册宏 + type_id::create() + override 早于创建 + 实例路径能匹配

四条里缺任何一条,override 都可能静默失效。这是 UVM 里最典型的“测试没报错,但验证根本没做”的场景之一。

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值