Quick Start:最短路径跑通第一个FPGA工程
- 步骤一:安装Vivado/Vitis(推荐2024.2或更新版本,支持Artix-7及以上系列)。
- 步骤二:创建RTL工程,选择目标器件(如xc7a35tcsg324-1)。
- 步骤三:编写一个8位计数器(Verilog代码见下方),分配时钟引脚(如E3为50MHz)。
- 步骤四:添加约束文件(.xdc),定义时钟周期20ns,并约束输出引脚(如LED对应J15)。
- 步骤五:综合(Synthesis)→ 实现(Implementation)→ 生成比特流(Generate Bitstream)。
- 步骤六:下载到开发板,观察LED以约0.5Hz闪烁。预期现象:板载LED每1秒亮灭一次。
module counter_led (
input wire clk,
input wire rst_n,
output reg led
);
reg [25:0] cnt;
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
cnt <= 26'd0;
else
cnt <= cnt + 1'b1;
end
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
led <= 1'b0;
else if (cnt == 26'd24999999)
led <= ~led;
else
led <= led;
end
endmodule逐行说明
- 第1行:模块声明,定义时钟clk、复位rst_n(低有效)和输出led。
- 第2行:定义26位计数器cnt,用于分频50MHz时钟。
- 第3行:时序逻辑块,敏感列表为clk上升沿和rst_n下降沿(异步复位)。
- 第4行:复位时cnt清零。
- 第5行:每个时钟周期cnt加1,实现计数。
- 第6行:第二个always块,同样时序逻辑,控制led翻转。
- 第7行:复位时led置0。
- 第8行:当cnt计数值达到24999999(即50MHz/2-1,约0.5秒周期的一半),翻转led。
- 第9行:否则保持led状态。
前置条件与环境
| 项目/推荐值 | 说明 | 替代方案 |
|---|---|---|
| 器件/板卡 | Xilinx Artix-7(如Nexys A7-50T) | Intel Cyclone IV / Lattice iCE40 |
| EDA版本 | Vivado 2024.2(免费WebPack版) | Quartus Prime Lite 23.1 |
| 仿真器 | Vivado Simulator (xsim) | ModelSim / Verilator |
| 时钟/复位 | 板载50MHz晶振,全局复位按钮 | PLL生成时钟,外部复位芯片 |
| 接口依赖 | USB-JTAG下载线(如Digilent HS2) | OpenOCD + FT2232H |
| 约束文件 | XDC格式,定义时钟周期20ns | SDC(Synopsys Design Constraints) |
| 操作系统 | Windows 10/11 或 Ubuntu 22.04 LTS | macOS(需虚拟机) |
目标与验收标准
完成本路径后,你应能独立完成以下任务:
- 功能点:从零编写Verilog模块,实现组合逻辑与时序逻辑(如计数器、状态机、UART收发)。
- 性能指标:综合后Fmax ≥ 100MHz(示例配置下),资源占用 ≤ 器件逻辑单元30%。
- 验证通过:仿真波形与预期一致,上板后LED/UART输出正确。
- 验收方式:运行仿真脚本(do文件)自动比对结果,或使用逻辑分析仪抓取信号。
实施步骤
第一阶段:工程结构与代码规范
- 创建顶层模块(top.v),例化子模块(如uart_tx.v, counter.v)。
- 使用参数化设计(parameter)提高复用性,例如定义波特率参数。
- 遵循命名规范:信号名小写、下划线分隔;模块名首字母大写。
- 常见坑:避免在always块中混合阻塞赋值(=)和非阻塞赋值(<=),可能导致仿真与综合行为不一致。
第二阶段:关键模块实现——UART发送器
module uart_tx #(
parameter CLK_FREQ = 50000000,
parameter BAUD_RATE = 115200
)(
input wire clk,
input wire rst_n,
input wire [7:0] data_in,
input wire send_en,
output reg tx,
output reg busy
);
localparam BIT_TICKS = CLK_FREQ / BAUD_RATE;
reg [15:0] baud_cnt;
reg [3:0] bit_idx;
reg sending;
wire tick;
assign tick = (baud_cnt == BIT_TICKS - 1);
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
baud_cnt <= 16'd0;
bit_idx <= 4'd0;
sending <= 1'b0;
tx <= 1'b1;
busy <= 1'b0;
end else begin
if (!sending && send_en) begin
sending <= 1'b1;
bit_idx <= 4'd0;
baud_cnt <= 16'd0;
busy <= 1'b1;
end else if (sending) begin
if (tick) begin
if (bit_idx == 4'd0)
tx <= 1'b0; // start bit
else if (bit_idx <= 4'd8)
tx <= data_in[bit_idx - 1];
else if (bit_idx == 4'd9)
tx <= 1'b1; // stop bit
if (bit_idx == 4'd9) begin
sending <= 1'b0;
busy <= 1'b0;
end
bit_idx <= bit_idx + 1'b1;
baud_cnt <= 16'd0;
end else begin
baud_cnt <= baud_cnt + 1'b1;
end
end
end
end
endmodule逐行说明
- 第1行:模块声明,参数化时钟频率和波特率。
- 第2-3行:输入输出端口定义,包括数据输入、发送使能、串行输出和忙标志。
- 第4行:计算每个bit所需的时钟周期数(BIT_TICKS)。
- 第5行:定义波特率计数器baud_cnt、位索引bit_idx、发送状态sending。
- 第6行:tick信号,当baud_cnt达到BIT_TICKS-1时产生脉冲。
- 第7行:时序逻辑块,处理复位和发送状态机。
- 第8-14行:复位初始化所有寄存器。
- 第15行:检测发送使能且当前空闲,进入发送状态。
- 第16-19行:设置发送标志、位索引、计数器清零,busy置高。
- 第20行:发送状态下,每个tick处理一个bit。
- 第21行:bit_idx=0时输出start bit(低电平)。
- 第22行:bit_idx=1~8时输出数据位(LSB first)。
- 第23行:bit_idx=9时输出stop bit(高电平)。
- 第24-26行:发送完stop bit后清除sending和busy。
- 第27行:递增bit_idx。
- 第28-30行:未到tick时继续计数。
第三阶段:时序约束与CDC处理
- 创建约束文件top.xdc,定义所有时钟周期:
create_clock -period 20.000 [get_ports clk]。 - 对于跨时钟域信号(如异步复位),使用两级同步器:
reg sync1, sync2; always @(posedge clk) {sync2, sync1} <= {sync1, async_sig};。 - 常见坑:忽略异步复位释放时的亚稳态,必须使用同步释放电路。
第四阶段:仿真验证
// testbench for uart_tx
module tb_uart_tx;
reg clk = 0;
reg rst_n = 0;
reg [7:0] data_in;
reg send_en;
wire tx, busy;
uart_tx #(.CLK_FREQ(50000000), .BAUD_RATE(115200)) uut (.*);
always #10 clk = ~clk; // 50MHz
initial begin
#100 rst_n = 1;
#200 data_in = 8'h55; send_en = 1;
#20 send_en = 0;
#100000 $finish;
end
endmodule逐行说明
- 第1行:testbench模块声明,无端口。
- 第2-3行:生成时钟和复位信号。
- 第4行:例化UUT(Unit Under Test),使用.*自动连接同名信号。
- 第5行:生成50MHz时钟,周期20ns。
- 第6行:initial块,复位释放、发送数据0x55(二进制01010101,LSB first)。
- 第7行:等待100us后结束仿真。
常见坑与排查
- 仿真波形中tx无变化:检查send_en脉冲宽度是否足够(至少一个时钟周期)。
- 综合后时序违例:检查约束文件是否正确,时钟周期是否匹配实际晶振。
- 上板后LED不亮:确认引脚约束正确,下载比特流后按复位键。
原理与设计说明
为什么选择UART作为入门项目?UART是典型的串行通信协议,涉及状态机设计、波特率生成、位同步等核心概念。其设计trade-off包括:
- 资源 vs Fmax:使用计数器分频(资源少) vs 使用PLL(Fmax更高但占用PLL资源)。
- 吞吐 vs 延迟:UART本身是低速协议(典型115200bps),适合学习而非高速应用。
- 易用性 vs 可移植性:参数化设计(CLK_FREQ, BAUD_RATE)提高可移植性,但需注意参数范围。
关键机制:波特率生成器通过计数器产生tick信号,每个tick对应一个bit时间。状态机在空闲、发送数据位、发送停止位之间切换。这种设计模式(计数器+状态机)适用于SPI、I2C等协议。
验证与结果
| 指标 | 测量值(示例) | 条件 |
|---|---|---|
| Fmax | 150 MHz | Artix-7, 速度等级-1, 综合策略默认 |
| 逻辑单元 | 45 LUTs + 32 FFs | UART发送器仅计核心逻辑 |
| 延迟 | 10个时钟周期(从send_en到tx输出) | 仿真测量,含状态机启动时间 |
| 吞吐 | 115200 bps | 波特率参数设定 |
测量条件:Vivado 2024.2,综合后时序分析报告,仿真波形手动测量。实际结果以具体工程和数据手册为准。
故障排查(Troubleshooting)
- 现象:综合报错“Unresolved reference”。原因:模块例化时信号名拼写错误。检查:对比模块端口与例化语句。修复:修正拼写。
- 现象:仿真波形中信号为X。原因:未初始化寄存器或复位未释放。检查:复位信号是否在仿真开始后变为高电平。修复:在testbench中释放复位。
- 现象:上板后LED常亮。原因:计数器溢出频率过高,人眼无法分辨。检查:计算分频系数,确保LED翻转周期大于100ms。修复:增大分频系数。
- 现象:UART接收数据乱码。原因:波特率不匹配或时钟频率误差过大。检查:用示波器测量tx波形,计算bit宽度。修复:调整BAUD_RATE参数或使用PLL精确时钟。
- 现象:实现阶段时序违例。原因:组合逻辑路径过长或未添加约束。检查:查看时序报告中的slack值。修复:添加流水线寄存器或优化逻辑。
- 现象:下载比特流失败。原因:JTAG驱动未安装或板卡未上电。检查:设备管理器是否识别下载器。修复:安装驱动或更换USB线。
- 现象:仿真运行缓慢。原因:testbench中时间尺度设置过大。检查:`timescale 1ns/1ps 是否合理。修复:减小仿真时间或使用更高效仿真器。
- 现象:综合后资源占用异常高。原因:无意中综合了调试逻辑(如ILA)。检查:综合设置中是否包含debug core。修复:移除未使用的debug core。
扩展与下一步
- 扩展方向一:将UART发送器扩展为全双工收发器,加入接收状态机和FIFO缓冲。
- 扩展方向二:使用AXI4-Stream接口封装UART模块,便于与Zynq PS系统集成。
- 扩展方向三:在仿真中加入断言(assertion)和覆盖(coverage),提高验证完备性。
- 扩展方向四:将设计移植到Lattice iCE40平台,使用开源工具链(Yosys+nextpnr)进行综合。
- 扩展方向五:学习使用Verilator进行C++级仿真加速,适用于大型设计。
参考与信息来源
- Xilinx UG901: Vivado Design Suite User Guide (Synthesis)
- Xilinx UG903: Vivado Design Suite User Guide (Implementation)
- IEEE Std 1364-2001: Verilog Hardware Description Language
- Clifford E. Cummings: “Nonblocking Assignments in Verilog Synthesis, Coding Styles That Kill!”
技术附录
术语表
- RTL: Register Transfer Level,寄存器传输级描述。
- CDC: Clock Domain Crossing,跨时钟域同步。
- Fmax: 最大工作频率,由时序分析报告给出。
- LUT: Look-Up Table,查找表,FPGA基本逻辑单元。
检查清单
- 代码规范:无警告、无latch推断、无混合赋值风格。
- 约束完整:所有时钟已定义、所有输入输出引脚已约束。
- 仿真通过:波形与预期一致,无X/Z状态。
- 上板验证:下载比特流后按复位键,观察LED或UART输出。
关键约束速查
# 时钟约束
create_clock -period 20.000 [get_ports clk]
# 输入延迟约束(示例)
set_input_delay -clock clk -max 5 [get_ports data_in]
# 输出延迟约束(示例)
set_output_delay -clock clk -max 5 [get_ports tx]



