AMBA总线(1)—— APB协议

APB是最简单的AMBA总线了,功耗很低,它多用于低速外围设备和访问寄存器。相比AHB和AXI,有几个很不一样的点:

  • 最快只能背靠背(back to back)传输,至少2个周期传输一个数据,PSEL起来然后PENABLE起来。
    • 背靠背传输,即连续传输,这笔传输传完,紧挨着下一个Cycle就可以开始下一笔传输。
  • 不能Pipeline传输、Burst传输、Outstanding传输,数据有效时,其地址必然是当前数据的对应地址。
    • pipeline传输,即流水式传输,指当前传输的结束Cycle可以是下一笔传输的起始Cycle,起到无缝衔接。
    • burst传输,即只需指定起始地址和突发长度,即可自动对后面连续地址进行操作,无需提供连续地址。
    • outstanding传输,即不需要等待读写数据完成,即可继续提供下一笔传输的命令和地址,提高传输效率。
  • 不能读写同时传输,因为其读写地址是共用的。(AHB也不能读写同时传输)
  • 不能仲裁,因为是单主多从协议。典型的APB协议包括唯一的APB桥作为Master,而所有的APB模块都是APB slave。

1 前言

1.2 APB 版本

1998年发布的 APB Specification Rev E 现已过时,并被以下三个修订版所取代:

  • 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)

1.2.1 AMBA2 APB Specification(APB2)

AMBA2 APB 规范详见AMBA2 APB Specification Rev2(ARM IHI 0011A)

该规范定义了接口信号、基本的读写传输以及APB的两个组件APB bridge和APB slave。

规范的这个版本被称为APB2。

1.2.2 AMBA3 APB Protocol Specification v1.0(APB3)

AMBA3 APB Protocol Specification v1.0定义了以下附加功能:

  • 等待状态。参见Transfers
  • 错误报告。参见Error response

以下接口信号支持此功能:

  • PREADY 准备就绪的信号,表示APB传输完成。
  • PSLVERR 传输失败的错误信号。

规范的这个版本被称为APB3。

1.2.3 AMBA APB Protocol Specification v2.0(Issue C, APB4)

AMBA APB Protocol Specification v2.0定义了以下附加功能:

  • 事务的保护。参见Protection unit support
  • 稀疏数据传输。参见Write strobes

以下接口信号支持此功能:

  • PPROT 一种保护信号,用于支持非安全事务和安全事务。
  • PSTRB 一种写掩码信号,用于在写数据总线上实现稀疏数据传输。

规范的这个版本被称为APB4。

1.2.4 AMBA APB Specification (Issue D/E, APB5)

AMBA APB Protocol Specification issue D/E 定义了以下附加功能:

  • PWAKEUP 信号。
  • User 信号。
  • Parity protection and check 信号。
  • Reaml Management Extension (RME) 支持。(issue E)

规范的这个版本被称为APB5。

2 APB信号

2.1 数据总线

  • APB协议有两个独立的数据总线,一个用于读取数据,一个用于写入数据。
  • 总线可以达到32位宽。
  • 由于总线没有各自的握手信号,因此数据传输不可能同时发生在两个总线上。

3 APB传输

3.1 写传输

介绍写传输的几种类型:

  • 没有等待状态。
  • 具有等待状态。

3.1.1 没有等待状态

图3-1表示没有等待传输的基本写传输。

T1时,写传输开始于地址PADDR、写数据PWDATA、写信号PWRITE、选择信号PSEL,寄存在PCLK上升沿。这称为写传输的起始阶段。

T2时,使能信号PENABLE和准备信号PREADY寄存在PCLK上升沿。

当断言时,PENABLE表示传输的访问阶段的开始。

当断言时,PREADY表示Slave可以在PCLK的下一个上升边完成传输。

地址PADDR、写数据PWDATA和控制信号都保持有效,直到传输在访问阶段的T3完成结束。

使能信号PENABLE在传输结束时被撤销。

选择信号PSEL也被去断言,除非在此传输之后立即有另一个传输到同一Slave。

下图给出了burst写的例子:

3.1.2 具有等待状态

图3-2在显示了如何在访问阶段使用PREADY信号扩展传输。当PENABLE为高,而Slave驱动PREADY为低时扩展传输。其他信号保持不变,而PREADY保持低。

