FPGA開発日記

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

RISC-Vベクトル拡張におけるロードストア命令のグロい仕様

RISC-Vベクトル拡張命令は非常に複雑なことで有名だが、命令を読み込んでISSの実装、RTLの実装をしているなかで非常に仕様として分かりにくい、複雑な命令があるのでメモしておく。

github.com

ロードストア命令には、大きく分けて4つのアドレッシングモードがある。

  • ユニットストライドアドレッシング(Unit Stride): 単純に各要素のアドレスがメモリ上で隣接している
  • ストライドアドレッシング(Strided) : ストライドのアドレスが汎用レジスタに格納されており、各要素は線形に飛び飛びになっている
  • オーダー付きインデックスアドレッシング(Index Ordered): 各要素のアドレスは汎用レジスタ+ベクトルレジスタの各要素となり、各要素のアドレスは完全にランダムとなる。デバイス向けアクセスなど、安全にアクセスされることを保証しなければならない。
  • アンオーダーインデックスアドレッシング(Index Unordered)s: 各要素のアドレスは汎用レジスタ+ベクトルレジスタの各要素となり、各要素のアドレスは完全にランダムとなる

ちなみにこれだけではなくて、ベクトルメモリアクセスには"Segmented"と呼ばれるアドレッシングモードもあるので、話はさらにややこしくなる。 代表的なこれらの命令を並べてみるとこんな感じだ。<EEW>の部分には8,16,32,64などのアクセスサイズが入る。 <2-8>の部分は1命令で同時にアップデートできるレジスタの数を指す(8つのベクトルレジスタを同時にアップデートできる命令が存在しているなんてとてもRISCとは思いたくないが...)

Non-segment Segment
Unit-Stride VLE.v VLSEG<2-8>E.v
Strided VLSE.v VLSSEG<2-8>E.v
Index Ordered VLOXEI.v VLOXSEG<2-8>EI.v
Index Unordered VLUXEI.v VLOXSEG<2-8>EI.v

上記を見ればわかる通り、普通RISC-Vのベクトル拡張命令はvtypeレジスタに格納されているデータ幅の単位に従って演算が行われるが、ロードストア命令はそうではない。ロードストア命令はそれぞれ、

  • VLE8.v (8ビットデータアクセス)
  • VLE16.v (16ビットデータアクセス)
  • VLE32.v (32ビットデータアクセス)

などが定義されており、vtypeレジスタの値を切り替えなくても任意のサイズのデータにアクセス可能である。 逆に言うとvtypeの設定は無視されるということになる。これが後々非常に厄介ということになるのだが... ちなみにベクトル長は変わらないので8/16/32の違いは何だと思われるかもしれないが、これはアドレスアライン例外の発生条件が異なる。

  • VLUXEI命令とVLOXEI命令の違い

VLUXEI命令は"Vector Load with Unordered with Indexed EI"と考えることができ、ベクトルレジスタにロードされるデータのアドレスは、ベースレジスタアドレス+ベクトルレジスタvs2の各要素とすることができる。 上記ではベクトルメモリアクセス命令はvtypeを無視するといったが、実はこのVLUXEI, VLOXEI(そして同じ種類のストア命令)はvtype.sewを使用する。この辺からとんでもなく分かりにくくなる。 つまり、これらのインデックスメモリアクセス命令は、アクセスサイズはvtype.sew, インデックスのサイズは<EEW>から切り取られる。

通常の命令がVLE<EEW>としてEEWの前に"E"という接頭語が付くのに対して、VLUXEI<EEW>EEWの前にEIという接頭語が付くのは、まさしくこの違いを表現している。

疑似コードにしてみると以下のような感じになるだろうか。このときにさらにややこしくなるのが、書き込みレジスタVPR[vs3]は(vtype.sew=16なので)16ビット毎に処理するのに対して、VPR[vs2]は(VLUXEI32なので) 32ビット毎に進められることだ。つまり、半分までの値をロードするとVPR[vs2]の値がなくなってしまう。そこで、VPR[vs2+1]の値をさらに読みながらインデックスを生成し続けることになる。

// VLUXEI32.v の場合 (vtype.sew=16)
for (i = 0; VLEN/16; i++) {
  VA = GPR[rs1] + VPR[vs2][i * 32 +: 32];
  PA = MMU(VA);
  VPR[vs3][i * 16 +: 16] = MEM[PA];
}

VLEN=128のときに具体的なアドレス生成方法を示す。この表から分かる通り、VPR[v10]の後半は、インデックスレジスタとしてVPR[v24]ではなくVPR[v25]を使う必要があり、アドレス生成がさらにややこしいものとなっている。

  • VLUXEI32.v v10, x10, v24 (vtype.sew=16, VLEN=128)
Vector Register Write Memory Address
VPR[v10][ 15: 0] GPR[x10] + VPR[v24][ 31: 0]
VPR[v10][ 31: 16] GPR[x10] + VPR[v24][ 63: 32]
VPR[v10][ 47: 32] GPR[x10] + VPR[v24][ 95: 64]
VPR[v10][ 63: 48] GPR[x10] + VPR[v24][127: 96]
VPR[v10][ 79: 64] GPR[x10] + VPR[v25][ 31: 0]
VPR[v10][ 94: 80] GPR[x10] + VPR[v25][ 63: 32]
VPR[v10][111: 96] GPR[x10] + VPR[v25][ 95: 64]
VPR[v10][127:112] GPR[x10] + VPR[v25][127: 96]