刚接触SoC设计的工程师常常会被各种总线协议搞得晕头转向。作为初学者,你可能已经熟悉UART、I2C这些基础外设的工作原理,但当需要将它们集成到SoC系统中时,APB3总线就成了绕不开的话题。本文将从一个实际项目案例出发,手把手教你如何通过APB3总线连接常见外设,让CPU能够顺利控制它们。
在SoC设计中,不同的外设对性能要求差异很大。想象一下,一个系统同时包含高速的DDR内存控制器和低速的UART串口——如果让它们都直接连接在高速总线上,不仅会造成资源浪费,还会增加系统复杂度。这就是APB总线存在的意义。
APB(Advanced Peripheral Bus)是ARM AMBA总线家族中的低速成员,专门为连接低带宽外设而设计。与高速的AXI或AHB总线相比,APB具有以下特点:
在实际项目中,我们通常采用总线分层架构:
code复制CPU → AXI/AHB总线 → APB桥 → APB总线 → 低速外设(UART/I2C/SPI等)
这种架构既保证了高速组件的性能,又简化了低速外设的连接方式。APB桥在这里扮演着"协议转换器"的角色,将高速总线上的请求转换为APB协议。
理解APB3总线的第一步是掌握它的信号组成。与早期版本相比,APB3引入了PREADY和PSLVERR两个重要信号,大大提升了总线灵活性。以下是核心信号列表:
| 信号名称 | 方向 | 描述 |
|---|---|---|
| PCLK | 输入 | APB总线时钟,所有传输同步于上升沿 |
| PRESETn | 输入 | 异步复位信号(低有效) |
| PADDR | 主→从 | 32位地址总线,用于选择外设寄存器 |
| PWRITE | 主→从 | 读写控制:1=写,0=读 |
| PSELx | 主→从 | 片选信号(x表示第x个从设备),高电平有效 |
| PENABLE | 主→从 | 使能信号,与PSEL配合指示传输阶段 |
| PWDATA | 主→从 | 写数据总线(32位) |
| PRDATA | 从→主 | 读数据总线(32位) |
| PREADY | 从→主 | 从设备准备好信号,可插入等待周期 |
| PSLVERR | 从→主 | 错误指示信号(可选) > 提示:实际设计中,UART/I2C这类简单外设通常不需要PSLVERR信号,可以省略不接。 |
理解这些信号的最好方式是通过时序图。APB3总线定义了三种状态:
让我们通过一个具体案例——连接16550兼容UART控制器,来看看APB3总线如何工作。UART通常需要配置以下寄存器:
假设我们的UART控制器设计为APB从设备,地址映射如下:
verilog复制module uart_apb (
input PCLK,
input PRESETn,
input [31:0] PADDR,
input PWRITE,
input PSEL,
input PENABLE,
input [31:0] PWDATA,
output reg [31:0] PRDATA,
output PREADY,
output PSLVERR
);
// 寄存器地址偏移量
localparam REG_RBR = 0; // 接收缓冲
localparam REG_THR = 0; // 发送保持
localparam REG_IER = 1; // 中断使能
localparam REG_IIR = 2; // 中断标识
localparam REG_LCR = 3; // 线路控制
localparam REG_MCR = 4; // Modem控制
localparam REG_LSR = 5; // 线路状态
localparam REG_MSR = 6; // Modem状态
配置UART波特率的APB写操作时序如下:
SETUP阶段(T1周期):
ACCESS阶段(T2周期):
waveform复制时钟周期: | T1 | T2 |
PCLK : _|‾|_|‾|_
PADDR : <0x03>|--------
PWRITE : __|‾‾‾|________
PSEL : __|‾‾‾|________
PENABLE : ________|‾‾‾|__
PWDATA : <0x83>|--------
PREADY : ________|‾‾‾|__
当外设响应较慢时,可以通过PREADY插入等待。例如UART发送缓冲区满时:
verilog复制// 在Verilog实现中
assign PREADY = (tx_fifo_full) ? 1'b0 : 1'b1;
这会导致总线保持在ACCESS状态,直到PREADY变高。实际波形如下:
waveform复制时钟周期: | T1 | T2 | T3 | T4 |
PCLK : _|‾|_|‾|_|‾|_|‾|_
PADDR : <0x00>|----------------
PWRITE : __|‾‾‾|______________
PSEL : __|‾‾‾|______________
PENABLE : ________|‾‾‾‾‾‾‾‾|__
PWDATA : <data>|----------------
PREADY : ________|_____|‾‾‾|__
^ 等待2周期
I2C控制器是另一个常见的APB外设。与UART不同,I2C通常需要更复杂的寄存器控制:
| 地址偏移 | 寄存器名称 | 功能描述 |
|---|---|---|
| 0x00 | CTRL | 控制寄存器(使能/中断等) |
| 0x04 | STATUS | 状态寄存器(忙/错误标志) |
| 0x08 | DATA | 数据收发寄存器 |
| 0x0C | ADDR | 从设备地址配置 |
| 0x10 | CLK_DIV | 时钟分频配置 |
读取I2C状态寄存器的APB时序:
SETUP阶段:
ACCESS阶段:
verilog复制always @(posedge PCLK or negedge PRESETn) begin
if (!PRESETn) begin
PRDATA <= 32'h0;
end else if (PSEL && !PWRITE && PENABLE && PREADY) begin
case (PADDR[4:2])
1: PRDATA <= {29'h0, i2c_busy, i2c_error, i2c_irq};
// 其他寄存器...
endcase
end
end
在实际项目中,APB总线连接常会遇到各种问题。以下是几个调试经验:
问题1:传输永远卡在ACCESS状态
问题2:写数据未能正确更新寄存器
问题3:读操作返回错误数据
注意:在FPGA原型验证阶段,建议在APB桥添加监控逻辑,记录总线活动,这对后期调试非常有帮助。
调试APB总线时,这个小技巧很实用:在设计中添加一个APB监视器模块,它可以非侵入式地记录总线活动:
verilog复制module apb_monitor (
input PCLK,
input PRESETn,
input [31:0] PADDR,
input PWRITE,
input PSEL,
input PENABLE,
input [31:0] PWDATA,
input [31:0] PRDATA,
input PREADY
);
always @(posedge PCLK) begin
if (PSEL && PENABLE && PREADY) begin
$display("[APB] %s @0x%h: %s=0x%h",
PWRITE ? "WR" : "RD",
PADDR,
PWRITE ? "WDATA" : "RDATA",
PWRITE ? PWDATA : PRDATA);
end
end
endmodule
这个简单的监视器会在每次成功传输时打印日志,极大方便了调试工作。