ARTICLE DETAIL

资讯详情

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

VCS运行选项全解析:编译仿真、UVM调试与Verdi联合验证实战

VCS运行选项全解析:编译仿真、UVM调试与Verdi联合验证实战 做数字IC验证的人几乎天天和VCS打交道。跑一条用例命令行短则七八个选项长则二三十个选项一旦换环境、换项目、换回归脚本最先出问题的往往不是RTL代码而是这些看着不起眼的运行选项。很多人习惯从老脚本里复制一组参数跑通了就再也不动直到某天波形没出来、随机种子复现不了、覆盖率漏了一大片才发现自己根本没搞懂每个选项到底在干什么。这篇东西我把VCS运行选项按用途重新拆了一遍结合Verdi联合仿真、UVM跑测、覆盖率收集这些实际场景整理出一份可以直接抄的选项说明和排错清单希望能帮你少走点弯路。1. 先把VCS的选项体系捋清楚1.1 编译和仿真两个阶段各管各的VCS是典型的两步式仿真工具先用vcs命令完成分析、elaboration生成simv可执行文件再执行simv跑真正的仿真。很多人把-sverilog、-timescale、-debug_accessall写在编译命令里把UVM_TESTNAME、ntb_random_seed、-l、-cm写在仿真命令里这个习惯是对的。如果把选项放错位置轻则VCS不认打一行WARNING继续跑重则直接报option ignored或者识别成未知参数导致整个命令退出。有个容易混淆的点是plusarg。这类“加号开头的参数”既可能被VCS仿真器本身消费也有可能只是透传给testbench里的$value$plusargs或UVM的run_test()。举例来说vcsfinish200us是VCS自己认的用来控制仿真结束时间而UVM_TESTNAMExxx其实不是VCS内核的功能是UVM库通过$value$plusargs读走的。所以排查问题时先分清选项归属于“仿真器”还是“验证环境”能少做很多无用功。阶段典型命令常见选项主要产物编译vcs -sverilog -debug_accessall -kdb ...-sverilog、-timescale、-debug_accessall、-kdb、-ntb_opts uvm、-Psimv、csrc、simv.daidir、KDB数据库仿真./simv UVM_TESTNAMEtest -l run.log ...UVM_TESTNAME、ntb_random_seed、vcsfinish、-l、-cm、-vcd日志、波形、覆盖率结果、返回码1.2 运行选项为什么值得单独研究编译选项解决的是“能不能编译过”的问题运行选项解决的才是“跑得对不对、快不快、能不能调”的问题。实际项目里面UVM打印级别不合适、随机种子没固定、波形dump时机不对、覆盖率没收集全这些坑几乎全出在运行阶段。更现实的一点是编译选项一般由Makefile或者回归脚本固定好一两个星期都不动一次但运行选项在调试阶段几乎天天改。今天要开 trace明天要关波形后天要固定seed复现一个随机失败用例。如果把运行选项和编译选项混在一起很容易出现“为了调一个问题把整个工程重新编译一遍”的尴尬局面。我在项目里习惯把编译选项和运行选项分开写在两个变量里例如COMP_OPTS和RUN_OPTS这样回归脚本里可以按需覆盖RUN_OPTS。后面第四部分我会给一个完整的Makefile示例可以直接参考。2. 从实际项目看VCS运行选项的六大用途2.1 控制仿真时长和结束时机跑一个大型SoC仿真一不小心就是几千万cycle不控制结束时机仿真器会一直跑下去。VCS提供了一组以vcs开头的运行时选项最常用的是vcsstop时间和vcsfinish时间。前者到时间进入交互模式适合调试后者到时间直接结束仿真适合回归。举个例子./simv vcsstop100us这条命令会让仿真在100us处停下来进入交互命令行方便你查看此刻的信号状态。如果改成vcsfinish100us仿真到点直接调用$finish退出适合无人值守的批量回归。需要特别注意的是时间单位。VCS默认按照testbench里的timescale来理解数字但也支持显式单位比如vcsfinish100us或者vcsfinish50ns。如果只用纯数字vcsfinish100000它会被解释成100000个当前时间单位不同模块timescale不一致时容易出偏差。我的建议是写单位明确、不猜、可读性好。2.2 把波形和日志稳稳落盘没有波形调试就是盲人摸象。VCS支持VCD、FSDB等波形格式。VCD通用性好但文件大、dump慢FSDB是Verdi原生格式压缩率高加载速度快做数字IC验证的基本都用它。日志方面-l run.log是最基础的运行选项它把仿真过程里的$display、$monitor、UVM打印以及VCS自身的告警全部写到文件里。如果你不写-l日志直接刷在终端一旦跑上百万cycle终端缓冲一爆前面的信息全丢想回头查什么都查不到。dump波形最简单的方式是在testbench里调用系统函数initial begin $fsdbDumpfile(top.fsdb); $fsdbDumpvars(0, tb_top, all); end$fsdbDumpvars的第一个参数0表示dump所有层级1表示只dump当前层级第二个参数指定从哪个模块开始抓all表示抓所有类型信号包括wire、reg、integer等。实际项目中如果信号太多文件会很大建议按需只dump顶层关键模块比如写成$fsdbDumpvars(0, tb_top.dut, all);这样只dump DUT的信号testbench里那些辅助变量不进去文件体积能小不少。2.3 复现随机约束仿真约束随机验证是UVM流程的核心跑十次随机每次的结果都不一样。如果某个seed跑挂了下一条命令复现不了那这个bug等于白抓。VCS运行时的关键参数是ntb_random_seed./simv ntb_random_seed2024 UVM_TESTNAMEbase_test -l run.log固定之后SystemVerilog的randomize()和UVM内部随机数都会按照这个种子产生。回归脚本里我一般用日期、用例编号、机器名拼一个seed比如20240617_01这样既能保证每个用例每天不同又能保证同一运行命令能精确复现。有个细节ntb_random_seed只是固定仿真随机种子不负责固定操作系统环境里的$system调用结果。如果你的testbench里调用了外部脚本或者使用了$fopen读取不稳定的文件依然可能影响复现。2.4 调UVM层次和打印级别UVM跑起来之后最大痛苦之一就是打印信息太多或太少。默认UVM_MEDIUM会把常规的sequence和driver信息打出来但查问题的时候往往需要更多的细节。运行选项里直接加UVM_VERBOSITYUVM_HIGH可以把打印级别抬高到HIGH看到更多report信息。如果只是某个component特别吵也可以用工厂覆盖或uvm_report_object单独控制但日常调试还是全局调级别最快。还有两个调试UVM结构的神器UVM_OBJECTION_TRACE和UVM_CONFIG_DB_TRACE。前者会把每个raise_objection、drop_objection都打印出来特别适合排查“仿真不结束卡在那里一直有objection没drop”的问题。后者会把config_db的set和get路径全部打印出来config_db盖不到、继承不到、类型不匹配一看便知。2.5 覆盖率收集覆盖率不是仿真跑完自动就有的编译时要用-cm指定收集类型运行时还要用-cm让仿真器把覆盖率数据落到指定目录。编译阶段常见写法vcs -sverilog -debug_accessall -cm linecondfsmtglbranch -f filelist.f -l compile.log运行时对应./simv -cm linecondfsmtglbranch -cm_name test_case_01 -cm_dir cov_dir -l run.log-cm_name指定本次仿真的覆盖率数据名-cm_dir指定输出目录。如果不指定-cm_nameVCS默认会生成一个名字多个用例的数据混在一起merge的时候很难看。我的习惯是每个用例一个独立名字最终统一用vcs -cm_merge合并。2.6 和Verdi联合调试验证VCS和Verdi是同一套流程里最常见的组合。编译阶段加-debug_accessall -kdb配合PLI库让Verdi能直接打开simv的数据库。运行时用$fsdbDumpfile或UCLI脚本dump FSDB波形然后丢进Verdi里看波形、追踪RTL信号、查看UVM调用关系。具体配置方法第四部分专门讲。3. 高频运行选项逐条拆解3.1 行为控制类vcsfinish、vcsstop、vcsdumpoffvcsfinish时间是回归脚本里最不能缺的选项。你可以在仿真环境里写好$finish但有时候sequence跑飞了testbench的$finish根本执行不到外部加一个总超时控制是最后一道保险。我的习惯是./simv vcsfinish500us -l run.log如果500us还没跑完强制结束。此时返回码不一定为0回归脚本里判断结果不要只看返回码还要看关键日志里有没有“TEST PASSED”之类的标志。vcsstop时间适合调试。比如你觉得问题出现在50us附近可以直接./simv vcsstop50us仿真会停在50us进入交互环境。你可以在这里用UCLI命令继续step、force信号、或者查看仿真队列。vcsdumpoff这个选项对应的是动态关闭波形dump。比如你前面100us要看波形后面纯粹为了跑完回归、不想让dump拖慢仿真可以提前用$fsdbDumpoff配合时间控制也可以在运行时用类似plusarg 控制更可控的方式是在testbench里写initial begin #100us; $fsdbDumpoff(); end这样100us之后不再dump文件大小和仿真速度都能得到改善。3.2 波形类VCD与FSDB的切换VCD虽然笨重但胜在通用Verdi、GTKWave都能看。VCS运行阶段可以用-vcd./simv -vcd top.vcd -l run.log这条命令会按照testbench中$dumpvars指定的范围生成VCD文件。如果testbench里没有调用$dumpvars-vcd也抓不到东西所以通常还是显式在testbench中写initial begin $dumpfile(top.vcd); $dumpvars(0, tb_top); endFSDB则不一样。FSDB必须通过Verdi的PLI库支持编译阶段要加-P $VERDI_HOME/share/PLI/VCS/LINUX64/novas.tab $VERDI_HOME/share/PLI/VCS/LINUX64/pli.a然后testbench里调用$fsdbDumpfile、$fsdbDumpvars。如果你看到“$fsdbDumpvarsis not defined”或者PLI加载失败的报错基本都是编译阶段没把nova PLI挂上。对于大型设计我推荐纯FSDB路线。多个用例的FSDB还可以直接丢给Verdi做功耗分析、覆盖率分析生态完整得多。3.3 随机种子与回归复现ntb_random_seed前面讲过是固定随机种子的。这里补充一个easy to miss的点如果你用的是UVMUVM_TESTNAME决定跑哪个test而同一条test命令里seed固定下来整个环境的随机化序列在同一个编译版本下是可复现的。实际回归中我的Makefile通常这样组织seedSEED ? date %s run: ./simv ntb_random_seed${SEED} UVM_TESTNAME${TEST} -l run_${TEST}_${SEED}.log这样每次回归都生成带seed的独立日志。一旦某条用例挂了日志名里就有seed重新执行同一条命令就能复现。不用重新查脚本也不用猜。多次踩坑之后你会发现这个习惯能救命的。还有一个隐藏福利如果你怀疑随机约束里有死循环可以把seed固定住然后配合vcsfinishtime设置一个短超时反复定位卡住的位置。这个方法比在代码里到处插$display高效很多。3.4 UVM控制类TESTNAME、VERBOSITY、OBJECTION_TRACEUVM验证环境的选择、打印级别和调试开关几乎都靠运行时plusarg控制。一组比较完整的调试用命令如下./simv UVM_TESTNAMEreg_sequence_test \ UVM_VERBOSITYUVM_HIGH \ UVM_OBJECTION_TRACE \ UVM_CONFIG_DB_TRACE \ ntb_random_seed1024 \ -l debug.logUVM_TESTNAME的值对应UVM环境中已经注册的test类名大小写敏感、必须和类名完全一致。常见的错误是环境里注册的是my_reg_test命令行写成了my_regtestUVM会报“FATAL: Test not found”。UVM_VERBOSITY可以设置成UVM_NONE、UVM_LOW、UVM_MEDIUM、UVM_HIGH、UVM_FULL。回归时用UVM_LOW减少日志量调试时用UVM_HIGH或UVM_FULL。有一点要注意UVM_FULL会产生海量打印跑大case前先评估磁盘空间。UVM_OBJECTION_TRACE对每个objection的raise和drop都会打印。如果仿真卡在最后不退出大概率是某个sequence忘了drop objection打开这个选项后一眼就能看到是谁。4. 实操VCS与Verdi联合仿真配置4.1 编译阶段要加的参数VCS与Verdi的联合仿真编译是最关键的一步。少了任何一个PLI相关参数后面dump FSDB都会失败。一个我经常用的编译命令长这样vcs -sverilog -full64 \ -debug_accessall \ -kdb \ -timescale1ns/1ps \ -ntb_opts uvm \ -P $VERDI_HOME/share/PLI/VCS/LINUX64/novas.tab \ $VERDI_HOME/share/PLI/VCS/LINUX64/pli.a \ -f filelist.f \ -l compile.log逐项解释一下-sverilog开启SystemVerilog语法支持。必须加否则UVM环境编译不过。-full64以64位模式编译运行。现代设计动辄几个GB不用64位根本跑不动。-debug_accessall打开所有调试访问权限Verdi读信号、看层次结构都靠它。-kdb生成Verdi直接可读的KDB数据库。有了KDBVerdi里看schematics、查信号路径都更快。-P .../novas.tab .../pli.a加载Verdi的PLI接口FSDB系统函数才能被解析。-ntb_opts uvm让VCS编译UVM库支持UVM-1.2等版本。如果你用的VCS版本和Verdi版本都很新且安装路径匹配有些流程里去掉-P也能正常dump FSDB。但从兼容性角度显式挂上PLI更稳新老版本都不会出问题。4.2 运行阶段加的参数与dump方案编译通过后testbench里dump FSDB的初始化块要写好initial begin $fsdbDumpfile(top.fsdb); $fsdbDumpvars(0, tb_top, all); end然后运行./simv UVM_TESTNAMEbase_test \ ntb_random_seed20240617 \ vcsfinish200us \ -l run.log跑完之后直接得到top.fsdb拖进Verdiverdi -f filelist.f -ssf top.fsdb 这里-ssf指定FSDB文件Verdi会打开波形窗口。如果你还希望看到UVM的层次结构和sequence关系可以加-uvmversion 1.2之类参数但最基本的打开波形是不需要的。如果你不想在testbench里写死FLAG也可以用UCLI方式动态控制dump./simv -ucli -i dump.tcldump.tcl内容类似fsdbDumpfile wave.fsdb fsdbDumpvars 0 tb_top run quit这样灵活度更高testbench代码不用频繁改特别适合调试阶段。4.3 一个可以直接抄的Makefile示例最后给一个我常用结构的Makefile把编译选项和运行选项分开方便覆盖。VCS vcs SIMV ./simv VERDI_HOME ? /tools/synopsys/verdi TEST ? base_test SEED ? 20240617 TIME ? 200us LOG ? run.log COMP_OPTS -sverilog -full64 -debug_accessall -kdb -timescale1ns/1ps COMP_OPTS -ntb_opts uvm COMP_OPTS -P $(VERDI_HOME)/share/PLI/VCS/LINUX64/novas.tab \ $(VERDI_HOME)/share/PLI/VCS/LINUX64/pli.a COMP_OPTS -f filelist.f -l compile.log RUN_OPTS UVM_TESTNAME$(TEST) RUN_OPTS ntb_random_seed$(SEED) RUN_OPTS vcsfinish$(TIME) RUN_OPTS -l $(LOG) compile: $(VCS) $(COMP_OPTS) run: compile $(SIMV) $(RUN_OPTS) debug: compile $(SIMV) $(RUN_OPTS) UVM_VERBOSITYUVM_HIGH UVM_OBJECTION_TRACE clean: rm -rf simv csrc simv.daidir *.fsdb *.log *.vcd使用方式make compile TESTreg_test make run TESTreg_test SEED12345 TIME500us make debug TESTreg_test这样改test、改seed、改时间都不用碰Makefile主体命令行覆盖变量就行。5. 常见运行问题排查与心得5.1 编译通过但仿真没有任何波形这个问题我见过太多次。第一种情况是编译阶段没加-debug_accessall导致testbench里的$fsdbDumpvars虽然被编译了但FSDB系统函数没有实际生效运行log里静悄悄没有波形信息。第二种情况是PLI没挂对Verdi的novas.tab和pli.a路径不对运行时报“Failed to load PLI library”。第三种情况是dump路径不对$fsdbDumpfile(top.fsdb)写死了相对路径而你的运行目录和编译目录不一致波形跑到了别的文件夹下面。排查顺序建议先搜运行日志里的Error、Warning、fsdb关键字。确认编译命令里有没有-debug_accessall和-P。确认当前工作目录下有没有生成FSDB文件没有就到$fsdbDumpfile指定的路径找。5.2 仿真时间到了却没退出有的同学设置了vcsfinish200us结果200us到了case还在跑第一反应是选项没生效。其实最常见的原因是时间单位不一致。testbench主timescale是1ns/1ps时vcsfinish200us表示200us如果某个模块是1ps/1ps这个数字含义就可能和你预想的不同。建议显式写单位并且用vcsstop200us先验证一下交互模式能不能正常停。还有一种情况是testbench里有多个initial块其中有个块自己执行了forever循环并且不退出而你的$finish被某些事件阻塞。vcsfinish是仿真器级的强制结束一般能打断如果打断不了检查有没有挂在UVM objecton上或者仿真器交互模式下执行finish命令。5.3 随机种子固定了还是无法复现固定ntb_random_seed后依然出现两次跑出的结果不同大概率不是VCS的问题而是环境不干净。常见的污染源有testbench里用了$system调用外部命令外部命令读取了系统时间。用了$readmemh或者$fopen读取外部文件文件内容被意外修改。多个test并行跑在同一个目录下log和临时文件互相覆盖。编译时没有清除旧的csrc和simv.daidir增量编译把过期代码带了进去。我的习惯是做一个干净的复现环境清掉所有临时目录固定seed固定编译版本然后把整个目录打包。如果这样还能复现随机差异再怀疑仿真器。5.4 覆盖率收集不全或merge失败编译时-cm和运行时-cm必须一致否则仿真器会报警并丢弃不匹配的覆盖率数据。如果想收集line、cond、fsm、tgl、branch编译和运行都要带上同样的一组。另外-cm_name不能重复重复了后面的数据会覆盖前面的。我习惯在回归脚本里用test名_seed作为-cm_name。merge失败更常见的原因是运行目录里没有test.vdb或者版本不一致。用VCS的merge命令前先确认每个-cm_dir下都存在对应的.vdb文件并且是由同一个VCS版本产生的。5.5 选项不认识、被忽略、还踩到版本差异VCS版本更新很快网上很多教程还停留在-debug、-debug_all时代新版本更推荐-debug_accessall。有的选项在不同版本里会废弃比如部分老PLI写法在新版本里要改。遇到“unrecognized option”不要慌先执行vcs -help ./simv -help看一下当前版本支持哪些选项。如果是从别人脚本里抄来的建议逐项核对尤其注意选项是放在编译段还是运行段。还有一种情况是选项名正确但license feature不支持比如某些覆盖率选项需要单独的license报错会提示license缺失这种就只能联系管理员。我在实际项目里吃过最大的亏就是把运行选项固化在脚本里从不逐项确认结果连续跑了好几周回归某一天发现所有case的FSDB都是空的原因是升级VCS版本后老PLI路径失效编译阶段-P挂了报错但脚本没有严格检查。现在无论多急我都先跑一条最小用例确认日志、波形、seed、覆盖率四样东西都正常再放全量回归。最后再分享一个小技巧每次升级工具或新接项目先用./simv -h查一下运行选项别拿两三年前的博客硬套新版本。
返回列表