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

こんにちは。のどかです。CPU実験ではコア係として3人班(コンパイラとシミュが兼担)で頑張っておりました。CPU実験の詳細についてはここでは書かないので他の方のブログなりを参照してください。

さて、CPU実験ではまず皆さん線表というものを書かされると思います。計画表ですね。大体みんなこんな感じのを書きます。

しかし実際はこうなります。

これうちの班はかなりマシな方です。22erは8班中3班しか最終発表会(2/14)までに完動しませんでした。

私の推測ですが、以前はこの線表が大体正しく動作していたのだと思います。しかしCPU実験の単位要件がどんどん厳しくなる一方、線表は前年度の線表を参照して作り年々ほぼ同じものが引き継がれていった結果ここまで大きな乖離が生まれてしまったのだと思っています。

というわけで皆さんが線表を作るうえで真に参考になる資料(?)として、私が1月末にコアを完成させるまで、どの時期にどんなことをしていたかということをひたすら書いていきます。

内容は基本的に、CPU実験期間中にSlackのプライベートチャンネルで一人壁打ちしてた呟きに基づいています。この方法ソフトウェア係さんに勧められたのですがおすすめです。Twitterがうるさすぎると嫌われるので。

10/4-10/6: 準備

CPU実験の初回説明があり班分けがされた。事前のアンケートで「3人班でも4人班でもよい」の項目をチェックしたら見事に3人班に振り分けられた。自分が兼担じゃなかっただけマシかもしれない。 HW実験の内容もほぼ忘れ、何から手を付けていいのか分からなかったので、とりあえずGitHubリポジトリとVivadoのプロジェクトを作成し、Lチカを動かしたりしていた。HW実験ではVivadoのblock designをフル活用していたが、gitでの管理が大変そうなのもあり、今回は使わずにいくことにした。壊れていると噂だったHW実験のUART回路も動作することを確認した(つもりだったが、完動後に本当は壊れていたことが判明した)。

10/7-10/25: シングルサイクルコアの実装

VivadoやVerilogに慣れたところで、シングルサイクルコアを実装した。とりあえずはfibが動くことを目標に据え、メモリやI/Oの命令は除外した。I/OとFPUさえなければ、コアは基本的にハリス本のコピペでいける。 その後はテストベンチを作成してシミュで動作確認をした。もし1st ISAがRISC-Vなのであればこちらのサイトでアセンブリコンパイルできるのでおすすめ。

riscvasm.lucasteske.dev

I/Oを実装していないので、実機での動作確認はレジスタを直接LEDに接続してその中身をバイナリ表示した(fib程度ならこれで十分)。 この後load, storeへの対応も行ったが、これはのちに重大なミスに気付くことになる。

10/25-10/26: パイプラインコア(ハザード非対応)の完成

高速化のためにはパイプライン化は必須なのでこの段階で実装を進めた。この時点ではコンパイラ係さんから「ハザードの対応はすべてこちらで行う」との心強い言葉をもらっていたのでハザードへの対応は一切行っていない。ハザードさえなければレジスタを追加するだけでとても簡単にできる。

10/27-11/4: I/Oの実装

いよいよ(それまで難しそうで放置していた)I/Oに着手した。この時点では、入出力のバッファとしてメインメモリを使うことを考えており、そのような実装にしていた(これも無駄が多いことに大分後になって気づく)。また、データの送受信とコアの実行は同時には行わない実装になっていた(これは後に改善する予定だった)。まだserver.pyには対応していなかったので、loopback的な回路を作って動作確認した。

11/4-11/11: server.pyへの対応、I/Oの完成(?)

この時点でserver.pyの仕様を確認し、それに対応できるようなFSMを実装した。記録によるとバグりまくって大変だったらしいがあまり記憶にない。よく分からないうちに直ったらしい。

11/13-11/15: ハザードへの対応

このくらいからだんだんコンパイラ係がやばそうな雰囲気を醸し出してきたので、コアがハザードへの対応を行うことになった。ハリス本を参照したが、理解せずに実装するのも怖かったのでコピペはしていない。fibが動くことを確認した。

11/16-11/30: I/Oの改良、FPU合体

この段階ではデータの受信が完了してからコアを実行する実装になっていたが、これでは遅いのは明白なうえ、受信するデータのサイズが固定でないといけないので、データを受信しながらコアも実行するように改善した。具体的には受信したデータをコアとは独立して動くモジュールがデータメモリに書き込み、コアは入力データが未受信ならストール、受信済みならメモリから読みだす。これはあとでいろいろと無駄が多いことに気が付くことになる。なおこの段階では送信についてはコアが終了してから行っている。

また、FPU(パイプライン)が完成したのもこの頃だったので、コアとの合体を実装し始めた。

12/1 - 12/6: FPU合体つづき + I/Oの仕様の再確認

I/Oについて、とりあえず入出力ともに4バイト単位で行う方針で進めていたが(メモリが4バイト単位でしか読み書きできなかったため)、本当にそれでいいのか疑念を抱き始めて、レイトレのコードやPPMのspecificationをひたすら漁っていた。PPMのspecificationがすぐには見つからなかった気がするのでここに貼っておく。

PPM Format Specification

結論としては、入力は4バイト単位、出力は1バイト単位のみできればよいということが分かった。あとPPMはデフォルトではP3だが絶対P6の方がいいと思う。理由はいろいろ。

FPUの合体も引き続き継続。合体というより、fsgnjやflwのようなコア側で実装すべきfloat命令の実装を行っていた。

12/8 - 12/14: float命令周りのハザード対応、コードの整理?

この期間の記録が残っていないので何をしていたが不明だが、逆に言うと大したことをしていないのだと思う。float命令が入ってきたらハザード周りがだいぶ大変になったらしい。

12/15 - 12/19: float周りのデバッグ

この辺も記録があまりないが、FPU組み込みに伴うデバッグを行っていた模様。たぶんfib(float版、アセンブリ手書き)とかで動作確認したはず。

12/20 - 12/30: メモリ合体 (+float実装のつづき)

メモリが完成したので合体しようとして、盛大な合体事故を起こした。メモリ側の入力ポートはaddr(読み書き共通) + rw(read or write) + validだったのだが、コア側の想定はraddr + waddr + wenableであったので、これを全部書き直すはめになった。また、メモリを1サイクルでアクセスできる前提下でしかテストしてなかったため、複数サイクルでのアクセスで起こるバグへの対処にも追われた。

メモリの動作確認用にBlock RAMを使って複数クロックで読み書きを行う(つまり、見かけ上はDDR2と動作が同じ)mock DDRのモジュールを作成したが、これはとても良かったと思う。DDRをシミュレーション上で動かそうとすると異常に時間がかかるので、シミュ上でデバッグをしたいときにとても便利だった。あとメモリ周りがおかしいときにコアとメモリどっちが原因なのか分かる。

長くなりそうなので、年明け以降の部分はまた次の記事に書こうと思います。それでは。