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
地址译码的逻辑其实很简单。假设我们的按键消抖模块占用 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
字节使能的处理逻辑:
// 字节使能过滤
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
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
读操作相对简单,直接从寄存器取值返回即可。这部分我们下一章再细讲。
3.5 整体结构图
下面这张图展示了写操作三个通道的交互关系:
从图上可以看得很清楚:三个通道是独立的,但最终都汇聚到从机控制逻辑。控制逻辑负责协调三个通道的时序,确保写操作按顺序完成。
好了,今天的内容就到这里。写地址、写数据、写响应这三个通道,说白了就是「问地址→给数据→等回执」的过程。你想想看,是不是跟你去银行办业务差不多?先填单子(地址),再交钱(数据),最后拿回执(响应)。
下一章我们接着讲读操作通道的实现。到时候你会发现,读通道比写通道简单多了——毕竟不用等回执嘛。