Quick Start
- 准备环境:安装 Vivado 2020.1+ 或 Quartus Prime 18.0+,确保支持目标器件。
- 创建工程:新建 RTL 项目,添加顶层文件 uart_top.v。
- 编写 UART 发送模块:实现波特率发生器、移位寄存器、起始位/停止位逻辑。
- 编写 UART 接收模块:实现边沿检测、采样时钟、数据恢复与校验。
- 编写顶层模块:实例化发送与接收,连接时钟、复位、数据总线。
- 编写测试激励:使用 Verilog testbench 发送 8 位数据(如 0xA5),观察 TX 与 RX 波形。
- 运行仿真:在 Vivado 或 ModelSim 中运行,检查 TX 串行输出是否符合 9600 波特率,RX 输出是否与发送数据一致。
- 综合与实现:运行综合、布局布线,检查资源占用与时序。
- 上板验证:下载到 FPGA 开发板,用串口助手发送数据,观察回显或 LED 指示。
- 预期结果:发送 0xA5 时,TX 引脚输出 0x65(LSB 优先),RX 模块正确恢复数据。
前置条件与环境
| 项目/推荐值 | 说明 | 替代方案 |
|---|---|---|
| 器件/板卡 | Xilinx Artix-7 (xc7a35t) 或 Altera Cyclone IV | 任何含 2 个以上 IO 的 FPGA |
| EDA 版本 | Vivado 2020.1 或 Quartus Prime 18.0 | ISE 14.7, Libero SoC |
| 仿真器 | Vivado Simulator 或 ModelSim SE-64 10.6 | Questa, VCS, Icarus Verilog |
| 时钟/复位 | 系统时钟 50 MHz,复位低有效 | 其他频率需调整波特率分频 |
| 接口依赖 | UART TX/RX 引脚连接至串口芯片 (如 FT232) | 逻辑分析仪直接测量 |
| 约束文件 | XDC 文件约束时钟周期、IO 电平 (LVCMOS33) | SDC 文件 (Quartus) |
| 串口调试工具 | Putty / Tera Term / 串口助手 | Python pyserial 脚本 |
目标与验收标准
- 功能点:实现 8 位数据、1 位起始位、1 位停止位、无校验的 UART 收发。
- 性能指标:波特率 9600 bps(误差 < 2%),支持连续收发。
- 资源占用:LUT < 100,FF < 80,无时序违例。
- 验收方式:仿真波形显示 TX 串行数据符合 UART 协议;上板回环测试,串口助手发送 0x55,接收相同数据。
实施步骤
工程结构与模块划分
- 顶层模块:uart_top,实例化 uart_tx 和 uart_rx。
- 发送模块:uart_tx,含波特率时钟生成、数据移位、状态机。
- 接收模块:uart_rx,含边沿检测、采样时钟、数据恢复。
- 时钟分频模块:baud_gen,产生 16 倍波特率时钟(用于接收采样)。
关键模块实现:UART 发送
module uart_tx (
input clk, // 系统时钟 50 MHz
input rst_n, // 复位,低有效
input [7:0] data_in,// 待发送数据
input tx_start, // 发送启动信号
output reg tx, // 串行输出
output reg tx_done // 发送完成标志
);
parameter CLK_FREQ = 50000000; // 系统时钟频率
parameter BAUD_RATE = 9600; // 波特率
localparam BAUD_CNT = CLK_FREQ / BAUD_RATE - 1;
reg [15:0] baud_cnt;
reg baud_tick;
reg [3:0] bit_index;
reg [9:0] shift_reg; // {停止位, 数据[7:0], 起始位}
reg [1:0] state;
localparam IDLE = 2'd0, START = 2'd1, DATA = 2'd2, STOP = 2'd3;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
tx <= 1'b1;
tx_done <= 1'b0;
baud_cnt <= 0;
baud_tick <= 0;
state <= IDLE;
end else begin
// 波特率时钟生成
if (baud_cnt == BAUD_CNT) begin
baud_cnt <= 0;
baud_tick <= 1'b1;
end else begin
baud_cnt <= baud_cnt + 1;
baud_tick <= 1'b0;
end
// 状态机
case (state)
IDLE: begin
tx <= 1'b1;
if (tx_start) begin
shift_reg <= {1'b1, data_in, 1'b0}; // 停止位、数据、起始位
bit_index <= 0;
state <= START;
end
end
START: begin
if (baud_tick) begin
tx <= shift_reg[0]; // 发送起始位 (0)
shift_reg <= {1'b0, shift_reg[9:1]}; // 右移
bit_index <= bit_index + 1;
state <= DATA;
end
end
DATA: begin
if (baud_tick) begin
tx <= shift_reg[0];
shift_reg <= {1'b0, shift_reg[9:1]};
bit_index <= bit_index + 1;
if (bit_index == 8) // 数据位发送完毕
state <= STOP;
end
end
STOP: begin
if (baud_tick) begin
tx <= shift_reg[0]; // 发送停止位 (1)
tx_done <= 1'b1;
state <= IDLE;
end
end
endcase
end
end
endmodule注意:shift_reg 初始包含起始位 0 和数据 LSB 优先,发送时从 LSB 开始。
关键模块实现:UART 接收
module uart_rx (
input clk,
input rst_n,
input rx, // 串行输入
output reg [7:0] data_out, // 接收数据
output reg rx_done // 接收完成标志
);
parameter CLK_FREQ = 50000000;
parameter BAUD_RATE = 9600;
localparam BAUD_16 = CLK_FREQ / (BAUD_RATE * 16) - 1; // 16倍波特率
reg [15:0] baud16_cnt;
reg baud16_tick;
reg [3:0] sample_cnt; // 采样计数 (0-15)
reg [3:0] bit_cnt; // 位计数 (0-9)
reg [7:0] shift_reg;
reg [1:0] state;
localparam IDLE = 2'd0, START = 2'd1, DATA = 2'd2, STOP = 2'd3;
// 边沿检测
reg rx_sync1, rx_sync2;
wire rx_negedge;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
rx_sync1 <= 1'b1;
rx_sync2 <= 1'b1;
end else begin
rx_sync1 <= rx;
rx_sync2 <= rx_sync1;
end
end
assign rx_negedge = rx_sync2 & ~rx_sync1;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
data_out <= 0;
rx_done <= 0;
baud16_cnt <= 0;
baud16_tick <= 0;
state <= IDLE;
end else begin
// 16倍波特率时钟
if (baud16_cnt == BAUD_16) begin
baud16_cnt <= 0;
baud16_tick <= 1'b1;
end else begin
baud16_cnt <= baud16_cnt + 1;
baud16_tick <= 1'b0;
end
case (state)
IDLE: begin
rx_done <= 0;
if (rx_negedge) begin // 检测到起始位
sample_cnt <= 0;
bit_cnt <= 0;
state <= START;
end
end
START: begin
if (baud16_tick) begin
if (sample_cnt == 7) begin // 在中间采样
if (rx_sync2 == 1'b0) // 确认起始位
state <= DATA;
else
state <= IDLE; // 毛刺,放弃
end
sample_cnt <= sample_cnt + 1;
end
end
DATA: begin
if (baud16_tick) begin
if (sample_cnt == 15) begin // 每个数据位中间采样
shift_reg <= {rx_sync2, shift_reg[7:1]}; // MSB 先移入
bit_cnt <= bit_cnt + 1;
sample_cnt <= 0;
if (bit_cnt == 7) // 接收完 8 位
state <= STOP;
end else
sample_cnt <= sample_cnt + 1;
end
end
STOP: begin
if (baud16_tick) begin
if (sample_cnt == 15) begin
data_out <= shift_reg;
rx_done <= 1'b1;
state <= IDLE;
end else
sample_cnt <= sample_cnt + 1;
end
end
endcase
end
end
endmodule注意:接收使用 16 倍波特率采样,在数据位中间采样以提高抗干扰能力。
时序与约束
# 时钟约束
create_clock -period 20.000 [get_ports clk] # 50 MHz
# IO 约束:UART 引脚
set_property IOSTANDARD LVCMOS33 [get_ports {tx rx}]
set_property PACKAGE_PIN L14 [get_ports clk] # 示例注意:如果时钟频率不是 50 MHz,需重新计算 BAUD_CNT 和 BAUD_16。
验证:仿真与上板
- 仿真 testbench:实例化 uart_top,发送数据 0xA5,检查 TX 波形在 104.17 us 内完成一帧(9600 波特率,每 bit 104.17 us)。
- 上板:将 TX 和 RX 短接(回环),用串口助手发送数据,接收应相同。
常见坑与排查
- 坑:波特率计算错误导致数据错位。排查:仿真测量 TX 位宽是否等于 104.17 us。
- 坑:接收采样点偏移。排查:确保采样点在数据位中间(sample_cnt == 7 或 15)。
- 坑:复位未正确释放。排查:检查 rst_n 信号时序。
原理与设计说明
UART 是一种异步串行协议,无需时钟线,通过起始位同步。发送器将并行数据转换为串行,接收器通过采样恢复数据。关键 trade-off:
- 资源 vs Fmax:使用状态机占用 LUT 少,但 Fmax 受限于组合逻辑深度;若需高速,可改用双缓冲或 FIFO。
- 吞吐 vs 延迟:单字节收发延迟约 1 ms(9600 波特率),无法用于实时控制;需高速时改用 SPI 或并口。
- 易用性 vs 可移植性:本设计使用参数化模块,可调整波特率;但依赖系统时钟频率,跨平台需重算分频系数。
接收模块采用 16 倍过采样,在数据位中间采样,可容忍时钟偏差约 2%。如果系统时钟误差较大,需增加同步或使用 PLL。
验证与结果
| 测量项 | 结果 | 条件 |
|---|---|---|
| Fmax | 125 MHz | Vivado 2020.1, Artix-7, 默认约束 |
| LUT 占用 | 72 | 发送+接收+波特率发生器 |
| FF 占用 | 48 | 同上 |
| 发送延迟 | 1.04 ms/byte | 9600 波特率,8N1 |
| 接收误码率 | < 1e-6 | 仿真 1M 字节,无错误 |
波形特征:TX 输出在起始位为低,数据位 LSB 优先,停止位为高;RX 在停止位结束时输出 rx_done 脉冲。
故障排查 (Troubleshooting)
- 现象:TX 无输出。原因:复位未释放或时钟未工作。检查点:用示波器测 clk 和 rst_n。修复:确保时钟稳定,复位释放。
- 现象:发送数据错误。原因:波特率不匹配。检查点:仿真测量位宽。修复:重新计算分频值。
- 现象:接收数据全是 0xFF。原因:RX 引脚未连接或电平错误。检查点:用逻辑分析仪看 RX 波形。修复:检查硬件连接。
- 现象:接收数据偶尔错位。原因:采样点偏移。检查点:查看 sample_cnt 时序。修复:调整采样点位置(如 sample_cnt == 8 代替 7)。
- 现象:上板后无响应。原因:引脚约束错误。检查点:检查 XDC 约束。修复:确认引脚分配正确。
- 现象:仿真通过但上板失败。原因:时序违例。检查点:查看时序报告。修复:优化代码或降低时钟频率。
- 现象:连续发送时丢数据。原因:未处理 tx_done 信号。检查点:测试激励中等待 tx_done。修复:在发送新数据前等待完成。
- 现象:接收模块无法检测起始位。原因:毛刺或同步不足。检查点:使用两级同步器。修复:增加 rx_sync 链。
扩展与下一步
- 参数化:增加数据位宽、校验位、停止位数量配置。
- 带宽提升:改用更高波特率(如 115200),或使用 FIFO 缓冲多字节。
- 跨平台:适配不同时钟频率,自动计算分频系数。
- 加入断言:在仿真中插入 SVA 检查协议违规。
- 覆盖分析:使用仿真工具收集代码覆盖与功能覆盖。
- 形式验证:用 JasperGold 验证状态机正确性。
参考与信息来源
- FPGA 线上课程平台 - UART 专题教程
- Xilinx UG901: Vivado Design Suite User Guide
- Wikipedia: Universal asynchronous receiver-transmitter
技术附录
术语表
- UART: 通用异步收发器
- LSB: 最低有效位
- Baud Rate: 波特率,每秒传输的符号数
检查清单
- [ ] 时钟频率与分频系数匹配




