4. AXI总线协议与数据搬运:AXI4-Stream与AXI4-Full的区别,如何设计高效的DMA控制器
大家好,我是你们的老朋友。今天我们来聊聊AI芯片部署里最绕不开的一个话题——AXI总线协议。说白了,就是数据怎么在芯片内部各个模块之间“跑”起来。
很多刚入行的朋友,一看到AXI4-Stream和AXI4-Full就头大。我当年第一次接触时也懵,心想不就是个总线吗,怎么还分这么多版本?后来踩了几个坑才明白,选错协议,性能直接腰斩。
4.1 AXI4-Stream vs AXI4-Full:核心差异
先给个直观的对比。你可以把AXI4-Full想象成“带地址的快递专车”,每趟运输都要告诉司机送到哪个门牌号。而AXI4-Stream就是“流水线上的传送带”,只管把数据往前推,不问送到哪。
| 对比维度 | AXI4-Full | AXI4-Stream |
|---|---|---|
| 地址通道 | 有独立的读写地址通道 | 无地址通道,纯数据流 |
| 数据通道 | 读/写数据通道分离 | 单一数据通道,带握手 |
| 握手协议 | VALID/READY + 地址握手 | VALID/READY 简单握手 |
| 典型延迟 | 较高(地址解析+仲裁) | 极低(流水线直通) |
| 适用场景 | DDR读写、寄存器配置 | 卷积计算流、特征图搬运 |
| 数据包格式 | 固定地址+固定长度 | 支持TLAST包尾标记 |
我个人习惯,在AI加速器里这样分配:AXI4-Full负责“慢速控制”和“大块数据存取”,比如从DDR加载权重、写回计算结果;AXI4-Stream负责“快速计算流水线”,比如卷积层之间的特征图传递。
核心原则:需要随机访问用Full,需要连续吞吐用Stream。千万别混用,否则你会被握手协议折腾死。
4.2 为什么AI加速器偏爱AXI4-Stream?
你想想看,3A算法(比如卷积、池化、激活)的本质是什么?是数据流计算。输入特征图从一端进来,经过一系列算子,从另一端出去。中间不需要随机跳转地址,只需要按顺序往前推。
AXI4-Stream正好匹配这个模型。它只有三个核心信号:
- TVALID:发送方说“数据准备好了”
- TREADY:接收方说“我能收了”
- TLAST:这一包数据结束了
没有地址、没有长度、没有burst。延迟极低,硬件实现也简单。我在项目中遇到过,用AXI4-Stream做卷积层间数据搬运,吞吐率能做到接近理论带宽的95%。换成AXI4-Full,同样的数据量,因为地址握手和仲裁开销,只能跑到70%左右。
小技巧:如果你的Stream数据需要对齐到特定边界(比如64字节),可以在发送端插入一个简单的FIFO做对齐,不要用Full总线的地址对齐逻辑,太浪费资源。
4.3 高效的DMA控制器设计
好了,协议选对了,接下来就是怎么把数据搬得快。DMA控制器就是干这个的。但很多人的DMA设计,说白了就是个“带地址的搬运工”,效率很低。
我设计DMA时,重点关注三个维度:带宽利用率、延迟隐藏、描述符管理。
4.3.1 带宽利用率:别让总线闲着
AXI总线的带宽是固定的,但你的DMA能不能用满,取决于burst长度和outstanding能力。
- Burst长度:我建议至少用16拍(beat),甚至64拍。短burst(比如4拍)会导致频繁的地址握手,带宽利用率直线下降。
- Outstanding:允许DMA在未收到前一笔数据响应时,就发出下一笔请求。这能隐藏DDR的访问延迟。我一般设到8~16笔。
// 伪代码:DMA配置示例
dma_config.burst_len = 64; // 64拍burst
dma_config.outstanding = 16; // 16笔未完成请求
dma_config.data_width = 512; // 512位数据位宽(64字节)
dma_config.src_addr = 0x8000_0000;
dma_config.dst_addr = 0x9000_0000;
dma_config.transfer_size = 1024 * 1024; // 1MB
我曾经踩过的坑:把burst长度设成256,结果DDR控制器报错。后来查手册才发现,DDR控制器的最大burst长度是128。所以,一定要看目标存储器的规格,不是越大越好。
4.3.2 延迟隐藏:双缓冲与乒乓操作
DMA搬数据时,计算单元在干嘛?如果计算单元在干等,那就是浪费。我常用的方法是双缓冲(Double Buffering),也叫乒乓操作。
简单说:准备两个缓冲区。DMA往缓冲区A写数据时,计算单元从缓冲区B读数据。等A写满、B读空,交换角色。这样搬运和计算完全重叠,延迟被完美隐藏。
关键指标:双缓冲能让有效计算时间占比从60%提升到95%以上。如果你的加速器还在用单缓冲,赶紧改。
4.3.3 描述符管理:链式DMA
当你要搬运的数据不是连续的一块,而是分散在多个地址时(比如非连续的特征图行),怎么办?
我建议用链式DMA(Descriptor Chain)。每个描述符包含:源地址、目的地址、长度、下一个描述符指针。DMA自动按链表顺序执行,不需要CPU干预。
// 描述符结构体示例
struct dma_descriptor {
uint64_t src_addr;
uint64_t dst_addr;
uint32_t length;
uint32_t next_desc; // 下一个描述符的地址
uint32_t control; // 控制位(中断使能、完成标志等)
};
这样做的好处是:CPU只需要配置一次链表头,DMA自己跑完整个传输。对于复杂的3A算法数据流(比如多通道、多行的特征图搬运),链式DMA能省掉大量的中断开销。
4.4 整体架构:从DDR到计算核的完整通路
下面这张图,是我个人比较推荐的一种DMA架构。它把AXI4-Full和AXI4-Stream结合起来,各取所长。
这个架构的核心思路是:DMA作为桥梁,一头连AXI4-Full(DDR),一头连AXI4-Stream(计算核)。DMA内部做协议转换、数据重排、双缓冲管理。计算核只看到干净的Stream数据流,不用管地址。
4.5 避坑指南:我踩过的几个雷
最后,分享几个实战中容易翻车的地方:
- 握手信号没处理好导致死锁:我曾经有一个设计,TVALID和TREADY互相等待,结果整个流水线卡死。后来加了超时复位逻辑才解决。记住:握手信号必须能独立置位,不能依赖对方。
- 描述符链表断裂:链式DMA的最后一个描述符,next指针必须为0或特殊值。我见过有人忘了初始化,DMA跑飞了。建议在硬件里加一个链表结束检测。
- 数据位宽不匹配:DDR通常是64字节(512位),但计算核可能只需要32位。DMA里要做位宽转换和字节对齐,否则数据会错位。
- 中断风暴:每搬完一小块数据就发中断,CPU会被淹死。我建议批量完成中断,比如搬完一整张特征图再通知CPU。
个人习惯:在DMA设计初期,先写一个SystemC或Verilator模型,模拟整个数据流。跑通之后再上FPGA验证。这样能提前发现90%的握手和时序问题。