アーキテクチャの概要

CPUの外部仕様

はじめに 〜コンテストのルール2004年度版〜

CPUの概要を説明する前に、まず、コンテストのルールを説明することにしよう。年によっても違うだろうし。

レイトレのデータは、基盤上で動いているプログラムとノートPC上で動いている審判プログラムとの通信でやりとりする。通信には、ethernetケーブルではなくUSBケーブルを用いた。この辺の詳細は今後変わるかもしれないので注意。

まず、審判プログラムに初期化データを流す。これは、例えばSDRAMに表引きデータを置く場合に、そのデータを送るのに使えたりする。まぁ、SDRAMを使える超人が今後出るかどうか疑問だが。どちらにしろ、USB通信の都合上、なんらかのダミーデータを送ることになるであろうが。

それから、審判プログラムは、基盤側から準備OKのデータを待つことになる。このデータを受信したタイムを0とし、審判プログラムは時間の計測を始める。

まず、審判プログラムはsldデータを送る。テキスト形式のsldファイルを数値に直すのは、全部審判プログラムがやってくれる。また、オプションでエンディアンの設定を変えることもできる。

その後、基盤側はピクセルデータの受信を待つことになる。形式はppmだった。すべてのピクセルデータを受信した時点でのタイムが公式タイムとして記録される。また、計測中もファイルに書き出してるので、途中経過を見ることもできる。

精度は、fadd/subが超高精度、fmulが高精度。fdiv,fsqrtがそれなりの精度といった感じ。いずれもround evenの丸めはしなくても満たす程度になってるが、来年以降どうなるかは知らない。

50MHz RISCアーキテクチャ

RISCは、命令数を必要な命令に限りクリティカルパスを短くし、クロックの向上を狙うもの。

本当は、66MHzで動くはずだった
クリティカルパスは14ns(浮動小数演算のレイテンシを増やせば、12.4nsまでいく見込みあった)
CLKFXを上手く動作させることができずに断念

5段パイプライン

フェッチ(F) -> デコード/レジスタリード(R) -> 命令実行(Ex) -> メモリアクセス(M) -> ライトバック(レジスタライト) (Wb)の5ステージ
浮動小数も同様。Exステージ後のデータパスは、CPUとFPUの2つに分かれる。

Float/Integerそれぞれに32本のレジスタ

なぜ、レジスタを分けるか?それは以下のような理由が挙げられる。

・MLプログラムのレベルで見ても、ほとんどの演算はFloat同士、Integer同士の演算である。int/floatレジスタの転送が必要な関数として、int_of_float,float_of_int,flessなどが考えられるが、実行数はそれほど大きくなく、分けることによるソフトウェア的な弊害は少ない。
・IntegerとFloatを分けることでハードウェア的に離れた位置に置くことができる。特に、fadd,fsub,fmulなど、float命令の中でも実行数が多く、速度の向上が期待される命令はレジスタの近くに置くことになるわけだが、それらの演算器が、同様に高速性が期待されるIntegerの演算ユニット等を圧迫せずに済む。
・あわせて64本だと命令長6bitになるが、それぞれ32本だと命令長5bitになる。これにより、命令フォーマットに余裕ができる。

なお、不必要なInteger/Floatレジスタ間転送命令を防ぐため、Integerと似たような命令をFloat側にも作る必要が出てくる。具体的には、条件分岐、メモリアクセス命令などである。
このように様々なメリットのあるFloat/Integer分離だが、デメリットもある。コンパイラが型の実装をしなければならないことだ。これはがんばってもらうしかない。

16KB Instruction/Data Cache

更に詳細を述べて置くと、ブロックサイズが共に4WORD。アドレスはワードアドレッシング。つまり、バイト単位で読み書きする命令はなく、またアドレスを指定するときに左2シフトなどの小細工はいらない。MISS時のレイテンシはInstructionが6、Dataが5だ。これは、MISS/HITにかかわらず、投機的にロードストア命令を実行しているためだ。ブロックサイズが4WORDなのは、レイトレの性質上、xyz座標を扱うことが多く、空間的局所性が高いと思われたからだ。ステートの遷移やハザードの条件など苦労した部分はたくさんあるが、具体的な話はここでは割愛。

