首页 / 资讯中心 / 文章详情

用Makefile高效管理VCS+Verdi仿真流程的完整指南

用Makefile高效管理VCS+Verdi仿真流程的完整指南 ★ FEATURED ARTICLE
做数字IC验证的同学大概率都经历过这样的一幕为了跑一次仿真在终端里敲出一长串VCS编译命令几十个文件路径、一堆编译选项手一抖漏掉一个-debug_accessall波形里全是unknown又得重新敲一遍。这种重复劳动浪费的不仅是时间还特别容易出错。用Makefile来管理VCS和Verdi的仿真流程是我认为投入产出比极高的一次改造。编译命令、仿真参数、波形加载逻辑全部固化到一个文件里跑回归只需一条make命令看波形只需make verdi新人接手项目也不需要翻文档找半天。这篇文章就把我在工程里实际用的这套Makefile方案拆开讲清楚从整体设计思路到每一条命令的含义再到完整跑通一次仿真的实操步骤和排坑实录希望能给同样在VCS/Verdi仿真上花过时间的同学一些参考。1. 为什么要把VCS和Verdi的启动流程交给Makefile1.1 手敲仿真命令的三个痛点先说说我在刚开始做验证那会儿的状态。那时候跑仿真基本是复制粘贴大法从某个旧测试用例的脚本里复制一条vcs命令改改文件列表回车等编译再跑simv跑完再用verdi打开波形。这个流程有三个问题特别明显第一个是命令太长、太容易出错。VCS的编译命令动辄一两百个字符涉及-I包含路径、-y库目录、incdir头文件路径、-f文件列表、-debug_access、-timescale、-assert等一堆选项。手敲或者复制时漏掉一个选项轻则编译报错重则编译过了但仿真结果不对排查起来非常痛苦。第二个是增量编译没有真正落地。VCS本身支持增量编译但前提是编译参数和文件列表没变。如果靠人肉记忆今天在这个测试用例里加了-include明天在另一个用例里忘了VCS会重新编译整个设计一次全量编译在稍微大一点的项目里要跑十几分钟到半小时时间就白白浪费了。第三个是团队协作成本高。每个人跑仿真的习惯不一样有人用VCS的交互模式有人喜欢先编译再跑simv还有人习惯直接把波形打开。命令不统一队友之间的命令分享和问题复现都很费劲。我见过最夸张的情况是同事仿出来的波形是VPD格式我这边Verdi配置读的是FSDB格式两个人的结果对不上定位了一个小时才发现是工具用法的差异。1.2 Makefile给仿真流程带来什么Makefile本质上是一个基于目标-依赖关系的构建调度器。它最擅长的就是两件事一是判断哪些目标过期了需要重新构建二是把复杂的命令序列封装成简单可靠的接口。在VCS仿真这个场景里Makefile能把上面的问题一次性解决掉命令标准化编译、仿真、看波形、清理每个动作都对应一个明确的make目标想干什么就make什么增量编译自然生效文件列表和编译选项作为依赖的一部分只有依赖变化时才触发全量重编仿真可执行文件本身没变就直接复用省掉大量重复编译时间参数可配置用变量抽出文件名、选项、路径换测试用例、换设计版本只需要改变量脚本本身不用动团队共享一个Makefile放进工程仓库里所有人都用同一套流程新人上来跑一次make就能干活。用生活化的类比来说手敲命令就像每次出门前都临时整理行李箱Makefile就是固定的打包清单和行李架虽然第一次配置花点时间但以后每次出门都是按清单取物省心得多。1.3 整体方案长什么样我用的这套方案的完整工作流分三段编译阶段make comp把RTL文件、测试平台文件、仿真选项交给VCS产出可执行的simv仿真阶段make sim运行simv并生成FSDB格式波形文件同时打印仿真日志调试阶段make verdi用Verdi打开FSDB波形和设计源代码进行信号分析和调试。每一条命令都不长背后做的事却是一整套经过设计的流程。下面我先把Makefile的必备基础过一遍再逐行拆解可以直接使用的模板。2. 环境准备与Makefile基础速览2.1 你需要准备好的东西开始写Makefile之前确认环境里有以下几样东西VCS仿真工具负责RTL的编译和仿真输出simv可执行文件和FSDB波形Verdi波形调试工具负责打开波形、分析信号、追踪逻辑GNU Make工具Linux环境下一般自带用make -v确认版本即可仿真工程源文件RTL设计文件.v/.sv、测试平台文件testbench、文件列表.f文件以及一个存放仿真输出的run目录。这里特别提一下License的问题。VCS和Verdi都需要有效的License环境启动前检查一下License是否正常。我在实际操作中遇到过几次VCS启动报错但License其实没掉的情况多半是环境变量如VCS_HOME、PATH、LD_LIBRARY_PATH没有正确source后面排坑部分会详细讲。2.2 Makefile的核心机制Makefile的基本结构是三要素目标target、依赖prerequisites和命令recipe。target: prerequisites recipe commandmake在执行时会先检查依赖是否存在再判断依赖是否比目标新。如果目标不存在或者任何一个依赖比目标新就执行命令否则跳过。这个机制正是增量编译的基石。还要弄明白几个常用概念变量用或:赋值用$(变量名)引用用来集中管理命令选项自动变量$表示目标$^表示全部依赖$表示第一个依赖在规则里直接用能省不少事伪目标用.PHONY声明不生成文件的目标比如clean、comp、sim这些避免同名的真实文件干扰执行。对仿真场景来说最常用到的其实就这三板斧。语法不用多会表达依赖关系和命令执行就够用了。后续模板里涉及到的每一条语法我都会尽量解释清楚不太熟Makefile的同学也能跟着搭起来。2.3 一个最小可跑的Makefile雏形先用一个最简的例子感受一下# 最小VCS仿真Makefile TOOL_PATH : /eda/synopsys/vcs VCS : $(TOOL_PATH)/bin/vcs SIMV : ./simv RTL_FILES : src/tb_top.sv src/dut.sv comp: $(VCS) -sverilog -debug_accessall \ $(RTL_FILES) -o $(SIMV) sim: comp $(SIMV) -l sim.log .PHONY: comp sim这个例子把编译和仿真分成了两个目标sim依赖comp所以make sim会自动先编译再仿真。虽然简陋但整个体系已经立起来了。接下来我在这个基础之上把实际工程里用到的完整版本逐行拆开。3. 逐行拆解一版可直接用的Makefile模板3.1 变量定义区先把所有可改的东西集中起来我在实际工程里的Makefile最上面永远是变量定义区。这样后面换设计、换版本、加文件只需要改最上面几行不会动到规则本身。# 工具路径配置 VCS_HOME : /eda/synopsys/vcs VERDI_HOME : /eda/synopsys/verdi VCS : $(VCS_HOME)/bin/vcs VERDI : $(VERDI_HOME)/bin/verdi # 工程配置 TOP_MODULE : tb_top FILE_LIST : ./filelist.f TIMESCALE : 1ns/1ps DEBUG_OPTS : -debug_accessall -debug_regioncell # 波形与输出配置 RUN_DIR : ./run FSDB_NAME : $(RUN_DIR)/dump.fsdb LOG_DIR : ./logs为什么要用文件列表filelist.f而不是直接在Makefile里列出源文件原因有两个一是RTL文件多的时候Makefile本身会变得臃肿二是文件列表文件可以直接被VCS的-f选项读取和Tcl脚本、其他工具也能共享同一份文件清单避免维护多份不一致的列表。这是我在多个项目里实践出来的选择强烈建议按这个思路组织。3.2 编译目标的实现这些VCS选项到底在做什么编译目标是整个Makefile里最核心的环节。我在工程里长期使用的一版关键命令是这样comp: mkdir -p $(RUN_DIR) mkdir -p $(LOG_DIR) $(VCS) -sverilog \ -timescale$(TIMESCALE) \ -f $(FILE_LIST) \ incdir./rtl incdir./tb \ define$(SEED_DEFINE) \ -debug_accessall \ -assert svaext \ -l $(LOG_DIR)/comp.log \ -o $(RUN_DIR)/simv逐个说说这些选项的作用-sverilog告诉VCS编译SystemVerilog代码几乎所有现代验证环境都得带-timescale1ns/1ps默认时间尺度设计里没写timescale的模块都用这个设置避免时间精度不一致导致的仿真时序问题-f filelist.f指定文件列表列表里按行写源文件路径VCS会按顺序编译这些文件incdir路径添加include目录代码里include头文件时按这个路径搜索define宏定义编译宏可以用来切换测试模式、选择约束我常用它来区分不同seed或配置-debug_accessall打开VCS的调试访问权限后续Verdi才能读出完整的信号状态和寄存器数据-assert svaext打开SystemVerilog断言SVA编译支持验证环境里有断言就必带-l 日志把编译输出写到指定日志文件排查问题全靠它-o 输出名指定仿真可执行文件的名字默认是simv我习惯改成run目录下的simv把中间产物集中管理。这里有个非常容易踩的坑-debug_accessall不能随便拿掉。很多同学为了编译速度把调试选项去掉编译确实快了但Verdi打开波形后要么看不到信号要么只能看到顶层信号内部信号全是空的。就是因为VCS编译时没有把调试信息写进可执行文件。省这么点时间不值得后面后悔的功夫更大。3.3 仿真阶段跑simv只是第一步编译完成后仿真阶段的核心是把simv跑起来同时保证三样东西能顺利产出正确的仿真日志、可用于后续调试的FSDB波形、以及明确的结束标记。sim: comp cd $(RUN_DIR) ./simv ntb_random_seed$(SEED) \ -l $(LOG_DIR)/sim_$(SEED).log很多教程会建议直接./simv不传参数。但我在实际工程里几乎都会做两件事一是通过ntb_random_seed指定随机种子让随机约束用例在回归时每条用例的种子可控可复现二是用-l把日志写到logs目录下和编译日志分开管理。这样出问题时logs目录里积累的每一次运行记录都能对照查找。那FSDB波形在哪里生成关键点在于FSDB由Verdi的dumplib机制产生VCS编译时已经通过-debug_accessall把对FSDB的支持包含进去仿真代码里还需要调用$fsdbDumpfile和$fsdbDumpvars这两个系统任务。这两个调用一般放在initial块里initial begin $fsdbDumpfile(dump.fsdb); $fsdbDumpvars(0, tb_top); end这里有个小细节$fsdbDumpfile的路径要和Makefile里FSDB_NAME变量保持一致路径不一致时Verdi那边加载波形文件就会找不到。我在多个项目里见过这种低级但特别耗费时间的错误写Makefile时务必统一。$fsdbDumpvars的第一个参数0表示把所有层级、所有信号都dump出来。如果只关心某些模块也可以写模块名缩小波形文件体积、加快仿真速度。回归测试时我经常只dump顶层快速跑完定位bug时再dump全层次波形会大很多但信息也全得多。3.4 启动Verdi把打开波形也变成一条make命令Verdi的打开命令我见过好几种写法。最简单的是直接verdi然后手动在界面上加载设计、加载波形。但项目一旦大了这种方式很浪费时间而且Verdi的GUI配置每次都要重新做一遍。我推荐的写法是把设计文件和波形文件都在命令行里传进去verdi: comp sim $(VERDI) -f $(FILE_LIST) incdir./rtl incdir./tb \ -top $(TOP_MODULE) \ -ssf $(FSDB_NAME) \ -nologo 关键选项说明-f和incdir和编译时保持一致确保Verdi能加载到同一套源代码和头文件-top指定顶层模块Verdi用它做层次结构的入口-ssf指定FSDB波形文件路径Verdi启动后直接加载波形结尾的表示后台启动GUI终端不会被占住。我在实际使用中还会在verdi命令里加-nologo参数目的是跳过Verdi启动时的logo动画省那几秒钟的等待。另外要注意verdi目标依赖comp和sim含义是只要想打开波形就确保编译和仿真都已经完成。但这里有个隐患如果编译和仿真已经执行过而make verdi却把它们又重复跑了一遍说明时间戳判断出了问题这个在排坑章节细讲。3.5 clean与其他辅助目标仿真工程跑久了run目录里堆满临时文件、中间变量和旧日志磁盘越来越满而且旧的simv和新的文件列表混在一起还会造成编译结果不可信。所以我习惯维护一个clean目标clean: rm -rf $(RUN_DIR)/* $(LOG_DIR)/*再追加一个常用的reg目标把所有测试用例以回归形式跑一遍SEEDS : 1 2 3 4 5 reg: comp for s in $(SEEDS); do \ cd $(RUN_DIR) ./simv ntb_random_seed$$s -l ../$(LOG_DIR)/reg_$$s.log || exit 1; \ done这里用shell的for循环把不同的随机种子轮询跑一遍。两条细节值得注意第一前缀是取消make默认的回显命令行为不然每次运行所有命令都会打到终端上日志会很乱第二循环体里用了$$s而不是$s因为在Makefile中$是特殊字符要传给shell的变量必须写成$$。这个坑我踩过一次之后印象特别深新手尤其容易在这里反复报错。4. 实操记录从零到跑通一次完整仿真4.1 构造一个可以跟着练的迷你工程为了让整个流程可复现我用一个超级简单的例子来做演示一个小型计数器模块外加一个简单的测试平台跑一次仿真实测。工程目录结构如下proj/ ├── Makefile ├── filelist.f ├── rtl/ │ └── counter.sv └── tb/ └── tb_counter.svcounter.sv内容是一个带load使能的同步计数器tb_counter.sv里做时钟激励、复位、load操作然后在initial块里调用$fsdbDumpfile和$fsdbDumpvars。filelist.f里写./rtl/counter.sv ./tb/tb_counter.sv我的Makefile在模板基础上做了适配TOP_MODULEtb_counter不额外加defineSIM_TIME设为2us实际上用initial里的$finish控制结束。这样一个工程虽然简单但把Makefile、编译、仿真、波形生成、Verdi加载整条链路都覆盖了。4.2 编译阶段现场输出怎么看在工程根目录执行make comp终端上看到的第一屏是VCS的版本信息、编译引擎启动的信息。重点要关注的是最后几行通常是类似下面这种输出Portability check passed. Elaboration time 2.1s Making 64-bit portable simulation executable ... Chisel compilation completed successfully.看到completed successfully就说明编译过了run目录下会多出一个simv可执行文件。如果哪一行报error这时候第一件事是去logs/comp.log里搜error关键词不要只看终端屏幕因为终端有时被VCS的banner刷掉了一部分内容。编译阶段最常出现的两类error是文件路径写错、语法错误。文件路径错误在VCS输出里一般会直接打出找不到文件的完整路径照着filelist.f一行行核对就行。SystemVerilog语法错误会显示行号和错误类型同样要先从第一个error开始修因为编译器经常被第一个错误带偏后续一长串error往往都是误报。4.3 仿真阶段确认波形真的生成了make comp通过之后执行make sim。simv跑起来后会打印时间推进的进度通常每个单位时间会输出一个点号跑到$finish或$stop就停下来。结束之后第一件事去run目录确认波形文件存在$ ls -la run/ drwxr-xr-x ... simv -rw-r--r-- ... dump.fsdb看到dump.fsdb存在文件大小不为0说明FSDB波形生成成功。如果文件不存在大概率是仿真没跑到dump的系统任务就退出了或者在tb代码里根本没有调用$fsdbDumpvars。$finish放在太靠前的位置也会导致波形没来得及写盘我把这个检查点放在波形缺失排查的最前面因为它的发生率真的不低。日志方面sim.log里如果看到类似Simulation finished的信息并且没有任何Error或Fatal基本可以认定这一轮的仿真结果是可信的。退出码方面simv正常结束时exit code一般是0如果是非0务必去日志末尾看有没有Fatal或UVM_ERROR。4.4 用Verdi打开波形一个完整的排查现场仿真跑完、dump.fsdb就位后执行make verdi。Verdi的GUI启动后你会看到源代码窗口、层次树、波形窗口三个主要区域。层次树里点开tb_counter能找到counter实例在源代码窗口选中某个信号右键选择添加信号到波形窗口就能在波形窗口看到时序变化。在真实项目里我拿到一个失败用例后的排查路径一般是这样的先看FSDB里被检查信号的实际波形和期望值做对照波形异常时往上游信号一层层回推把相关模块的中间信号都拉进波形窗口确认是哪个时间点逻辑开始偏差再回到RTL源码里打点检查状态机、计数器或组合逻辑。Verdi还有两个我特别依赖的功能一个是波形窗口里的信号值搜索功能在超长仿真时间里快速定位特定数值出现的时间点另一个是层次树的只看关键信号过滤可以显著减少波形窗口里无关信号的干扰。这些功能用熟之后定位问题的效率比纯看日志要高一到两个数量级。5. 我踩过的坑与排查技巧实录5.1 常见报错速查表我把这几年在MakefileVCSVerdi组合下遇到的高频问题整理成一张表按症状、常见原因、解决办法三列排布症状常见原因解决办法make: vcs: command not foundPATH环境变量没配置或工具路径写错source环境脚本核对VCS_HOME和PATH编译报错Unknown option选项顺序或写法不对或者用了老版本不支持的选项对照VCS版本文档查选项同团队统一版本仿真日志全空simv退出太快$finish写在initial最前头检查tb代码结构把dump和finish的顺序理顺FSDB文件不存在tb里漏掉$fsdbDumpfile / $fsdbDumpvars在initial块补上系统任务并确认路径一致Verdi打开信号全是X/Z编译时没加-debug_accessall编译目标补上调试选项重新编译再仿真make sim总是重复编译依赖关系少写了或者clean不彻底确认sim依赖comp检查simv和源文件时间戳比较逻辑VCS报License expiredLicense服务状态不稳或环境变量指向错误自查License环境确认服务状态正常这个表里的问题覆盖了我遇到的九成情况。重要的是每一种问题都对应着一个明确的操作动作排查时按表格一步步走基本都能快速定位。5.2 增量编译不生效的排查时间戳和文件列表Makefile构建系统底层的判断逻辑是时间戳如果目标文件例如simv比所有依赖都新make就认为不需要重新编译否则就触发重编。所以增量编译不生效原因集中在几个方向依赖列表没把真正的源文件写全filelist.f里少了某个文件改了这个文件但make不知道要重编clean目标太激进每次make comp之前先clean等于放弃了增量能力跑回归时间全花在编译上编译选项改变了但make没有感知比如你改了-timescale但目标文件simv的时间戳没变make不会自动重编。我处理这类问题的经验是不需要把清理做得很勤反而是要让Makefile的依赖关系尽量完整。把filelist.f里所有文件都作为comp目标的依赖用$(shell cat filelist.f)动态获取文件列表再拼到依赖里这样VCS编译选项变了、文件列表变了make都能感知到。增量编译能带来的提速非常可观值得专门花点时间调好。5.3 波形文件打不开或内容不对的处理FSDB波形是调试环节的生命线波形出问题整个调试就断了。我遇到过的波形问题主要有三个场景场景一Verdi提示找不到FSDB文件。这种多半是路径不一致Verdi里-ssf传的参数和仿真时实际生成的路径对不上。解决方法是把FSDB_NAME定义成Makefile和tb代码都能引用的同一个相对路径不要让两边各自维护一套路径。场景二能打开但波形没有数据或者全是X态。通常是编译时调试选项不全或者$fsdbDumpvars第一个参数写了0但后面跟的模块名写错导致dump范围超出了实际层次。解决办法是核对tb里的dump调用和RTL层次是否一致必要时把参数改成0全量dump确认波形正常后再缩小范围。场景三Verdi启动后没有加载任何源码。这通常是因为-f文件列表或incdir的路径没找对。Verdi和VCS虽然是同一家公司工具但加载代码的解析逻辑不完全一样有的路径在VCS下能用到Verdi这就不认了。我的习惯是给Verdi和VCS传一模一样的文件列表和include路径不让两边各自维护一份差异自然会消失。5.4 让回归更省力的小改进仿真规模一大单条make命令已经不够用我后来做了几个小改进效果非常显著第一个是并行跑多seed。用make -j 4 reg这种并行方式一个目标里的多个seed用例可以用多个核同时跑前提是Makefile里每个seed的输出文件不能互相覆盖。我习惯把日志、波形都按seed编号命名这样不仅并行安全事后排查也能直接对应到具体seed。第二个是给Makefile加usage目标。运行make不带参数时打印所有可用目标每个目标一行注释效果相当于内置了命令手册。团队里新人上手快自己隔两个月再回来也不会忘。第三个是把Verdi打开后加载的配置比如常用信号的显示分组存成rc文件在verdi命令里加-rc选项加载。这样打开波形时常见的关注信号都已经按分组排好省下大量重复操作。这些改进单独看都是小事情合在一起的效果是整个仿真调试过程中最耗时的等待编译重复配置被压到最低留出更多时间真正去分析设计问题。我在实际使用中最深的体会是脚本的真正价值不在于逻辑多复杂而在于它能让团队里每个人都用同样的方式处理同样的事让跑仿真这件事从需要记忆和口口相授的琐碎操作变成一条条所有人都能闭眼执行的标准动作。每当看到新同事用make comp、make sim、make verdi跑通第一轮仿真的时候我都会想这几条短命令背后省下的时间真的够写好几篇文章了。
阅读完成 · 觉得有帮助?
咨询建站