FPGA開発日記

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

RISC-V Vector Extension v0.9のCSR仕様1

RISC-Vのベクトル拡張の理解に当たり、複雑怪奇なシステムレジスタを理解するのは大変だ。ここではRISC-Vベクトル拡張が備える謎のシステムレジスタについて一気に解説していきたい。

ちなみに最新のRISC-V Vector Extension 0.9をベースに解説している。


RISC-V Vector Extension 0.9では以下に示すCSRを新たに定義している。

f:id:msyksphinz:20200710010429p:plain

Vector Extensionを理解するにあたりとりあえず無視していいのが、"vxsat", "vxrm", "vcsr"である。これらのCSRはFixed Point命令のためのレジスタなので本質にあまり関係しない。

そしていくつかVector Extension固有の定数について理解しておこう。以下に示す数値は「実装に固有の数値」であり、ハードウェアにより定義されるものだ。これらは命令の実行中に変更することはできない。

  • VLEN : ベクトルレジスタ1本が持つビット長。例えばベクトルレジスタ1本が512ビットであれば、VLEN=512である。
  • ELEN : ベクトルレジスタ内の1つの要素を示す最大のビット数。例えばベクトルレジスタで最大64ビットのデータを1要素として扱うことができるとすると、ELEN=64となる。当然、ELENはXLEN or FLENよりも大きな数値である必要がある。
  • SLEN : ベクトルレジスタ内のインデックス参照方法をストリップして表現することができる。これは複雑なので必要になれば理解すれば良い。

これを踏まえ、システムレジスタでまず理解しなければならないのはvtype CSRだ。このCSRはいろんなフィールドを持っており、理解するのが難しい。

f:id:msyksphinz:20200710010622p:plain

vsewから始める。これは現在のベクトルレジスタの1要素を示すビット長だ。RISC-V Vector Extensionでは、1命令で複数のデータ長を扱う。例えば32ビットのベクトル加算も、64ビットのベクトル加算も両方ともvadd.vvで表現されるため、今自分がどのビット長で計算しているのかが分からない。このためvtype.vsewで現在実行中のデータ長を把握する。

f:id:msyksphinz:20200710011250p:plain

もう一つ、vlmulというフィールドがある。これは、1つのレジスタインデックスで複数のベクトルレジスタを取り扱うことができるようになる。vlmulの値に伴いLMULというパラメータがエンコードされ、一度に何個のレジスタを扱うかを指定できる。大量のデータを扱い、同じ操作を複数のレジスタに対して実行する必要があるときに便利だ。

f:id:msyksphinz:20200710011539p:plain

具体的には、通常はvtype.vlmul=0(LMUL=1)の時、以下の命令は、v8レジスタとv16レジスタを読み取り、加算を行いその結果をv0レジスタに格納する。

vadd.vv v0, v8, v16   # v0 <= v8 + v16

ところが、vtype.vlmul=1(LMUL=2)とすると、vNレジスタ(Nは2の倍数でなければならない、それ以外は命令例外が発生する)とvN+1レジスタに対して操作が適用される。つまり上記のvadd.vv命令は、

vadd.vv v0, v8, v16  # {v1, v2} <= {v9,v8} + {v17,v16}

となり、1命令で複数のレジスタを扱うことができる。命令フェッチ幅を抑えるための巨大なベクトルレジスタのような使い方ができるようになる。これは最大でLMUL=8まで指定することができ、最大でvN~vN+7のレジスタを同時に扱うことができるようになる。

これを踏まえ、現在ベクトル命令が実行されると何個の要素が1つのレジスタとして扱うことができるようになるのだろうか?これを示しているのがvlレジスタである。

vlレジスタの値は:「VLEN / SEW * LMUL」で表現される。SEWvtype.sewをエンコードした値、LMULvtype.vlmulをエンコードした値である。

具体例を見る。VLEN=512SEW=64LMUL=1である場合、vl = 512 / 64 * 1 = 8であり、64ビットの値が8個まとまったものが1つのベクトルレジスタに集約されている。これは簡単。

では次に、VLEN=512SEW=32LMUL=1である場合、vl = 512 / 32 * 1 = 16となり、32ビットの値が16個まとまったものが1つのベクトルレジスタに集約されていると考えられる。つまり、SEWLMULの値によってvlの値は逐次変化していく!

さらにややこしいことに、VLEN=512SEW=64LMUL=4とすると、vl=512 / 64* 4 = 32となり、1つのレジスタインデックスを参照することで32個の要素に対する処理が行われることを意味する。このように、コンフィグレーションによりレジスタの定義範囲が大きく変わるので、現在の状態が把握しにくいのがRISC-V Vector Extensionの特徴である。

f:id:msyksphinz:20200710192203p:plain

このシステムレジスタの設定方法だが、多くの場合はvsetvli命令によって設定される。vsetvliの命令仕様についてはあまり詳細は説明しないが、Spikeの実装を参考にするのが良いと思う。

 vsetvli rd, rs1, vtypei # rd = new vl, rs1 = AVL, vtypei = new vtype setting 
 vsetvl  rd, rs1, rs2    # rd = new vl, rs1 = AVL, rs2 = new vtype value
f:id:msyksphinz:20200711003245p:plain
   // set vl
   if (vlmax == 0) {
     vl = 0;
   } else if (rd == 0 && rs1 == 0) {
     vl = vl > vlmax ? vlmax : vl;
   } else if (rd != 0 && rs1 == 0) {
     vl = vlmax;
   } else if (rs1 != 0) {
     vl = reqVL > vlmax ? vlmax : reqVL;
   }