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画个状态机图,方便大家理解:

IDLE SETUP ACCESS 有传输请求 下一时钟 传输完成 连续传输

3.4 APB读写传输时序

时序这块是重点,我给大家拆开讲。

3.4.1 写传输时序

写传输的流程是这样的:

  1. 第一个时钟周期(SETUP):主机拉高PSEL,设置PADDR和PWRITE=1,PWDATA准备好
  2. 第二个时钟周期(ACCESS):主机拉高PENABLE,从机在PCLK上升沿采样数据
  3. 从机拉高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 读传输时序

读传输稍微有点不同:

  1. 第一个时钟周期(SETUP):主机拉高PSEL,设置PADDR和PWRITE=0
  2. 第二个时钟周期(ACCESS):主机拉高PENABLE,从机在此时将数据驱动到PRDATA
  3. 主机在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