05【Verilog实战】AMBA 3 APB接口设计(附源码RTL/TB)

官方手册:点击下载
脚  本:makefile
工  具:vcs & verdi


写在前面

  1. 这个专栏的内容记录的是个人学习过程,博文中贴出来的代码是调试前的代码,方便bug重现。
  2. 调试后的程序提供下载,【下载地址

路 线:



一、APB协议

1.1 简介

APB2 :AMBA 2 APB
APB3 :AMBA 3 APB,比 APB2 多两个信号(PREADY , PSLVERR)
APB4 :AMBA 4 APB,比 APB3 多两个信号(PPROT , PSTRB )

  APB(Advanced Peripheral Bus),高级外设总线。APB总线协议是ARM公司提出的AMBA总线结构之一,是一种片上总线结构。
  APB主要用于低带宽的周边外设之间的连接,例如UART、IIC等,它的总线架构不像AHB支持多个主模块,在APB里面没有仲裁器,APB 桥是唯一的主模块。其特点:低带宽;高性能;非流水作业,至少需要两个时钟周期传输,且数据均在时钟上升沿变化;无需等待周期和回应信号;控制逻辑简单,只有四个控制信号,且APB上的传输可采用状态机表示。


1.2 APB2

1.2.1 Interface

SignalSourceDescription
PCLKClock sourceClock. The rising edge of PCLK times all transfers on the APB.
PRESETnSystem bus equivalentReset. The APB reset signal is active LOW. This signal is normally connected directly to the system bus reset signal.
PADDRAPB bridgeAddress. This is the APB address bus. It can be up to 32 bits wide and is driven by the peripheral bus bridge unit.
PSELxAPB bridgeSelect. The APB bridge unit generates this signal to each peripheral bus slave.It indicates that the slave device is selected and that a data transfer is required.There is a PSELx signal for each slave.
PENABLEAPB bridgeEnable. This signal indicates the second and subsequent cycles of an APB transfer.
PWRITEAPB bridgeDirection. This signal indicates an APB write access when HIGH and an APB read access when LOW.
PWDATAAPB bridgeWrite data. This bus is driven by the peripheral bus bridge unit during write cycles when PWRITE is HIGH. This bus can be up to 32 bits wide.
PRDATASlave interfaceRead Data. The selected slave drives this bus during read cycles when

由标准协议手册接口描述可以知道,读\写地址共用地址总线,PADDR\PWDATA\PRDATA总线的最大宽度都是32bit。


1.2.2 Timing
(1)写操作
在这里插入图片描述

  1. 在T0~T1阶段,所有总线处于IDLE状态。
  2. 在T1~T2阶段,第一个时钟周期,处在Setup phase状态。T1时刻,PADDR、PWRITE、PWDATA变化,PSEL拉高,即Master把这些数据发送到总线上。
  3. 在T2~T3阶段,第二个时钟周期,处在Access phase状态。T2时刻,这个时候对应的Slave接收到Master发送过来的地址和写控制命令,此时Slave得知Master要准备发数据过来了,做好准备。
  4. T3时刻,采到PENABLE为高电平,表示Master在这一时刻之前一次写操作已经完成,Master回到IDLE状态,而Slave把数据取走。

(2)读操作
在这里插入图片描述

  1. 在T0~T1阶段,所有总线处于IDLE状态。
  2. 在T1~T2阶段,第一个时钟周期,处在Setup phase状态。T1时刻,PADDR、PWRITE、PWDATA变化,PSEL拉高,即Master把这些数据发送到总线上。
  3. 在T2~T3阶段,第二个时钟周期,处在Access phase状态。T2时刻,这个时候对应的Slave接收到Master发送过来的地址和读控制命令,此时Slave得知自己要将这个地址反馈给Master了。
  4. T3时刻,采到PENABLE为高电平,表示Master在这一时刻之前一次读操作已经完成,Master回到IDLE状态。

1.3 APB3

