4. AHB-Lite总线协议:信号定义、传输机制与从机接口设计

AHB-Lite,说白了就是ARM公司为高性能系统准备的一条“高速公路”。它只支持一个主机,所以仲裁逻辑简单很多。我在做第一个SoC项目时,用的就是AHB-Lite。当时觉得它比APB复杂不少,但跑起来是真的快。

4.1 AHB-Lite信号定义

先看信号。AHB-Lite的信号可以分为几组:全局信号、地址/控制信号、数据信号、还有响应信号。我习惯把它们记成“谁控制、谁传输、谁应答”。

信号名 方向 描述
HCLK 输入 总线时钟,所有操作同步于此
HRESETn 输入 低电平复位
HADDR[31:0] 主机→从机 32位地址总线
HWRITE 主机→从机 写使能:1为写,0为读
HSIZE[2:0] 主机→从机 传输大小:字节、半字、字等
HBURST[2:0] 主机→从机 突发类型:单次、4拍、8拍、16拍等
HWDATA[31:0] 主机→从机 写数据总线
HRDATA[31:0] 从机→主机 读数据总线
HREADY 从机→主机 传输完成信号,高有效
HRESP[1:0] 从机→主机 传输响应:OKAY、ERROR、RETRY、SPLIT
HSEL 译码器→从机 从机选择信号

关键点:AHB-Lite只有HRESP[1:0]中的OKAY和ERROR两种响应。RETRY和SPLIT在Lite版本中不用。我刚开始设计时,还傻傻地给从机加了RETRY逻辑,结果仿真一直挂死。后来查手册才发现,Lite版本不支持这俩。

4.2 单次传输与突发传输

单次传输,就是一次只传一笔数据。地址发出去,数据回来,完事。突发传输则是一次地址,连续传多笔数据。你想想看,如果CPU要加载一段代码到Cache,用单次传输得发几十次地址,效率太低了。

突发传输的类型由HBURST决定:

HBURST[2:0] 类型 说明
000 SINGLE 单次传输
001 INCR 未指定长度的增量突发
010 WRAP4 4拍回环突发
011 INCR4 4拍增量突发
100 WRAP8 8拍回环突发
101 INCR8 8拍增量突发
110 WRAP16 16拍回环突发
111 INCR16 16拍增量突发

回环突发(WRAP)常用于Cache line fill。地址会回绕到对齐边界。举个例子,WRAP4在字传输时,地址只会在4字对齐的范围内循环。增量突发(INCR)则是一直往上加。

我的经验:设计从机时,一定要检查HBURST和HSIZE的组合是否合法。比如HSIZE=3(64位)配HBURST=WRAP4,地址对齐就有讲究。我曾经遇到一个从机,没检查这个组合,结果地址计算出了bug,仿真跑了一周才发现。

4.3 流水线操作

AHB-Lite最牛的地方,就是流水线操作。地址阶段和数据阶段是分开的。当前拍的地址,对应的数据在下一拍才出现。这样总线可以一直发地址,不用等数据回来。

我画个图你就明白了:

HCLK T1 T2 T3 T4 T5 地址A 地址B 地址C 地址阶段 数据A 数据B 数据C 数据阶段 流水线:地址A在T1发出,数据A在T2返回 地址B在T2发出,数据B在T3返回,以此类推

看到没?地址A在T1发出,数据A在T2才回来。但T2同时可以发地址B。这样总线利用率就上去了。如果从机来不及处理,可以把HREADY拉低,插入等待周期。

注意:流水线虽然快,但设计从机时要注意地址和数据的对应关系。我曾经见过一个从机,地址阶段用了寄存器,数据阶段也用了寄存器,结果地址和数据错了一拍。查了三天才发现是流水线深度没对齐。

4.4 从机接口设计

从机接口设计,是AHB-Lite中最容易出问题的地方。我总结了一个“三步法”:

  1. 地址译码:根据HADDR和HSEL判断是否命中本从机
  2. 传输控制:根据HWRITE、HSIZE、HBURST决定读写操作
  3. 数据响应:准备好HRDATA或接收HWDATA,并给出HREADY和HRESP

下面是一个简单的从机接口代码框架:

module ahb_slave (
    input         HCLK,
    input         HRESETn,
    input         HSEL,
    input  [31:0] HADDR,
    input         HWRITE,
    input  [2:0]  HSIZE,
    input  [2:0]  HBURST,
    input  [31:0] HWDATA,
    output [31:0] HRDATA,
    output        HREADY,
    output [1:0]  HRESP
);

    // 状态机
    reg [1:0] state;
    localparam IDLE = 2'b00,
               BUSY = 2'b01;

    // 地址锁存
    reg [31:0] addr_r;
    always @(posedge HCLK or negedge HRESETn) begin
        if (!HRESETn)
            addr_r <= 32'b0;
        else if (HSEL && HREADY)
            addr_r <= HADDR;
    end

    // 读写控制
    reg we_r;
    always @(posedge HCLK or negedge HRESETn) begin
        if (!HRESETn)
            we_r <= 1'b0;
        else if (HSEL && HREADY)
            we_r <= HWRITE;
    end

    // 数据输出(读操作)
    reg [31:0] rdata;
    always @(*) begin
        case (addr_r[3:2])  // 字对齐地址
            2'b00: rdata = mem[0];
            2'b01: rdata = mem[1];
            2'b10: rdata = mem[2];
            2'b11: rdata = mem[3];
        endcase
    end

    assign HRDATA = rdata;
    assign HREADY = 1'b1;  // 简单从机,总是准备好
    assign HRESP  = 2'b00; // OKAY

endmodule

避坑指南:我曾经设计一个从机,HREADY直接拉高。结果主机发突发传输时,从机来不及处理,数据全乱了。后来我加了FIFO缓冲,才解决问题。所以,如果你的从机处理速度跟不上总线,一定要用HREADY插入等待周期。

从机接口设计时,还有几个细节要注意:

  • 地址对齐:HSIZE=0(字节)时地址任意;HSIZE=1(半字)时地址必须2字节对齐;HSIZE=2(字)时地址必须4字节对齐
  • 突发边界:WRAP4在4字边界回绕,INCR4则一直递增。从机要能正确处理地址回绕
  • 响应时序:HRESP必须在HREADY为高时有效。如果HREADY拉低,HRESP要保持不变

嗯,AHB-Lite其实不难。关键是理解流水线的思想,以及从机如何与主机配合。我当年第一次写AHB从机时,也是对着手册一行一行地看。现在回想起来,其实就那么几个要点。你只要把地址、数据、控制、响应这四类信号理清楚,设计起来就顺了。

可可精品资料库