
1. 为什么需要精准控制库信号的 dump 策略做过大规模 SoC 验证的人都有一个共同体会仿真跑起来不难难的是跑完之后怎么高效地看波形。一个中等规模的芯片设计DUT 加上各种 IP、存储器模型、标准单元库动辄几百万甚至上千万个信号节点。如果全量 dumpFSDB 文件几个 GB 起步Verdi 打开要等好几分钟翻波形的时候卡到怀疑人生。更麻烦的是大部分库单元内部的信号在实际 debug 过程中根本用不到。你关心的是自己 RTL 里的控制逻辑、数据通路、状态机跳转而不是标准单元内部那个与非门的中间节点。但这些库信号默认情况下会被一起 dump 下来白白占用空间和加载时间。fsdbskip_cell_instance就是解决这个问题的。它是 VCS 在生成 FSDB 波形时的一个编译/仿真选项作用是在 dump 阶段跳过指定 cell instance 内部的信号只保留端口级别的信息。配合debug_region之类的辅助手段可以做到“该看的全看得到不该看的一个不留”。这篇文章适合正在用 VCS Verdi 做仿真调试的验证工程师、设计工程师尤其是那些被大波形文件折磨过的朋友。不管你是刚接触 VCS 的新手还是已经用了几年但没深入研究过 dump 策略的老手下面这些内容应该都能帮你省下不少时间。2. 核心机制与方案选型拆解2.1 FSDB dump 的默认行为与痛点VCS 生成 FSDB 波形通常有两种方式一种是在 testbench 里调用$fsdbDumpfile和$fsdbDumpvars系统任务另一种是通过编译选项-fsdb配合fsdb...系列参数。不管哪种方式默认的 dump 范围都是“全量”——从指定的顶层模块往下所有层级的信号全部记录。这就带来一个问题标准单元库、IO 库、存储器编译器等第三方 IP 内部的信号也会被一并 dump。这些信号的特点是数量极大、层次极深、命名规则统一比如u_xxx/gate_1234/n567而且绝大多数情况下你根本不会去看。我实测过一个案例一个包含 4 个 CPU 核的子系统DUT 本身 RTL 信号大约 80 万个加上库单元内部信号后总 dump 节点数飙到 600 多万。FSDB 文件从预期的 2GB 涨到了 14GBVerdi 加载时间从 40 秒变成了 6 分钟。这种开销在回归测试阶段是灾难性的。2.2 skip_cell_instance 的工作原理fsdbskip_cell_instance的核心逻辑是在 FSDB 写入引擎遍历设计层次时遇到匹配指定条件的 cell instance就跳过其内部所有信号只保留该 instance 的输入输出端口。这里需要理解几个关键概念cell instance指的是设计中例化的一个模块实例。在 VCS 的层次结构中每个 instance 都有唯一的路径名。skip 的粒度可以精确到某个具体的 instance 路径也可以用通配符匹配一类 instance。端口保留被 skip 的 instance其端口信号仍然会被 dump这样你在波形里还能看到这个模块的输入输出变化只是看不到内部细节。这个机制和$fsdbDumpvars的深度控制参数比如$fsdbDumpvars(0, top, all)中的深度参数有本质区别。深度控制是按层次层级来限制而skip_cell_instance是按 instance 名称模式来过滤更加灵活精准。2.3 与其他 dump 控制方案的对比在实际项目中控制 dump 范围的手段不止一种。我把常见的几种方案列出来对比一下方案控制粒度灵活性对仿真速度影响适用场景$fsdbDumpvars深度参数按层次深度低小简单设计层次规整fsdbskip_cell_instance按 instance 名称模式高极小含大量库单元的设计debug_region编译选项按模块定义中中需要区分 debug 和非 debug 模块-fsdb fsdbdump_scope按 scope 路径中高小指定特定区域 dump手动$fsdbDumpvars逐模块调用按调用位置最高小小规模精准控制从表中可以看出skip_cell_instance的优势在于它不需要你修改 testbench 代码只需要在编译或仿真命令里加一个参数就能全局生效。而且它用的是名称模式匹配对于命名规范的库单元特别有效。2.4 为什么选择 skip 而不是只 dump 指定区域有人可能会问既然库信号不需要那我为什么不直接用$fsdbDumpvars只 dump 我关心的模块而是要用 skip 的方式这个问题问得好。两种思路的区别在于白名单方式只 dump 指定模块你需要明确列出所有想看的模块。在大型设计中这个列表可能很长而且随着 debug 需求变化要不断修改。漏掉一个模块波形里就缺了关键信息。黑名单方式skip 不需要的模块你只需要列出不想看的库单元模式。库单元的命名通常很规整几条通配符就能覆盖绝大部分。即使漏掉一些也只是多 dump 了一些信号不会导致关键信息缺失。在实际项目中黑名单方式更稳妥。因为 debug 过程中你永远不知道下一秒需要看哪个模块白名单容易漏黑名单只会多。3. 核心细节解析与实操要点3.1 语法格式与参数说明fsdbskip_cell_instance的基本语法如下fsdbskip_cell_instanceinstance_pattern其中instance_pattern是 instance 路径的匹配模式支持通配符*和?。可以多次指定这个参数来匹配多种模式。在 VCS 编译命令中它通常这样使用vcs -full64 -sverilog -debug_accessall \ -fsdb \ fsdbskip_cell_instance*u_*_lib* \ fsdbskip_cell_instance*genblk*ram* \ -f filelist.f \ -o simv也可以在仿真运行时通过-ucli或者$fsdbDumpvars的扩展参数来动态控制但编译期指定是最稳定的方式。注意不同版本的 VCS 对这个参数的支持程度略有差异。VCS 2018.09 之后的版本支持比较完善更早的版本可能需要配合-debug_access的特定级别。建议先确认你使用的 VCS 版本。3.2 如何确定要 skip 哪些 instance这是整个流程中最关键的一步。skip 错了要么波形里缺了关键信号要么没起到优化效果。我的经验是分三步走第一步先跑一次全量 dump导出层次结构用-debug_accessall编译后在仿真里执行simv -ucli ucli% fsdbDumpfile full.fsdb ucli% fsdbDumpvars 0 top ucli% run跑一小段仿真后用 Verdi 打开 FSDB通过Tools - Hierarchy或者直接看 nWave 的层次树导出完整的 instance 列表。第二步分析 instance 命名规律把导出的层次列表按名称排序观察哪些模式对应库单元。常见的库单元命名特征包括包含lib、cell、stdcell、io、pad等关键词层次深度特别深比如超过 10 层同一父节点下有大量同名前缀的 instance来自特定库文件的模块名如sky130_*、tsmc28_*等第三步用脚本生成 skip 模式对于大规模设计手动列模式不现实。我通常写一个简单的 Python 脚本来处理import re from collections import Counter # 读取层次列表 with open(hierarchy.txt) as f: instances [line.strip() for line in f if line.strip()] # 统计各层级名称模式 patterns Counter() for inst in instances: parts inst.split(/) for part in parts: # 提取名称中的字母前缀 match re.match(r([a-zA-Z_]), part) if match: patterns[match.group(1)] 1 # 输出高频模式 for pattern, count in patterns.most_common(50): print(f{pattern}: {count})根据输出结果挑选出明显属于库单元的模式组合成 skip 参数。3.3 通配符使用的注意事项通配符用起来方便但有几个坑需要注意*匹配任意字符包括/这意味着*lib*会匹配路径中任何位置包含lib的 instance。如果不小心可能会误伤你自己 RTL 里名字带lib的模块。匹配是大小写敏感的*LIB*和*lib*不一样。库单元命名通常是小写但有些 IP 会用大写需要确认。多个模式之间是“或”关系只要匹配任意一个模式就会被 skip。所以模式要尽量精确避免过度匹配。路径分隔符在 VCS 的 instance 路径中分隔符是/。模式里写*u_ram*会匹配top/u_ram_0和top/sub/u_ram_1但不匹配top/u_ram因为没有后续字符。如果要匹配u_ram本身需要写*u_ram*或者*u_ram。我踩过的一个坑早期用*mem*来 skip 存储器模型结果把自己 RTL 里一个叫mem_ctrl的模块也 skip 了导致调试时找不到关键控制信号。后来改成*u_mem_model*和*genblk*mem_array*才精准匹配。3.4 与 debug_region 的配合使用debug_region是 VCS 的另一个有用选项它允许你在编译时指定哪些模块需要 debug 信息哪些不需要。和skip_cell_instance配合使用效果更好。基本用法vcs -full64 -sverilog \ -debug_accessall \ debug_regioncelllib \ debug_regioncellmy_design \ -fsdb \ fsdbskip_cell_instance*lib* \ -f filelist.fdebug_regioncelllib表示对名为lib的模块不生成 debug 信息debug_regioncellmy_design表示对自己的设计模块生成完整 debug 信息。两者的区别在于debug_region影响的是编译期的 debug 信息生成skip_cell_instance影响的是运行期的 dump 行为。前者可以减少编译时间和仿真内存占用后者主要减少 FSDB 文件大小和加载时间。两者结合效果最佳。提示debug_region的语法在不同 VCS 版本中变化较大建议查阅对应版本的 User Guide。如果找不到确切文档可以先只用skip_cell_instance它更稳定。4. 完整实操流程与配置示例4.1 环境准备与版本确认在开始之前先确认你的环境# 确认 VCS 版本 vcs -ID | head -5 # 确认 Verdi 版本 verdi -version # 确认 FSDB 相关库路径 echo $VERDI_HOME ls $VERDI_HOME/share/PLI/VCS/LINUX64我用的环境是 VCS 2022.06 Verdi 2022.06Linux CentOS 7.9。这个组合对skip_cell_instance的支持很完善。4.2 编译脚本配置下面是一个完整的编译脚本示例包含了 skip 策略#!/bin/bash # compile.sh DESIGN_TOPtb_top FILELIST./filelist.f SIMV_NAMEsimv vcs -full64 \ -sverilog \ -timescale1ns/1ps \ -debug_accessall \ -kdb \ -lca \ -fsdb \ fsdbskip_cell_instance*u_*_lib* \ fsdbskip_cell_instance*genblk*ram* \ fsdbskip_cell_instance*u_io_pad* \ fsdbskip_cell_instance*u_pll_model* \ fsdbskip_cell_instance*u_adc_model* \ fsdbskip_cell_instance*u_dac_model* \ fsdbskip_cell_instance*stdcell* \ fsdbskip_cell_instance*sky130* \ -top $DESIGN_TOP \ -f $FILELIST \ -o $SIMV_NAME \ -l compile.log几个关键点说明-debug_accessall开启完整 debug 访问权限这是使用 FSDB dump 的前提。-kdb生成 Verdi 的 KDB 数据库加速 Verdi 加载。-lca启用一些高级特性某些 VCS 版本需要。-fsdb启用 FSDB dump 支持。多个fsdbskip_cell_instance每个指定一种模式按需增减。4.3 Testbench 中的 dump 控制编译选项只是第一步testbench 里的 dump 调用也需要配合。一个典型的 testbench dump 初始化代码如下initial begin // 设置 FSDB 文件名 $fsdbDumpfile(wave.fsdb); // dump 顶层及其下所有信号深度 0 表示不限 // all 表示包含所有信号类型 $fsdbDumpvars(0, tb_top, all); // 如果只想 dump 特定模块可以这样写 // $fsdbDumpvars(0, tb_top.u_dut, all); // $fsdbDumpvars(0, tb_top.u_dut.u_cpu, all); // 设置 FSDB 文件大小限制可选 $fsdbDumpoff; #1000; $fsdbDumpon; end这里有个技巧如果你在编译时已经用skip_cell_instance过滤了库单元testbench 里就可以放心地用$fsdbDumpvars(0, tb_top, all)全量 dump不用担心文件爆炸。4.4 仿真运行与波形验证编译完成后运行仿真./simv -ucli -i run.tcl -l sim.log其中run.tcl可以包含fsdbDumpfile wave.fsdb fsdbDumpvars 0 tb_top all run 100us quit仿真结束后用 Verdi 打开波形验证效果verdi -ssf wave.fsdb -nologo 在 Verdi 中检查以下几点文件大小对比之前全量 dump 的 FSDB 文件应该有明显减小。我实测的案例中从 14GB 降到了 3.2GB减少了约 77%。加载时间Verdi 打开时间应该从几分钟降到几十秒。层次结构在 nWave 的层次树中被 skip 的库单元应该只显示端口展开后没有内部信号。关键信号完整性确认你关心的 RTL 信号都还在没有误伤。4.5 参数调优与效果量化skip 策略不是越激进越好。skip 太多可能导致 debug 时缺少必要信息skip 太少则优化效果不明显。我通常按以下步骤调优第一轮只 skip 最明显的库单元模式比如*lib*、*stdcell*。跑一次仿真记录 FSDB 大小和 Verdi 加载时间。第二轮根据第一轮的结果增加更多模式。比如存储器模型、IO 模型、PLL 模型等。再次记录数据。第三轮检查是否有误伤。打开波形随机抽查几个被 skip 的 instance确认它们的端口信号还在且没有你需要的内部信号被跳过。下面是一个效果对比表来自我最近做的一个项目轮次skip 模式数量FSDB 大小Verdi 加载时间备注全量014.2 GB6 min 12 s基准第一轮38.7 GB3 min 45 s只 skip 标准单元第二轮83.2 GB48 s增加存储器、IO、PLL第三轮122.8 GB35 s精细调优无误伤从表中可以看出第二轮的效果最明显第三轮的边际收益已经不大。实际项目中做到第二轮通常就够了。5. 常见问题与排查技巧实录5.1 skip 不生效的几种原因这是最常见的问题。你明明加了fsdbskip_cell_instance但 FSDB 文件大小没变化Verdi 里库信号还在。可能的原因有原因一参数位置不对。fsdbskip_cell_instance必须放在-fsdb之后且要在-f filelist.f之前。如果放在 filelist 后面VCS 可能忽略它。原因二模式匹配不上。VCS 的 instance 路径和你想象的可能不一样。比如你写*u_ram*但实际路径是tb_top.u_dut.u_ram_0这个模式是能匹配的。但如果实际路径是tb_top.u_dut.ram_0没有u_前缀就匹配不上。解决办法是先用 Verdi 导出准确的层次路径。原因三VCS 版本不支持。某些老版本 VCS 对skip_cell_instance的支持不完整或者需要额外的 license 特性。确认版本后如果确实不支持可以考虑升级或者改用$fsdbDumpvars的白名单方式。原因四testbench 里用了$fsdbDumpvars的all参数覆盖。某些情况下testbench 里的 dump 调用会覆盖编译选项。检查 testbench 代码确保没有冲突的设置。5.2 误伤了自己 RTL 信号怎么办这是第二常见的问题。skip 模式写得太宽泛把自己设计里的模块也跳过了。比如用*mem*匹配存储器模型结果把mem_ctrl、mem_arbiter这些 RTL 模块也 skip 了。解决办法加前缀限定把*mem*改成*u_mem_model*或*genblk*mem_array*利用库单元通常有特定前缀的特点。用更长的模式模式越长匹配越精确。宁可多写几个模式也不要用一个宽泛的模式。验证阶段抽查每次调整 skip 模式后在 Verdi 里随机抽查几个 RTL 模块确认它们的内部信号还在。我个人的习惯是所有 skip 模式都加上u_前缀假设库单元例化名都以u_开头这样基本不会误伤 RTL。如果某些库单元没有这个前缀再单独处理。5.3 FSDB 文件仍然过大的优化思路即使加了 skipFSDB 文件可能还是很大。这时候可以从以下几个方向继续优化减少 dump 时间窗口不需要全程 dump。在 testbench 里用$fsdbDumpoff和$fsdbDumpon控制只在关键时间段开启 dump。降低 dump 频率对于变化不频繁的信号可以用$fsdbDumpvars的fsdbdelta或fsdbregion参数控制采样方式。使用 FSDB 压缩Verdi 支持 FSDB 压缩选项可以在$fsdbDumpfile里指定压缩级别$fsdbDumpfile(wave.fsdb, 100); // 100 表示压缩级别数值越大压缩率越高分文件 dump把不同模块的波形 dump 到不同文件按需加载。比如$fsdbDumpfile(cpu.fsdb); $fsdbDumpvars(0, tb_top.u_dut.u_cpu, all); $fsdbDumpfile(gpu.fsdb); $fsdbDumpvars(0, tb_top.u_dut.u_gpu, all);5.4 常见问题速查表问题现象可能原因排查方法解决方案skip 不生效参数位置错误检查编译命令顺序确保在-fsdb之后、-f之前skip 不生效模式匹配不上Verdi 导出层次路径对比调整通配符模式skip 不生效版本不支持查看 VCS 版本和文档升级 VCS 或改用白名单误伤 RTL 信号模式过于宽泛抽查 RTL 模块波形加前缀限定精确匹配FSDB 仍过大dump 时间过长检查 dump 时间窗口用$fsdbDumpoff/on控制FSDB 仍过大压缩未开启检查$fsdbDumpfile参数设置压缩级别Verdi 加载慢KDB 未生成检查编译选项加-kdb选项Verdi 加载慢FSDB 未压缩检查文件大小开启压缩或分文件5.5 几个实战避坑心得心得一先全量跑一次再逐步加 skip。不要一上来就写一堆 skip 模式。先全量 dump 一次确认设计能正常跑通波形能正常看。然后再逐步加 skip每加一批就验证一次。这样出问题容易定位。心得二skip 模式要写注释。在编译脚本里每个 skip 模式后面加注释说明它对应什么库单元。过几个月回头看你绝对记不住*genblk*ram*到底是 skip 什么的。心得三保留一个“全量 dump”的编译配置。有时候 debug 需要看库单元内部信号这时候要能快速切换回全量模式。我的做法是维护两个编译脚本compile_full.sh和compile_fast.sh根据需要选用。心得四注意fsdbskip_cell_instance和fsdbskip_cell的区别。前者 skip 的是 instance后者 skip 的是 cell 定义。在大多数场景下用 instance 版本更精准。但如果你确定某个 cell 的所有 instance 都不需要 dump用 cell 版本更简洁。心得五仿真日志里确认 skip 生效。VCS 在编译时会输出 skip 相关的信息检查compile.log里有没有类似FSDB: skipping cell instance matching pattern ...的记录。如果没有说明参数没被识别。6. 进阶技巧与扩展应用6.1 动态 skip 策略除了编译期指定VCS 还支持在仿真运行时动态控制 skip。通过 UCLI 接口可以在仿真过程中随时调整# 在 UCLI 中动态添加 skip 模式 fsdbDumpvars 0 tb_top all fsdbSkipCellInstance *u_ram* fsdbSkipCellInstance *u_rom* run 100us这种方式适合在 debug 过程中临时调整策略不需要重新编译。但稳定性不如编译期指定建议只在调试阶段使用。6.2 结合覆盖率分析优化 skip 策略如果你在做覆盖率驱动的验证可以把覆盖率数据和 skip 策略结合起来。具体做法是先跑一次带覆盖率的仿真收集覆盖率数据。分析哪些模块的覆盖率已经达标哪些还没有。对覆盖率已达标的模块加大 skip 力度对未达标的模块保留完整 dump。这样可以在保证验证质量的前提下最大化波形优化效果。6.3 在回归测试中的应用回归测试是 skip 策略最能发挥价值的场景。一个典型的回归测试可能跑几百个 testcase每个都生成 FSDB 文件。如果不做优化磁盘空间很快就不够了。我的做法是日常回归使用最激进的 skip 策略只保留 RTL 关键信号。FSDB 文件小跑得快磁盘压力小。失败 case 复现切换到全量 dump 模式重新跑失败的 testcase获取完整波形用于 debug。定期全量验证每周或每两周跑一次全量 dump 的回归确保没有因为 skip 导致遗漏问题。这种分层策略既保证了回归效率又不影响 debug 质量。6.4 与其他 EDA 工具的协同skip_cell_instance是 VCS 特有的选项如果你同时使用其他仿真器比如 Xcelium、Questa需要找对应的替代方案。不过 FSDB 格式是 Verdi 通用的所以波形查看端的体验是一致的。如果你用 Verdi 的 KDB 数据库做信号追踪skip 策略会影响 KDB 的生成。被 skip 的 instance 在 KDB 里也不会有内部信号信息。这一点在做跨模块信号追踪时需要注意。7. 个人实操体会与建议写了这么多最后分享几点我在实际项目中的真实体会。第一不要追求一步到位。我见过有工程师花两天时间研究 skip 模式想一次性写出完美的配置。结果仿真跑起来发现误伤了一堆信号又得重新调。正确的做法是先用最简单的模式跑通然后根据实际效果逐步优化。每次调整不超过 3 个模式调整后立即验证。第二FSDB 文件大小不是唯一指标。有时候文件大小降下来了但 Verdi 加载时间没怎么变。这可能是因为 KDB 数据库没有优化或者 FSDB 的索引结构有问题。这时候要检查-kdb选项和 FSDB 压缩设置。第三团队协作时skip 策略要统一。如果每个人用自己的 skip 配置波形文件格式不一致互相之间没法复现问题。建议在项目初期就确定一套标准的 skip 策略写进团队的编译脚本模板里。第四定期回顾 skip 策略。随着设计迭代库单元可能会更新instance 命名可能会变化。每季度回顾一次 skip 模式确保它们仍然有效。我一般会在项目里程碑节点做这个回顾。第五遇到问题先查 compile.log。VCS 的编译日志里包含了大量有用信息包括 skip 参数是否被识别、匹配了多少个 instance、有没有警告信息。养成看日志的习惯能省很多排查时间。这个技巧后续还可以这样扩展把 skip 策略和 CI/CD 流程结合起来在 Jenkins 或 GitLab CI 里根据 testcase 类型自动选择不同的编译配置。日常回归用 fast 配置失败复现用 full 配置全自动切换效率提升很明显。