
说实话我第一次在Efinity里点开仿真按钮看着自带仿真器把计数器波形跑出来时心里还挺高兴的。但马上遇到一个现实问题整个验证小组的回归环境、覆盖率脚本、波形查看习惯全部建立在ModelSim/Questa这一套工具链上。总不能我这边用Efinity自带仿真器跑通了回头交波形、跑回归的时候再手工把结果搬运过去。更别提同学、同事之间互相看不通波形文件项目一多人就乱套。所以“Efinity工程能不能交给ModelSim去仿真”几乎是每个易灵思FPGA新手绕不过去的坎。这篇文章总结的就是我在Efinity与ModelSim联仿这条路上趟出来的完整流程和脚本从仿真库编译、RTL仿真、门级时序仿真到各种奇怪的坑一次讲清楚。适合刚接触易灵思Trion/Titanium器件、正在被仿真环节卡住的人直接抄作业。1. 为什么Efinity工程要跑到ModelSim里联仿1.1 Efinity自带仿真器不是不能用而是不够用先给Efinity自带仿真器一个公道评价它真的能跑。简单工程里写个Testbench点击仿真按钮波形就能出来用来验证计数器、状态机、SPI/UART收发这些基础逻辑完全没问题。官方文档里也把自带仿真器的使用方式写得很详细新手刚上手时完全可以用它先跑通前几个工程。问题出在规模和协作上。自带仿真器对SystemVerilog/UVM这些验证方法的支持有限断言、覆盖率、随机约束基本不要想波形导出格式也相对封闭别人用ModelSim打不开你的波形文件。我见过不止一个同学在群里发截图说“我仿真没问题的你们看”结果对方在ModelSim里打开发现格式不对最后还是要回到通用仿真器上重新跑。1.2 三个场景逼着你必须联仿第一个是团队回归。公司或者实验室一旦定了ModelSim/QuestaSim作为统一仿真工具所有测试用例、回归脚本、错误日志格式都围绕这套工具写好了。你的FPGA工程如果自己另起炉灶用Efinity自带仿真器意味着验证工程师没法帮你跑用例也没法自动收集仿真结果。第二个是学校和培训场景。很多数字逻辑、EDA课程里教的仿真工具就是ModelSim课程作业要求提交ModelSim波形截图。这时候你即使工程在Efinity里能综合能布线交作业的仿真环节还是在ModelSim里做不联仿就等于提前宣告这题不会。第三个是IP复用。公司内部积累了不少BFM、AHB/AXI VIP、UVM环境全部是围绕QuestaSim验证的。Efinity自带仿真器跑不了这些只有联仿才能把FPGA设计接入公司统一的验证平台。所以我的结论很明确Efinity自带仿真器适合“验证单模块”ModelSim联仿适合“接入团队流程”。两者不是替代关系而是不同阶段的选择。2. 联仿的两条主线RTL级与门级时序仿真2.1 先把参与仿真的文件按三种角色分清楚联仿最容易懵的地方是搞不清到底要把哪些文件丢给ModelSim。我习惯把文件分成三份来看被测设计DUT你自己写的RTL代码。测试平台TB产生时钟、复位、激励检查输出。仿真库Efinity官方提供的器件原语模型和IP模型。比如你例化了易灵思的PLL、GPIO、LRAM这些模块在RTL里只是一个黑盒子真正的仿真行为在官方的仿真模型文件里。ModelSim不认识这些模型必须先编译进去。这里有一个关键认知如果你的设计纯粹是手写逻辑一个Efinity原语都没有例化那ModelSim完全不需要任何Efinity库也能仿真。但真实工程几乎必然会用到PLL做时钟用到RAM做缓存用到GPIO做引脚输入输出所以仿真库几乎是必编译项。2.2 RTL仿真和门级时序仿真完全是两码事RTL仿真跑的是你写的代码行为基于功能逻辑不包含门延时和布线延时。它的目的是验证功能对不对计数器计得对不对、状态机跳得对不对、协议时序符不符合预期。门级时序仿真则是在布局布线之后把映射后的门级网表和SDF标准延迟文件一起交给ModelSim。这时候信号跳变有时间信息能够发现组合逻辑过长导致的时序违例、竞争冒险、异步处理不完整这类RTL仿真看不见的问题。对于新手我强烈建议先把RTL仿真彻底跑通再碰门级时序仿真。原因是门级仿真多出两个变量一个是SDF反标的路径经常出错一个是大量时序检查警告刷屏容易把人吓住。文章后面给的脚本两个都覆盖了但别好高骛远一步一步来。2.3 六类文件的编译去向文件角色典型文件编译工具落库位置器件原语仿真模型trion/titanium原语.vvlogefx_primIP仿真模型pll_sim.v / ram_sim.vvlogip_simRTL设计top.v / spi_slave.vvlog/vcomwork测试平台tb_top.vvlog/vcomwork门级网表xxx_fit.vfit后生成vlogworkSDF延迟文件xxx.sdf无需编译vsim反标外部文件弄清楚这张表联仿的骨架就搭起来了后面所有脚本都是围绕这个映射关系写的。3. 环境准备阶段最容易被忽略的四个细节3.1 版本组合真的会影响成败ModelSim版本不需要和Efinity严格绑定但也不建议用太老的版本。Efinity新版里的IP仿真模型用了不少SV语法老版本ModelSim对always_ff、logic这类关键字的支持不完整编译时可能冒出莫名其妙的语法错误。我目前常用的组合是Efinity 2023.x配ModelSim DE 10.7c或者QuestaSim 10.7以上跑起来比较顺。如果手头只有老版本ModelSim编译原语库报错先别急着怀疑脚本先看看是不是版本语法兼容问题。另外一点ModelSim按版本有SE、DE、PE、OEM之分联仿用DE起步比较合适PE容量限制比较让人头疼。3.2 路径带空格或中文脚本直接废掉Efinity安装目录、工程目录、ModelSim启动目录三者的路径里都不要出现中文和空格。Windows下很常见的一个问题是把工程放在“C:\Users\张三\桌面\FPGA项目\”这种路径里ModelSim的Tcl脚本解析路径时各种转义问题编译报错找不到文件最后排查半天发现就是路径惹的祸。我自己固定用这种结构D:/work/efx_project/ rtl/ tb/ ip_sim/ sim_libs/路径里全部用正斜杠Tcl脚本里不需要转义Windows和Linux下都能跑。这个习惯帮我省了非常多时间。3.3 License和启动环境的常识ModelSim需要合法授权才能完整使用学校、公司一般都有购买授权或者学生授权别去碰来路不明的破解版本。值得注意的是ModelSim在读取License的时候对环境变量LM_LICENSE_FILE敏感如果你同时装了其他EDA工具License指定优先级容易打架表现为“打开ModelSim一闪就退出”。这种问题往往和工程没关系先检查工具自己能不能正常起来。3.4 在Efinity里先指好外部仿真器路径在Efinity工程里Project Settings下面的仿真选项可以指定外部仿真器。把vsim.exe的完整路径填进去Efinity生成仿真相关文件的时候就会按外部仿真器的流程准备。这一步不做的话后面在Efinity界面里点仿真按钮要么没有反应要么还是启动自带仿真器。不过要提醒一下Efinity自动生成的仿真脚本只是帮你起了个头实际用起来还是得自己写脚本原因在下一章讲。4. 三套联仿脚本编译库、RTL仿真、门级时序仿真4.1 第一套编译Efinity仿真库这套脚本是联仿的地基。思路是先用vlib建库再用vmap把逻辑库名和物理路径挂钩最后用vlog把原语模型和IP模型编译进去。# compile_efx_libs.tcl # 用法ModelSim命令行执行 do compile_efx_libs.tcl # 注意以下路径均为示例请按自己实际安装目录修改 set EFX_HOME D:/efinity/2023.2 ;# Efinity安装目录 set IP_SIM_DIR ./ip_sim ;# 从Efinity工程里拷贝出来的IP模型目录 set FAMILY_LIB $EFX_HOME/lib/sim/trion ;# Trion器件族Titanium请换titanium目录 # 1. 建库并映射 file mkdir sim_libs/efx_prim file mkdir sim_libs/ip_sim vlib sim_libs/efx_prim vlib sim_libs/ip_sim vmap efx_prim sim_libs/efx_prim vmap ip_sim sim_libs/ip_sim vmap work ./work # 2. 编译器件原语仿真库 # 具体文件名以实际安装目录为准可用下面命令先搜索确认 # find $EFX_HOME/lib/sim -name *.v vlog -work efx_prim $FAMILY_LIB/trion_primitives.v vlog -work efx_prim $EFX_HOME/lib/sim/common/efx_sim_utils.v # 3. 编译工程用到的IP仿真模型 vlog -work ip_sim -sv $IP_SIM_DIR/pll_sim.v vlog -work ip_sim -sv $IP_SIM_DIR/ram_sim.v # 4. 编译RTL设计和测试平台 vlog -work work ./rtl/top.v vlog -work work ./tb/tb_top.v quit -f这里最容易被坑的是找不到原语模型文件。不同Efinity版本目录结构有差异不要照抄路径正确姿势是在Efinity安装目录里搜一遍*.v把lib/sim开头的文件路径找出来。我自己习惯在命令行先执行find /d/efinity/2023.2/lib/sim -name *.v确认实际文件名后再填进脚本。IP仿真模型更省事Efinity工程里使用到的每个IP在生成时都会产生仿真模型文件直接把它们拷贝到工程目录下的ip_sim文件夹统一管理。4.2 第二套RTL仿真运行脚本库编译好之后RTL仿真脚本就很简洁了。它的核心动作只有三步编译设计文件、启动仿真、加载波形。# sim_rtl.tcl # 用法ModelSim命令行执行 do sim_rtl.tcl vlib work vmap work work # 编译RTL设计多个文件按依赖顺序排列 vlog -work work ./rtl/top.v vlog -work work ./rtl/spi_slave.v # 编译测试平台 vlog -work work ./tb/tb_top.v # 启动仿真。关键参数 -voptargsacc防止设计被过度优化导致信号不可观测 vsim -voptargsacc -L work -L efx_prim -L ip_sim work.tb_top # 添加波形 add wave -position end sim:/tb_top/* add wave -position end sim:/tb_top/dut/* run -all-voptargsacc这个参数值得专门说一下。ModelSim在vsim启动时默认会对设计做优化某些中间信号会被优化掉波形窗口里看不到想加也加不进去。新手第一次跑会以为是自己代码问题其实是仿真工具把信号“吞”了。acc的意思是保留所有信号的访问能力代价是仿真速度略微下降但调试体验提升巨大。在我这里RTL仿真阶段永远带上它。4.3 第三套门级时序仿真脚本带SDF反标门级时序仿真比RTL仿真多两个步骤编译门级网表然后反标SDF延迟文件。# sim_gate.tcl # 用法ModelSim命令行执行 do sim_gate.tcl # 前提Efinity完成布局布线后在输出目录生成门级网表和SDF文件 vlib work vmap work work # 1. 编译门级网表注意这里不编译RTL了编译的是fit后生成的网表 vlog -work work ./impl/top_fit.v # 2. 编译测试平台 vlog -work work ./tb/tb_top.v # 3. 启动仿真并反标SDF # -sdftyp /tb_top/dut./impl/top_fit.sdf 表示把SDF延迟标到tb_top下的dut例化 vsim -voptargsacc -L work -L efx_prim \ -sdftyp /tb_top/dut./impl/top_fit.sdf \ work.tb_top add wave -position end sim:/tb_top/* run -all也有人习惯在Testbench里用$sdf_annotate(top_fit.sdf, dut)方式反标。两种方式我都用过命令行-sdftyp更可靠因为它的路径相对vsim启动目录解析不容易受$sdf_annotate内部路径约定影响。如果要用$sdf_annotate一定要确认SDF文件路径相对于ModelSim工作目录的位置这是门级仿真最常见的坑之一。门级网表文件名每个Efinity版本不完全一样在工程输出目录里用find ./impl -name *.v和find ./impl -name *.sdf找到实际文件再替换。4.4 批量回归脚本把多少用例都塞进循环联仿最终要服务于回归。ModelSim支持批处理模式vsim -c不启动图形界面直接跑仿真跑完自动退出。配合shell脚本的for循环可以一口气跑完整个用例集。Windows下的批处理echo off REM run_all.bat vsim -c -do compile_efx_libs.tcl vsim -c -do sim_rtl.tcl echo ALL DONE pauseLinux下的shell脚本也正好回应了很多人问的“shell脚本for循环怎么写”#!/bin/bash # run_all.sh tests(tb_spi tb_i2c tb_uart) for tb in ${tests[]}; do echo running ${tb} vsim -c -do vlog -work work ./rtl/top.v ./tb/${tb}.v; vsim -voptargsacc work.${tb}; run -all; quit -f \ || exit 1 done echo ALL TEST PASSED关键点是-do字符串里的分号分隔命令以及quit -f保证每个用例跑完强制退出不会卡在交互界面。配合-wlf result.wlf可以把波形保存下来后面用vsim -view result.wlf单独分析失败用例。5. 高频踩坑现场从波形红线到SDF反标异常5.1 波形全红全X八成不是代码逻辑问题这是联仿里出现频率最高的问题网上搜“modelsim仿真波形是红线”能搜出一大堆。红线代表信号处于X态未知或者Z态高阻我见过的新手场景基本都能归到这几类时钟没有产生。Testbench里忘了给clk写always #5 clk~clk或者时钟只在initial块里翻了几次就停了。复位一直没有释放。复位信号持续有效整个设计一直被锁在复位状态。例化了Efinity的PLL但Testbench没有等待PLL的locked信号拉高就直接喂数据。PLL锁定是需要时间的仿真模型里locked没拉高时输出时钟是无效的。一个带PLL等待逻辑的Testbench骨架长这样timescale 1ns/1ps module tb_top; reg clk_ref; reg rst_n; wire clk_sys; wire pll_locked; initial begin clk_ref 0; forever #5 clk_ref ~clk_ref; // 100MHz参考时钟 end initial begin rst_n 0; #200 rst_n 1; // 复位释放 wait(pll_locked); // 等PLL锁定 #50; // 到这里才开始真正的激励 end dut_top dut ( .clk_ref (clk_ref), .rst_n (rst_n), .clk_sys (clk_sys), .pll_locked (pll_locked) ); endmodule排查红线问题我有一套固定顺序先看时钟有没有再看复位有没有释放然后看PLL/RAM这类IP有没有准备好最后检查例化端口有没有接反漏接。按这个顺序走下来绝大多数红线都能定位。5.2 ModelSim“一闪而过”或者点了没反应如果在Windows下双击vsim图标直接闪退先怀疑License和安装问题不是工程问题。如果在批处理里执行vsim -c -do sim_rtl.tcl马上就退出了多半是脚本里有编译错误但-do模式默认会继续往下走。建议在do文件第一行加上onerror {quit -f}这样只要第一条命令出错就立即停住不会出现“看起来跑完了其实全是编译失败”的假象。排查的时候把-do换成-do sim_rtl.tcl -debug之类交互式启动能看清楚到底错在哪一步。另外vsim启动目录非常重要。ModelSim对库路径的解析是相对启动目录的如果你双击快捷方式从“C:\Windows\System32”启动那vmap work ./work映射的路径完全不是你工程里的目录。正确做法是先cd到工程目录再启动vsim或者用批处理里先cd /d D:\work\efx_project再调vsim。5.3 SDF反标后一片报警波形还乱了门级时序仿真里SDF反标报错集中在三类第一$sdf_annotate的路径解析错误。SDF文件路径是相对当前工作目录解析的如果vsim启动目录不是工程根目录路径就错了。命令行-sdftyp方式能绕开这个坑。第二SDF文件里的单元和门级网表不匹配。Efinity版本更新后原语模型和SDF里引用的单元名可能对不上。解决办法是确认门级网表和SDF是从同一次布局布线产生的别混用。第三时序检查警告刷屏比如$setup / $hold violation。这个要冷静看待门级仿真的时序检查就是会捕捉到很多实际中可能不存在的violation特别是Testbench里的激励信号沿正好落在时钟沿附近的时候。可以先加notimingchecks跑一遍功能验证确认逻辑本身没问题再带上时序检查做最终验证。别一看到一堆rror就慌。5.4 同一个设计Efinity自带仿真器能跑ModelSim却编译不过这种情况我遇到过好几次。原因通常有两个一个是timescale缺失或单位不一致。Efinity自带仿真器对缺省timescale比较宽容ModelSim则严格按默认单位处理导致延时行为完全对不上。解决办法是在每个Verilog文件头部都写清楚timescale 1ns/1ps尤其Testbench必须写。另一个是预处理宏差异。有些Efinity的IP仿真模型靠ifdef区分不同仿真器行为自带仿真器预定义了某个宏而ModelSim没有。排查思路是看编译报错信息指向哪个ifdef分支在vlog命令后面用defineXXX补上对应宏或者反过来去掉不该有的宏。这种问题代码本身没错纯粹是仿真平台预定义环境不一样。5.5 Efinity里改完设计仿真却还在跑旧逻辑这个坑特别隐蔽。联仿环境中ModelSim编译的RTL文件和Efinity工程里的RTL文件是两份拷贝。你在Efinity里改了顶层模块、加了一个IP但如果ModelSim编译的还是旧路径下的文件仿真自然还是旧行为。我吃过一次大亏在Efinity里把一个FIFO深度从128改成512重新综合布线后直接跑ModelSim时序仿真结果波形还是128深度的行为排查了半天才发现ModelSim工程里编译的IP模型没有从Efinity工程目录同步更新。从那以后我给自己定了一条铁律改设计之后第一步永远先检查仿真文件路径第二步重新编译库和设计第三步才允许自己点运行。每次Efinity里变更IP参数都强制重新编译ip_sim库这个习惯救了我很多次。5.6 编译库时出现奇怪的语法错误如果你用的ModelSim版本偏老编译Efinity新版本IP模型时可能会遇到SystemVerilog语法报错。先别急着改官方文件按这个顺序排查检查是不是用vlog而不是vlog -sv编译。IP模型是SV语法时必须加-sv。检查ModelSim版本是否太旧升级到10.7c或更高版本。检查编译顺序。有些IP模型依赖原语库先编译如果原语没编好IP模型里的引用就会报错。还有一种情况是ModelSim 32位版和64位版混用Windows下装了多个版本环境变量互相干扰。干净的做法是只保留一个ModelSim版本并在PATH里显式指到它的win64目录。6. 把联仿沉淀成团队工作流的工具箱6.1 用do文件固化波形窗口告别每次都手动加信号调试时每个人都经历过这种痛苦start simulation之后一层一层展开module树手动选中几十个信号往波形窗口里拖。这个过程重复十次以上就会烦躁而且不同人加的信号还不一样交流起来麻烦。我的做法是把观察信号写进一个wave_all.do文件# wave_all.do onerror {resume} quietly set StdArithNoWarnings 1 add wave -position end sim:/tb_top/* add wave -position end sim:/tb_top/dut/* add wave -position end sim:/tb_top/dut/mem_inst/*然后每次仿真启动后只执行一句do wave_all.do所有关键信号一次性进来。团队里传这个文件大家看的波形完全一致出问题指哪打哪效率高很多。6.2 批处理跑回归GUI只看失败波形联仿环境成熟的标志之一是“跑用例不用开图形界面”。vsim -c模式下波形默认不显示但可以用-wlf参数把波形实时记录到文件里vsim -c -wlf result.wlf -do run -all; quit -f跑完之后再单独打开波形分析vsim -view result.wlf这样做的好处很明显几十个用例在后台排队跑不需要人工守着跑完只打开失败的几个波形文件定位效率高。配合add wave -r /tb_top/*把整棵模块树的信号都记录下来就算当时没加某个内部信号事后也能从wlf文件里找到。6.3 联仿环境自检清单最后把我的检查清单放出来建议每次搭新工程、换机器、换版本时都过一遍Efinity和ModelSim版本是否适配路径是否全英文无空格ModelSim能否独立正常启动License是否正常Efinity仿真库是否已编译IP模板是否更新到最新参数Testbench是否写清楚了timescale复位释放和PLL等待是否完整RTL仿真是否已跑通波形是否存在X态门级仿真是否使用了同一次布局布线产生的网表和SDF批处理回归脚本在干净目录下能否一键跑完这套流程看着繁琐其实第一次搭好后就是个模板后面每个新工程复制改改就能用。我个人实际用下来的体会是联仿最磨人的不是脚本语法而是对“两个工具各管哪一段”的理解。只要把库编译、RTL仿真、门级反射这三条主线理顺了后面所有问题都能顺着这个框架去定位。最后再分享一个小技巧每搭好一个新工程的联仿环境一定先用一个最简的计数器或者LED流水灯工程把全链路跑通一次再往里边加协议模块。这个“最小可仿真工程”的做法能帮你避开90%的联仿玄学问题。