FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

自作CPUにベクトル命令を追加する実装検討 (8. 算術演算命令のオペランド拡充)

自作CPUにおいて、ある程度ベクトル移動命令が動作するようになってきた。

ベクトル命令のオペランドは、ベクトル・レジスタ、整数レジスタ、浮動小数点レジスタ、即値が選択できる。 これをどのように制御するかについて考えた。

  scariv_vec_alu_datapath
  u_vec_alu_datapath
    (
     .i_op        (r_ex1_pipe_ctrl.op                            ),
     .i_sew       (r_ex1_issue.vlvtype.vtype.vsew                ),
     .i_vs1       (r_ex1_vpr_rs_data[0][d_idx*64 +: 64]          ),
     .i_rs1_valid (r_ex1_issue.rd_regs[0].typ != scariv_pkg::VPR ),
     .i_rs1       (r_ex1_rs1_data                                ),
     .i_vs2       (r_ex1_vpr_rs_data[1][d_idx*64 +: 64]          ),
     .i_wr_old    (r_ex1_vpr_wr_old_data[d_idx*64 +: 64]         ),
     .i_en_mask   (w_ex1_en_mask                                 ),
     .i_v0        ('h0                                           ),
     .o_res       (w_ex1_vec_result [d_idx*64 +: 64]             )
     );

整数レジスタと浮動小数点の選択は、読み込みレジスタの違いで制御している。

    r_ex1_rs1_data <= i_ex0_issue.rd_regs[0].valid & (i_ex0_issue.rd_regs[0].typ == scariv_pkg::FPR) ? ex0_fpr_regread_rs1.data :
                      i_ex0_issue.rd_regs[0].valid & (i_ex0_issue.rd_regs[0].typ == scariv_pkg::GPR) ? ex0_xpr_regread_rs1.data :
                      i_ex0_issue.inst[19:15];

汎用レジスタを使用するときは、オペランドはベクトルの各レーンに展開されるので、事前に展開する。

assign w_vs1    = i_rs1_valid ? w_rs1 : i_vs1;
assign w_vs2    = i_vs2;
assign w_wr_old = i_wr_old;

always_comb begin
  unique case (i_sew)
    scariv_vec_pkg::EW8 : for (int b = 0; b < 8; b++) w_rs1.w8 [b] = i_rs1[ 7: 0];
    scariv_vec_pkg::EW16: for (int b = 0; b < 4; b++) w_rs1.w16[b] = i_rs1[15: 0];
    scariv_vec_pkg::EW32: for (int b = 0; b < 2; b++) w_rs1.w32[b] = i_rs1[31: 0];
    scariv_vec_pkg::EW64: for (int b = 0; b < 1; b++) w_rs1.w64[b] = i_rs1[63: 0];
    default             :                             w_rs1 = 'h0;
  endcase // unique case (i_sew)
end

演算自体は以下のように定義する。

    OP_ADD : begin
      unique case (i_sew)
        scariv_vec_pkg::EW8 : for (int b = 0; b < 8; b++) w_res.w8 [b] = w_vs1.w8 [b] + w_vs2.w8 [b];
        scariv_vec_pkg::EW16: for (int b = 0; b < 4; b++) w_res.w16[b] = w_vs1.w16[b] + w_vs2.w16[b];
        scariv_vec_pkg::EW32: for (int b = 0; b < 2; b++) w_res.w32[b] = w_vs1.w32[b] + w_vs2.w32[b];
        scariv_vec_pkg::EW64: for (int b = 0; b < 1; b++) w_res.w64[b] = w_vs1.w64[b] + w_vs2.w64[b];
        default             :                             w_res = 'h0;
      endcase // unique case (i_sew)
    end

最後に、マスクを適用する。

always_comb begin
  unique case (i_sew)
    scariv_vec_pkg::EW8 : for (int b = 0; b < 8; b++) w_masked_res.w8 [b] = i_en_mask[b] ? w_res.w8 [b] : w_wr_old.w8 [b];
    scariv_vec_pkg::EW16: for (int b = 0; b < 4; b++) w_masked_res.w16[b] = i_en_mask[b] ? w_res.w16[b] : w_wr_old.w16[b];
    scariv_vec_pkg::EW32: for (int b = 0; b < 2; b++) w_masked_res.w32[b] = i_en_mask[b] ? w_res.w32[b] : w_wr_old.w32[b];
    scariv_vec_pkg::EW64: for (int b = 0; b < 1; b++) w_masked_res.w64[b] = i_en_mask[b] ? w_res.w64[b] : w_wr_old.w64[b];
    default             :                             w_masked_res = 'h0;
  endcase // unique case (i_sew)
end

これで、一応基本的な動作をするベクトル算術演算命令が実行できるようになった。

 1 / 29 : vfmv_v_f  : PASS
 2 / 29 : vmv_v_i   : PASS
 3 / 29 : vand_v_v  : PASS
 4 / 29 : vmin_v_v  : PASS
 5 / 29 : vmv_v_x   : PASS
 6 / 29 : vsub_v_x  : PASS
 7 / 29 : vadd_v_v  : PASS
 8 / 29 : vmv_v_v   : PASS
 9 / 29 : vmaxu_v_v : PASS
10 / 29 : vmax_v_v  : PASS
11 / 29 : vadd_v_x  : PASS
12 / 29 : vor_v_v   : PASS
13 / 29 : vminu_v_v : PASS
14 / 29 : vxor_v_v  : PASS
15 / 29 : vsetvli01 : PASS
16 / 29 : vsub_v_v  : PASS
17 / 29 : vrsub_v_x : PASS
18 / 29 : vor_v_x   : PASS
19 / 29 : vxor_v_i  : PASS
20 / 29 : vmaxu_v_x : PASS
21 / 29 : vand_v_x  : PASS
22 / 29 : vadd_v_i  : PASS
23 / 29 : vor_v_i   : PASS
24 / 29 : vmax_v_x  : PASS
25 / 29 : vmin_v_x  : PASS
26 / 29 : vminu_v_x : PASS
27 / 29 : vxor_v_x  : PASS
28 / 29 : vrsub_v_i : PASS
29 / 29 : vand_v_i  : PASS