2、日志的艺术(上):日志分级与内容规范
日志这东西,说简单也简单,说难也难。我见过太多团队,日志写得跟天书似的,出了bug根本没法查。说白了,日志就是你调试时的眼睛。眼睛不好使,你怎么找问题?
今天咱们先聊日志分级和内容规范。这是基本功,但很多人就是做不好。
日志分级:别什么都往一个桶里扔
先问个问题:你见过那种满屏都是日志的仿真吗?我见过。有一次帮同事看一个UVM环境,一跑仿真,终端刷刷刷地滚,根本看不清哪条是有效信息。为什么?因为他把所有信息都打成了$display。
日志分级,说白了就是给信息分个优先级。我一般用这六级:
| 级别 | 含义 | 使用场景 |
|---|---|---|
| FATAL | 致命错误 | 仿真必须终止,比如内存越界、协议严重违规 |
| ERROR | 错误 | 功能错误,但仿真可以继续,比如数据比对不一致 |
| WARN | 警告 | 潜在问题,比如时序违例、配置异常 |
| INFO | 信息 | 正常流程信息,比如初始化完成、测试开始 |
| DEBUG | 调试 | 调试用信息,比如中间变量值、状态机跳转 |
| TRACE | 跟踪 | 最细粒度,比如每拍信号变化、函数调用栈 |
我个人习惯,平时跑回归测试只开INFO及以上。只有出问题了,才把DEBUG和TRACE打开。你想想看,如果一上来就开TRACE,那日志量能把你淹死。
核心原则:FATAL和ERROR必须保证100%正确。WARN可以容忍少量误报,但别太多。INFO要简洁明了,别写废话。
日志内容规范:时间戳、模块名、关键变量
光有分级还不够,内容怎么写也很关键。我在项目中遇到过最头疼的事:日志里只有一句话「data mismatch」,然后呢?哪个模块?哪个时间点?哪个变量?全没有。这跟没写有什么区别?
一条合格的日志,至少包含三要素:
- 时间戳:精确到仿真时间,最好带单位(ns/ps)
- 模块名:谁打的这条日志,层级要清晰
- 关键变量值:出问题时的上下文数据
举个例子,这是我在实际项目中用的格式:
// 好的日志格式
`uvm_info("AXI_MONITOR", $sformatf("[%0t ns] addr=0x%h, data=0x%h, burst_len=%0d",
$time, addr, data, burst_len), UVM_MEDIUM)
// 差的日志格式
`uvm_info("MON", "data mismatch", UVM_LOW)
看出区别了吗?好的日志,你一看就知道是哪个模块、什么时间、哪个地址出了问题。差的日志,你还要去翻代码猜上下文。
我的小技巧:时间戳用$time配合%0t格式化,这样可读性最好。模块名用UVM的层次路径,别自己瞎起名字。
避坑指南:我曾经踩过的坑
嗯,这里要注意几个常见问题。
第一,别滥用FATAL。 我曾经见过一个同事,数据比对不一致就直接`uvm_fatal。结果呢?仿真跑一次要几个小时,中间一个小错误就全停了,根本没法定位问题。FATAL只留给那些「不停止就会出大乱子」的情况。
第二,DEBUG和TRACE要加开关。 别一股脑全打出来。我习惯用UVM的verbosity控制:
// 只在DEBUG级别以上才打印
if (uvm_report_enabled(UVM_DEBUG)) begin
`uvm_info("SEQ", $sformatf("state=%0d, counter=%0d", state, counter), UVM_DEBUG)
end
第三,INFO别写废话。 比如「test started」「test passed」这种,可以。但「entering function xxx」「leaving function xxx」这种,除非你在调试,否则别写。你想想看,一个仿真几百万行日志,全是这种废话,谁受得了?
警告:千万别在日志里打印敏感信息,比如密码、密钥。我见过有人把AES密钥直接打出来的,那要是流片了,芯片就废了。
实战建议:从第一天就规范起来
我个人建议,项目一开始就定好日志规范。别等到出问题了再补。我见过太多项目,前期图省事,日志随便写,后期debug时哭都哭不出来。
你可以这样起步:
- 定好分级标准,写进团队的coding guideline
- 每个模块的日志格式统一,时间戳、模块名、变量一个不能少
- 回归测试时只开INFO,出问题再逐级打开DEBUG和TRACE
- 定期review日志质量,别让坏习惯蔓延
说白了,日志就是你调试时的救命稻草。平时多花点心思,关键时刻能省你几天时间。下一节咱们聊聊日志的进阶技巧——怎么用日志快速定位问题,以及怎么避免日志成为性能瓶颈。