AMBA APB总线协议

AMBA


1 前言

        APB(Advanced Peripheral Bus),高级外设总线,是AMBA体系中专门为“延迟不敏感(Latency-insensitive)”及“低带宽需求”的外设寄存器访问而定义的总线标准,例如UART、IIC等。它的总线架构如下图所示:

img1

        可以看出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:

        官方文档:


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时刻主机把PSELPWRITE拉高;PADDRPWDATA准备好地址和数据,把PENABLE拉低表示下一拍实施写入。

        第三阶段为ACCESS,T2时刻主机把PENABLE拉高,表示该数据有效;从机采样到PSEL拉高,会把PREADY拉高表示会在T3时刻接收写入数据;PADDRPWDATA以及其它控制信号必须保持稳定,直至传输完成。

        第四阶段退出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同时为高时)如果之前采集PADDRPWDATA等信号有问题,则认为写传输有误,所以在此时把PSLVERR也拉高。主机会在下一个上升沿采集到此错误信号,得知写入错误。

        PSLVERR信号仅在PSELPENABLEPREADY同时为HIGH的最后周期内有效。建议(但非强制要求)在PSELPENABLEPREADY为低电平时,将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

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

Back to Archive