深度学习处理器运算器设计

8

深度学习处理器运算器设计

实验环境

通过 Verilog 语言实现的模块需要使用 HDL 仿真工具进行编译调试。本节使用 Ubuntu 24.04 LTS 版本的操作系统,在该环境上用 Verilator 和 GTKWave 仿真工具来完成实验。

运行环境:

镜像收藏:mlu370_ubuntu22.04-for-student :v8.1

目录:

代码文件组织:

1
2
3
4
5
6
7
8
9
10
• 目录 src,包含编写的实验 Verilog 源代码。 
• 目录 sim,仿真相关脚本、测试文件及编译产物。
– 文件 tb_top.cpp,测试顶层文件,生成激励信号,实例化矩阵运算子单元模块。
– 文件 Makefile,管理编译流程,定义如何将 Verilog 与 C++ 编译成可执行文件。
– 文件 compile.f,编译文件列表,用于指定编译脚本需要编译的文件。
• 目录 data,包含仿真输入输出数据文件。
– 向量规模描述文件 config,描述需进行内积计算的向量规模。
– 输入神经元文件 neuron,存储输入的神经元数据。
– 输入权重文件 weight,存储输入的权重数据。
– 输出结果文件 result,存储运算结果。

实验内容:

本实验分为三个步骤,分别实现内积运算器、矩阵乘向量运算器和矩阵乘法运算器。最后介绍如何使用仿真工具对代 码进行编译和调试。

代码补全

内积运算器

内积运算器的核心功能是:实现向量的乘积累加,即输入神经元激活值向量与权重向量逐元素相乘,然后再将所有乘积结果累加到一起。

以固定向量长度为 4 来展示一个例子:该示例采用同步复位、输入寄存器以及固定的两级流水线(输入采样一拍、计算输出一拍),每个时钟周期可完成一个完整的长度为 4 的内积运算。

image-20260521111516158

输入神经元和权重数据均需要声明为有符号数(signed),确保 Verilog 代码中使用的乘法运算符(*)能够正确生成有符号数乘法器。

让ds老师做了个表

设备 第1拍 (reset=1) 第2拍 (reset=0, enable=1, 输入A0/W0) 第3拍 (enable=1, 输入A1/W1) 第4拍 (enable=0, 输入A2/W2无效) 第5拍 (enable=1, 输入A3/W3)
输入寄存器 (activations_reg & weights_reg) 同步清零,输出全0 时钟上升沿锁存 A0, W0 时钟上升沿锁存 A1, W1 保持 A1, W1(未锁存新数据) 时钟上升沿锁存 A3, W3
乘法器 (4个并行) 输出全0(0×0) 计算 A0×W0,输出四个32位乘积 计算 A1×W1,输出四个32位乘积 保持输出 A1×W1 的乘积(输入未变) 计算 A3×W3,输出四个32位乘积
加法器 (加法树) 输出 0 累加四个乘积,输出 sum0 累加四个乘积,输出 sum1 保持输出 sum1 累加四个乘积,输出 sum3
结果寄存器 (result) 同步清零,输出 0 保持 0(上一拍的值) 时钟上升沿锁存 sum0,输出 sum0 保持 sum0(使能无效不更新) 时钟上升沿锁存 sum1,输出 sum1

代码

inner_product_4x16。这个是给好的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
module inner_product_4x16 (
input wire clk,
input wire reset,
input wire enable,
input wire signed [15:0] activations [0:3],
input wire signed [15:0] weights [0:3],
output reg signed [31:0] result
);
// 输入寄存器
reg signed [15:0] activations_reg [0:3];
reg signed [15:0] weights_reg [0:3];

// 组合逻辑部分
wire signed [31:0] dot_product =
(activations_reg[0] * weights_reg[0]) +
(activations_reg[1] * weights_reg[1]) +
(activations_reg[2] * weights_reg[2]) +
(activations_reg[3] * weights_reg[3]);

// 寄存器更新逻辑
integer i;
always @(posedge clk) begin
if (reset) begin
for (i = 0; i < 4; i = i + 1) begin
activations_reg[i] <= 16'd0;
weights_reg[i] <= 16'd0;
end
result <= 32'd0;
end else if (enable) begin
for (i = 0; i < 4; i = i + 1) begin
activations_reg[i] <= activations[i];
weights_reg[i] <= weights[i];
end
result <= dot_product;
end
end
endmodule

矩阵乘向量运算器

image-20260528145357587

matrix_vector_mult_4x4x16

该设计继承内积运算器的两级流水线时序特性。第一拍完成输入神经元激活值向量和 权重矩阵的锁存,第二拍四个内积运算器并行完成乘累加计算。在使能信号有效后的第二 个时钟上升沿,结果向量将稳定输出。该设计每个时钟周期可完成一次完整的 4 阶方矩阵 与长度为 4 的向量之间的乘法运算,但需要注意运算结果依然固定延迟 2 拍才产生。

代码

注意最后要留一个空行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
module matrix_vector_mult_4x4x16 (
input wire clk,
input wire reset,
input wire enable,
input wire signed [15:0] activations [0:3],
input wire signed [15:0] weights [0:3][0:3],
output wire signed [31:0] results [0:3]
);

inner_product_4x16 ipu0 (
.clk(clk),
.reset(reset),
.enable(enable),
.activations(activations),
.weights(weights[0]),
.result(results[0])
);

