ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

VCS+Verdi FSDB波形dump实战:编译选项、系统任务与常见报错排查

VCS+Verdi FSDB波形dump实战:编译选项、系统任务与常见报错排查 1. 为什么FSDB dump这件事值得单独拿出来讲做数字前端验证的人几乎都绕不开波形调试。你写完一个模块跑完仿真发现输出不对第一反应就是打开波形看看信号到底怎么跳的。这时候问题来了你用的是VCS仿真器想看波形得靠Verdi而Verdi能读的波形格式是FSDB。于是你需要在testbench里加一段dump FSDB的代码把仿真过程中信号的变化记录下来生成一个.fsdb文件再用Verdi打开分析。听起来很简单对吧但我见过太多新手在这个环节翻车。有人加了$fsdbDumpfile和$fsdbDumpvars编译报错说找不到系统任务有人仿真跑完了目录里死活找不到fsdb文件有人fsdb文件生成了但用Verdi打开发现里面只有顶层信号子模块的信号一个都没有还有人dump出来的文件几个G仿真速度慢得像蜗牛爬。这些问题的根源往往不是代码写错了而是对FSDB dump的机制理解不到位。这篇内容就是把这些年我在实际项目中反复踩过的坑、总结出来的经验系统地梳理一遍。从最基础的编译选项配置到dump变量的粒度控制再到常见报错的排查思路尽量讲透。不管你是刚接触Verilog仿真的在校学生还是刚转行做IC验证的工程师只要你在用VCSVerdi这套工具链这篇内容应该都能帮你少走一些弯路。需要提前说明的是FSDB dump涉及的工具主要是VCS和Verdi这两个都是Synopsys的商用工具。不同版本的VCS在编译选项和系统任务的支持上可能有细微差异我下面讲的内容基于较常见的VCS 2018及以上版本和Verdi 2018及以上版本如果你用的是更老的版本部分选项可能需要调整。2. FSDB dump的完整链路从编译到波形生成2.1 编译阶段-debug_access和-kdb到底在做什么很多人第一次用FSDB dump的时候会直接照着网上的教程在testbench里写$fsdbDumpfile和$fsdbDumpvars然后编译结果VCS报了一堆错说这些系统任务未定义。原因很简单VCS默认编译出来的仿真器是不带FSDB dump功能的你需要在编译选项里显式开启。最核心的编译选项是-debug_access。这个选项告诉VCS在编译时保留调试信息包括信号名、层次结构、变量类型等。没有这些信息Verdi打开fsdb文件时就没法把波形和RTL代码对应起来。-debug_access后面可以跟不同的参数常见的有all、r、f等。all表示开启所有调试功能包括读写信号值、force/release、波形dump等。如果你只是需要dump波形r只读可能就够了但实际项目中我一般直接用all省得后面发现某个功能没开又要重新编译。另一个关键选项是-kdb。KDB是Knowledge Database的缩写Verdi用它来存储设计的知识库信息。加上-kdb选项后VCS会在编译时生成一个simv.daidir/kdb文件Verdi打开fsdb时会自动读取这个文件来获取设计的层次结构和信号信息。如果没有-kdbVerdi虽然也能打开fsdb但可能无法正确显示信号的全路径名或者无法和RTL代码做关联。一个典型的编译命令长这样vcs -full64 -sverilog -debug_accessall -kdb -lca \ -f filelist.f \ -o simv这里的-full64表示编译64位仿真器-sverilog开启SystemVerilog支持-lca是开启一些较新的特性Limited Customer Availability某些VCS版本需要它才能支持完整的FSDB功能。注意-debug_accessall会显著增加编译时间和仿真器的体积因为它保留了大量的调试信息。如果你的设计很大编译一次可能要几十分钟甚至更久。建议在项目初期就确定好调试策略避免频繁重新编译。2.2 仿真阶段$fsdbDumpfile和$fsdbDumpvars的配合编译通过之后接下来就是在testbench里调用系统任务来dump波形。最基础的两个任务是$fsdbDumpfile和$fsdbDumpvars。$fsdbDumpfile用来指定生成的fsdb文件名。它的调用格式是$fsdbDumpfile(wave.fsdb);这行代码通常放在initial块的最开始确保在仿真开始之前就设置好文件名。文件名可以带路径比如../sim/wave.fsdb但要注意路径中的目录必须已经存在否则VCS会报错说无法创建文件。$fsdbDumpvars用来指定要dump哪些信号。它的调用格式有几种// 方式一dump所有层次的信号 $fsdbDumpvars(0, top); // 方式二dump指定层次的信号 $fsdbDumpvars(1, top.u_dut); // 方式三dump指定信号 $fsdbDumpvars(0, top.u_dut.clk, top.u_dut.rst_n);第一个参数是dump的深度。0表示dump所有层次1表示只dump指定模块及其下一层2表示再往下两层以此类推。第二个参数开始是要dump的模块或信号。这里有一个很容易踩的坑$fsdbDumpvars的第一个参数如果是0它会递归dump指定模块下的所有信号包括所有子模块。如果你的设计有几十万门这个操作会让fsdb文件变得巨大无比仿真速度也会急剧下降。我见过一个项目新手直接在top层调了$fsdbDumpvars(0, top)结果仿真跑了两个小时才跑完fsdb文件有十几个GVerdi打开都卡了半天。正确的做法是根据调试需求精确控制dump的范围。比如你只关心某个子模块的行为就只dump那个子模块$fsdbDumpvars(0, top.u_dut.u_alu);或者你只关心几个关键信号就单独列出来$fsdbDumpvars(0, top.u_dut.state, top.u_dut.cnt, top.u_dut.data_out);2.3 文件生成fsdb到底什么时候写入磁盘还有一个新手经常困惑的问题fsdb文件是什么时候生成的是在仿真结束的时候一次性写入还是仿真过程中逐步写入答案是仿真过程中逐步写入。VCS在仿真运行时会按照你指定的dump范围在每个时间步收集信号值然后写入fsdb文件。这意味着如果仿真中途崩溃或者被kill掉已经写入的那部分波形数据是可以保留的。这一点在实际调试中很有用——有时候仿真跑了一半发现有问题你可以直接kill掉仿真然后用Verdi打开已经生成的fsdb文件查看崩溃之前的波形。但这也带来一个问题如果仿真时间很长fsdb文件会持续增长可能把磁盘写满。所以在大规模仿真中通常需要配合$fsdbDumpoff和$fsdbDumpon来控制dump的时机只在关键时间段开启dump。initial begin $fsdbDumpfile(wave.fsdb); $fsdbDumpvars(0, top.u_dut); $fsdbDumpoff; // 先关闭dump #1000; $fsdbDumpon; // 1000ns后开启dump #5000; $fsdbDumpoff; // 6000ns后再次关闭 end这样fsdb文件里只会记录1000ns到6000ns之间的波形文件大小可以大幅减小。3. 那些年我们踩过的FSDB dump报错3.1 编译报错undefined system task $fsdbDumpfile这是最常见的新手错误。编译时VCS报错Error-[SVA] Undefined system task or function $fsdbDumpfile原因通常有三个第一编译选项里没有加-debug_access。VCS默认不加载FSDB相关的系统任务必须通过-debug_accessall或-debug_accessr来开启。第二没有链接Verdi的库。有些VCS安装配置中FSDB系统任务的实现是在Verdi的库里的需要在编译时加上-P选项指定Verdi的plilib。不过较新的VCS版本通常会自动处理这个如果你用的是老版本可能需要手动加vcs -full64 -sverilog -debug_accessall -kdb \ -P ${VERDI_HOME}/share/PLI/VCS/LINUX64/novas.tab \ ${VERDI_HOME}/share/PLI/VCS/LINUX64/pli.a \ -f filelist.f第三环境变量VERDI_HOME没有设置。VCS需要知道Verdi安装在哪里才能找到相关的库文件。检查一下你的.bashrc或.cshrc里有没有设置VERDI_HOME以及PATH里有没有包含$VERDI_HOME/bin。3.2 仿真报错cannot open FSDB file编译通过了仿真也能跑但运行到$fsdbDumpfile那一行时报错Error: cannot open FSDB file wave.fsdb这个错误通常是文件路径问题。如果你指定的文件名带了路径比如../sim/wave.fsdb那么../sim/这个目录必须已经存在。VCS不会自动创建目录它只会尝试在已有目录中创建文件。解决办法很简单要么确保目录存在要么直接用不带路径的文件名让fsdb生成在当前工作目录下。还有一种可能是磁盘空间不足。fsdb文件在仿真过程中会持续增长如果磁盘满了VCS就无法继续写入报的错误可能也是cannot open或cannot write。这种情况下需要清理磁盘空间或者调整dump范围减小文件大小。3.3 Verdi打开fsdb后看不到信号fsdb文件生成了用Verdi也能打开但波形窗口里只有顶层信号子模块的信号一个都看不到。这个问题通常是因为$fsdbDumpvars的深度参数设置不对。比如你写了$fsdbDumpvars(1, top);这里的1表示只dump top层及其下一层。如果你的DUT在top.u_dut这一层而你想看的是top.u_dut.u_alu里面的信号那就需要把深度设为2或者0。另一个可能的原因是-kdb选项没有加。没有KDB文件Verdi虽然能读到fsdb里的信号值但无法正确构建层次结构导致信号显示不全或者路径名不对。还有一种情况是信号被优化掉了。VCS在编译时如果发现某些信号没有被使用可能会把它们优化掉这样即使你dump了这些信号fsdb里也不会有它们的数据。解决办法是在编译时加上nospecify和notimingcheck等选项或者在RTL里给这些信号加上/* keep */属性。3.4 fsdb文件过大导致仿真变慢这个问题在大规模设计中特别常见。一个几十万门的设计如果全量dumpfsdb文件可能达到几十个G仿真速度可能下降几倍甚至十几倍。解决思路有几个第一精确控制dump范围。不要用$fsdbDumpvars(0, top)这种全量dump而是只dump你关心的模块和信号。第二使用$fsdbDumpoff和$fsdbDumpon控制dump的时间窗口只在关键时间段开启dump。第三使用$fsdbDumpvars的mda选项来dump memory数组。默认情况下memory数组不会被dump如果你需要看memory的内容需要显式加上这个选项。但要注意dump memory会让文件大小急剧增加只在必要时使用。第四考虑使用VCS的-fsdb选项来启用FSDB压缩。某些VCS版本支持在编译时加上-fsdb选项让生成的fsdb文件自动压缩可以减小文件大小。4. 进阶技巧让FSDB dump更高效4.1 按层次精确dump$fsdbDumpvars的深度参数详解$fsdbDumpvars的深度参数是控制dump范围最直接的手段。但很多人对它的理解不够准确导致要么dump太多要么dump太少。深度参数的含义是这样的0表示无限深度会递归dump指定模块下的所有层次1表示只dump指定模块这一层不包括子模块2表示dump指定模块及其直接子模块以此类推。举个例子假设你的设计层次是这样的top ├── u_cpu │ ├── u_alu │ └── u_regfile └── u_mem └── u_sram如果你写$fsdbDumpvars(1, top.u_cpu)那么fsdb里会有top.u_cpu这一层的信号但不会有top.u_cpu.u_alu和top.u_cpu.u_regfile的信号。如果你写$fsdbDumpvars(2, top.u_cpu)那么fsdb里会有top.u_cpu和它的直接子模块top.u_cpu.u_alu、top.u_cpu.u_regfile的信号但不会有更深层次的信号。如果你写$fsdbDumpvars(0, top.u_cpu)那么top.u_cpu下的所有层次都会被dump。实际使用中我通常会在调试初期用0深度dump整个DUT快速定位问题所在的大致范围。一旦定位到某个子模块就改成只dump那个子模块减小文件大小加快仿真速度。4.2 动态控制dump$fsdbDumpoff和$fsdbDumpon的实战用法$fsdbDumpoff和$fsdbDumpon是一对开关用来在仿真过程中动态开启和关闭dump。这对大规模仿真特别有用因为很多时候你只关心仿真中的某一段时间窗口。比如你在做一个启动流程的验证系统上电后前1000个周期是初始化阶段你不太关心1000到5000周期是关键的配置阶段你需要仔细看波形5000周期之后是正常运行阶段你只需要偶尔抽查。那么你可以这样写initial begin $fsdbDumpfile(wave.fsdb); $fsdbDumpvars(0, top.u_dut); $fsdbDumpoff; // 等待初始化完成 wait(top.u_dut.init_done 1b1); $fsdbDumpon; // 配置阶段结束后关闭dump wait(top.u_dut.cfg_done 1b1); $fsdbDumpoff; end这样fsdb文件里只会记录配置阶段的波形文件大小可能只有全量dump的几分之一。需要注意的是$fsdbDumpoff和$fsdbDumpon必须成对使用而且不能在同一个时间步内同时调用。另外$fsdbDumpoff之后之前已经dump的信号值仍然保留在fsdb文件里只是不再记录新的变化。4.3 多文件dump按模块拆分fsdb在某些场景下你可能希望把不同模块的波形dump到不同的fsdb文件里。比如你有一个SoC设计CPU和GPU的波形你希望分开看避免一个文件太大。这时候可以用多次$fsdbDumpfile调用来切换当前的文件名initial begin // 先dump CPU的波形 $fsdbDumpfile(cpu.fsdb); $fsdbDumpvars(0, top.u_cpu); // 再dump GPU的波形 $fsdbDumpfile(gpu.fsdb); $fsdbDumpvars(0, top.u_gpu); end但要注意$fsdbDumpfile调用之后后续的$fsdbDumpvars会把信号dump到新的文件里。如果你希望两个文件同时记录需要在每次切换文件后重新调用$fsdbDumpvars。这种方式的缺点是Verdi需要同时打开多个fsdb文件才能看到完整的波形操作上稍微麻烦一些。但在某些调试场景下分开文件确实比一个巨大的文件更好管理。4.4 和Verdi的配合如何让波形和RTL代码自动关联FSDB dump的最终目的是用Verdi看波形。Verdi有一个很强大的功能叫nWave可以把波形和RTL代码关联起来你点击波形上的某个信号Verdi会自动跳转到对应的RTL代码行。这个功能依赖两个东西fsdb文件里的信号信息和KDB文件里的设计信息。要让这个功能正常工作编译时必须加-kdb选项仿真时必须正确调用$fsdbDumpvars。另外Verdi打开fsdb时需要指定KDB文件的路径。通常Verdi会自动在当前目录下查找simv.daidir/kdb如果找不到你需要手动指定verdi -ssf wave.fsdb -dbdir simv.daidir/kdb还有一个细节如果你的RTL代码在仿真之后被修改过Verdi关联到的代码可能和仿真时的不一致。所以调试阶段最好保持RTL代码不变如果必须修改建议重新编译仿真。5. 不同仿真场景下的FSDB dump策略5.1 模块级验证小规模设计的dump配置模块级验证通常设计规模不大信号数量有限这时候可以比较放心地全量dump。我一般的配置是initial begin $fsdbDumpfile(wave.fsdb); $fsdbDumpvars(0, tb.dut); $fsdbDumpvars(0, tb); end这里dump了DUT和testbench的所有信号。模块级验证的仿真时间通常不会太长fsdb文件大小也在可控范围内。全量dump的好处是你不需要提前想好要看哪些信号调试时想查什么就查什么效率更高。但即使是模块级验证如果DUT里包含大的memory模型也需要注意。memory数组默认不会被dump如果你需要看memory内容要加上mda选项。但memory dump会让文件大小增加很多建议只在确实需要时开启。5.2 系统级验证大规模SoC的dump取舍系统级验证就完全不一样了。一个SoC设计可能有几十个模块几百万门全量dump会让fsdb文件达到几十个G仿真速度下降一个数量级。这时候必须做取舍。我的策略是分层dump第一层顶层接口信号。这些信号数量不多但能反映系统的整体行为必须dump。第二层关键子模块。根据当前调试的重点选择一两个子模块进行深度dump。第三层其他模块只dump关键控制信号不dump数据通路。具体配置可能是这样的initial begin $fsdbDumpfile(wave.fsdb); // 顶层接口信号 $fsdbDumpvars(1, top); // 关键子模块深度dump $fsdbDumpvars(0, top.u_cpu); // 其他模块只dump关键信号 $fsdbDumpvars(0, top.u_dma.req, top.u_dma.ack, top.u_dma.state); end这样既能保证关键模块的调试需求又能控制文件大小。5.3 回归测试如何平衡dump开销和调试需求回归测试是验证工作中最耗时的环节。一个完整的回归测试可能跑几百个case每个case都dump波形的话磁盘空间和仿真时间都受不了。我的做法是回归测试默认不dump波形只在case失败时才重新跑一遍并开启dump。具体实现可以通过plusarg来控制initial begin if ($test$plusargs(DUMP_FSDB)) begin $fsdbDumpfile(wave.fsdb); $fsdbDumpvars(0, tb.dut); end end这样在回归测试时不加DUMP_FSDB参数仿真速度最快。当某个case失败时单独跑这个case并加上DUMP_FSDB就能得到波形文件。这种方式的缺点是失败case需要重新跑一遍才能得到波形。如果仿真时间很长重新跑一遍可能也要几十分钟。所以对于特别耗时的case可以考虑在回归测试时就开启dump但只dump关键信号控制文件大小。6. 排查FSDB dump问题的通用思路6.1 从编译日志里找线索遇到FSDB dump相关的问题第一步永远是看编译日志。VCS的编译日志会告诉你哪些选项被识别了哪些库被链接了哪些系统任务被定义了。如果你在编译日志里看到类似这样的信息Warning-[LNXOSD] Linux OS deprecation ... Loading verdi PLI library...说明Verdi的PLI库已经被正确加载。如果没有任何关于Verdi或FSDB的信息那很可能是编译选项没配对。另外编译日志里如果有undefined system task的报错直接定位到报错的行号检查对应的系统任务是否拼写正确以及编译选项是否支持这个任务。6.2 用$fsdbDumpflush强制刷新有时候你会遇到这样的情况仿真还在跑你想看看当前的波形但Verdi打开fsdb文件发现里面是空的或者只有很少的数据。这是因为VCS在仿真过程中会缓存一部分波形数据定期写入磁盘而不是每个时间步都写。如果你希望立即把缓存的数据写入磁盘可以调用$fsdbDumpflush$fsdbDumpflush;这个任务会强制把当前缓存的所有波形数据刷新到fsdb文件里。在调试过程中你可以在testbench里定期调用它或者通过交互式命令触发。但要注意频繁调用$fsdbDumpflush会影响仿真性能因为它会打断仿真的正常执行流程。建议只在需要的时候调用比如仿真卡住的时候。6.3 检查磁盘空间和文件权限FSDB dump失败的一个常见原因是磁盘空间不足或文件权限问题。特别是在公司服务器上你的home目录可能有配额限制fsdb文件写到一半发现空间不够仿真就会报错。排查方法很简单在仿真之前用df -h看一下当前目录所在分区的剩余空间。如果空间紧张可以调整dump范围或者把fsdb文件写到空间更大的分区。文件权限问题通常出现在多人共用的服务器上。如果你没有当前目录的写权限VCS就无法创建fsdb文件。用ls -la检查一下目录权限确保你有写权限。6.4 版本兼容性问题VCS和Verdi的版本兼容性也是一个大坑。不同版本的VCS生成的fsdb文件格式可能略有不同用不匹配的Verdi版本打开可能会报错或者显示异常。一般来说Verdi的版本应该不低于VCS的版本。比如你用VCS 2018编译的仿真最好用Verdi 2018或更高版本打开fsdb。如果Verdi版本太低可能会提示fsdb文件格式不支持。另外-kdb选项生成的KDB文件也有版本兼容性问题。如果VCS和Verdi的版本差距太大KDB文件可能无法被正确读取导致Verdi无法关联RTL代码。提示在项目开始之前确认一下团队使用的VCS和Verdi版本尽量保持一致。如果必须混用不同版本建议先做一个小规模的测试确认fsdb文件能正常生成和打开。7. 一些容易被忽略的细节7.1 $fsdbDumpvars的返回值$fsdbDumpvars其实是有返回值的返回的是成功dump的信号数量。虽然大多数时候我们不需要关心这个返回值但在调试dump范围问题时它可以帮你确认到底有多少信号被dump了。integer dump_count; initial begin dump_count $fsdbDumpvars(0, top.u_dut); $display(Dumped %0d signals, dump_count); end如果返回值是0说明没有任何信号被dump那肯定是模块路径写错了或者编译时信号被优化掉了。7.2 信号名的大小写敏感Verilog是大小写敏感的语言$fsdbDumpvars里的信号名必须和RTL里定义的完全一致。top.u_dut.Clk和top.u_dut.clk是两个不同的信号。如果你写错了大小写VCS可能不会报错但fsdb里就是没有这个信号。这个坑在跨平台移植时特别容易踩。有些团队的代码规范要求信号名全小写有些要求驼峰命名如果你从别的项目拷贝代码过来很容易因为大小写不一致导致dump失败。7.3 generate块里的信号dump如果你的RTL里用了generate块来实例化多个模块dump这些模块里的信号时需要特别注意路径名。generate块会给每个实例生成一个带索引的路径名比如top.u_dut.gen_blk[0].u_sub。如果你不确定generate块里的信号路径可以在Verdi里先打开KDB文件查看设计的层次结构找到正确的路径名后再写$fsdbDumpvars。7.4 接口信号和struct的dumpSystemVerilog里的interface和struct类型信号在dump时可能需要特殊处理。有些VCS版本默认不会dump interface里的信号需要加上额外的选项。对于struct类型的信号Verdi通常可以展开显示每个成员的值。但如果struct里包含union或者动态数组dump可能会失败或者显示异常。这种情况下建议把需要观察的成员单独提取成独立的信号来dump。8. 写在最后的一些个人体会FSDB dump这件事说简单也简单无非就是加两行代码、配几个编译选项。但说复杂也复杂因为实际项目中遇到的情况千变万化不同版本的工具、不同规模的设计、不同的调试需求都会影响你的dump策略。我自己的经验是不要等到出了问题才去研究dump配置。在项目开始阶段就花点时间把FSDB dump的链路跑通确认编译选项、系统任务、Verdi关联都正常工作。这样后面调试的时候你才能把精力集中在设计本身的问题上而不是被工具问题分散注意力。另外dump范围的控制是一个需要不断调整的过程。刚开始可以dump得全一些方便定位问题。一旦定位到具体模块就缩小dump范围提高仿真效率。不要怕麻烦每次调整dump配置可能只需要改一行代码但节省的仿真时间可能是几十分钟甚至几个小时。还有一个很实用的技巧在testbench里加一个plusarg来控制是否开启dump这样同一套testbench既可以用于快速回归测试也可以用于详细调试。这个小小的改动在项目后期会给你带来很大的便利。最后如果你在FSDB dump上遇到了奇怪的问题先检查三件事编译选项有没有加-debug_access和-kdb环境变量VERDI_HOME有没有设置磁盘空间够不够。这三件事能解决80%以上的常见问题。剩下的20%多半是版本兼容性或者信号路径写错了耐心排查总能找到原因。
返回列表