FPGA项目实战:基于UART的通信模块设计与验证

二牛学FPGA
文章2026-04-26
48

Quick Start

  1. 准备环境:安装 Vivado 2020.1 及以上版本,并确认已添加目标器件(如 XC7A35T-1CSG324C)。
  2. 创建工程:新建一个 RTL 工程,选择目标器件,添加顶层文件 uart_top.v
  3. 编写代码:将本文提供的 UART 发送模块(uart_tx)和接收模块(uart_rx)代码复制到工程中。
  4. 添加约束:创建 .xdc 文件,绑定系统时钟(如 50MHz)和 UART 引脚(TX、RX)。
  5. 综合与实现:运行 Synthesis 和 Implementation,确保无错误。
  6. 生成比特流:点击 Generate Bitstream,下载到 FPGA 开发板。
  7. 连接串口:用 USB-UART 线连接开发板 TX 到 PC 的 RX,打开串口助手(波特率 115200,8N1)。
  8. 测试发送:在顶层模块中设置一个按键,按下时发送固定数据(如 0x55),串口助手应收到 0x55。
  9. 测试接收:用串口助手发送 0xAA,开发板上的 LED 应显示接收到的数据。
  10. 验收点:发送/接收数据一致,无误码,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_delayset_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。

验证与结果

指标测量值条件
Fmax245 MHzVivado 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)

  • 现象:综合报错“Unresolved reference”

    原因:模块例化时名称拼写错误或文件未添加。

    检查点:检查模块名和文件名是否一致,确认所有文件已添加到工程。

    修复建议:修正拼写,重新添加文件。

  • <!–
    • 现象:串口助手无数据

      原因:引脚绑定错误或连接线松动。

      检查点:确认 XDC 中引脚号与原理图一致,检查 USB-UART 线是否插紧。

      修复建议:重新绑定引脚,更换 USB 线。

    • 现象:数据乱码

      原因:波特率不匹配或时钟频率偏差。

      检查点:计算分频系数,确认串口助手波特率设置与设计一致。

      修复建议:调整分频系数,或使用 PLL 生成精确时钟。

    • 现象:接收数据全为 0

      原因:起始位检测失败,可能由于复位未释放或 RX 线被拉低。

      检查点:检查复位信号,用示波器观察 RX 线电平。

      修复建议:确保复位后 RX 线为高电平(空闲状态)。

    • 现象:发送数据全为 1

      原因:发送模块状态机卡在 IDLE 或 STOP 状态。

      检查点:检查 tx_start 信号是否有效,仿真波形确认状态跳转。

      修复建议:确保 tx_start 脉冲宽度至少一个时钟周期。

    • 现象:仿真波形无变化

      原因:测试文件未正确驱动时钟或复位。

      检查点:检查 testbench 中时钟生成和复位释放逻辑。

      修复建议:添加 `initial` 块生成时钟,释放复位。

    • 现象:综合报错“Unresolved reference”

      原因:模块例化时名称拼写错误或文件未添加。

      检查点:检查模块名和文件名是否一致,确认所有文件已添加到工程。

      修复建议:修正拼写,重新添加文件。

    • <!–

    分类
    技术分享
    标签
    fpgaUART通信模块
    浏览 48
    分享:

    相关推荐

    同频道 · 相近分类

    暂无相关推荐

    作者

    二牛学FPGA查看主页

    同分类阅读

    文章

    延伸阅读与实操

    • 文章 + 课程联动深度文章常对应体系课章节,可一键选课。
    • 学习产出可参考笔记与作业案例在学习产出广场持续更新。

    探索全站