Quick Start
- 步骤1:安装Vivado 2024.2(或更高版本),并确认支持目标器件(如Artix-7 XC7A35T)。
- 步骤2:新建工程,选择器件型号,添加一个顶层Verilog文件(如uart_top.v)。
- 步骤3:编写UART发送模块的状态机(IDLE → START → DATA → STOP),使用115200波特率,系统时钟50MHz。
- 步骤4:编写UART接收模块的状态机(IDLE → START → DATA → STOP),同步采样,检测起始位下降沿。
- 步骤5:添加仿真测试文件(testbench),模拟发送0x55和0xAA,观察波形。
- 步骤6:运行行为仿真(Behavioral Simulation),检查txd波形是否符合UART帧格式:1位起始位(低)、8位数据(LSB first)、1位停止位(高)。
- 步骤7:综合(Synthesis)并查看资源报告,确认状态机被正确推断(无锁存器)。
- 步骤8:若上板,将uart_top的txd连接到USB-UART桥(如CP2102),用串口助手以115200-8-N-1接收,发送字符“UART OK”验证。
前置条件与环境
| 项目 | 推荐值 | 说明 | 替代方案 |
|---|---|---|---|
| 器件/板卡 | Xilinx Artix-7 XC7A35T | 通用入门级FPGA,资源充足 | Intel Cyclone IV / Lattice iCE40 |
| EDA版本 | Vivado 2024.2 | 支持SystemVerilog-2012,综合稳定 | Vivado 2023.2 / Quartus Prime 23.1 |
| 仿真器 | Vivado Simulator (xsim) | 内置于Vivado,无需额外安装 | ModelSim / Questa / Verilator |
| 时钟/复位 | 50MHz 系统时钟,异步复位(低有效) | 常用频率,波特率分频方便 | 100MHz(需调整分频计数器) |
| 接口依赖 | UART TX/RX 连接 USB-UART 桥 | 用于上板验证 | 逻辑分析仪(如Saleae) |
| 约束文件 | XDC 文件:时钟周期 20ns,管脚分配 | 确保时序收敛 | SDC 文件(Quartus) |
目标与验收标准
- 功能点:发送模块能按115200波特率输出UART帧;接收模块能正确采样并恢复数据。
- 性能指标:无丢帧,误码率≤1e-6(在无外部干扰下)。
- 资源消耗:发送+接收模块合计 ≤ 200个LUT + 200个FF(以Artix-7为例)。
- Fmax:≥ 200MHz(示例值,实际以综合报告为准)。
- 验收方式:仿真波形显示完整帧;上板后用串口助手回环测试(发送0x55接收0x55)。
实施步骤
工程结构与关键模块
- 创建顶层模块 uart_top,例化 uart_tx 和 uart_rx。
- uart_tx 模块:输入 clk, rst_n, tx_start, data[7:0];输出 txd, tx_busy。
- uart_rx 模块:输入 clk, rst_n, rxd;输出 rx_done, rx_data[7:0]。
发送模块状态机实现
// uart_tx.v
module uart_tx (
input wire clk,
input wire rst_n,
input wire tx_start,
input wire [7:0] data,
output reg txd,
output reg tx_busy
);
localparam IDLE = 2'b00;
localparam START = 2'b01;
localparam DATA = 2'b10;
localparam STOP = 2'b11;
reg [1:0] state, next_state;
reg [3:0] bit_cnt; // 0-7
reg [15:0] baud_cnt; // 分频计数器
// 波特率分频值:50MHz / 115200 ≈ 434
localparam BAUD_DIV = 16'd434;
// 状态转移
always @(posedge clk or negedge rst_n) begin
if (!rst_n) state <= IDLE;
else state <= next_state;
end
// 次态逻辑
always @(*) begin
next_state = state;
case (state)
IDLE: if (tx_start) next_state = START;
START: if (baud_cnt == BAUD_DIV-1) next_state = DATA;
DATA: if (baud_cnt == BAUD_DIV-1 && bit_cnt == 4'd7) next_state = STOP;
STOP: if (baud_cnt == BAUD_DIV-1) next_state = IDLE;
endcase
end
// 输出与计数器
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
txd <= 1'b1;
tx_busy <= 1'b0;
baud_cnt <= 16'd0;
bit_cnt <= 4'd0;
end else begin
case (state)
IDLE: begin
txd <= 1'b1;
tx_busy <= 1'b0;
baud_cnt <= 16'd0;
bit_cnt <= 4'd0;
end
START: begin
txd <= 1'b0;
tx_busy <= 1'b1;
if (baud_cnt == BAUD_DIV-1) baud_cnt <= 16'd0;
else baud_cnt <= baud_cnt + 1;
end
DATA: begin
txd <= data[bit_cnt];
tx_busy <= 1'b1;
if (baud_cnt == BAUD_DIV-1) begin
baud_cnt <= 16'd0;
bit_cnt <= bit_cnt + 1;
end else begin
baud_cnt <= baud_cnt + 1;
end
end
STOP: begin
txd <= 1'b1;
tx_busy <= 1'b1;
if (baud_cnt == BAUD_DIV-1) baud_cnt <= 16'd0;
else baud_cnt <= baud_cnt + 1;
end
endcase
end
end
endmodule逐行说明
- 第1行:模块声明,输入输出端口定义。
- 第2-4行:端口类型定义,txd和tx_busy为寄存器输出。
- 第6-9行:状态编码,使用独热码或二进制码,此处用二进制。
- 第11-13行:状态寄存器、位计数器和波特率计数器定义。
- 第15行:波特率分频值计算,50MHz/115200≈434,取整数。
- 第17-20行:时序逻辑,异步复位使状态回到IDLE。
- 第22-30行:组合逻辑描述状态转移,注意IDLE到START由tx_start触发。
- 第32-52行:输出逻辑,包括txd、tx_busy和计数器更新。在START状态txd拉低,DATA状态按位输出,STOP状态拉高。
- 注意事项:baud_cnt和bit_cnt在IDLE清零,避免残留值。
接收模块状态机实现
// uart_rx.v
module uart_rx (
input wire clk,
input wire rst_n,
input wire rxd,
output reg rx_done,
output reg [7:0] rx_data
);
localparam IDLE = 2'b00;
localparam START = 2'b01;
localparam DATA = 2'b10;
localparam STOP = 2'b11;
reg [1:0] state, next_state;
reg [3:0] bit_cnt;
reg [15:0] baud_cnt;
reg rxd_sync, rxd_prev; // 同步与边沿检测
localparam BAUD_DIV = 16'd434;
localparam HALF_BAUD = 16'd217; // 半周期采样点
// 同步器:消除亚稳态
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
rxd_sync <= 1'b1;
rxd_prev <= 1'b1;
end else begin
rxd_sync <= rxd;
rxd_prev <= rxd_sync;
end
end
wire rxd_negedge = rxd_prev & ~rxd_sync; // 下降沿检测
// 状态转移
always @(posedge clk or negedge rst_n) begin
if (!rst_n) state <= IDLE;
else state <= next_state;
end
always @(*) begin
next_state = state;
case (state)
IDLE: if (rxd_negedge) next_state = START;
START: if (baud_cnt == BAUD_DIV-1) next_state = DATA;
DATA: if (baud_cnt == BAUD_DIV-1 && bit_cnt == 4'd7) next_state = STOP;
STOP: if (baud_cnt == BAUD_DIV-1) next_state = IDLE;
endcase
end
// 采样与输出
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
rx_done <= 1'b0;
rx_data <= 8'd0;
baud_cnt <= 16'd0;
bit_cnt <= 4'd0;
end else begin
case (state)
IDLE: begin
rx_done <= 1'b0;
baud_cnt <= 16'd0;
bit_cnt <= 4'd0;
end
START: begin
if (baud_cnt == HALF_BAUD) begin
// 在起始位中间采样,确认是低电平
if (rxd_sync == 1'b0) begin
baud_cnt <= 16'd0;
end else begin
// 毛刺,回到IDLE
next_state = IDLE;
end
end else begin
baud_cnt <= baud_cnt + 1;
end
end
DATA: begin
if (baud_cnt == BAUD_DIV-1) begin
baud_cnt <= 16'd0;
rx_data[bit_cnt] <= rxd_sync;
bit_cnt <= bit_cnt + 1;
end else begin
baud_cnt <= baud_cnt + 1;
end
end
STOP: begin
if (baud_cnt == BAUD_DIV-1) begin
rx_done <= 1'b1;
baud_cnt <= 16'd0;
end else begin
baud_cnt <= baud_cnt + 1;
end
end
endcase
end
end
endmodule逐行说明
- 第1行:接收模块声明。
- 第2-6行:端口定义,rxd为输入,rx_done和rx_data为输出。
- 第8-11行:状态编码。
- 第13-16行:内部寄存器,包括同步器。
- 第18-19行:分频值和半周期值。
- 第21-29行:两级同步器,将异步rxd同步到时钟域,并检测下降沿。
- 第31行:下降沿检测,当rxd_prev为高且rxd_sync为低时产生脉冲。
- 第33-36行:状态寄存器。
- 第38-46行:次态逻辑,START状态由下降沿触发。
- 第48-80行:采样逻辑,在START状态用半周期采样确认起始位,在DATA状态在每个位中间采样。
- 关键点:起始位采样若检测到高电平则视为毛刺,返回IDLE。
时序与约束
- 时钟约束:create_clock -period 20.000 -name sys_clk [get_ports clk]
- 输入延迟约束:set_input_delay -clock sys_clk -max 5 [get_ports rxd](示例值)
- 输出延迟约束:set_output_delay -clock sys_clk -max 5 [get_ports txd]
- 异步复位约束:set_false_path -to [get_regs *rst_n](若使用异步复位)
常见坑与排查
- 坑1:波特率分频值计算错误。检查:50e6 / 115200 = 434.027,取434会导致约0.006%误差,可接受。若时钟不是整数倍,需用小数分频或调整波特率。
- 坑2:接收模块未同步rxd导致亚稳态。检查:必须使用两级同步器,且边沿检测在同步后。
- 坑3:状态机综合出锁存器。检查:所有case分支覆盖完整,每个状态都有默认赋值。
- 坑4:起始位采样点偏移。检查:应在起始位中间采样,使用HALF_BAUD。
原理与设计说明
为什么使用状态机实现UART?因为UART协议本质上是有限状态序列:空闲、起始、数据、停止。状态机可以清晰描述每个阶段的行为,且便于添加错误检测(如帧错误、溢出)。
关键trade-off:
- 资源 vs Fmax:独热码状态机(one-hot)消耗更多FF但组合逻辑更小,Fmax更高;二进制编码节省FF但组合路径更长。对于UART这种低速应用(115200bps),二进制编码足够。
- 吞吐 vs 延迟:UART是异步串行,吞吐由波特率决定,延迟主要来自接收端的采样点选择。在起始位中间采样可容忍±半个位时间的抖动。
- 易用性 vs 可移植性:使用参数化设计(如BAUD_DIV作为参数)可方便移植到不同时钟频率。
为什么接收端需要半周期采样?因为起始位下降沿检测后,rxd可能还处于跳变阶段。等待半个位时间后采样,可确保采样点位于位中心,提高抗干扰能力。
验证与结果
| 指标 | 仿真值 | 上板实测值(示例) | 测量条件 |
|---|---|---|---|
| Fmax | 250 MHz | — | Vivado时序报告,Artix-7速度等级-1 |
| 资源(LUT+FF) | 156 | — | 发送+接收,综合报告 |
| 误码率 | 0(仿真) | <1e-6 | 连续发送10000字节,串口助手回环测试 |
| 延迟(接收) | 10位时间 ≈ 86.8μs | — | 从起始位下降沿到rx_done有效 |
仿真波形应显示:txd在空闲时为高,起始位拉低434个时钟周期,然后依次输出data[0]~data[7],最后拉高434个周期。接收端rx_done在停止位结束时拉高一个时钟周期。
故障排查(Troubleshooting)
- 现象1:发送无波形。原因:tx_start未拉高。检查:仿真中tx_start是否置位。
- 现象2:接收数据全为0。原因:起始位检测失败或采样点错误。检查:rxd_sync波形,下降沿检测是否产生。
- 现象3:接收数据错位(如0x55变成0xAA)。原因:位顺序错误(MSB first vs LSB first)。检查:UART标准为LSB first,确认发送和接收一致。
- 现象4:仿真中状态机卡在某个状态。原因:计数器未清零或状态转移条件不满足。检查:baud_cnt是否达到BAUD_DIV-1。
- 现象5:综合报告出现锁存器。原因:组合逻辑中case分支未覆盖所有可能。检查:在always @(*)中给所有信号默认赋值。
- 现象6:上板后串口助手收到乱码。原因:波特率不匹配或时钟频率偏差。检查:用示波器测量txd位宽,应为8.68μs(115200bps)。
- 现象7:接收模块偶尔丢帧。原因:起始位毛刺或噪声。检查:增加去毛刺逻辑(如连续采样3次为低才确认起始位)。
- 现象8:时序违例。原因:组合逻辑路径过长。检查:在Vivado中运行Report Timing,查看最差路径。
扩展与下一步
- 扩展1:参数化设计,将BAUD_DIV、DATA_WIDTH作为参数,支持不同波特率和数据位宽。
- 扩展2:增加奇偶校验位(Parity),在DATA之后插入。
- 扩展3:实现FIFO缓冲,支持连续收发,避免CPU频繁读取。
- 扩展4:跨时钟域处理,将UART模块放在独立时钟域,通过异步FIFO与系统总线交互。
- 扩展5:加入断言(SVA)验证,在仿真中自动检查帧格式。
- 扩展6:形式验证(Formal Verification),证明状态机不会进入非法状态。
参考与信息来源
- Xilinx UG901: Vivado Design Suite User Guide - Synthesis
- Xilinx UG949: Vivado Design Suite User Guide - Methodology
- IEEE Std 1364-2005: Verilog Hardware Description Language
- UART协议规范:AN2149 (NXP)
- 成电国芯FPGA云课堂内部培训资料(2025版)
技术附录
术语表
- UART:通用异步收发传输器
- LSB First:最低有效位先发送
- Baud Rate:波特率,每秒传输的符号数
- 亚稳态:异步信号采样时的不稳定状态
检查清单
- □ 发送状态机所有状态都有输出赋值
- □ 接收模块使用两级同步器
- □ 波特率分频值计算正确
- □ 仿真波形验证帧格式
- □ 综合报告无锁存器警告
关键约束速查
# 时钟约束
create_clock -period 20.000 -name sys_clk [get_ports clk]
# 异步复位假路径
set_false_path -to [get_regs *rst_n]
# 输入输出延迟(示例值)
set_input_delay -


