prime's diary

そすうの日々を垂れ流しちゃうやつだよ

Brainf*ckを直接実行できるCPUを作った (その2) マルチサイクルCPU化【いろいろなコンピューター Advent Calendar 2023 10日目】

この記事はいろいろなコンピューター Advent Calendar 2023の10日目の記事です。(遅刻です すみません…)

adventar.org

前日の記事

primenumber.hatenadiary.jp

の続きとなっております。よければそちらからお読みください。

シングルサイクル・マルチサイクル・パイプラインCPU

CPUのマイクロアーキテクチャの設計には大きく分けて3種類あります。

シングルサイクルCPU

シングルサイクルCPUの動作

1命令を1サイクルかけて実行します。設計難易度的にはもっとも単純です。

シングルサイクルCPUでは命令のフェッチ、命令のデコード、レジスタの読出し、演算処理、レジスタやメモリへの書き込みといったすべての処理を1サイクルに詰めこみます。 そのため、クリティカルパス(一連の処理の流れのうち、最も時間のかかる経路)が長くなり、動作周波数を上げづらいという問題があります。

リソースや性能の観点から実用にされることはほとんどありません。

マルチサイクルCPU

マルチサイクルCPUの動作

1命令を複数サイクルかけて実行します。何サイクルかけるかは設計により異なります。上の画像の例では、1命令の処理をIF(Instruction Fetch), ID(Instruction Decode), Ex(Execute), WB(Writeback)の4ステージを4サイクルかけて処理しています。

一見、シングルサイクルよりサイクル数が増える分だけ遅そうですが、回路のクリティカルパスを短くできるため、動作周波数を上げて補うことができます。

また、1命令をNサイクルで処理するとき、異なるステップで共通して使うモジュールを一つにまとめることで、リソースの有効活用が可能です。

パイプラインCPU

パイプラインCPUの動作

1命令を複数サイクルかけて実行しますが、各ステージを完全に分離することで、各ステージが命令1を処理し終わった次のサイクルで、命令2の処理を行い、その次のサイクルでは命令3の処理を行い…とすることで、1サイクルあたり(最大で)1命令を処理できます。

マルチサイクルCPUと同様、クリティカルパスを短くできるため、シングルサイクルCPUよりも動作周波数を上げることができ、トータルでシングルサイクルCPUより単位時間当たりに処理できる命令数が向上します。

パイプライン処理では、命令同士にデータの依存関係がある場合に、後続の命令がデータを必要としていてもまだレジスタに書き戻されていない、といったことも発生します。 そのため、レジスタを経由せずにパイプラインのステージ間でデータをフォワーディングする、などといった対策が必要です。

さらに、分岐命令が実行されて実行パスが変わった時、すでにフェッチした命令を一旦捨てて分岐先の命令を読み直す、といった処理も必要です。 このため、シングルサイクルCPUやマルチサイクルCPUよりずっと設計難易度が高まります。

今日は扱いませんが、現代の高性能なCPUは単にパイプライン化されているだけでなく、1サイクルで複数の命令を実行することで、さらに高い性能を実現しています。

Brainf*ck CPUをマルチサイクルCPU化する

昨日作ったBrainf*ck CPUはシングルサイクルCPUでした。今日はこれをマルチサイクル化してみます。ついでに、メモリをFPGAのBlock RAMで実装できるようにしていきます。

できたものがこちらになります。

github.com

Chiselで生成したSystemVerilogファイルを、Vitisに読み込ませてsynthesisして、命令メモリ、データメモリがBlock RAMに推論されることを確認しています。

主な変更点は次の通りです。

命令実行時の状態を3つに分割

シングルサイクル実装の状態遷移図

マルチサイクル実装の状態遷移図

Fetch, Execution, Writebackの3ステージを作りました。これまでは実行中は([ ]によるジャンプ先探索状態を除くと)Executionだけでした。

メモリアクセスのレイテンシーを考慮

Block RAMはアドレスを入力してからデータが出てくるまでに1サイクル以上かかります。これまでは即座にデータが返ってくることが前提の実装だったので、そこを修正しました。 シングルサイクルCPUでは、そもそもこういうことはできないので、マルチサイクル化が必須でした。

今後の展望

直近で改善したいこととしては、

  • 命令書き込みモードを作る
  • [ によるジャンプと ] によるジャンプで状態を分けているが、進む向きが違うだけなので共通化する
  • FetchとWritebackは簡単に同時実行可能なので、同時に実行する(計算上、ジャンプ先探索以外のスループットが1.5倍になる)

その先は、完全なパイプライン化やFPGA上での動作の実現、ジャンプ先キャッシュの実装を目指していきたいです。

(追記) その3はこちら

primenumber.hatenadiary.jp