FPGA 与硬件加速

FPGA 神经网络加速器的数据流设计

梳理 FPGA 上神经网络加速器的数据复用、流水线、片上缓存和 AXI 传输组织方式。

目录数据流目标常见结构为什么数据流比 MAC 数量更重要复用分析输入复用权重复用输出复用Tile 设计AXI 传输HLS 与 RTL边界和异常情况一个检查清单阶段性总结

FPGA 神经网络加速器的设计难点通常不是乘加单元本身,而是数据如何稳定、连续、低开销地进入计算阵列。

当网络规模变大后,单纯堆 DSP 并不能保证吞吐提升。计算阵列需要输入特征、权重和部分和持续供应;如果数据搬运跟不上,DSP 会大量空转。FPGA 上的神经网络加速器设计,本质上是在片上存储、外部带宽、流水线深度和控制复杂度之间做平衡。

这篇笔记整理一个面向 CNN 类工作负载的数据流设计框架。它适合学习和研究原型阶段使用,不包含具体板卡上的性能结论。

数据流目标

  • 减少 DDR 访问次数
  • 提高片上 buffer 复用
  • 保持流水线连续
  • 让 AXI burst 更规则
  • 降低控制状态复杂度

这些目标之间并不总是一致。例如,更大的 tile 可以提高数据复用,但会占用更多 BRAM/URAM;更深的流水线可以提高频率,但会让 valid 对齐和边界处理变复杂;更灵活的尺寸支持会提升通用性,但会增加控制逻辑和验证成本。

第一版原型通常应该优先保证数据流清晰和验证可控,而不是追求最复杂的调度策略。

常见结构

DDR
 └─ AXI DMA
     └─ Line Buffer
         └─ Compute Array
             └─ Output Buffer
                 └─ AXI DMA

这个结构可以拆成几个阶段:

阶段主要任务关键问题
DDR 读取从外部存储搬运输入和权重burst 长度、地址连续性、带宽利用
片上缓存保存当前 tile 的数据BRAM 容量、端口冲突、双缓冲
窗口生成形成卷积窗口padding、stride、边界 valid
计算阵列执行 MACDSP 映射、并行度、流水线
输出写回写回结果或部分和累加位宽、写回顺序、带宽

为什么数据流比 MAC 数量更重要

以一个 K×KK \times K 卷积为例,每个输出点需要 K2CinK^2 \cdot C_{in} 次乘加。如果每次乘加都从 DDR 读取输入和权重,外部带宽会成为主要瓶颈。片上数据复用的目标就是让一次 DDR 读取的数据参与尽可能多的计算。

输入特征图的空间复用可以来自滑动窗口:

row 0: x00 x01 x02 x03 ...
row 1: x10 x11 x12 x13 ...
row 2: x20 x21 x22 x23 ...

3x3 window moves by one column:
  old: x00 x01 x02    new: x01 x02 x03
       x10 x11 x12         x11 x12 x13
       x20 x21 x22         x21 x22 x23

两个相邻窗口之间大部分数据是重叠的。如果没有 line buffer 和 window buffer,就会重复从外部存储读取相同数据。

复用分析

卷积层中的输入特征图可以在空间维度复用,权重可以在输出通道维度复用。若片上存储不足,需要分块处理。

复用对象常见策略风险
输入特征行缓存、窗口缓存边界处理复杂
权重通道分块权重加载开销
输出局部累加累加位宽增长

更具体地说,数据复用可以分成三类。

输入复用

输入复用主要利用卷积窗口的重叠。对 3x3 卷积来说,窗口每向右移动一列,只需要引入一列新数据。line buffer 可以保存前几行数据,window buffer 可以保存当前窗口。

Input Stream → Line Buffer → Window Buffer → MAC Array

边界处理是输入复用中的常见复杂点。padding 区域可以选择在读入阶段补零,也可以在 window generator 中根据坐标产生 zero valid。

权重复用

同一组权重会用于多个空间位置。因此可以把当前输出通道或输出通道 tile 的权重预加载到片上 buffer 中,再扫描输入特征图。

权重 buffer 的大小取决于:

Toc×Tic×Kh×Kw×WbitsT_{oc} \times T_{ic} \times K_h \times K_w \times W_\mathrm{bits}

其中 TocT_{oc}TicT_{ic} 是输出通道和输入通道的 tile 大小。tile 越大,权重复用越好,但片上存储压力也越大。

输出复用

当输入通道被分块处理时,输出需要跨多个 TicT_{ic} tile 累加。此时可以把部分和保存在片上 buffer 中,等所有输入通道处理完成后再写回 DDR。

