FPGA新手避坑指南:用Verilog手搓一个带FIFO缓冲的UART串口(附完整代码)
2026/5/4 17:45:32 网站建设 项目流程

FPGA实战:从零构建带FIFO缓冲的UART通信系统

在嵌入式系统和数字电路设计中,UART(通用异步收发器)是最基础也最常用的通信接口之一。对于FPGA开发者而言,实现一个稳定可靠的UART通信模块是必备技能。本文将带您从零开始,在Vivado/Quartus环境中构建一个带FIFO缓冲的UART通信系统,重点解决实际开发中的常见陷阱和调试技巧。

1. UART通信基础与FPGA实现挑战

UART作为一种异步串行通信协议,其核心特点是仅需两根信号线(TXD和RXD)即可实现全双工通信。在FPGA中实现UART模块时,我们需要特别关注几个关键参数:

  • 波特率:常见的有9600、115200等,需要精确的时钟分频
  • 数据格式:包括数据位(5-8位)、停止位(1-2位)和可选的奇偶校验位
  • 采样时机:异步通信中,接收端需要在数据位中间点采样以确保稳定性

FPGA实现UART的主要挑战在于:

  1. 精确的时序控制:需要根据系统时钟准确生成波特率时钟
  2. 状态机设计:发送和接收都需要严谨的状态机控制
  3. 跨时钟域问题:当系统时钟与UART波特率不同步时可能出现数据丢失
  4. 数据缓冲需求:处理突发数据传输时需要有适当的缓冲机制

2. 发送模块(TX)设计与关键陷阱

发送模块的核心是一个状态机,典型状态包括:IDLE(空闲)、START(起始位)、DATA(数据位)、CHECK(校验位,可选)和STOP(停止位)。以下是Verilog实现的关键要点:

module tx_uart #( parameter BPS = 115200, // 波特率 parameter CLK = 50_000_000, // 系统时钟频率 parameter DATA_BIT = 8 // 数据位宽 )( input clk, input rst_n, input tx_data_vld, input [DATA_BIT-1:0] tx_data, output reg tx ); localparam BPS_CNT_MAX = CLK/BPS; reg [31:0] bps_cnt; reg [3:0] bit_cnt; reg [2:0] state; always @(posedge clk or negedge rst_n) begin if(!rst_n) begin state <= IDLE; bps_cnt <= 0; bit_cnt <= 0; tx <= 1'b1; end else begin case(state) IDLE: begin if(tx_data_vld) begin state <= START; bps_cnt <= 0; end end START: begin tx <= 1'b0; if(bps_cnt == BPS_CNT_MAX-1) begin state <= DATA; bps_cnt <= 0; end else begin bps_cnt <= bps_cnt + 1; end end // 其他状态类似... endcase end end endmodule

常见陷阱及解决方案:

  1. 波特率精度问题

    • 错误:直接使用除法计算分频系数可能导致累积误差
    • 解决:采用累加器方法或使用更高精度的时钟分频
  2. 状态机跳转时机错误

    • 错误:在波特率计数器未满时就跳转状态
    • 解决:严格在每个状态的最后一个时钟周期跳转
  3. 多bit数据传输顺序混淆

    • 错误:高位先发还是低位先发不明确
    • 解决:统一约定(通常低位先发),并在代码中明确注释

3. 接收模块(RX)设计与抗干扰技巧

接收模块比发送模块更复杂,因为它需要检测起始位并准确采样数据位。关键设计要点包括:

module rx_uart #( parameter BPS = 115200, parameter CLK = 50_000_000, parameter DATA_BIT = 8 )( input clk, input rst_n, input rx, output reg [DATA_BIT-1:0] rx_data, output reg rx_data_vld ); // 双寄存器同步消除亚稳态 reg rx_reg0, rx_reg1; always @(posedge clk or negedge rst_n) begin if(!rst_n) begin rx_reg0 <= 1'b1; rx_reg1 <= 1'b1; end else begin rx_reg0 <= rx; rx_reg1 <= rx_reg0; end end // 下降沿检测起始位 wire start_bit = (rx_reg1 && !rx_reg0);

