CPU実験2016年度D班コア係(CPU実験でマルチコア)
CPU実験を終えて後輩のために書き残しておけそうなことをまとめます。
CPU実験とは何ぞや、という所については去年の先輩方の記事を見てもらうのが早いと思います。(今年は昨年と違ってFPU係がなくなったり、実験で使うHDLがVHDLからSystemVerilogに変わったりしていますが)
- 東大理情名物のCPU実験で毎週徹夜したお話(概要編) – Eureka Engineering – Medium
- CPU実験でコンパイラの改造でハマったところ - Handwriting
- CPU実験でTravis CIを使ってみた - Handwriting
自班のコンパイラ係も記事を書いてくれました。
目次
- 目次
- 参考書籍
- アドバイス的なもの
- SystemVerilogについて
- コア係が遭遇したバグ
- size of variable 'hoge' is too large to handle のようなエラーが出る→解決するコマンドがあった
- 入力バッファをつけたらプログラムローダが壊れた→入力バッファが勝手にブロックRAMになっていた
- mandelbrotがVivadoのシミュレーションでは動くのに実機では動かない→変数を使う前に宣言しないと定数0になってしまうことがあるらしい
- mandelbrotは動くのにminrtは動かない→ケアレスミス
- 出力画像が実行するたびに異なる→ブロックRAMの仕様を誤解していた
- 見た目は正しいが少しだけ暗い画像が出る→ケアレスミス
- プログラムを替えると動かなくなる→Block Memory Generatorの明示的なリセットが必要らしい
- 構造体の配列に変数添字でアクセスするとVivadoのシミュレーションでバグることがある
- Vivado 2015.4のシミュレーションでは動かないのにVivado 2016.2のシミュレーションでは動く
- マルチコアについて
- 参考リンク
参考書籍
コンピュータ設計の基礎/高性能コンピュータ技術の基礎

- 作者: Hisa Ando
- 出版社/メーカー: マイナビ出版
- 発売日: 2010/11/25
- メディア: Kindle版
- この商品を含むブログを見る

- 作者: Hisa Ando
- 出版社/メーカー: マイナビ出版
- 発売日: 2011/06/23
- メディア: Kindle版
- この商品を含むブログを見る
RTL設計スタイルガイド Verilog HDL編

RTL設計スタイルガイド Verilog HDL編―LSI設計の基本
- 作者: STARC
- 出版社/メーカー: 培風館
- 発売日: 2011/06/01
- メディア: 単行本
- 購入: 1人 クリック: 3回
- この商品を含むブログを見る
RTL設計スタイルガイドVerilogHDL版が電子書籍として復活|EDA EXPRESS
FPGAプログラミング大全 Xilinx編

- 作者: 小林優
- 出版社/メーカー: 秀和システム
- 発売日: 2016/12/15
- メディア: 単行本
- この商品を含むブログ (1件) を見る
パタヘネ/ヘネパタ

- 作者: ジョン・L.ヘネシー,デイビッド・A.パターソン,成田光彰
- 出版社/メーカー: 日経BP社
- 発売日: 2014/12/06
- メディア: 単行本
- この商品を含むブログ (2件) を見る

- 作者: デイビッド・A・パターソン,ジョン・L・ヘネシー,成田光彰
- 出版社/メーカー: 日経BP社
- 発売日: 2014/12/06
- メディア: 単行本
- この商品を含むブログ (2件) を見る

ヘネシー&パターソン コンピュータアーキテクチャ 定量的アプローチ 第5版
- 作者: ジョン・L・ヘネシー,デイビッド・A・パターソン
- 出版社/メーカー: 翔泳社
- 発売日: 2015/01/19
- メディア: Kindle版
- この商品を含むブログ (1件) を見る
アドバイス的なもの
1stアーキテクチャは簡単なものにした方が良いと思う
実験の開始時に、1stアーキテクチャは簡単なものにして2ndアーキテクチャで高速化を目指すか、1stアーキテクチャから高速化を目指すかという相克がありますが、自分は1stアーキテクチャは簡単なものにした方が良いと思います。その理由は、
- 実験開始時の知見の少ない状況ではつまらないポイントでハマりやすい
- 1stで得た知見を2ndに活かせる(命令数の統計など)
- 単位を確保できるため、精神的に安定する
からです。
1st命令セットはPowerPCかSPARCをベースにするのが良いと思う
自班は1st命令セットはMIPSライクなものにしましたが、オリジナルのmin-camlはPowerPC、SPARC、x86をサポートしているので、1st命令セットはPowerPCかSPARCをベースにした方がコンパイラ係の負担を減らせたと思いました。(x86命令セットは複雑なので使わない方が良いらしいです。)命令セットの洗練は上述の通り2ndでやっても遅くはないと思います。
(とはいえ、PowerPCかSPARCをそのまま流用するのでは面白くないから他の命令セットにするという考え方もありだと思います。)
プログラムローダ方式 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は似ていますが次のような違いがあると思います。
- structの利点
- 一括代入できる
- 配列にできる(interfaceの配列もできるという記事がありますが、Vivadoのシミュレーションでうまく動かなかったので使うのをやめました)
- parameter, localparamにできる
- アサインメントパターンで初期化できる
- functionの引数にできる
- interfaceの利点
- メンバごとに入出力の方向を変えられる
- interface内にassign, always, function等を置ける(例)
- port, modport expression, parameter等が使える
コア係が遭遇したバグ
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や最終発表会スライドにアーキテクチャの説明があります。
この並列化において、並列実行が可能であることをコンパイラで判定するのにコンパイラ係がかなり苦労していました。。。(コンパイラ係の記事)
参考リンク
- 自班の資料
- 16erの他の記事
- CPU実験:マルチコアで並列実行するまで(コンパイラ係目線) - eguchishi (自班のコンパイラ係)
- CPU Experiment (他班のコア係)
- 15erの方々(15er以前はSystemVerilogではなくVHDLを使っていました)
- 技術
- http://marsee101.blog19.fc2.com/blog-entry-2574.html Vivadoでの回路図の表示方法
- http://rilo.moo.jp/vlog/verilog03_1.html システムタスク、システム関数
- http://jz5.jp/2013/11/19/verilog-systemverilog-loop-generate/ genvarの使い方
- http://jz5.jp/2013/11/13/verilog-clog2/ clog2(ここには天井関数と書いてありますが実際には底関数になっています)