1.3.1 Interface(+APB2)

SignalSourceDescription
PREADYSlave interfaceReady. The slave uses this signal to extend an APB transfer.
PSLVERR Slave interfaceThis signal indicates a transfer failure. APB peripherals are not required to support the PSLVERR pin. This is true for both existing and new APB peripheral designs. Where a peripheral does not include this pin then the appropriate input to the APB bridge is tied LOW.

1.3.2 Timing
- 无错误传输的情形

(1)无等待的写操作
在这里插入图片描述

  1. 在T0~T1阶段,所有总线处于IDLE状态。
  2. 在T1~T2阶段,第一个时钟周期,处在Setup phase状态。T1时刻,PADDR、PWRITE、PWDATA变化,PSEL拉高,即Master把这些数据发送到总线上。
  3. 在T2~T3阶段,第二个时钟周期,处在Access phase状态。T2时刻,这个时候对应的Slave接收到Master发送过来的地址和写控制命令,此时Slave得知Master要准备发数据过来了,做好准备。
  4. T3时刻,采到 PENABLE & PREADY 为高电平,表示Master这一时刻之前一次写操作已经完成,Master回到IDLE状态,而Slave把数据取走。

(2)有等待的写操作
在这里插入图片描述

  1. 在T0~T1阶段,所有总线处于IDLE状态。
  2. 在T1~T2阶段,第一个时钟周期,处在Setup phase状态。T1时刻,PADDR、PWRITE、PWDATA变化,PSEL拉高,即Master把这些数据发送到总线上。
  3. 在T2~T3阶段,第二个时钟周期,处在Access phase状态。T2时刻,这个时候对应的Slave接收到Master发送过来的地址和写控制命令,此时Slave得知Master要准备发数据过来了,做好准备。
  4. T3时刻,采到 PENABLE 为高电平,但是 PREADY 为低电平,表示Master数据还没准备好。
  5. T5时刻,采到 PENABLE & PREADY 为高电平,表示Master在这一时刻之前一次写操作已经完成,Master回到IDLE状态,而Slave把数据取走。

(3)无等待的读操作
在这里插入图片描述

  1. 在T0~T1阶段,所有总线处于IDLE状态。
  2. 在T1~T2阶段,第一个时钟周期,处在Setup phase状态。T1时刻,PADDR、PWRITE、PWDATA变化,PSEL拉高,即Master把这些数据发送到总线上。
  3. 在T2~T3阶段,第二个时钟周期,处在Access phase状态。T2时刻,这个时候对应的Slave接收到Master发送过来的地址和读控制命令,此时Slave得知自己要将这个地址反馈给Master了。
  4. T3时刻,采到PENABLE & PREADY 为高电平,表示Master这一时刻之前一次读操作已经完成,Master回到IDLE状态。

(4)有等待的读操作
在这里插入图片描述1. 在T0~T1阶段,所有总线处于IDLE状态。
2. 在T1~T2阶段,第一个时钟周期,处在Setup phase状态。T1时刻,PADDR、PWRITE、PWDATA变化,PSEL拉高,即Master把这些数据发送到总线上。
3. 在T2~T3阶段,第二个时钟周期,处在Access phase状态。T2时刻,这个时候对应的Slave接收到Master发送过来的地址和读控制命令,此时Slave得知自己要将这个地址反馈给Master了。
4. T3时刻,采到 PENABLE 为高电平,但是 PREADY 为低电平,表示Slave数据还没准备好。
5. T5时刻,采到 PENABLE & PREADY 为高电平,表示Master这一时刻之前一次读操作已经完成,Master回到IDLE状态。


- 错误传输的情形
(1) 写操作
在这里插入图片描述


(2) 读操作
在这里插入图片描述

