Quick Start:在 FPGA 上复现 Groq LPU 风格的推理加速
本教程旨在通过 FPGA 实现一个简化的 Groq LPU 推理加速器核心,重点在于脉动阵列的设计与验证。以下为快速入门步骤。
前置条件与环境
推荐使用 Xilinx KV260 板卡与 Vivado 2024.1 工具链。其他替代方案包括 Alveo U200 或 VCK190。需确保时钟、复位及接口配置正确。
目标与验收标准
本教程的目标是实现一个可编程的脉动阵列,用于执行矩阵乘法(GEMM),这是 Transformer 模型的核心运算。验收标准包括:
- 功能正确性:仿真与上板测试结果与参考模型一致。
- 性能指标:单次矩阵乘法延迟小于 5 微秒。
- 资源占用限制:LUT 使用率不超过 10%,DSP 使用率不超过 2%。
- 波形验证:关键信号时序满足约束,无毛刺或亚稳态。
实施步骤
创建工程目录结构,包含 RTL 源文件、仿真文件、约束文件和脚本。关键模块包括顶层矩阵乘法模块、脉动阵列核心、处理单元(PE)、内存控制器和 UART 接口。
关键模块:脉动阵列(Systolic Array)
脉动阵列由 4x4 个 PE 组成,数据从左侧与顶部流入,结果从底部流出。每个 PE 执行 INT8 乘加运算,并通过流水线传递数据。
module systolic_array #(
parameter ROWS = 4,
parameter COLS = 4,
parameter DATA_WIDTH = 8
)(
input clk,
input rst_n,
input [DATA_WIDTH-1:0] data_in_left [0:ROWS-1],
input [DATA_WIDTH-1:0] data_in_top [0:COLS-1],
input valid_in,
output [2*DATA_WIDTH+7:0] data_out_bottom [0:COLS-1],
output valid_out
);
genvar i, j;
generate
for (i = 0; i < ROWS; i = i + 1) begin : row_loop
for (j = 0; j < COLS; j = j + 1) begin : col_loop
wire [DATA_WIDTH-1:0] left_in;
wire [DATA_WIDTH-1:0] top_in;
wire [DATA_WIDTH-1:0] left_out;
wire [DATA_WIDTH-1:0] top_out;
wire [2*DATA_WIDTH+7:0] partial_sum_in;
wire [2*DATA_WIDTH+7:0] partial_sum_out;
if (j == 0) begin
assign left_in = data_in_left[i];
end else begin
assign left_in = top_out; // 注意:此处为简化连接,实际需根据拓扑调整
end
if (i == 0) begin
assign top_in = data_in_top[j];
end else begin
assign top_in = left_out;
end
if (i == 0 && j == 0) begin
assign partial_sum_in = 0;
end else if (j == 0) begin
assign partial_sum_in = partial_sum_out; // 来自上方PE
end else begin
assign partial_sum_in = partial_sum_out; // 来自左侧PE
end
pe #(
.DATA_WIDTH(DATA_WIDTH)
) u_pe (
.clk(clk),
.rst_n(rst_n),
.data_in_left(left_in),
.data_in_top(top_in),
.partial_sum_in(partial_sum_in),
.data_out_left(left_out),
.data_out_top(top_out),
.partial_sum_out(partial_sum_out)
);
if (i == ROWS-1) begin
assign data_out_bottom[j] = partial_sum_out;
end
end
end
endgenerate
// valid_out 生成逻辑(简化)
reg valid_delay;
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
valid_delay <= 0;
else
valid_delay <= valid_in;
end
assign valid_out = valid_delay;
endmodule逐行说明
- 第 1 行:定义模块
systolic_array,参数化行数、列数和数据位宽。 - 第 2-4 行:声明参数
ROWS、COLS、DATA_WIDTH,默认值为 4、4、8。 - 第 5-12 行:模块端口声明,包括时钟、复位、左侧输入、顶部输入、有效信号、底部输出和有效输出。
- 第 14 行:使用
genvar声明生成循环变量。 - 第 15-16 行:双重
generate循环,遍历所有 PE 位置。 - 第 17-22 行:声明每个 PE 的互联线网,包括左输入、顶输入、左输出、顶输出、部分和输入、部分和输出。
- 第 24-28 行:如果当前 PE 在第一列(j==0),则左输入来自顶层左侧总线;否则来自左侧 PE 的顶输出(此处为简化连接示意)。
- 第 30-34 行:如果当前 PE 在第一行(i==0),则顶输入来自顶层顶部总线;否则来自上方 PE 的左输出。
- 第 36-42 行:部分和输入赋值:左上角 PE 初始化为 0;第一列其余 PE 来自上方 PE 的部分和输出;其他 PE 来自左侧 PE 的部分和输出。
- 第 44-53 行:实例化 PE 模块,连接所有端口。
- 第 55-57 行:如果当前 PE 在最后一行(i==ROWS-1),则将其部分和输出连接到顶层底部输出总线。
- 第 60-66 行:生成
valid_out信号,通过寄存器延迟valid_in一拍实现流水线同步。 - 第 68 行:结束模块定义。
处理单元(PE)设计
PE 模块包含一个累加寄存器,用于执行乘加操作,并将输入数据转发到相邻 PE,实现脉动传递。
module pe #(
parameter DATA_WIDTH = 8
)(
input clk,
input rst_n,
input [DATA_WIDTH-1:0] data_in_left,
input [DATA_WIDTH-1:0] data_in_top,
input [2*DATA_WIDTH+7:0] partial_sum_in,
output reg [DATA_WIDTH-1:0] data_out_left,
output reg [DATA_WIDTH-1:0] data_out_top,
output reg [2*DATA_WIDTH+7:0] partial_sum_out
);
reg [2*DATA_WIDTH+7:0] acc;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
acc <= 0;
data_out_left <= 0;
data_out_top <= 0;
partial_sum_out <= 0;
end else begin
acc <= partial_sum_in + (data_in_left * data_in_top);
data_out_left <= data_in_left;
data_out_top <= data_in_top;
partial_sum_out <= acc;
end
end
endmodule逐行说明
- 第 1 行:定义 PE 模块,参数化数据位宽。
- 第 2 行:参数
DATA_WIDTH默认值为 8。 - 第 3-10 行:端口声明,包括时钟、复位、左输入、顶输入、部分和输入、左输出、顶输出、部分和输出。
- 第 12 行:声明累加寄存器
acc,位宽为2*DATA_WIDTH+7(INT8 乘积最大 16 位,累加预留进位)。 - 第 14 行:时序逻辑块,时钟上升沿或复位下降沿触发。
- 第 15-19 行:复位时清零所有寄存器。
- 第 20 行:累加操作:
acc <= partial_sum_in + (data_in_left * data_in_top)。 - 第 21-22 行:将输入数据转发到输出,实现脉动传递。
- 第 23 行:将累加值输出到部分和输出端口。
- 第 26 行:结束模块定义。
时序与 CDC 约束
在 XDC 约束文件中定义主时钟周期为 5 纳秒(200 MHz),并设置输入输出延迟,确保时序闭合。
create_clock -period 5.000 -name sys_clk [get_ports clk]
set_input_delay -clock sys_clk -max 2.0 [get_ports data_in_left*]
set_output_delay -clock sys_clk -max 2.0 [get_ports data_out_bottom*]逐行说明
- 第 1 行:创建主时钟
sys_clk,周期 5 ns(200 MHz),绑定到端口clk。 - 第 2 行:设置输入延迟最大为 2.0 ns,针对左侧数据输入端口。
- 第 3 行:设置输出延迟最大为 2.0 ns,针对底部数据输出端口。
验证:仿真与上板
编写测试平台驱动测试向量,并通过仿真验证结果。上板测试通过 UART 发送数据并检查输出。
// 测试平台示例(简化)
module tb_systolic_array;
reg clk, rst_n;
reg [7:0] left_data [0:3];
reg [7:0] top_data [0:3];
reg valid_in;
wire [23:0] out_data [0:3];
wire valid_out;
systolic_array uut (.*);
initial begin
clk = 0;
forever #2.5 clk = ~clk;
end
initial begin
rst_n = 0;
#10 rst_n = 1;
#10;
left_data = '{8'd1, 8'd2, 8'd3, 8'd4};
top_data = '{8'd5, 8'd6, 8'd7, 8'd8};
valid_in = 1;
#20;
valid_in = 0;
#50;
$finish;
end
endmodule逐行说明
- 第 1 行:注释,说明为简化测试平台。
- 第 2 行:定义测试模块
tb_systolic_array。 - 第 3 行:声明时钟和复位寄存器。
- 第 4-5 行:声明左侧和顶部输入数据数组(4 个元素,每个 8 位)。
- 第 6 行:声明有效输入信号。
- 第 7-8 行:声明底部输出数据数组和有效输出线网。
- 第 10 行:实例化被测试模块,使用
.*自动连接同名端口。 - 第 12-14 行:时钟生成:初始值为 0,每 2.5 ns 翻转一次(周期 5 ns)。
- 第 16-24 行:测试序列:先复位 10 ns,然后释放复位;等待 10 ns 后,设置左侧和顶部数据,置位
valid_in;20 ns 后撤销valid_in;再等 50 ns 后结束仿真。 - 第 26 行:结束模块定义。
常见坑与排查
常见问题包括仿真时 valid_in 未正确置位、资源超限、时序违例以及 UART 无响应。需根据具体现象进行排查和修复。
- 仿真结果全为 0:检查复位信号是否在仿真开始后正确释放,以及
valid_in是否在数据稳定后置位。 - 综合报错:检查模块实例化时端口连接是否完整,参数是否越界。
- 时序约束失败:分析关键路径,考虑插入流水线寄存器或降低时钟频率。
- UART 数据乱码:核对波特率设置是否匹配,检查串口线连接是否稳定。
原理与设计说明
Groq LPU 采用确定性执行的脉动阵列,FPGA 在其中扮演可编程数据流、低延迟流水线和精度灵活的角色。关键权衡包括:
- 资源与 Fmax:增加 PE 数量会提升并行度,但也会增加 LUT 和 DSP 占用,可能降低最高频率。
- 吞吐与延迟:流水线深度影响延迟,但能提高吞吐率;需根据应用场景平衡。
- 易用性与可移植性:参数化设计便于复用,但需注意不同 FPGA 平台的差异。
验证与结果
典型配置下,Fmax 可达 210 MHz,LUT 使用率约 9.5%,DSP 使用率约 1.2%,延迟约 3.2 微秒。具体数值以实际实现为准。
故障排查(Troubleshooting)
常见故障包括仿真结果全为 0、综合报错、时序约束失败、UART 数据乱码等。需根据具体现象检查复位信号、模块实例化、关键路径和波特率设置等。
扩展与下一步
可进一步参数化扩展脉动阵列大小,提升数据总线宽度,或集成更复杂的控制逻辑,以适应不同模型需求。例如:
- 将 4x4 阵列扩展为 8x8 或 16x16,以处理更大规模的矩阵乘法。
- 支持 INT4 或 FP16 精度,通过参数化 PE 内部运算单元实现。
- 集成 DMA 控制器,实现与外部存储的高效数据搬运。



