【数字IC】从UART协议到Verilog实现:一个IC工程师的实践指南

张开发
2026/4/18 19:42:20 15 分钟阅读

分享文章

【数字IC】从UART协议到Verilog实现:一个IC工程师的实践指南
1. UART协议基础从理论到硬件视角第一次接触UART协议时我被它简单的外表迷惑了——不就是起始位数据位停止位吗直到真正用Verilog实现时才发现这个看似简单的异步协议藏着不少坑。先说说UART的核心特点异步传输意味着没有时钟线同步串行通信则要求严格的时序控制。我在项目中遇到过最典型的场景是MCU通过UART与传感器通信当时因为波特率偏差2%导致数据错乱调试了整整两天。UART帧格式的灵活性既是优点也是挑战。数据位可以是5-8位校验位可选停止位1-2位。这种灵活性在硬件实现时需要特别注意比如当数据位设置为7位时我们的Verilog代码必须正确处理字节对齐问题。实际项目中我习惯用参数化设计应对这种变化parameter DATA_WIDTH 8; // 可配置为5/6/7/8 parameter STOP_WIDTH 1; // 可配置为1/22. 波特率生成器的设计艺术波特率生成是UART设计的第一个难点。假设系统时钟50MHz要实现9600波特率分频系数50M/9600≈5208。但直接使用计数器分频会有累积误差我推荐使用累加器方案reg [15:0] baud_acc; always (posedge clk) begin baud_acc baud_acc 16d123; // 动态调整值 baud_tick (baud_acc 16d123); end实测发现传统分频电路在115200波特率时误差会超过3%而累加器方案能将误差控制在0.1%以内。这里有个坑要注意多数开发板提供的时钟并非精确的整数MHz比如常见的11.0592MHz就是专为UART设计的它能整除常用波特率。3. 接收端抗干扰设计实战异步接收最头疼的就是噪声干扰。我的经验是必须采用过采样多数表决机制。以16倍过采样为例每个bit周期采样16次取中间3次采样结果进行表决// 多数表决逻辑示例 always (*) begin case ({sample[2], sample[1], sample[0]}) 3b000, 3b001, 3b010, 3b100: voted_bit 0; 3b111, 3b110, 3b101, 3b011: voted_bit 1; endcase end在工业现场测试时这种设计能有效抵抗50ns级别的毛刺干扰。关键是要确保采样时钟与波特率的严格同步我通常会设计一个数字PLL来动态调整采样相位。4. 可配置寄存器接口设计好的UART IP核必须提供完善的配置接口。我总结出这几个必备寄存器控制寄存器开关发送/接收、中断使能波特率寄存器16位分频系数数据格式寄存器数据位宽、停止位、校验设置// 寄存器写入示例 always (posedge clk) begin if(reg_wr_en) begin case(reg_addr) 2b00: baud_div reg_data[15:0]; 2b01: {parity_en, stop_bits} reg_data[1:0]; endcase end end实际项目中建议为每个寄存器保留默认值比如波特率默认115200数据位默认8位。这样上电后即使不配置也能正常工作。5. 状态机设计从协议到RTL发送和接收都需要精心设计的状态机。以接收状态机为例我的实现通常包含这些状态IDLE等待起始位下降沿START确认起始位有效DATA移位接收数据位PARITY校验位检测STOP验证停止位always (posedge clk) begin case(state) IDLE: if(!rx_pin) state START; START: if(bit_center) state DATA; DATA: if(bit_cnt DATA_WIDTH) state PARITY; // ...其他状态转移 endcase end调试时最常见的坑是状态转移时机不对。我的经验是在bit周期中心点比如16倍过采样的第8个采样点进行状态转移最可靠。6. 验证策略与常见问题验证UART模块时我必做的几个测试边界测试5位/8位数据、无校验/奇偶校验组合波特率压力测试连续切换9600/115200/230400错误注入人为制造帧错误、奇偶校验错误遇到最多的问题是亚稳态。解决方法是在所有跨时钟域信号上添加两级触发器同步always (posedge clk) begin rx_sync {rx_sync[0], rx_pin}; // 双级同步 end另一个常见问题是发送FIFO溢出。建议实现至少16字节的FIFO并设置水位标志如半满、全满。

更多文章