从零构建你的第一个AXI-Lite从机:协议、代码与仿真实战
很多刚开始接触FPGA片上系统设计的工程师,一看到AXI总线协议文档里那几十个信号和复杂的时序图,心里就有点发怵。我刚开始接触Zynq平台的时候也是这样,总觉得这东西太“高级”了,应该是那些资深架构师才需要掌握的内容。但后来在实际项目中,当我需要为自定义的硬件加速器添加一个寄存器配置接口时,才发现AXI-Lite其实是我们最常打交道、也最容易上手的一种总线协议。
如果你正在设计一个需要被处理器访问的FPGA模块——比如一个自定义的DMA控制器、一个图像处理流水线的配置寄存器组,或者任何需要软件进行参数控制的硬件逻辑——那么掌握AXI-Lite从机接口的实现几乎是必经之路。与完整的AXI4协议相比,AXI-Lite做了大量简化:它不支持突发传输,每次只能读写一个数据;数据位宽固定为32位或64位;没有缓存和独占访问这些复杂机制。正是这些简化,使得我们可以用相对较少的逻辑资源,实现一个稳定可靠的处理器外设接口。
这篇文章不会只是给你一段“可以用的”模板代码然后让你照搬。我会带你从协议的核心握手机制开始理解,一步步推导出每个always块应该怎么写,最后用ModelSim仿真验证整个设计。当你跟着走完这个过程,你会真正理解为什么代码要这样写,而不仅仅是记住了一段可以复用的Verilog。
1. 理解AXI-Lite:握手、通道与依赖关系
在开始写任何代码之前,我们需要先搞清楚AXI-Lite协议到底规定了什么。很多人一上来就研究信号列表,记了一堆AWADDR、WDATA、BRESP之类的名字,但真正关键的其实是这些信号之间的握手协议和依赖关系。
1.1 核心握手机制:VALID与READY的舞蹈
AXI-Lite的每个通道都使用一对VALID/READY信号进行握手。这种设计非常巧妙:VALID由数据发送方控制,表示“我这里有有效数据”;READY由数据接收方控制,表示“我现在可以接收数据”。只有当VALID和READY同时为高的时钟上升沿,数据传输才真正完成。
// 这是一个概念性的握手示例,不是实际代码
// 在时钟上升沿检查:
if (VALID && READY) begin
// 数据在这一刻被捕获
data_latched <= data_to_transfer;
end
这种双向握手机制让通信双方都能控制传输速率。如果接收方还没准备好(READY=0),发送方可以保持VALID=1等待;如果发送方还没数据(VALID=0),接收方也可以提前拉高READY表示“我准备好了,你随时可以发”。
注意:在实际的AXI-Lite从机设计中,我们通常会让READY信号的生成逻辑稍微复杂一些,因为它需要根据协议规定的依赖关系来决定何时可以拉高。
1.2 五个通道的分工
AXI-Lite将一次完整的传输分解到五个独立的通道中,这种分离设计是它高性能的基础:
| 通道名称 | 方向(从机视角) | 承载信息 | 关键信号 |
|---|---|---|---|
| 写地址通道 | 输入 | 要写入的寄存器地址 | AWADDR, AWVALID, AWREADY |
| 写数据通道 | 输入 | 要写入的数据 | WDATA, WSTRB, WVALID, WREADY |
| 写响应通道 | 输出 | 写操作是否成功 | BRESP, BVALID, BREADY |
| 读地址通道 | 输入 | 要读取的寄存器地址 | ARADDR, ARVALID, ARREADY |
| 读数据通道 | 输出 | 读取到的数据 | RDATA, RVALID, RREADY |
这里有个容易混淆的点:写操作需要三个通道协同工作(地址、数据、响应),而读操作只需要两个通道(地址、数据),因为读响应信息被合并到了读数据通道的RRESP信号中。
1.3 关键的依赖关系:谁必须等谁
协议文档中最让人头疼的部分可能就是那些依赖关系图了。但如果你理解了背后的逻辑,其实很简单:协议要防止死锁,确保传输能顺利进行。
对于写操作,协议规定:
- 主机不能等待从机的READY信号——主机想写数据时,必须主动拉高VALID,不能“等从机准备好”
- 从机可以等待主机的VALID信号——从机可以等看到VALID再拉高READY,也可以提前拉高READY
- 写响应(BVALID)必须在地址和数据都传输完成后才能拉高
用更直白的话说:写的时候,主机要“主动伸手”,从机可以“等着被握”,也可以“主动伸手”。但响应的时候,角色互换,从机必须“主动伸手”告诉主机“我写完了”。
读操作的依赖关系类似,但更简单一些:
- 主机不能等待从机的ARREADY——读地址通道上,主机要先伸手
- 从机必须等收到读地址后,才能拉高RVALID——不能还没收到地址就说“数据准备好了”
- 从机不能等待主机的RREADY——数据准备好后,从机要主动伸手
这些依赖关系会直接体现在我们的状态机设计里。比如,如果我们违反了“从机不能等待主机的RREADY”这条规则,就可能在某些情况下造成死锁。
2. 设计思路:从协议到代码的转换
现在我们已经理解了协议的基本规则,接下来要把这些规则转换成具体的Verilog设计。我会按照实际开发的思考过程来讲解,而不是直接抛给你一段完整的代码。
2.1 写操作的关键问题
当你开始设计写通道时,需要回答三个核心问题:
第一,什么时候可以捕获写地址?
根据协议,写地址通道的握手可以独立发生。但一个更实用的设计是:只有当写地址和写数据都有效时,才认为这是一次有效的写请求。这样设计可以简化状态管理,因为AXI-Lite不支持突发传输,每次写操作都对应一个地址和一个数据。
// 判断是否发生有效的写传输
wire write_transfer_happening = (s_axi_awvalid && axi_awready &&
s_axi_wvalid && axi_wready);
第二,如何处理WSTRB信号?
WSTRB(Write Strobe)信号指示了WDATA中哪些字节是有效的。对于32位数据宽度,WSTRB是4位,每位对应一个字节:
| WSTRB位 | 对应的数据字节 |
|---|---|
| [0] | WDATA[7:0] |
| [1] | WDATA[15:8] |
| [2] | WDATA[23:16] |
| [3] | WDATA[31:24] |
如果WSTRB[n]=1,表示WDATA对应的字节应该被写入寄存器;如果为0,则保持寄存器中原有的值不变。这种设计允许主机

&spm=1001.2101.3001.5002&articleId=154228587&d=1&t=3&u=feb33f17ce804443a4b7fa4301e0c2a2)

被折叠的 条评论
为什么被折叠?



