1. 先用一段亲身经历说明:为什么我非要把ModelSim接进高云工程
先交代一下背景。我在一个项目里用高云GW1NR-9做视频采集端的控制逻辑,开发环境用的是Gowin Software,RTL代码写了一版,功能仿真跑下来也没发现什么大问题,但一旦把PLL、RAM这些IP核加进去,Gowin自带的仿真器就开始让我头疼:波形查看的交互逻辑不顺,批量跑回归测试的时候脚本能力又弱,项目组其他人习惯用ModelSim,发过来的测试用例我用自带仿真器经常打不开,反过来我发给他们的波形文件他们也看不了。
这就逼着我做了一个决定:把高云FPGA的开发流程和ModelSim的仿真流程打通,以后所有功能仿真、时序仿真和回归测试,统一在ModelSim里完成。这个环境搭建过程,前前后后踩了不少坑,网上资料又零散,写这篇文章就是想把这些经验和完整的操作链路整理出来,让后面接手的人少走弯路。
这篇文章适合谁看?正在用高云Gowin FPGA做开发、觉得自带仿真器不够趁手的工程师,以及学校实验室里做国产FPGA方向、需要做较复杂仿真验证的学生。不需要你有非常深的ModelSim基础,我尽量从原理讲到实操,每一步都说明白为什么这么做。
2. 开工前先理清:高云IDE、ModelSim与仿真库之间的关系
2.1 两者的分工逻辑
很多人第一次接触联合仿真时会懵:仿真不是IDE自带功能吗?为什么还要单独接一个ModelSim?其实这里面的逻辑不复杂,你要理解FPGA仿真的几个阶段。
在FPGA设计流程里,功能仿真(也叫行为仿真)验证的是RTL代码的逻辑行为,这个阶段不关心门延迟和布线延迟,只看波形和逻辑功能对不对。时序仿真是综合之后、布局布线之后做的,仿真模型里加入了器件的实际延迟信息,用来检查时序是否满足设计要求。高云的Gowin Software自带仿真器能跑这两种仿真,但它的波形查看和分析能力比较基础,遇到复杂设计、大文件波形、长时间仿真时,体验会下降不少。
ModelSim作为业界使用最广泛的HDL仿真器之一,优势在于编译速度快、仿真性能稳定、debug工具成熟,还支持Tcl脚本做批量仿真和自动化回归。把这些优势接入高云FPGA开发流程里,等于给设计验证环节换了一套更顺手的工具链。
2.2 高云工程中哪些文件是给ModelSim用的
在Gowin IDE里,当你的工程综合、布局布线完成后,IDE会生成一系列仿真相关文件。你需要从里面找到这几类,它们是联合仿真的核心:
- 仿真网表文件:由综合/布局布线后的设计导出,通常是
.vo后缀(Verilog Output),里面就是门级或原语级的网表描述。功能仿真的话,也可以用RTL源文件直接编译。 - 测试激励文件:你自己写的testbench,
.v或.sv后缀。 - 高云器件仿真库文件:包括GowinSim.v、GowinRTL.v等,这些是高云FPGA原语的仿真模型,ModelSim不认识高云器件自带的各种原语(比如PLL、OSC、DDR3的硬核控制器),必须把高云的仿真库加进去,ModelSim才知道这些原语的行为是什么。
- IP核仿真模型:工程里用了高云的IP核,比如PLL、RAM、FIFO之类的,Gowin IDE会在生成IP时同时生成对应的仿真模型。这部分文件也必须在ModelSim里编译。
打个比方,高云IDE相当于生产了一套“零件”和“图纸”,而ModelSim是加工车间,它需要先把零件清单(仿真库)和图纸(网表文件、Testbench)全部读进去,才能开始运转。缺了任何一样,出来的结果必然有问题。
2.3 版本匹配是第一个隐形坑
版本问题我放在最前面说,因为它最容易让人心态崩溃。ModelSim和Gowin IDE各自的版本更新速度不同,但绝大多数情况下,ModelSim SE 10.x系列(比如10.6、10.7)和Gowin Software 1.9.x系列搭配是没问题的,我目前主力用的就是ModelSim SE 2020.4配Gowin 1.9.9。
比较常见的坑是:Gowin IDE版本过旧,生成的原语列表和最新ModelSim的编译语法有冲突;或者是ModelSim版本太旧(很多教程还在用SE 10.1),对SystemVerilog的新特性支持不全,高云IP核生成的仿真模型编译不过去。
建议:安装前先确认Gowin IDE版本对应的Release Note,里面会标明支持的第三方EDA工具版本范围。如果你用的恰好是两者都比较新的版本,并且遇到编译报错,优先考虑换成官方文档推荐的那一档ModelSim版本,别在编译报错上死磕,性价比很低。
3. 在高云IDE里把设计“打包”给ModelSim
3.1 建立工程阶段的建议
好的开始是成功的一半。在高云IDE里新建工程时,我建议提前想清楚哪些代码要参与仿真。不需要参与仿真的模块(比如只用于上板测试的下载逻辑)可以放到单独的目录里,避免后面全部一股脑编译进ModelSim。
工程建好后,编写RTL代码时也要养成一个习惯:把时序逻辑的复位信号统一起来。后面ModelSim仿真出现大面积未知态(X态)时,大部分原因就是复位信号没有处理好,这个点我在后面专门讲。
3.2 生成仿真文件的具体操作
当RTL代码写完、综合通过之后,在Gowin IDE里执行以下操作来导出仿真文件:
- 在菜单栏找到“Tools”,点击下拉菜单中的“Compile Simulation Files”。
- 在弹出的对话框里选择仿真工具。如果列表里有ModelSim相关选项就直接选,没有就选“Others”。
- 点击确定后,IDE会在工程目录下生成一个
sim文件夹,里面包含了编译好的仿真库文件、仿真网表文件和一组示例脚本。 - 打开
sim目录,你会看到.v格式的库文件(例如GowinSim.v)以及由当前设计导出得到的网表文件。
有一点要特别注意:如果你在工程里用了IP核,确认一下sim目录里是否包含了对应IP的仿真模型。有些版本的IDE不会自动导出所有IP的模型文件,需要你到IP生成目录下手动拷贝。漏掉这一点,到ModelSim那边编译就会报Module not found的错误,而报错信息又不直接提示是IP模型缺失,排查起来很费劲。
3.3 关于测试激励的编写习惯
Testbench是联合仿真的入口,写的好坏直接决定调试效率。我的习惯是:每个testbench文件头部固定写清楚四个信息——被测模块名、时钟频率、复位信号极性、本用例验证的功能点。
高云FPGA的IP核,像PLL、OSC,对复位和时钟稳定时间有严格要求。比如高云PLL的lock信号,在仿真模型里通常需要几十微秒才能拉高,如果你在testbench里只等了几个时钟周期就检测lock信号,仿真波形看起来就会很奇怪:逻辑明明没问题,输出却一直不对。这种情况下,testbench里应该等足够的仿真时间再开始喂数据,或者在复位释放后、操作真正开始前,等待PLL lock信号稳定。
4. ModelSim侧配置:仿真库映射与do脚本
4.1 第一次打开ModelSim需要做的事
打开ModelSim,先在你的工程目录下建一个专门放仿真文件的文件夹,比如msim_work。然后在ModelSim的Transcript窗口里,先把当前工作目录切换过去:
cd <你的工程仿真目录>接下来要创建一个库,用来存放编译后的设计文件。ModelSim里的work库是默认库,但如果多个工程复用同一个ModelSim安装环境,建议每个工程单独建库,避免冲突。指令很简单:
vlib work vmap work workvmap命令的作用是把逻辑库名和物理路径映射起来,这一步相当于给ModelSim指路:让它在编译源文件时知道把结果放到哪里去。
4.2 编译高云仿真库和设计文件
然后就是编译高云仿真库。把高云IDE生成的GowinSim.v和GowinRTL.v编译进ModelSim,指令是:
vlog -work work <高云IDE安装目录>/sim/GowinSim.v vlog -work work <高云IDE安装目录>/sim/GowinRTL.v接着编译你自己的设计文件。这里有个关键点:编译顺序一定要正确。如果一个模块A调用了模块B,必须先把B编译进来,再编译A。Verilog里虽然不严格要求先声明后使用,但ModelSim在增量编译时对顺序很敏感,一旦顺序反了,就会报Instantiation of module B failed。编译顺序比较稳妥的安排是:先编译库文件,再编译IP仿真模型,最后编译自己的RTL文件和testbench。如果你用了高云的IP,那IP的仿真模型要放在你的RTL之前编译。
再来说vlog命令的参数。一般我建议加上-sv,表示允许SystemVerilog语法,因为高云有些IP核的仿真模型是用SystemVerilog写的。如果你在编译过程中报了一堆看不懂的语法错误,先检查一下是不是没加-sv参数。
4.3 用do脚本跑仿真而不是手动点鼠标
ModelSim支持命令行操作,高手基本都靠do脚本而不是界面上点鼠标,因为do脚本可复用、可自动化,还能放进版本管理里。下面是一个简洁的do脚本模板:
vlib work vmap work work vlog -work work -sv D:/project/gowin_sim/GowinSim.v vlog -work work -sv D:/project/gowin_sim/GowinRTL.v vlog -work work -sv D:/project/rtl/top.v vlog -work work -sv D:/project/rtl/pll_ip.v vlog -work work -sv D:/project/tb/tb_top.v vsim -t 1ps -L work work.tb_top add wave -position end sim:/tb_top/* add wave -position end sim:/tb_top/uut/* run -all简单说下每行各干了什么。
vsim -t 1ps是设置仿真时间精度为1皮秒,这个精度对高云FPGA的时序仿真基本够用。-L work是把work库作为设计库加载进来,这个参数很关键,不加的话ModelSim可能找不到高云原语的定义。
add wave不加的话,仿真完了之后查看波形面板一片空白。我一般会把顶层testbench的信号和被测模块(uut)内部的信号都加进去,这样既能看整体交互,也能看内部状态机跳转。如果内部信号层级比较深,在波形窗口里操作不方便,直接在do脚本里手动指定路径是最高效的方式。
run -all表示一直跑到有$finish或$stop为止。如果你的testbench里没写这两个系统任务,那就得在do脚本里用run -us 100这种指定仿真时长的写法,不然仿真会一直挂在那里跑不完。
4.4 初始化脚本路径要注意的事
高云IDE生成的库文件路径里常常包含空格,比如默认安装目录C:\Program Files\Gowin\Software_1.9.9,在ModelSim的Tcl环境里直接使用带有空格和反斜杠的路径,会很麻烦。
建议一:在do脚本里统一用正斜杠/代替反斜杠,ModelSim解析起来更稳。
建议二:把高云安装目录下的仿真库文件拷贝到工程目录下再引用,或者用vmap建立虚拟映射,不要每次都写一长串绝对路径。我是直接把GowinSim.v、GowinRTL.v拷到一个sim_lib目录里,这样脚本拿到任何一台电脑上都能用,不会因为安装路径不一致而报错。
5. 波形一片红?仿真不跑?顺着这条链路自查
5.1 红线和X态:先看时钟和复位
如果你打开波形,看到大片红色或者高阻态Z线,别急着怀疑代码逻辑,先查两个东西:时钟有没有产生、复位有没有正常释放。
时钟问题:高云FPGA内部的时钟源(OSC原语)和PLL,在仿真模型里不是一通电就立刻稳定输出的。尤其是PLL,从输入时钟稳定到lock信号拉高,中间有锁定时间。在功能仿真里,很多testbench直接把PLL的lock信号忽略掉,直接给被测模块喂时钟,这样做在简单逻辑里能跑通,但在带复位状态机的设计里,容易出现模块在复位尚未释放时收到了跳变的时钟边沿,导致状态机进入非法状态。我的建议是:testbench里仿真开始后先等一段时间(比如10us),再释放复位,之后再过几个周期才开始真实激励。
这里有个小知识点:ModelSim波形显示中,红色通常表示X态(未知态),蓝色或绿色表示0/1正常电平,高阻态Z则显示为一条水平线(具体颜色可在设置里调)。看到X态,优先怀疑信号没有被初始化。在Verilog里,reg类型的信号仿真初值是X,如果你在复位逻辑里只处理置位不复位,那这个信号就会一直保持X态,进而污染后续所有逻辑。
5.2 编译报错:从第一条错误开始看
ModelSim编译报错时,Transcript窗口会给出错误行号和错误类型。很多人习惯从头开始一条一条修,这个方向其实不对。我的习惯是:先看第一条报错,然后回到源文件对应的那一行,把上下文看一遍。往往第一条报错是根因,后面的报错全是“连锁反应”。
常见的高云联合仿真编译报错有几种:
Module not found:某个模块没有被编译进来,优先检查是否漏了高云IP仿真模型。Port width mismatch:端口位宽不匹配。高云IP核有的端口是可变位宽,你在例化时位宽写错了,编译时就会报这个错。Unknown identifier:变量未定义,检查是不是漏了声明,或者把reg和wire用混了。Can't find module "Gowin_PLL":典型的高云原语库没编译进去。
报错信息的处理策略很简单:不要沉浸在报错堆里,而是回到“库文件、IP模型、RTL、testbench”这条编译链路上按顺序排除。
5.3 testbench顶层没选对,仿真跑了但波形空
这个坑特别隐蔽:ModelSim的vsim仿真指令里,指定的是testbench模块名,但如果你用了run -all时仿真立即结束、波形窗口里什么信号都看不到,那有可能是你的testbench里自己加了$finish,或者仿真时间单位设置得太大,导致一眨眼就跑完了。
还有一种情况:testbench里例化了被测模块,但例化名称写得和RTL中不一致,或者在testbench里把被测模块接到了错误端口上,导致内部信号完全没有翻转。检查方法很简单——在波形窗口里添加被测模块内部的信号,如果内部信号没有任何翻转,那就不是仿真环境的问题,而是testbench的连接问题。
5.4 高云PLL仿真的lock信号处理
关于PLL的仿真,我再多写几句。高云的PLL模型在ModelSim中需要额外的编译选项,某些版本需要-L gowin参数来指定库路径,否则PLL仿真模型里的内部逻辑会无法解析。具体表现是:PLL输入时钟正常,但输出时钟一直是X态或Z态,lock信号一直拉不高。
解决办法是在vsim指令中显式加载高云库:
vsim -t 1ps -L work -L gw1n9 work.tb_top等号后面的gw1n9是具体器件系列的库名,不同器件的库名不一样,比如GW2A系列可能是gw2a。具体库名可以到高云IDE安装目录下的sim文件夹里,查看.v文件名和库目录名来确认。
6. 工程化提速:回归测试、批处理与团队协作
6.1 把do脚本推广到全项目组
单机环境搭好之后,如果项目组有多个人要做仿真验证,建议把do脚本、文件清单和目录结构统一下来。我们的做法是:每个模块的设计目录下都放一个sim子目录,里面固定放三个文件——compile.do(负责编译)、simulate.do(负责启动仿真并加载波形)、run_all.bat(Windows批处理,一键依次执行前两个脚本)。
run_all.bat的内容也不复杂:
@echo off vsim -c -do compile.do -do simulate.do这里的-c是让ModelSim以命令行模式运行,不弹图形界面,速度更快,适合批量回归。
6.2 用批处理跑回归测试
功能改动频繁时,手动在ModelSim里加载一个testbench跑一遍点一下,效率太低了。我通常会把所有测试用例写成一个个独立的do脚本,再用一个主控脚本依次调用:
do test_case1.do do test_case2.do do test_case3.do每个test_case的do脚本里,在run -all之后加一行检查语句:
if {[runStatus] eq "break"} { echo "Test FAILED" } else { echo "Test PASSED" }runStatus可以获取当前仿真的状态,如果仿真中途遇到了断言失败或者$stop,状态就是break,此时脚本自动打印FAILED。如果正常跑到了$finish,状态是finish,就会打印PASSED。这样批量回归下来,哪个用例挂了、挂在哪,一看输出日志就知道。
6.3 覆盖率收集与跨工具协作
ModelSim还有一个很好用的功能是覆盖率收集。在高云FPGA项目里,覆盖率分析可以帮你判断testbench写的是否充分。在vsim指令里加上:
vsim -t 1ps -c -coverage -L work work.tb_top仿真结束后,用coverage report命令导出覆盖率报告。不过我得提醒一句:覆盖率只是衡量验证充分性的一个指标,不是越高越好,重点关注的应该是状态机分支覆盖和关键信号翻转覆盖,别把时间浪费在追求不切实际的100%行覆盖率上。
团队协作方面,高云IDE工程文件(.gws)和ModelSim的do脚本建议一并纳入版本管理。这样任何人拉下代码后,先跑一下run_all.bat,就能在十几分钟内确认当前RTL版本能不能通过最基本的仿真验证。这个习惯对多人并行开发、频繁合并代码的场景价值非常大。
6.4 混用Verilog和SystemVerilog时的处理
如果你的高云工程里既有.v文件又有.sv文件,ModelSim编译时建议统一加-sv参数。不要觉得-sv只对.sv文件有意义,在实际操作中,某些.v文件里如果包含了SystemVerilog的语法片段(比如带类型的interface),不加-sv编译也会报错。全工程统一用vlog -sv编译是最省心的做法。
7. 我在实际项目中沉淀的几个使用习惯
环境搭好只是第一步,真正让这套联合仿真流程发挥价值的是日常使用的习惯。我分享几个自己实践下来比较管用的细节。
第一,模型库文件一定要固定版本。高云IDE升级之后,GowinSim.v可能会有变动,但如果你旧工程还依赖旧版本的库,不要急着把旧库文件删掉。我们的做法是:每个工程目录下放一个sim_lib文件夹,里面记录当前工程所用的库文件版本号。升级IDE后新建工程时,会主动拷贝新库文件进来,而旧工程维持旧库不变。这样既保证了新工程的特性支持,又不会让旧工程莫名其妙跑不通。
第二,命名规范对仿真调试效率的影响很大。时钟信号统一用clk做前缀、复位统一用rst_n或rst做后缀(同时要有注释说明极性),状态机信号统一叫state_*。这样在ModelSim波形窗口里加信号时,能通过前缀快速筛选,不用在长长的信号列表里翻半天。
第三,仿真波形存档。跑完一个重要验证用例后,把波形文件(.wlf格式)一并提交到共享目录,或者连同testbench一起提交到版本管理。不要觉得波形文件占空间浪费,等出了问题回头排查时,你会感谢当时存下来的波形。亲眼见到过好几个同事,重构完代码后拍胸脯说逻辑没变,结果对比波形才发现细微的时序差异,没有历史波形这个结论得花好几天才能查出来。
第四,遇到ModelSim界面卡顿的情况,优先想到是不是波形数据量太大。ModelSim加载几百MB的wlf波形时,界面会明显卡顿。这种情况下,用add wave只添加关注的关键信号,而不是把所有信号一股脑全加进去。仿真时长也可以适当缩短,分段dump波形比一次性跑超长仿真再回头翻找信号要高效得多。
这套高云FPGA加ModelSim的联合仿真环境,前前后后帮我在多个项目里省下了大量验证时间。尤其是高频改动代码、需要反复回归验证的阶段,用脚本批量跑一遍比在GUI里手动操作快出好几倍。如果你是刚接触高云FPGA不久,建议先把环境搭起来,并用一个最简单的流水灯工程整个流程跑通一遍,不急着在复杂设计上直接切换,等熟悉了各个库文件和脚本的作用,再逐步搬到正式项目里用。