3、AXI4-Lite从机接口设计(一):写地址通道、写数据通道、写响应通道与寄存器映射

各位同学,今天我们正式进入AXI4-Lite从机接口的实战环节。说实话,很多初学者看到AXI协议那几百页的文档就头大。别担心,我们只挑最常用的部分来讲。今天先搞定写操作相关的三个通道:写地址、写数据、写响应。最后再聊聊寄存器地址怎么映射。

3.1 写地址通道逻辑实现

写地址通道,说白了就是主机告诉从机:「我要往哪个地址写数据」。这个通道的信号不多,但时序要求很严格。

我个人习惯把写地址通道的逻辑分成三块:

  • 地址锁存:当 AWVALID 和 AWREADY 同时为高时,锁存地址
  • 地址译码:判断地址是否落在本模块的地址范围内
  • 握手状态机:管理 AWREADY 的拉高时机

先看核心代码:

// 写地址通道 - 地址锁存与译码
always @(posedge aclk or negedge aresetn) begin
    if (!aresetn) begin
        awready    <= 1'b0;
        aw_addr    <= 32'd0;
        aw_valid_d <= 1'b0;
    end else begin
        // 握手成功时锁存地址
        if (awvalid && awready) begin
            aw_addr    <= awaddr;
            aw_valid_d <= 1'b1;
        end else if (bvalid && bready) begin
            aw_valid_d <= 1'b0;  // 写响应完成后清除
        end
        
        // AWREADY 控制:默认准备好接收
        awready <= ~aw_valid_d;  // 忙时拉低
    end
end
我的经验:曾经有个项目,我图省事让 AWREADY 一直拉高。结果主机连续发来多个写请求,从机根本处理不过来,数据全乱了。后来我加了这个「忙标志位」,问题就解决了。

地址译码的逻辑其实很简单。假设我们的按键消抖模块占用 4 个 32 位寄存器,基地址是 0x43C0_0000:

// 地址译码
wire [3:0] reg_sel;
assign reg_sel = (awaddr[31:4] == BASE_ADDR[31:4]) ? 
                 awaddr[3:2] : 2'b0;  // 取高28位比较,低2位按字对齐

嗯,这里要注意:AXI4-Lite 的地址是字节地址,但寄存器通常是 32 位对齐的。所以低 2 位我们一般不用,直接从 bit[3:2] 开始译码。

3.2 写数据通道逻辑实现

写数据通道和写地址通道是并行工作的。主机可以同时发送地址和数据,也可以先发地址再发数据。我们作为从机,必须等两者都到齐了才能开始写操作。

关键信号就两个:WVALID 和 WREADY。握手成功后,数据被锁存:

// 写数据通道
always @(posedge aclk or negedge aresetn) begin
    if (!aresetn) begin
        wready   <= 1'b0;
        w_data   <= 32'd0;
        w_strb   <= 4'b0;
    end else begin
        // 握手成功锁存数据
        if (wvalid && wready) begin
            w_data <= wdata;
            w_strb <= wstrb;  // 字节使能信号
        end
        
        // WREADY 与 AWREADY 同步控制
        wready <= awready;  // 简单做法:与地址通道同步
    end
end
避坑指南:我曾经犯过一个低级错误——忘了处理 WSTRB 信号。在按键消抖模块中,我们通常只用到低 8 位(按键值),但如果你不管 WSTRB,主机写 0xFFFF_0000 时可能会把高 16 位也改了。所以一定要用 WSTRB 做写使能过滤。

字节使能的处理逻辑:

// 字节使能过滤
wire [31:0] w_data_masked;
assign w_data_masked[7:0]   = w_strb[0] ? w_data[7:0]   : 8'h00;
assign w_data_masked[15:8]  = w_strb[1] ? w_data[15:8]  : 8'h00;
assign w_data_masked[23:16] = w_strb[2] ? w_data[23:16] : 8'h00;
assign w_data_masked[31:24] = w_strb[3] ? w_data[31:24] : 8'h00;

3.3 写响应通道逻辑实现

写响应通道是 AXI 协议里比较人性化的设计。主机写完数据后,从机必须给个回应:成功、失败还是其他状态。这个回应通过 BRESP 信号传递。

响应状态码只有三种:

