Raspberry PiのGPUで数値計算: (2)QPUの特徴

# Raspberry PiのGPUで数値計算: (2)QPUの特徴

http://qiita.com/Terminus-IMRC/items/7406fa835d6510790406 の続きです．

## QPUコア

前回説明した通り，QPUはVideoCore IV内に存在するプロセッサです．`/boot/config.txt`で指定された`gpu_freq`の周波数で動きます．デフォルトではRaspberry Pi 1，2では250MHz，Raspberry Pi 3では400MHzです．QPUは物理的に`3*4=12`基あり，その中に物理的に4コアあり，それぞれのコアが4クロックで4コアをエミュレートします．すなわち，QPU全体をみると`gpu_freq/4=62.5または100`MHzで動作する仮想的なコアが`12*4*4=192`個存在することになります．後述の通り，ALUが1命令で32ビットの加算と乗算を同時に行えるので，`192*2*62.5=24000`または`192*2*38400`となり，Raspberry Pi 1，2のQPUの性能は24Gflop/s，Raspberry Pi 3のQPUの性能は38.4Gflop/sとなります．

以下，便宜上QPUの仮想的なコアを単に「コア」と称します．

プログラムはQPU1基単位で行います．すなわち，QPUにプログラム実行のリクエストを投げると16コアで実行が始まります．


## QPUの命令とレジスタ

QPUの1命令は64ビットです．QPUはTMUやVPMやuniform(すべて後述)等の外部のハードウェアに触らない限り，1クロックで1命令実行することができます．命令キャッシュミスによるストールは (多分．私の実験によると命令キャッシュフェッチは他のフェッチよりも優先されるようです) ありません．これは同命令を実行するコアを複数クロックでエミュレートすることの恩恵でしょう．

QPUのALUは32ビットずつ扱います．整数も浮動小数点数も32ビットです．

命令にはおおまかにALUとload immediateとbranchとセマフォがあります．
ALU命令では計算ができます．
load immediate命令では32ビットの即値を代入することができます．
branch命令ではALU命令やload immediate命令によってセットされたフラグに応じてジャンプすることができます．delay slot ( https://ja.wikipedia.org/wiki/%E9%81%85%E5%BB%B6%E3%82%B9%E3%83%AD%E3%83%83%E3%83%88 ) が3つあるのでbranch命令の直後に入れる命令には注意してください．現在の命令アドレスを保存することができるので仮想的な関数呼び出し等も可能でしょう．
セマフォ命令ではセマフォを操作することができます．セマフォはQPU全体で共通で16個あります．

QPUの1コアにはadd ALUとmul ALUがあり，1命令中で両方を同時に使うことができます．
add ALUではfadd, fsub, fmin, fmax, fminabs, fmaxabs, ftoi, itof, add, sub, shr, asr, ror, shl, min, max, and, or, xor, not, clz, v8adds, v8subsをすることができます．
mul ALUではfmul, mul24, v8muld, v8min, v8max, v8adds, v8subsをすることができます．
mul24は符号を除いて最大24ビットの符号付き整数を入力として受けることができ，結果は32ビット符号付き整数で返されます．v8系の命令は32ビットを4分割した8x4ビットをminifloat ( https://en.wikipedia.org/wiki/Minifloat ) とみなして計算します．clzを遅延なしで実行できるの結構すごくないですか…? ちなみにIntelのHaswellのLZCNT命令のレイテンシは3です．

QPUのレジスタには距離の近いアキュムレータと遠いレジスタがあります．アキュムレータはr0-r3の4つと特殊なr4，r5からなります．r4はQPUからは読み込み専用で外部のハードウェアからの値を読みこんだりするのに使われます．r5はあるコアが持っている値を他のコアにコピーしたりするのに使われます．レジスタはra0-ra31，rb0-rb31の64個があります．数字が同じra，rb (たとえばra15とrb15) はregister file a，register file bと呼ばれ，アドレスは同じですが，読み込み・書き込み方法によりアクセス先が変わります．レジスタに書き込んだら，次の命令ではそのレジスタから読み込むことはできません．

## QPUのメモリI/Oインターフェイス

QPUのメモリI/Oインターフェイスは複数あります．表にまとめます．

|インターフェイス|読み込み|書き込み|値の種類|
|:--:|:--:|:--:|:--:|
|Uniform|o|x|スカラ|
|TMU|o|x|ベクトル/スカラ|
|VPM|o|o|ベクトル/スカラ|

Uniformはプログラム実行時に初期化され，CPU-QPU間で値をやりとりするのに重要なインターフェイスです．内部にアドレスを保持しており，uniform_readレジスタを読むとそのアドレスで示されている32ビットの値 (スカラ) を全コアに対して配布します．内部のアドレスは自動的にインクリメントされます．QPUのプログラムからuniforms_addressに書き込むと内部のアドレスをセットすることができます．マニュアルにはuniforms_addressに書き込んだあとの2命令ではuniformを読んではいけないことになっていますが，実際は **他のI/Oが混み合ってない限り** 読んでも問題ないようです．

TMU (texture memory unit) はdirect addressing方式と2D方式と3D方式があります．direct addressing方式ではメモリアドレスを書き込んでシグナルを送るとアドレスで示されている値がr4に書き込まれます．2D，3D方式はメモリ上の2次元，3次元のテクスチャを読むのに使われ，QPUからの書き込みとuniformでメモリアドレスやテクスチャのフォーマットや座標を指定し，シグナルを送ると，指定した値がr4に書き込まれます．TMUは1要素読み込むためにQPU側でアドレスを計算し，書き込み，さらにシグナルを送らなければならないのでオーバーヘッドが大きいですが，テクスチャを読むことができます．リクエストのキューの長さが8あるので，頑張ればオーバーヘッドを隠蔽できます．

VPM (vertex pipeline memory) はDMAを使ったメモリI/O方式です．DMAを使って64x16x32ビットあるVPMにメモリから値を読みこんだり，VPMからメモリに値を書き込んだりできます．さらに，QPUからVPMの値を呼んだり，VPMに書き込んだりできます．DMAやVPMの設定や操作はQPUから行います．VPMもTMUと同じくメモリアドレスを自分で計算したりload/storeを明示的にwaitしなければならなかったりしますが，一度に多くの値を読み書きできるので，オーバーヘッドはあまりありません．DMAの設定に関しては，一度設定したものはユーザが変更するまで有効なので，何度も同じ設定を設定し直す必要はありません．また，QPUとVPMの間には長さ2のキューがあり，VPMを読み込む際には値が一度ここにたまります．よって，DMAの読み込みを完了させる前にVPMの読み込みを設定してしまうと変な値が読み込まれてしまうので注意が必要です．また，先ほどVPMのサイズは64x16x32ビットと言いましたが，マニュアルのエラッタ情報によるとこれは192x16x32ビットに拡張されているようです．

--
これくらいにして，次回は実際に数値計算をするプログラムを書いてみます．