抗干扰设计技巧:

  1. 亚稳态处理

    • 采用双寄存器同步外部异步信号
    • 添加施密特触发器输入缓冲(如有硬件支持)
  2. 精确采样时机

    • 在数据位中间点采样(通常计数器计到半周期时)
    • 对于高速通信,可采用过采样技术
  3. 错误检测机制

    • 校验位验证
    • 帧错误检测(停止位是否为预期电平)

调试技巧:

  • 在仿真中故意注入抖动和毛刺,验证接收稳定性
  • 使用ILA(集成逻辑分析仪)抓取实际信号波形
  • 测试不同波特率下的兼容性

4. FIFO缓冲集成与系统优化

单纯的UART收发模块在实际应用中往往不够,添加FIFO缓冲可以显著提升系统可靠性。FIFO在UART系统中的作用包括:

功能无FIFO有FIFO
突发数据处理可能丢失缓冲存储
时钟域隔离困难自然隔离
流控实现复杂简单
系统吞吐量

FPGA FIFO实现方案对比:

  1. IP核实现(推荐):

    • 优点:经过验证,资源优化,支持各种配置
    • 缺点:不同厂商/型号间可移植性差
  2. Verilog手写FIFO

    • 优点:完全可控,可定制
    • 缺点:需要自行处理边界条件和时序

Xilinx Vivado中配置FIFO IP核的关键参数:

  • 接口类型:Native
  • 读写位宽:8位(匹配UART)
  • 深度:根据需求选择(通常16-256)
  • 时钟:与系统时钟一致
  • 复位:高电平有效

FIFO集成示例代码:

module uart_fifo_ctrl #( parameter DATA_WIDTH = 8, parameter FIFO_DEPTH = 16 )( input clk, input rst_n, input [DATA_WIDTH-1:0] rx_data, input rx_data_vld, input tx_ready, output [DATA_WIDTH-1:0] tx_data, output tx_data_vld ); wire fifo_empty, fifo_full; reg fifo_rd_en; // FIFO实例化 fifo_generator_0 u_fifo ( .clk(clk), .srst(~rst_n), .din(rx_data), .wr_en(rx_data_vld & !fifo_full), .rd_en(fifo_rd_en), .dout(tx_data), .full(fifo_full), .empty(fifo_empty) ); // FIFO读控制逻辑 always @(posedge clk or negedge rst_n) begin if(!rst_n) begin fifo_rd_en <= 1'b0; end else begin fifo_rd_en <= !fifo_empty && tx_ready; end end assign tx_data_vld = fifo_rd_en; endmodule

FIFO使用中的常见问题:

  1. 溢出处理

    • 监控full信号,避免数据丢失
    • 考虑添加流控机制(如RTS/CTS)
  2. 空读问题

    • 确保empty信号正确连接
    • 避免在empty时发起读操作
  3. 时钟域交叉

    • 如果读写时钟不同,选择异步FIFO
    • 添加足够的同步寄存器

5. 系统集成与实测验证

将各个模块整合成完整系统时,需要注意以下要点:

  1. 顶层设计

    • 清晰定义模块接口
    • 统一时钟和复位策略
    • 合理分配I/O引脚
  2. 测试策略

    • 分层测试:先模块级,后系统级
    • 边界测试:极端数据(全0、全1、交替01)
    • 压力测试:连续高速数据传输

上板调试技巧:

  1. 信号完整性检查

    • 使用示波器观察实际信号质量
    • 检查信号终端匹配是否合适
  2. 交叉验证

    • 与不同设备(PC、MCU等)互连测试
    • 测试不同电缆长度下的稳定性
  3. 性能评估

    • 测量实际达到的波特率
    • 统计误码率

常见问题排查指南:

现象可能原因解决方案
发送数据对方收不到波特率不匹配检查双方波特率设置
接收数据不稳定采样点不准调整采样时机
大数据量时丢失数据FIFO深度不足增大FIFO或添加流控
偶发错误信号干扰检查PCB布局,添加滤波

实际项目中,我曾遇到一个棘手问题:UART在低温环境下出现偶发通信失败。最终发现是时钟源温漂导致波特率偏移,通过改用更稳定的时钟源和添加自动波特率检测功能解决了问题。这提醒我们,实验室测试外,还需要考虑实际工作环境的多样性。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询