前阵子有个读者私信我,说装了Quartus Prime Lite Edition,写了半天的Verilog,点开ModelSim却不知道从哪里下手,对着满屏英文界面和一根根红色波形发呆了一下午。这场景我太熟了,当年我刚接触FPGA的时候,光是把工程建对、把仿真跑起来,就折腾了整整一个周末。其实这套流程本身并不复杂,只是网上很多教程要么只讲安装,要么直接把代码一扔说“你们自己跑”,中间最关键的那几步偏偏没人讲透。
这篇文章我就从零开始,把Quartus Prime Lite Edition里新建FPGA工程、编写RTL代码、配置ModelSim仿真环境,到最终跑出正确波形的完整流程捋一遍。全程用一个小而完整的计数器案例来做演示,你看完照做一遍,基本就能把这个工具链跑顺。适合刚入门FPGA、被Quartus和ModelSim折磨得怀疑人生的同学,也适合那些以前只写代码、没真正跑过仿真验证的工程师。
1. 认识你的工具:Quartus Prime Lite 和 ModelSim 到底是什么关系
1.1 版本认知:Lite、Standard、Pro和ModelSim有什么区别
很多人下载软件的时候根本分不清Quartus Prime Lite Edition、Standard Edition、Pro Edition之间的差异,看到官网上一堆版本直接懵了。简单说,Quartus Prime就是Intel FPGA的集成开发环境,负责工程管理、代码编译、引脚分配、下载配置这一整套流程。Lite Edition是免费版本,功能上有限制但绝不是阉割到不能用,它支持Cyclone IV、Cyclone 10 LP、MAX 10这些常见器件,对学生、入门玩家和小规模项目来说完全够用。Standard和Pro则是面向更复杂器件的付费版本,普通学习和做小项目根本用不上。
再说ModelSim,它是业界老牌的硬件描述语言仿真器,能对Verilog或者VHDL代码做功能仿真。Quartus管“编译布线”,ModelSim管“逻辑验证”,两者是配合关系。我见过不少新手以为装了Quartus就自带仿真功能,结果在Quartus界面上找半天“模拟器”按钮,其实Quartus本身不做仿真,它是调用外部ModelSim来完成的。换句话说,ModelSim就是Quartus请来的外援,专门负责把你写的代码“运行”起来看结果。
1.2 版本匹配:为什么我不建议单独装最新版ModelSim
这里有个特别容易踩的坑:很多同学自己从网上下了一个最新版的ModelSim SE或者ModelSim DE,结果在Quartus里怎么配置都连不上,或者打开后许可证报错。原因很简单,Quartus Prime Lite Edition内部集成的仿真工具是“ModelSim Intel FPGA Starter Edition”,也可以叫ModelSim-Altera,它和Quartus之间有一套自动配置的库和脚本。你手动安装的ModelSim SE虽然功能更强,但两者之间缺少那层自动衔接,新手很难配置成功。
所以我的建议是:除非你确实有特殊需求,否则直接用Quartus安装包里自带的ModelSim组件就行。它虽然叫“Starter Edition”,但功能上做常规的RTL功能仿真绰绰有余,而且与Quartus版本严格匹配,省去一堆配置烦恼。记住一句话:工具链匹配比版本新更重要。
2. 环境准备:下载、安装和免费许可一次说清
2.1 下载与安装的选型建议
去Intel官网下载Quartus Prime Lite Edition时,安装包通常有好几个选项,有的叫“Quartus Prime Lite Edition (includes Questa?)”,有的叫“Device files”独立包。这里不纠结具体文件名,你只要记住:下载的时候确认是Lite版本的安装包,并且包含ModelSim组件即可。下载安装包时会要求注册Intel账号,注册过程不复杂,就是正常填邮箱、设密码,之后下载链接会发到邮箱或者直接能在网页下载,实测都方便。
安装有几个硬性建议:
- 安装路径不要带中文,不要有空格,更不要装在
C:\Program Files这种系统受保护目录下。我习惯装到C:\intelFPGA_lite\21.1这种路径,干净又好找。 - 组件选择时,建议把常用的器件系列都勾上,尤其是Cyclone系列。如果硬盘紧张,可以先只装你手头板子对应的器件,但教学演示建议勾上Cyclone IV和MAX 10,这两个器件资料多、例子多。
- ModelSim组件一定要记得勾选,否则装完Quartus后你找不到仿真器,还得重新安装或者单独装,非常折腾。
2.2 免费许可配置:为什么Lite版不需要“折腾”
Quartus Prime Lite Edition本身是免费使用的,不需要额外买License。安装完成后第一次打开Quartus,如果弹出License提示,通常是因为你误选了Standard或者Pro模式,检查一下License Setup页面,把模式切回Lite Edition即可。它不会限制你的编译次数,也不会限制工程规模,仅仅在支持器件和部分高级功能上有取舍。
至于自带的ModelSim Starter Edition,正常情况下安装完Quartus后就已经把许可环境配置好了,直接双击打开就能用。如果你打开ModelSim时遇到许可证报错,十有八九是环境变量没生效。可以手动打开系统环境变量,检查是否有一条指向C:\intelFPGA_lite\<版本>\modelsim_ase\win32aloem或类似路径的LM_LICENSE_FILE变量。缺了就把这条补上,然后重启ModelSim。再多说一句,这个Starter Edition的许可只能配合Quartus Lite使用,你不能指望拿它去跑其他EDA厂商的流程。
2.3 验证安装:打开ModelSim后看一眼这里
安装结束后,建议做一个快速验证:从开始菜单找到ModelSim Intel FPGA Edition并打开,如果界面能正常起来,Transcript窗口没有大段红色错误,说明环境基本OK。然后再打开Quartus,随便建一个空工程试一下编译流程,如果也能正常走完,工具链就算通了。
很多人跳过这一步直接开始写代码,结果后面出了问题都不知道是工具链坏了还是代码写错了。花五分钟做个冒烟测试,后面省几个小时。
3. 从New Project Wizard到第一次编译:新建工程实操
3.1 新建工程的四步关键操作
打开Quartus Prime Lite Edition后,点击File -> New Project Wizard,这是新建FPGA工程的入口。第1步是设置工程目录、工程名和顶层实体名。这里有个经验:工程目录不要直接放在桌面,建议建一个专门的工作目录,比如E:\fpga_prj\counter_test,工程名叫counter_test,顶层实体名也叫counter_test。虽然工程名和顶层实体名可以不同,但新手阶段保持一致最省心,后面生成Testbench或者调用ModelSim时不容易弄混。
第2步选择工程类型,默认Empty project即可,不需要勾选“Create top-level design file from template”,因为我们会自己写代码。第3步是选择目标器件,如果你手里有具体开发板,就选对应的Family和具体型号,比如Cyclone IV E系列的EP4CE10F17C8;如果没有硬件,只是想学仿真,直接选择Cyclone IV或者MAX 10系列里的任意型号就行。第4步进入EDA Tool Settings,这是很多人跳过的关键步骤,具体看下一节。
3.2 EDA Tool Settings:仿真能不能跑起来就看这一步
在New Project Wizard的第4步,有一个EDA Tool Settings页面,里面分Design Entry/Synthesis、Simulation、Formal Verification、Board-Level等几个子页。你需要切到Simulation页,把Tool name从None改成ModelSim-Altera(不同版本可能显示为ModelSim Intel FPGA或者Questa)。旁边还有个Format下拉框,选Verilog。
这一步非常关键。如果你不在这里指定仿真工具,等工程建好之后,Tools -> Run Simulation Tool -> RTL Simulation这个菜单项就是灰色的,点不了。很多新手在Quartus里找半天“仿真按钮”,其实问题就出在这里——你根本没有告诉Quartus你要用哪个仿真器。设置好后点Finish完成工程创建。
3.3 第一个设计文件:4位计数器的Verilog实现
工程建好后,点击File -> New -> Verilog HDL File,新建一个Verilog文件,把下面的代码写进去:
module counter_test #( parameter WIDTH = 4 )( input wire clk, input wire rst_n, output reg [WIDTH-1:0] q ); always @(posedge clk or negedge rst_n) begin if (!rst_n) q <= {WIDTH{1'b0}}; else q <= q + 1'b1; end endmodule这个模块的作用非常简单:在时钟上升沿,如果复位信号rst_n为低电平,输出q清零;如果复位无效,q就加1。因为是4位计数器,所以q的范围是0到15,计数满了会自动回绕。保存文件时注意文件名必须和模块名一致,也就是保存为counter_test.v,否则编译时Quartus会报错找不到顶层模块。
保存完成后,把该文件添加到工程中。添加的方式有三种:在Project Navigator窗口里右键Files选Add/Remove Files in Project,或者在工程创建向导时直接添加,也可以在Assignments -> Settings -> Files里添加。添加完毕后,Project Navigator里应该能看到counter_test.v。
3.4 编译检查:先让Quartus帮你把关语法
写好的代码先别急着仿真,先做一次全编译,确认语法没有问题。点击Processing -> Start Compilation或者直接按Ctrl+L,Quartus会进行分析和综合,并生成下载文件。底部Messages窗口如果显示Full Compilation was successful,说明代码没有语法错误,工程链路也正常。
有人会问:我明明只是做仿真,为什么还要先编译?因为Quartus的完整编译过程包含Analysis & Synthesis,它本质上就是检查代码能否被综合成电路。很多低级错误比如端口名写错、模块名不匹配、括号不匹配,在综合阶段就会被抓出来。先编译一遍,相当于让Quartus帮你做一次免费的语法检查,后面进ModelSim时就能少踩一半的坑。
纯仿真其实不需要做全编译,但如果你是从学习FPGA的角度出发,每次写完代码先编译一次是很好的习惯。
4. 手把手跑通ModelSim仿真:从Testbench到波形查看
4.1 Testbench到底是个什么东西
很多新手第一次看到“Testbench”这个英文词就头大,其实它就是一个用来“测试”你的设计模块的Verilog文件。打个比方,你做了一台空调(counter_test模块),Testbench就是测试台,它负责给空调接上电源(时钟)、按一下开关(复位)、然后观察空调吹出来的风是不是冷的(检查输出q)。
Testbench和普通设计模块最大的区别是,它通常不需要被综合成电路,它只是仿真环境里的一段驱动代码。你可以把它理解成一段“剧本”,规定好时钟怎么跳、复位什么时候拉高、仿真到什么时间点结束。它不需要对应任何实际硬件,所以怎么写都行,完全不考虑面积、时序这些物理限制。
4.2 编写Testbench的核心要素
在Quartus中再新建一个Verilog文件,命名为tb_counter_test.v,写入下面的Testbench代码:
`timescale 1ns / 1ps module tb_counter_test; reg clk; reg rst_n; wire [3:0] q; // 例化被测试模块 counter_test uut ( .clk (clk), .rst_n (rst_n), .q (q) ); // 生成时钟信号:周期20ns,频率50MHz initial begin clk = 0; forever #10 clk = ~clk; end // 复位与仿真结束控制 initial begin rst_n = 0; #100; rst_n = 1; #1000; $finish; end // 可选:在控制台打印仿真信息 initial begin $monitor("time=%0t, rst_n=%b, q=%d", $time, rst_n, q); end endmodule这里有几个关键点需要解释:
`timescale 1ns / 1ps声明仿真时间单位和精度。#10就代表10纳秒,#100代表100纳秒。如果不写这一行,很多仿真器会按默认时间单位处理,可能出现你预期跑1秒结果它只跑了1纳秒的尴尬情况。forever #10 clk = ~clk;用于产生无限翻转的时钟信号。它在0时刻先给clk赋0,之后每过10ns翻转一次,所以时钟周期是20ns。这个写法是仿真里生成时钟最常用的方式,必须学会。- 两个
initial块是并行执行的,一个管时钟,一个管复位。复位先拉低100ns,让计数器清零,然后释放复位,让计数器开始正常计数。 $finish用于结束仿真。如果忘了写,ModelSim会一直跑下去,有时候会让人觉得像卡死了。$monitor会在仿真过程中自动打印信号变化,方便我们在Transcript窗口里直接看输出结果,不用一直盯着波形图。
4.3 调用ModelSim的两种方式:自动和手动
写好Testbench后,剩下的就是让ModelSim跑起来。这里有两种方式,我建议把两种都学会。第一种是通过Quartus的NativeLink自动调用,第二种是手动打开ModelSim自己跑。
自动方式的步骤是:先把tb_counter_test.v文件添加到工程中,然后在Assignments -> Settings -> EDA Tool Settings -> Simulation里,找到Compile test bench选项,点击Test Benches按钮,新建一个Testbench条目,在File name中填tb_counter_test,在Top level module in test bench也填tb_counter_test,然后在File name那一栏把tb_counter_test.v添加进去。完成后,点击Tools -> Run Simulation Tool -> RTL Simulation,Quartus会自动调用ModelSim,编译设计文件和Testbench,打开仿真界面,然后停在仿真时间0点。你只需要在ModelSim里添加波形、运行仿真即可。
自动方式的好处是Quartus会自动处理设计文件、Testbench和IP的编译顺序,不容易出错,尤其是当你用到PLL、FIFO这些IP核时,自动方式能帮你省去大量库配置工作。缺点是你得提前在工程设置里正确填Testbench信息,否则Quartus不知道你要仿哪个文件。
手动方式的步骤是:打开ModelSim,在Transcript窗口里敲命令,或者通过菜单操作。最直接的方法是在ModelSim的菜单里选择File -> New -> Project,新建一个工程,然后添加counter_test.v和tb_counter_test.v两个文件,接着点击Compile -> Compile All。编译完成后,Library窗口里会出现一个work库,展开它能看到tb_counter_test模块。双击这个模块,ModelSim会把它加载为仿真顶层。接下来在命令行输入add wave -position end sim:/tb_counter_test/*,把所有信号加到波形窗口,再输入run -all,仿真就会自动跑完,波形窗口里能看到结果。
手动方式的核心命令我整理在下面,以后你会经常用到:
vlib work vmap work work vlog counter_test.v tb_counter_test.v vsim work.tb_counter_test add wave -position end sim:/tb_counter_test/* run -all这五行命令的意思分别是:创建work库、把work库映射为当前工作库、编译两个源文件、指定tb_counter_test为仿真顶层、添加所有信号到波形窗口、运行整个仿真。
4.4 波形怎么看:以计数器为例的验证方法
仿真跑完后,波形窗口应该能看到clk、rst_n、q三条信号。你看到的可能是一堆密密麻麻的波形,不知道怎么验证对不对。先做这几步:
- 右键
q信号,选择Radix -> Unsigned,把q从二进制显示改成无符号十进制显示,这样更容易读值。 - 用波形窗口的缩放工具(放大镜图标)或者快捷键
Ctrl+滚轮,把波形放大到能看到每个时钟周期的程度。 - 用鼠标在波形上点击,拖动光标到不同时间点,查看q的值。
正确的仿真结果应该是:在刚开始0到100ns内,rst_n为低电平,q保持0;100ns之后rst_n拉高,每个时钟上升沿到来时q加1,依次显示0、1、2、3……一直到15,然后回绕为0。如果你看到的波形是这个规律,恭喜你,整个工具链已经跑通了。
5. 波形变红不用慌:红线问题排查与调试技巧
5.1 “红线”到底是什么信号状态
接触ModelSim后你会频繁看到一个现象:波形窗口里信号不是我们熟悉的绿色(表示0)或者黄色(表示1或高阻),而是一条红色线,交织着一些深浅不一的区域。红色在ModelSim中表示X状态,也就是未知状态,信号既不是0也不是1,可能是电路里某个寄存器没有初始化,也可能是信号压根没有被驱动。还有一种情况是蓝色或紫色线,通常表示Z高阻状态,多出现在三态总线上。
很多新手一看到红线就慌,觉得代码写错了。其实红线是仿真调试中最常见的信息,它只是告诉你“这个信号此刻的值是未知的”,你要做的是顺着信号往下追:是没初始化的原因,还是驱动源的问题,还是连错线了。
5.2 最常见的五个红线原因
根据我自己的经验,90%的红线问题都逃不过这五类:
第一,Testbench里面没有给寄存器信号赋初值。比如你声明了reg clk; reg rst_n;,但initial块里只写了rst_n = 0;却忘记给clk赋初值,结果时钟信号一直是X,整个设计完全动不了。
第二,复位时间太短或者根本没有复位信号。如果你的设计依赖复位把寄存器清零,但Testbench里始至终没有拉低过复位信号,那所有寄存器上电后都是X状态,计数器输出自然是一片红。
第三,忘记了例化DUT,只写了一个空的Testbench模块。这种情况最哭笑不得,你辛辛苦苦写了tb_counter_test,结果里面只声明了一堆信号,没有实例化counter_test模块,那被测试模块当然是完全没有驱动,信号全是X。
第四,时钟always或forever语句写错了。比如forever #10 clk = clk;这种写法等于没翻转,仿真只有静态电平;或者把#10写成了#10ns,而timescale又没有正确声明,导致时钟频率偏离预期。
第五,仿真运行时间不够。你把#1000写成了#100,仿真还没跑几个周期就结束了,看起来像信号没有变化,实际上只是时间太短。
5.3 红线排查三步走
遇到红线,我建议按下面三步来排查,效率最高。
第一步:看输入信号。把波形缩放到整个时间轴,先检查clk和rst_n有没有正常翻转和跳变。如果clk根本没有波形,那是Testbench时钟生成的问题;如果rst_n一直是X,那是复位初值的问题。输入信号都不对,就不要浪费时间看后面的输出了。
第二步:看DUT的中间信号。把波形窗口里所有信号都加上,尤其是DUT内部信号。在ModelSim里可以右键DUT的实例,选择Add Wave,把内部信号也拉出来看。如果输入正常、输出为X,那问题大概率在DUT内部,比如某个寄存器没有复位逻辑,或者组合逻辑产生了X传播。
第三步:用force命令强制赋值。如果怀疑某个信号有问题,可以在ModelSim命令行里直接输入force -freeze sim:/tb_counter_test/rst_n 0,把复位强制拉低,然后run 100ns,看看信号有没有变化。如果强制赋值后波形能恢复,说明是驱动源的问题;如果强制赋值后依然是X,那可能是信号本身存在多驱动冲突或者连接错误。
5.4 几个能救命的ModelSim调试小技巧
除了排查方法,我再分享几个随手就能用上的小技巧。第一个是$display和$monitor,在Testbench里加一句$display("time=%0t q=%0d", $time, q);,仿真时Transcript窗口就会打印出q的实时值,比看波形更直观。第二个是force命令,仿真过程中可以随时强制某个信号等于指定值,适合临时修改变量观察现象。第三个是configure wave -justify 0这类波形优化命令,设置起来比较繁琐,但能调整波形显示的可读性,有兴趣可以查一下ModelSim帮助文档。
我自己的习惯是,新写的模块第一次仿真如果出现红线,先不急着改代码,而是打开Transcript窗口看有没有Error或Warning,ModelSim会在编译阶段提示大部分问题。如果编译没有报错但波形就是红的,再用上面的三步走思路排查,基本都能定位到原因。
6. 把仿真流程工程化:脚本、库配置和避坑清单
6.1 用.do脚本固化你的仿真流程
每次打开ModelSim都要手动敲vlib、vlog、vsim这些命令其实很烦,而且容易敲错。更工程化的做法是把它们写到一个.do脚本文件里,以后直接执行脚本就行。比如在工程目录下创建run_sim.do文件,内容就是前面提到的那五行命令:
vlib work vmap work work vlog counter_test.v tb_counter_test.v vsim -voptargs=+acc work.tb_counter_test add wave -radix unsigned sim:/tb_counter_test/* run -all以后打开ModelSim,在命令行输入do run_sim.do,仿真流程全自动走完。注意这里我加了-voptargs=+acc参数,它会让ModelSim在优化编译时保留所有信号的访问能力,否则有些内部信号在波形窗口里可能看不到。用脚本的方式做仿真,最大的好处是可复现,不管三个月后还是三分钟后,跑出来结果一样,不用担心遗忘操作步骤。
6.2 使用IP核时仿真库怎么配
如果你后面开始用Quartus自带的IP核,比如PLL锁相环、FIFO、RAM等,仿真时就会涉及到仿真库的问题。原因很简单,这些IP核本质上是对底层器件原语的封装,仿真时需要对应的仿真模型和库文件。Quartus在生成IP核时通常会生成一个名为<ip_name>_sim的目录,里面有仿真用的源文件;同时会提醒你添加对应的仿真库,比如altera_mf、altera_lnsim等。
如果你用Quartus自动调用ModelSim的NativeLink方式,这些库一般会被自动编译配置好,不需要手动处理。如果你是自己打开ModelSim手动仿真,就需要手动把这些库编译进ModelSim。具体方法是:在ModelSim菜单里选择Tools -> Compile Simulation Libraries,然后选择Quartus安装目录下的对应库源文件路径,让ModelSim自动编译生成带altera_mf等库名的库文件。这一步配置一次就能复用,避免每次新建工程都重复操作。
6.3 避开这几个常见的“低级坑”
最后整理几个我见过无数人踩过的坑,说多了都是眼泪。
路径问题排第一。工程路径、Quartus安装路径、ModelSim工程路径,任何一级都不能有中文,最好不要有空格。我之前接过一个朋友的问题,他工程建在D:\学习\FPGA\计数器下面,Quartus编译没问题,但ModelSim一打开就找不到模块,最后改成纯英文路径才解决。
时间尺度问题排第二。Testbench里的`timescale一定要记得写,而且建议统一写1ns / 1ps。如果写的是1ps / 1ps,那#100只有100ps,仿真1000个单位也才1微秒,计数器当然看不到几个周期。
复位极性问题排第三。我的示例代码用的是低电平复位rst_n,所以Testbench里先拉低再拉高。如果你用的是高电平复位rst,那Testbench里就必须反着写。这个看似简单,但经常有人把复位极性搞反,导致波形一直保持在复位状态或者完全不复位。
文件名和模块名不一致的问题排第四。Verilog里文件名通常要求跟模块名相同,尤其是在多文件工程里,Quartus按文件名搜索模块,不一致就会报错。养成“一个文件一个模块,文件名等于模块名”的习惯,能省很多事。
7. 写在最后
这套流程走通之后,你会发现Quartus和ModelSim其实没有网上说的那么难,难的是第一步不知从何下手。我自己带过不少新人,发现大家最需要的就是一份能照着做的完整路线图,而不是零散的知识点。这篇教程里的计数器只是个小例子,但整个流程——新建工程、写RTL、写Testbench、配置仿真工具、跑ModelSim、排查红线——是完全可复用的。你后面做任何FPGA功能验证,本质都是这套流程换了个模块名而已。
最后分享一个我坚持了很多年的习惯:每写完一个模块,不管逻辑多简单,都强迫自己先写一个对应的Testbench,把最基本的功能跑一遍仿真再往下做。仿真比上板调试快得多,也能提前发现大量隐藏问题。别嫌麻烦,前期几十次的“麻烦”,换来的是后面少掉几百次的头发。