FPGA開発日記

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

RISC-V Vector命令をサポートした自作命令セットシミュレータの実装検討 (5. ベクトルマスクレジスタ用のテスト作成)

自作命令セットシミュレータのRISC-V Vector Extensionサポート、基本的なベクトルロードストア命令の実装が終わった。

テストを追加したいのだが、一応マスクレジスタのテストをしておきたい。Vector Extensionのマスクについてここでまとめておいて、シミュレータの実装に追加していこう。

RISC-V Vector Extensionではv0レジスタをマスクレジスタとして取り扱うことができる。これは命令中の1ビットフィールドを使用して指定され、vm=0ならばマスク適用、vm=1ならばマスクは無視されるという仕様になっている。このvmビットは命令中の25ビット目に配置されている。

vadd.vv v10,v11,v12,v0.t   # inst.vm=0, マスク適用
vadd.vv v10,v11,v12        # inst.vm=1,マスク不適用

このビットマスクの配置だが、通常のレジスタ要素の配置とは異なっている。例えばSEW=8で各ベクトルレジスタの要素サイズが8ビットとなっている場合でも、v0レジスタのマスクのビット配置は常に1要素につき1ビットが用意される。

f:id:msyksphinz:20200719004912p:plain

従ってISSのマスク実装も以下のようになっている。

  • swimmer_riscv/src/riscv_pe_vec_thread.cpp
template <class T>
void RiscvPeThread::MemStoreUnitStride(const Addr_t mem_base_addr, const RegAddr_t vs3_addr,
                                       bool vm, T type) {
  const int DWIDTH = sizeof(T) * 8;
  Word_t vl; CSRRead (static_cast<Addr_t>(SYSREG_ADDR_VL), &vl);
  Word_t vstart; CSRRead (static_cast<Addr_t>(SYSREG_ADDR_VSTART), &vstart);
  for (int i = vstart; i < vl; i++) {
    if (vm == 0) {
      const int midx = i / DWIDTH;    // マスクビットの要素位置を計算
      const int mpos = i % DWIDTH;    // マスクビットのビット位置を計算
      bool skip = ((ReadVReg<T>(0, midx) >> mpos) & 0x1) == 0;
      if (skip) {
        continue;
      }
    }
    T store_data = ReadVReg<T> (vs3_addr, i);
...

このマスクビットを使ってテストプログラムを作成してみる。作成するのは同じmemcpyであるが、マスクビット列を受け取ってビット位置が有効な要素のみコピーするというものだ。

int32_t mask_data[DATA_NUM/32] = {
  0x01234567, 0x89abcdef, 0x11112222, 0x33334444,
  0x55556666, 0x77778888, 0x9999aaaa, 0xbbbbcccc
};


int32_t source_data[DATA_NUM] = {
   845150577,  -965358962,  -733826200,   224278040,
 -1657493282,  1884651197, -1369202253,  -379841765,
   767247286,  1718024944,   759098771, -1236415500,
   460355761,  1171477725,  -346432824,  -303215321,
   565714941,   624152115,  -344452799, -1013465950,
...

mask_bataは一応int32_t型としているが、1ビットが1要素に当てはまるような構成にしており、またsource_dataint32_t型としているが実際には8ビットずつ分解して8ビット毎のコピーを行う構成とする。

これを用いてマスク付きコピーを行うテストプログラムを考えよう。

    .global     copy_data_mask_vec
# void copy_data_mask_vec(int8_t *dest_data, int8_t *source_data, int8_t *mask, int data_num);
# a0=dest, a1=src, a2=n
#
copy_data_mask_vec:
    li      t1, 8           # calculate element length of mask, VLEN=512 / ELEN=8 / 8-byte
_loop:
    vsetvli t0, t1, e8,m1   # Vectors of 8b
    vle8.v  v0, (a2)
    add     a2, a2, t0

まず、vsetvliの入力レジスタに8を設定している。これは現在のISSの設定でVLEN=512としており、8ビットの要素を扱うため1ベクトルレジスタで64要素、64要素ということはマスクレジスタで言えば64要素/8ビットを使用するため、8バイトロードして欲しいということを意味している。これをvsetvlivlレジスタに設定する。これにより次のマスクデータロードでは8バイト分がロードされる。そして8バイト分だけマスクレジスタ用のポインタを進める。

    vsetvli t0, a3, e8,m1   # Vectors of 8b
    vle8.v  v1, (a1), v0.t  # Load bytes
    add     a1, a1, t0      # Bump pointer
    sub     a3, a3, t0      # Decrement count
    vse8.v  v1, (a0), v0.t  # Store bytes
    add     a0, a0, t0      # Bump pointer
    bnez    a3, _loop      # Any more?
    ret                  # Return

そしてmemcpyの実体部分だが、もうマスクレジスタは想定通りのビットレイアウトで配置されているため特に何も考えずvle8.v v0.tvse8.v v0.tを使用する。これでマスクされた場所をスキップしつつmemcpyができるプログラムの完成だ。スカラ版とのテスト比較も上手く行った。