☰
VCS仿真波形优化:精准控制库信号dump策略
2026/10/6 11:18:31 网站建设 项目流程

1. 为什么需要精准控制库信号的 dump 策略

做过大规模 SoC 验证的人都有一个共同体会:仿真跑起来不难,难的是跑完之后怎么高效地看波形。一个中等规模的芯片设计,DUT 加上各种 IP、存储器模型、标准单元库,动辄几百万甚至上千万个信号节点。如果全量 dump,FSDB 文件几个 GB 起步,Verdi 打开要等好几分钟,翻波形的时候卡到怀疑人生。

更麻烦的是,大部分库单元内部的信号,在实际 debug 过程中根本用不到。你关心的是自己 RTL 里的控制逻辑、数据通路、状态机跳转,而不是标准单元内部那个与非门的中间节点。但这些库信号默认情况下会被一起 dump 下来,白白占用空间和加载时间。

+fsdb+skip_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 涨到了 14GB,Verdi 加载时间从 40 秒变成了 6 分钟。这种开销在回归测试阶段是灾难性的。

2.2 skip_cell_instance 的工作原理

+fsdb+skip_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深度参数按层次深度低小简单设计,层次规整
+fsdb+skip_cell_instance按 instance 名称模式高极小含大量库单元的设计
debug_region编译选项按模块定义中中需要区分 debug 和非 debug 模块
-fsdb +fsdb+dump_scope按 scope 路径中高小指定特定区域 dump
手动$fsdbDumpvars逐模块调用按调用位置最高小小规模精准控制

从表中可以看出,skip_cell_instance的优势在于:它不需要你修改 testbench 代码,只需要在编译或仿真命令里加一个参数,就能全局生效。而且它用的是名称模式匹配,对于命名规范的库单元特别有效。

2.4 为什么选择 skip 而不是只 dump 指定区域

有人可能会问:既然库信号不需要,那我为什么不直接用$fsdbDumpvars只 dump 我关心的模块,而是要用 skip 的方式?

这个问题问得好。两种思路的区别在于:

  • 白名单方式(只 dump 指定模块):你需要明确列出所有想看的模块。在大型设计中,这个列表可能很长,而且随着 debug 需求变化要不断修改。漏掉一个模块,波形里就缺了关键信息。
  • 黑名单方式(skip 不需要的模块):你只需要列出不想看的库单元模式。库单元的命名通常很规整,几条通配符就能覆盖绝大部分。即使漏掉一些,也只是多 dump 了一些信号,不会导致关键信息缺失。

在实际项目中,黑名单方式更稳妥。因为 debug 过程中你永远不知道下一秒需要看哪个模块,白名单容易漏,黑名单只会多。

3. 核心细节解析与实操要点

3.1 语法格式与参数说明

+fsdb+skip_cell_instance的基本语法如下:

+fsdb+skip_cell_instance=<instance_pattern>

其中<instance_pattern>是 instance 路径的匹配模式,支持通配符*和?。可以多次指定这个参数来匹配多种模式。

在 VCS 编译命令中,它通常这样使用:

vcs -full64 -sverilog -debug_access+all \ -fsdb \ +fsdb+skip_cell_instance="*u_*_lib*" \ +fsdb+skip_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_access+all编译后,在仿真里执行:

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_access+all \ +debug_region+cell+lib \ +debug_region+cell+my_design \ -fsdb \ +fsdb+skip_cell_instance="*lib*" \ -f filelist.f

+debug_region+cell+lib表示对名为lib的模块不生成 debug 信息,+debug_region+cell+my_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.06,Linux CentOS 7.9。这个组合对skip_cell_instance的支持很完善。

4.2 编译脚本配置

下面是一个完整的编译脚本示例,包含了 skip 策略:

#!/bin/bash # compile.sh DESIGN_TOP="tb_top" FILELIST="./filelist.f" SIMV_NAME="simv" vcs -full64 \ -sverilog \ -timescale=1ns/1ps \ -debug_access+all \ -kdb \ -lca \ -fsdb \ +fsdb+skip_cell_instance="*u_*_lib*" \ +fsdb+skip_cell_instance="*genblk*ram*" \ +fsdb+skip_cell_instance="*u_io_pad*" \ +fsdb+skip_cell_instance="*u_pll_model*" \ +fsdb+skip_cell_instance="*u_adc_model*" \ +fsdb+skip_cell_instance="*u_dac_model*" \ +fsdb+skip_cell_instance="*stdcell*" \ +fsdb+skip_cell_instance="*sky130*" \ -top $DESIGN_TOP \ -f $FILELIST \ -o $SIMV_NAME \ -l compile.log

几个关键点说明:

  • -debug_access+all:开启完整 debug 访问权限,这是使用 FSDB dump 的前提。
  • -kdb:生成 Verdi 的 KDB 数据库,加速 Verdi 加载。
  • -lca:启用一些高级特性,某些 VCS 版本需要。
  • -fsdb:启用 FSDB dump 支持。
  • 多个+fsdb+skip_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 中检查以下几点:

  1. 文件大小:对比之前全量 dump 的 FSDB 文件,应该有明显减小。我实测的案例中,从 14GB 降到了 3.2GB,减少了约 77%。
  2. 加载时间:Verdi 打开时间应该从几分钟降到几十秒。
  3. 层次结构:在 nWave 的层次树中,被 skip 的库单元应该只显示端口,展开后没有内部信号。
  4. 关键信号完整性:确认你关心的 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 不生效的几种原因

这是最常见的问题。你明明加了+fsdb+skip_cell_instance,但 FSDB 文件大小没变化,Verdi 里库信号还在。可能的原因有:

原因一:参数位置不对。+fsdb+skip_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 文件仍然过大的优化思路

即使加了 skip,FSDB 文件可能还是很大。这时候可以从以下几个方向继续优化:

减少 dump 时间窗口:不需要全程 dump。在 testbench 里用$fsdbDumpoff和$fsdbDumpon控制,只在关键时间段开启 dump。

降低 dump 频率:对于变化不频繁的信号,可以用$fsdbDumpvars的+fsdb+delta或+fsdb+region参数控制采样方式。

使用 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,根据需要选用。

心得四:注意+fsdb+skip_cell_instance和+fsdb+skip_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 策略结合起来。具体做法是:

  1. 先跑一次带覆盖率的仿真,收集覆盖率数据。
  2. 分析哪些模块的覆盖率已经达标,哪些还没有。
  3. 对覆盖率已达标的模块,加大 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 配置,全自动切换,效率提升很明显。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询