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通过APB Bridge连接至AHB或AXI等高性能主干总线,起到隔离高速翻转信号、降低全局时钟树负载的作用。不支持仲裁,因为是单主多从。
- 辅助总线定位: APB传输由APB bridge发起。APB bridge也可称为“Requester”。外设接口响应请求。APB外设也可称为“Completer”。
版本:
- APB2: 基础规范。固定2周期访问。
- 限制: 无法处理慢速外设,若Slave处理慢,Bridge必须强制插入系统级等待,效率极低。
- APB3: 引入灵活性。
- 新增 PREADY: 支持反压机制。Slave可以根据自身状态自主延长访问时间,不再被动匹配2周期。
- 新增 PSLVERR: 支持错误反馈。提升了复杂SoC的鲁棒性。
- APB4: 增强安全性与复杂数据流支持。
- 新增 PPROT[2:0]: 完善安全架构。
- 新增 PSTRB: 引入字节掩码,支持稀疏数据传输,允许在32位总线上仅更新特定字节。
- APB5:
官方文档:
- 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 | 低电平有效,通常直接连接到系统总线的复位信号 | ||
| Requester | APB2 | PADDR | 8/16/32 | Address | 表示字节地址(Byte Address) |
| 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 | PWRITE为1时由Master产生,位宽必须与PRDATA相同 | ||
| APB4 | PPROT | 3 | Protection type | 提供对非法传输的保护 | |
| PSTRB | 1/2/4 | Write strobes | 指示在写传输期间,要更新哪个字节通道 | ||
| APB5 | PNSE | 1 | Extension to protection type | ||
| PWAKEUP | 1 | Wake-up | |||
| PAUSER | ≤128 | ||||
| PWUSER | ≤DATA_WIDTH/2 | ||||
| Completer | APB2 | PRDATA | 8/16/32 | Read data | PWRITE为0时由Slave产生,位宽必须与PWDATA相同 |
| APB3 | PREADY | 1 | Ready | 表示APB传输完成 | |
| PSLVERR | 1 | Transfer error | 传输失败的错误信号 | ||
| APB5 | PRUSER | ≤DATA_WIDTH/2 | |||
| PRUSER | ≤16 |
3 状态转换
APB协议总共只有三种状态,分别是IDLE,SETUP和ACCESS。
- IDLE:默认空闲状态;
- SETUP:当主机需要传输数据的时候,会把Completer对应的PSEL拉高,
PENABLE拉低,此时进入SETUP,且只持续一拍,下一拍必进入ACCESS; - ACCESS:驱动PENBALE拉高,之后会采样Completer的
PREADY信号,如果为低则持续状态,如果为高则回到IDLE或回到SETUP,这取决于是否还有数据传输。SETUP进入ACCESS时需要以下信号保持不变:- PADDR;
- PPROT;
- PWRITE;
- PWDATA;
- PSTRB;
- PAUSER;
- PWUSER;
从Completer角度,状态转换如下图所示:
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 写传输
4.1.1 无等待状态
{ signal: [
{ name: "PCLK", wave: "pP..p" },
{ name: "PADDR", wave: "x6.x,", data:"Addr1"},
{ name: "PWRITE", wave: "x1.x." },
{ name: "PSEL", wave: "01.0." },
{ name: "PENABLE", wave: "x01x." },
{ name: "PWDATA", wave: "x6.x.", data:"Data1"},
{ name: "PREADY", wave: "x.1xx" },
{ name: "STATUS", wave: "3453.", data:"IDLE S A IDLE" },
],
head: {
text: 'Write transfers with no wait states',
tick: 0,
}
}
第一阶段为IDLE状态。
第二阶段为SETUP状态,T1时刻主机把PSEL和PWRITE拉高;PADDR和PWDATA准备好地址和数据,把PENABLE拉低表示下一拍实施写入。
第三阶段为ACCESS,T2时刻主机把PENABLE拉高,表示该数据有效;从机采样到PSEL拉高,会把PREADY拉高表示会在T3时刻接收写入数据;PADDR和PWDATA以及其它控制信号必须保持稳定,直至传输完成。
第四阶段退出ACCESS,如果PREADY为高,代表从机接收到信号,主机则拉低PENABLE信号。如果没有另一项针对同一外设的传输,主机拉低PSEL信号,进入IDLE状态;否则为连续写传输,时序图如下:
{ signal: [
{ name: "PCLK", wave: "pP........p" },
{ name: "PADDR", wave: "x3.4.5.6.x.", data:"Addr1 Addr2 Addr3 Addr4"},
{ name: "PWRITE", wave: "x1.1.1.1.1." },
{ name: "PSEL", wave: "01.1.1.1.0." },
{ name: "PENABLE", wave: "0.10101010." },
{ name: "PWDATA", wave: "x3.4.5.6.x.", data:"Data1 Data2 Data3 Data4"},
{ name: "PREADY", wave: "x.1010101xx" },
{ name: "STATUS", wave: "3454545453.", data:"IDLE S A S A S A S A IDLE" },
],
head: {
text: 'Write transfers with no wait states',
tick: 0,
}}
4.1.2 有等待状态
在ACESS期间,当PENABLE为高电平时,从机通过将PREADY拉低来延长传输,在PREADY保持低电平期间,以下信号保持不变:
- PADDR
- PWRITE
- PSELx
- PENABLE
- PWDATA
- PSTRB
- PPROT
- PAUSER
- PWUSER
{ signal: [
{ name: "PCLK", wave: "pP...." },
{ name: "PADDR", wave: "x6...x", data:"Addr1"},
{ name: "PWRITE", wave: "x1...x" },
{ name: "PSEL", wave: "01...0" },
{ name: "PENABLE", wave: "x01..x" },
{ name: "PWDATA", wave: "x6...x", data:"Data1"},
{ name: "PREADY", wave: "x.0.1x" },
{ name: "STATUS", wave: "34..53", data:"IDLE S A IDLE" },
],
head: {
text: 'Write transfers with wait states',
tick: 0,
}}
所以ACCESS可能不止一个周期,取决于从机什么时候回复PREADY信号,因此数据写入完成要大于两个时钟周期。
当PENABLE为低电平时,PREADY可以取任意值。这确保了具有固定两周期访问周期的外设可以将PREADY 固定为高。
4.2 读传输
4.2.1 无等待状态
类似写传输:
{ signal: [
{ name: "PCLK", wave: "pP..p" },
{ name: "PADDR", wave: "x6.x,", data:"Addr1"},
{ name: "PWRITE", wave: "x0.x." },
{ name: "PSEL", wave: "01.0." },
{ name: "PENABLE", wave: "x01x." },
{ name: "PRDATA", wave: "xx6x.", data:"Data1"},
{ name: "PREADY", wave: "x.1xx" },
{ name: "STATUS", wave: "3453.", data:"IDLE S A IDLE" },
],
head: {
text: 'Read transfers with no wait states',
tick: 0,
}
}
4.2.2 有等待状态
类似写传输:
{ signal: [
{ name: "PCLK", wave: "pP...." },
{ name: "PADDR", wave: "x6...x", data:"Addr1"},
{ name: "PWRITE", wave: "x0...x" },
{ name: "PSEL", wave: "01...0" },
{ name: "PENABLE", wave: "x01..x" },
{ name: "PRDATA", wave: "x...6x", data:"Data1"},
{ name: "PREADY", wave: "x.0.1x" },
{ name: "STATUS", wave: "34..53", data:"IDLE S A IDLE" },
],
head: {
text: 'Read transfers with wait states',
tick: 0,
}}
在 PREADY 保持低电平期间,以下信号保持不变:
- PADDR
- PWRITE
- PSELx
- PENABLE
- PPROT
- PAUSER
5 其他信号
5.1 PSTRB
PSTRB可以理解为写入选通,1bit控制PWDATA的8bit是否能写入:
| PSTRB | 3 | 2 | 1 | 0 |
|---|---|---|---|---|
| PWDATA | 31:24 | 23:16 | 15:8 | 7:0 |
在读传输期间,PSTRB不能跳变且所有bit必须被Requester拉低。
PSTRB是一个可选信号。Requester和Completer不一定都支持该信号,下表描述了在连接Requester和Completer时PSTRB的兼容性:
| PSTRB | Completer: signal not present | Completer: signal present |
|---|---|---|
| Requester: signal not present | 兼容,不支持Sparse writes | 兼容,所有写入数据字节通道在写入时均有效,将PSTRB输入端与Requester的PWRITE输出端连接 |
| Requester: signal present | 兼容,不支持Sparse writes | 兼容,支持Sparse writes |
5.2 PSLVERR
PSLVERR可用于指示APB传输中的错误状态。读取和写入事务均可能发生错误状态。
写传输时,当从机任务可以接受数据时,把PREADY拉高。同时(RSEL&PENABLE&PREADY同时为高时)如果之前采集PADDR、PWDATA等信号有问题,则认为写传输有误,所以在此时把PSLVERR也拉高。主机会在下一个上升沿采集到此错误信号,得知写入错误。
PSLVERR信号仅在PSEL、PENABLE与PREADY同时为HIGH的最后周期内有效。建议(但非强制要求)在PSEL、PENABLE或PREADY为低电平时,将PSLVERR拉低。
收到错误的交易可能已更改外设的状态,也可能未更改。这取决于具体的外设,两种状态均可接受。当写入交易收到错误时,这并不意味着外设内的寄存器未被更新。
收到错误的读取交易可能会返回无效数据。外设在发生读取错误时,没有要求必须将数据总线置为全 0。收到读取传输错误响应的请求方仍可使用该数据。完成方不能依赖错误响应来阻止读取 PRDATA 上的值。
完成方无需支持PSLVERR。如果完成方未包含PSLVERR,则请求方的相应输入应连接为低电平。
常见的错误有:
- 非法地址:
- 读/写地址超出范围
- 地址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、某些寄存器不可访问
写传输失败并伴随错误的时序图如下:
{ signal: [
{ name: "PCLK", wave: "pP..." },
{ name: "PADDR", wave: "x6..x", data:"Addr1"},
{ name: "PWRITE", wave: "x1..x" },
{ name: "PSEL", wave: "01..0" },
{ name: "PENABLE", wave: "x01.x" },
{ name: "PWDATA", wave: "x6..x", data:"Data1"},
{ name: "PREADY", wave: "x.01x" },
{ name: "PSLVERR", wave: "x..1x" },
{ name: "STATUS", wave: "34.53", data:"IDLE S A IDLE" },
],
head: {
text: 'Example failing write transfer',
tick: 0,
}}
读取传输也可能以错误响应结束,这表明没有有效的读取数据可用:
{ signal: [
{ name: "PCLK", wave: "pP...." },
{ name: "PADDR", wave: "x6...x", data:"Addr1"},
{ name: "PWRITE", wave: "x0...x" },
{ name: "PSEL", wave: "01...0" },
{ name: "PENABLE", wave: "x01..x" },
{ name: "PRDATA", wave: "x....." },
{ name: "PREADY", wave: "x.0.1x" },
{ name: "PSLVERR", wave: "x...1x" },
{ name: "STATUS", wave: "34..53", data:"IDLE S A IDLE" },
],
head: {
text: 'Example failing read transfer',
tick: 0,
}}
映射关系:
- AXI->APB:
PSLVERR上的APB错误在读取操作中映射回RRESP,在写入操作中映射回BRESP。 - AHB->APB:对于读写操作,
PSLVERR上的APB错误会被映射回HRESP。
5.3 PPROT
为了支持复杂的系统设计,通常需要系统中的互连设备和其他器件共同提供针对非法交易的保护。下表展示了PPROT的三种访问保护级别和对应的编码:
| PPROT | Protection | Description | Comments |
|---|---|---|---|
| PPROT[0] | Normal or Privileged | Requesters来指示处理模式。privileged模式通常在系统内具有更高的访问权限 | LOW:normal;HIGH:privileged |
| PPROT[1] | Secure or Non-secure | 适用于需要对处理模式进行更精细区分的系统 | LOW:secure;HIGH:non-secure |
| PPROT[2] | Data or Instruction | 指示该事务是数据访问还是指令访问,仅供参考,未必在所有情况下都准确 | LOW:data;HIGH:instruction |
PPROT的主要用途是作为安全或非安全交易的标识符。允许对PPROT[0]和PPROT[2]标识符采用不同的解释。
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