当PENABLE为低时,PREADY可以取任何值。这确保了具有固定两个周期访问的外围设备可以让PREADY tie1。

—— 注意 ————

建议地址和写信号在传输后不要立即改变,而是保持稳定,直到发生另一次访问。这能降低功耗。

3.2 写掩码

写掩码信号PSTRB在写数据总线上实现稀疏数据传输。

每个写掩码信号对应于写数据总线的一个字节。当断言为高时,写掩码指示写数据总线的相应字节通道包含有效信息。写数据总线的每8位都有一个写掩码,因此PSTRB[n]对应于PWDATA[(8n +7):(8n)]。

在32位数据总线上,这种关系如图3-3所示。

——注意——

读传输时,Master必须驱动PSTRB的所有位为低。

3.3 读传输

本节介绍两种类型的读传输:

  • 没有等待状态。
  • 具有等待状态。

3.3.1 没有等待状态

如图3-4所示。地址、写、选择和使能信号的时序和前文<写传输>中描述的一样。

Slave必须在读传输结束之前提供数据。

下图给出了burst读的例子。

3.3.2 具有等待状态

图3-5显示了PREADY信号如何扩展传输。如果PREADY在访问阶段被驱动为低,则传输将被扩展。

该协议确保其他信号在额外的周期内保持不变。

图3-5显示了使用PREADY信号添加两个周期。然而,你可以添加从0开始的任何数量的额外周期。

3.4 错误响应

可以使用PSLVERR指示APB传输上的错误条件。读和写事务都可能发生错误。

当PSEL、PENABLE和PREADY均为高时,PSLVERR仅在APB传输的最后一个周期中被认为有效。

建议(但非强制)在当PSEL、PENABLE或PREADY中的任何一个为低时,即未进行采样时驱动PSLVERR为低。

接收到错误事务可能(也可能没有)改变了Slave的状态,这是特定于外围设备的,两者都是可以接受的。当写事务收到一个错误时,这并不意味着外围设备中的寄存器没有更新。接收到错误读事务可能返回无效数据,但没有要求Slave要驱动data总线为全0。

APB外设不需要支持PSLVERR引脚,现有的和新的APB外围设计都是如此。如果外设没有这个引脚,那么Slave的合适输入为Tie 0。

3.4.1 写传输

图3-6给出了一个写传输失败并报错的例子。

3.4.2 读传输

读传输也可以在错误响应后完成,这表明没有有效的读数据可用。

如图3-7所示,读传输完成后出现错误响应。

3.4.3 PSLAVERR映射

当桥接时:

从AXI到APBPSLVERR被映射回RRESP/BRESP =SLVERR。这是通过将PSLVERR映射到用于读取的AXI信号的RRESP[1](对于读)和BRESP[1](对于写)来实现的。

从AHB到APBPSLVERR被映射回HRESP = ERROR(对于读和写)。这是通过将PSLVERR映射到AHB信号的HRESP[0]来实现的。

3.5 保护单元的支持

为了支持复杂的系统设计,通常需要系统中的互连和其他设备提供防止非法交易的保护。对于APB接口,这种保护由PPROT[2:0]信号提供。

访问保护的三个级别是:

(1)Normal 或Privileged,PPROT[0]

  • 0表示正常访问
  • 1表示特权访问。

这被一些Master用来表示它们的处理模式。Privileged处理模式通常在系统中具有更高级别的访问权限。

(2)Secure或non-Secure,PPROT[1]

  • 0表示安全访问
  • 1表示非安全访问。

这用于需要在处理模式之间进行更大程度区分的系统。

—— 注意 ————

这个位的配置是这样的:当它为1时,事务被认为是非安全的;当它为0时,事务被认为是安全的。

(3)Data or Instruction,PPROT[2]

  • 0表示数据访问
  • 1表示指令访问。

该位表示该事务是数据访问还是指令访问。

—— 注意 ————

此指示只是作为提示,并非在所有情况下都是准确的。例如里面事务包含指令和数据项的混合。默认情况下,建议将访问标记为数据访问,除非明确知道它是指令访问。

—— 注意 ————

PPROT的主要用途是作为安全或非安全事务的标识符。

使用PPROT[0]和PPROT[2]标识符的不同解释是可以接受的。