inner_product_4x16 ipu1 (
.clk(clk),
.reset(reset),
.enable(enable),
.activations(activations),
.weights(weights[1]),
.result(results[1])
);

inner_product_4x16 ipu2 (
.clk(clk),
.reset(reset),
.enable(enable),
.activations(activations),
.weights(weights[2]),
.result(results[2])
);

inner_product_4x16 ipu3 (
.clk(clk),
.reset(reset),
.enable(enable),
.activations(activations),
.weights(weights[3]),
.result(results[3])
);

endmodule

矩阵乘法运算器

image-20260528145654049

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
module matrix_mult_4x4x4x16 (
input wire clk,
input wire reset,
input wire enable,
input wire signed [15:0] activations [0:3][0:3],
input wire signed [15:0] weights [0:3][0:3],
output wire signed [31:0] results [0:3][0:3]
);

matrix_vector_mult_4x4x16 mxv0 (
.clk(clk),
.reset(reset),
.enable(enable),
.activations(activations[0]),
.weights(weights),
.results(results[0])
);
matrix_vector_mult_4x4x16 mxv1 (
.clk(clk),
.reset(reset),
.enable(enable),
.activations(activations[1]),
.weights(weights),
.results(results[1])
);
matrix_vector_mult_4x4x16 mxv2 (
.clk(clk),
.reset(reset),
.enable(enable),
.activations(activations[2]),
.weights(weights),
.results(results[2])
);
matrix_vector_mult_4x4x16 mxv3 (
.clk(clk),
.reset(reset),
.enable(enable),
.activations(activations[3]),
.weights(weights),
.results(results[3])
);

endmodule

编译调试

工具安装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 安装依赖工具
sudo apt-get update
sudo apt-get install git help2man perl python3 make autoconf g++ flex bison ccache
sudo apt-get install libgoogle-perftools-dev numactl perl-doc
sudo apt-get install libflib2 # 仅针对Ubuntu,忽略错误
sudo apt-get install libflib-dev # 仅针对Ubuntu,忽略错误
sudo apt-get install zlibc zlib1g zlib1g-dev # 仅针对Ubuntu,忽略错误

# 下载 Verilator 源码(国内镜像)
git clone https://gitee.com/mirrors/Verilator.git

# (进入Verilator目录)编译安装 Verilator
autoconf # 创建 configure 脚本
./configure # 配置 Makefile 文件
make -j `nproc` # 编译 Verilator(如果出错可简化为 make)
sudo make install

# 安装 GTKWave 波形查看工具
sudo apt-get install gtkwave

# 安装检查
verilator --version
gtkwave --version

编译运行

编译命令(在sim目录下)

1
2
3
make inner_product_4x16
make matrix_vector_mult_4x4x16
make matrix_mult_4x4x4x16

运行命令

1
2
3
./obj_dir/inner_product_4x16/Vinner_product_4x16          # 测试内积运算器
./obj_dir/matrix_vector_mult_4x4x16/Vmatrix_vector_mult_4x4x16 # 测试矩阵乘向量
./obj_dir/matrix_mult_4x4x4x16/Vmatrix_mult_4x4x4x16 # 测试矩阵乘法

这几个测试命令运行完会生成类似于wave_matrix_mult.vcd的文件,说明波形数据已经成功转储

运行时报错:

image-20260528211028754

模块 inner_product_4x16 输入 weights [0:3](4个权重),它只需要4个权重值。报错中的 weight=16 说明数据文件 weight 包含了16个数,与模块不匹配。所以需要修改数据文件,只保留前4个数

1
2
3
4
5
6
7
8
# 备份原文件
cp /opt/code_chap8/data/weight /opt/code_chap8/data/weight.full

# 只保留前4个数据
head -4 /opt/code_chap8/data/weight.full > /opt/code_chap8/data/weight

# (在sim目录下)重新运行测试
./obj_dir/inner_product_4x16/Vinner_product_4x16

内积运算器:

image-20260528223524878

image-20260528223504077

矩阵乘向量测试:

image-20260528213835767

做完了这个测试之后需要改回来,因为这俩又期望16个数据了。weight可以恢复,neuron和result只好复制粘贴了几遍

矩阵乘矩阵测试:

image-20260528214531420

让ds整理了一下数据规模:

测试目标 neuron 文件 weight 文件 result 文件
inner_product_4x16 4 个数 4 个数 1 个数
matrix_vector_mult_4x4x16 4 个数 16 个数(4×4 矩阵) 4 个数
matrix_mult_4x4x4x16 16 个数(4×4 矩阵) 16 个数 16 个数

gtkwave仿真

这里最大的问题在于,我VSCode Server连接上去,不知道为什么终端打不开。于是使用ssh命令连接上去做的实验,是没有图形界面的,无法直接启动 GTKWave:

image-20260528215305408

问了组员,本地下了一个GTKWave

启动仿真

1
gtkwave wave_inner.vcd wave_matrix_vector.vcd wave_matrix_mult.vcd

长这样

image-20260528221459628

迭代调试

当发现仿真结果错误后,重新修改代码,执行以下步骤:

  1. 运行“make clean”清理旧的编译产物;
  2. 运行“make”命令重新编译;
  3. 执行仿真可执行文件启动 GTKWave,执行仿真观察结果;
  4. 仿真完成后,退出仿真。