输出复用的主要问题是累加位宽和 buffer 容量。int8 乘法的输出通常不能直接用 int8 保存,需要更宽的累加类型。

Tile 设计

卷积层可以抽象成多个维度的 tile:

维度含义设计影响
T_h输出高度 tileline buffer 深度、边界处理
T_w输出宽度 tileburst 连续性、窗口滑动
T_ic输入通道 tile输入和权重 buffer 大小
T_oc输出通道 tileMAC 并行度、输出 buffer

一个 tile 的计算顺序可以写成:

for oc_tile:
  clear partial sums
  for ic_tile:
    load input tile
    load weight tile
    compute and accumulate
  write output tile

如果有双缓冲,可以在计算当前 tile 时预取下一组输入或权重:

time →
Buffer A: load tile 0 | compute tile 0 | load tile 2 | compute tile 2
Buffer B:             | load tile 1    | compute tile 1 | load tile 3

双缓冲能隐藏部分数据搬运延迟,但会增加控制和存储资源。第一版设计可以先不用双缓冲,等瓶颈明确后再加入。

AXI 传输

在 Zynq 或带 AXI 总线的 FPGA 系统中,数据搬运通常通过 AXI DMA、AXI master 或 HLS 生成的 m_axi 接口完成。AXI 访问性能和地址连续性密切相关。

需要关注:

  • 起始地址是否对齐。
  • burst 是否足够长。
  • 是否频繁跨行或跨 tile 跳转。
  • 读写通道是否能与计算重叠。
  • PS 和 PL 之间的数据 cache 是否需要 flush/invalidate。

一个比较规整的数据布局可以减少地址生成复杂度:

NCHW layout:
addr = base + (((n * C + c) * H + h) * W + w) * bytes

如果数据布局与计算访问顺序不匹配,可能需要在软件侧预处理,或者在硬件侧增加重排 buffer。二者都不是免费的,需要根据系统瓶颈选择。

HLS 与 RTL

HLS 适合快速验证数据流结构,但接口、数组分割和 pipeline pragma 需要严格检查。关键模块可以逐步替换成手写 RTL。

HLS 中常见优化包括:

#pragma HLS PIPELINE II=1
#pragma HLS ARRAY_PARTITION variable=weight complete dim=1
#pragma HLS DATAFLOW

这些 pragma 的效果必须通过综合报告和仿真验证确认。比如 PIPELINE II=1 可能因为存储端口冲突而无法达到,ARRAY_PARTITION 可能显著增加寄存器或 LUT 消耗,DATAFLOW 需要检查 FIFO 深度和死锁风险。

手写 RTL 的优势是控制更精细,尤其适合固定结构的数据通路、复杂握手和特定时序优化。缺点是开发周期更长、验证要求更高。

一个实用路径是:

  1. 用 Python 固定算法和定点化行为。
  2. 用 HLS 或高级模型快速探索 tile 和数据流。
  3. 对瓶颈模块改写手写 RTL。
  4. 保留统一测试向量和结果检查脚本。
  5. 在板级测试前完成仿真级接口和边界验证。

边界和异常情况

数据流设计最容易在边界情况下出错:

  • 输入宽度不是 tile 宽度的整数倍。
  • 输出通道数不能整除并行度。
  • padding 区域参与窗口生成。
  • stride 大于 1 时窗口移动不连续。
  • 最后一个 burst 长度不足。
  • 下游 ready 拉低导致流水线停顿。

这些情况应该进入 directed test,而不是只依赖随机测试。特别是 valid/ready 接口,需要主动插入 stall,检查窗口和输出是否错位。

一个检查清单

在确定某个数据流前,可以用下面的问题自查:

问题目的
每个输入元素平均被复用多少次?判断 line buffer 是否有效
权重加载是否被多个输出位置共享?判断权重 buffer 是否值得
部分和在哪里保存?判断输出 buffer 和累加位宽
AXI 访问是否连续?判断外部带宽利用
tile 边界如何处理?避免最后一块出错
stall 时状态是否保持一致?保证系统集成可靠

阶段性总结

当前笔记只提供设计框架,具体资源占用和性能数据必须由实际板卡、频率、位宽和网络结构决定。

FPGA 神经网络加速器的数据流设计,核心是让数据搬运和计算阵列匹配。输入、权重和输出的复用方式决定了片上 buffer 组织;tile 大小决定了资源和带宽平衡;AXI 访问形态决定了系统级效率。第一版设计应先追求清晰和可验证,再根据实测瓶颈逐步优化。

从算法模型到可综合 RTL 的完整流程

记录神经网络算子从 Python 模型、定点化、接口定义到可综合 RTL 的工程拆解方法。