4 APB运行阶段

4.1 运行阶段

图4-1表示APB的运行活动。

状态机通过以下状态运行:

(1)IDLE

这是APB的默认状态。

(2)SETUP

当需要传输时,总线进入SETUP状态,在该状态下,将断言适当的选择信号PSELx。总线只在一个时钟周期内保持SETUP状态,并且总是移动到时钟的下一个上升沿的ACCESS状态。

(3)ACCESS

使能信号PENABLE在ACCESS状态下被断言。地址、写、选择、写数据信号必须在从SETUP状态转换到ACCESS状态的过程中保持稳定。

从ACCESS状态的退出由Slave的PREADY信号控制:

  • 如果PREADY被slave保持为低,那么外围总线仍然处于ACCESS状态。
  • 如果PREADY被slave驱动为高,那么ACCESS状态将退出,如果不需要更多的传输,总线将返回IDLE状态。或者如果接下来发生另一个传输,总线直接移动到SETUP状态。

5 APB读写设计

随着时代的进步,SOC 设计中大多采用 AXI 总线进行数据的高速读写,而 APB 总线往往用于寄存器配置,下面设计一个模拟 APB 读写寄存器文件的设计。

整个逻辑框图如下所示:

各个模块的功能如下所示:

  • apb_top_tb:testbench文件,用于模拟APB接口,提供必要的激励。
  • apb_top:APB设计文件,内部包含APB时序转RAM读写时序,可以对寄存器文件 apb_rf.v 中进行读写操作。
  • apb_rf:寄存器文件,本质是个RAM,用于存放寄存器,实际综合会成为一个真实的RAM。

5.1 apb_rf

SOC设计中包含大量的寄存器控制信号,例如某个功能开关不希望定死,于是就会外接寄存器进行控制。又或者希望实时读取某个模块的状态,于是外接到只读寄存器中,方便软件读取。

这里我们模拟三个寄存器,他们的功能如下所示:

1、寄存器 alarm
bit[0] : 为1时开启报警功能,为0时关闭报警功能。可读可写。
bit[15:0]:报警阈值,达到该阈值后才会报警。可读可写。

2、寄存器 run
bit[0]:为1时开始运行。可读可写。
bit[1]:为1时结束运行。可读可写。

3、寄存器 status
bit[4:0]:模块运行状态。只读

这些寄存器要写在 ram 中必须要有一个单独模块,我们一般叫做 config 文件或者 register file。该文件会提供 ram 读写接口,还会根据寄存器实际内容,在接口上加入一些输入或输出信号。根据上面描述,我们可以制作如下的 apb_rf.v 文件。

//**************************************************************************
// *** 名称 : apb_rf.v
// *** 作者 : xianyu_FPGA
// *** 博客 : https://www.cnblogs.com/xianyuIC/
// *** 日期 : 2023年4月
// *** 描述 : 寄存器文件
//**************************************************************************

