FPGA開発日記

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

RISC-Vのオープンソースベクトル命令実装Araのデータパス実装を調べる (2. SIMD ALUモジュール)

RISC-Vのオープンソースベクトル実装のリファレンスとしてAraというものが公開されている。

github.com


AraのSIMD ALUモジュールについて。

simd_aluモジュール:

名前 方向 説明
operand_a_i elen_t I 入力オペランド1
operand_b_i elen_t I 入力オペランド2
valid_i logic I
vm_i logic I マスクが有効かを示す
mask_i strb_t I マスク
narrowing_select_i logic I narrowingが有効かを示す
op_i ara_op_e I 演算種類
vew_i vew_e I データサイズ
vxsat_o vxsat_t O 固定小数点の演算情報
rm strb_t I
vxrm_i vxrm_t I 固定小数点のまるめモード
result_o elen_t O 計算結果

elen_tの定義は以下。

  • ara_pkg.sv
  // Ara only supports vector elements up to 64 bits.
  localparam int unsigned ELEN  = 64;
  // Maximum size of a single vector element, in bytes.
  localparam int unsigned ELENB = ELEN / 8;

  typedef logic [ELEN-1:0] elen_t;

まずは入力データを、それぞれのデータタイプに対応できるように変換していく。

  • simd_alu.sv
   typedef union packed {
     logic [0:0][63:0] w64;
     logic [1:0][31:0] w32;
     logic [3:0][15:0] w16;
     logic [7:0][ 7:0] w8;
   } alu_operand_t;

   alu_operand_t opa, opb, res;
   assign opa      = operand_a_i;
   assign opb      = operand_b_i;
   assign result_o = res;

それでもって、各データ型に応じて演算を適用していく。この辺のデータパスの作り込みについてはどうすればいいのか迷っていたが、とりあえずこのようにひたすら並べていく安直な方法でもいいっぽい。

                   VADD, VADC, VMADC, VREDSUM, VWREDSUMU, VWREDSUM: unique case (vew_i)
             EW8: for (int b = 0; b < 8; b++) begin
                 automatic logic [ 8:0] sum = opa.w8 [b] + opb.w8 [b] +
                 logic'(op_i inside {VADC, VMADC} && mask_i[1*b] & ~vm_i);
                 res.w8[b] = (op_i == VMADC) ? {6'b0, 1'b1, sum[8]} : sum[7:0];
               end
             EW16: for (int b = 0; b < 4; b++) begin
                 automatic logic [16:0] sum = opa.w16[b] + opb.w16[b] +
                 logic'(op_i inside {VADC, VMADC} && mask_i[2*b] & ~vm_i);
                 res.w16[b] = (op_i == VMADC) ? {14'b0, 1'b1, sum[16]} : sum[15:0];
               end
             EW32: for (int b = 0; b < 2; b++) begin
                 automatic logic [32:0] sum = opa.w32[b] + opb.w32[b] +
                 logic'(op_i inside {VADC, VMADC} && mask_i[4*b] & ~vm_i);
                 res.w32[b] = (op_i == VMADC) ? {30'b0, 1'b1, sum[32]} : sum[31:0];
               end
             EW64: for (int b = 0; b < 1; b++) begin
                 automatic logic [64:0] sum = opa.w64[b] + opb.w64[b] +
                 logic'(op_i inside {VADC, VMADC} && mask_i[8*b] & ~vm_i);
                 res.w64[b] = (op_i == VMADC) ? {62'b0, 1'b1, sum[64]} : sum[63:0];
               end
           endcase

strb_tというのは、バイト単位のストローブ信号を示しており、マスクに使用している。

     localparam int    unsigned DataWidth    = $bits(elen_t),
     localparam int    unsigned StrbWidth    = DataWidth/8,
     localparam type            strb_t       = logic [StrbWidth-1:0]

マスクの使用については、とりあえず演算時にマスクに応じて何かをする必要はない。一部命令については必要なようだけども、それ以外は特に必要ないようだ。

               EW8: for (int b = 0; b < 8; b++) begin
                 automatic logic [ 8:0] sum = opa.w8 [b] + opb.w8 [b] +
                 logic'(op_i inside {VADC, VMADC} && mask_i[1*b] & ~vm_i);
                 res.w8[b] = (op_i == VMADC) ? {6'b0, 1'b1, sum[8]} : sum[7:0];
               end

