AXI_BRAM读写仿真:从单次传输到突发模式的实战解析

张开发
2026/4/17 14:50:20 15 分钟阅读

分享文章

AXI_BRAM读写仿真:从单次传输到突发模式的实战解析
1. AXI_BRAM读写仿真环境搭建在开始AXI_BRAM读写仿真之前我们需要先搭建好开发环境。我使用的是Vivado 2017.4开发环境和Kintex-7 xc7k325tffg900-2 FPGA芯片。这个组合在实际项目中很常见稳定性也经过验证。首先打开Vivado创建一个新工程。在IP Catalog中找到Block Memory Generator这是我们要用的核心IP。双击打开配置界面这里有几个关键参数需要注意接口类型选择AXI4内存类型选择Simple Dual Port RAM数据位宽设置为32位数据深度设置为1024配置完成后Vivado会自动生成一个AXI接口的BRAM控制器。这个IP会帮我们处理复杂的AXI协议细节让我们可以更专注于业务逻辑的实现。生成的IP会有一大堆接口信号初次接触可能会觉得眼花缭乱。别担心我们可以把这些信号分成几组来理解全局信号时钟(s_aclk)和复位(s_aresetn)写地址通道包括地址、突发长度、突发类型等写数据通道实际要写入的数据和写使能信号写响应通道写入完成后的状态反馈读地址通道类似写地址通道用于读取操作读数据通道返回的读取数据及其状态2. AXI总线握手机制详解AXI总线最重要的特性就是它的握手机制这也是很多新手容易出错的地方。我在刚开始学习时就因为没理解清楚握手规则导致仿真时总线一直处于挂起状态。AXI总线的五个通道写地址、写数据、写响应、读地址、读数据都使用相同的VALID/READY握手机制。这个机制的工作原理其实很简单发送方Master准备好数据后会拉高VALID信号接收方Slave准备好接收数据后会拉高READY信号只有当VALID和READY同时为高时数据传输才会真正发生这里有几个容易踩坑的地方需要特别注意VALID信号一旦拉高就不能降低直到握手完成READY信号可以在VALID为高之前就拉高发送方不能等待接收方的READY信号才拉高VALID如果双方都等待对方先动作就会导致总线死锁在实际代码中我通常会这样实现写地址通道的握手// 写地址通道 always (posedge s_aclk or negedge s_aresetn) begin if (!s_aresetn) begin s_axi_awvalid 1b0; end else if (state WRITE_ADDR !s_axi_awvalid) begin s_axi_awvalid 1b1; // 主动拉高VALID end else if (s_axi_awvalid s_axi_awready) begin s_axi_awvalid 1b0; // 握手完成后拉低 end end3. 单次读写模式实现我们先从最简单的单次读写开始这是理解AXI协议的基础。单次读写指的是每次只传输一个数据突发长度为1。3.1 单次写操作单次写操作需要经过三个步骤在写地址通道设置地址和相关参数在写数据通道提供要写入的数据等待写响应通道返回操作状态具体参数设置如下// 写地址设置 s_axi_awaddr 32h0000_0000; // 写入地址0 s_axi_awlen 8b0000_0000; // 突发长度1(实际值为0) s_axi_awsize 3b010; // 每次传输4字节(32位) s_axi_awburst 2b00; // 固定地址模式 s_axi_awvalid 1b1; // 地址有效 // 写数据设置 s_axi_wdata 32h1234_5678; // 要写入的数据 s_axi_wstrb 4b1111; // 所有字节有效 s_axi_wlast 1b1; // 因为是单次传输所以直接置1 s_axi_wvalid 1b1; // 数据有效这里有个细节需要注意AXI协议规定突发长度(awlen/arlen)的实际值是传输次数减1。所以单次传输时这个值应该设为0。3.2 单次读操作单次读操作相对简单只需要设置读地址通道参数然后等待读数据通道返回数据// 读地址设置 s_axi_araddr 32h0000_0000; // 读取地址0 s_axi_arlen 8b0000_0000; // 突发长度1 s_axi_arsize 3b010; // 每次传输4字节 s_axi_arburst 2b00; // 固定地址模式 s_axi_arvalid 1b1; // 地址有效 // 准备接收数据 s_axi_rready 1b1; // 准备好接收数据在仿真时我发现一个常见问题读出的数据和写入的不一致。经过排查发现是地址对齐的问题。AXI_BRAM的地址是按字节编址的而我们的数据位宽是32位(4字节)所以实际地址应该是4字节对齐的。也就是说地址0对应0-3字节地址4对应4-7字节以此类推。4. 突发读写模式进阶掌握了单次读写后我们可以尝试更高效的突发读写模式。突发传输可以在一次事务中连续传输多个数据大大提高了总线利用率。4.1 突发写实现突发写需要设置以下几个关键参数awlen突发长度实际传输次数awlen1awsize每次传输的数据大小awburst突发类型常用的是增量模式INCR下面是一个突发写16个数据的示例// 写地址设置 s_axi_awaddr 32h0000_0000; // 起始地址 s_axi_awlen 8b0000_1111; // 突发16个数据(151) s_axi_awsize 3b010; // 每次4字节 s_axi_awburst 2b01; // 增量模式 s_axi_awvalid 1b1; // 写数据控制 always (posedge s_aclk) begin if (write_active) begin if (s_axi_wready) begin wdata_counter wdata_counter 1; s_axi_wdata wdata_counter; // 写入递增值 s_axi_wlast (wdata_counter 15); // 第16个数据时拉高 end end end这里特别要注意wlast信号的控制它必须在最后一个数据时拉高。我在第一次实现时就漏掉了这个信号导致写操作无法正常结束。4.2 突发读实现突发读的实现与突发写类似// 读地址设置 s_axi_araddr 32h0000_0000; // 起始地址 s_axi_arlen 8b0000_1111; // 突发16个数据 s_axi_arsize 3b010; // 每次4字节 s_axi_arburst 2b01; // 增量模式 s_axi_arvalid 1b1; // 接收数据 always (posedge s_aclk) begin if (s_axi_rvalid s_axi_rready) begin read_data[rcount] s_axi_rdata; // 存储读取的数据 rcount rcount 1; if (s_axi_rlast) begin // 最后一个数据处理 read_done 1b1; end end end在突发读过程中AXI从设备会通过rlast信号指示最后一个数据。我们需要监控这个信号来知道突发传输何时结束。5. 常见问题与调试技巧在实际开发中我遇到过各种各样的问题。这里分享几个典型的案例和解决方法。5.1 地址计算错误最初实现突发读写时我发现写入和读出的数据对不上。通过仿真波形分析发现是地址计算有问题。在增量突发模式下地址应该根据传输大小自动递增。例如对于32位数据(awsize3b010)每次地址应该增加4。正确的地址计算应该是// 地址自动递增 always (posedge s_aclk) begin if (s_axi_awvalid s_axi_awready) begin next_addr next_addr (1 s_axi_awsize); end end5.2 握手信号时序问题另一个常见问题是握手信号的时序控制不当。特别是在状态机设计中容易过早地改变VALID信号。正确的做法是保持VALID信号直到握手完成// 正确的VALID控制 always (posedge s_aclk) begin case (state) WRITE_ADDR: if (!s_axi_awvalid) begin s_axi_awvalid 1b1; end else if (s_axi_awready) begin s_axi_awvalid 1b0; state WRITE_DATA; end // 其他状态... endcase end5.3 仿真波形分析技巧当遇到问题时仿真波形是最直接的调试工具。我通常会重点关注以下几个信号所有VALID/READY握手信号检查是否有死锁地址和数据信号确认传输内容是否正确wlast/rlast信号突发传输是否正常结束状态机状态确认流程是否正确跳转在Vivado仿真器中可以设置触发条件来捕获特定事件比如当发生错误响应时暂停仿真。这个功能在调试复杂问题时非常有用。6. 状态机设计与优化一个健壮的AXI控制器通常需要状态机来管理传输流程。根据我的经验基本的状态应该包括IDLE空闲状态等待命令WRITE_ADDR写地址阶段WRITE_DATA写数据阶段READ_ADDR读地址阶段READ_DATA读数据阶段RESPONSE等待响应状态状态机设计的关键点每个状态要有明确的进入和退出条件考虑所有可能的异常情况状态转换要符合AXI协议的时序要求这里分享一个我优化过的状态机片段always (posedge s_aclk or negedge s_aresetn) begin if (!s_aresetn) begin state IDLE; end else begin case (state) IDLE: if (write_req) state WRITE_ADDR; else if (read_req) state READ_ADDR; WRITE_ADDR: if (s_axi_awvalid s_axi_awready) state WRITE_DATA; WRITE_DATA: if (s_axi_wvalid s_axi_wready s_axi_wlast) state RESPONSE; // 其他状态... endcase end end在实际项目中我还添加了超时机制防止因为异常情况导致状态机卡死。这个改进解决了不少现场问题。7. 性能优化建议经过多次项目实践我总结出几个提升AXI_BRAM读写性能的技巧流水线操作可以在前一个传输还没完成时就启动下一个传输合理设置突发长度根据应用场景选择最佳的突发长度太长会导致延迟太短会影响吞吐量并行通道利用AXI支持读写通道并行操作可以同时进行读和写数据对齐确保数据地址按照总线宽度对齐可以避免不必要的拆分操作例如下面是一个简单的流水线读实现// 流水线读 always (posedge s_aclk) begin if (s_axi_arready !s_axi_rvalid) begin // 当前读操作还未返回数据时就可以发起下一个读 s_axi_arvalid 1b1; s_axi_araddr next_read_addr; end end这些优化技巧在我的一个图像处理项目中将AXI总线的吞吐量提升了近3倍。当然具体优化效果取决于实际应用场景。

更多文章