1. 为什么我建议用安路TD + ModelSim做联合仿真
安路TD是安路科技官方的FPGA开发套件,它覆盖了从代码编写、综合、布局布线到比特流生成、下载调试的完整流程。TD配套了自带的仿真环境,但在处理大规模设计、复杂时序验证、长时间回归测试时,自带的仿真器在波形查看效率、调试手段、脚本化批量跑仿真方面,确实不如ModelSim顺手。
如果你是从Xilinx Vivado或Intel Quartus转过来的,对ModelSim(或QuestaSim)这套仿真流程应该不陌生。安路TD本身是支持导出仿真工程和仿真模型给第三方EDA工具用的,官方推荐对接的就是ModelSim SE或DE版本。TD负责“设计”,ModelSim负责“验证”,各干各擅长的活,这是这套方案最核心的思路。
这篇文章适合谁?
- 正在用安路TD做FPGA开发,想用ModelSim做功能仿真(前仿真)的工程师;
- 刚接触安路国产FPGA平台,被“TD里怎么调用ModelSim”“仿真库里到底编译哪个文件”这类问题卡住的新手;
- 想把仿真流程脚本化、自动化,减少重复点鼠标操作的效率派。
我会把从IP核配置、仿真模型导出、ModelSim工程搭建、testbench编写、波形分析到常见报错排障的完整链路过一遍。很多细节是官方文档没写全、但实际工程中一定会踩到的,我尽量把这些坑都摊开讲。
2. 环境选型与版本匹配
2.1 安路TD版本怎么选
安路TD目前主流版本有TD 4.x、TD 5.x、TD 6.x等,不同版本对芯片系列的支持范围不一样,比如早期版本的TD只支持ELF系列,后来逐步扩展到EF2、EG4、EG5等。我的建议是:
- 用开发板厂商或者安路FAE提供的最新稳定版,不要追最新版,也不要停在太老的版本;
- 新版本TD对ModelSim接口的适配更好,仿真模型导出脚本也更完善。
安装TD时注意安装路径不要带中文和空格,ModelSim也一样。Windows下建议装到D:\Tools\TD、D:\Tools\modelSim这样的路径,避免后面编译库、仿真路径解析时出现奇怪报错。我自己第一次装的时候图省事直接装在桌面,结果路径里带中文,导致ModelSim脚本里读取IP模型时一直报文件找不到,排查了一下午才反应过来是路径编码问题。
2.2 ModelSim版本怎么选
TD对ModelSim的对接方式并不复杂,本质就是通过编译脚本和仿真模型文件实现,所以ModelSim SE、DE、PE版本都可以用。工程里我更推荐ModelSim SE-64,如果你的license支持的话。
ModelSim SE-64 2020.4是一个比较稳的版本,对SystemVerilog支持、波形窗口性能、运行速度都够用。ModelSim DE 2022.2属于桌面级增强版,界面更现代化,对大工程的波形抓取和分析更友好。选哪个版本主要看:
- 你现有的license支持哪个版本;
- 是否需要同时兼容其他FPGA厂商的仿真。如果你既要弄安路,又要弄Xilinx或者Intel的工程,选一个统一的SE版本更省事。
2.3 为什么不在TD里直接仿真
很多新人会问:安路TD不是有自带的仿真器吗,为什么非要折腾ModelSim?我给你说几个实际场景:
- TD自带仿真器在波形查看功能上比较基础,做简单的组合逻辑、小规模状态机还行,一旦波形长、信号多,缩放、查找、分组标记这些操作都不够快;
- TD自带仿真器对SystemVerilog testbench的支持有限,很多验证常用的随机约束、覆盖率、断言功能都没有;
- 团队协作时,验证环境如果跑在ModelSim/QuestaSim上,FPGA工程师用TD导出仿真工程给验证工程师,大家才能在同一套工具链下工作。
所以联合仿真的本质是:TD负责“设计输入和IP配置”,ModelSim负责“行为级验证”,两边结合才能把国产FPGA的开发效率拉满。
3. IP核配置与仿真模型导出
3.1 在TD里配置IP核
安路TD的IP核配置界面和Xilinx的IP Catalog、Intel的Platform Designer不太一样,但思路一致。以最常见的PLL(锁相环)为例,操作流程是:
- 在TD的工程管理器中,选择“Tools”下的“IP Generator”(不同版本的菜单名可能不同,也有叫“Manage IP”的);
- 在目标芯片型号下,选择IP类型,比如PLL、RAM、FIFO、乘法器、DDS等;
- 填写参数:输入时钟频率、输出时钟频率、倍频/分频系数、输出相位、占空比等。注意参考芯片数据手册,确保输出频率在允许范围内;
- 点击生成,TD会在工程目录下生成IP核的例化模板、约束模板和仿真模型。
这里有个关键点:IP核在联合仿真时需要“仿真模型”。TD生成的IP核仿真模型通常是经过封装的模型文件,作用是让仿真器可以在功能层面模拟这个IP的行为,而不需要真实物理实现。比如PLL的仿真模型会模拟锁定过程、时钟输出行为、失锁标志等,这些行为对功能验证非常关键。
3.2 导出仿真模型和编译库
TD安装目录下一般带有针对ModelSim的仿真库编译脚本或预编译好的库文件。在TD中,你可以通过工程菜单里的“Simulation”相关选项,把当前设计用到的IP核、原语、simulation model一起整理到一个仿真目录。
以我常用的TD工程目录为例,典型结构是这样的:
ProjectA/ ├── src/ # RTL源码 ├── ip/ # IP核文件,含仿真模型 ├── tb/ # testbench目录 ├── con/ # 约束文件(.fdc/.sdc) └── sim/ # 仿真脚本和输出目录联合仿真前,需要找到TD分发包里针对ModelSim的库编译脚本,通常是Tcl脚本或者批处理文件,一般位于TD安装目录的sim_lib或template目录下。把安路基础的仿真库(包含逻辑单元仿真库、IO仿真库、PLL等硬核模型)在ModelSim里编译一次,生成ModelSim认识的标准库。
如果你在ModelSim里直接编译自己的RTL代码报错,报“module not found”或者“unknown identifier”,十有八九是仿真库没有编译或者仿真库文件选错了。
3.3 测试平台(testbench)怎么搭
Testbench是整个联合仿真的入口。建议单独放在tb目录,不跟RTL混在一起。一个最低限度的testbench结构包含以下部分:
- module tb顶层,例化你要验证的设计模块(DUT);
- 时钟产生:用
always #10 clk = ~clk;,注意时钟周期和IP核输入频率对应; - 复位产生:上电复位,保持若干周期后释放;
- 激励产生:使能、请求、数据输入等;
- 监测输出:用
$display、$monitor打印关键信息; - 结束控制:用
$finish结束仿真。
这里我特别想说一个新手很容易踩的坑:安路TD生成的IP核例化模板里的端口,未必和ModelSim的testbench直接兼容。尤其是带参数(parameter)的接口,或者带有仿真专用端口的IP(比如PLL的locked输出、FIFO的almost full等),在testbench例化时端口名、位宽必须与IP核例化模板完全一致。请在TD生成的例化模板文件里复制端口列表,不要手敲。
4. ModelSim工程搭建与联合仿真实操流程
4.1 建工程还是用脚本
在ModelSim里做联合仿真,有图形界面和脚本两种方式:
- 图形界面方式:File -> New -> Project,把源码加进去,编译,仿真。适合小工程和临时调试;
- 脚本方式:写.do脚本,用vlib、vlog、vsim、add wave、run命令完成全部动作。适合反复回归、多人协作、自动化验证。
我强烈建议从一开始就用脚本方式,哪怕工程小也学着用。理由有三点:
- 脚本可以重复执行,改一次上电时序,全流程重跑,不用重新点几十次鼠标;
- 脚本可以放到版本管理里,团队共享,避免“我这边仿真能过,你那边仿不过”的尴尬;
- 后续做自动化批量仿真、覆盖率跑批,都是基于脚本扩展。
4.2 标准仿真脚本流程
以下是我在实际工程中常用的一套ModelSim脚本骨架,你可以直接参考。
编译阶段脚本:
# compile_sim.tcl # 清理旧库 if {[file exists work]} { vdel -all -lib work } # 建立新库 vlib work vmap work work # 编译安路仿真库(根据实际路径调整) vlog -work work +incdir+../bsp_lib ../bsp_lib/glbl.v vlog -work work +incdir+../bsp_lib ../bsp_lib/*.v # 编译IP核仿真模型 vlog -work work +incdir+../ip/pll ../ip/pll/pll_sim.v vlog -work work +incdir+../ip/ram ../ip/ram/ram_sim.v # 编译RTL源码 vlog -work work +incdir+../src ../src/top.v vlog -work work +incdir+../src ../src/*.v # 编译testbench vlog -work work +incdir+../tb ../tb/tb_top.v仿真阶段脚本:
# run_sim.tcl vsim -voptargs=+acc work.tb_top # 添加波形 add wave -hex /tb_top/clk add wave -hex /tb_top/rst_n add wave -hex /tb_top/dut/* # 运行仿真 run 100us注意几个细节:
vsim -voptargs=+acc是为了禁止ModelSim在优化时把层次结构打平。不加这个的话,add wave的时候经常看不到内部信号;add wave -hex表示以十六进制显示,也可以用-radix unsigned、-radix decimal等;add wave /tb_top/dut/*是把DUT下面的所有信号一次性加进去,层级结构比较深的时候很好用;run 100us表示跑100微秒仿真时间。也可以用run -all,但run -all如果testbench里没有$finish,ModelSim会一直跑,建议在tb里用$finish控制结束,或配合超时保护。
4.3 时间单位和仿真精度设置
ModelSim默认的时间单位和testbench里的timescale有关。建议在testbench文件开头写清楚:
`timescale 1ns/1ps这样,一个10MHz时钟对应周期100ns,always #50 clk = ~clk;就是10MHz。如果不做timescale设置,ModelSim可能会以默认的1ns/1ns或者更奇怪的方式解释语句,导致波形时间轴和预期对不上。这个坑虽然低级,但我见过不少人卡在这里,仿真跑出来的时钟频率跟预期差几个数量级还找不到原因。
5. 波形分析与调试实践
5.1 波形分析的三个层次
仿真跑完,ModelSim的wave窗口里会显示添加的信号。实际调试中,我一般按照从整体到局部的方式推进:
- 第一层:看全局信号。时钟clk、复位rst_n、使能en、关键状态机状态信号。如果复位释放后状态机没有进入预期状态,先检查复位释放时间和时钟是否正常;
- 第二层:看总线级别的数据。比如AXI总线的读写通道握手信号、FIFO的读写计数、RAM的地址和数据。这里建议把位宽比较大的信号group成一个bus,以十六进制显示,阅读效率高很多;
- 第三层:看IP核的输出。比如PLL锁定信号、FIFO的水位指示、串口发送的串行波形等。对照IP核数据手册里的时序图,确认行为是否一致。
5.2 常用波形操作技巧
以下几个操作我几乎每次仿真都会用到:
- 光标测量:点击wave窗口工具栏的光标按钮,可以添加一个或者多个光标,直接看两个事件的间隔。测量时钟周期、信号延迟、FIFO间隔等非常方便;
- 波形搜索:Ctrl+F可以在波形里搜索指定数值或跳变沿,比如搜索某个信号从0变1的时刻,不用肉眼慢慢找;
- 信号重命名:双击信号名改成有意义的名字,比如clk_pll_out,方便截图和讨论;
- 多bit信号切换显示进制:在wave窗口右键信号,选择Radix,改成Unsigned或Hexadecimal。总线、数据信号强烈建议改成十六进制显示;
- 波形缩放:Zoom Fit的快捷键是Ctrl+Z,一键让所有波形都显示在窗口里。波形很长的时候要靠它快速总览。
5.3 从波形倒推问题
波形分析的本质是“对照设计预期找差异”。我的实操习惯是:
- 先确定一个“基准时间点”,比如系统复位释放那个时钟沿。所有问题的讨论都以这个点为基准;
- 从输出端往下追。比如串口没有正常发出数据,先看发送模块的tx信号有没有波形。如果tx一直是高,说明根本没有开始发送;再往前看发送触发信号有没有拉高;再看FIFO有没有数据读到;再看状态机卡在哪一步;
- 用
$display在关键节点打印信息,跟波形互相印证。波形告诉你“发生了什么”,$display告诉你“代码内部执行到了哪里”,两者对照,定位速度翻倍。
6. 常见报错与解决方案实录
6.1 报错速查表
我把这些年安路TD + ModelSim联合仿真过程中常遇到的报错整理成了表格,基本覆盖了90%的入门问题:
| 报错现象 | 可能原因 | 解决方案 |
|---|---|---|
| ModelSim无法启动,提示license问题 | license配置错误或过期 | 检查环境变量LM_LICENSE_FILE、license.dat路径,确认license主机ID匹配 |
| vlog编译时报“Cannot find design unit” | 仿真库未编译或库路径错误 | 先在ModelSim里编译安路仿真库,再用vmap指定库名 |
| vsim时报“Unknown identifier”或“No such object” | testbench例化端口名与DUT端口不一致 | 从TD生成的例化模板复制端口列表,逐位核对 |
| 仿真波形全是红X或高阻Z | 信号未驱动、复位一直有效、IP核模型未加载 | 检查testbench激励、复位释放时序、IP仿真模型是否编译成功 |
| 波形有clk但所有信号都是X | 初值未初始化或复位时序不对 | testbench里给寄存器提供初始值,确保复位释放时间足够 |
| 编译IP核仿真模型报语法错误 | 部分仿真模型需要SystemVerilog编译 | 用vlog -sv编译对应文件 |
| 仿真时间无穷无尽,run -all不结束 | testbench没有$finish | 在合适位置加$finish或超时保护 |
| add wave看不到内部信号 | vsim没有加+acc,层次被优化 | vsim命令加-voptargs=+acc |
| 波形里带转义字符的信号名查询不到 | 转义信号名问题 | 用{/tb_top/dut/bus_name[7:0]}花括号包裹 |
| ModelSim闪退或崩溃 | 工程路径过长、带中文、库损坏 | 换短路径,重新编译库,避免中文目录 |
6.2 高频问题详细排查
第一类:编译阶段的“Cannot find design unit”。
这个报错的含义是:ModelSim在编译当前文件时,找不到它依赖的某个模块或单元。比如你的顶层例化了RAM IP,但编译RAM IP的仿真模型文件排在后面,或者根本没被编译进来。排查思路:
- 确认当前ModelSim工程里已经添加了所有需要的源文件。检查IP核目录下是否有仿真模型文件(一般叫xxx_sim.v、xxx_bb.v或xxx_model.v);
- 确认编译顺序。Verilog对声明顺序要求不严格,但依赖模块最好先编译。最简单的方式是:先编译库文件和IP仿真模型,再编译RTL,最后编译testbench;
- 如果IP模型内部用了include文件,记得把include路径指向IP目录或TD的include目录。
第二类:波形全红X或全高阻Z。
红X在ModelSim里代表未知值,高阻Z代表没有被驱动。出现这个现象通常不是“模型坏了”,而是设计里信号根本没被赋初值,或者复位信号一直保持有效导致所有时序逻辑没有跳变。排查步骤:
- 先看复位信号rst_n释放了没有。很多人写testbench时复位拉低后忘了拉高,导致所有寄存器永远停留在复位状态;
- 看时钟信号有没有。没有时钟波形,所有时序逻辑全都动不了;
- 看IP核的仿真模型有没有使能端口。比如PLL在没有locked之前,输出时钟可能保持为0或X,需要等locked信号拉高;
- 如果某一组信号是Z,说明这组信号有多个驱动源互相竞争,或者根本没连驱动。检查testbench里有没有对三态总线做上拉或下拉。
第三类:路径问题导致编译不到文件。
Windows下TD生成的路径可能是相对路径,也可能带反斜杠和空格。ModelSim脚本里建议统一使用正斜杠“/”,并且对路径用花括号包起来:
set ip_dir {D:/Works/ProjectA/ip/pll} vlog -work work ${ip_dir}/pll_sim.v遇到“can't open file”之类的报错,先检查路径是否存在、文件是否存在,别急着怀疑工具坏了。
6.3 我踩过的一些比较耗时的坑
分享几个不算高频、但一旦遇到很费时间的坑。
第一个坑:TD生成的PLL仿真模型和ModelSim版本兼容性问题。早期某个TD版本生成的PLL模型里用了较新的SystemVerilog语法,用老版本ModelSim编译直接报错。后来排查发现是模型内部用了接口相关语法,解决办法是用更新的ModelSim版本,或者在编译时打开SystemVerilog模式。
第二个坑:testbench里用过程赋值做延时注入,但信号是wire类型,仿真波形上看不到预期的毛刺。原因是wire类型在被连续赋值时,如果用assign #5 a = b;,这个延时是对的;但如果用过程赋值always @(*) #5 a = b;,每次事件变化都会触发新的延时,波形行为会退化。类似这种问题只能靠波形辅助判断,单看代码很难发现。
第三个坑:FIFO IP核的仿真模型里带超时保护机制。某些FIFO核模型为了模拟硬件行为,会检测空满边界,如果testbench在FIFO满的时候还一直写入,模型可能会打印错误信息或者直接挂起。这不是你的代码有bug,是IP模型的保护机制生效了,需要检查写使能和满标志的时序关系。
7. 效率提升建议:把仿真脚本沉淀成模板
写过几次完整的联合仿真流程之后,我建议你把自己的仿真脚本整理成一个“项目模板”目录,包含:
- compile_sim.tcl:编译库和源码的脚本;
- run_sim.tcl:启动仿真、添加波形、控制run时间的脚本;
- tb_template.v:testbench骨架,时钟复位激励预设好;
- README:记录TD和ModelSim的版本对应关系、安装路径、注意事项。
每次新开FPGA工程,直接复制模板,改一改顶层文件名和IP核路径,就能省掉大量重复劳动。脚本化仿真还有个隐藏好处:换电脑、换同事、换版本时,只要脚本描述的是同一套流程,仿真结果稳定复现,不会因为“某人环境没配好”而互相甩锅。
如果你的工程规模比较大,可以考虑用ModelSim的batch mode跑仿真:
vsim -c -do compile_sim.tcl -do run_sim.tcl -do quit-c表示命令行模式,不打开图形界面,跑完后自动退出,适合服务器上批量回归。波形文件可以用log命令保存到wlf文件里,之后随时用ModelSim打开回看。
最后再说一个我在工作中一直沿用的习惯:每次仿真跑通之后,把脚本和测试平台提交到版本管理,并且顺手在README里记录一下“这个工程用的TD版本、ModelSim版本、IP核版本”。不要小看这个动作,半年后再拾起一个老项目,版本对应关系全靠这些记录才能快速恢复环境。
好了,以上就是我从IP核配置到ModelSim波形分析的全流程记录,尤其把几个容易卡住人的地方拆开讲了。如果你用的TD版本或芯片系列跟我的不完全一样,配置菜单和生成的文件名可能有些差异,但整体流程和排查思路是通用的。下次做什么联合仿真卡住的时候,回来翻翻这篇,大概率能省下半天查文档的时间。