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时哭都哭不出来。

你可以这样起步:

  1. 定好分级标准,写进团队的coding guideline
  2. 每个模块的日志格式统一,时间戳、模块名、变量一个不能少
  3. 回归测试时只开INFO,出问题再逐级打开DEBUG和TRACE
  4. 定期review日志质量,别让坏习惯蔓延

说白了,日志就是你调试时的救命稻草。平时多花点心思,关键时刻能省你几天时间。下一节咱们聊聊日志的进阶技巧——怎么用日志快速定位问题,以及怎么避免日志成为性能瓶颈。