2. FSDB波形生成与信号记录策略
FSDB(Fast Signal Database)是 Verdi 的原生波形格式,相比 VCD 具有更高的压缩率和更快的随机访问速度。但 FSDB 文件并不是仿真器自动产生的,它依赖仿真过程中由 PLI/VPI 接口注册的系统任务来驱动。理解 $fsdbDumpfile 与 $fsdbDumpvars 的语义,以及如何按层次、模块、信号做选择性记录,是控制波形规模与调试效率的第一道关口。
两个核心系统任务的分工
$fsdbDumpfile 负责“开文件”,它决定波形写到哪个路径、文件大小上限是多少;$fsdbDumpvars 负责“选内容”,它决定从哪个层次开始、往下递归几层、是否记录多维数组和内存、是否记录强度信息等。二者必须配合使用:只调用 $fsdbDumpfile 而不调用 $fsdbDumpvars,会得到一个空文件;反之,若未先指定文件名,$fsdbDumpvars 会使用默认文件名(通常为 novas.fsdb)并可能覆盖上一次结果。
initial begin
$fsdbDumpfile("wave.fsdb", 2048); // 文件名 + 大小上限(MB)
$fsdbDumpvars(0, tb_top, "+all"); // 从 tb_top 起全层次记录
end
第一个参数 0 表示“不限深度”,即从指定层次递归到最底层;若写 1,则只记录该层次本身及其直接子层次;写 2 则再往下多一层。第二个参数是起始层次,通常用顶层 testbench 名或某个 DUT 实例名。"+all" 是常用选项,表示连同多维数组、memory、实数等一并记录;若只关心普通信号,可省略它以减小文件。
按层次与模块选择性 dump
全芯片 dump 在大型 SoC 中往往产生几十 GB 的 FSDB,既拖慢仿真又让 Verdi 打开缓慢。更实际的做法是分层记录:对关键模块(如 CPU 核、DMA 控制器、总线仲裁器)做完整 dump,对已验证稳定的外设只记录接口信号。实现方式有两种。
第一种是多次调用 $fsdbDumpvars,每次指定不同层次和深度:
$fsdbDumpfile("soc.fsdb", 4096);
$fsdbDumpvars(0, tb_top.u_cpu); // CPU 全层次
$fsdbDumpvars(1, tb_top.u_dma); // DMA 仅顶层+子层
$fsdbDumpvars(0, tb_top.u_bus, "+mda"); // 总线含 memory/数组
第二种是使用 $fsdbDumpvarsByFile,把要记录的信号清单写在一个文本文件里,每行一个层次路径,适合信号数量多、需要版本管理的场景。此外还有 $fsdbDumpoff / $fsdbDumpon 这对任务,可以在仿真运行中动态暂停和恢复记录,常用于跳过复位初始化阶段或只抓取特定事务窗口。
多时钟域与复位阶段的记录策略
多时钟域设计里,各域信号翻转频率差异很大。若统一按最快时钟采样,慢时钟域信号会被大量冗余采样点淹没;若统一按最慢时钟,快域又会丢失细节。FSDB 本身按事件记录,不强制统一采样率,但仿真器在 $fsdbDumpvars 时仍会受 +fsdb+ 类选项影响。实践中更稳妥的做法是:对每个时钟域分别指定 dump 范围,让 Verdi 在查看时按域分组,而不是在 dump 阶段强行统一。
复位阶段同样值得单独处理。复位期间大量寄存器被强制到已知值,波形上表现为密集跳变,但对调试价值有限。可以在复位释放后再调用 $fsdbDumpon,或把复位逻辑单独放在一个不 dump 的层次中。下面这张图展示了从仿真启动到波形落盘的完整信号链,以及各环节可施加的控制点。
文件大小与性能的权衡
FSDB 的大小主要由三部分决定:被记录信号的数量、信号翻转的次数、以及每个信号的数据宽度。假设一个 32 位总线在 100 MHz 下每周期都翻转,仿真 1 ms 就产生约 105 次事件;若同时记录 1000 根这样的信号,文件很容易达到数 GB。压缩算法能降低存储,但无法消除记录本身带来的仿真开销——每次信号变化都要经过 PLI 回调写入缓冲区。
因此权衡的核心是“记录什么”而非“怎么压缩”。一个可操作的判断依据是:先只 dump 顶层接口和待调试模块,运行一次仿真观察 FSDB 大小和仿真耗时;若文件小于 2 GB 且仿真减速在 20% 以内,再逐步加入下一层信号。反之,若某模块加入后文件翻倍而调试中几乎不看,就应把它移出 dump 范围。
$fsdbDumpfile 的第二个参数)单位是 MB。当文件达到上限时,仿真器会停止写入并给出警告,而不是自动滚动覆盖。若需要长时间仿真,建议配合 $fsdbDumpoff 分段记录,或使用 +fsdb+autoflush 选项控制缓冲区刷新频率。
$fsdbDumpfile 指向不同文件而不关闭前一个,这可能导致文件句柄泄漏或波形错乱。若确实需要分段,应先用 $fsdbDumpoff 暂停,再重新指定文件名并 $fsdbDumpon。
一个可复用的配置片段
下面给出一个面向多时钟域 SoC 的 dump 配置示例,假设顶层为 tb_top,含 CPU、DMA、总线三个主要子系统,复位信号为 rst_n。该片段在复位释放后才开始记录,并对不同子系统采用不同深度。
initial begin
$fsdbDumpfile("soc_debug.fsdb", 4096);
$fsdbDumpvars(0, tb_top.u_cpu, "+all"); // CPU 全量
$fsdbDumpvars(2, tb_top.u_dma); // DMA 两层
$fsdbDumpvars(1, tb_top.u_bus); // 总线接口层
$fsdbDumpoff; // 先关闭
@(posedge tb_top.rst_n); // 等复位释放
#100;
$fsdbDumpon; // 再开启记录
end
这段配置的效果是:复位期间不产生波形,复位释放 100 ns 后开始记录,CPU 内部所有信号可见,DMA 只看到两层,总线只看接口。实际项目中可根据调试目标调整层次和深度,但“先小后大、按需扩展”的原则普遍适用。