自作命令セットシミュレータの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ビットが用意される。
従って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_data
もint32_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バイトロードして欲しいということを意味している。これをvsetvli
でvl
レジスタに設定する。これにより次のマスクデータロードでは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.t
とvse8.v v0.t
を使用する。これでマスクされた場所をスキップしつつmemcpyができるプログラムの完成だ。スカラ版とのテスト比較も上手く行った。