可以使用PSLVERR来指示APB传输错误。当PSEL, PENABLE以及PREADY 都为高时,PSLVERR才在最后一个周期进行判断。当任何一个PSEL, PENABLE或者PREADY为低时,你可以将PSLVERR拉低,这是推荐,并不是强制要求。事物处理收到一个错误后,可能或不可能改变外围器件的状态。APB外围设备不要求必须支持PSLVERR引脚,当不使用该引脚时,应被置低。


1.4 APB4

1.4.1 Interface(+APB2)

SignalSourceDescription
PPROTAPB bridgeProtection type. This signal indicates the normal, privileged, or secure protection level of the transaction and whether the transaction is a data access or an instruction access.
PSTRBAPB bridgeWrite strobes. This signal indicates which byte lanes to update during a write transfer. There is one write strobe for each eight bits of the write data bus. Therefore, PSTRB[n] corresponds to PWDATA[(8n + 7):(8n)]. Write strobes must not be active during a read transfer.
PREADYSlave interfaceReady. The slave uses this signal to extend an APB transfer.
PSLVERRSlave interfaceThis signal indicates a transfer failure. APB peripherals are not required to support the PSLVERR pin. This is true for both existing and new APB peripheral designs. Where a peripheral does not include this pin then the appropriate input to the APB bridge is tied LOW.

1.4.2 Write strobes
在这里插入图片描述

由官方手册可以看到,PSTRB信号可以使写数据总线上的数据片段化,即指示数据总线上那几个字节数据有效。由于数据位最大为32bit,即4个字节。因此PSTRB位宽为4位,每一位对应一个字节,设置为1时,指示该字节数据有效。注意的是,在读操作中,PSTRB所有位必须设置为低电平。


1.4.3 Protection unit support
在这里插入图片描述

为了⽀持复杂系统设计,使⽤PPROT[2:0]来标识合法的互联和传输,抢占式总线很容易被监听,因此总线保护⾮常有必要。APB总线提供三层访问保护,PPROT[0]表⽰正常或私有传输,PPROT[1]表⽰安全或不安全传输,PPROT[2]表⽰数据还是指令传输,具体如下表所⽰:

PPROT[2:0]保护等级
[0]1:私有传输
0:一般传输
[1]1:不安全传输
0:安全传输
[2]1:指令传输
0:数据传输

二、Spec

2.1 Function descripton

  apb桥实现了将上游的数据转换成AMBA 3 APB协议发送出去,并接收读到的数据。


2.2 Feature list

  1. 系统工作时钟50Mhz,异步复位。
  2. 采用 APB3 协议标准,无需错误指示。
  3. cmd_i,56bit;[55:48]:r/w;[48:32]:paddr;[31:0]:pwdata

2.3 Block Diagram

在这里插入图片描述


2.4 Interface description

SignalWidthDirectionDescription
pclk_i1inputAPB时钟,50MHz
prst_n_i1inputAPB复位信号
cmd_i56input上游的数据
[55:48]:读写指示位
[47:32]:地址位
[31:0]:数据数据位
cmd_vld_i1input数据有效指示信号
paddr_o16output地址总线,最大32位,这里规定为16位
psel_o1outputslave片选信号,1:选中;0:不选中
penable_o1output使能信号,1:使能;0:不使能
pwrite_o1output读写指示,1:写操作;0:读操作
pwdata_o32output写数据总线,最大为32bit,这里设定为32bit
prdata_o32output读数据总线,最大为32bit,这里设定为32bit
pready_i1inputAPB转移扩展信号,低电平扩展,高电平结束
pslverr_i1input错误指示信号,这里不用,接低电平

2.5 Timing

(1)Write
在这里插入图片描述


(2)Read
在这里插入图片描述


2.6 FSM

在这里插入图片描述


三、Design and Verification

3.1 RTL

