この記事は ハードウェア開発、CPUアーキテクチャ Advent Calendar 2016 - Qiita の15日目の記事です。
Advent-Calendarを埋めてくれるかた、今からでも募集中です!是非参加してください! 僕一人では、クオリティのある記事を続けられそうにありません。。。(弱音)
ところで、RISC-V 5th Workshopの資料がやっとアップロードされました。今回は、その中でも一番気になっていた、RISC-V Vectore Extensionについて見ていきたいと思います。
1. RISC-V Vector Extensionの歴史
RISC-V のVector Extension v0.1
まず、このVector Extensionについては、今回の5th Workshopが初登場で無いことについて言及しておきたい。過去、2nd RISC-V Workshopにおいて、Vector Extensionの最初のProposalが行われている。
ちなみに、Vector ExtensionはSIMDとは別物と考えたほうが良いかもしれない。これは"V" Extensionだが、別に"P"拡張 Packed Extensionも存在する。
- Vector Extension Proposal
https://riscv.org/wp-content/uploads/2015/06/riscv-vector-workshop-june2015.pdf
次に、既にVector Extensionを実装したプロジェクトとして、Hwachaというプロジェクトがある。Hwachaとは、古代の武器のことで、槍のようなものを同時平行に数十本同時に放つ武器のことだ。 Vector処理ということで、同時平行に演算を実行することからこの名前を付けたらしい。 Google画像検索をすると、恐しい写真が大量に出てくる。
こちらにも、Vector Extensionに関する資料が大量に出てくるので、興味のある人は調査して欲しい。
- The Hwacha Vector-Fetch Architecture Manual, Version 3.8.1
https://www2.eecs.berkeley.edu/Pubs/TechRpts/2015/EECS-2015-262.pdf
- The Hwacha Microarchitecture Manual, Version 3.8.1
https://www2.eecs.berkeley.edu/Pubs/TechRpts/2015/EECS-2015-263.pdf
そして今回、5th WorkshopにてRISC-V Vector Extension Proposal v0.2が公開された。
- Vector Extension Proposal v0.2
https://riscv.org/wp-content/uploads/2016/12/Wed0930-RISC-V-Vectors-Asanovic-UC-Berkeley-SiFive.pdf
2. RISC-V Vector Extension仕様概要
まず、RISC-VのVector命令は、汎用レジスタ、浮動小数点レジスタ群と分離した専用のベクトルレジスタを持っている。いくつかのシステムレジスタ、さらに最大で8エントリ分のPredicateレジスタで構成されている。
ベクトルレジスタのサイズは、Maximum Vector Length(MVL)によって定義される。これはベクトルレジスタ毎に(おそらく)共通で、物理的に実装されているベクトルレジスタのサイズを表わしている。
(5th Workshop Vector Extension資料より抜粋)
これ、ちょっと面白いのだが、Vectorレジスタのサイズは、各レジスタエレメント毎に調整することができる。また、Predicateレジスタの量もコンフィギャラブルだ。最適なサイズを選択できるようなアーキテクチャにしているという訳だ(汎用プロセッサにこの機能がどれだけ必要なのかは分からないが...)
(5th Workshop Vector Extension資料より抜粋)
各ベクトルレジスタの設定
各ベクトルレジスタには、vcmaxwとvctypeが設定可能だ。それぞれ、
- vcmaxw 当該ベクトルレジスタで使用可能な最大エレメントサイズ(最大データタイプとでも言おうか)
- vctype 当該ベクトルレジスタで現在使用しているエレメントサイズ(現在のデータタイプとでも言おうか)
もちろん、vctypeはvcmaxwよりも大きな値を設定は出来ないだろうし、vcmaxwを設定すると、デフォルトで該当するデータサイズがvctypeに設定されるようだ。
では、このような設定を具体的にどのように行うのであろうか?資料には例が載っていた。
(5th Workshop Vector Extension資料より抜粋)
これをパッっと見ただけでは分かりにくいのだが、例えばRV32の場合を見てみると、どうやら若いベクトルレジスタアドレス順に、小さなデータタイプから設定可能なようだ。
- X32=1 であるため、v0-v1(0は初期値)までは、X32で使用する。
- F32=12 であるため、v2-v12まではF32で使用する。
- F64=18 であるため、v13-v18まではF64で使用する。
- v19以降は使用しない
と解釈できる。つまり、同一データタイプのレジスタをバラバラに配置することは出来ず、若い方から順に配置しなさい、ということか。
たとえば、vcfgdを下記の図のように指定した場合、ベクトルレジスタは下記のように取り扱われる。
3. 実際の演算例
32bit整数同士のVector加算
まず単純な、32bit整数の加算から。ここでは、Way数を4として図を書いてみた。
vcfgd 2*X32 # Only need two vector registers stripmine: vsetvl t0, a0 # a0 holds vector length vld v0, a1 # Get first vector vld v1, a2 # Get second vector vadd v1, v0 # Add vectors vst v1, a3 # Store result vector sll t1,t0,2 # Multiply count by 4 to get byte add a1, t1 # Bump pointers add a2, t1 add a3, t1 sub a0, t0 # Subtract number done bnez a0, stripmine # Any more? vuncfg # Turn off vector unit by zeroing config
vcfgd 2*X32
によって、v0-v1までをX32整数として利用することを宣言している。これにより、以下のようなレジスタ配置で演算されることが分かる。
この演算命令を見て分かるように、各命令には特に型が指定されない。どのような型でも、統一した宣言がなされる。
上記の例では、vld, vadd, vst
は全てX32の整数として宣言される訳だ。
16bit整数同士のVector加算
次に、16bit整数同士の演算ではどのようになるのか見てみる。
vcfgd 2*X16 # Only need two vector registers stripmine: vsetvl t0, a0 # a0 holds vector length vld v0, a1 # Get first vector vld v1, a2 # Get second vector vadd v1, v0 # Add vectors vst v1, a3 # Store result vector sll t1,t0,1 # Multiply count by 2 to get byte add a1, t1 # Bump pointers add a2, t1 add a3, t1 sub a0, t0 # Subtract number done bnez a0, stripmine # Any more? vuncfg # Turn off vector unit by zeroing config
このソースコードを見て、最初は以下のようなものを想像した。つまり、32bit整数で4-wayなら、16bit整数で8-wayになるというからくりだ。ただ良く考えるとこれは違う。さらに後述する異なる型同士の演算も辻褄が合わなくなる。
つまり、vcmaxwによって演算の最大型が決まるのであって、その中で実際にどの型を使うのかによってWay数が増減することは無い。そう考えるのが自然ではないか。そもそも、Way数はMVLで決まっちゃってるしね。
また、この場合もvadd, vld, vst
の演算粒度は、vcfgdの設定によって自動的に決まっている。
16bit整数と32bit整数のVector加算
じゃあ最後に、異なる型同士の加算はどのようになるのだろうか。
vcfgd 1*X32|1*X16 stripmine: vsetvl t0, a0 # a0 holds vector length vld v0, a1 # Get first 16-bit vector vld v1, a2 # Get second 32-bit vector vadd v1, v0 # Add vectors vst v1, a3 # Store result vector sll t1,t0,1 # Multiply count by 2 to get byte sll t2,t0,2 # Multiply count by 4 to get byte add a1, t1 # Bump pointers add a2, t2 add a3, t2 sub a0, t0 # Subtract number done bnez a0, stripmine # Any more? vuncfg # Turn off vector unit by zeroing config
下記のように考えるのが自然だろう。X16とX32の加算では、型変換や符号の拡張が必要になるため、おそらく混在の演算のバリエーションには制限がある。ただ一方で、簡単にそのような演算が出来るのならば良いのかもしれない(型変換が不要になる?)
4. 考察. 各型で命令名が同一なのは良いこと?
今回のVectorアーキテクチャでは、X16で演算しようがX32で演算しようが、F32だろうがF64だろうが、同一のニーモニックだ。どの型で演算するかは、vcfgdの設定に依存してしまう。
ただし、はたしてこれは命令の解析をする場合に、良いことなのだろうか?命令のシンボルを見ても、ぱっと見ではどの型の演算か分からない訳だ。これはデバッガビリティに重大な影響を与える気がする。
もちろん、命令空間を節約するためには良い方法だ。だが、コンパイル結果を解析したり、チューニングする場合に逆アセンブル結果を見て、任意の命令1つに限定出来ないのは、とても解析がやりづらいように思う。