3. APB协议详解:从入门到实战
大家好,我是你们的芯片架构设计课讲师。今天我们来聊聊APB协议——这个在SoC系统里看似简单、实则暗藏玄机的总线协议。
APB,全称Advanced Peripheral Bus,是ARM公司AMBA总线体系中的一员。说白了,它就是专门为低速外设设计的“慢车道”。我刚开始做SoC集成时,总觉得APB太简单了,不就是读写寄存器嘛。直到有一次,一个从机时序没处理好,整个系统都卡死了……嗯,从那以后我再也不敢小看它了。
3.1 APB协议特性
APB协议有几个核心特性,我给大家梳理一下:
- 非流水线架构:每个传输需要两个时钟周期。你想想看,这跟AHB的流水线完全不同。
- 简单接口:信号数量少,从机设计起来很轻松。
- 低功耗:状态机只有两个状态(IDLE和SETUP),功耗控制很友好。
- 无等待机制:从机不能插入等待周期,必须在一个周期内完成响应。
- 支持32位地址总线:地址空间最大4GB,对于外设来说绰绰有余。
我个人习惯:在设计低功耗芯片时,APB总线频率通常控制在系统主频的1/4到1/8。这样既能满足外设需求,又能大幅降低动态功耗。
3.2 APB信号定义
APB的信号非常精简,我列个表给大家看:
| 信号名 | 方向 | 描述 |
|---|---|---|
| PCLK | 输入 | 时钟信号,所有操作同步于此 |
| PRESETn | 输入 | 异步复位,低电平有效 |
| PADDR[31:0] | 输入 | 地址总线,由主机驱动 |
| PSELx | 输入 | 从机选择信号,每个从机独享一个 |
| PENABLE | 输入 | 使能信号,指示传输的第二个周期 |
| PWRITE | 输入 | 读写控制:1为写,0为读 |
| PWDATA[31:0] | 输入 | 写数据总线 |
| PRDATA[31:0] | 输出 | 读数据总线 |
| PREADY | 输出 | 从机准备好信号(APB3新增) |
| PSLVERR | 输出 | 传输错误指示(APB3新增) |
避坑指南:我曾经在项目中遇到一个问题——从机的PSEL和PENABLE时序没对齐,导致数据丢失。后来我养成了一个习惯:在从机内部,一定要用PCLK打一拍再处理PSEL,避免组合逻辑冒险。
3.3 APB状态机
APB的状态机其实很简单,就三个状态:
- IDLE:空闲状态,PSEL为低,PENABLE为低
- SETUP:建立状态,PSEL拉高,PENABLE仍为低
- ACCESS:访问状态,PSEL和PENABLE同时为高
状态转换逻辑是这样的:
- IDLE → SETUP:当有新的传输请求时
- SETUP → ACCESS:下一个时钟周期自动进入
- ACCESS → IDLE:传输完成,且没有新的请求
- ACCESS → SETUP:传输完成,但还有连续传输
下面我用SVG画个状态机图,方便大家理解:
3.4 APB读写传输时序
时序这块是重点,我给大家拆开讲。
3.4.1 写传输时序
写传输的流程是这样的:
- 第一个时钟周期(SETUP):主机拉高PSEL,设置PADDR和PWRITE=1,PWDATA准备好
- 第二个时钟周期(ACCESS):主机拉高PENABLE,从机在PCLK上升沿采样数据
- 从机拉高PREADY表示接收完成
看个Verilog代码示例:
// APB从机写操作示例
always @(posedge PCLK or negedge PRESETn) begin
if (!PRESETn) begin
reg_data <= 32'h0;
end else if (PSEL && PENABLE && PWRITE) begin
// 在ACCESS阶段采样写数据
reg_data <= PWDATA;
end
end
3.4.2 读传输时序
读传输稍微有点不同:
- 第一个时钟周期(SETUP):主机拉高PSEL,设置PADDR和PWRITE=0
- 第二个时钟周期(ACCESS):主机拉高PENABLE,从机在此时将数据驱动到PRDATA
- 主机在PCLK上升沿采样PRDATA
注意:读数据必须在PENABLE拉高后的下一个时钟上升沿之前准备好。我曾经见过一个设计,从机用组合逻辑直接输出PRDATA,结果因为路径延迟太大,导致主机采到了错误数据。解决办法是在从机内部用寄存器打一拍再输出。
3.5 APB从机设计要点
这部分是我最想跟大家分享的实战经验。设计APB从机时,有几个关键点必须注意:
3.5.1 地址译码
地址译码要干净利落。我一般这样写:
// 地址译码示例
wire [7:0] reg_addr = PADDR[7:0]; // 只取低8位
always @(*) begin
case (reg_addr)
8'h00: sel_reg0 = PSEL && PENABLE;
8'h04: sel_reg1 = PSEL && PENABLE;
8'h08: sel_reg2 = PSEL && PENABLE;
default: sel_regx = 1'b0;
endcase
end
3.5.2 PREADY生成
对于简单寄存器,PREADY可以直接拉高。但如果是带内部状态机的从机(比如SPI控制器),就需要根据内部状态来生成PREADY。
我的经验:PREADY千万不要用组合逻辑直接输出,一定要用寄存器打一拍。否则在时序分析时会给你带来一堆麻烦。
3.5.3 PSLVERR处理
PSLVERR用于指示传输错误。常见场景:
- 访问了不存在的地址
- 写入了只读寄存器
- 读出了只写寄存器
- 内部状态机异常
我建议每个从机都实现PSLVERR,哪怕只是简单的地址越界检查。这在调试阶段能省下大量时间。
3.5.4 复位状态
所有寄存器必须有确定的复位值。我见过一个设计,复位后寄存器是X态,结果仿真时一切正常,上板后直接跑飞。
// 正确的复位写法
always @(posedge PCLK or negedge PRESETn) begin
if (!PRESETn) begin
ctrl_reg <= 32'h0000_0001; // 复位值
status_reg <= 32'h0;
end else begin
// 正常操作
end
end
3.5.5 时序收敛
APB虽然简单,但时序问题一样不能忽视。特别是当多个从机挂在同一条总线上时,从机的输出延迟会累加。我建议:
- 从机输出路径控制在2ns以内
- 地址译码用组合逻辑,但输出用寄存器
- 如果从机数量超过8个,考虑插入流水线寄存器
总结一下:APB协议看似简单,但要做好从机设计,需要关注地址译码、PREADY生成、错误处理、复位状态和时序收敛这五个方面。我在多个项目中验证过,只要把这五点做好,APB从机基本不会出问题。
好了,APB协议就讲到这里。记住,协议是死的,但设计是活的。多在实践中积累经验,你也能成为APB设计的高手。
公众号:蓝海资料掘金营,微信deep3321