これはrogy Advent Calendar 2015の18日目の記事
「狭義Lチカか広義Lチカか自作CPUのハナシ」です。

13の有塩(@salt_free_free)です.
2015-12-14-190453_107x85_scrotOpera

低レイヤは好きですか
低レイヤを知ろう
低レイヤは楽しい

FPGA上に載せるGPUもどきをつくるハナシをします.
ただし現在設計中なのでほとんど調べたことの垂れ流しです.
やでもアウトプットは大事だってサークルナカマも言ってたぞ

GPUとは
GPU-Wikipedia
画像処理をする計算装置です.
お手元のパソコンやスメイトホンの画面は実はこれかこれに相当するものに繋がってます.
3Dモデル描画を例に簡単に言うと,
CPUから「ココにこんな三角形書いて」と命令が飛んでくるのでひたすら描画をこなします.

もちろんそういう処理までCPUがやっても良いんですが,決まった処理の繰り返しが多いので
専用のハードウェアをつけてやってそこで処理を行うことで高速化・高性能化します.
それがGPUです.
アレ
なんでGPUもどきをつくるの
弊大学では6学期の実験で,半期で一人でFPGAでなにか作って発表する枠があります.
作品完成度が高いとか投票で1位になるとお点数がgetできるのでこれは頑張るしかないというワケ.
もともと低レイヤが好きで,CPUやGPUの勉強・設計をしてみたかったのでいいチャンスと思い,
CPUは5学期にアレしたのとそもそも実装済みのCPUが用意されているので今度はGPUにしました.

どこまで作るの
SFC版スターフォックス(やったことない)みたいなゲームができれば相当すごいと思います.
作るとは言っていません.

面のある三角形を描きたいですね.
最初はワイヤフレームだけの描画を考えていたんですが,
どうやら前期に面のある三角形を表示させてるヒトがいたらしく,
つまり面を出さないとネタ被りにすらならないということでこうなりました.
許すまじ

環境・仕様は
  • ハード:
    FPGAはSpartan3Eと512KBのSRAMが載ってるボード 組み込み乗算器が20コしかねーぞ!
  • RTL:
    MIPS32のサブセットが書いてあるverilogコードをもらいました.ありがたや
    これを拡張(?)していく形になります.
  • 画面出力:
    VGA出力で画面解像度は800*600,フレームレートは72Hz
    256色,ダブルバッファリング
    実際の描画はVRAMの都合上400*300なので縦横2倍にします

何すればGPUになるの
こっからが調べ学習のアウトプット.
以下の処理がこなせれば不自由なく三角形が書けるっぽいのでそれを実装したいです.
これをグラフィックスパイプラインと呼ぶそうです.
資源と技量と時間がなさそうなので本当のパイプライン化はできませんが...
  • モデルビュー変換
  • 射影変換
  • クリッピング・カリング
  • ビューポート変換
  • ラスタライズ
モデルビュー変換とは
3Dモデルを世界に配置して,モデルの回転・移動をしたりカメラから見た時の座標を求めたりします.

射影変換
カメラから見た時の座標(3次元)から,画面に映せる座標(2次元)を求めます.

クリッピング・カリング
描画する必要のない座標にあったり,裏から見えていたりする三角形を弾きます.

ビューポート変換
画面に映すための座標を求めます

ラスタライズ
三角形の内側に色を塗ります.

これらのうち座標変換は4x4行列の乗算で行います.
どんな行列を掛ければ各変換になるのかは名前で検索すると出てくるのでそっちに任せます.

CPUとのインターフェースは

CPUからどんなふうにGPUに命令だすのってハナシです.
結論から言うと,命令をメモリにマップして,そのアドレスにアクセスが起こると実行されることにしました.
書き込もうとした値をそのままパラメータとして使います.
例えば1byte幅でrotateXが割り当てられたアドレスに128が書き込まれたら,
X軸で180度回転する行列を現在のモデルビュー変換行列に右から掛けます.

命令はopenglを参考にして,
モデルビュー変換
  • 平行移動 translate
  • 回転 rotate
  • 拡大縮小 scale
  • カメラからみた座標に移動 lookat
射影変換
  • 平行投影変換をロード ortho
  • 透視投影変換をロード perspective
コントロール
  • バッファをクリア
  • バッファをスワップ
  • 色指定
  • モデルビュー変換行列push/pop
  • 線描画
  • 三角形描画