module apb_rf(
//========================< port >==========================================
//system --------------------------------------------
input                       pclk_i                  ,
input                       prst_n_i                ,
//ram -----------------------------------------------
input                       ram_wr                  ,
input                       ram_rd                  ,
input       [15:0]          ram_addr                ,
input       [31:0]          ram_wdata               ,
output  reg [31:0]          ram_rdata               ,
//---------------------------------------------------
//register output
output  reg                 alarm_int               ,
output  reg [15:0]          alarm_value             ,
output  reg                 run_start               ,
output  reg                 run_stop                ,
//register input
input       [ 4:0]          status
);
//========================< signal >========================================
wire                        wr_alarm                ;
wire                        wr_run                  ;
//==========================================================================
//==    write enable
//==========================================================================
assign wr_alarm = ram_wr & (ram_addr == 16'h0);
assign wr_run   = ram_wr & (ram_addr == 16'h4);
//==========================================================================
//==    write data
//==========================================================================
always @(posedge pclk_i or negedge prst_n_i) begin
    if(!prst_n_i) begin
        alarm_int   <= 1'b0;
        alarm_value <= 16'h0;
    end
    else if(wr_alarm) begin
        alarm_int   <= ram_wdata[    0];
        alarm_value <= ram_wdata[16: 1];
    end
end

always @(posedge pclk_i or negedge prst_n_i) begin
    if(!prst_n_i) begin
        run_start   <= 1'b0;
        run_stop    <= 1'b0;
    end
    else if(wr_run) begin
        run_start <= ram_wdata[0];
        run_stop  <= ram_wdata[1];
    end
end
//==========================================================================
//==    read data
//==========================================================================
always @(*) begin
    if(ram_rd)
        case(ram_addr)
            //reg alarm
            16'h0: begin
                    ram_rdata[    0] = alarm_int;
                    ram_rdata[16: 1] = alarm_value;
            end
            //reg run
            16'h4: begin
                    ram_rdata[0] = run_start;
                    ram_rdata[1] = run_stop;
            end
            //reg status
            16'h8: begin
                    ram_rdata[4:0] = status;
            end
            default: ram_rdata[31:0] = 32'h0;
        endcase
    else
        ram_rdata = 32'h0;
end

endmodule

5.2 apb_top

APB 接口无法直接对 RAM 进行读写操作,因此需要一个 APB 转 RAM 的设计,这里建立一个 apb_top.v 文件,将 apb_rf.v 文件包起来,同时加入 APB 转 RAM 设计。可以看到这个 apb_top 接口完全就是 APB 接口了,可以看成是一个 APB Slave。

//**************************************************************************
// *** 名称 : apb_top.v
// *** 作者 : xianyu_FPGA
// *** 博客 : https://www.cnblogs.com/xianyuIC/
// *** 日期 : 2023年4月
// *** 描述 : APB读写RAM寄存器
//**************************************************************************

module apb_top(
//========================< port >==========================================
input                       pclk_i                  ,
input                       prst_n_i                ,
//---------------------------------------------------
input       [15:0]          paddr_i                 ,
input                       pwrite_i                ,
input                       psel_i                  ,
input                       penable_i               ,
input       [31:0]          pwdata_i                ,
output reg  [31:0]          prdata_o                ,
output                      pready_o                ,
output                      pslverr_o
);
//========================< signal >========================================
wire                        ram_wr                  ;
wire                        ram_rd                  ;
wire    [15:0]              ram_addr                ;
wire    [31:0]              ram_wdata               ;
wire    [31:0]              ram_rdata               ;
//---------------------------------------------------
wire                        alarm_int               ;
wire    [15:0]              alarm_value             ;
wire                        run_start               ;
wire                        run_stop                ;
//wire    [ 4:0]            status                  ;
//==========================================================================
//==    APB trans to RAM
//==========================================================================
assign ram_wdata = (psel_i & pwrite_i) ? pwdata_i : 32'h0;
assign ram_addr  = psel_i ? paddr_i : 16'h0;
assign ram_wr    = psel_i & pwrite_i & penable_i;
assign ram_rd    = psel_i & (~pwrite_i) & (~penable_i);

always @(posedge pclk_i or negedge prst_n_i) begin
    if(!prst_n_i)
        prdata_o <= 32'h0;
    else
        prdata_o <= ram_rdata;
end
//==========================================================================
//==    APB register
//==========================================================================
apb_rf u_apb_rf
(
    .pclk_i                 (pclk_i                 ),  //I
    .prst_n_i               (prst_n_i               ),  //I
    //ram -------------------------------------------
    .ram_wr                 (ram_wr                 ),  //I
    .ram_rd                 (ram_rd                 ),  //I
    .ram_addr               (ram_addr               ),  //I
    .ram_wdata              (ram_wdata              ),  //I
    .ram_rdata              (ram_rdata              ),  //O
    //register output -------------------------------
    .alarm_int              (alarm_int              ),  //O
    .alarm_value            (alarm_value            ),  //O
    .run_start              (run_start              ),  //O
    .run_stop               (run_stop               ),  //O
    //register input --------------------------------
    .status                 (5'd4                   )   //I
);

endmodule

5.3 apb_top_tb

最后是激励部分,由于 apb_top 模块可以看成是一个 APB slave 接口,那么这个 apb_top_tb 文件就可以看成是一个上游的 APB Master。我们根据 APB 时序,对上面的 3 个寄存器进行读写操作。

//**************************************************************************
// *** 名称 : apb_top_tb.v
// *** 作者 : xianyu_FPGA
// *** 博客 : https://www.cnblogs.com/xianyuIC/
// *** 日期 : 2023年4月
// *** 描述 : APB读写激励
//**************************************************************************
`timescale 1ns/1ps  //时间精度
`define    Clock 20 //时钟周期
 
module apb_top_tb;
//========================< signal >========================================
reg                         pclk_i                  ;
reg                         prst_n_i                ;
//---------------------------------------------------
reg  [15:0]                 paddr_i                 ;
reg                         pwrite_i                ;
reg                         psel_i                  ;
reg                         penable_i               ;
reg  [31:0]                 pwdata_i                ;
wire [31:0]                 prdata_o                ;
wire                        pready_o                ;
wire                        pslverr_o               ;
//---------------------------------------------------
reg  [31:0]                 rdata                   ;
//==========================================================================
//==    instantiation
//==========================================================================
apb_top u_apb_top
(
    .pclk_i                 (pclk_i                 ),
    .prst_n_i               (prst_n_i               ),
    //-----------------------------------------------
    .paddr_i                (paddr_i                ),
    .pwrite_i               (pwrite_i               ),
    .psel_i                 (psel_i                 ),
    .penable_i              (penable_i              ),
    .pwdata_i               (pwdata_i               ),
    .prdata_o               (prdata_o               ),
    .pready_o               (pready_o               ),
    .pslverr_o              (pslverr_o              )
);
//==========================================================================
//==    clock and reset
//==========================================================================
initial begin
    pclk_i = 1;
    forever
        #(`Clock/2) pclk_i = ~pclk_i;
end

initial begin
    paddr_i    = 0;
    pwrite_i   = 0;
    psel_i     = 0;
    penable_i  = 0;
    pwdata_i   = 0;
    prst_n_i   = 0;
    repeat(10) @(negedge pclk_i);
    prst_n_i   = 1;
end
//==========================================================================
//==    fsdb
//==========================================================================
/*
initial begin
  $fsdbDumpfile("apb_tb.fsdb");
  $fsdbDumpvars;
  $fsdbDumpMDA;
end
*/
//==========================================================================
//==    task: apb_wr apb_rd
//==========================================================================
task apb_wr;
    input [15:0] addr;
    input [31:0] data;
    begin
        @(posedge pclk_i)
        paddr_i   = addr;
        pwrite_i  = 1;
        pwdata_i  = data;
        psel_i    = 1;
        penable_i = 0;
        
        @(posedge pclk_i)
        penable_i = 1;
        
        @(posedge pclk_i)
        pwrite_i  = 0;
        psel_i    = 0;
        penable_i = 0;
    end
endtask

task apb_rd;
    input [15:0] addr;
    output[31:0] data;
    begin
        @(posedge pclk_i)
        paddr_i   = addr;
        psel_i    = 1;
        penable_i = 0;
        
        @(posedge pclk_i)
        penable_i = 1;
        
        @(posedge pclk_i)
        psel_i    = 0;
        penable_i = 0;
        data = prdata_o;
    end
endtask
//==========================================================================
//==    main test
//==========================================================================
initial begin
    wait(prst_n_i);
    $display("\nAPB test begin! @%t\n",$time);
    
    repeat(10) @(negedge pclk_i);
    apb_wr(16'h0000, 32'h31);
    apb_wr(16'h0004, 32'h1);
 
    apb_rd(16'h0008, rdata);
    repeat(10) @(negedge pclk_i);
    $display("\nAPB test end! @%t\n",$time);

    $finish;
end
 
endmodule

5.4 仿真结果

如果有 Verdi 可以用 VCS/IRUN 等编译工具对这些代码进行编译,生成 fsdb 文件给 Verdi 用。这里还是用以前的老办法,采用 Modelsim 进行仿真,结果如下所示:

从波形中可以看出,两次写和一次读符合 APB 数据手册。根据寄存器写入情况,apb_rf 模块中的相应信号也进行了变化。

 

参考资料:

[1] ARM官方数据手册:https://www.arm.com/architecture/system-architectures/amba/amba-specifications

[2] 芯王国:https://blog.csdn.net/weixin_40377195/article/details/124899571

posted @ 2023-04-01 19:46  咸鱼IC  阅读(8491)  评论(0编辑  收藏  举报