この記事はいろいろなコンピューター Advent Calendar 2023の10日目の記事です。(遅刻です すみません…)
前日の記事
の続きとなっております。よければそちらからお読みください。
シングルサイクル・マルチサイクル・パイプラインCPU
CPUのマイクロアーキテクチャの設計には大きく分けて3種類あります。
シングルサイクルCPU
1命令を1サイクルかけて実行します。設計難易度的にはもっとも単純です。
シングルサイクルCPUでは命令のフェッチ、命令のデコード、レジスタの読出し、演算処理、レジスタやメモリへの書き込みといったすべての処理を1サイクルに詰めこみます。 そのため、クリティカルパス(一連の処理の流れのうち、最も時間のかかる経路)が長くなり、動作周波数を上げづらいという問題があります。
リソースや性能の観点から実用にされることはほとんどありません。
マルチサイクルCPU
1命令を複数サイクルかけて実行します。何サイクルかけるかは設計により異なります。上の画像の例では、1命令の処理をIF(Instruction Fetch), ID(Instruction Decode), Ex(Execute), WB(Writeback)の4ステージを4サイクルかけて処理しています。
一見、シングルサイクルよりサイクル数が増える分だけ遅そうですが、回路のクリティカルパスを短くできるため、動作周波数を上げて補うことができます。
また、1命令をNサイクルで処理するとき、異なるステップで共通して使うモジュールを一つにまとめることで、リソースの有効活用が可能です。
パイプライン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で実装できるようにしていきます。
できたものがこちらになります。
Chiselで生成したSystemVerilogファイルを、Vitisに読み込ませてsynthesisして、命令メモリ、データメモリがBlock RAMに推論されることを確認しています。
主な変更点は次の通りです。
命令実行時の状態を3つに分割
Fetch, Execution, Writebackの3ステージを作りました。これまでは実行中は([
]
によるジャンプ先探索状態を除くと)Executionだけでした。
メモリアクセスのレイテンシーを考慮
Block RAMはアドレスを入力してからデータが出てくるまでに1サイクル以上かかります。これまでは即座にデータが返ってくることが前提の実装だったので、そこを修正しました。 シングルサイクルCPUでは、そもそもこういうことはできないので、マルチサイクル化が必須でした。
今後の展望
直近で改善したいこととしては、
- 命令書き込みモードを作る
[
によるジャンプと]
によるジャンプで状態を分けているが、進む向きが違うだけなので共通化する- FetchとWritebackは簡単に同時実行可能なので、同時に実行する(計算上、ジャンプ先探索以外のスループットが1.5倍になる)
その先は、完全なパイプライン化やFPGA上での動作の実現、ジャンプ先キャッシュの実装を目指していきたいです。
(追記) その3はこちら