VERDVANA'S BLOG Verdvana

AMBA APB总线协议


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<b
PSTRB 1/2/4 Write strobes 指示在写传输期间,要更新哪个字节通道
写数据总线的每8bit对应1bitPSTRB
在读传输期间,PSTRB不能跳变
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拉高。同时如果之前采集PADDR、PWDATA等信号有问题,则认为写传输有误,所以在此时把PSLVERR也拉高。主机会在下一个上升沿采集到此错误信号,得知写入错误。

        读传输也可能出现错误,表示无可读数据。


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来生成一个8*512的单口SRAM,然后例化:

module SPRAM #(
	parameter  DATA_WIDTH = 8,
                   ADDR_WIDTH = 9
)(
	// Clock
        input	wire						clk,			// Clock
        input	wire						cs_n,			// Chip select
        input   wire                                            we_n,
        input   wire    [ADDR_WIDTH-1:0]                        addr,
        input   wire    [DATA_WIDTH-1:0]                        din,
        output  logic   [DATA_WIDTH-1:0]                        dout
);


    SRAM_SP_512_8 u_SRAM_SP_512_8(
        .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		DATA_WIDTH	= 8,
					ADDR_WIDTH  = 9

)(
	// Clock and reset
	input	wire											pclk,			// Clock
	input	wire											preset_n,		// Async reset
	// APB bus signal
	input	wire	[ADDR_WIDTH-1:0]						paddr,			// Address
	input	wire											psel,			// Select
	input	wire											penable,		// Enable
	input	wire											pwrite,			// Write/Read
	input	wire	[DATA_WIDTH-1:0]						pwdata,			// Write data
	input	wire	[2:0]									pprot,			// Protection type
	input	wire	[(DATA_WIDTH/8)-1:0]					pstrb,			// Write storbes

	output	logic	[DATA_WIDTH-1:0]						prdata,			// Read data
	output	logic											pready,			// Read ready
	output	logic											pslverr			// Transfer error
);

	//=========================================================================
	// 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
	logic	[(DATA_WIDTH/8)-1:0] [7:0]	wr_mask;
	logic	[DATA_WIDTH-1:0]	wr_data;
	logic						sram_clk;
	logic						sram_cs_n;
	logic						sram_we_n;
	logic	[ADDR_WIDTH-1:0]	sram_addr;
	logic	[DATA_WIDTH-1:0]	sram_din;
	logic	[DATA_WIDTH-1:0]	sram_dout;

	//=========================================================================
	// 
	enum {
		IDLE,
		SETUP,
		ACCESS
	} curr_state,next_state;

	always_ff@(posedge pclk, negedge preset_n)begin
		if(!preset_n)
			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)
					next_state	= IDLE;
				else if(psel && !penable)
					next_state	= SETUP;
				else
					next_state	= ACCESS;
			end
			default:next_state	= IDLE;
		endcase
	end

	assign	sram_clk	= pclk;
	assign	sram_cs_n	= !psel || penable;
	assign	sram_we_n	= psel?~pwrite:sram_we_n;
	assign	sram_addr	= (next_state == SETUP) ? paddr:sram_addr;
	assign	wr_data	= (next_state == SETUP) ? pwdata:wr_data;
	assign	prdata		= sram_dout;
	assign	pready		= penable;

	always_comb begin
		for(int i=0;i<(DATA_WIDTH/8);i++)begin
			if(pstrb[i])
				wr_mask[i]	= wr_data[8*i+:8];
			else
				wr_mask[i]	= '0;
		end
	end

	assign	sram_din	= wr_mask;

	genvar k;
	generate for(k=0;k<(DATA_WIDTH/8);k++)begin:inst
		SPRAM #(
			.DATA_WIDTH(8),
			.ADDR_WIDTH(9)
		) u_SPRAM_0(
			.clk(sram_clk),		//input,	Clock
			.cs_n(sram_cs_n),	//input,	Chip select
			.we_n(sram_we_n),	//input,	1:read;0:write
			.addr(sram_addr),	//input,	Address
			.din(sram_din[k*8+:8]),		//input,	Write data
			.dout(sram_dout[k*8+:8])	//output,	Read data
		);
	end
	endgenerate

endmodule