1. 课程导入
欢迎进入《FPGA脉动阵列GEMM矩阵乘加加速器设计》课程。矩阵乘加(GEMM,General Matrix Multiply)是深度学习推理、科学计算、雷达信号处理与图计算中最常见、也最耗算力的核心算子。无论是卷积神经网络中的全连接层与卷积展开,还是 Transformer 中的注意力矩阵运算,最终都可以归结为大规模 GEMM。因此,如何用有限的硬件资源高效完成 GEMM,是 FPGA 加速器设计的关键命题。
本课程的目标不是停留在“会写一个矩阵乘法模块”,而是带你从算法、微架构、RTL 实现到板级验证,完整走通一条脉动阵列(Systolic Array)GEMM 加速器的设计路径。学完之后,你应当能够独立完成一个可综合、可仿真、可上板测试的加速器原型,并能解释其中每一个参数选择背后的权衡。
1.1 学习目标
本课程围绕“能设计、能分析、能优化”三个层次展开,具体目标如下:
- 原理层:理解 GEMM 的分块(Tiling)、数据复用与脉动阵列的数据流动机制,能推导阵列规模、带宽与吞吐率之间的关系。
- 架构层:掌握 PE(Processing Element)单元、行/列广播、权重驻留、双缓冲等典型结构,能画出数据流图并估算资源占用。
- 实现层:能用 Verilog/SystemVerilog 编写可综合的 MAC 阵列、FIFO 接口与状态机,并完成仿真验证。
- 优化层:能针对 DSP、BRAM、布线拥塞与时钟频率进行折中,理解量化、稀疏化对加速器的影响。
- 验证层:能搭建 testbench,对比软件参考结果,定位时序与数据错位问题。
1.2 应用背景:为什么是 FPGA + 脉动阵列
GEMM 的算力需求增长远快于通用处理器的提升速度。GPU 虽然峰值算力高,但在小批量、低延迟、确定性时延和功耗受限的场景中并不总是最优。FPGA 的优势在于可定制数据通路、片上存储带宽高、功耗可控,特别适合边缘推理、车载感知、工业检测与专用信号处理。
而脉动阵列之所以成为 GEMM 加速器的经典结构,是因为它把“计算”和“数据搬运”解耦:数据像血液一样按节拍流过 PE 阵列,每个 PE 只做一次乘加并把结果传递给邻居。这样既减少了全局布线压力,又实现了极高的数据复用率。下面这张图展示了本节最核心的对象——一个 4×4 脉动阵列中,矩阵 A 沿行、矩阵 B 沿列流动,部分和沿对角线累加的基本结构。
图中每个 PE 在每个时钟周期完成一次乘加:C += A × B。A 元素从左侧进入后逐拍向右传递,B 元素从顶部进入后逐拍向下传递,部分和则沿对角线方向流动。正是这种“邻居传递”的规则,让阵列无需全局广播即可持续计算。
1.3 整体课程框架与学习路径
本课程共 30 章,按“由外到内、由静到动、由仿真到上板”的顺序组织。下表给出模块划分与对应章节重点,便于你建立全局地图。
| 阶段 | 章节范围 | 核心内容 | 阶段产出 |
|---|---|---|---|
| 导入与基础 | 第 1–4 章 | GEMM 数学定义、分块策略、脉动阵列原理、性能指标 | 能推导阵列规模与吞吐关系 |
| 微架构设计 | 第 5–12 章 | PE 单元、MAC 阵列、权重驻留、双缓冲、数据流控制 | 完成阵列数据流图与接口定义 |
| RTL 实现 | 第 13–20 章 | Verilog 编码、状态机、FIFO、时序约束、综合报告分析 | 可综合的加速器 RTL 代码 |
| 验证与优化 | 第 21–26 章 | Testbench、参考模型、时序收敛、资源折中、量化 | 仿真通过并完成资源评估 |
| 上板与复盘 | 第 27–30 章 | 板级测试、性能测量、瓶颈定位、设计复盘 | 可演示的加速器原型 |
1.4 典型场景与常见误区
假设我们要在 FPGA 上实现一个 256×256×256 的 GEMM,若直接展开需要 256×256 个 PE,资源显然不够。实际做法是分块:把大矩阵切成 16×16 的小块,用 16×16 的脉动阵列循环计算,配合片上 BRAM 缓存。这样既控制了资源,又保持了较高的数据复用率。
初学者常见的误区包括:
- 只关注算力,忽略带宽:PE 再多,如果数据供给跟不上,阵列也会空转。必须同时计算带宽需求。
- 忽视数据对齐:脉动阵列对节拍极其敏感,A、B 到达时间差一拍就会导致结果错位。
- 过早优化频率:在功能未验证前就追求高频,往往导致调试困难。应先保证功能正确,再逐步加流水线。
- 忽略量化误差:定点化能节省 DSP,但位宽选择不当会显著降低精度。
下面给出一个最小化的 PE 行为参考,帮助你建立第一印象。它描述了每个时钟周期内 PE 的乘加与传递动作:
// PE 行为级参考(每个时钟上升沿)
always @(posedge clk) begin
if (rst) begin
a_out <= 0;
b_out <= 0;
c_acc <= 0;
end else begin
c_acc <= c_acc + a_in * b_in; // 乘加
a_out <= a_in; // A 向右传递
b_out <= b_in; // B 向下传递
end
end
从这段代码可以看到,PE 本身非常简单,真正的难点在于如何组织成百上千个 PE 的协同工作,以及如何让数据在正确的时间到达正确的位置。这正是本课程后续章节要逐一解决的问题。
现在,请带着三个问题进入后续学习:你的目标矩阵规模是多少?片上存储能容纳多大的分块?你愿意用多少 DSP 换取多少吞吐?想清楚这三个问题,脉动阵列的设计就有了明确的边界。