1 前言
APB(Advanced Peripheral Bus),高级外设总线,是AMBA体系中专门为“延迟不敏感(Latency-insensitive)”及“低带宽需求”的外设寄存器访问而定义的总线标准,例如UART、IIC等。它的总线架构如下图所示:
可以看出APB Bridge是AMBA APB中的唯一总线主机,APB Bridge也是AMBA中的一个从机。
特点:
- 非流水线(Non-pipelined): 每一笔传输至少占用两个时钟周期,设计逻辑简单。不支持pipeline、Busrst、Outstanding传输,最快只能Back to back。
- 单时钟沿触发: 所有信号均在PCLK上升沿采样,极大简化了静态时序分析(STA)与自动测试向量生成(ATPG)。不能读写同时传输,AHB也不行,AXI可以;
- 辅助总线定位: 典型拓扑中,APB通过APB Bridge连接至AHB或AXI等高性能主干总线,起到隔离高速翻转信号、降低全局时钟树负载的作用。不支持仲裁,因为是单主多从。
版本:
- AMBA2 APB Specification(APB2)
- AMBA3 APB Protocol Specification v1.0(APB3)
- AMBA APB Protocol Specification v2.0/Issue C(APB4)
- AMBA APB Protocol Specification Issue D/E(APB5)
2 APB interface
| Source | Version | Signal | Width | Attribute | Description |
|---|---|---|---|---|---|
| Global | APB2 | PCLK | 1 | Clock | APB的所有传输在PCLK的上升沿有效 |
| PRESETn | 1 | Reset | 低电平有效,通常直接连接到系统总线的复位信号 | ||
| APB bridge | APB2 | PADDR | 8/16/32 | Address | 最多32bit |
| PSELx | 1 | Select | 每个APB Slave都有一个PSELx信号,由ABP Bridge产生 | ||
| PENABLE | 1 | Enable | 指示APB传输的第二及后续周期 | ||
| PWRITE | 1 | Direction | 1'b1: 写 1'b0: 读 |
||
| PWDATA | 8/16/32 | Write data | 最多32bit,PWRITE为1时由Master产生 | ||
| APB4 | PPROT | 3 | Protection type | PPROT[2]: 1=Instruction; 0=Data PPROT[1]: 1=Nonsecure; 0=Secure PPROT[0]: 1=Privileged; 0=Normal |
|
| PSTRB | 1/2/4 | Write strobes | 指示在写传输期间,要更新哪个字节通道 写数据总线的每8bit对应1bitPSTRB 在读传输期间,PSTRB不能跳变 |
||
| APB5 | |||||
| Slave interface | APB2 | PRDATA | 8/16/32 | Read data | 最多32bit,PWRITE为0时由Slave产生 |
| APB3 | PREADY | 1 | Ready | 表示APB传输完成 | |
| PSLVERR | 1 | Transfer error | 传输失败的错误信号 |
3 状态转换
APB协议总共只有三种状态,分别是IDLE,SETUP和ACCESS。
- IDLE:默认空闲状态;
- SETUP:当主机需要传输数据的时候,会把从机对应的PSEL拉高,PENABLE拉低,此时进入SETUP,且只持续一拍,下一拍必进入ACCESS;
- ACCESS:驱动PENBALE拉高,之后会采样从机的PREADY信号,如果为低则持续状态,如果为高则回到IDLE或回到SETUP,这取决于是否还有数据传输。SETUP进入ACCESS时需要以下信号保持不变:
- PADDR;
- PPROT;
- PWRITE;
- PWDATA;
- PSTRB;
- PAUSER;
- PWUSER;
从APB从机角度,状态转换如下图所示:
graph LR
IDLE -->|PSEL = 1; PENABLE = 0| SETUP;
SETUP --> ACCESS;
ACCESS -->|PSEL = 1; PENABLE = 0| SETUP;
ACCESS -->|PREADY = 0| ACCESS;
ACCESS -->|PREADY = 1| IDLE;
4 APB Timing
4.1 写传输
没有等待状态:
第一阶段为IDLE状态。
第二阶段为SETUP,此时主机把PSEL和PWRITE拉高;PADDR和PWDATA准备好地址和数据;把PENABLE拉低表示下一拍实施写入。
第三阶段为ACCESS,主机把PENABLE拉高,表示该数据有效;从机采样到PSEL拉高,会把PREADY拉高表示接收数据;
第四阶段退出ACCESS,如果PREADY为高,代表从机接收到信号,主机则拉低PSEL和PENABLE信号,进入IDLE状态。
没有等待状态的连续写时序:
具有等待状态:
ACCESS可能不止一个周期,取决于从机什么时候回复PREADY信号,因此数据写入完成要大于两个时钟周期。
4.2 读传输
没有等待状态,类似写传输:
具有等待状态,类似写传输:
5 其他信号
- APB2 (AMBA 2.0): 基础规范。固定2周期访问。
- 限制: 无法处理慢速外设,若Slave处理慢,Bridge必须强制插入系统级等待,效率极低。
- APB3 (AMBA 3.0): 引入灵活性。
- 新增 PREADY: 支持反压机制。Slave可以根据自身状态自主延长访问时间,不再被动匹配2周期。
- 新增 PSLVERR: 支持错误反馈。提升了复杂SoC的鲁棒性。
- APB4 (AMBA 4.0): 增强安全性与复杂数据流支持。
- 新增 PPROT[2:0]: 完善安全架构。
PPROT[0]:Normal (0) vs Privileged (1) 访问。PPROT[1]:Secure (0) vs Non-secure (1) 访问。PPROT[2]:Data (0) vs Instruction (1) 访问。
- 新增 PSTRB: 引入字节掩码,支持稀疏数据传输,允许在32位总线上仅更新特定字节。
- 新增 PPROT[2:0]: 完善安全架构。
5.1 PSTRB
PSTRB可以理解为写入选通,1bit控制PWDATA的8bit是否能写入:
| PSTRB | 3 | 2 | 1 | 0 |
|---|---|---|---|---|
| PWDATA | 31:24 | 23:16 | 15:8 | 7:0 |
读传输时,PSTRB必须全位拉低。
5.2 PSLVERR
PSLVERR表示从机认为写传输有错误。时序图如下:
写传输时,当从机任务可以接受数据时,把PREADY拉高。同时(RSEL&PENABLE&PREADY同时为高时)如果之前采集PADDR、PWDATA等信号有问题,则认为写传输有误,所以在此时把PSLVERR也拉高。主机会在下一个上升沿采集到此错误信号,得知写入错误。
PSLVERR信号仅在PSEL、PENABLE与PREADY同时为HIGH的最后周期内有效。
读传输也可能出现错误,表示无可读数据。
常见的错误有:
- 非法地址:
- 读/写地址超出范围
- 地址decode错误
- 未对齐访问:
- 半字/字节访问未对齐
- 访问类型错误:
- 对只读寄存器写
- 对只写寄存器读
- 不支持的访问类型
- 只允许安全访问或supervisor write
- 权限错误(APB5)
- PPROT[0]:指令/数据属性违规
- PPROT[1]:特权级违规:User访问特权或控制寄存器
- PPROT[2]:安全域违规:Non-secure master访问Secure-only寄存器
- 内部(NVM、EEPROM、OTP)超时
- 数据范围非法
- 校验/奇偶/ECC错误(读路径)
- 功能未实现:某寄存器存在但功能未开启
- 电源/时钟不可用
- 防火墙拒绝
- 调试/测试/诊断:JTAG锁定、Debug fuse关闭
- 低功耗状态限制:retention-only、deep sleep、某些寄存器不可访问
6. 低功耗设计特性
APB在SoC中扮演着“功耗屏障”的角色,其节能设计主要通过以下机制实现:
- 零转换功耗: 在非活动期间,APB从机接口保持静默。由于没有流水线,不需要像AHB那样维护忙碌状态(BUSY/IDLE HTRANS)。
- 静态保持(Static Retention): APB Bridge在传输结束后应保持地址和写数据信号稳定,而非将其复位或悬空。这样可以显著减少高电容总线上的信号翻转率(Toggle Rate),从而降低动态功耗。
- 局部时钟门控: APB极简的握手机制使得外设能够轻松实现局部Clock Gating,仅在PSEL有效时开启模块时钟。
7. 总结与设计最佳实践
何时选择APB?
- 配置寄存器: 绝大多数慢速外设的寄存器配置接口。
- 窄带宽外设: 如UART、定时器、看门狗等。
- 功耗敏感域: 当需要隔离高速切换信号以节省全局功耗时。
设计注意事项:
- 复位同步释放: 虽然
PRESETn是异步复位,但必须在PCLK域内同步释放,否则可能导致状态机跳变异常。 - 字节通道对齐 (Byte Lane Mapping): 在处理8位或16位外设接入32位APB总线时,必须严格遵守端序映射(Endianness)。在读操作中,Slave只需在对应的字节通道驱动数据,Bridge负责数据聚合。
- Ready信号严谨性: 如果Slave不支持等待状态,必须将
PREADY固定接高,不得悬空。
通过对APB协议的深度掌握,架构师能够在保证系统功能的前提下,实现逻辑复杂性与功耗开销的完美平衡。
8 APB SRAM RTL设计
设计一个16bit数据位宽、512字节(9bit地址位宽)的APB协议的SRAM。
6.1 Interface & Architecture
接口符合APB4协议。
| Signal | Width | Direction |
|---|---|---|
| pclk | 1 | I |
| preset_n | 1 | I |
| paddr | ADDR_WIDTH | I |
| psel | 1 | I |
| penable | 1 | I |
| pwrite | 1 | I |
| pwdata | DATA_WIDTH | I |
| pprot | 3 | I |
| pstrb | DATA_WIDTH/8 | I |
| prdata | DATA_WIDTH | O |
| pready | 1 | O |
| pslverr | 1 | O |
文件结构:
- APB_SRAM
- SRAM
- SRAM_IP
- SRAM
6.2 SRAM
采用ARM Artisan Physical IP来生成一个4096*32的单口SRAM,然后例化:
module spram (
// Clock
input wire clk, // Clock
input wire cs_n, // Chip enable
input wire we_n, // Write enable
input wire [12-1:0] addr, // Address
input wire [32-1:0] din, // Data input
output logic [32-1:0] dout // Data output
);
//=========================================================================
// The time unit and precision of the internal declaration
timeunit 1ns;
timeprecision 1ps;
//=========================================================================
// Parameter
//localparam TCO = 1.6; // Simulate the delay of the register
//=========================================================================
// Signal
//=========================================================================
//
SRAM_SP_4096x32 u_SRAM_SP_4096x32(
.CLK(clk),
.CEN(cs_n),
.WEN(we_n),
.A(addr),
.D(din),
.Q(dout),
.EMA(3'b000)
);
endmodule
6.3 APB_SRAM
根据APB协议,把APB读写操作转化为SRAM的读写操作。
module apb_spram#(
parameter PPROT_CLASS = 1'b0, // Normal or privileged, PPROT[0]
PPROT_SUCURE = 1'b1, // Secure or non-secure, PPROT[1]
PPROT_TYPE = 1'b0 // Instruction or data, PPROT[2]
)(
// Clock and reset
input wire pclk, // Clock
input wire presetn, // Async reset
input wire [31:0] paddr, // APB Address bus
input wire psel, // APB Select
input wire penable, // APB Enable
input wire pwrite, // APB Write control signal (1=Write
input wire [31:0] pwdata, // APB Write data
input wire [2:0] pprot, // APB Protection type (not used)
input wire [3:0] pstrb, // APB Strobe
output logic [31:0] prdata, // APB Read Data
output logic pready, // APB Ready
output logic pslverr // APB Slave Error
);
//=========================================================================
// The time unit and precision of the internal declaration
timeunit 1ns;
timeprecision 1ps;
//=========================================================================
// Parameter
localparam TCO = 0.6; // Simulate the delay of the register
//=========================================================================
// Signal
logic ram_we_n; // RAM Write Enable
logic ram_cs_n; // RAM Enable
logic [11:0] ram_addr; // RAM Address
logic [31:0] ram_wdata; // RAM Write Data
logic [31:0] ram_rdata; // RAM Read Data
logic [31:0] wdata_mask; // Write data by PTRB mask
logic pprot_err; // PPROT Error
logic slv_err; // Slave Error
logic addr_err; // Address Error
//=========================================================================
// APB State Machine
enum {
IDLE,
SETUP,
ACCESS
} curr_state,next_state;
always_ff@(posedge pclk, negedge presetn)begin
if(!presetn)
curr_state <= #TCO IDLE;
else
curr_state <= #TCO next_state;
end
always_comb begin
case(curr_state)
IDLE: begin
if(psel && !penable)
next_state = SETUP;
else
next_state = IDLE;
end
SETUP: begin
next_state = ACCESS;
end
ACCESS: begin
if(psel&&penable&&pready)
next_state = IDLE;
else if(psel&&penable&&!pready)
next_state = ACCESS;
else if(psel&&!penable)
next_state = SETUP;
else
next_state = IDLE;
end
default:next_state = IDLE;
endcase
end
//=========================================================================
// Write data mask by PSTRB
always_comb begin
for(int i=0;i<4;i++)begin
if(pstrb[i])
wdata_mask[8*i+:8] = pwdata[8*i+:8];
else
wdata_mask[8*i+:8] = '0;
end
end
//=========================================================================
// Slave Error Generation
assign addr_err = |paddr[31:12];
assign pprot_err = ~((pprot[0] == PPROT_CLASS) && (pprot[1] == PPROT_SUCURE) && (pprot[2] == PPROT_TYPE));
assign slv_err = pprot_err || addr_err;
//assign pslverr = (curr_state == ACCESS) ? slv_err : 1'b0;
always_ff@(posedge pclk, negedge presetn)begin
if(!presetn)
pslverr <= #TCO '0;
else if (curr_state == SETUP)
pslverr <= #TCO slv_err;
else
pslverr <= #TCO '0;
end
//=========================================================================
// APB Signals
assign pready = (curr_state == ACCESS) ? 1'b1 : 1'b0;
assign prdata = ((curr_state == ACCESS)&& ~pslverr) ? ram_rdata:prdata;
//=========================================================================
// SPRAM Control Signals
assign ram_cs_n = (curr_state == SETUP) ? ~psel : 1'b1;
assign ram_we_n = (curr_state == SETUP) ? ~pwrite : 1'b1;
assign ram_addr = (curr_state == SETUP) ? paddr[11:0] :'0;
assign ram_wdata = ((curr_state == SETUP)&& ~slv_err) ? wdata_mask :'0;
//=========================================================================
// Instantiation
// SPRAM Instance
spram u_spram (
.clk (pclk),
.cs_n (ram_cs_n),
.we_n (ram_we_n),
.addr (ram_addr),
.din (ram_wdata),
.dout (ram_rdata)
);
endmodule