/*-------------------------------------------------------------
-- modified by xlinxdu, 2022/05/27
-- pclk 50MHz
-- APB3,No pslverr signal
-- cmd_i:56bit;[55:48]:r/w ,8'b0 -> read,8'b1 -> write
               [47:32]:paddr ,
               [31:0]:pwdata
-------------------------------------------------------------*/
module apb
#(
  parameter RD_FLAG        = 8'b0           ,
  parameter WR_FLAG        = 8'b1           ,
  parameter CMD_RW_WIDTH   = 8              ,
  parameter CMD_ADDR_WIDTH = 16             ,
  parameter CMD_DATA_WIDTH = 32             ,
  parameter CMD_WIDTH      = CMD_RW_WIDTH   + 
                             CMD_ADDR_WIDTH + 
                             CMD_DATA_WIDTH
)(
//-- system signal
  input                           pclk_i       ,
  input                           prst_n_i     ,

//-- cmd_in
  input      [CMD_WIDTH-1:0]      cmd_i        ,
  input                           cmd_vld_i    ,
  output reg [CMD_DATA_WIDTH-1:0] cmd_rd_data_o,

//-- apb interface
  output reg [CMD_ADDR_WIDTH-1:0] paddr_o      ,
  output reg                      pwrite_o     ,
  output reg                      psel_o       ,
  output reg                      penable_o    ,
  output reg [CMD_DATA_WIDTH-1:0] pwdata_o     ,
  input      [CMD_DATA_WIDTH-1:0] prdata_i     ,
  input                           pready_i     ,
  input                           pslverr_i
);

//-- FSM state
parameter IDLE   = 3'b001;
parameter SETUP  = 3'b010;
parameter ACCESS = 3'b100;

//-- current state and next state
reg [2:0] cur_state;
reg [2:0] nxt_state;

//-- data buf
reg                      start_flag     ;
reg [CMD_WIDTH-1:0]      cmd_in_buf     ;
reg [CMD_DATA_WIDTH-1:0] cmd_rd_data_buf;


