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结合起来,各取所长。

AI加速器DMA架构图 AI加速器DMA架构:AXI4-Full + AXI4-Stream 混合通路 DDR存储器 AXI4-Full 总线 DMA控制器 链式描述符 + 双缓冲管理 AXI4-Stream 总线 计算核(卷积/池化/激活) 流水线处理,无地址依赖 中断/完成信号 说明: DMA通过AXI4-Full从DDR加载数据,转换为AXI4-Stream推送给计算核。计算完成后,结果通过另一路AXI4-Stream回传DMA,再通过AXI4-Full写回DDR。

这个架构的核心思路是:DMA作为桥梁,一头连AXI4-Full(DDR),一头连AXI4-Stream(计算核)。DMA内部做协议转换、数据重排、双缓冲管理。计算核只看到干净的Stream数据流,不用管地址。

4.5 避坑指南:我踩过的几个雷

最后,分享几个实战中容易翻车的地方:

  1. 握手信号没处理好导致死锁:我曾经有一个设计,TVALID和TREADY互相等待,结果整个流水线卡死。后来加了超时复位逻辑才解决。记住:握手信号必须能独立置位,不能依赖对方。
  2. 描述符链表断裂:链式DMA的最后一个描述符,next指针必须为0或特殊值。我见过有人忘了初始化,DMA跑飞了。建议在硬件里加一个链表结束检测。
  3. 数据位宽不匹配:DDR通常是64字节(512位),但计算核可能只需要32位。DMA里要做位宽转换和字节对齐,否则数据会错位。
  4. 中断风暴:每搬完一小块数据就发中断,CPU会被淹死。我建议批量完成中断,比如搬完一整张特征图再通知CPU。

个人习惯:在DMA设计初期,先写一个SystemC或Verilator模型,模拟整个数据流。跑通之后再上FPGA验证。这样能提前发现90%的握手和时序问题。