CPU実験2016年度D班コア係(CPU実験でマルチコア)

CPU実験を終えて後輩のために書き残しておけそうなことをまとめます。

CPU実験とは何ぞや、という所については去年の先輩方の記事を見てもらうのが早いと思います。(今年は昨年と違ってFPU係がなくなったり、実験で使うHDLがVHDLからSystemVerilogに変わったりしていますが)

自班のコンパイラ係も記事を書いてくれました。

目次

参考書籍

コンピュータ設計の基礎/高性能コンピュータ技術の基礎

コンピュータ設計の基礎

コンピュータ設計の基礎

高性能コンピュータ技術の基礎

高性能コンピュータ技術の基礎

前者の本は3年Sセメスターの計算機構成論の参考書になっていましたが、この2冊はわかりやすいと思いました。コアの設計を考える上で非常に参考になりました。実際にコアを作ってみると本を読む時には気づかなかった悩みポイントが出てくるものの、この2冊は考えるヒントになってくれます。計算機構成論の試験勉強にも役立ちました。

RTL設計スタイルガイド Verilog HDL編

RTL設計スタイルガイド Verilog HDL編―LSI設計の基本

RTL設計スタイルガイド Verilog HDL編―LSI設計の基本

SystemVerilogのtipsがちりばめられています。必要に応じて参照しました。STARC監修と表記されているものは絶版になったようですが、大学の図書館に1冊あり、また電子書籍もあるようです。
RTL設計スタイルガイドVerilogHDL版が電子書籍として復活|EDA EXPRESS

FPGAプログラミング大全 Xilinx

FPGAプログラミング大全 Xilinx編

FPGAプログラミング大全 Xilinx編

Vivadoの使い方が懇切丁寧に書かれており、3年Sセメスターのハードウェア実験にも役立ちそうです。特にロジックアナライザの使い方はこの本に助けられました。

パタヘネ/ヘネパタ

コンピュータの構成と設計 第5版 上

コンピュータの構成と設計 第5版 上

コンピュータの構成と設計 第5版 下

コンピュータの構成と設計 第5版 下

ヘネシー&パターソン コンピュータアーキテクチャ 定量的アプローチ 第5版

ヘネシー&パターソン コンピュータアーキテクチャ 定量的アプローチ 第5版

定番の本ですが、上記のコンピュータ設計の基礎/高性能コンピュータ技術の基礎の方がコンパクトで読みやすいかもしれません。しかし話題の広さと説明の細かさがあり、つまみ読みしていました。

アドバイス的なもの

1stアーキテクチャは簡単なものにした方が良いと思う

実験の開始時に、1stアーキテクチャは簡単なものにして2ndアーキテクチャで高速化を目指すか、1stアーキテクチャから高速化を目指すかという相克がありますが、自分は1stアーキテクチャは簡単なものにした方が良いと思います。その理由は、

  • 実験開始時の知見の少ない状況ではつまらないポイントでハマりやすい
  • 1stで得た知見を2ndに活かせる(命令数の統計など)
  • 単位を確保できるため、精神的に安定する

からです。

1st命令セットはPowerPCSPARCをベースにするのが良いと思う

自班は1st命令セットはMIPSライクなものにしましたが、オリジナルのmin-camlはPowerPCSPARCx86をサポートしているので、1st命令セットはPowerPCSPARCをベースにした方がコンパイラ係の負担を減らせたと思いました。(x86命令セットは複雑なので使わない方が良いらしいです。)命令セットの洗練は上述の通り2ndでやっても遅くはないと思います。
(とはいえ、PowerPCSPARCをそのまま流用するのでは面白くないから他の命令セットにするという考え方もありだと思います。)

プログラムローダ方式 vs. ROM方式

