UVM 报告机制:severity、verbosity 与海量日志治理
更新时间:2026-09-25。本文是「集成电路大类 · 芯片设计 · 数字验证」序列里的一篇长尾实操文。前一篇 UVM 组件与 phase:env/agent/driver/monitor/scoreboard 怎么搭起来 结尾留了个伏笔:仿真的 PASS/FAIL 不靠
$display,而由报告体系统计判决。这一篇就把 UVM 报告机制拆开讲——它既是调试时的眼睛,也是回归时日志爆炸的灭火器。回归侧的配合打法见 UVM 回归管理与失败分流。
本文要回答的问题
- 一条
uvm_info打出来,中间经过了哪些过滤和处理,为什么有时看不见? - 调试时想看细节、回归时想收敛日志,verbosity 该按什么粒度设?
- 已知的良性 warning 刷屏,能不能不改 RTL、不改 VIP 源码就把它压住?
一、两个正交的维度:severity 与 verbosity
UVM 报告机制里每条消息带两个独立属性。severity(严重等级)描述「这条消息多重要」,verbosity(冗余等级)描述「这条消息多啰嗦」。发消息时两者一起给出,过滤时只比 verbosity,判决时只看 severity——这是理解整套机制的钥匙。
| severity | 典型宏 | 默认含义 | 对仿真结果的影响 |
|---|---|---|---|
UVM_INFO | `uvm_info("ID","msg",UVM_HIGH) | 普通信息 | 无 |
UVM_WARNING | `uvm_warning | 异常但可继续 | 不直接失败 |
UVM_ERROR | `uvm_error | 功能出错 | 计数,默认达上限后停 |
UVM_FATAL | `uvm_fatal | 无法继续 | 默认立即结束仿真 |
verbosity 是一组整数阈值,宏的第三参就是它:
| 等级 | 数值 | 什么时候该用 |
|---|---|---|
UVM_NONE | 0 | 不用来发消息,表示「全部静音」 |
UVM_LOW | 100 | 用例关键节点:配置、复位完成、最终判决 |
UVM_MEDIUM | 200 | 默认阈值;事务级摘要、寄存器访问 |
UVM_HIGH | 300 | 每个 transaction 的细节 |
UVM_DEBUG | 400 | 引脚级、状态机内部轨迹 |
判定规则一句话:消息的 verbosity 不高于当前设置值才会被放出来。默认阈值是 UVM_MEDIUM,所以你写 `uvm_info(..., UVM_HIGH) 却看不到,不是宏坏了,是它在阈值之下被静默丢掉了。把 severity 想成邮件的紧急程度,把 verbosity 想成订阅时勾选的「接收哪些列表」,两件事互不干扰。
除了这两个维度,每条消息还带一个字符串 ID(宏的第一个参数,如 "DRVPKT")。ID 不是装饰:action、verbosity、catcher 都支持按 ID 精确匹配,等于给消息分了类别。建议自研代码按「组件 + 场景」命名 ID,全平台别重复,输出里还会自动附上组件的完整层次路径,一条日志就能定位到是哪个 agent 的哪个环节发出的。
二、一条消息的流水线:handler、catcher、server
消息从宏到屏幕不是一步到位,中间要穿过三层对象。下图画出这条流水线,这也是后面所有 API 各自插入的位置。

顺着图走:组件里的宏先交给本组件的 uvm_report_handler(报告处理器),handler 按 verbosity 阈值做第一道过滤;活下来的消息进入 uvm_report_catcher(报告捕手)链,捕手可以改 severity、改 ID、改文案甚至直接吞掉;随后 handler 查这一 severity(还可细化到 severity + ID)对应的 action(动作集合)并执行;最后 uvm_report_server(报告服务器)负责全局计数,在 report phase 打印「X errors Y warnings」并参与 PASS/FAIL 判决。记住这张图,后面的每个方法都能对号入座。
三、verbosity 设置:全局、按组件、递归
set_report_verbosity_level 可以在三个作用域上操作,粒度从粗到细:
// 1. 全局:影响所有组件(在 test 或顶层 initial 里调)
uvm_top.set_report_verbosity_level(UVM_HIGH);
// 2. 只影响单个组件:只看 scoreboard 的 DEBUG 细节
env.scb.set_report_verbosity_level(UVM_DEBUG);
// 3. 递归设置:env 及其下整棵子树一起调到 HIGH
env.set_report_verbosity_level_hier(UVM_HIGH);
// 4. 按 ID 精确过滤:只放出某类消息
env.agt.drv.set_report_id_verbosity("DRVPKT", UVM_DEBUG);作用域的层次关系如下图所示,递归设置沿组件树向下覆盖,而组件自己的设置优先于继承来的值。

命令行也能不改代码做同样的事,这对调试最方便:+UVM_VERBOSITY=UVM_HIGH 设全局,+uvm_set_verbosity=env.agt.drv,DRVPKT,UVM_DEBUG,run 可以按层次、按 ID、甚至按指定 phase 临时打开。实践中建议让 VIP 和自研组件默认都用 UVM_HIGH 发事务细节——平时安静,需要时一个 plusarg 全打开,不必为了调试重新编译。
有个时序上的坑:递归方法要在子组件都已经创建之后才生效。在父组件的 build_phase 里调 set_report_verbosity_level_hier 时,孙组件往往还没建出来,这棵子树不会被覆盖;稳妥的位置是 connect_phase 或 test 的 end_of_elaboration,那时拓扑已经定型。同理,命令行 plusarg 由 UVM 根组件在合适阶段统一消化,不存在这个问题,这也是调试时优先用 plusarg 的另一个理由。
四、severity action:报错之后到底发生什么
每种 severity 绑定一个 action 位集,决定消息出现后系统做什么。常用 action 如下:
| action | 行为 |
|---|---|
UVM_DISPLAY | 打到标准输出(INFO/WARNING 默认) |
UVM_LOG | 写入通过 set_report_severity_file 指定的文件 |
UVM_COUNT | 计入 server 的错误统计(ERROR 默认) |
UVM_STOP | 立刻停在该语句,进交互调试 |
UVM_EXIT | 直接结束仿真(FATAL 默认) |
UVM_NO_ACTION | 什么都不做(等价静默) |
用 set_report_severity_action 改默认行为,例如让 warning 也计入失败统计、或让首个 ERROR 立即停下进调试器:
// warning 也计数:团队对 warning 零容忍时
env.scb.set_report_severity_action(UVM_WARNING,
UVM_DISPLAY | UVM_COUNT);
// 调试期:第一个 ERROR 就 STOP,保留现场看波形
uvm_top.set_report_severity_action(UVM_ERROR,
UVM_DISPLAY | UVM_STOP);
// 更细:只对某个 ID 的错误立即退出
set_report_severity_id_action(UVM_ERROR, "PROTO", UVM_EXIT);另外两个常用旋钮是 set_report_max_quit_count(ERROR 累计到多少个后终止仿真,避免错误雪崩后刷出几十万行同源日志,调试单个问题时设成 1 最干净)和按 ID 的动作覆盖,它的优先级高于按 severity 的设置。action 是位集,可以用按位或自由组合,比如「显示 + 落盘 + 计数」三合一;想恢复库默认行为时用 UVM_DEFAULT_ACTION 即可。
顺带说一句自定义 server 的场景:uvm_report_server 是可以替换的单例,继承它重写 report_summary,就能把统计结果输出成回归脚本好解析的格式(例如按 ID 分组的错误清单、带层次路径的 CSV),而不必事后 grep 自由文本。绝大多数项目不需要走到这一步,但当回归结果要自动入库、按 ID 做失败聚类时,换一个 server 比在日志上做字符串切割稳得多。
五、uvm_report_catcher:不动源码拦截消息
回归跑到第三千个用例时,某颗 VIP 持续刷一个已确认无害的 warning,一天产出几 GB 日志。改 VIP 源码不现实,压 verbosity 又会误伤别的信息。这时用 uvm_report_catcher——它就是第二节流水线图里黄色那一环,在 action 执行之前拦截消息:
class benign_warn_catcher extends uvm_report_catcher;
virtual function action_e catch();
if (get_severity() == UVM_WARNING &&
get_id() == "VIPBENCH") begin
// 三种处理任选:
set_severity(UVM_INFO); // 降级:变成普通信息
return CAUGHT; // 丢弃:根本不输出
end
return THROW; // 其余放行
endfunction
endclass
// 在 test 的 build/connect phase 挂载,可指定作用组件
benign_warn_catcher c = new;
c.callback_handle = env.vip_agt;
uvm_report_cb::add(env.vip_agt, c);捕手里能做三件事:set_severity 升级或降级(把可疑 INFO 升成 ERROR)、return CAUGHT 直接丢弃、set_message/set_verbosity 改写内容与级别;THROW 表示放行。也可以反向用它「升级」——把本该是错误却只报 INFO 的第三方组件消息提级,纳入失败判决。要克制的是:catcher 只能压已分析确认的良性消息,并且每条屏蔽都要带 ID 和理由,最好同时写进 waiver 清单;无差别吞掉所有 warning 等于拆掉验证平台的报警器。
六、日志落盘与回归治理
回归场景下屏幕日志没意义,关键是分文件、可检索、体量可控。标准做法是给不同 severity 或不同组件挂文件:
integer fh = $fopen("sim_log/errors.log", "a");
uvm_top.set_report_severity_file(UVM_ERROR, fh);
uvm_top.set_report_severity_action(UVM_ERROR,
UVM_DISPLAY | UVM_LOG | UVM_COUNT);文件打开后要记得仿真结束时由平台统一收尾,多次回归用追加模式时注意清空旧文件,否则错误会跨用例累积,看起来像越跑越糟。除了按 severity 分文件,也可以用 set_report_id_file 给某一类消息(比如协议违例)单独成册,分流粒度完全按回归脚本怎么消费来定。
结合前面的工具,一套能跑海量回归的组合是:回归命令行统一 +UVM_VERBOSITY=UVM_LOW 收敛总量;ERROR/FATAL 单独落盘,失败用例只看这个文件;已知良性 warning 用 catcher 按 ID 精确屏蔽并登记 waiver;DEBUG 级细节平时不开,某个用例失败复现时再用 +uvm_set_verbosity 对该用例单独打开。这样日志从「每个用例几万行、没人看得完」变成「默认精简、按需放大、错误单独成册」,排查路径短,磁盘和归档成本也压得下来,和 UVM 回归管理与失败分流 里的失败分流流程正好衔接。
这一篇在知识体系里的位置
组件树与 report phase 的来历见 UVM 组件与 phase;报告在 sequence/scoreboard 里如何报错见 UVM sequence/driver/scoreboard 实战;批量用例下怎么消费这些报告见 UVM 回归管理与失败分流;waiver 治理的思路与 lint waiver 签核 同源。
总结
UVM 报告机制用 severity 和 verbosity 两个正交维度管理消息:severity(INFO/WARNING/ERROR/FATAL)决定 action(DISPLAY/LOG/COUNT/STOP/EXIT)与最终判决,verbosity(NONE/LOW/MEDIUM/HIGH/DEBUG)决定消息能否越过阈值,set_report_verbosity_level 支持全局、按组件和递归三档粒度,命令行 plusarg 可免编译临时调节;消息依次穿过 handler 过滤、uvm_report_catcher 拦截(降级、升级、丢弃)、action 执行和 server 计数落盘——调试时把 verbosity 调高看清细节,回归时全局收敛到 LOW、错误单独落文件、用 catcher 按 ID 精确屏蔽已确认的良性 warning,才是海量日志不淹没人的正确用法。