1Clock Load/Store命令

これぞ我が班の中核をなす命令。これによって、即値命令を削ったり、関数呼び出しを早くしたりなどの小細工をすることができた。

具体的に説明すると、これはSRAMにアクセスせずにBlockRAMと呼ばれるXilinx内に40個ある18KBitのRAMのうち2つ(Integer/Floatそれぞれ1つずつ)を使って、必ず1Clockで行えるメモリアクセス命令を実装したものだ。我が班に即値命令がないのはこの命令のおかげだ。どのようにコンパイラで実装されたか簡単に説明しよう。

まず、コンパイラは次のようなコードを生成したとしよう

fadd %f2,%f1,%f1
fimm %f3,0x3f800000
fadd %f1,%f3,%f2

この真ん中のfimmは擬似命令で、%f3に32Bitの即値を代入するもの。実際にこのような命令を作ることはできないので、これを1Clockで行えるldf,stf,fldf,fstf命令を用いて変換する。すると次のようになる。

プログラムの冒頭
fld %f1,%r30,12 ! %r30は、SRAM上にある即値のデータ領域の開始位置を指すものとする
fstf %f1,%r29,12 ! %r29は、BlockRAM上の即値を置く領域の開始位置を指すものとする

上の該当部分
fadd %f2,%f1,%f1
fldf %f3,%r29,12
fadd %f1,%f3,%f2

このようにすると、即値代入が1Clockで行うことができる上、命令フォーマットを単純にし、デコードを用意に行うことができる。規模を小さくし、R/Exステージの間に置くことで、ロードのすぐ直後で値を使ってもハザードは生じない。ちなみにこの命令、16Kbit=2KBのRAMを使用しているので、ワード単位で512個のデータしか保存できない。また、即値があまり出てこなさそうというプログラムの特徴を利用しているので、来年度以降どうなるかによってまた変わってくるはずだ。

1段遅延スロット&静的分岐予測

パイプライン化してクロック周波数を上げようとすると、分岐命令をどうしても2ステージに分けることになる。その結果、1段の遅延スロットを入れざるを得なくなる。

さて、遅延スロットを埋めなければならないわけだが、単にコンパイラさんがんばってくれと言っても限界はある。埋められないものは、いくらがんばったところで埋まらない。そこで、少しでも無駄を削減しようと導入したのがbzl命令だ。この命令は、分岐した場合は通常通り遅延スロットを実行するが、分岐しなかった場合、遅延スロットを実行しない、つまりnopに置き換えてしまうという命令だ。言い換えれば、分岐しなかった場合だけnopが入る命令なわけだ。これによって、例えばn回ループして抜ける場合などに、ループして戻った先の命令を遅延スロットに持ってくることで、(n-1)/nのヒット率を得ることができる。また、どうしても遅延スロットを埋められない場合に、分岐先、または論理を逆にして分岐しない場合の命令を持ってくることで、少しでもミス時のペナルティを減らすことができる。

fdiv,fsqrtをハードウェアでサポート

コンテストで決められた精度を満たすfdiv,fsqrt回路を実現した。

fdivは名前こそ除算だが、実際はレジスタ1個を受け取り、その逆数を求める命令とした。x/yを計算する場合は、x*(1/y)を求めることになる。元になるデータをBlockRAM1個を使って表引きし、その値をNewton法を1回使って精度を決められた精度まであげた。

fsqrtもx^(-1/2)を求める仕様。コレも求めた値にxをかけることになる。実装方法はfdivと同じだが、x^2をあらかじめ表引きして得ることで、乗算器を減らし、レイテンシを1下げることができた。

戻る