文章目录
项目要求
基本要求
输入有9个矩阵,权重矩阵有8个,分别是Weight I0~I7,Input矩阵I -1。
8个矩阵都是都是16行*16列的,且矩阵中的每个元素是16位补码形式的有符号定点数(1位符号位,6位整数位,9位小数位)

要求将Weight I0依次乘以Input I-1 ,Weight I1 ,Weight I2 ,Weight I3 ,Weight I4, Weight I5, Weight I6 , Weight I7,依次得到Input I0 ,Input I1 ,Input I2 ,Input I3 ,Input I4 ,Input I5 ,Input I6 ,Input I7
最终输出Input I7
截断要求
对于矩阵AB=C,C矩阵的第i行第j列元素是A的第i行和B的第j列进行乘加运算得到的,
由于矩阵的元素是16位,两个16位元素相乘结果需要用216-1=31位表示,再考虑相加,因此需要31+4=35位来表示。
在这个项目中不考虑相加后会超过31位的情况,只用31位表示。
对于Weight I0 * Input I-1=Input I0,Input I0中的每个元素都是31位的,有1位符号位,18位小数,12位整数,要求截断为16位再作为下一层的输入。

A[15:0]*B[15:0]=C_31[30:0] 要将C_31[30:0]转为C_16[15:0]需要考虑低位截断和高位饱和操作:
低位截断
低位C_31[8:0]截断,要考虑C_31[8]的四舍五入,
如果符号位为0,则向前进1,
如果符号位为1,直接截断。
这个操作的正确性证明如下:

高位饱和
C[15:9]能表示的范围在[-64:63],如果C_31[30:24]超过该范围,产生上/下溢,C_16[14:9]则取能表示的最大/最小值
如果符号位为0,C_31[29:24]存在1则上溢,
如果符号位为1,C_31[29:24]存在0则下溢。
参考结果
这里给出第一层和最后一层(第八层)的输出作为参考,需要注意的是矩阵AB和BA结果是不同的,这里第一层的结果是Weight I0在左边乘以Input -1 ,得到的Input 0


项目实现
实现思路
如图所示,其中标记绿色的是调用$readmemh从文件中直接读取的,项目实现思路如下:
-
首先,使用$readmemh从文件中读取
Weight I0,Input I-1 ,Weight I1 ,Weight I2 ,Weight I3 ,Weight I4, Weight I5, Weight I6 , Weight I7,
存储到对应的二维数组中,然后将这些二维数组转为一维数组(因为Verilog模块的端口不能使用多维数组)。 -
接着,实例化第一个16*16的模块matrix_multiplier_16,输入I_16_1d和W0_2d,得到相乘的结果I0_31_1d,将其转为二维矩阵I0_31_2d,然后按照截断要求转为I0_16_2d
-
下面,反复实例化matrix_multiplier_16,每次将上一层计算结果和对应的Weight矩阵作为输入,得到相乘的结果,最终结果是第八层的输出I7_16_2d。

实现代码
matrix_multiplier_16.v
`timescale 1ns / 1ps
//输入端口A矩阵,B矩阵是16行16列,每个元素16位有符号定点数(1位符号位,6位整数位,9位小数位)
//输出端口Result矩阵是A*B的结果,是16行16列,每个元素是31位有符号定点数(1位符号位,12位整数位,18位小数位)
//Verilog模块的端口只能是一维数组,所以每次调用该模块要保证输入的矩阵已经被转化成了一维长数组
module matrix_multiplier_16(A,B,Result);
input [16*16*16-1:0] A;
input [16*16*16-1:0] B;
output [16*16*31-1:0] Result;
reg [16*16*31-1:0] reg_result;
reg signed [15:0] A1[0:15][0:15];
reg signed [15:0] B1[0:15][0:15];
reg signed [30:0] Res1[0:15][0:15];
integer i,j,k;
always@(A or B) begin
//在模块内将一维转二维,方便计算
for(i=0;i<16;i=i+1) begin
for(j=0;j<16;j=j+1) begin
A1[i][j]=A[i*16*16+j*16+:16];
B1[i][j]=B[i*16*16+j*16+:16];
end
end
//初始化结果为0
for(i=0;i<16;i=i+1) begin
for(j=0;j<16;j=j+1) begin
Res1[i][j]=31'd0;
end
end
//使用矩阵相乘的定义计算Res1矩阵的结果
//Res1[i][j]是A的第i行和B的第j列相乘再相加的结果
for(i=0;i<16;i=i+1)begin
for(j=0;j<16;j=j+1)begin
for(k=0;k<16;k=k+1)begin
//A1[i][k]和B1[k][j]都是补码形式的有符号定点数
//A[补码]*B[补码]=(A*B)[补码]
Res1[i][j]=Res1[i][j]+A1[i][k]*B1[k][j];
end
end
end
//将二维转回一维,将Res1[0][0]存储在reg_result的最高位,Res[i][j]前面有i*16+j个数据,每个31位,因此转为1维的索引是
//16*16*31-1-(i*16+j)*31
//这里涉及到一个语法:A[7:3]和A[7-:4]是等价的 同理A[0+:3]和A[0:2]等价
for(i=0;i<16;i=i+1)begin
for(j=0;j<16;j=j+1)begin
//When 2d converted to 1d,it is stored from high bit to low bit.
reg_result[16*16*31-1-((i*16+j)*31) -: 31]=Res1[i][j];
end
end
end
assign Result=reg_result;
endmodule
tb_mm_mlp.v
命名为tb_mm_mlp是因为mm是matrix multiplier,mlp是多层感知机,深度学习中多层感知机的计算实际上就是矩阵乘法加激活函数。
`timescale 1ns/1ps
`define FILE_INPUT 1
`define PRINT_A_B_OUTPUT 1
module tb_mm_mlp();
reg signed [15:0] I_16_2d[0:15][0:15];
reg [16*16*16-1:0] I_16_1d;
reg signed [15:0] I0_16_2d[0:15][0:15];
reg signed [15:0] I1_16_2d[0:15][0:15];
reg signed [15:0] I2_16_2d[0:15][0:15];
reg signed [15:0] I3_16_2d[0:15][0:15];
reg signed [15:0] I4_16_2d[0:15][0:15];
reg signed [15:0] I5_16_2d[0:15][0:15];
reg signed [15:0] I6_16_2d[0:15][0:15];
reg signed [15:0] I7_16_2d[0:15][0:15];
reg [16*16*16-1:0] I0_16_1d;
reg [16*16*16-1:0] I1_16_1d;
reg [16*16*16-1:0] I2_16_1d;
reg [16*16*16-1:0] I3_16_1d;
reg [16*16*16-1:0] I4_16_1d;
reg [16*16*16-1:0] I5_16_1d;
reg [16*16*16-1:0] I6_16_1d;
reg [16*16*16-1:0] I7_16_1d;
reg signed [15:0] W0_2d[0:15][0:15];
reg signed [15:0] W1_2d[0:15][0:15];
reg signed [15:0] W2_2d[0:15][0:15];
reg signed [15:0] W3_2d[0:15][0:15];
reg signed

该文描述了一个Verilog项目,用于实现16x16矩阵乘法并进行低位截断和高位饱和处理,适用于深度学习中的多层感知机计算。文章详细阐述了矩阵元素的格式、截断规则以及实现过程,包括读取矩阵数据、实例化乘法模块和处理截断操作。
最低0.47元/天 解锁文章
160

被折叠的 条评论
为什么被折叠?