を用意すれば十分でしょう.lookatだけ粒度が違いますが,多少はね.
モデルビュー変換命令は,現在のモデルビュー行列に右から変換行列を掛けて上書きします.※
これはopengl準拠(?)です.

ちょっと脱線.
座標変換時,変換行列は座標のベクトルに左から掛けます.
つまり前述の方法※だと先に呼んだ命令が後に実行されるわけですが,
これにより命令を"スタック"したように扱うことができます.

例えば原点にある太陽を画面中心に太陽と地球と月と金星を(簡易に)描画するとしたら,
  1. カメラを原点に持ってくるように座標変換
  2. 太陽描画
  3. (変換行列push)
  4. 地球の公転分回転
  5. 太陽と地球の距離分平行移動
  6. (変換行列push)
  7. 地軸の傾き分回転,自転分回転
  8. 地球描画
  9. (変換行列pop,5.と同じ変換行列に戻る)
  10. 地球から見た月の公転分回転
  11. 地球と月の距離分平行移動
  12. 月描画
  13. (変換行列pop,2.と同じ変換行列に戻る)
  14. 金星の公転分回転
  15. 太陽と金星の距離分平行移動
  16. 金星描画
という流れになります.(用語が適当ですんません)
ミソは衛星である月が地球との相対的な座標変換ができることで,
これによって太陽(原点)からの月の座標をわざわざ計算する必要がないというメリットがあります.
また,個々のモデルに対して必要になるローカルな座標変換は木の末端で呼ばれるため,
pushとpopを使い都合よく元に戻せます.すごい.

実は私はPC上で3Dグラフィックを扱ったことが無いので,このシクミを知るまで
なんでこういう命令の発行順なのか,特になんで皆ビュー変換を最初に行っているのか,
openglを使ったサンプルコードを見ても分かりませんでした.キマリなのだろうと.
でも分かってしまえば振り返ってみると全部†必然†で固められていて,なるほどを得ます.
一回なるほどを得れば,あとは自信を持って上のレイヤーの実装ができますね.
おっと,低レイヤーの理解は大事だということに気付かされてしまいました.

結局できるの?
GPUを作るに当たり必要そうなモノを列挙してきました.
「4x4行列の乗算…」などサラっと書いてあるものも,本当は,
「乗算器4つ使って16クロックかけて計算するモジュールが〜」
など沢山考えることがあるのですが,キリがないので,概要だけお伝えしました.

で,"動くっぽい"モノはキアイで書けるとして,そもそも"動く"の?というハナシです.

塗りつぶしがヤバい

VRAMへの書き戻しがネックになりそうです.
例えば200*200くらいの矩形に収まる大きい三角形を描いちゃったとして,
適当に矩形で走査して塗りつぶしていくとそれだけで
200*200/(100M/72fps)=0.0288
1フレームを描画する貴重な時間の内3%が取られてしまいます.
hantei
つまり最善を尽くしてもそんな三角形は30個程度しか描けないワケです.ちょっと心もとない・・
でもフレームレートを落とせばナントカなりそうではあります.

メモリアクセスがヤバい
SRAM@100MHz上にVRAMを取るんですが,
ディスプレイ信号生成用に少なくとも7%ほど時間的占有を受けてしまいます.長い.
しかもそのアクセスとGPUからのアクセスを捌くスケジューラがまともに書けないともっとロスが生じてしまう...
でもこれもフレームレートを落とせばナントカなりそう.

記憶容量がヤバい
SRAMはVRAMとして使ってしまうので後がありません.
BRAMは半分くらいCPUに充てられています.
sin,cosのテーブル,頂点バッファ,行列のスタックに湯水の如く使われるレジスタ君達…
果たして資源が足りるのか.こればっかりは一度Mapしないと分かりません.

デバッグがヤバい
書けるとしますという前提が崩されてしまいました.GPU部分のテストベンチを書ける気がしません.

...

思ってたよりなんかずっと大変そうだ

ということで闇が見え隠れする制限時間付きGPU設計ですが,
無事完成して発表までこぎつけたらまたブログを書きたいと思います.
デキなかったっぽい時は骨を埋めてやってください.

前述のとおり目標を高く設定してるので,妥協すればなんとでもなるだろと思ってたりして.
あと,限られた資源でどう実現するかはハードウェアに近い「低レイヤ」ならではの楽しみであったりします.


最後までお読み下さりありがとうございました.
では〜