从AMBA总线到串口通信:手把手教你用Verilog实现一个APB-UART控制器
从AMBA总线到串口通信手把手教你用Verilog实现一个APB-UART控制器在嵌入式系统和SoC设计中低速外设的集成一直是个值得深入探讨的话题。想象这样一个场景你正在设计一款物联网终端芯片需要为系统调试和日志输出添加一个简单的串口接口。这个接口不仅要与处理器内核无缝通信还要尽可能节省功耗和逻辑资源。此时将UART控制器挂载在AMBA-APB总线上就成了最优雅的解决方案。本文将带您从零开始构建一个完整的APB-UART控制器重点解决三个核心问题如何实现APB总线协议与UART协议的桥接如何设计可配置的波特率发生器以及如何处理跨时钟域的数据传输不同于理论讲解我们会通过可综合的Verilog代码示例展示每个关键模块的实现细节。无论您是刚接触数字IC设计的工程师还是希望深入理解总线协议的FPGA开发者都能从中获得可直接复用的工程经验。1. APB-UART整体架构设计APB-UART控制器的核心使命是在AMBA总线架构下为处理器提供访问串口设备的标准化接口。其架构设计需要同时满足总线协议和串口通信的双重要求。典型的应用场景包括系统启动时的调试信息输出嵌入式设备的固件升级接口传感器数据的低速传输通道1.1 模块划分与接口定义完整的控制器应包含以下子模块模块名称功能描述APB从机接口处理总线协议实现地址解码、读写寄存器和数据缓冲寄存器组存储配置参数波特率、数据位宽、校验方式等和状态标志波特率发生器根据配置生成精确的时钟信号支持多种标准波特率发送状态机实现并行到串行转换自动添加起始位、停止位和校验位接收状态机实现串行到并行转换完成起始位检测和数据采样跨时钟域同步逻辑解决APB时钟域与UART时钟域之间的数据同步问题接口信号定义示例module apb_uart ( // APB总线接口 input PCLK, // APB时钟 input PRESETn, // APB复位(低有效) input [31:0] PADDR, // APB地址总线 input PSEL, // 设备选择 input PENABLE, // 使能信号 input PWRITE, // 写使能 input [31:0] PWDATA, // 写数据 output [31:0] PRDATA, // 读数据 output PREADY, // 传输就绪 output PSLVERR, // 错误响应 // UART物理接口 output TXD, // 串行发送 input RXD, // 串行接收 output IRQ // 中断请求 );1.2 寄存器映射设计合理的寄存器布局是控制器易用性的关键。建议采用以下寄存器规划地址偏移寄存器名称读写属性功能描述0x00UART_DRR/W数据寄存器(收发共用)0x04UART_CRW控制寄存器(使能收发、中断等)0x08UART_FRR状态寄存器(发送空、接收满等)0x0CUART_BRDIVR/W波特率分频系数0x10UART_LCRR/W线路控制(数据位宽、停止位等)提示实际设计中应考虑加入FIFO深度配置、DMA接口等高级功能寄存器以提升IP核的灵活性。2. APB从机接口实现APB协议以其简洁性著称特别适合低速外设连接。在UART控制器中APB接口需要完成三个主要任务地址解码、数据传输和状态响应。2.1 基本读写时序实现APB传输的SETUP和ENABLE两个阶段可以通过状态机优雅地实现localparam ST_IDLE 2b00; localparam ST_SETUP 2b01; localparam ST_ACCESS 2b10; always (posedge PCLK or negedge PRESETn) begin if (!PRESETn) begin state ST_IDLE; PREADY 1b0; end else begin case (state) ST_IDLE: begin if (PSEL !PENABLE) begin state ST_SETUP; PREADY 1b0; end end ST_SETUP: begin state ST_ACCESS; PREADY 1b1; end ST_ACCESS: begin if (!PSEL) begin state ST_IDLE; PREADY 1b0; end end endcase end end2.2 寄存器访问逻辑APB读写操作最终要落实到对UART内部寄存器的访问。以下是数据寄存器的处理示例// 写数据处理 always (posedge PCLK or negedge PRESETn) begin if (!PRESETn) begin tx_data 8h0; end else if (PSEL PENABLE PWRITE (PADDR[7:0] UART_DR_OFFSET)) begin tx_data PWDATA[7:0]; // 只使用低8位 tx_valid 1b1; // 触发发送 end else begin tx_valid 1b0; end end // 读数据处理 always (*) begin case (PADDR[7:0]) UART_DR_OFFSET: PRDATA {24h0, rx_data}; UART_FR_OFFSET: PRDATA {28h0, tx_empty, rx_full, 2b00}; default: PRDATA 32h0; endcase end注意实际工程中应添加地址范围检查对非法访问返回错误响应(PSLVERR)。3. 可配置波特率发生器波特率是串口通信的基础参数其精度直接影响通信质量。传统的分频方法在需要支持多种波特率时显得不够灵活。3.1 分频系数计算波特率分频系数由以下公式决定分频系数 (系统时钟频率) / (16 × 目标波特率) - 1例如当系统时钟为50MHz目标波特率为115200时分频系数 50,000,000 / (16 × 115200) - 1 ≈ 26Verilog实现示例module baudrate_gen ( input clk, input rst_n, input [15:0] div_val, // 分频系数 output baud_clk // 16倍波特率时钟 ); reg [15:0] counter; reg baud_tick; always (posedge clk or negedge rst_n) begin if (!rst_n) begin counter 16h0; baud_tick 1b0; end else begin if (counter div_val) begin counter 16h0; baud_tick 1b1; end else begin counter counter 1; baud_tick 1b0; end end end assign baud_clk baud_tick; endmodule3.2 时钟同步与抖动处理在实际系统中时钟抖动可能导致采样点偏移。采用以下技术可提高稳定性使用16倍过采样技术添加数字滤波器消除毛刺在数据位中点采样接收端采样逻辑的关键代码// 16倍过采样计数器 always (posedge baud_clk or negedge rst_n) begin if (!rst_n) begin sample_cnt 4h0; end else if (rx_state RX_IDLE) begin sample_cnt 4h0; end else begin sample_cnt sample_cnt 1; end end // 中点采样逻辑 always (posedge baud_clk) begin if (sample_cnt 4b0111) begin // 第8个采样点(中点) rx_sample RXD; end end4. 发送与接收状态机设计UART的核心是发送和接收状态机它们负责完成数据格式的转换和时序控制。4.1 发送状态机实现发送过程需要严格按照串口协议帧格式起始位(1位低电平)数据位(5-8位)校验位(可选)停止位(1-2位高电平)localparam TX_IDLE 3b000; localparam TX_START 3b001; localparam TX_DATA 3b010; localparam TX_PARITY 3b011; localparam TX_STOP 3b100; always (posedge baud_clk or negedge rst_n) begin if (!rst_n) begin tx_state TX_IDLE; TXD 1b1; tx_busy 1b0; end else begin case (tx_state) TX_IDLE: begin if (tx_valid) begin tx_state TX_START; tx_busy 1b1; TXD 1b0; // 起始位 bit_cnt 3h0; end end TX_START: begin tx_state TX_DATA; TXD tx_data[0]; // 发送LSB end TX_DATA: begin if (bit_cnt data_bits-1) begin if (parity_en) begin tx_state TX_PARITY; TXD ^tx_data; // 奇偶校验位 end else begin tx_state TX_STOP; TXD 1b1; // 停止位 end end else begin bit_cnt bit_cnt 1; TXD tx_data[bit_cnt1]; end end // 其他状态处理... endcase end end4.2 接收状态机优化接收状态机需要更强的鲁棒性来处理以下情况起始位毛刺过滤波特率偏差补偿帧错误检测改进的接收状态机设计要点// 起始位检测 always (posedge baud_clk) begin if (rx_state RX_IDLE) begin if (!RXD start_det_cnt 8) begin start_det_cnt start_det_cnt 1; end else if (RXD) begin start_det_cnt 0; end if (start_det_cnt 7) begin rx_state RX_DATA; bit_cnt 0; end end end // 错误状态处理 always (posedge baud_clk) begin if (rx_state ! RX_IDLE sample_cnt 15) begin if (!RXD stop_bit_exp) begin frame_error 1b1; end end end5. 跨时钟域处理与中断机制APB时钟与UART波特率时钟通常不同源需要特别注意跨时钟域信号同步。5.1 异步信号同步技术采用双触发器同步链处理跨时钟域信号// APB时钟域到UART时钟域的同步 reg [1:0] sync_tx_valid; always (posedge baud_clk or negedge rst_n) begin if (!rst_n) begin sync_tx_valid 2b00; end else begin sync_tx_valid {sync_tx_valid[0], tx_valid_apb}; end end // UART时钟域到APB时钟域的同步 reg [1:0] sync_rx_ready; always (posedge PCLK or negedge PRESETn) begin if (!PRESETn) begin sync_rx_ready 2b00; end else begin sync_rx_ready {sync_rx_ready[0], rx_ready_uart}; end end5.2 中断产生逻辑合理的中断设计可以降低CPU轮询开销。典型的中断源包括接收数据就绪发送缓冲区空帧错误溢出错误中断控制寄存器设计示例// 中断使能寄存器 reg [3:0] int_enable; // bit0: RX, bit1: TX, bit2: Error // 中断状态寄存器 wire [3:0] int_status { frame_error, tx_empty, rx_full, 1b0 }; // 中断生成逻辑 assign IRQ |(int_status int_enable);在完成所有模块设计后建议进行全面的仿真验证。使用SystemVerilog编写的测试平台可以自动化验证各种边界情况如连续背靠背传输异常波特率测试错误注入测试时钟抖动测试一个经过充分验证的APB-UART控制器不仅能在当前项目中可靠工作还可以作为可重用IP核应用于后续项目。在实际流片项目中我们曾发现波特率精度偏差导致的通信失败问题最终通过添加自动波特率检测功能解决。这提醒我们即使看似简单的串口模块也需要考虑各种实际应用场景的挑战。