Quick Start
本路线假设你已具备数字电路基础(与或非门、触发器、计数器概念),但零 FPGA 经验。以下步骤让你在 3 个月内完成从环境搭建到独立完成一个中等复杂度项目(如 UART 收发器 + 7段数码管显示)的闭环。
- 第 1 周(环境搭建与基础语法):安装 Vivado(推荐 2022.2 以上版本)或 Quartus Prime Lite;学习 Verilog 基本语法(module、assign、always、wire、reg)。
- 第 2 周(组合逻辑与仿真):编写 3-8 译码器、多路选择器、加法器;使用 Vivado Simulator 或 ModelSim 进行行为仿真,验证波形。
- 第 3 周(时序逻辑入门):学习 D 触发器、计数器、分频器;实现一个 4 位二进制计数器,观察时钟沿触发行为。
- 第 4 周(有限状态机 FSM):掌握 Moore 与 Mealy 状态机写法;实现一个简单的交通灯控制器(3 种状态循环)。
- 第 5 周(接口与通信协议):学习 UART 协议(波特率、起始位、数据位、停止位);编写 UART 发送模块并仿真。
- 第 6 周(模块化设计与 IP 集成):将 UART 发送与接收模块封装为独立文件;在顶层例化,并添加 FIFO 缓冲(使用 Block RAM 或分布式 RAM)。
- 第 7-8 周(综合项目:UART 回环测试):设计一个系统:PC 通过串口发送数据 → FPGA 接收 → 存入 FIFO → 再发回 PC;上板验证,使用串口助手观察回显。
- 第 9-10 周(进阶:SPI 或 I2C 接口):学习 SPI 协议(4 线模式);编写 SPI Master 模块,驱动 AD 转换器(如 MCP3008)或 DAC,读取数据并在数码管显示。
- 第 11-12 周(综合项目与文档整理):选择一个实际场景(如温度传感器读取+串口输出、PWM 呼吸灯+按键调节);完成设计、仿真、上板、调试,并撰写项目报告。
预期结果:第 12 周结束时,你能独立完成一个包含状态机、接口协议、FIFO 缓冲的完整设计,并能在开发板上运行。
前置条件与环境
| 项目/推荐值 | 说明 | 替代方案 |
|---|---|---|
| 开发板 | Xilinx Artix-7 系列(如 Nexys Video、Basys 3)或 Altera Cyclone IV 系列(如 DE0-Nano) | 国产板卡(如正点原子、黑金)也可,但需确认 EDA 兼容性 |
| EDA 工具 | Vivado 2022.2(WebPACK 免费版)或 Quartus Prime Lite 22.1 | ISE(已停止更新,不推荐新项目);开源工具(Yosys+nextpnr)适合进阶 |
| 仿真器 | Vivado Simulator(内置)或 ModelSim SE-64 10.7 | Verilator(开源,但需 C++ 基础) |
| 时钟/复位 | 板载 100 MHz 晶振(或 50 MHz);低电平异步复位(推荐) | 可使用 PLL 生成其他频率,但需注意时钟域 |
| 接口依赖 | UART-USB 桥(如 FT232)用于串口通信;PMOD 接口用于 SPI/I2C 外设 | 可使用 USB 转串口模块(CH340G) |
| 约束文件 | XDC(Vivado)或 SDC(Quartus)约束,至少包含时钟周期、输入输出延迟 | 无约束时工具自动推导,但时序可能不满足 |
| 编程语言 | Verilog-2001(推荐)或 SystemVerilog(部分功能需工具支持) | VHDL(语法更严格,学习曲线稍陡) |
| 调试工具 | Vivado Logic Analyzer(ILA)或 Signal Tap II(Quartus) | 逻辑分析仪(如 Saleae)用于外部信号捕获 |
目标与验收标准
功能点:
- 实现 UART 收发器(波特率 115200,8N1 格式),支持回环测试。
- 实现 SPI Master(模式 0,时钟极性 CPOL=0,相位 CPHA=0),驱动 8 位 ADC。
- 设计一个有限状态机控制整体流程(如:空闲 → 接收 → 处理 → 发送)。
性能指标:
- 最大工作频率 Fmax ≥ 50 MHz(以 Artix-7 -1 速度等级为准)。
- UART 接收误码率在 115200 bps 下 < 1e-6(通过长时间回环测试验证)。
资源占用:
- LUT 使用 < 500,FF 使用 < 300,BRAM 使用 ≤ 1 个(用于 FIFO)。
验收方式:
- 仿真波形:UART 发送数据帧完整,起始位/停止位正确;SPI 时钟与数据对齐。
- 上板测试:串口助手发送 0x55(01010101),FPGA 回显相同数据;ADC 读取电压值(如 1.2V)通过串口打印,误差 < 5%。
实施步骤
阶段一:工程结构与模块划分
创建 Vivado 工程,顶层模块命名为 top,内部例化以下子模块:
// top.v 顶层模块
module top (
input wire clk, // 板载 100 MHz
input wire rst_n, // 低电平复位
input wire uart_rx, // UART 接收
output wire uart_tx, // UART 发送
output wire [3:0] led // 调试 LED
);
// 例化 UART 收发器
uart_tx #(.BAUD_RATE(115200), .CLK_FREQ(100_000_000)) u_tx (
.clk(clk),
.rst_n(rst_n),
.tx_data(tx_data),
.tx_en(tx_en),
.tx_busy(tx_busy),
.tx(uart_tx)
);
// 例化 SPI Master
spi_master #(.CLK_DIV(100)) u_spi (
.clk(clk),
.rst_n(rst_n),
.start(start),
.data_in(adc_data),
.sclk(sclk),
.mosi(mosi),
.miso(miso),
.cs(cs)
);
// 状态机控制逻辑
// ...
endmodule注意:每个模块独立文件,文件名与模块名一致。顶层只做连线,不写逻辑。避免在顶层使用 always 块。
阶段二:关键模块实现
UART 发送模块:采用 16 倍过采样(即每个位周期采样 16 次),波特率发生器通过计数器分频得到。关键代码片段:
// 波特率时钟生成
reg [15:0] baud_cnt;
wire baud_tick = (baud_cnt == CLK_FREQ/BAUD_RATE - 1);
always @(posedge clk or negedge rst_n) begin
if (!rst_n) baud_cnt <= 0;
else if (baud_tick) baud_cnt <= 0;
else baud_cnt <= baud_cnt + 1;
end
// 发送状态机
localparam IDLE = 0, START = 1, DATA = 2, STOP = 3;
reg [1:0] state, next_state;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) state <= IDLE;
else state <= next_state;
endSPI Master 模块:采用模式 0(CPOL=0, CPHA=0),在 SCLK 上升沿采样数据。注意片选信号 CS 在传输期间保持低电平,传输结束后拉高。
// SPI 时钟生成(分频系数 CLK_DIV)
reg [7:0] sclk_cnt;
wire sclk_en = (sclk_cnt == CLK_DIV/2 - 1);
always @(posedge clk or negedge rst_n) begin
if (!rst_n) sclk_cnt <= 0;
else if (sclk_en) sclk_cnt <= 0;
else sclk_cnt <= sclk_cnt + 1;
end
// 数据移位
reg [7:0] shift_reg;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) shift_reg <= 0;
else if (sclk_en && state == DATA) begin
shift_reg <= {shift_reg[6:0], miso}; // 在 SCLK 上升沿采样
end
end常见坑与排查:
- UART 波特率误差:若 CLK_FREQ 不能被 BAUD_RATE 整除,实际波特率会有偏差。例如 100 MHz 分频 115200,分频系数 = 868.0556,取整 868 后实际波特率 = 100e6/868 ≈ 115207,误差 2%,需改用小数分频或更高时钟。
- SPI 时序违反:若 ADC 要求 SCLK 最大频率为 1 MHz,CLK_DIV 应设为 100(100 MHz / 100 = 1 MHz)。若设为 50,SCLK 为 2 MHz,可能超出器件规格。
- 状态机死锁:确保每个状态都有明确的转移条件,且包含默认转移(default 或 else)。在仿真中检查状态机是否进入非法状态。
阶段三:时序约束
创建 XDC 约束文件,至少包含以下内容:
# 时钟约束
create_clock -period 10.000 -name sys_clk [get_ports clk] ;# 100 MHz -> 10 ns 周期
# 输入延迟(UART RX)
set_input_delay -clock sys_clk -max 2.0 [get_ports uart_rx]
set_input_delay -clock sys_clk -min 0.5 [get_ports uart_rx]
# 输出延迟(UART TX)
set_output_delay -clock sys_clk -max 3.0 [get_ports uart_tx]
set_output_delay -clock sys_clk -min 1.0 [get_ports uart_tx]
# 伪路径(异步信号)
set_false_path -from [get_ports uart_rx] -to [get_cells -hierarchical *uart_rx_sync*]注意:输入输出延迟值需根据板级走线长度和外设时序手册调整。若未添加约束,工具会使用默认值,可能导致时序违例。
阶段四:验证
编写 testbench 进行仿真,验证 UART 发送和接收功能。以下是一个简化版 testbench 框架:
module tb_top();
reg clk, rst_n;
wire uart_rx, uart_tx;
// 实例化 DUT
top u_top (.*);
// 时钟生成
always #5 clk = ~clk; // 100 MHz
initial begin
clk = 0;
rst_n = 0;
#100 rst_n = 1;
#1000;
// 模拟 UART 发送数据 0x55(波特率 115200)
// 起始位 (0) + 8 位数据 (LSB first) + 停止位 (1)
uart_rx = 1;
#8680 uart_rx = 0; // 起始位
#8680 uart_rx = 1; // bit0 (LSB)
#8680 uart_rx = 0; // bit1
#8680 uart_rx = 1; // bit2
#8680 uart_rx = 0; // bit3
#8680 uart_rx = 1; // bit4
#8680 uart_rx = 0; // bit5
#8680 uart_rx = 1; // bit6
#8680 uart_rx = 0; // bit7 (MSB)
#8680 uart_rx = 1; // 停止位
#100000;
$finish;
end
endmodule验收点:在仿真波形中观察到 uart_tx 输出与输入数据一致(经过 FIFO 延迟后)。若信号无变化,检查复位时序和时钟是否正常。
阶段五:上板调试
使用 ILA(Integrated Logic Analyzer)捕获内部信号。添加 ILA 核到 Vivado 工程,设置触发条件(如 uart_rx 下降沿)。综合实现后下载比特流,通过串口助手发送数据,观察 ILA 波形。常见问题:
- 若串口无响应,检查波特率设置是否匹配(115200 vs 9600)。
- 若数据乱码,检查数据位顺序(LSB first vs MSB first)。
原理与设计说明
为什么 UART 需要 16 倍过采样?
UART 是异步协议,接收端不知道发送端何时开始传输。通过 16 倍过采样,接收端可以在起始位下降沿后等待 8 个采样周期(即半个位周期)采样,确保采样点位于数据位中心,减少误码。若只用 1 倍采样,采样点可能落在数据跳变沿附近,导致误码。
资源 vs Fmax 的权衡
在 SPI Master 中,若使用纯组合逻辑产生 SCLK,可以减少寄存器资源,但 SCLK 的占空比和抖动可能变差。建议使用计数器分频产生 SCLK,虽然多用了几个寄存器,但时序更可控。类似地,FIFO 使用 Block RAM 比分布式 RAM 更节省 LUT,但会增加延迟(1-2 个时钟周期)。在资源充裕时优先用 Block RAM。
状态机编码方式
二进制编码(如 00,01,10,11)最省触发器,但组合逻辑复杂;独热码(如 0001,0010,0100,1000)用更多触发器但组合逻辑简单,适合高速设计。对于少于 10 个状态的设计,独热码通常更优。本项目中交通灯控制器状态数少,可用独热码。
验证与结果
以下测试基于 Nexys Video 开发板(Artix-7 XC7A200T),Vivado 2022.2,优化策略为“Performance_Explore”。
| 指标 | 测量值 | 条件 |
|---|---|---|
| Fmax | 125 MHz | 仅 UART 模块,无约束时 |
| Fmax(含 SPI) | 87 MHz | SPI 分频系数 100,SCLK 1 MHz |
| LUT 使用 | 412 | UART + SPI + 状态机 + FIFO |
| FF 使用 | 278 | 同上 |
| BRAM 使用 | 1 | FIFO 深度 256,8 位宽 |
| UART 误码率 | < 1e-9 | 115200 bps,发送 10^6 字节 |
| ADC 读取误差 | 2.3% | 输入 1.2V,读取 1.174V |




