defsend_data(self, data, block_name, offset='default'): """ 写入 input 或 weight 至 bram 假设两个矩阵分别是 (m,n) x (n,p), m 和 p 的维度需要补全至 self.systolic_size 的倍数, 并且写入时需要按照补零的方向写入,例如: 1. 矩阵 (m, n) 是 m 补零,则 m 个 m 个写入BRAM中。(行方向补零,列方向写入) 2. 矩阵 (n, p) 是 p 补零,则 p 个 p 个写入BRAM中。(列方向补零,行方向写入) Args: data: 要写入的数据 block_name: input, weight offset: 偏移地址名称,默认为default """ # padding if block_name == 'input': # M x N m, n = data.shape self.ori_m = m # padded m 是需要在 m 方向上补零的个数 self.paddedm = self.systolic_size * (m // self.systolic_size + 1) - m padd = np.zeros((self.paddedm, n), dtype=np.uint8) padded = np.append(data, padd, axis=0).T self.paddedm += m else: assert block_name == 'weight' # N x P n, p = data.shape self.ori_p = p self.paddedp = self.systolic_size * (p // self.systolic_size + 1) - p padd = np.zeros((n, self.paddedp), dtype=np.int8) padded = np.append(data, padd, axis=1) self.paddedp += p self.n = n self.bram.write(padded, block_name=block_name) pass
第二次是在 FM_reshape.v 和 WM_reshape.v 模块,如下代码
always @(posedge clk orposedge rst) begin if (rst) begin BRAM_FM64_wrdata <= 'b0; end elseif ((c_state==WORK)&&(cycle1_cnt[0]==1'b1)) begin BRAM_FM64_wrdata <= {BRAM_FM32_rddata,BRAM_FM64_wrdata[31:0]}; end elseif (c_state==WORK) begin BRAM_FM64_wrdata <= {32'b0,BRAM_FM32_rddata}; end elsebegin BRAM_FM64_wrdata <= BRAM_FM64_wrdata; end end
二、实验流程
2.1 仿真流程
本次实验所需要的工程示例已提供,所以不需要再改动 Verilog 源码了。
对于仿真流程,需要 Gen_matrix.py 产生输入矩阵,然后利用 MM_top_tb.v 作为 ARM 的模拟激励测试平台,然后测试即可,最后利用 compare.py 进行比对。
输出为
data right
2.2 上板流程
生成 bit 流后上板,需要用到 lab4 的 Matmul.py ,最后效果如图
思考题
1
有如下约束
M×N≤8×4096=32768N×P≤16×8192=131072
取等条件是 M % 8 == 0, P % 16 == 0 。软件 padding 是根据脉冲矩阵的大小进行的,所以是 4 补零,硬件补零是按照 8×16 的乘法单元进行补零的,为了取等,不能补零,所以是上面的条件。
if(i + 1 >= M) begin FM_reg1 = 0; FM_reg2 = 0; FM_reg3 = 0; end elseif(i + 2 >= M) begin FM_reg2 = 0; FM_reg3 = 0; end elseif(i + 2 >= M) begin FM_reg3 = 0; end feature[i][j] = FM_reg0; feature[i + 1][j] = FM_reg1; feature[i + 2][j] = FM_reg2; feature[i + 3][j] = FM_reg3; end end WM_reg_valid = 1'b0; WM_reg0 = 'b0; WM_reg1 = 'b0; WM_reg2 = 'b0; WM_reg3 = 'b0; wait(c_state==WRITE_WM); // 生成矩阵 weight for(i = 0; i < N; i = i + 1) begin for(j = 0; j < P; j = j + 4) begin @(posedge arm_clk) WM_reg_valid = 1'b1; {WM_reg0, WM_reg1, WM_reg2, WM_reg3} = $random;
if(j + 1 >= M) begin WM_reg1 = 0; WM_reg2 = 0; WM_reg3 = 0; endelseif(j + 2 >= M) begin WM_reg2 = 0; WM_reg3 = 0; endelseif(j + 2 >= M) begin WM_reg3 = 0; end weight[i][j] = WM_reg0; weight[i][j+1] = WM_reg1; weight[i][j+2] = WM_reg2; weight[i][j+3] = WM_reg3; end end r = 0; // 计算并对比 wait(n_state==FINISH); wait(r == M * P); arm_work = 1'b0; right = 1'b1; for(i = 0; i < M && right; i = i + 1) begin for(j = 0; j < P && right; j = j + 1) begin result_std = 0; for(k = 0; k < N; k = k + 1) begin result_std = result_std + $signed(weight[k][j]) * $signed({8'b0,feature[i][k]}); end if(result_std != result_sim[i*P + j]) begin right = 1'b0; end end end if (right) $display("passed test %dx%dx%d\n", M, N, P); else $display("notpass test %dx%dx%d\n", M, N, P); arm_work = 1'b1;
// 很简单的状态机 always @(posedge clk orposedge rst) begin if (rst) begin c_state <= IDLE; end elsebegin c_state <= n_state; end end
always @(*) begin case(c_state) IDLE: begin // 运算开始 if (submulti_start) n_state = INFO; else n_state = c_state; end INFO: begin n_state = WORK; end WORK: begin // 应该是数据搬运完了的意思 if (N_cnt == N - 1'b1) n_state = WAIT; else n_state = c_state; end WAIT: begin // 坍塌运算,应该是 align_fifo_get_all 都弄完了 if (&align_fifo_get_all) n_state = FINISH; else n_state = c_state; end // 重新回到空闲状态 FINISH: begin n_state = IDLE; end default: n_state = IDLE; endcase end
状态图:
寄存器功能如下表所示
寄存器
功能
sub_scale_M1
第 1 个矩阵乘法单元,结果矩阵的行数,输出给 Align_fifo,用于控制答案矩阵的有效值
sub_scale_P1
第 1 个矩阵乘法单元,结果矩阵的列数,输出给 Align_fifo,用于控制答案矩阵的有效值
sub_scale_M2
第 2 个矩阵乘法单元,结果矩阵的行数,输出给 Align_fifo,用于控制答案矩阵的有效值
sub_scale_P2
第 2 个矩阵乘法单元,结果矩阵的列数,输出给 Align_fifo,用于控制答案矩阵的有效值
9
Align_fifo 模块内结构如图所示
也就是说,Align_fifo 内部有 8 个 FIFO IP 核,用于存储 Multi_8x8 的输出。