/*-----------------------------------------------\
 --             update cmd_in_buf              --
\-----------------------------------------------*/
always @ (posedge pclk_i or negedge prst_n_i) begin
  if (!prst_n_i) begin
    cmd_in_buf <= {(CMD_WIDTH){1'b0}};
  end
  else if (cmd_vld_i && pready_i) begin
    cmd_in_buf <= cmd_i;
  end
end

/*-----------------------------------------------\
 --             start flag of transfer         --
\-----------------------------------------------*/
always @ (posedge pclk_i or negedge prst_n_i) begin
  if (!prst_n_i) begin
    start_flag <= 1'b0;
  end
  else if (cmd_vld_i && pready_i) begin
    start_flag <= 1'b1;
  end
  else begin
    start_flag <= 1'b0;
  end
end

/*-----------------------------------------------\
 --           update current state             --
\-----------------------------------------------*/
always @ (posedge pclk_i or negedge prst_n_i) begin
  if (!prst_n_i) begin
    cur_state <= IDLE;
  end
  else begin
    cur_state <= nxt_state;
  end
end

/*-----------------------------------------------\
 --               update next state            --
\-----------------------------------------------*/
always @ (*) begin
  case(cur_state)
    IDLE  :if(start_flag)begin
             nxt_state = SETUP;
           end
           else begin
             nxt_state = IDLE;
           end

    SETUP :nxt_state = ACCESS;
          
    ACCESS:if (!pready_i)begin
             nxt_state = ACCESS;
           end
           else if(start_flag)begin
             nxt_state = SETUP;
           end
           else if(!cmd_vld_i && pready_i)begin
             nxt_state = IDLE;
           end
  endcase
end

/*-----------------------------------------------\
 --         update signal of output            --
\-----------------------------------------------*/
always @ (posedge pclk_i or negedge prst_n_i) begin
  if (!prst_n_i) begin
    pwrite_o  <= 1'b0;
    psel_o    <= 1'b0;
    penable_o <= 1'b0;
    paddr_o   <= {(CMD_ADDR_WIDTH){1'b0}};
    pwdata_o  <= {(CMD_DATA_WIDTH){1'b0}};
  end
  
  else if (nxt_state == IDLE) begin
    psel_o    <= 1'b0;
    penable_o <= 1'b0;
  end

  else if(nxt_state == SETUP)begin
    psel_o    <= 1'b1;
    penable_o <= 1'b0;
    paddr_o   <= cmd_in_buf[CMD_WIDTH-CMD_RW_WIDTH-1:CMD_DATA_WIDTH];
    //-- read
    if(cmd_in_buf[CMD_WIDTH-1:CMD_WIDTH-8] == RD_FLAG)begin
      pwrite_o <= 1'b0;
    end
    //-- write
    else begin
      pwrite_o  <= 1'b1;
      pwdata_o  <= cmd_in_buf[CMD_DATA_WIDTH-1:0];
    end
  end

  else if(nxt_state == ACCESS)begin
    penable_o <= 1'b1;
  end
end

/*-----------------------------------------------\
 --            update cmd_rd_data_buf          --
\-----------------------------------------------*/
always @ (posedge pclk_i or negedge prst_n_i) begin
  if (!prst_n_i) begin
    cmd_rd_data_buf <= {(CMD_DATA_WIDTH){1'b0}};
  end
  else if (pready_i && psel_o && penable_o) begin
    cmd_rd_data_buf <= prdata_i;
  end
end

/*-----------------------------------------------\
 --            update cmd_rd_data_o            --
\-----------------------------------------------*/
always @ (posedge pclk_i or negedge prst_n_i) begin
  if (!prst_n_i) begin
    cmd_rd_data_o <= {(CMD_DATA_WIDTH){1'b0}};
  end
  else begin
    cmd_rd_data_o <= cmd_rd_data_buf;
  end
end

endmodule

3.2 Test bench

//-- modified by xlinxdu, 2022/05/28
`timescale 1ns/1ns
module tb_apb;
  reg         pclk_i       ;
  reg         prst_n_i     ;
                          
  reg  [55:0] cmd_i        ;
  reg         cmd_vld_i    ;
  wire [31:0] cmd_rd_data_o;
                          
  wire [15:0] paddr_o      ;
  wire        pwrite_o     ;
  wire        psel_o       ;
  wire        penable_o    ;
  wire [31:0] pwdata_o     ;
  reg  [31:0] prdata_i     ;
  reg         pready_i     ;
  reg         pslverr_i    ;

initial begin
 // rst; 
  pclk_i   = 0;
  prst_n_i = 1;
  pslverr_i = 0;
  cmd_i = 56'b0;
  cmd_vld_i = 0;
  prdata_i = 32'b0;
  pready_i = 1;
  #20 prst_n_i = 0;
  #20 prst_n_i = 1;

 // cmd_in_wr(cmd_i,56'h01_FF_EE_DD_CC_BB_AA);
    cmd_i     = 56'h01_FF_EE_DD_CC_BB_AA;
    cmd_vld_i = 1   ;
    #20 cmd_vld_i = 0;
    #30 pready_i = 0;
    #80 pready_i = 1;

  #90;
  //cmd_in_rd(cmd_i,56'h00_AA_BB_CC_DD_EE_FF,prdata_i,32'h12_34_56_78);
    cmd_i = 56'h00_AA_BB_CC_DD_EE_FF;
    cmd_vld_i = 1;
    #20 cmd_vld_i = 0;
    #30 pready_i = 0;

    #60 pready_i = 1;
        prdata_i = 32'h12_34_56_78;

    cmd_i = 56'h00_AA_BB_CC_DD_EE_FF;
    cmd_vld_i = 1;
    #20 cmd_vld_i = 0;
    #30 pready_i = 0;

    #50 pready_i = 1;
        prdata_i = 32'h11_22_33_44;


end

always #10 pclk_i = ~pclk_i;

//-- RST
task rst;
  begin
    pclk_i   = 1;
    prst_n_i = 1;
    pslverr_i = 0;
    cmd_i = 56'b0;
    cmd_vld_i = 0;
    prdata_i = 32'b0;
    pready_i = 1;
    #20 prst_n_i = 0;
    #10 prst_n_i = 1;
    //cmd_i = 56'h01_FF_EE_DD_CC_BB_Ab;
  end
endtask

//-- write
task cmd_in_wr;
  output [55:0] cmd;
  input  [55:0] data;

  begin
    cmd     = data;
    cmd_vld_i = 1   ;
    #20 cmd_vld_i = 0;
    #20 pready_i = 0;
    #40 pready_i = 1;
  end
endtask

//-- read
task cmd_in_rd;
  output [55:0] cmd;
  input  [55:0] data ;
  output [31:0] prdata;
  input  [31:0] rd_data;

  begin
    cmd = data;
    cmd_vld_i = 1;
    #20 cmd_vld_i = 0;
    #20 pready_i = 0;
    #40 pready_i = 1;
        prdata = rd_data;
  end
endtask
initial begin
  #1000 $finish;
end
apb tb_apb(
            .pclk_i       (pclk_i       ),
            .prst_n_i     (prst_n_i     ),
            .cmd_i        (cmd_i        ),
            .cmd_vld_i    (cmd_vld_i    ),
            .cmd_rd_data_o(cmd_rd_data_o),
            .paddr_o      (paddr_o      ),
            .pwrite_o     (pwrite_o     ),
            .psel_o       (psel_o       ),
            .penable_o    (penable_o    ),
            .pwdata_o     (pwdata_o     ),
            .prdata_i     (prdata_i     ),
            .pready_i     (pready_i     ),
            .pslverr_i    (pslverr_i    )
          );


initial begin
  $fsdbDumpfile("apb.fsdb");
  $fsdbDumpvars            ;
  $fsdbDumpMDA             ;
end

endmodule

3.3 Analyse

在这里插入图片描述

bug1:由仿真波形可以看到,当前状态cur_state和下一个状态nxt_state、prdata_i和cmd_data_buf在同一拍变化,但是设计中两者采用的是时序逻辑,应该会错一拍更新。

在这里插入图片描述

由官方手册可以看到,这两个输入信号在T4时刻之后一点才拉高,而搭建的testbench中,他们是同一时刻变化,这时候,T4会采到一个亚稳态数据,导致出错,因此在tb中改一下这里即可。

 // cmd_in_wr(cmd_i,56'h01_FF_EE_DD_CC_BB_AA);
    cmd_i     = 56'h01_FF_EE_DD_CC_BB_AA;
    cmd_vld_i = 1   ;
    #20 cmd_vld_i = 0;
    #30 pready_i = 0;
    #80 pready_i = 1;

更改为:pready_i多延迟1ns输入,错开和时钟同拍

 // cmd_in_wr(cmd_i,56'h01_FF_EE_DD_CC_BB_AA);
    cmd_i     = 56'h01_FF_EE_DD_CC_BB_AA;
    cmd_vld_i = 1   ;
    #20 cmd_vld_i = 0;
    #31 pready_i = 0;
    #80 pready_i = 1;

在这里插入图片描述

由这张图可以看到nxt_state产生了一个中间态IDLE,这是由于三段状态机的第二段状态翻转采用的是组合逻辑,但由于cur_state和nxt_state之间是时序关系,等时钟沿来临的时候,早已经稳定,因此并未影响正确的逻辑。在硬件上的表现是波形出现毛刺


四、Result

4.1 Write

(1)有等待的写操作
在这里插入图片描述


4.2 Read

(1)有等待的读操作

在这里插入图片描述


(2)无等待的读操作

在这里插入图片描述


4.3 Output

在这里插入图片描述


作者:xlinxdu
版权:本文版权归作者所有
转载:未经作者允许,禁止转载,转载必须保留此段声明,必须在文章中给出原文连接。

评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xlinxdu

你的鼓励是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值