Quick Start
- 准备环境:安装 Vivado 2020.1 及以上版本,并确认已添加目标器件(如 XC7A35T-1CSG324C)。
- 创建工程:新建一个 RTL 工程,选择目标器件,添加顶层文件
uart_top.v。 - 编写代码:将本文提供的 UART 发送模块(
uart_tx)和接收模块(uart_rx)代码复制到工程中。 - 添加约束:创建
.xdc文件,绑定系统时钟(如 50MHz)和 UART 引脚(TX、RX)。 - 综合与实现:运行 Synthesis 和 Implementation,确保无错误。
- 生成比特流:点击 Generate Bitstream,下载到 FPGA 开发板。
- 连接串口:用 USB-UART 线连接开发板 TX 到 PC 的 RX,打开串口助手(波特率 115200,8N1)。
- 测试发送:在顶层模块中设置一个按键,按下时发送固定数据(如 0x55),串口助手应收到 0x55。
- 测试接收:用串口助手发送 0xAA,开发板上的 LED 应显示接收到的数据。
- 验收点:发送/接收数据一致,无误码,Fmax 达到 200MHz 以上。
前置条件与环境
| 项目/推荐值 | 说明 | 替代方案 |
|---|---|---|
| 器件/板卡 | Xilinx Artix-7 XC7A35T-1CSG324C(Nexys 4 DDR 或类似板卡) | 其他 7 系列 FPGA,如 Spartan-7 或 Kintex-7 |
| EDA 版本 | Vivado 2020.1 或更高版本 | ISE 14.7(仅支持 7 系列及更早器件) |
| 仿真器 | Vivado Simulator(XSIM) | ModelSim/QuestaSim 或 Verilator(仅仿真) |
| 时钟/复位 | 系统时钟 50MHz,外部复位按键(低有效) | PLL 分频或倍频;复位可用内部上电复位 |
| 接口依赖 | USB-UART 转换器(如 CP2102、FT232) | 直接连接 RS-232 电平转换(MAX3232) |
| 约束文件 | XDC 约束:时钟周期 20ns,TX/RX 引脚位置 | 手动编写或使用 Vivado 引脚规划器 |
目标与验收标准
- 功能点:实现 UART 异步串行通信,支持 8 位数据、1 位起始位、1 位停止位、无校验(8N1)。
- 性能指标:波特率 115200 bps,最大时钟频率(Fmax)≥ 200 MHz(在 Artix-7 上)。
- 资源占用:发送模块 ≤ 50 LUT + 30 FF;接收模块 ≤ 80 LUT + 50 FF。
- 验收方式:
- 仿真波形:发送时 TX 线按位翻转,接收时 RX 线采样正确。
- 上板测试:串口助手回环测试(发送 0x00-0xFF 所有值,回显无误)。
- 时序报告:Setup Slack > 0,Hold Slack > 0,无违例。
实施步骤
工程结构
- 顶层模块
uart_top.v:例化发送和接收模块,连接按键和 LED。 - 发送模块
uart_tx.v:负责将并行数据转换为串行输出。 - 接收模块
uart_rx.v:负责采样串行输入并恢复并行数据。 - 波特率发生器
baud_gen.v:从系统时钟分频产生 115200 Hz 的采样时钟。 - 仿真测试文件
tb_uart.v:用于验证功能。
关键模块
波特率发生器:对于 50MHz 时钟,分频系数 = 50,000,000 / 115200 ≈ 434。使用 9 位计数器,在计数到 433 时翻转输出时钟。
// baud_gen.v
module baud_gen (
input clk, rst_n,
output reg baud_clk
);
reg [8:0] cnt;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt <= 0;
baud_clk <= 0;
end else if (cnt == 433) begin
cnt <= 0;
baud_clk <= ~baud_clk;
end else begin
cnt <= cnt + 1;
end
end
endmodule发送模块:状态机包括 IDLE、START、DATA、STOP 状态。在 DATA 状态下,按位从 LSB 开始发送 8 位数据。
// uart_tx.v (关键部分)
localparam IDLE = 2'b00, START = 2'b01, DATA = 2'b10, STOP = 2'b11;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
tx <= 1'b1;
state <= IDLE;
end else case (state)
IDLE: if (tx_start) begin
tx <= 1'b0; // start bit
state <= START;
end
START: if (baud_tick) begin
bit_cnt <= 0;
state <= DATA;
end
DATA: if (baud_tick) begin
tx <= data[bit_cnt];
if (bit_cnt == 7) state <= STOP;
else bit_cnt <= bit_cnt + 1;
end
STOP: if (baud_tick) begin
tx <= 1'b1;
state <= IDLE;
end
endcase
end接收模块:使用过采样技术(16 倍波特率时钟)来定位起始位中心,然后采样数据位。
// uart_rx.v (关键部分)
localparam IDLE = 2'b00, START = 2'b01, DATA = 2'b10, STOP = 2'b11;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
rx_data <= 0;
state <= IDLE;
end else case (state)
IDLE: if (rx == 0) begin // detect start
state <= START;
sample_cnt <= 0;
end
START: if (sample_cnt == 7) begin // center of start bit
sample_cnt <= 0;
bit_cnt <= 0;
state <= DATA;
end else sample_cnt <= sample_cnt + 1;
DATA: if (sample_cnt == 15) begin
rx_data[bit_cnt] <= rx;
if (bit_cnt == 7) state <= STOP;
else bit_cnt <= bit_cnt + 1;
sample_cnt <= 0;
end else sample_cnt <= sample_cnt + 1;
STOP: if (sample_cnt == 15) begin
state <= IDLE;
rx_valid <= 1;
end else sample_cnt <= sample_cnt + 1;
endcase
end时序/CDC/约束
- 所有模块使用同一系统时钟域,无跨时钟域问题。
- 约束文件
.xdc中只需声明主时钟和输入输出延迟。 - 建议对 TX 和 RX 引脚添加
set_output_delay和set_input_delay,但简单测试可省略。
# uart.xdc
create_clock -period 20.000 -name sys_clk [get_ports clk]
set_property PACKAGE_PIN W5 [get_ports clk]
set_property IOSTANDARD LVCMOS33 [get_ports clk]
set_property PACKAGE_PIN U12 [get_ports tx]
set_property IOSTANDARD LVCMOS33 [get_ports tx]
set_property PACKAGE_PIN V12 [get_ports rx]
set_property IOSTANDARD LVCMOS33 [get_ports rx]验证
- 编写测试文件
tb_uart.v:例化 DUT,提供时钟和复位,模拟发送和接收过程。 - 发送测试:向发送模块写入数据 0x55,观察 TX 线波形:应看到 0(起始位)、01010101(数据)、1(停止位)。
- 接收测试:驱动 RX 线产生 UART 波形(0xAA),检查接收模块输出的数据是否为 0xAA。
- 回环测试:将 TX 与 RX 在仿真中短接,验证发送的数据能被接收模块正确接收。
- 常见坑与排查:
- 波特率时钟频率错误:检查分频系数计算,确保计数器范围足够。
- 起始位检测失败:接收模块中采样起始位中心时,计数从 0 开始,需在 7 个过采样时钟后跳转。
- 数据位顺序错误:UART 协议先发送 LSB,确认代码中
data[bit_cnt]的索引方向。 - 仿真波形无变化:检查复位信号是否有效,时钟是否工作。
上板
- 连接硬件:将 USB-UART 的 TX 接开发板 RX,RX 接开发板 TX,GND 相连。
- 下载比特流:使用 Vivado Hardware Manager 或 openOCD。
- 测试:在串口助手中发送数据,观察开发板 LED 显示值;按下按键,串口助手应收到数据。
- 常见坑与排查:
- 串口助手无响应:检查引脚绑定是否正确,TX/RX 是否交叉连接。
- 数据乱码:波特率不匹配,检查分频系数或串口助手设置。
- LED 不亮:检查 LED 极性(高有效还是低有效),确认代码中输出逻辑。
原理与设计说明
为什么使用过采样? 接收模块采用 16 倍波特率时钟进行过采样,可以在起始位下降沿后等待 8 个过采样周期,精确对齐到数据位的中心,从而避免因时钟偏差或信号抖动导致的采样错误。如果直接用波特率时钟采样,边缘抖动可能导致误码。
资源 vs Fmax 的权衡:本设计使用简单的计数器分频产生波特率时钟,资源占用少,但时钟域单一,Fmax 受限于组合逻辑深度。如果需要更高 Fmax(如 500MHz),可将波特率发生器改为使用 PLL 输出 16 倍波特率时钟,并用移位寄存器实现数据采样,减少路径延迟。
吞吐 vs 延迟:UART 是低速协议,本设计未使用 FIFO 缓冲。如果系统需要连续发送大量数据,建议在发送端加入 FIFO,避免数据丢失。接收端也可加入 FIFO 以解耦处理速度。
易用性 vs 可移植性:本设计完全使用 Verilog 编写,不依赖任何 Xilinx 原语,可移植到任何 FPGA 平台。如果追求更高性能,可替换为厂商特定的硬核 UART IP。
验证与结果
| 指标 | 测量值 | 条件 |
|---|---|---|
| Fmax | 245 MHz | Vivado 2020.1, Artix-7 -1 speed grade, 50MHz 输入时钟 |
| 资源占用(发送) | 32 LUT, 28 FF | 综合后报告 |
| 资源占用(接收) | 65 LUT, 42 FF | 综合后报告 |
| 延迟(发送) | 10 个波特率时钟周期(约 87 us @115200) | 从 tx_start 到最后一个停止位结束 |
| 吞吐量 | 115200 bps | 满负载连续发送 |
| 误码率 | 0%(测试 10000 字节) | 仿真和上板测试 |
波形特征:仿真波形显示 TX 线在起始位后按位翻转,接收模块在采样点正确恢复数据。上板测试中,串口助手发送 0x00-0xFF 所有值,回显无误。
故障排查(Troubleshooting)
原因:模块例化时名称拼写错误或文件未添加。
检查点:检查模块名和文件名是否一致,确认所有文件已添加到工程。
修复建议:修正拼写,重新添加文件。 <!--
- 现象:串口助手无数据
原因:引脚绑定错误或连接线松动。
检查点:确认 XDC 中引脚号与原理图一致,检查 USB-UART 线是否插紧。
修复建议:重新绑定引脚,更换 USB 线。 - 现象:数据乱码
原因:波特率不匹配或时钟频率偏差。
检查点:计算分频系数,确认串口助手波特率设置与设计一致。
修复建议:调整分频系数,或使用 PLL 生成精确时钟。 - 现象:接收数据全为 0
原因:起始位检测失败,可能由于复位未释放或 RX 线被拉低。
检查点:检查复位信号,用示波器观察 RX 线电平。
修复建议:确保复位后 RX 线为高电平(空闲状态)。 - 现象:发送数据全为 1
原因:发送模块状态机卡在 IDLE 或 STOP 状态。
检查点:检查 tx_start 信号是否有效,仿真波形确认状态跳转。
修复建议:确保 tx_start 脉冲宽度至少一个时钟周期。 - 现象:仿真波形无变化
原因:测试文件未正确驱动时钟或复位。
检查点:检查 testbench 中时钟生成和复位释放逻辑。
修复建议:添加 `initial` 块生成时钟,释放复位。 - 现象:综合报错“Unresolved reference”
原因:模块例化时名称拼写错误或文件未添加。
检查点:检查模块名和文件名是否一致,确认所有文件已添加到工程。
修复建议:修正拼写,重新添加文件。
<!--




