AXI总线实战:从零设计一个AXI-Lite Slave模块(附Verilog代码)

张开发
2026/4/21 13:53:20 15 分钟阅读

分享文章

AXI总线实战:从零设计一个AXI-Lite Slave模块(附Verilog代码)
AXI总线实战从零设计一个AXI-Lite Slave模块附Verilog代码在FPGA和ASIC设计中AXI总线已经成为片上通信的事实标准。对于刚接触AXI协议的工程师来说最有效的学习方式莫过于亲自动手实现一个功能模块。本文将带你从零开始设计一个AXI-Lite Slave模块通过完整的Verilog实现和仿真测试深入理解AXI-Lite协议的核心机制。1. AXI-Lite协议精要AXI-Lite是AXI协议的简化版本去掉了突发传输、乱序等复杂特性保留了最基本的读写操作机制。它特别适合寄存器配置等简单场景是初学者理解AXI协议的理想切入点。1.1 关键信号解析AXI-Lite接口分为5个独立通道写地址通道AW包含AWADDR地址、AWVALID主机有效、AWREADY从机就绪写数据通道W包含WDATA数据、WSTRB字节使能、WVALID、WREADY写响应通道B包含BRESP响应、BVALID、BREADY读地址通道AR包含ARADDR、ARVALID、ARREADY读数据通道R包含RDATA、RRESP、RVALID、RREADY握手机制是AXI的核心特征每个通道的传输都需要VALID和READY信号同时有效才能完成。这种双向握手机制使得通信双方可以自主控制传输节奏。1.2 典型传输时序写操作分为三个阶段地址阶段主机通过AW通道发送地址数据阶段主机通过W通道发送数据响应阶段从机通过B通道返回响应读操作分为两个阶段地址阶段主机通过AR通道发送地址数据阶段从机通过R通道返回数据// 典型AXI-Lite接口定义 module axi_lite_slave ( input ACLK, input ARESETn, // 写地址通道 input [31:0] AWADDR, input AWVALID, output AWREADY, // 写数据通道 input [31:0] WDATA, input [3:0] WSTRB, input WVALID, output WREADY, // 写响应通道 output [1:0] BRESP, output BVALID, input BREADY, // 读地址通道 input [31:0] ARADDR, input ARVALID, output ARREADY, // 读数据通道 output [31:0] RDATA, output [1:0] RRESP, output RVALID, input RREADY );2. AXI-Lite Slave架构设计我们的目标设计是一个具有4个32位寄存器的配置模块支持读写操作。整体架构包含三个主要部分2.1 寄存器文件采用简单的地址映射方式0x00: 控制寄存器CTRL0x04: 状态寄存器STATUS0x08: 数据寄存器0DATA00x0C: 数据寄存器1DATA1reg [31:0] reg_ctrl; reg [31:0] reg_status; reg [31:0] reg_data0; reg [31:0] reg_data1; // 寄存器写使能信号 wire wr_ctrl (AWADDR[7:0] 8h00) write_active; wire wr_status (AWADDR[7:0] 8h04) write_active; wire wr_data0 (AWADDR[7:0] 8h08) write_active; wire wr_data1 (AWADDR[7:0] 8h0C) write_active;2.2 状态机设计采用三段式状态机管理传输流程写操作状态机IDLE等待AWVALIDWRITE_ADDR锁存地址等待WVALIDWRITE_DATA处理数据写入WRITE_RESP发送响应读操作状态机IDLE等待ARVALIDREAD_ADDR锁存地址READ_DATA准备并发送数据2.3 响应生成逻辑AXI定义了四种响应类型00OKAY - 正常访问成功01EXOKAY - 独占访问成功10SLVERR - 从机错误11DECERR - 解码错误通常由互联组件产生对于我们的简单设计只需实现OKAY和DECERR响应// 地址解码错误判断 wire addr_decode_err (AWADDR[7:0] 8h0C) | (ARADDR[7:0] 8h0C); // 响应生成 assign BRESP addr_decode_err ? 2b11 : 2b00; assign RRESP addr_decode_err ? 2b11 : 2b00;3. Verilog完整实现以下是AXI-Lite Slave模块的完整代码实现module axi_lite_regs ( input ACLK, input ARESETn, // 写地址通道 input [31:0] AWADDR, input AWVALID, output AWREADY, // 写数据通道 input [31:0] WDATA, input [3:0] WSTRB, input WVALID, output WREADY, // 写响应通道 output [1:0] BRESP, output BVALID, input BREADY, // 读地址通道 input [31:0] ARADDR, input ARVALID, output ARREADY, // 读数据通道 output [31:0] RDATA, output [1:0] RRESP, output RVALID, input RREADY ); // 寄存器定义 reg [31:0] reg_ctrl; reg [31:0] reg_status; reg [31:0] reg_data0; reg [31:0] reg_data1; // 写操作控制信号 reg awready_reg; reg wready_reg; reg bvalid_reg; reg [31:0] awaddr_latched; // 读操作控制信号 reg arready_reg; reg rvalid_reg; reg [31:0] araddr_latched; // 状态定义 localparam IDLE 2b00; localparam WRITE_ADDR 2b01; localparam WRITE_DATA 2b10; localparam WRITE_RESP 2b11; localparam READ_ADDR 2b01; localparam READ_DATA 2b10; reg [1:0] write_state; reg [1:0] read_state; // 地址解码 wire wr_ctrl (awaddr_latched[7:0] 8h00); wire wr_status (awaddr_latched[7:0] 8h04); wire wr_data0 (awaddr_latched[7:0] 8h08); wire wr_data1 (awaddr_latched[7:0] 8h0C); wire rd_ctrl (araddr_latched[7:0] 8h00); wire rd_status (araddr_latched[7:0] 8h04); wire rd_data0 (araddr_latched[7:0] 8h08); wire rd_data1 (araddr_latched[7:0] 8h0C); // 地址错误检测 wire addr_decode_err (awaddr_latched[7:0] 8h0C) | (araddr_latched[7:0] 8h0C); // 写操作状态机 always (posedge ACLK or negedge ARESETn) begin if (!ARESETn) begin write_state IDLE; awready_reg 1b0; wready_reg 1b0; bvalid_reg 1b0; awaddr_latched 32b0; end else begin case (write_state) IDLE: begin if (AWVALID) begin awready_reg 1b1; awaddr_latched AWADDR; write_state WRITE_ADDR; end end WRITE_ADDR: begin awready_reg 1b0; if (WVALID) begin wready_reg 1b1; write_state WRITE_DATA; // 寄存器写入 if (wr_ctrl) reg_ctrl WDATA; if (wr_status) reg_status WDATA; if (wr_data0) reg_data0 WDATA; if (wr_data1) reg_data1 WDATA; end end WRITE_DATA: begin wready_reg 1b0; bvalid_reg 1b1; write_state WRITE_RESP; end WRITE_RESP: begin if (BREADY) begin bvalid_reg 1b0; write_state IDLE; end end endcase end end // 读操作状态机 always (posedge ACLK or negedge ARESETn) begin if (!ARESETn) begin read_state IDLE; arready_reg 1b0; rvalid_reg 1b0; araddr_latched 32b0; end else begin case (read_state) IDLE: begin if (ARVALID) begin arready_reg 1b1; araddr_latched ARADDR; read_state READ_ADDR; end end READ_ADDR: begin arready_reg 1b0; rvalid_reg 1b1; read_state READ_DATA; end READ_DATA: begin if (RREADY) begin rvalid_reg 1b0; read_state IDLE; end end endcase end end // 读数据选择 assign RDATA rd_ctrl ? reg_ctrl : rd_status ? reg_status : rd_data0 ? reg_data0 : rd_data1 ? reg_data1 : 32b0; // 响应信号 assign BRESP addr_decode_err ? 2b11 : 2b00; assign RRESP addr_decode_err ? 2b11 : 2b00; // 输出信号连接 assign AWREADY awready_reg; assign WREADY wready_reg; assign BVALID bvalid_reg; assign ARREADY arready_reg; assign RVALID rvalid_reg; endmodule4. 仿真测试与验证4.1 测试平台搭建我们使用SystemVerilog构建测试平台主要包含以下组件时钟和复位生成器AXI-Lite Master行为模型待测Slave实例结果检查器module tb_axi_lite_regs(); logic ACLK; logic ARESETn; // AXI-Lite接口信号 logic [31:0] AWADDR; logic AWVALID; logic AWREADY; logic [31:0] WDATA; logic [3:0] WSTRB; logic WVALID; logic WREADY; logic [1:0] BRESP; logic BVALID; logic BREADY; logic [31:0] ARADDR; logic ARVALID; logic ARREADY; logic [31:0] RDATA; logic [1:0] RRESP; logic RVALID; logic RREADY; // 时钟生成 initial begin ACLK 0; forever #5 ACLK ~ACLK; end // 复位生成 initial begin ARESETn 0; #20 ARESETn 1; end // 实例化DUT axi_lite_regs dut ( .ACLK(ACLK), .ARESETn(ARESETn), .AWADDR(AWADDR), .AWVALID(AWVALID), .AWREADY(AWREADY), .WDATA(WDATA), .WSTRB(WSTRB), .WVALID(WVALID), .WREADY(WREADY), .BRESP(BRESP), .BVALID(BVALID), .BREADY(BREADY), .ARADDR(ARADDR), .ARVALID(ARVALID), .ARREADY(ARREADY), .RDATA(RDATA), .RRESP(RRESP), .RVALID(RVALID), .RREADY(RREADY) ); // 测试用例 initial begin // 初始化信号 AWADDR 0; AWVALID 0; WDATA 0; WSTRB 4b1111; WVALID 0; BREADY 0; ARADDR 0; ARVALID 0; RREADY 0; // 等待复位完成 wait(ARESETn); (posedge ACLK); // 测试1写入CTRL寄存器 $display(Test 1: Write CTRL register); axi_write(32h00, 32hA5A5A5A5); // 测试2读取CTRL寄存器 $display(Test 2: Read CTRL register); axi_read(32h00); // 测试3写入无效地址 $display(Test 3: Write to invalid address); axi_write(32h20, 32h12345678); // 测试4读取无效地址 $display(Test 4: Read from invalid address); axi_read(32h20); // 测试5连续读写操作 $display(Test 5: Sequential read/write); axi_write(32h08, 32h11111111); axi_write(32h0C, 32h22222222); axi_read(32h08); axi_read(32h0C); $display(All tests completed); $finish; end // AXI写任务 task axi_write(input [31:0] addr, input [31:0] data); (posedge ACLK); AWADDR addr; AWVALID 1b1; WDATA data; WVALID 1b1; BREADY 1b1; wait(AWREADY WREADY); (posedge ACLK); AWVALID 1b0; WVALID 1b0; wait(BVALID); (posedge ACLK); BREADY 1b0; $display(Write: addr0x%h, data0x%h, resp%b, addr, data, BRESP); endtask // AXI读任务 task axi_read(input [31:0] addr); (posedge ACLK); ARADDR addr; ARVALID 1b1; RREADY 1b1; wait(ARREADY); (posedge ACLK); ARVALID 1b0; wait(RVALID); (posedge ACLK); RREADY 1b0; $display(Read: addr0x%h, data0x%h, resp%b, addr, RDATA, RRESP); endtask endmodule4.2 常见问题排查在AXI-Lite实现过程中开发者常会遇到以下典型问题问题1死锁情况现象仿真挂起无法继续执行原因VALID和READY信号互相等待解决方案确保Slave在IDLE状态时能接收新的传输请求避免在同一个时钟沿同时采样VALID和READY信号问题2地址解码错误现象访问无效地址时未返回DECERR响应检查点地址范围检查逻辑是否正确响应信号生成是否与地址解码关联问题3时序违规现象建立/保持时间不满足调试方法检查所有寄存器输出是否在时钟上升沿更新确保状态机转换条件明确无歧义5. 进阶优化方向完成基础实现后可以考虑以下优化方向提升模块的实用性和可靠性5.1 添加字节使能支持当前的实现假设所有写操作都是32位完整的实际应用中可能需要按字节写入// 增强的寄存器写入逻辑 always (posedge ACLK) begin if (wr_ctrl) begin if (WSTRB[0]) reg_ctrl[7:0] WDATA[7:0]; if (WSTRB[1]) reg_ctrl[15:8] WDATA[15:8]; if (WSTRB[2]) reg_ctrl[23:16] WDATA[23:16]; if (WSTRB[3]) reg_ctrl[31:24] WDATA[31:24]; end // 其他寄存器同理... end5.2 添加中断支持扩展设计使其能够基于寄存器值变化产生中断信号// 中断生成逻辑 reg interrupt_en; reg interrupt_status; // 中断输出 assign IRQ interrupt_en interrupt_status; // 在寄存器写入逻辑中添加中断控制 always (posedge ACLK) begin if (wr_ctrl) begin interrupt_en WDATA[0]; // bit0控制中断使能 end if (wr_data0 | wr_data1) begin interrupt_status 1b1; // 数据寄存器写入触发中断 end if (wr_status) begin interrupt_status WDATA[0] ? 1b0 : interrupt_status; // 写1清除中断 end end5.3 添加性能计数器为调试和分析添加传输统计功能// 性能计数器 reg [31:0] write_count; reg [31:0] read_count; reg [31:0] error_count; always (posedge ACLK or negedge ARESETn) begin if (!ARESETn) begin write_count 0; read_count 0; error_count 0; end else begin if (BVALID BREADY) begin write_count write_count 1; if (BRESP ! 2b00) error_count error_count 1; end if (RVALID RREADY) begin read_count read_count 1; if (RRESP ! 2b00) error_count error_count 1; end end end

更多文章