前阵子有个刚转行做验证的朋友问我:拿到一个新写的RTL工程,最基本的仿真该从哪里入手?我回了一句:先把VCS跑通。在数字IC验证领域,VCS基本是见面率最高的那个工具,它是Synopsys家的Verilog编译型仿真器,核心工作就是把RTL设计代码和测试平台编译成一个可执行文件,然后在服务器上把整个仿真流程跑起来,输出波形供调试定位。今天这篇是这个系列的第一篇,重点把工具定位、环境准备、编译仿真的完整流程和几个最容易踩的坑一次性讲清楚,适合刚接触数字IC验证的同学,也适合从Xcelium或者Questa转过来的工程师快速切换到VCS的工作方式。
1. 数字IC验证里VCS真正的角色:编译型仿真器解决了什么问题
1.1 一个验证用例在机器里到底经历了什么
理解VCS之前,先要搞清楚验证在数字IC开发流程里的位置。通常你手上会有一份RTL代码(可能是Verilog,也可能是SystemVerilog),这是设计团队写出来的数字逻辑;验证团队要做的事情是写一个testbench(测试平台),把激励灌进RTL里,看输出是否符合预期。这个过程中最核心的一步就是“仿真”——把RTL和testbench一起交给一个仿真工具去执行,仿真工具会按照时间轴逐条跑事件,更新信号值,最终产出一条波形文件。
VCS在这条链路里承担的就是仿真执行引擎的角色。它要做的事情从用户角度看似简单:读入源文件、编译、生成可执行文件、运行。但这个过程的底层远比表面上复杂。VCS内部有一套完整的event-driven仿真内核,它维护着一张事件队列,所有信号的变化、进程的唤醒、延时语句的触发,都被塞进这张队列里按照仿真时间顺序逐个调度。这也是为什么verilog仿真能精确模拟硬件时序行为——因为事件队列保证了“时间”是单调递增且可预测的。
1.2 编译型仿真器和解释型仿真器的本质差异
VCS最鲜明的一个标签是“编译型”。这一点我建议初学者一定要记牢,因为它直接决定了你使用VCS的习惯。
所谓编译型,是说它先把Verilog/SV源码编译成C/C++代码,再通过C编译器生成一个二进制的可执行文件(默认叫simv)。整个仿真过程分成两个阶段:编译阶段和运行阶段。和它相对的是一些解释型仿真器,直接解析HDL源码,边解释边执行,省去了编译环节,但每次运行都要重新解析一遍语法。
类比一下:编译型仿真器相当于把菜谱先翻译成一整套标准化的做菜流程,给厨师做成一条流水线,之后每次想吃到这道菜,直接启动流水线就行;解释型仿真器则像每做一次菜都要重新翻一遍菜谱现学现做。VCS之所以在工业界被大规模使用,一个核心原因就是它的编译型架构让仿真速度做到了一众工具的前列。面对几千个testcase的回归测试,这个速度差异就是实打实的时间成本。
这个区别也解释了为什么VCS的编译不能“零成本”。很多刚从ModelSim转过来的同学,会觉得每次改一行testbench都要重跑整个编译很痛苦。实际上VCS支持增量编译机制,只有内容发生变化的文件才会被重新编译,其余模块会复用上一次编译的中间产物,实际耗时远没有你想象的那么夸张。
1.3 vcs命令、simv可执行文件、Verdi三者的关系
用VCS做验证,你最少会接触到三个东西:vcs命令、simv可执行文件、Verdi调试工具。很多新人对这三者的分工摸不着头脑。
简单说:vcs是编译工具,它的输入是RTL和testbench的源码,输出是simv。simv是运行仿真的可执行文件,./simv一执行,仿真就跑起来了,生成各种输出(日志、波形等)。而Verdi是一个独立的波形调试工具,它不负责跑仿真,只负责把仿真产生的波形文件(比如fsdb)加载进来,供你查看信号时序、定位逻辑错误。
打个比方:vcs就像厨师训练,simv就是训练完的厨师本人,Verdi则是给菜品拍照点评的顾客。厨师做菜的过程你看不到内部细节没关系,但最终菜好不好吃,得靠顾客仔细看每一道程序。
2. VCS、Xcelium、QuestaSim三选一:不同场景下我为什么这么建议
2.1 三大仿真工具的江湖版图
数字IC仿真工具市场,基本被三家EDA巨头瓜分:Synopsys的VCS、Cadence的Xcelium(前身是Incisive,再往前叫NC-Verilog)、Siemens EDA的QuestaSim(前身是ModelSim)。很多学校的课程里用的是ModelSim/Questa,因为上手门槛低、license相对好拿,但到了工业界,VCS和Xcelium才是SoC和ASIC项目里的主流。
| 工具 | 厂商 | 历史沿革 | 主要特点 |
|---|---|---|---|
| VCS | Synopsys | 收购Chronologic获得技术核心 | 编译型架构,仿真速度快,与Verdi、VCS VIP等Synopsys生态紧密结合 |
| Xcelium | Cadence | 前身Incisive/NC-Verilog | 对大型设计的资源管理灵活,和Cadence的IP、验证IP配合顺滑 |
| QuestaSim/ModelSim | Siemens EDA | ModelSim是经典PC端工具 | 启动快,调试界面友好,常用于小规模设计和FPGA验证 |
这里必须强调一点:选型这件事,很多时候不是你个人能拍板的。公司里一旦定了某一条EDA工具链,整个项目的脚本、流程、VIP、回归环境都会围绕它搭建。但你仍然需要了解各家工具的差异,因为跳槽换项目、或者公司多工具并存的情况太常见了。
2.2 为什么SoC项目普遍选择VCS
从我个人的经验看,SoC级的大规模验证项目选择VCS的比例确实是最高的。原因不外乎几个方面:
第一,Synopsys的生态太完整。RTL设计可能是Synopsys的Design Compiler,验证环境里可能用VCS跑仿真、Verdi看波形、VCS VIP做协议级验证,再加上覆盖率工具VCS Coverage Metrics,整个流程从编译到分析全部一家包办,兼容性问题少很多。
第二,VCS在超大规模设计的仿真性能上有优势。它不是在所有场景下都脸最亮,但在几百万门级以上的设计中,配合多核并行编译和良好的编译选项,整体吞吐量往往比竞争对手更稳定。回归测试是验证团队消耗服务器资源的大头,VCS的成熟度让它在几十上百个case并发跑的时候不太容易把服务器搞挂。
第三,调试工具Verdi的市场占有率实在太高了。VCS和Verdi的搭配是很多公司的标准操作。你在VCS里编译时加上调试选项,仿真时导出fsdb波形,丢进Verdi里看,整个闭环顺畅无比。相比之下,如果你不用VCS,虽然照样能生成fsdb给Verdi看,但就没那么“原汁原味”的顺畅感。
2.3 Xcelium和Questa什么时候也值得拥有姓名
我说VCS是主流,但并不否认另外两家的实力。Xcelium在某些大规模验证场景里的表现实际上相当抢眼,它对内存的分配策略更细腻,有些在VCS上频繁出现内存瓶颈的测试,换到Xcelium反而能跑完。另外如果你的项目大量依赖Cadence的IP和验证IP,那Xcelium天然兼容得更好,省掉很多跨工具调试的麻烦。
QuestaSim则在小设计、FPGA验证、教学场景里依然保有不可替代的位置。它启动速度快,GUI调试界面直观,安装也轻量。刚入门验证的同学,在家里用自己的电脑装一个QuestaSim先练手完全没问题,等进了公司再上VCS也不迟。
但如果你想验证一个结论——VCS学的东西是否值得——我可以很肯定地告诉你:值。因为核心的仿真概念(编译、仿真时间、事件调度、force/release、交互调试)在三个工具里是相通的,你只需要把命令和脚本语法换成对应工具的方言即可。
3. 环境配置实操:从拿到安装包到vcs -ID通过
3.1 安装前要确定的几件事
VCS的安装本身不是高深的事,但有几个前置条件容易让人反复折腾。首先确认操作系统版本,VCS主流支持的是64位Linux环境,CentOS/RHEL和Ubuntu都有对应版本。然后确认gcc版本。VCS编译生成的中间C代码需要用系统里的gcc来编译链接,但这个gcc版本必须在VCS官方支持列表里。版本过高或过低,都会在编译阶段冒出莫名其妙的gcc相关报错。
很多公司的EDA服务器上会装多个gcc版本,管理工具用environment module来切换。如果你有这种条件,建议优先选VCS文档里推荐的版本。如果没得选,也可以在编译时手动指定编译器路径,VCS支持通过环境变量或者命令行参数来指定gcc路径,具体方法下文会提到。
3.2 一份标准的bashrc配置
拿到VCS安装包之后,把压缩包解压到某个目录(比如/home/eda/synopsys/vcs-2023.12),然后编辑你的~/.bashrc,加上下面这几行:
# VCS 环境配置 export VCS_HOME=/home/eda/synopsys/vcs-2023.12 export PATH=$VCS_HOME/bin:$PATH export SNPSLMD_LICENSE_FILE=27020@license-server这里是正版license的配置方式。SNPSLMD_LICENSE_FILE指向license服务器,IP和端口以你公司的实际配置为准。如果你用的是单机license文件,也可以写成:
export SNPSLMD_LICENSE_FILE=/home/eda/license/synopsys.dat配置完记得source ~/.bashrc让环境变量生效。
3.3 用vcs -ID确认安装是否成功
这是我最推荐的安装验证方式,没有之一。执行:
vcs -ID如果环境没问题,你会看到VCS的版本号、编译平台、发布日期、gcc版本等一系列信息。看到这些信息就代表VCS本体可以正常启动,license也拿到了。
如果报错,最常见的两类:一类是command not found,说明PATH没配置对,检查$VCS_HOME/bin是否存在;另一类是license相关的报错,提示找不到license或者feature不存在,这时候优先检查SNPSLMD_LICENSE_FILE变量是否准确、license server是否能ping通、端口是否开启。记住,先本机排查环境变量,再排查网络连通性,这个顺序能帮你少走很多弯路。
3.4 多版本VCS共存时的PATH优先级问题
不少项目的服务器上不可能只装一个VCS版本。老项目的回归脚本可能锁定在2018版本,新项目用2023版本。这时PATH里谁排前面就格外重要。
which vcs这条命令能立刻告诉你当前终端实际用的是哪个路径下的vcs。如果发现版本不对,要么调整PATH顺序,要么直接在编译脚本里用绝对路径调用,比如/home/eda/synopsys/vcs-2023.12/bin/vcs。我个人的习惯是:写项目回归脚本时永远用绝对路径,避免不同项目之间因为PATH切换导致的版本混乱。这个习惯在多人协作的验证环境里尤其有用——否则一个case在你机器上能编译过,到同事机器上报错,最后发现是两边用的VCS版本不一样,排查半天心态直接崩掉。
4. 一次完整的编译仿真流程:从简单DUT到simv运行
4.1 准备一个最小可跑的例子
环境搞定之后,我们上手做一次完整的编译仿真。我准备了一个非常简单的DUT——一个带复位的寄存器,能把输入的8位数据打一拍输出:
// dut.sv module dut ( input wire clk, input wire rst_n, input wire [7:0] din, output reg [7:0] dout ); always @(posedge clk or negedge rst_n) begin if (!rst_n) dout <= 8'b0; else dout <= din; end endmodule再写一个最简单的testbench,产生时钟和复位,给几个激励:
// testbench.sv module tb; logic clk; logic rst_n; logic [7:0] din; logic [7:0] dout; dut u_dut ( .clk (clk), .rst_n (rst_n), .din (din), .dout (dout) ); initial clk = 0; always #5 clk = ~clk; initial begin $monitor("%t: dout = %0d", $time, dout); rst_n = 0; din = 0; #20 rst_n = 1; repeat (5) begin #10 din = $random; end #20 $finish; end endmodule这个例子虽然简单,但五脏俱全:有时钟生成、有复位、有激励、有输出监控。足够用来演示VCS的核心流程。
4.2 编译命令:你第一次应该敲出的那行
在终端里进入工程目录,执行:
vcs -sverilog -debug_access+all -timescale=1ns/1ps dut.sv testbench.sv -o simv -l compile.log拆解一下这行命令的每一个参数:
-sverilog:告诉VCS按SystemVerilog语法来编译源代码。现在新写的testbench基本都会用到SV的语法,这个选项必须加。-debug_access+all:生成完整的调试信息,后面跑UCLI交互调试和用Verdi看波形都依赖它。-timescale=1ns/1ps:设置默认的时间单位和精度。如果你的每个文件里都自己写了`timescale,这个参数可以不设,但为了规范最好统一。-o simv:指定输出的可执行文件名。默认就是simv,但能指定总比不能指定好,尤其是你要同时编多个用例的时候。-l compile.log:把编译过程的日志写到compile.log里。这一步强烈建议养成习惯,因为编译信息量很大,直接刷屏根本看不过来,写进日志文件里方便随时用grep查。
编译顺利的话,logs里最后会看到类似simv generated的提示,并且当前目录下多出一个simv文件。
提示:VCS编译会在当前目录生成一堆中间文件,比如csrc、simv.daidir等。建议每个验证用例建一个独立目录,避免多个用例的中间产物混在一起互相污染。
4.3 运行仿真和查看结果
编译通过只是第一步,真正跑起来是另一句命令:
./simv -l run.log-l run.log是仿真日志文件。运行结束,你会在run.log里看到$monitor打印的dout变化。
如果调动仿真时间到某个值,可以在运行simv时加:
./simv +vcs+finish+time@1000ns -l run.log意思是在仿真时间到达1000ns时自动结束simv。这在跑长回归、只想确认前一段行为的时候非常方便。
4.4 必须记住的VCS常用编译选项速查表
刚开始用VCS,不用把帮助文档翻个底朝天,先把下面这张表里的弄熟:
| 选项 | 作用 | 使用场景 |
|---|---|---|
-sverilog | 启用SystemVerilog语法 | 几乎所有现代testbench |
+v2k | 兼容Verilog-2001语法 | 遇到老的RTL代码时 |
-timescale=1ns/1ps | 设置默认时间单位和精度 | 统一全局时间刻度 |
-debug_access+all | 完整调试信息 | 需要UCLI或Verdi调试时 |
-debug_access+pp | 轻量调试信息 | 只需要交互调试,不导波形 |
-o simv | 指定输出文件名 | 多用例并行编译时区分文件 |
-l compile.log | 编译日志文件 | 排查编译报错必备 |
-f filelist.f | 从文件列表读源文件 | 大工程管理源文件 |
+incdir+目录 | 指定include目录 | 代码里有`include时 |
+define+名字 | 定义宏 | 条件编译场景 |
-full64 | 以64位模式编译运行 | 大设计、内存需求高时 |
-j N | 并行编译任务数 | 多核服务器加速编译 |
尤其要注意-f和+incdir+这两个组合。真实工程项目里源文件数量动不动上百个,不可能每次全写命令行上,规范做法是把所有源文件路径写进一个filelist.f里,VCS加-f filelist.f读取。这里给一个filelist的样例:
# filelist.f ./rtl/dut.sv ./rtl/controller.sv ./rtl/register.sv +incdir+./rtl/include +incdir+./tb/include ./tb/testbench.sv ./tb/interface.sv注意filelist里+incdir+放在文件列表前面比较稳妥,因为后续文件用`include时必须能找到头文件。
4.5 编译报错的快速定位套路
VCS的编译报错信息量很大,第一次见到满屏的Error和Warning别慌。我的处理顺序是:
grep -n "Error" compile.log | head -20先把最前面的报错捞出来看。绝大多数情况下,真正引起失败的根因在最早的那一两个错误里,后面的报错都是因为前面失败了产生的连锁反应(比如变量没有声明,导致后面所有使用这个变量的语句全部报错)。
再把Warning过一次:
grep -n "Warning" compile.log | head -20Warning级别的问题不一定会让编译失败,但很可能埋着功能隐患。比如位宽截断、隐式网络声明之类的,尽早处理比后面在波形里发现诡异现象再回头查要省力得多。
5. 仿真运行参数与UCLI交互调试:别只会./simv
5.1 常用仿真运行参数:种子、时长、输出
./simv本身加一堆运行期参数,下面这几个我几乎每次都用:
./simv +ntb_random_seed=1024 -l run.log+ntb_random_seed用于指定随机种子。如果你的testbench里用了$random、std::randomize这些随机化手段,固定种子就能保证每次仿真跑出来的激励序列完全一致。这在复现bug时是救命级的操作——同一份代码,同事你怎么都复现不出问题,最后发现是随机种子不同导致的激励差异。
./simv +vcs+finish+time@200us -l run.log+vcs+finish+time@用来设置自动结束时间点。适用于确认某个功能模块在指定时间窗口内没有异常,不需要等整个testbench跑完。
还有一个很实用的是:
./simv -assert disable_cover -l run.log用来关掉SVA断言覆盖率统计,回归的时候能省一点资源。这个属于进阶级,但记下来没坏处。
5.2 进入UCLI交互模式:什么时候用、怎么用
交互式调试是我个人觉得VCS最被低估的一个功能。很多初学者习惯在testbench里狂打$display来定位问题,遇到复杂设计时就发现打印出来的信息又乱又全,根本看不过来。UCLI交互模式让你跑仿真时像调试C程序一样下断点、单步执行、查看信号。
先编译时加上-debug_access+pp或-debug_access+all,然后运行:
./simv -ucli -l run.log命令行会进入ucli>提示符。
最常用的几个命令:
ucli> run 100ns让仿真前进100ns。观察一下信号状态再决定下一步。
ucli> bp tb.sv:20在testbench.sv的第20行下断点。这里的行号是源文件中的行号。
ucli> run继续运行,遇到断点会停住。
ucli> scope tb.u_dut ucli> get doutscope切换当前查看的模块层次,get读出某个信号当前值。
ucli> quit退出UCLI。如果仿真还没结束,这个命令会直接终止仿真进程。
UCLI还有一个特别好用的组合:先run 50ns把仿真推进一段,然后用get看关键信号值,再继续run 10ns看变化。这比拍脑袋加$display高效得多,因为你是在仿真现场实时审视信号,而不是提前猜测什么时候该打印什么。
5.3 一个实际交互调试的小例子
接上文的testbench,假设我想看复位释放之后dout什么时候开始跟随din变化。按部就班地写$monitor也能看到,但如果信号很多、关系复杂,区别就明显了。
编译时加-debug_access+all,运行./simv -ucli,输入:
ucli> run 30ns先跑过复位释放点。然后:
ucli> scope tb.u_dut ucli> get dout看到dout当前值。接着run 10ns看时钟上升沿后的变化。这样一步步推进,把一个行为的时序细节摸得清清楚楚。
注意:没有加
-debug_access+all的simv,进UCLI以后很多信号是看不到内部值的,会报“signal not accessible”之类的错。所以编译选项和调试需求要匹配,别等进交互模式发现看不了信号再回头重新编译,浪费时间。
6. 大设计下的编译与运行经验:提速和排错的基本功
6.1 文件数量多了以后,别再手动罗列vcs参数
前面提到过用-f filelist.f管理源文件。这里再往深走一步:真实项目里,filelist通常不是手写的,而是由构建脚本(Makefile、CMake或者公司自研的flow)自动生成。你需要做的是确认几条规则:
- 排序别乱:被include的头文件路径通过
+incdir+提前声明,避免源文件解析时找不到头文件。 - 重复包含:VCS对同一个文件被重复加进编译列表是会有报错的,filelist生成脚本里最好加去重逻辑。
- 相对路径:filelist里尽量写相对路径,或者通过通配符统一指向某个目录,这样整个工程搬到新服务器、新用户目录下不用改。
我自己吃过一次亏:有段时间工程里rtl目录下被谁多放了一个同名旧版本dut模块的副本,filelist生成脚本没去重,VCS编译直接报多个模块定义冲突,查了很久才发现是文件列表问题。所以遇到奇怪的编译报错,第一反应应该是看filelist,而不是怀疑RTL代码本身。
6.2 借助增量编译和并行编译把等待时间压下来
VCS编译大设计动辄几十分钟甚至更久,等编译是所有验证工程师每天的痛。两个改善手段:
第一个,增量编译。VCS只要检测到源文件内容没有变化,就会复用上一次编译留下的中间产物,不会重新从头编一遍。所以建议你保持固定的工作目录,不要每次都清理中间文件再编译。只有确实需要干净环境时才用:
vcs -clean或者手动删掉csrc、simv.daidir这些中间产物目录。
第二个,并行编译。多核服务器上别浪费资源:
vcs -sverilog -j 8 -f filelist.f -o simv -l compile.log-j 8让VCS用8个任务并行编译,编译时间肉眼可见地下降。但注意,-j开得太大也不一定能线性加速,因为文件之间存在依赖关系,而且磁盘IO可能成为瓶颈。我实测过,8到16之间是大部分机器上的甜点区间,具体数值可以自己多试几次找到最优。
6.3 用full64和调试选项做时间和内存的平衡
VCS默认编译模式在32位地址空间限制下,大设计很容易在编译或仿真阶段爆内存,报out of memory或者进程崩溃。遇到这种情况,编译时加-full64:
vcs -full64 -sverilog -f filelist.f -o simv -l compile.log64位模式能使用大得多的地址空间,大设计的稳定性立刻上了一个台阶。代价是可执行文件体积变大,在某些老旧的32位工具链上可能还有兼容性问题。但现在的验证服务器基本都是64位系统,直接用-full64没毛病。
调试选项的选择也更讲究。-debug_access+all虽然功能最全,但它会拖慢编译速度、增大生成simv的体积,甚至让仿真运行变慢。回归测试阶段如果不需要调试,用:
vcs -sverilog -debug_access+pp -f filelist.f -o simv -l compile.log+pp只保留UCLI交互调试能力,比+all轻量不少。只有当你需要开Verdi导fsdb波形时,才重新编译成+all模式。这是工程上非常常见的分工:日常回归用轻量编译跑,定位问题用完整调试选项重新编一轮。
6.4 遇到“仿真进程死了”怎么排查
仿真跑到一半进程退出了,run.log里却没什么有用的报错,这种情况我遇到过太多次。排查顺序按下面三步走:
第一步,看系统日志。dmesg | tail -20,重点看有没有OOM(out of memory)信息。如果进程被内核杀掉,大概率是内存不够了。
第二步,检查run.log最后几行,看有没有断言失败、$fatal、空指针之类的提示。VCS仿真进程的退出码也有参考价值——比如exit code 139通常是段错误,多半和bad memory access有关。
第三步,如果怀疑是testbench逻辑导致的死循环,可以先缩短仿真时间窗口,比如用+vcs+finish+time@1ms,看能不能跑过某个边界点。如果缩短时间后正常,用二分法继续缩小范围,很快能锁定到具体是哪个时间窗口触发的问题。
7. 给Verdi留好接口:fsdb波形导出的第一行代码
7.1 为什么要用VCS跑仿真、Verdi看波形
VCS和Verdi是验证调试的黄金搭档。VCS负责把仿真跑完,但纯看文本日志定位复杂逻辑问题效率太低——你需要看到信号随时间翻转的波形,需要追踪状态机的跳转路径,需要比较某两个信号之间的时序关系。Verdi就是干这个的。
VCS原生支持VPD格式的波形($vcdpluson),但验证工程师用Verdi时,默认更常导出fsdb格式。fsdb是Verdi高效读取的波形格式,加载速度快、占用内存相对友好。VCS并不直接内置fsdb导出能力,需要通过Synopsys的fsdb dump相关库,在testbench里调用专门的系统任务来完成。
7.2 testbench里加两行代码导出fsdb
在testbench里加上:
initial begin $fsdbDumpfile("tb.fsdb"); $fsdbDumpvars(0, tb); end$fsdbDumpfile指定导出的波形文件名,$fsdbDumpvars指定导出哪些层次。第一个参数0表示导出整个tb层次以下的所有信号;如果只想导出某个子模块,可以写$fsdbDumpvars(0, tb.u_dut)。
有个细节要注意:$fsdbDumpfile和$fsdbDumpvars的调用时机。如果你不想记录仿真一开始的波形(因为上电复位部分对某个问题没用),可以在复位结束之后再用$fsdbDumpvars开启记录。不过初学者建议从仿真一开始就全部记录,等后面工程规模大了再考虑选择性记录,能省不少磁盘空间。
编译时记得用:
vcs -sverilog -debug_access+all -f filelist.f -o simv -l compile.log跑完仿真,目录下会多出一个tb.fsdb文件。这个文件的体积可能会很夸张,几十GB也不意外,所以磁盘空间管理也是验证工程师的必修课——大仿真前先df -h看一眼磁盘余量。
7.3 Verdi打开波形的一行命令
拿到fsdb之后,启动Verdi:
verdi -f filelist.f -ssf tb.fsdb &-f filelist.f让Verdi加载源文件,-ssf指定加载fsdb波形文件。打开后你会看到源文件列表、结构树窗口和波形窗口。添加信号的方式很简单,在结构树里找到dut,右键add to waveform。
Verdi里的详细操作(看状态机、追踪逻辑cone、波形对比、批量加信号)属于进阶内容,我会放到这个系列后面几篇细讲。今天先把“能导出波形、能打开波形”这一步趟平,已经是从“只会看日志”到“会看波形”的关键跨越了。
我自己第一次用VCS和Verdi联调时犯过一个低级错误:testbench里只写了$fsdbDumpfile,忘了写$fsdbDumpvars,仿真跑完生成的fsdb文件是0字节。当时盯着Verdi空荡荡的波形窗口发呆了好几分钟,还以为是编译选项的问题。这个便宜教训后来被我写进了团队的验证checklist里——确认fsdb文件大小非零,再去开Verdi。看波形之前先确认数据真的录进去了,这比什么快捷键技巧都实在。