VERDVANA'S BLOG Verdvana

AMBA APB总线协议

AMBA


1 前言

        APB(Advanced Peripheral Bus),高级外设总线,主要用于低带宽的周边外设之间的连接,例如UART、IIC等。它的总线架构如下图所示:

img1

        可以看出APB Bridge是AMBA APB中的唯一总线主机,APB Bridge也是AMBA中的一个从机。

        特点:

  • 低带宽;高性能;
  • 不支持pipeline、Busrst、Outstanding传输,最快只能Back to back,至少需要两个时钟周期传输;
  • 无需等待周期和回应信号;
  • 不能读写同时传输,AHB也不行,AXI可以;
  • 不支持仲裁,因为是单主多从。

        版本:


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 写传输

        没有等待状态:

img2

        第一阶段为IDLE状态。

        第二阶段为SETUP,此时主机把PSEL和PWRITE拉高;PADDR和PWDATA准备好地址和数据;把PENABLE拉低表示下一拍实施写入。

        第三阶段为ACCESS,主机把PENABLE拉高,表示该数据有效;从机采样到PSEL拉高,会把PREADY拉高表示接收数据;

        第四阶段退出ACCESS,如果PREADY为高,代表从机接收到信号,主机则拉低PSEL和PENABLE信号,进入IDLE状态。

        没有等待状态的连续写时序:

img3

        具有等待状态:

img4

        ACCESS可能不止一个周期,取决于从机什么时候回复PREADY信号,因此数据写入完成要大于两个时钟周期。

4.2 读传输

        没有等待状态,类似写传输:

img5

        具有等待状态,类似写传输:

img6


5 其他信号

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表示从机认为写传输有错误。时序图如下:

img7

        写传输时,当从机任务可以接受数据时,把PREADY拉高。同时(RSEL&PENABLE&PREADY同时为高时)如果之前采集PADDR、PWDATA等信号有问题,则认为写传输有误,所以在此时把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、某些寄存器不可访问

6 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;

	//=========================================================================
	// Inst
    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