BRESP[1:0] 含义 使用场景
2'b00 OKAY 正常写完成
2'b01 EXOKAY 排他性访问成功(AXI4-Lite 不用)
2'b10 SLVERR 从机错误(地址越界、写保护等)
2'b11 DECERR 译码错误(地址不在任何从机范围)

对于按键消抖模块,我们只需要 OKAY 和 SLVERR 两种状态:

// 写响应通道
always @(posedge aclk or negedge aresetn) begin
    if (!aresetn) begin
        bvalid <= 1'b0;
        bresp  <= 2'b00;
    end else begin
        // 地址和数据都准备好后,产生响应
        if (aw_valid_d && w_valid_d && !bvalid) begin
            bvalid <= 1'b1;
            // 地址越界检查
            if (awaddr[31:4] != BASE_ADDR[31:4])
                bresp <= 2'b10;  // SLVERR
            else
                bresp <= 2'b00;  // OKAY
        end else if (bvalid && bready) begin
            bvalid <= 1'b0;  // 主机接收响应后清除
        end
    end
end
关键点:写响应必须在地址和数据都有效之后才能发出。我见过有人只等数据有效就发响应,结果地址还没锁存,写到了错误的寄存器上。这种 bug 很难查,因为概率性出现。

3.4 寄存器地址映射与译码

好了,三个通道的逻辑都讲完了。现在要把它们整合起来,实现寄存器的实际读写。我们的按键消抖模块需要以下寄存器:

地址偏移 寄存器名称 功能描述 读写属性
0x00 KEY_DATA 按键值寄存器(只读) RO
0x04 KEY_CTRL 控制寄存器(使能、模式选择) RW
0x08 KEY_DEBOUNCE 消抖时间配置寄存器 RW
0x0C KEY_STATUS 状态寄存器(中断标志、忙标志) RW(部分位只读)

地址译码的核心逻辑:

// 寄存器写操作译码
wire write_en = aw_valid_d && w_valid_d && !bvalid;
wire [1:0] reg_index = aw_addr[3:2];  // 寄存器索引

always @(posedge aclk or negedge aresetn) begin
    if (!aresetn) begin
        key_ctrl_reg    <= 32'd0;
        key_debounce_reg <= 32'd100000;  // 默认10ms (100MHz时钟)
        key_status_reg  <= 32'd0;
    end else if (write_en && (bresp == 2'b00)) begin
        case (reg_index)
            2'b00: ;  // KEY_DATA 只读,不写
            2'b01: key_ctrl_reg <= w_data_masked;
            2'b10: key_debounce_reg <= w_data_masked;
            2'b11: key_status_reg <= w_data_masked;
        endcase
    end
end

读操作相对简单,直接从寄存器取值返回即可。这部分我们下一章再细讲。

我的建议:寄存器地址映射最好用宏定义或者参数化,不要硬编码。我见过一个项目,地址偏移量散落在代码各个角落,后来要加一个寄存器,改得人想哭。用 `localparam` 定义好,维护起来轻松很多。

3.5 整体结构图

下面这张图展示了写操作三个通道的交互关系:

AXI4-Lite 写操作通道结构图 AXI4 Master 写地址通道 AWADDR, AWVALID, AWREADY 写数据通道 WDATA, WSTRB, WVALID, WREADY 写响应通道 BRESP, BVALID, BREADY 从机控制逻辑 地址译码 握手状态机 数据锁存 响应生成 寄存器映射 寄存器阵列 KEY_DATA (0x00) KEY_CTRL (0x04) KEY_DEBOUNCE (0x08) KEY_STATUS (0x0C) 图例 写地址通道 写数据通道 写响应通道

从图上可以看得很清楚:三个通道是独立的,但最终都汇聚到从机控制逻辑。控制逻辑负责协调三个通道的时序,确保写操作按顺序完成。

好了,今天的内容就到这里。写地址、写数据、写响应这三个通道,说白了就是「问地址→给数据→等回执」的过程。你想想看,是不是跟你去银行办业务差不多?先填单子(地址),再交钱(数据),最后拿回执(响应)。

下一章我们接着讲读操作通道的实现。到时候你会发现,读通道比写通道简单多了——毕竟不用等回执嘛。


可可精品资料库