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パイプラインの最終段で必要な情報のみを削り取る。