从零开始:手把手教你用Verilog实现一个APB-UART控制器(附完整RTL代码)

从零开始:手把手教你用Verilog实现一个APB-UART控制器(附完整RTL代码) 从零构建APB-UART控制器数字IC工程师的实战指南在嵌入式系统开发中UART作为最古老的串行通信协议之一依然保持着不可替代的地位。当我们需要为SoC设计一个调试接口或连接低速外设时基于AMBA-APB总线的UART控制器往往是最佳选择。本文将带领读者从协议原理到RTL实现完整构建一个可集成在SoC中的APB-UART控制器。1. 理解UART与APB协议的核心要点1.1 UART协议的工程实现关键UART通信的本质是异步串行数据传输其核心特征包括非同步时钟收发双方没有共享时钟信号依靠预先约定的波特率实现同步帧结构灵活每个数据帧包含起始位(1b)、数据位(5-8b)、可选的校验位(1b)和停止位(1-2b)双工通信独立的TXD和RXD线路支持全双工操作在硬件实现上UART控制器需要解决三个关键问题波特率同步接收端需要以16倍波特率的频率采样准确捕捉起始位下降沿噪声抑制通过多数表决机制过滤线路上的瞬时干扰数据缓冲使用FIFO解决处理器访问与串行传输的速度不匹配问题1.2 AMBA-APB总线协议精要APB作为AMBA总线家族中的轻量级成员具有以下典型特征特性描述拓扑结构单主设备APB桥多从设备传输类型非流水线式读写操作时序要求每个传输包含SETUP和ENABLE两个阶段数据宽度支持8/16/32位配置时钟要求所有信号在PCLK上升沿采样APB接口的关键信号包括PSELx从设备选择信号PENABLE传输使能标志PADDR[31:0]32位地址总线PWDATA[31:0]写数据总线PRDATA[31:0]读数据总线PREADY从设备就绪指示2. APB-UART架构设计2.1 整体模块划分我们的APB-UART控制器采用分层设计主要包含以下子模块module apb_uart ( // APB接口信号 input PCLK, input PRESETn, input PSEL, input PENABLE, input PWRITE, input [31:0] PADDR, input [31:0] PWDATA, output [31:0] PRDATA, output PREADY, // UART物理接口 output TXD, input RXD, // 中断信号 output TX_IRQ, output RX_IRQ );2.2 关键模块功能说明APB接口模块实现APB协议状态机处理寄存器读写操作生成PREADY响应信号波特率发生器可编程分频器支持常用波特率生成16倍波特率的采样时钟典型配置寄存器定义位域名称功能[15:0]DIV分频系数 PCLK/(16*波特率)[31:16]Reserved保留位发送引擎包含16字节深度的发送FIFO并行到串行转换逻辑自动添加起始位、校验位和停止位接收引擎16倍过采样时钟同步多数表决滤波电路串行到并行转换逻辑3. 核心RTL实现详解3.1 波特率生成器设计波特率生成器的核心是一个可编程分频器其Verilog实现如下module baud_gen ( input PCLK, input PRESETn, input [15:0] div, output baud16 ); reg [15:0] counter; reg baud16_reg; always (posedge PCLK or negedge PRESETn) begin if (!PRESETn) begin counter 16h0; baud16_reg 1b0; end else begin if (counter div) begin counter 16h0; baud16_reg 1b1; end else begin counter counter 1b1; baud16_reg 1b0; end end end assign baud16 baud16_reg; endmodule注意实际工程中需要添加div0的保护逻辑避免分频系数为0导致锁死3.2 接收状态机实现接收状态机是UART控制器中最复杂的部分其状态转换图如下IDLE - START - DATA - PARITY - STOP - IDLE对应的Verilog实现关键代码localparam [2:0] IDLE 3b000, START 3b001, DATA 3b010, PARITY 3b011, STOP 3b100; always (posedge PCLK or negedge PRESETn) begin if (!PRESETn) begin state IDLE; bit_cnt 3b0; shift_reg 8h0; end else if (baud16) begin case (state) IDLE: if (!RXD_sync) begin // 检测起始位 sample_cnt 4h0; state START; end START: if (sample_cnt 4h8) begin if (!majority_vote) begin // 确认有效起始位 state DATA; bit_cnt 3b0; end else state IDLE; end DATA: if (sample_cnt 4hF) begin shift_reg {majority_vote, shift_reg[7:1]}; if (bit_cnt DATA_BITS-1) state PARITY; else bit_cnt bit_cnt 1b1; end // 其他状态处理... endcase end end4. 系统集成与验证4.1 寄存器映射设计完整的APB-UART控制器需要提供以下寄存器接口地址偏移名称读写描述0x00DATAR/W数据寄存器0x04STATRO状态寄存器0x08CTRLR/W控制寄存器0x0CDIVR/W波特率分频系数0x10IRQR/W中断控制寄存器状态寄存器(STAT)的典型位定义assign stat_reg { 2b0, // Reserved tx_full, // TX FIFO满标志 tx_empty, // TX FIFO空标志 rx_full, // RX FIFO满标志 rx_empty, // RX FIFO空标志 parity_err, // 奇偶校验错误 frame_err, // 帧错误(停止位不正确) overrun_err // 溢出错误(新数据覆盖未读数据) };4.2 验证环境搭建建议采用以下验证策略模块级验证使用SystemVerilog搭建验证环境对每个子模块(波特率生成器、发送引擎、接收引擎)单独验证module uart_tx_tb; // 时钟和复位生成 bit PCLK 0; bit PRESETn 0; always #10 PCLK ~PCLK; initial begin #100 PRESETn 1; // 测试用例... end // 实例化DUT uart_tx dut (.*); // 测试用例 initial begin // 测试波特率配置 write_reg(DIV_ADDR, 16d104); // 配置9600波特率16MHz // 测试数据发送 write_reg(DATA_ADDR, 8h55); check_tx_sequence(10b0_01010101_1); // 验证发送波形 end endmodule系统级验证构建APB总线验证环境验证寄存器访问和中断功能进行回环测试(loopback test)FPGA原型验证在开发板上验证实际通信功能使用逻辑分析仪抓取信号波形测试不同波特率下的通信稳定性在完成RTL实现后建议采用覆盖率驱动的验证方法确保达到以下覆盖率目标代码覆盖率 99%功能覆盖率 95%断言覆盖率 100%实际项目中我曾遇到一个典型的波特率同步问题当APB时钟与UART波特率不是整数倍关系时会出现周期性数据错误。解决方案是在波特率生成器中添加小数分频逻辑或者调整系统时钟频率使其与目标波特率形成整数倍关系。