1. 项目概述与核心价值
如果你刚拿到一块DE0-Nano开发板,面对FPGA、Quartus II、Verilog这些名词感到无从下手,那么这篇实战指南就是为你准备的。我花了大量时间,从最基础的工程创建到复杂的Nios II软核系统搭建,把整个流程中容易踩的坑、必须注意的细节都梳理了出来。FPGA的魅力在于“硬件可编程”,它不像单片机那样执行固定的指令流,而是让你用代码“画”出一块真正的数字电路。DE0-Nano作为一款入门级但功能齐全的开发板,搭载了Altera(现Intel)的Cyclone IV FPGA,是学习数字逻辑设计、片上系统开发的绝佳平台。
本文的核心价值在于提供一条清晰的、可复现的路径。我不会只告诉你“点击这里,然后点击那里”,而是会解释每一个操作背后的意图:为什么这里要用PLL?引脚分配的依据是什么?SOPC Builder里每个组件的作用是什么?通过一个完整的项目——从让LED闪烁的简单计数器,到构建一个能运行C程序的Nios II处理器系统——你将不仅掌握工具的使用,更能理解FPGA开发的核心思想。无论你是电子相关专业的学生,还是希望拓展硬件技能的软件工程师,这篇超过五千字的详实记录都能让你少走弯路,快速上手。
2. 开发环境部署与板卡初识
在开始写第一行代码之前,扎实的环境准备是成功的基石。这一部分我们解决“用什么”以及“怎么连”的问题。
2.1 软件工具链安装与配置
FPGA开发离不开厂商提供的集成环境。对于DE0-Nano,我们的核心工具是Intel Quartus Prime Lite Edition(早期版本叫Quartus II)。我强烈建议直接从Intel官网下载最新版本的Lite版,它对于Cyclone IV系列器件是免费的,并且包含了我们所需的所有功能:设计输入、综合、布局布线、编程下载以及SOPC Builder(或更新版本的Platform Designer)。
安装过程比较直接,但有几个关键点需要注意。首先,安装路径不要包含中文或空格,像C:\Altera\或D:\IntelFPGA\这样的路径是最稳妥的。安装程序可能会询问是否安装USB-Blaster驱动,务必勾选。如果安装时漏了,也不用担心,驱动文件通常位于Quartus安装目录的drivers\usb-blaster文件夹下。当第一次通过USB线连接DE0-Nano板卡到电脑时,系统会提示发现新硬件,此时手动指定到这个目录安装驱动即可。驱动安装成功后,在Quartus的Tools -> Programmer窗口中点击Hardware Setup,应该能看到USB-Blaster [USB-0]的选项,这标志着你的电脑已经可以和板卡通信了。
注意:不同版本的Quartus界面和组件名称可能略有差异(例如SOPC Builder在新版中已整合为Platform Designer),但核心逻辑和流程是相通的。本文以经典的Quartus II 13.0/13.1界面为例进行讲解,其原理适用于后续版本。
2.2 DE0-Nano开发板硬件资源解析
工欲善其事,必先利其器。让我们快速浏览一下DE0-Nano板上的核心资源,这对后续设计至关重要:
- FPGA芯片:核心是一颗Cyclone IV EP4CE22F17C6。
EP4CE22表示Cyclone IV E系列,逻辑单元约22K;F17代表484脚的FBGA封装;C6是速度等级。这些信息在我们创建工程选择器件时必须完全匹配。 - 时钟源:板载一颗50MHz的有源晶振,连接至FPGA的全局时钟引脚(R8)。这是我们所有同步设计的基础时钟。
- 用户I/O:
- 8个绿色LED:连接到FPGA引脚,低电平点亮(共阳极接法)。
- 2个按键:连接到FPGA引脚,默认高电平,按下时为低电平。
- GPIO扩展口:两排共76个引脚,将FPGA的I/O引出,可以连接自定义外设或扩展板。
- 配置与调试接口:USB-Blaster电路,用于编程FPGA和调试Nios II系统(通过JTAG协议)。
- 电源:可通过USB口或外部电源接口供电。
理解这些资源后,我们就能明白,我们的设计就是要用代码去控制这些LED、读取按键状态,并通过GPIO与外界交互。
2.3 使用DE0-Nano System Builder快速搭建框架
对于初学者,手动分配所有引脚和约束是一件繁琐且易错的工作。Terasic(DE0-Nano的生产商)提供了一个非常实用的工具:DE0-Nano System Builder。它本质上是一个图形化的引脚分配和顶层模块生成器。
它的用法很简单。找到开发板资料光盘或官网下载包中的Tools\DE0_NANO_SystemBuilder目录,直接运行DE0_NANO_SystemBuilder.exe即可,无需安装。工具界面清晰:
- 输入项目名称:例如
led_blink。这将成为你顶层Verilog模块的名字。 - 系统配置:这里以复选框形式列出了板载的所有资源:时钟、按键、LED、ADC、加速度计等。你只需要勾选你设计里会用到的部分。例如,如果你只用到LED和按键,就只勾选
LED和KEY。工具会自动为你生成这些信号对应的引脚分配。 - GPIO扩展:如果你使用了官方的扩展板(如LCD、VGA等),可以在这里选择对应的型号,工具会生成相应的引脚定义。如果是自定义扩展板,选择
GPIO Default,然后你需要根据自己定义的引脚功能,在生成的qsf文件中手动修改信号名和方向。 - 生成项目:点击
Generate按钮。工具会在当前目录下创建一个以项目名命名的文件夹,里面包含:<project_name>.v:顶层Verilog文件,声明了所有你用到的I/O端口。<project_name>.qpf和<project_name>.qsf:Quartus项目文件和设置文件,其中.qsf包含了所有自动生成的引脚位置约束。<project_name>.sdc:时序约束文件模板,通常需要你根据设计修改。<project_name>.htm:引脚分配文档,方便查阅。
实操心得:即使是有经验的开发者,在项目初期我也推荐使用System Builder生成框架。它能确保基本的引脚分配正确无误,避免因引脚号输入错误导致的硬件不工作问题。生成后,你可以把
.v文件作为你的顶层模块,在其中实例化你自己的功能模块。
3. 第一个FPGA项目:从闪烁LED到用户交互
现在,我们抛开System Builder,从头开始手动创建一个完整的Quartus项目,目的是实现一个交互式LED闪烁器。通过这个项目,你将熟悉Quartus开发全流程。
3.1 创建Quartus II工程与器件选择
启动Quartus II,选择File -> New Project Wizard。
- 目录、名称与顶层实体:工作目录建议用一个干净的路径,例如
E:\fpga_projects\my_first_fpga。项目名和顶层实体名都设为my_first_fpga。这里有个关键概念:顶层实体就是你设计中最顶层的模块名,Quartus将从这个模块开始综合整个设计。 - 添加文件:如果已有设计文件(
.v或.bdf),可以在这里添加。我们从头创建,直接点击Next跳过。 - 器件选择:这是至关重要的一步。在
Family中选择Cyclone IV E。在Target device选项中,选择Specific device selected in 'Available devices' list。然后在过滤器中,手动选择EP4CE22F17C6,封装为FBGA,引脚数484,速度等级6。务必与板卡上的芯片丝印核对一致。 - EDA工具设置:综合、仿真等工具暂时用Quartus自带的,默认即可。
- 完成:检查总结信息,点击
Finish。此时一个空的Quartus项目就创建好了。
3.2 设计输入:混合使用原理图与Verilog HDL
Quartus支持多种设计输入方式:原理图、Verilog HDL、VHDL、Block Diagram等。在实际项目中,通常是混合使用的。这里我们采用一种经典的学习路径:用Verilog编写核心功能模块,用原理图(BDF)作为顶层进行模块连接和I/O管理。
3.2.1 编写Verilog计数器模块
我们首先创建一个简单的32位计数器模块。
File -> New -> Verilog HDL File,新建一个Verilog文件。- 输入以下代码并保存为
simple_counter.v:
module simple_counter ( input wire CLOCK_5, // 5MHz时钟输入(由PLL产生) output reg [31:0] counter_out // 32位计数器输出 ); always @ (posedge CLOCK_5) begin counter_out <= counter_out + 1; // 每个时钟上升沿,计数器加1 end endmodule这个模块的行为非常简单:当时钟信号CLOCK_5的上升沿到来时,32位寄存器counter_out的值加1。由于计数器是32位的,从0累加到2^32-1需要很长时间,这为我们观察LED变化提供了基础。
- 将该文件转换为符号(Symbol):
File -> Create/Update -> Create Symbol Files for Current File。这步操作会生成一个simple_counter.bsf文件,允许我们在原理图中像调用一个芯片一样调用这个计数器模块。
3.2.2 使用Megafunction:锁相环PLL
板载晶振是50MHz,直接驱动计数器的话,LED变化太快,人眼无法分辨。我们需要一个更慢的时钟。虽然可以用计数器分频,但更专业、更灵活的方式是使用锁相环PLL。PLL是FPGA内部的硬核电路,可以产生频率稳定、相位可调的新时钟。
- 新建一个Block Diagram/Schematic File (
my_first_fpga.bdf)作为顶层文件。 - 在原理图空白处右键,
Insert -> Symbol,点击Megawizard Plug-In Manager。 - 选择
Installed Plug-Ins -> I/O -> ALTPLL。 - 设定输出文件为Verilog HDL,命名为
pll。 - 在参数配置页面:
- 输入时钟频率
inclk0设为50 MHz。 - 在输出时钟
c0配置页面,我们想要一个5MHz的时钟。由于输入是50MHz,我们将Clock division factor设为10(即50MHz / 10 = 5MHz)。其他选项如相位偏移保持默认。
- 输入时钟频率
- 完成向导,将生成的
pll符号放置到原理图中。
3.2.3 顶层原理图连接与I/O引脚添加
现在,我们在顶层原理图中将各个模块连接起来。
- 添加输入时钟引脚:从符号库
primitives -> pin中找到input符号,放置并命名为CLOCK_50,连接到PLL的inclk0输入口。这代表来自板载50MHz晶振的时钟信号。 - 连接PLL与计数器:使用画线工具,将PLL的
c0输出(5MHz)连接到计数器simple_counter的CLOCK_5输入。 - 添加输出总线与多路选择器:计数器的
counter_out是一个32位总线。我们想用其中不同的位来控制LED的闪烁速度。添加一个lpm_mux(多路选择器)Megafunction,配置为2路输入,每路4位宽。将计数器的bit[26:23]连接到data1x,bit[24:21]连接到data0x。这两组比特位在计数器中的位置不同,因此它们的翻转速度也不同(高位翻转慢,低位翻转快)。 - 添加用户控制引脚:添加一个
input引脚,命名为KEY[0],连接到多路选择器的sel选择端。这样,通过按下按键,就可以选择让哪一组计数器位去驱动LED。 - 添加LED输出引脚:添加一个4位宽的
output引脚,命名为LED[3..0],连接到多路选择器的result输出端。这里的[3..0]是Quartus原理图中表示总线位宽的语法。
至此,一个完整的、带用户交互的LED控制器硬件电路图就设计完成了。其功能是:默认情况下,LED以较慢的速度(由计数器高4位控制)进行二进制计数闪烁;当按下KEY[0]时,LED切换为较快的速度(由计数器稍低的4位控制)闪烁。
3.3 引脚分配、时序约束与设计编译
设计完成后,必须告诉Quartus每个输入输出信号对应到FPGA芯片的哪个物理引脚上。
引脚分配:选择
Assignments -> Pin Planner。你会看到一个表格,列出了设计中的所有I/O信号(CLOCK_50,KEY[0],LED[0]~LED[3])。根据DE0-Nano的用户手册,将信号分配到正确的引脚号:CLOCK_50->PIN_R8KEY[0]->PIN_J15LED[0]->PIN_A15LED[1]->PIN_A13LED[2]->PIN_B13LED[3]->PIN_A11分配完成后,关闭Pin Planner。这些约束会自动保存到.qsf文件中。
时序约束:对于同步设计,必须定义时钟的周期,以便时序分析工具进行优化和验证。我们创建一个简单的
.sdc文件。Tools -> TimeQuest Timing Analyzer。File -> New SDC File。- 输入以下内容:
第一条命令定义了主时钟create_clock -period 20.000 -name CLOCK_50 [get_ports {CLOCK_50}] derive_pll_clocks derive_clock_uncertaintyCLOCK_50的周期为20ns(对应50MHz频率)。后两条命令指示工具自动推导PLL生成的时钟并进行时钟不确定性分析。 - 保存文件为
my_first_fpga.sdc。Quartus会自动识别与顶层模块同名的SDC文件。
全编译:点击工具栏上的蓝色三角形按钮(Start Compilation),或选择
Processing -> Start Compilation。编译过程包括综合、布局布线、时序分析和生成编程文件。在Compilation Report中,重点关注“Timing Analyzer”部分,确保没有时序违规(Slack为正数)。对于这个简单设计,通常都能满足时序要求。
3.4 程序下载与硬件验证
编译成功后会生成一个.sof文件(SRAM Object File)。
- 用USB线连接DE0-Nano和电脑,打开电源。
Tools -> Programmer。- 点击
Hardware Setup,选择USB-Blaster [USB-0]。 - 点击
Auto Detect,软件应能识别出FPGA型号(EP4CE22)。 - 点击
Add File,选择生成的my_first_fpga.sof文件。 - 确保
Program/Configure选项被勾选。 - 点击
Start。进度条走完后,程序即下载到FPGA中。
现在观察板卡:四个LED应该开始缓慢地以二进制形式闪烁。按下KEY[0]按键并保持,LED的闪烁速度应该明显变快。松开按键,恢复慢速。这说明你的第一个FPGA设计已经在硬件上成功运行了!
常见问题与排查:
- LED不亮或常亮:检查引脚分配是否正确,特别是LED的极性(DE0-Nano是低电平点亮)。检查原理图中连线是否正确,总线位宽是否匹配。
- 按键无反应:检查
KEY[0]的引脚分配,并确认原理图中多路选择器的sel端连接正确。按键信号可能需要消抖处理,但在这个简单实验中,由于计数器速度相对较慢,机械抖动的影响不明显。- 编程失败:确认USB-Blaster驱动已安装,线缆连接可靠。尝试给板卡重新上电。在Programmer中尝试
JTAG Chain Debugger功能检查JTAG链路是否畅通。
4. 进阶:构建Nios II软核处理器系统
让LED闪烁只是FPGA的“开胃菜”。FPGA真正的威力在于其可编程性,我们甚至可以在其中“搭建”出一个CPU系统。Altera的Nios II就是一个可以嵌入到FPGA中的软核处理器。下面我们一步步在DE0-Nano上构建一个Nios II系统。
4.1 SOPC Builder系统集成工具入门
SOPC Builder(在新版Quartus中为Platform Designer)是一个图形化的系统集成工具,用于将处理器、存储器控制器、外设IP核等组件连接起来,构成一个完整的片上系统。
- 新建一个Quartus工程,命名为
myfirst_niosii,器件同样选择EP4CE22F17C6。 - 选择
Tools -> SOPC Builder,启动工具。系统名称我们命名为DE0_NANO_SOPC,时钟输入命名为clk_50(50MHz)。
4.2 添加与配置系统组件
一个最小的可运行系统需要三个核心组件:处理器、存储程序和数据的存储器、以及一个用于调试通信的接口。
Nios II Processor:在组件库中选择
Processors -> Nios II Processor。点击Add。- 在配置向导中,选择
Nios II/e经济型内核即可,它占用逻辑资源最少,适合DE0-Nano。 - 其他设置保持默认,点击Finish。在系统窗口中,将其重命名为
cpu。
- 在配置向导中,选择
JTAG UART:这是通过JTAG电缆实现PC与FPGA系统间串行通信的IP核,是调试和打印信息的生命线。添加
Interface Protocols -> Serial -> JTAG UART,使用默认设置,重命名为jtag_uart。On-Chip Memory:Nios II处理器需要内存来存放程序和变量。我们使用FPGA内部的RAM资源。添加
Memories and Memory Controllers -> On-Chip -> On-Chip Memory (RAM or ROM)。- 内存类型选择
RAM (Writable)。 - 数据宽度
32 bits(与Nios II总线位宽一致)。 - 总内存大小:这里需要计算。DE0-Nano的FPGA内部RAM(M9K)总量是~600Kbits。我们设置为
26000 bytes(约25.4KB),这是一个安全且充足的值,确保综合不会因资源不足而失败。重命名为onchip_memory2。
- 内存类型选择
PIO (Parallel I/O):为了控制LED,我们需要一个并行I/O核。添加
Peripherals -> Microcontroller Peripherals -> PIO (Parallel I/O)。- 设置宽度为
8(对应8个LED)。 - 方向选择
Output。 - 重命名为
pio_led。
- 设置宽度为
系统连接与地址分配:SOPC Builder会自动用
Avalon-MM总线将组件互联。我们需要手动为CPU指定启动地址。- 双击
cpu组件进行编辑。 - 在
Reset Vector和Exception Vector的Memory下拉框中,都选择onchip_memory2。这表示处理器上电复位和异常处理都从这片片上内存开始。 - 点击
System -> Auto-Assign Base Addresses和Auto-Assign IRQs,让工具自动分配各组件的内存映射地址和中断号。
- 双击
生成系统:点击
Generate按钮。工具会生成一个代表整个系统的Verilog模块文件(DE0_NANO_SOPC.v)以及一系列支持文件。生成成功后,退出SOPC Builder。
4.3 创建顶层模块与集成
SOPC Builder生成的是一个子系统模块,我们需要一个顶层Verilog文件来将它实例化,并连接到FPGA的物理引脚。
- 新建一个Verilog HDL文件,输入以下代码:
module myfirst_niosii ( input wire CLOCK_50, // 50MHz时钟输入 output wire [7:0] LED // 8位LED输出 ); // 实例化由SOPC Builder生成的系统 DE0_NANO_SOPC DE0_NANO_SOPC_inst ( .clk_50 (CLOCK_50), // 连接50MHz时钟 .out_port_from_the_pio_led (LED), // 连接LED输出 .reset_n (1'b1) // 复位信号拉高(不复位) ); endmodule - 保存为
myfirst_niosii.v,并将其设置为顶层实体(在Project Navigator中右键该文件,选择Set as Top-Level Entity)。 - 进行引脚分配:
CLOCK_50分配至PIN_R8,LED[7:0]依次分配至对应的LED引脚(可参考原理图)。 - 同样需要创建或复用之前的
.sdc文件,定义50MHz的时钟约束。 - 执行全编译。
4.4 Nios II EDS与软件开发
硬件系统(myfirst_niosii.sof)生成后,我们需要为Nios II处理器编写C程序来控制LED。
- 启动Nios II Software Build Tools for Eclipse:在开始菜单或Quartus安装目录下找到并打开Nios II SBT。
- 创建BSP工程:
File -> New -> Nios II Board Support Package。选择刚才SOPC Builder生成的.sopcinfo文件(位于项目目录下)。设定BSP工程名,如myfirst_bsp。在Settings中,特别注意:stdout,stderr,stdin:全部指向jtag_uart。这样printf等函数输出才能通过JTAG在Eclipse的终端显示。Timestamp timer:可以指向cpu自带的定时器,便于软件延时。- 其他保持默认,点击Finish生成BSP。这个工程提供了硬件抽象层(HAL)驱动。
- 创建应用工程:
File -> New -> Nios II Application。选择上一步创建的BSP工程作为基础。命名应用工程,如led_controller。 - 编写C程序:在应用工程的
src文件夹下新建main.c文件。
这段代码不断将一个递增的#include <stdio.h> #include "system.h" #include "altera_avalon_pio_regs.h" int main() { unsigned int count = 0; printf("Nios II LED Controller Started!\n"); while (1) { // 将计数值写入PIO核,从而控制LED IOWR_ALTERA_AVALON_PIO_DATA(PIO_LED_BASE, count); count++; // 简单延时 usleep(200000); // 延时200ms } return 0; }count变量写入pio_led外设的寄存器,从而实现LED流水灯或二进制计数的效果。usleep函数依赖于BSP中配置的定时器。 - 构建项目:右键点击应用工程,选择
Build Project。这会编译C代码,并链接BSP库,最终生成一个.elf可执行文件。
4.5 系统下载与联合调试
这是最后一步,将硬件比特流和软件程序分别下载到FPGA中。
- 下载硬件设计:在Quartus Programmer中,将
myfirst_niosii.sof文件下载到FPGA。 - 运行Nios II程序:在Nios II SBT Eclipse中,右键点击应用工程,选择
Run As -> Nios II Hardware。软件会通过JTAG接口,将.elf文件写入到FPGA的片上内存中,并启动Nios II处理器执行。
此时,你应该能看到DE0-Nano板上的8个LED开始有规律地闪烁或计数。同时,在Eclipse的Nios II Console视图中,可以看到打印出的"Nios II LED Controller Started!"信息。这标志着一个完整的、包含软核处理器和自定义外设的片上系统成功在你的FPGA上运行了起来。
深度解析与避坑指南:
- 资源评估:在SOPC中添加组件时,要时刻留意左下角的资源使用情况。Nios II系统会消耗大量的逻辑单元和内存资源。对于资源紧张的EP4CE22,建议从Nios II/e经济内核开始,并谨慎添加外设。
- 地址空间冲突:如果软件运行时行为异常(如跑飞),首先检查SOPC Builder中各组件的基地址是否有重叠。务必使用
Auto-Assign Base Addresses功能。- 软件找不到硬件:确保
.sof文件已正确下载,且Eclipse中Run Configuration里连接的硬件与实际一致(都是USB-Blaster)。有时需要重启Nios II SBT或重新连接USB线。- printf不输出:检查BSP设置中
stdout是否指向了jtag_uart,并且软件中包含了<stdio.h>。JTAG UART的驱动在HAL中已集成,但需要正确初始化。
从手动配置每一个逻辑门到搭建一个可编程的处理器系统,FPGA开发的不同层次在此展现得淋漓尽致。这个过程里最深刻的体会是,硬件描述语言(如Verilog)定义了电路的结构和行为,而SOPC Builder这类工具则是在更高的抽象层次上进行系统集成。当你看到自己编写的C代码通过自己搭建的硬件系统驱动起真实的LED时,那种对计算机体系结构从底层到上层的贯通感,是单纯学习软件或硬件都无法给予的。后续你可以尝试添加更多外设,如定时器、中断控制器,甚至自定义的Avalon总线外设,从而打造出真正为特定应用量身定制的片上系统。