【电路笔记 通信】AXI4-Lite协议 FPGA实现 Valid-Ready Handshake 握手协议
AXI4-Lite Top 模块
-
本文的分析基于项目:https://github.com/arhamhashmi01/Axi4-lite/tree/main/Axi4-lite-vivado
-
寄存器转换级略图 RTL SCHEMATIC:
-
重新涂色与标注的效果:
-
注:常见一种做法是 Master 直接常拉高 BREADY,参考代码未使用,这样只要 Slave 提供写响应,握手就能立即完成,不会卡住,这样可以大幅简化逻辑:
- Master 的 RREADY、BREADY → 可以常高(随时能接收)。
- Slave 的 AWREADY、WREADY、ARREADY → 也可以常高(随时能接收)。
-
注:上图Slave侧只标注了 Read Channel (Slave -> Master)
`timescale 1ns / 1ps
module axi4_lite_top#(// 参数定义parameter DATA_WIDTH = 32, // 数据宽度parameter ADDRESS = 32 // 地址宽度)(input ACLK, // AXI 时钟信号input ARESETN, // AXI 复位信号(低有效)input read_s, // 顶层控制信号:触发读事务input write_s, // 顶层控制信号:触发写事务input [ADDRESS-1:0] address, // 顶层输入地址input [DATA_WIDTH-1:0] W_data // 顶层输入写数据);// AXI Master-Slave 之间交互的信号线定义logic M_ARREADY, S_RVALID, M_ARVALID, M_RREADY;logic S_AWREADY, S_BVALID, M_AWVALID, M_BREADY;logic M_WVALID, S_WREADY;logic [ADDRESS-1 : 0] M_ARADDR; // Master -> Slave 读地址logic [ADDRESS-1 : 0] M_AWADDR; // Master -> Slave 写地址logic [DATA_WIDTH-1:0] M_WDATA; // Master -> Slave 写数据logic [DATA_WIDTH-1:0] S_RDATA; // Slave -> Master 读数据logic [3:0] M_WSTRB; // 写数据字节使能(32bit=4字节,所以 4bit),写数据掩码logic [1:0] S_RRESP; // Slave -> Master 读响应logic [1:0] S_BRESP; // Slave -> Master 写响应// 实例化 AXI4-Lite Masteraxi4_lite_master u_axi4_lite_master0(.ACLK (ACLK),.ARESETN (ARESETN),// 控制信号.START_READ (read_s), // 触发读.START_WRITE(write_s), // 触发写.address (address), // 读写地址.W_data (W_data), // 写入数据// Read Channel (Master -> Slave).M_ARADDR (M_ARADDR), // 读地址.M_ARVALID (M_ARVALID), // 地址有效.M_ARREADY (M_ARREADY), // 从机就绪.M_RDATA (S_RDATA), // 从机返回的数据.M_RRESP (S_RRESP), // 读响应(OKAY/SLVERR).M_RVALID (S_RVALID), // 从机返回数据有效.M_RREADY (M_RREADY), // 主机准备好接收数据// Write Channel (Master -> Slave).M_AWADDR (M_AWADDR), // 写地址.M_AWVALID (M_AWVALID), // 地址有效.M_AWREADY (S_AWREADY), // 从机就绪.M_WDATA (M_WDATA), // 写数据.M_WSTRB (M_WSTRB), // 写数据掩码.M_WVALID (M_WVALID), // 写数据有效.M_WREADY (S_WREADY), // 从机准备好接收数据.M_BRESP (S_BRESP), // 从机写响应.M_BVALID (S_BVALID), // 响应有效.M_BREADY (M_BREADY) // 主机准备好接收响应);// 实例化 AXI4-Lite Slaveaxi4_lite_slave u_axi4_lite_slave0(.ACLK (ACLK),.ARESETN (ARESETN),// Read Channel (Slave -> Master).S_ARADDR (M_ARADDR), // 主机发来的读地址.S_ARVALID (M_ARVALID), // 地址有效.S_ARREADY (M_ARREADY), // 从机就绪.S_RDATA (S_RDATA), // 返回的数据.S_RRESP (S_RRESP), // 读响应.S_RVALID (S_RVALID), // 数据有效.S_RREADY (M_RREADY), // 主机就绪// Write Channel (Slave -> Master).S_AWADDR (M_AWADDR), // 主机发来的写地址.S_AWVALID (M_AWVALID), // 地址有效.S_AWREADY (S_AWREADY), // 从机就绪.S_WDATA (M_WDATA), // 写数据.S_WSTRB (M_WSTRB), // 写数据掩码.S_WVALID (M_WVALID), // 数据有效.S_WREADY (S_WREADY), // 从机就绪.S_BRESP (S_BRESP), // 写响应.S_BVALID (S_BVALID), // 响应有效.S_BREADY (M_BREADY) // 主机准备接收响应);
endmodule
axi4_lite_top描述
这个 axi4_lite_top
模块就是一个 AXI4-Lite 主从对接的顶层封装:
axi4_lite_master
接收顶层输入的read_s
/write_s
信号,发起 AXI 读写事务;axi4_lite_slave
响应 Master 的请求,返回读写数据与响应;- 两者通过 AXI4-Lite 信号互联,形成一个完整的读写通路。
激励文件
`timescale 1ns / 1ps
module axi4_lite_top_tb();logic ACLK_tb;logic ARESETN_tb;//logic read_s_tb; // 注释掉读信号logic write_s_tb;logic [31:0] address_tb;logic [31:0] W_data_tb;axi4_lite_top u_axi4_lite_top0(.ACLK(ACLK_tb),.ARESETN(ARESETN_tb),//.read_s(read_s_tb), // 注释掉读接口.write_s(write_s_tb),.address(address_tb), // 顶层输入地址.W_data(W_data_tb) // 顶层输入写数据);initial begin#5;ACLK_tb=0;ARESETN_tb=0;//read_s_tb=0; // 注释掉读操作write_s_tb=0; #5;ACLK_tb=1;ARESETN_tb=1;write_s_tb=0;#15;// -------- 写测试开始 --------write_s_tb=1;address_tb = 5;W_data_tb = 4;#10;write_s_tb=0;#20;// -------- 写测试结束 --------// -------- 以下是读测试,注释掉 --------//write_s_tb=0;//read_s_tb=0;//#30;//read_s_tb=1;//address_tb = 5;//#10;//read_s_tb=0;// -----------------------------------#40;$finish; endalways begin#5 ACLK_tb = ~ACLK_tb;end
endmodule
AXI4-Lite 主设备(Master)
时序分析
- 仿真的写时序:
VALID & READY 握手基本规则
-
VALID:由 发送方 发出,表示“数据/地址/响应已经准备好,可以被接收”。
-
READY:由 接收方 发出,表示“我已经准备好接收”。
-
握手成功条件: 当 VALID = 1 且 READY = 1 时,数据才算真正传输成功。
-
握手特点:
- VALID 不能依赖 READY 才拉高(发送方必须主动把数据放出来)。
- READY 可以提前为高,也可以等到看到 VALID 才拉高。
- 只有 VALID 和 READY 同时为高的那个时钟周期,传输的数据才被“采纳”。例如:
assign write_addr = M_AWVALID && M_AWREADY; // 地址握手完成条件
// 写地址和写数据握手都完成 → 等待写响应
WRITE_CHANNEL : if (write_addr && write_data)
next_state = WRESP__CHANNEL;
握手条件是 M_ARVALID && M_ARREADY,FSM 中体现为:
RADDR_CHANNEL : if (M_ARVALID && M_ARREADY)
next_state = RDATA__CHANNEL; // 读地址握手成功,进入等待读数据状态。
- 官方写时序 :https://docs.amd.com/r/en-US/pg202-mipi-dphy/AXI4-Lite-Interface
状态机
- AXI4-Lite Master 状态机,能发起 一次读事务 或 一次写事务,并根据握手机制自动切换状态。状态机流程:
- IDLE → 等待外部触发
- WRITE_CHANNEL → 发送地址和数据
- WRESP__CHANNEL → 等待写响应
- RADDR_CHANNEL → 发送读地址
- RDATA__CHANNEL → 等待读数据
代码实现
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 05/04/2024 05:52:55 PM
// Design Name:
// Module Name: axi4_lite_master
// Project Name:
// Target Devices:
// Tool Versions:
// Description: AXI4-Lite Master 实现,包含读写通道握手逻辑和有限状态机
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////module axi4_lite_master #(parameter ADDRESS = 32, // 地址总线宽度parameter DATA_WIDTH = 32 // 数据总线宽度)(// ================= 全局信号 ==================input ACLK, // AXI 时钟input ARESETN, // AXI 低电平复位 (active low)input START_READ, // 触发一次读事务input START_WRITE, // 触发一次写事务input [ADDRESS-1 : 0] address, // 读/写地址,来自TOPinput [DATA_WIDTH-1:0] W_data, // 要写入的数据,来自TOP// =============== AXI4-Lite 输入信号(从 Slave 来) ===============// Read Address Channelinput M_ARREADY, // 从机准备好接收读地址// Read Data Channelinput [DATA_WIDTH-1:0] M_RDATA, // 从机返回的数据input [1:0] M_RRESP, // 从机返回的响应input M_RVALID, // 从机提供的数据有效// Write Address Channelinput M_AWREADY, // 从机准备好接收写地址// Write Data Channelinput M_WREADY, // 从机准备好接收写数据// Write Response Channelinput [1:0] M_BRESP, // 从机返回的写响应input M_BVALID, // 从机写响应有效// =============== AXI4-Lite 输出信号(Master 发出) ===============// Read Address Channeloutput logic [ADDRESS-1 : 0] M_ARADDR, // 读地址output logic M_ARVALID, // 读地址有效// Read Data Channeloutput logic M_RREADY, // Master 准备好接收数据// Write Address Channeloutput logic [ADDRESS-1 : 0] M_AWADDR, // 写地址output logic M_AWVALID, // 写地址有效// Write Data Channeloutput logic [DATA_WIDTH-1:0] M_WDATA, // 写数据output logic [3:0] M_WSTRB, // 写掩码(字节选通)output logic M_WVALID, // 写数据有效// Write Response Channeloutput logic M_BREADY // Master 准备好接收写响应);// ============== 内部寄存器与信号 ==============logic read_start; // 记录一次读事务开始logic write_addr; // 写地址握手完成标志logic write_data; // 写数据握手完成标志logic write_start; // 记录一次写事务开始// 状态机定义:AXI4-Lite 事务状态typedef enum logic [2 : 0] {IDLE, // 空闲状态WRITE_CHANNEL, // 发送写地址和写数据WRESP__CHANNEL, // 等待写响应RADDR_CHANNEL, // 发送读地址RDATA__CHANNEL // 等待读数据返回} state_type;state_type state , next_state;// ============== AXI4-Lite 接口输出逻辑 ==============// --- Read Address Channel ---assign M_ARADDR = (state == RADDR_CHANNEL) ? address : 32'h0; // 只有在发读地址时有效assign M_ARVALID = (state == RADDR_CHANNEL) ? 1 : 0; // 发出读地址握手信号// --- Read Data Channel ---assign M_RREADY = (state == RDATA__CHANNEL || state == RADDR_CHANNEL) ? 1 : 0; // 进入读阶段时,准备接收数据// --- Write Address Channel ---assign M_AWVALID = (state == WRITE_CHANNEL) ? 1 : 0; // 发出写地址握手信号assign M_AWADDR = (state == WRITE_CHANNEL) ? address : 32'h0; // 写地址assign write_addr = M_AWVALID && M_AWREADY; // 地址握手完成条件assign write_data = M_WVALID && M_WREADY; // 数据握手完成条件// --- Write Data Channel ---assign M_WVALID = (state == WRITE_CHANNEL) ? 1 : 0; // 写数据有效assign M_WDATA = (state == WRITE_CHANNEL) ? W_data : 32'h0; // 写数据assign M_WSTRB = (state == WRITE_CHANNEL) ? 4'b1111 : 0; // 默认写全字节// --- Write Response Channel ---assign M_BREADY = ((state == WRITE_CHANNEL)||(state == WRESP__CHANNEL)) ? 1 : 0; // Master 随时准备接收写响应// ============== 状态机寄存器(时序逻辑) ==============always_ff @(posedge ACLK) beginif (~ARESETN) beginstate <= IDLE; // 复位回到空闲态end else beginstate <= next_state; // 状态跳转endend// 捕捉输入控制信号,打一拍always_ff @(posedge ACLK) beginif (~ARESETN) beginread_start <= 0;write_start <= 0;end else beginread_start <= START_READ; // 外部触发读write_start <= START_WRITE; // 外部触发写endend// ============== 状态机组合逻辑 ==============always_comb begincase (state)// 空闲态:等待开始信号IDLE : beginif (write_start) beginnext_state = WRITE_CHANNEL; // 进入写地址/数据阶段end else if (read_start) beginnext_state = RADDR_CHANNEL; // 进入读地址阶段end else beginnext_state = IDLE;endend// 发读地址 → 等待读数据RADDR_CHANNEL : if (M_ARVALID && M_ARREADY) next_state = RDATA__CHANNEL;// 读数据握手成功 → 回到空闲RDATA__CHANNEL : if (M_RVALID && M_RREADY) next_state = IDLE;// 写地址和写数据握手都完成 → 等待写响应WRITE_CHANNEL : if (write_addr && write_data) next_state = WRESP__CHANNEL;// 写响应握手成功 → 回到空闲WRESP__CHANNEL : if (M_BVALID && M_BREADY) next_state = IDLE;// 默认兜底default : next_state = IDLE;endcaseend
endmodule
AXI4-Lite 从设备(Slave)
- IDLE → AW (写地址握手) → W (写数据握手) → B (写应答) → IDLE
- IDLE → AR (读地址握手) → R (读数据握手) → IDLE
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 05/04/2024 05:52:55 PM
// Design Name:
// Module Name: axi4_lite_slave
// Project Name:
// Target Devices:
// Tool Versions:
// Description: AXI4-Lite 从设备,内部包含一个寄存器阵列,用于存储和返回数据。
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
模块定义
module axi4_lite_slave #(parameter ADDRESS = 32, // 地址宽度(通常为 32 bit)parameter DATA_WIDTH = 32 // 数据总线宽度(通常为 32 bit))(// 全局信号input ACLK, // 时钟信号input ARESETN, // 复位信号,低有效//// Read Address Channel (读地址通道) 输入input [ADDRESS-1:0] S_ARADDR, // 读地址input S_ARVALID, // 读地址有效// Read Data Channel (读数据通道) 输入input S_RREADY, // 主设备准备好接收数据// Write Address Channel (写地址通道) 输入input [ADDRESS-1:0] S_AWADDR, // 写地址input S_AWVALID, // 写地址有效// Write Data Channel (写数据通道) 输入input [DATA_WIDTH-1:0] S_WDATA, // 写数据input [3:0] S_WSTRB, // 写字节使能(通常忽略,写全字)input S_WVALID, // 写数据有效// Write Response Channel (写响应通道) 输入input S_BREADY, // 主设备准备好接收写响应 // Read Address Channel 输出output logic S_ARREADY, // 从设备准备好接收读地址// Read Data Channel 输出output logic [DATA_WIDTH-1:0]S_RDATA, // 从设备返回的数据output logic [1:0] S_RRESP, // 读响应(OKAY=00)output logic S_RVALID, // 读数据有效// Write Address Channel 输出output logic S_AWREADY, // 从设备准备好接收写地址output logic S_WREADY, // 从设备准备好接收写数据// Write Response Channel 输出output logic [1:0] S_BRESP, // 写响应(OKAY=00)output logic S_BVALID // 写响应有效);
内部变量和寄存器定义
localparam no_of_registers = 32; // 定义 32 个寄存器(寄存器阵列)logic [DATA_WIDTH-1 : 0] register [no_of_registers-1 : 0]; // 从设备内部存储器logic [ADDRESS-1 : 0] addr; // 保存当前访问地址logic write_addr; // 写地址握手成功标志logic write_data; // 写数据握手成功标志
FSM(有限状态机)状态定义
typedef enum logic [2 : 0] {IDLE, // 空闲WRITE_CHANNEL, // 等待写地址 + 写数据WRESP__CHANNEL, // 写响应RADDR_CHANNEL, // 接收读地址RDATA__CHANNEL // 返回读数据} state_type;state_type state , next_state;
信号分配
// 读地址握手assign S_ARREADY = (state == RADDR_CHANNEL) ? 1 : 0;// 读数据返回assign S_RVALID = (state == RDATA__CHANNEL) ? 1 : 0;assign S_RDATA = (state == RDATA__CHANNEL) ? register[addr] : 0; // 从寄存器阵列读数据assign S_RRESP = (state == RDATA__CHANNEL) ? 2'b00 : 0; // OKAY// 写地址握手assign S_AWREADY = (state == WRITE_CHANNEL) ? 1 : 0;// 写数据握手assign S_WREADY = (state == WRITE_CHANNEL) ? 1 : 0;assign write_addr = S_AWVALID && S_AWREADY; // 地址握手成功assign write_data = S_WREADY && S_WVALID; // 数据握手成功// 写响应assign S_BVALID = (state == WRESP__CHANNEL) ? 1 : 0;assign S_BRESP = (state == WRESP__CHANNEL) ? 2'b00 : 0; // OKAY
寄存器读写逻辑
integer i;always_ff @(posedge ACLK) begin// 异步复位:清零寄存器if (~ARESETN) beginfor (i = 0; i < 32; i++) beginregister[i] <= 32'b0;endendelse begin// 写操作if (state == WRITE_CHANNEL) beginregister[S_AWADDR] <= S_WDATA; // 将写数据存入地址对应寄存器end// 读操作else if (state == RADDR_CHANNEL) beginaddr <= S_ARADDR; // 保存读地址endendend
状态机转换
// 状态寄存器always_ff @(posedge ACLK) beginif (!ARESETN) beginstate <= IDLE;endelse beginstate <= next_state;endend// 下一个状态逻辑always_comb begincase (state)IDLE : beginif (S_AWVALID) beginnext_state = WRITE_CHANNEL; // 有写请求end else if (S_ARVALID) beginnext_state = RADDR_CHANNEL; // 有读请求end else beginnext_state = IDLE;endendRADDR_CHANNEL : if (S_ARVALID && S_ARREADY ) next_state = RDATA__CHANNEL;RDATA__CHANNEL : if (S_RVALID && S_RREADY ) next_state = IDLE;WRITE_CHANNEL : if (write_addr && write_data) next_state = WRESP__CHANNEL;WRESP__CHANNEL : if (S_BVALID && S_BREADY ) next_state = IDLE;default : next_state = IDLE;endcaseend
endmodule