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 的内积运算。
输入神经元和权重数据均需要声明为有符号数(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
矩阵乘向量运算器
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
矩阵乘法运算器
代码 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 updatesudo apt-get install git help2man perl python3 make autoconf g++ flex bison ccachesudo apt-get install libgoogle-perftools-dev numactl perl-docsudo apt-get install libflib2 sudo apt-get install libflib-dev sudo apt-get install zlibc zlib1g zlib1g-dev git clone https://gitee.com/mirrors/Verilator.git autoconf ./configure make -j `nproc ` sudo make installsudo apt-get install gtkwaveverilator --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的文件,说明波形数据已经成功转储
运行时报错:
模块 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.fullhead -4 /opt/code_chap8/data/weight.full > /opt/code_chap8/data/weight./obj_dir/inner_product_4x16/Vinner_product_4x16
内积运算器:
矩阵乘向量测试:
做完了这个测试之后需要改回来,因为这俩又期望16个数据了。weight可以恢复,neuron和result只好复制粘贴了几遍
矩阵乘矩阵测试:
让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:
问了组员,本地下了一个GTKWave
启动仿真 1 gtkwave wave_inner.vcd wave_matrix_vector.vcd wave_matrix_mult.vcd
长这样
迭代调试 当发现仿真结果错误后,重新修改代码,执行以下步骤:
运行“make clean”清理旧的编译产物;
运行“make”命令重新编译;
执行仿真可执行文件启动 GTKWave,执行仿真观察结果;
仿真完成后,退出仿真。