做FPGA验证的朋友应该都有过这种经历:Vivado自带xsim跑仿真,中型设计一跑就是半小时起步,调一个波形等得人发慌。转投VCS+Verdi之后,速度确实上来了,但摆在第一关的就是“仿真库编译”——Xilinx的IP核、原语模型不提前编译成VCS能认的库,仿真根本跑不起来。网上教程零零散散,版本匹配、gcc版本、库路径、Verdi挂载,每个点都能卡住人。
这篇我把从Vivado 2022.2切换到VCS 2021.12+Verdi的完整过程整理出来,重点讲清楚仿真库编译这步怎么做、为什么这么做,以及编译完怎么跟Verdi高效联动。适合已经会用Vivado但没碰过第三方仿真器的朋友,也适合卡在库编译这一步不知道从哪下手的工程师。
1. 先搞清楚:编译仿真库到底在编译什么,解决什么问题
1.1 Vivado仿真库里都有哪些“零件”
Vivado安装目录下自带了一批Xilinx官方仿真模型,按功能大致分成几类:
- unisim:最基础的原语仿真模型,比如BUFG、IBUFDS、FDRE这些基本单元的仿真行为都在这里。做RTL仿真和门级仿真都会用到。
- unimacro:由多个原语拼成的宏单元模型,像一些常见的计数器、移位寄存器宏。
- secureip:加密IP核的仿真模型。Xilinx很多IP(比如MIG、高速收发器)的模型是加密的,编译后会生成.so共享库,仿真时由VCS动态加载。
- xpm:Xilinx Parameterized Macros,就是那套标准化的XPM原语库,做FIFO、RAM、AXI跨时钟域的时候经常用到。
这些模型源码都是Verilog/VHDL写的,理论上任何仿真器都能读。但问题是,VCS有自己的语法解析规则和数据格式,直接把一堆.v文件丢给VCS跑,性能和兼容性都不行。尤其secureip这种加密模型,必须用VCS自带的编译流程转成它认得的二进制格式。
1.2 为什么第三方仿真器必须单独编译,Vivado不能直接用
Vivado里的xsim之所以能直接仿真IP,是因为Vivado在生成IP的时候已经做了适配,仿真文件由Vivado自动组织好。但VCS不知道这些IP内部怎么组织,更不知道unisim库里的加密模型怎么链接。它需要你把Xilinx提供的所有仿真模型,用VCS的语法标准重新编译一遍,生成VCS自己的库索引文件。
打个比方:你从国外买了一批零件,说明书全是外文,VCS这个“装配工”看不懂原版说明书,你得先让人把说明书翻译成它认识的语言,再按目录归档好,它才肯开工。编译仿真库干的就是这个“翻译+归档”的活。
1.3 编译路径选择:GUI还是命令行
Vivado提供两种编译方式:
- GUI方式:打开Vivado,Tools -> Compile Simulation Libraries,弹窗里选好仿真器、器件系列、输出路径,点编译就行。
- Tcl命令行方式:在Vivado Tcl Console里执行
compile_simlib命令,或者写成脚本批量跑。
我强烈建议用命令行方式。原因有三:一是GUI方式每次都要手动点一堆选项,换一台机器又得重新点一遍;二是命令行可以写进脚本,配合Makefile一键完成;三是有经验的工程师排查问题时,第一件事就是看编译日志,命令行方式输出日志方便,也方便在CI环境里复用。GUI适合第一次跑通或者临时编译,真要长期用,脚本化才是正路。
2. 编译前必须核对的环境清单(版本匹配是最大的坑)
2.1 Vivado和VCS的版本对应关系
Vivado官方对每个版本都有第三方仿真器的支持列表,虽然不强制限制,但版本差别太大会出现奇怪的问题。比如某些Vivado版本对应VCS 2020.12-SP2,某些对应2021.12-SP1。官方文档ug973和ug900里都有明确的兼容矩阵,编译之前先翻一下。
我自己的组合是Vivado 2022.2 + VCS 2021.12-SP2 + Verdi 2021.12-SP2,跑了大半年没出过版本相关的幺蛾子。如果你现在要新装环境,建议优先选Vivado版本发布时“当季”的VCS版本,而不是最新版——很多新版本VCS反而会因为太新,还没进Vivado的支持列表,编译库时触发一些意料之外的报错。
2.2 gcc/g++版本:一个被忽视的硬门槛
这是整个编译过程里最容易踩的坑。VCS在编译C语言相关的工程(也包括Vivado编译secureip加密模型)时会调用gcc,而且对gcc版本有严格限制。我遇到过最典型的情况是:VCS 2021.12要求gcc 6.3或7.3,但系统装的是gcc 9,结果编译secureip时报了一堆“unsupported gcc version”或者更隐晦的模板报错。
解决办法有这么几种:
- 查看VCS安装目录下的
doc或者etc目录,找到它宣称支持的gcc版本列表。 - 安装VCS时,通常会附带一个匹配版本的gcc,路径一般在
/opt/synopsys/vcs/<version>/bin/或者它依赖的第三方工具链里。 - 用
update-alternatives临时切换gcc版本,编译完再切回去。
注意:不要只在命令行里切换gcc,还要确认
g++的版本同步切换,很多编译脚本里面同时调用了gcc和g++。
2.3 环境变量和License配置
编译之前,先把这些环境变量在~/.bashrc或~/.cshrc里配置好:
export VCS_HOME=/opt/synopsys/vcs/R-2021.12-SP2 export VERDI_HOME=/opt/synopsys/verdi/R-2021.12-SP2 export PATH=$VCS_HOME/bin:$VERDI_HOME/bin:$PATH export LM_LICENSE_FILE=27000@license-server export LD_LIBRARY_PATH=$VCS_HOME/lib:$VERDI_HOME/share/PLI/VCS/LINUX64:$LD_LIBRARY_PATHLM_LICENSE_FILE这个必须确认指向的是有效可用的License Server,不然编译到一半报license check failed,重新来非常浪费时间。另外,VCS和Verdi的License可能是分开的feature,用lmstat检查一下是否都有授权。
2.4 磁盘空间与目录规划
编译仿真库会产生大量文件。如果只编译一个器件系列的一种语言,大概需要几个GB;如果所有系列、Verilog和VHDL全勾上,轻松突破几十GB。
建议目录规划成这样:
/home/user/eda/xilinx_vcs_lib/ ├── artix7/ ├── kintex7/ ├── zynq/ └── ...把目标器件系列单独拆开,方便后续增量编译,也避免误删。同时,这个目录最好放固定路径,因为后续VCS仿真时要反复引用它,路径太长太深会让人抓狂。
3. 实操:用compile_simlib完成VCS仿真库编译
3.1 GUI方式,第一次跑通用这个最快
打开Vivado,Tools -> Compile Simulation Libraries,弹窗里按顺序设置:
- Simulator:选“VCS MX”或者“VCS”,如果你的设计里含VHDL文件,选VCS MX更稳妥。
- Output location:填目标目录,比如
/home/user/eda/xilinx_vcs_lib。 - Language:建议选“All”,反正编译很快。
- Device family:只勾你要用的器件系列,比如Zynq-7000或Artix-7,别全选。
点Compile,然后就是漫长的等待。日志会一行行刷,如果中间变红,大概率就是2.2节说的gcc版本问题,或者License没生效。
GUI方式的优点是省脑,缺点是每次换项目换器件系列都得重新点一遍。所以我建议GUI只用于第一次验证环境,验证完马上转命令行。
3.2 命令行方式,进阶必备
在Vivado Tcl Console里执行:
compile_simlib \ -simulator vcs_mx \ -family artix7 \ -language all \ -dir /home/user/eda/xilinx_vcs_lib \ -jobs 8 \ -verbose参数含义如下:
-simulator vcs_mx:指定目标仿真器,vcs_mx是混合语言版本,既能编Verilog也能编VHDL。纯Verilog可以只用vcs,但没必要省这个事。-family artix7:编译目标器件系列。这里可以传多个值,比如-family artix7 kintex7 zynq,用空格隔开。-language all:编译所有语言。-dir:输出根目录。-jobs 8:并行编译任务数。核多就开大点,8到16都行,但注意别把机器I/O跑死。-verbose:输出详细日志,排查问题时必加。
跑完之后,建议用tee把日志留一份:
vivado -mode batch -source compile_lib.tcl | tee compile_lib.log这样即使中间断了,也能从日志里看到具体卡在哪一步。
3.3 编译产物该怎么看
编译完成后,到输出目录下看看,结构大致是这样的:
xilinx_vcs_lib/ ├── artix7/ │ ├── verilog/ │ │ ├── unisim/ │ │ ├── unimacro/ │ │ ├── secureip/ │ │ └── xpm/ │ └── vhdl/ │ ├── unisim/ │ └── unimacro/其中secureip目录下会有编译好的.so文件,这是加密模型被VCS编译后的共享库。后续用VCS仿真时,它自己会去动态加载。
有几点经验供参考:
- 不同器件系列的secureip内容不一样,编译时根据实际用的芯片型号选择,可以省不少时间。
- 如果以后用Vivado新版本升级了IP核,建议重新编译一次仿真库,不然仿真时可能出现IP模型和源文件对不上的诡异问题。
- 输出目录可以重复编译,建议在命令里加
-force或者先清空旧目录,避免残留旧文件干扰。
3.4 常用参数速查表
| 参数 | 说明 | 建议值 |
|---|---|---|
| -simulator | 目标仿真器 | vcs_mx |
| -family | 器件系列 | 只勾需要用的 |
| -language | 语言范围 | all |
| -dir | 输出目录 | 固定统一路径 |
| -jobs | 并行数 | CPU核数的一半到全部 |
| -verbose | 详细输出 | 首次编译/排障时开启 |
| -log | 日志文件路径 | 方便留档 |
4. 编译完成后:验证VCS+Verdi联合仿真流程
4.1 用一个最小工程验证库可用性
编译完仿真库,别急着做大设计,先跑一个最小验证工程,确认库真的能用。我习惯的做法是:生成一个简单的Xilinx FIFO IP核,然后用VCS跑一个只往FIFO里写数据、读数据的testbench。
在Vivado里创建IP时,Product Guide里会标明仿真文件的位置,比如:
<project>/<project>.gen/sources_1/ip/fifo_generator_0/fifo_generator_0_sim_netlist.v用VCS编译仿真时,文件列表里要包含IP的仿真模型文件,同时要从仿真库里引用unisim和secureip。一个典型的VCS编译命令长这样:
vcs -full64 -sverilog \ -debug_access+all -kdb \ -P $VERDI_HOME/share/PLI/VCS/LINUX64/novas.tab \ $VERDI_HOME/share/PLI/VCS/LINUX64/novas.a \ -y /home/user/eda/xilinx_vcs_lib/artix7/verilog/unisim +libext+.v \ -y /home/user/eda/xilinx_vcs_lib/artix7/verilog/secureip +libext+.v \ -y /home/user/eda/xilinx_vcs_lib/artix7/verilog/xpm +libext+.sv \ +define+SIMULATION +define+XILINX_SIMULATOR \ -f filelist.f \ /home/user/eda/xilinx_vcs_lib/artix7/verilog/glbl.v \ tb_top.sv注意命令里那行glbl.v,这是Vivado仿真必须的全局复位模块。很多人在这个文件上栽跟头,后面避坑章节细说。
4.2 仿真时如何让Verdi高效“接管”波形
VCS编译阶段用-debug_access+all -kdb生成KDB数据库,这能让Verdi读波形时快得多。跑仿真前,在testbench里加fsdb dump:
initial begin $fsdbDumpfile("tb.fsdb"); $fsdbDumpvars(0, tb_top); end然后跑仿真:
./simv +fsdb+auto+default跑完后用Verdi打开:
verdi -sv -f filelist.f -ssf tb.fsdb &这一步如果卡住,多半是-P参数里的novas库路径不对,或者环境变量里LD_LIBRARY_PATH没有包含Verdi的PLI目录。
4.3 编译和仿真常见报错速查表
| 报错现象 | 大概率原因 | 解决办法 |
|---|---|---|
| 找不到unisim/secureip库 | -y路径写错或库没编译 | 检查输出目录,核对路径 |
| module glbl not found | 没把glbl.v加入文件列表 | 在Vivado安装目录data/verilog/src/glbl.v找它 |
| unsupported gcc version | gcc版本和VCS不匹配 | 切换到VCS配套gcc版本 |
| License check failed | LM_LICENSE_FILE不对 | 检查license server状态 |
| secureip编译时报错 | 系统缺少libelf等依赖 | 安装对应依赖库 |
| Verdi打不开fsdb | -P库路径错或版本不符 | 核对novas.tab和novas.a路径 |
| KDB数据库生成失败 | -debug_access和-kdb选项冲突 | 统一用-debug_access+all -kdb |
4.4 Verdi界面下的几个常用操作
Verdi拿到fsdb之后,比较常用的操作:
- 看波形加信号:直接在设计树里选中信号拖到nWave窗口。
- 看状态机:Verdi能自动识别FSM,用“Get State Machine”功能一键展开状态转移图。
- 看源码联动:在nWave里点信号跳变沿,nTrace会自动跳到对应源码行,这个功能在追数据通路问题的时候极其好用。
- hierarchy树:设计层次乱了就用Shift+H重排。
这些操作看起来基础,但很多新人实际卡在“波形有了但不知道看哪里”的阶段,建议配合“查找信号的驱动/负载”功能(快捷键是Shift+D去掉f?不同的版本不一样,直接在菜单里搜Driver/Load),这样能快速定位信号的来源和去向。
5. 避坑实录:我在实际项目中踩过的几个坑
5.1 编译全系列库导致磁盘爆炸
第一次编译仿真库的时候,我在GUI界面里看到Device family默认全选就没管,结果编译了快两个小时,输出目录占了30多GB。问题是大部分器件系列我根本用不上,纯粹浪费磁盘和电费。
后来学乖了,每次编译之前先确认项目用的FPGA型号,再反查对应的family名称。比如Zynq-7020对应zynq系列,Artix-7系列对应artix7,不要全选。
5.2 后仿Memory初始化的坑
用VCS做后仿(门级仿真/时序仿真)时,经常遇到Memory初始化的问题。Xilinx的RAM/FIFO IP在门级网表里,如果初始化配置不当,后仿时所有存储单元都是X态,数据读出来全是不定值,仿真根本没法看。
解决办法有几种:
- 使用XPM的Memory IP,并且通过
MEMORY_INIT_FILE参数指定初始化文件,文件内容格式通常是.mem或.coe。 - 如果IP核生成时已经带了初始化,VCS仿真时确保对应的
$readmemh能正确读到文件路径,最好用绝对路径,别用相对路径。 - 在VCS运行选项中加
+vcs+initreg+0或+vcs+initmem+0之类的初始值选项,让仿真器把未初始化的寄存器/内存置为0。这个命令是给通用仿真器用的,对Xilinx IP内部的BRAM不一定100%生效,但实测对大多数场景有效。 - 终极办法还是回到IP配置阶段,把初始化和复位策略都配好,不要指望仿真阶段补。
5.3 glbl.v这个特殊文件
这个文件坑过太多人了。
Vivado仿真库编译的时候,它自己会带一个glbl.v的全局限位信号生成模块,这个模块里定义了全局复位和全局时钟使能的初始化行为。用xsim仿真时Vivado自动处理了,但换到VCS上,必须手动把这个文件加进编译文件列表。
在Vivado安装目录下可以找到:
<vivado_install>/data/verilog/src/glbl.v把它加进VCS编译命令里,放在文件列表的最后,否则会报module glbl not found,仿真跑不起来。别问我怎么知道的,当年在这个问题上耗了一下午。
5.4 版本混用导致“灵异事件”
有一次同事用Vivado 2021.1编译的仿真库,配VCS 2020.12跑,结果仿真到一半,某些IP内部信号出现异常跳变,波形完全不符合预期。排查了一整天,最后发现就是仿真库版本和Vivado版本不匹配导致的IP模型行为差异。
从那以后,我的原则是:Vivado版本、VCS版本、Verdi版本,三者保持同一时期发布,至少Vivado和VCS要严格匹配。升级任何一方,就重新编译一次仿真库。虽然编译要花点时间,但相比排查诡异问题的时间,这点成本非常划算。
5.5 我个人的工作流习惯
最后分享一个我自己用起来很顺手的习惯。编译仿真库不是一次性的活,换机器、换版本、同事协作都要用。我习惯把编译过程写成一个compile_lib.tcl脚本,配合一个简单的Makefile,新环境部署时一条命令搞定:
compile: vivado -mode batch -source compile_lib.tcl | tee compile_lib.log clean: rm -rf $(LIB_DIR)/*脚本里把器件系列、输出路径、jobs数都做成变量,换项目只改变量,不用改逻辑。
另外,库编译完成后,我会把输出目录路径统一记录到一个env_setup.sh里,每次新开终端source一下,VCS和Verdi即拿即用。这套流程一开始搭建时花点功夫,但后续每次仿真都受益。
如果你也在搭建这套VCS+Verdi流程,编译仿真库是最好的第一步,把这步做扎实了,后面跑仿真、调波形都会顺很多。过程中遇到编译报错不要慌,先看日志,基本都能定位到是版本问题、环境变量问题还是路径问题——这些问题,上面基本都覆盖到了。