CPU実験 完動までの記録 by コア係 その2

前回の記事を読んでいない方はまずそちらをお読みください。本記事はその続きになります。

danook-programming.hatenablog.com

この記事では年明けから完動までのおよそ1カ月の記録を書いていきます。完動後の最適化の悪戦苦闘はまた別記事に書くかもしれないし書かないかもしれません。

1/1 - 1/7: I/Oのデバッグ・実装修正

今まで面倒で避けてきたI/Oのデバッグと正面から向き合わなければならなくなってきたので、本格的に取り組み始めた。

I/Oは今までずっと「データメモリの一部をバッファ代わりとして用いる」という方針で行っており、データメモリがBRAMからDDRに変わったあともその方針で実装を続けていた。しかし、コアとI/Oのモジュールが同時にメモリにアクセスしようとした場合の処理の複雑さがバグの温床になっており、かなり苦戦を強いられていた。何度やってもI/Oが永遠に終わらないか入力を途中までしか受け取らないかのどっちかだった。

しかしここで私は「実は入出力のデータを一時保存しておくのはデータメモリでなくてもよくないか??」という重大な真実に気づいてしまう。冷静に考えればデータメモリとは別にBRAMでバッファを作ってそこに置けばよい。目の前の実装に必死で全く問題を俯瞰できていなかった。

というわけでこれまで必死に作り上げてきたI/O周りのコードを全部ぶち壊して書き直した。バグは出なくなった。

ちなみに私はIPの存在をあまり認識していなかったので、自分でBRAMを使ってFIFO的なものを実装したが、21erの方とかはFIFOのIPをI/Oのバッファとして使った人が多かったらしい。

あとはレイトレをI/O込みでシミュ上でデバッグするために、server.py(の大幅縮小版)をVerilogでエミュレートするコードを書いた。これは後で大いに役立ったので作っておいた方がいい気がする。

1/8 - 1/13: メモリデバッグその1

DDRを組み込んだ状態でシミュでデバッグした後、実機でも実行してみた。入力したバイナリデータをメモリに書き込んで、再び読みだして出力するだけのプログラムだった気がする。なぜかシミュでは動いたのに実機では動かず絶望。半ばあきらめて他のところをいじったりしていたら、いつの間にか動くようになっていた。不思議に思いつつも、実はメモリが原因じゃなかったのかもなーと思ってそのまま放置して先に進んだ。

結論から言うとこれは放置すべきではなかった。バグは実は直っていなかったことが大分後の時期になって発覚し、メモリ係を大いに振り回してしまう結果になった(本当に申し訳ない)。

1/14 - 1/22: コンパイラへの対応、コンパイラが吐いたコードの実行

このくらいの時期になって、シミュ係と兼担のコンパイラ係氏がRustでフルスクラッチで死にそうになりながら書いていたコンパイラがようやく形になってきていた(fibなどは吐けるようになっていた)(本当にお疲れ様です)ので、コンパイラが吐いたfibやackermanなどのコードをコアでテストしたりした。

またそれにあたっていろいろコンパイラ係とすり合わせておくべきことがあったので、その辺の仕様を確定していった。

  • メモリのaddressing (word- or byte-)
  • heap pointer, stack pointerなどのレジスタの初期値
  • プログラムファイルのフォーマット

このくらいから少し大きめのプログラムでのデバッグも開始したので、シミュレータもデバッグに活用しはじめた。

1/24 - 1/26: レイトレデバッグ準備

1/24にシミュが完動し、コアも小さめのプログラムでは一通り動くようになったので、いよいよレイトレ実行を視野に入れ始めた。そこで命令メモリをレイトレが載るくらい大きくして合成してみたのだが、ここではじめてBlock RAMの使用率が全く増えていないことに気付く。そしてBlock RAMが非同期読み出しに対応していないことをこの段階で初めて知る(普通にコア係として失格レベルだと思う)。命令メモリなどの実装を大幅に書きなおすことになった。同期読み出しに直した後もなかなかVivadoがBlock RAMを推定してくれなくて手間取った。Block RAM推定はかなり至難の業なので、基本的には公式ドキュメントとかにあるBlock RAM実装例をそのままコピペするのが確実だと思う。それか私が知らないだけでもっと賢い方法があるのかもしれない。

これによって合成はできたが、動かないことに変わりはなく、ここで以前直ったと思っていたメモリのバグが直っていなかったことに気付く。すぐに解決するのは難しかったため、とりあえずメモリ係にも原因究明をお願いして自分はBlock RAMだけで動かせる16x16のレイトレを完動させることを優先にした。

1/26 - 1/30: レイトレデバッグ with メモリデバッグその2

というわけでダメ元で16x16レイトレを実機で動かしてみたが当然のように何も返ってこず、Vivadoシミュレーション上で地道に波形とにらめっこしつづけた。2x2や16x16のレイトレでDDRを使わなければシミュでも十分速く実行できる…と思っている(速いの定義による)。ここで以前作ったserver.pyのエミュレートが大いに役立った。

その後何とかそれっぽい画像が返ってくるところまで動くようになったが、正解画像とは微妙に違っており、もう少し高解像度で画像を確認するためにメモリの問題の解決が急務になった。メモリ係と二人であーでもないこーでもないと試行錯誤した結果、コアとキャッシュで違うクロックが入力されていることが原因ではないかという結論に至った。コアはCLK100MHZをそのまま使用していたのに対しキャッシュはClock Wizardの出力を使用していたので、今思うと動かないのは当然なのだが。1/29にようやくメモリが正常に動作するようになった。

というわけで256のレイトレを動かしてみたところ、影の向きが微妙に異なることと、画像のUARTでの送信がうまくできていない箇所があることが分かった。前者はUARTでSLDが上手く送れていなかったか、FPUの比較演算が間違っていたか、原因はよく分からないがいろいろ修正しているうちに直っていた。UARTに関してはbaudrateを9600まで落とすことで、1/30に無事に完動することができた。1/30の朝の段階で「あとbaudrateさえ落とせば99%完動する」と確信していたので、班員全員でzoomに集まっていた一緒に見守っていた。意外と感動はしなかった気がする。ちなみに後日談だが、UARTはHW実験から転用したモジュールがバグっていただけで、正しく作れば高いbaudrateでも普通に動く。