マスク自体は外から与えられる。

  • valu.sv
simd_alu #(
     .FixPtSupport      (FixPtSupport                                                    )
   ) i_simd_alu (
     .operand_a_i       (alu_operand_a                                                   ),
     .operand_b_i       (alu_operand_b                                                   ),
     .valid_i           (valu_valid                                                      ),
     .vm_i              (vinsn_issue_q.vm                                                ),
     .mask_i            ((mask_valid_i && !vinsn_issue_q.vm) ? mask_i : {StrbWidth{1'b1}}),
     .narrowing_select_i(narrowing_select_q                                              ),
     .op_i              (vinsn_issue_q.op                                                ),
     .vew_i             (vinsn_issue_q.vtype.vsew                                        ),
     .vxsat_o           (alu_vxsat                                                       ),
     .vxrm_i            (alu_vxrm_i                                                      ),
     .rm                (r                                                               ),
     .result_o          (valu_result                                                     )
   );

マスクの情報は、Mask Unitから来ているようだった。これはレーンに関係なく1つモジュールがインスタンスされている。

   /////////////////
   //  Mask unit  //
   /////////////////

   masku #(
     .NrLanes(NrLanes),
     .vaddr_t(vaddr_t)
   ) i_masku (
     .clk_i                   (clk_i                           ),
     .rst_ni                  (rst_ni                          ),
     // Interface with the main sequencer
     .pe_req_i                (pe_req                          ),
     .pe_req_valid_i          (pe_req_valid                    ),
     .pe_vinsn_running_i      (pe_vinsn_running                ),
     .pe_req_ready_o          (pe_req_ready[NrLanes+OffsetMask]),
     .pe_resp_o               (pe_resp[NrLanes+OffsetMask]     ),
     .result_scalar_o         (result_scalar                   ),
     .result_scalar_valid_o   (result_scalar_valid             ),
     // Interface with the lanes
     .masku_operand_i         (masku_operand                   ),
     .masku_operand_valid_i   (masku_operand_valid             ),
     .masku_operand_ready_o   (masku_operand_ready_masku       ),
     .masku_result_req_o      (masku_result_req                ),
     .masku_result_addr_o     (masku_result_addr               ),
     .masku_result_id_o       (masku_result_id                 ),
     .masku_result_wdata_o    (masku_result_wdata              ),
     .masku_result_be_o       (masku_result_be                 ),
     .masku_result_gnt_i      (masku_result_gnt                ),
     .masku_result_final_gnt_i(masku_result_final_gnt          ),
     // Interface with the VFUs
     .mask_o                  (mask                            ),
     .mask_valid_o            (mask_valid                      ),
     .mask_valid_lane_o       (mask_valid_lane                 ),
     .lane_mask_ready_i       (lane_mask_ready                 ),
     .vldu_mask_ready_i       (vldu_mask_ready                 ),
     .vstu_mask_ready_i       (vstu_mask_ready                 ),
     .sldu_mask_ready_i       (sldu_mask_ready                 )
   );

ここまで来て、とりあえず全体構成を把握する必要があるのではないかという気がしてきたので一端そっちを見てみる。

ara
  ara_dispatcher i_dispatcher();
  ara_sequencer  i_sequencer();
  for (genvar lane = 0; lane < NrLanes; lane++) begin: gen_lanes
    lane i_lane();
      spill_register       i_mask_ready_spill_register();
      lane_sequencer       i_lane_sequencer();
      operand_requestor    i_operand_requestor();
      vector_regfile       i_vrf();
      operand_queues_stage i_operand_queues();
      vector_fus_stage     i_vfus();
        valu  i_valu();
           stream_register i_mask_operand_register();
           spill_register  i_alu_reduction_spill_register();
           simd_alu        i_simd_alu();

        vmfpu i_vmfpu();
        
  end endgenerate
  vlsu i_vlsu();
  sldu i_sldu();
  masku i_masku();