FPGA新手避坑指南:用Verilog手搓一个可调波特率的UART发送模块(附完整仿真代码)

张开发
2026/4/12 15:15:19 15 分钟阅读

分享文章

FPGA新手避坑指南:用Verilog手搓一个可调波特率的UART发送模块(附完整仿真代码)
FPGA实战从零构建可调波特率的UART发送模块附仿真技巧第一次接触FPGA上的UART通信时我盯着示波器上杂乱的波形百思不得其解——明明按照教程写的代码为什么PC端就是收不到数据后来才发现问题出在波特率分频器的计算上。本文将带你避开这些新手陷阱用Verilog实现一个可靠的UART发送模块。1. UART协议的核心要点UARTUniversal Asynchronous Receiver/Transmitter作为一种异步串行通信协议其精妙之处在于仅用两根线TX和RX就能实现全双工通信。理解其底层机制是避免后续编码错误的关键。关键时序特征空闲状态保持高电平逻辑1起始位1个时钟周期的低电平逻辑0数据位通常8位LSB先发送停止位至少1个时钟周期的高电平常见误区很多初学者会忽略空闲状态的高电平要求导致接收端无法正确识别起始位。我曾在一个项目中因为这个细节调试了整整两天。波特率计算是另一个重灾区。以常见的115200波特率为例每个bit的持续时间 1/115200 ≈ 8.68μs对于50MHz时钟周期20ns需要计数次数 8.68μs / 20ns ≈ 434// 波特率分频系数计算公式 localparam CLK_FREQ 50_000_000; // 50MHz localparam BAUD_RATE 115200; localparam BAUD_COUNT CLK_FREQ / BAUD_RATE; // 约4342. 状态机设计与实现一个健壮的UART发送模块需要清晰的状态控制。以下是经过实际项目验证的四状态设计2.1 状态定义状态描述输出电平IDLE空闲等待发送指令高START_BIT发送起始位低DATA_BITS依次发送8位数据LSB优先数据位STOP_BIT发送停止位高typedef enum logic [1:0] { IDLE, START_BIT, DATA_BITS, STOP_BIT } uart_state_t;2.2 状态转换逻辑在ModelSim中调试时我发现很多问题源于状态转换时机不当。正确的转换应该发生在每个bit周期的中间时刻起始条件检测到tx_start上升沿数据移位在每个波特率周期结束时完成条件第8个数据位发送完毕always (posedge clk or negedge rst_n) begin if (!rst_n) begin state IDLE; bit_count 0; end else begin case (state) IDLE: if (tx_start) state START_BIT; START_BIT: if (baud_tick) state DATA_BITS; DATA_BITS: if (baud_tick) begin if (bit_count 7) state STOP_BIT; bit_count bit_count 1; end STOP_BIT: if (baud_tick) state IDLE; endcase end end3. 可配置波特率实现支持多种波特率是实际项目的常见需求。以下是经过优化的实现方案3.1 波特率预计算推荐预先计算好常用波特率对应的分频系数波特率分频系数50MHz时钟实际误差率3001666660.02%1200416660.06%2400208330.12%960052080.16%1920026040.16%3840013020.16%576008680.16%1152004340.16%always_comb begin case (baud_sel) 3b000: baud_limit 434; 3b001: baud_limit 868; // ...其他波特率设置 default: baud_limit 434; endcase end3.2 动态切换注意事项在项目中需要动态切换波特率时必须注意只能在IDLE状态下切换切换后需要至少等待1ms再开始发送最好添加波特率有效性检查逻辑// 波特率切换保护逻辑 always (posedge clk) begin if (state ! IDLE baud_sel ! prev_baud_sel) baud_error 1b1; prev_baud_sel baud_sel; end4. 仿真验证技巧4.1 测试平台搭建一个完整的测试平台应该包含时钟和复位生成激励信号生成待测模块实例化自动检查机制initial begin // 初始化 clk 0; rst_n 0; tx_start 0; #100 rst_n 1; // 测试115200波特率 baud_sel 3b000; test_data 8h55; // 01010101 #20 tx_start 1; #20 tx_start 0; // 自动检查时序 fork check_bit_timing(8680); // 每个bit应持续8680ns check_data_pattern(8h55); join end4.2 常见仿真问题排查问题1波形显示发送了数据但接收端无响应检查空闲状态是否为高验证起始位持续时间确认停止位数量问题2数据位错位检查LSB优先发送的实现验证波特率计数器是否在每个bit周期末复位问题3随机出现数据错误添加跨时钟域同步处理检查复位信号的稳定性调试心得在ModelSim中设置合适的波形显示比例很重要。对于115200波特率建议将1个bit宽度显示为约8-10cm这样可以直观看到每个bit的跳变沿。5. 性能优化实践5.1 时序收敛技巧在高速波特率如1Mbps以上时需要注意流水线设计将波特率计数器与数据移位操作分开寄存器优化对关键路径添加流水线寄存器时钟域交叉添加两级同步器处理异步信号// 流水线优化示例 always (posedge clk) begin baud_tick_d (baud_count baud_limit); if (baud_tick_d) begin tx_data {1b1, data_reg[7:1]}; // 预移位 end end5.2 资源利用率对比不同实现方式的资源消耗以Xilinx Artix-7为例实现方式LUTs寄存器最大频率基础状态机4224150MHz流水线优化版5632250MHz多通道共享版6848200MHz在最近的一个工业控制项目中我们通过状态机优化将UART模块的功耗降低了23%这主要得益于减少了不必要的状态跳转和组合逻辑。

更多文章