プログラムをUSB入力から与えるのがプログラムローダ方式、論理合成時にプログラムを組み込むのがROM方式です。それぞれ以下のような利点がありますが、プログラムローダ方式はデバッグ向き、ROM方式は高速化向きといえると思います(プログラムローダがバグの原因になることもありますが)。

  • プログラムローダ方式の利点
    • プログラムを替えるたびにSynthesis→Implementation→Generate Bitstreamしなくて済む
  • ROM方式の利点
    • プログラムローダの実装は面倒
    • プログラムローダのためにリソースを消費せずに済む
    • プログラムローダがない分だけ周波数を上げられるかもしれない

自班は1stと2ndの最初はデバッグ効率の良いプログラムローダ方式にして、2ndをマルチコア化する際にプログラムローダの実装が面倒になってきた & コア数をできるだけ増やすためにリソースを節約する必要があったのでROM方式に切り替えました。

コアのデバッグについて

コアのデバッグは難しいので、いきなりminrtを動かそうとせず、loopback→fib→mandelbrot→minrtのように簡単なプログラムから順に動かせるようにしていくと良いと思います。(mandelbrotはhttps://github.com/esumii/min-caml/blob/master/shootout/mandelbrot.mlにあります。)2ndの時はmandelbrotは動くのにminrtは動かなかった(地獄)ので、minrtの画像サイズを1x1にして、sldファイルもオブジェクトの少ないものにしてやったりしていました。
他には、(自分はやりませんでしたが)モジュールごとに単体テストするのも良いかもしれません。

ロジックアナライザについて

FPGA内蔵のロジックアナライザの使い方はFPGAプログラミング大全 Xilinx編にある通りですが、特に-flatten_hierarchyをnoneにするのはやった方が良いと思います。合成後は配線名がけっこう変わってしまいデバッグに苦労するからです。(-flatten_hierarchyをnoneにしてもけっこう変わってしまうんですが、デフォルトのrebuildよりは良いです)

SystemVerilogについて

struct vs. interface

structとinterfaceは似ていますが次のような違いがあると思います。

コア係が遭遇したバグ

size of variable 'hoge' is too large to handle のようなエラーが出る→解決するコマンドがあった

これはバグではなく仕様のようですが、何のためかはわかりませんが配列のサイズに制限があるようです。このフォーラムにあるようにこのコマンド(最後の数字は適宜変更)をVivadoのコンソールで実行すれば制限値を変えられます。

入力バッファをつけたらプログラムローダが壊れた→入力バッファが勝手にブロックRAMになっていた

このバグは恐ろしいことにVivadoのシミュレーション(behavioral simulation)では発生せず実機でのみ発生するものでした。
ブロックRAMをデュアルポートにする場合、一方のポートで書き込むと同時に他方のポートでその新しいデータを読み出すことはできません。READ_FIRSTモードでは古いデータが読み出され、WRITE_FIRSTまたはNO_CHANGEモードでは読み出しデータが不定になります。(https://japan.xilinx.com/support/documentation/user_guides/j_ug573-ultrascale-memory-resources.pdf p.13、https://japan.xilinx.com/support/documentation/user_guides/j_ug473_7Series_Memory_Resources.pdf p.18-19)入力バッファが勝手にブロックRAMになった結果、新しいデータを読み出しているつもりが古いデータを読み出していて壊れていたようです。ブロックRAMではなく分散RAMを使うように指定したら解決しました。
論理合成するとブロックRAMになる場合でも、behavioral simulationでは分散RAMのように扱われてしまい、シミュレーションと実機との不一致が生じるようです。

mandelbrotがVivadoのシミュレーションでは動くのに実機では動かない→変数を使う前に宣言しないと定数0になってしまうことがあるらしい

このバグも恐ろしいことにVivadoのシミュレーション(behavioral simulation)では発生せず実機でのみ発生するものでした。
ロジックアナライザで地道に調べていったところ、ここtag_match(gpr_cdb, gpr_arch_read[i].tag)が定数0として合成されていることがわかりました。そして、gpr_cdbという変数を宣言より前に使っていたのですが、使う前に宣言するようにしたら直りました。
宣言より前に使っていた変数は他にもたくさんあったのですが、なぜかここだけバグが発生していました。思い当たる点は、ここだけfunctionを使っているということです。いずれにせよ、変数は使う前に宣言した方が良さそうです。

mandelbrotは動くのにminrtは動かない→ケアレスミス

ストアキューが一杯のときにストア命令が来るとストールするのですが、ストアアドレスを計算するユニットが無効なデータを有効扱いしてしまい、ストアアドレスが間違ってしまうというものでした。
ストアキューが一杯のときにストア命令が来るという特殊な状況でのみ発生するバグだったためmandelbrotでは発覚しませんでした。

出力画像が実行するたびに異なる→ブロックRAMの仕様を誤解していた

上述のように、ブロックRAMをデュアルポートにする場合、WRITE_FIRSTまたはNO_CHANGEモードで一方のポートで書き込むと同時に他方のポートで同じアドレスから読み出そうとすると読み出しデータが不定になります。これを知らずにWRITE_FIRSTモードを使っていたため非常に寿命の縮む思いをしました。。。

見た目は正しいが少しだけ暗い画像が出る→ケアレスミス

マルチコアの単一モードではストア命令は全コアのメモリにストアするというISAになっていますが、ストアデータだけ渡してストアアドレスを渡していませんでした。。。

プログラムを替えると動かなくなる→Block Memory Generatorの明示的なリセットが必要らしい

ブロックRAMを使うためのIPコア Block Memory Generator ではブロックRAMの初期値をcoeファイルというもので指定できますが、このcoeファイルを変更したら、Block Memory Generatorを単に再合成するのではなくリセットしてから(Reset Output Products)再合成しないとcoeファイルの変更が反映されないようです。(out of contextでやったのでglobalだとどうなるかは不明です)

構造体の配列に変数添字でアクセスするとVivadoのシミュレーションでバグることがある

このコミットの変更前のように構造体の配列[変数].構造体メンバの形の式が、Vivadoのシミュレーション(behavioral simulation)では正しい値が左辺に代入されないことがあります。添字が定数の時は起こらず変数の時のみ起こるようで、それも必ず起きるとは限らないようです。面倒ですが構造体の配列[変数]をいったん別の変数に代入することで回避できます。

Vivado 2015.4のシミュレーションでは動かないのにVivado 2016.2のシミュレーションでは動く

ということがありました。恐ろしいですね。。。

マルチコアについて

自班の2ndアーキテクチャでは複数コアによる並列計算機構を実装し、CPU実験の最速記録を更新することができました。(ただし、基盤の性能向上、浮動小数点数IPコアの使用解禁等があったので過去より条件は良いです。)下はコンテストサーバで数回計測した結果です。

  • 6コア、計算順序が変わらないよう制御する設計
    • 5.196081 s
    • 5.193502 s
    • 5.193487 s
    • 5.193501 s
  • 7コア、計算順序が変わりうる設計
    • 4.666948 s
    • 4.666928 s
    • 4.666824 s
    • 4.666841 s

minrt.ml中の関数iter_trace_diffuse_rays再帰関数になっており実質的にforループになっていますが、各イテレーションでやっていることは実質的にはグローバル変数diffuse_rayに積算しているだけです。他のグローバル変数も読み書きしていますがイテレーション間依存があるのはdiffuse_rayだけなので、各イテレーションを並列に実行することができます。この際、後のイテレーションが前のイテレーションより早く終わると積算の順序が変わり、浮動小数点数加算なので丸め誤差により計算結果が変わる可能性がありますが、実際には計算順序が変わりうる設計でも出力画像のdiffはありませんでした。
参考リンクISA最終発表会スライドアーキテクチャの説明があります。
この並列化において、並列実行が可能であることをコンパイラで判定するのにコンパイラ係がかなり苦労していました。。。(コンパイラ係の記事