FPGA開発日記

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

自作CPUのベクトルLSUパイプラインのまとめ(2. マスクの作り方)

RISC-Vのベクトル・パイプラインには、実際には演算が適用されない領域がある。それが、

  • Prestart Region
  • Masked Element
  • Tail Region

というものだ。PrestartはVSTARTよりも小さな値(要するに、ベクトル命令がリスタートするときの最初の要素) Masked Elementはマスクがかけられる位置。Tail Regionは最終処理要素の位置になる。

VSTARTはまだ未対応なので、Masked ElementとTail Regionの作り方について。 マスクはv0レジスタの値を使用する。厄介なのは、要素のデータ幅が異なるときに参照するv0レジスタのビット位置が違うこと。つまり、

  • EW32: データ0(data[31: 0]), データ1(data[63:32]), データ2(data[95:64]) ...
  • EW64: データ0(data[63: 0]), データ1(data[127:64]), データ3(data[191:128]) ...

というようにデータが並んだ時、常にデータ0はv0[0], データ1はv0[1]... を参照することになる。データ幅が異なると、一度に処理するデータ数も異なるため、参照するv0の場所も異なる。

以下のようなVerilogコードを用意している。割と強引だし、乗算の演算とか使っているけれども、オペランドの片方は固定値なのでなんとか最適化されてほしい。 要するに処理するデータの方に応じてビット位置を細かく切り替えている。

vec_lmul_indexというのは、LMUL>1の時に何番目のレジスタを扱うための命令なのか、vec_step_indexというのは、VLEN>DLENのときに、1命令の中でレジスタのどの部分を処理しているのかを表現するためのインデックスである。

 scariv_vec_pkg::vlenbmax_t w_ex1_vl_ew8_start;
 scariv_vec_pkg::vlenbmax_t w_ex1_vl_ew16_start;
 scariv_vec_pkg::vlenbmax_t w_ex1_vl_ew32_start;
 scariv_vec_pkg::vlenbmax_t w_ex1_vl_ew64_start;

 assign w_ex1_vl_ew8_start  = r_ex1_issue.vec_lmul_index * (riscv_vec_conf_pkg::VLEN_W / 8) + r_ex1_issue.vec_step_index * (riscv_vec_conf_pkg::DLEN_W /  8);
 assign w_ex1_vl_ew16_start = r_ex1_issue.vec_lmul_index * (riscv_vec_conf_pkg::VLEN_W /16) + r_ex1_issue.vec_step_index * (riscv_vec_conf_pkg::DLEN_W / 16);
 assign w_ex1_vl_ew32_start = r_ex1_issue.vec_lmul_index * (riscv_vec_conf_pkg::VLEN_W /32) + r_ex1_issue.vec_step_index * (riscv_vec_conf_pkg::DLEN_W / 32);
 assign w_ex1_vl_ew64_start = r_ex1_issue.vec_lmul_index * (riscv_vec_conf_pkg::VLEN_W /64) + r_ex1_issue.vec_step_index * (riscv_vec_conf_pkg::DLEN_W / 64);

 logic [riscv_vec_conf_pkg::DLEN_W/ 8-1: 0]     w_ex1_v0_mask;

 always_comb begin
   w_ex1_v0_valid = ~r_ex1_issue.inst[25];
   unique case (r_ex1_issue.vlvtype.vtype.vsew)
     scariv_vec_pkg::EW8 : w_ex1_v0_mask = w_ex1_vpr_v0_data[w_ex1_vl_ew8_start  +: riscv_vec_conf_pkg::DLEN_W/ 8];
     scariv_vec_pkg::EW16: w_ex1_v0_mask = w_ex1_vpr_v0_data[w_ex1_vl_ew16_start +: riscv_vec_conf_pkg::DLEN_W/16];
     scariv_vec_pkg::EW32: w_ex1_v0_mask = w_ex1_vpr_v0_data[w_ex1_vl_ew32_start +: riscv_vec_conf_pkg::DLEN_W/32];
     scariv_vec_pkg::EW64: w_ex1_v0_mask = w_ex1_vpr_v0_data[w_ex1_vl_ew64_start +: riscv_vec_conf_pkg::DLEN_W/64];
     default             : w_ex1_v0_mask = 'h0;
   endcase // unique case (i_sew)                                                                                                                                                                                                                                                                                                                                                         
 end // always_comb                                                                                                                                                                                                                                                                                                                                                                       

Tailの生成方法はもっとややこしい。VLMAXの値と比較する必要がある。この計算式も合っているのか若干自信がない。

 always_comb begin
   unique case (r_ex1_issue.vlvtype.vtype.vsew)
     scariv_vec_pkg::EW32: begin
       w_ex1_temp_vl         = min(r_ex1_issue.vlvtype.vl > w_ex1_vl_ew32 ? r_ex1_issue.vlvtype.vl - w_ex1_vl_ew32 : 0, riscv_vec_conf_pkg::DLEN_W / 32);
       w_ex1_fpnew_simd_mask = r_ex1_issue.vlvtype.vl > w_ex1_vl_ew32 + riscv_vec_conf_pkg::DLEN_W / 32 ? {(riscv_vec_conf_pkg::DLEN_W / 32){1'b1}} : (1 << w_ex1_temp_vl) - 1;
     end
     scariv_vec_pkg::EW64: begin
       w_ex1_temp_vl         = min(r_ex1_issue.vlvtype.vl > w_ex1_vl_ew64 ? r_ex1_issue.vlvtype.vl - w_ex1_vl_ew64 : 0, riscv_vec_conf_pkg::DLEN_W / 64);
       w_ex1_fpnew_simd_mask = r_ex1_issue.vlvtype.vl > w_ex1_vl_ew64 + riscv_vec_conf_pkg::DLEN_W / 64 ? {(riscv_vec_conf_pkg::DLEN_W / 64){1'b1}} : (1 << w_ex1_temp_vl) - 1;
     end
     default             : begin
       w_ex1_temp_vl         = 0;
       w_ex1_fpnew_simd_mask = 'h0;
     end
   endcase // unique case (i_sew)                                                                                                                                                                                                                                                                                                                                                         
 end // always_comb                                                                                                                                                                                                                                                                                                                                                                       

最終的にこれらのマスク値を使って、SIMDパイプラインの最終段で必要な情報のみを削り取る。