このようなパイプラインの制御は色々なやり方が可能で、例えば、現在は、命令Aの4サイクル目、命令Bの3サイクル目、命令Cの2サイクル目、命令Dの1サイクル目という状態であるので、それぞれのマルチプレクサを切り替える信号はこうなり、次のサイクルでは命令Aの5サイクル目、命令Bの4サイクル目、…となるので、制御信号はこうなるという巨大な状態数の順序回路を作ることも、理論的には可能である。

しかし、このような中央集権の順序回路は非常に複雑となるので、通常は制御を行う情報も処理するデータとともにパイプラインを流して行くという方法が取られる。これは流れ作業で言うと、ラインを流すモノにそれぞれのステーションでの作業手順を書いた紙をつけて流すというのと同じやり方である。

命令をデコードし、それがADDなどの演算命令の場合の制御は、図4.11のような構造で実現される。命令デコードの結果は演算の種別を示すOPRフィールド、ソースオペランド1を指定するBC1とSRC1フィールド、ソースオペランド2を指定するBC2、SRC2フィールド、そして結果の格納場所を指定するDESTフィールドからなるブルーのレジスタに格納される。BCフィールドは、Valid、Bypass 0、Bypass 1の区別を示す情報を格納し、SRCフィールドはBCフィールドがValidの場合、データを読むレジスタ番号を格納する。そして、DESTフィールドは結果を格納するレジスタ番号を格納している。

図4.11:データと並行して制御信号を流すパイプラインの制御方式

図4.11に示すように、レジスタ読み出しサイクルにおいては、SRC1とSRC2をレジスタファイルのReadポートに供給して、ソースオペランドを読み、並行して、バイパスマルチプレクサをBC1とBC2で制御して、指定されたオペランドを演算ステージに供給する。

そして、デコードされた命令フィールドの内、演算種別を指定するOPRフィールドと、結果の格納を指定するDESTフィールドは、次の演算ステージのグリーンの制御レジスタにコピーする。そして演算ステージでは、OPRフィールドの情報でALUが行う演算の種別を指定する。この図のように、演算すべきオペランドと実行する演算種別が並列にパイプラインを流れるので、オペランドと演算の対応が保たれる。

そして、制御レジスタのDEST部分が、次のステージのピンクの制御レジスタのDESTにコピーされ、このDESTフィールドを使ってレジスタファイルの書き込みアドレスを指定する。なお、図4.11ではパイプライン的な動きを示すためにレジスタファイルが2つ描かれているが、これは同一のレジスタファイルの読み出しと書込みを区別して書いたものである。

また、結果を格納するレジスタの状態としては、DESTフィールドのレジスタ番号を使って、命令実行開始時点のレジスタ読み込みステージではBypass 0を書き込み、次の演算ステージではBypass 1を書き込み、レジスタ書込みステージではValidを書き込んでやればよい。

コントロールハザードを減らす手法

コントロールハザードは、図4.12に示すように、分岐命令(BR)によりPCレジスタの内容が変更された場合に発生する。この図では3サイクル目にCCレジスタを読み、4サイクル目で指定しされた分岐条件と一致するかどうかを判定し、条件を満たす場合には5サイクル目でPCを変更するという条件分岐命令の処理状況を書いている。分岐が発生する場合は実行すべき命令が変わってしまうので、5サイクル目でPCが変更された後に、次の命令をメモリから読み直す必要が生じる。このため、この例では4サイクルのストールが発生し、データハザードに較べて性能に対するロスが大きい。

図4.12:分岐命令によるストール

最初の命令がデコードされ、分岐命令であるかどうかが判明するのは2サイクル目の終わりであるので、パイプラインは、2サイクル目には分岐をしない連続アドレスの次の命令を読んでしまう。しかし、3サイクル目の開始時点では、先行する命令が分岐命令であることが分かるので、ここで命令のフェッチを停止して、PCの変更が終わる5サイクル目までストールするという処理を行う。

具合の悪いことに、平均的に分岐命令は4~5命令に1回出てくる。理想的にはパイプライン化により毎サイクル1命令の実行ができることになるが、分岐命令が4命令に1回出てくるとすると4命令に1回は4サイクルのストールが発生してしまう。こうなると、合計8サイクルに4命令の実行となり、平均的に1サイクルに0.5命令実行と、性能が半減してしまう。しかし、上に述べた制御法は余りに悲観的で、分岐しない場合は、図4.13のように1サイクルのストールで処理することができる。

図4.13:条件分岐が不成立のケース

このような処理を行うためには、次の命令のメモリからのフェッチ、デコードまでは無条件に実行する。そして、4サイクル目の判定で条件が成立した場合は、図4.12のように6サイクル目に分岐先の命令を読む。一方、条件不成立の場合は、図4.13のように第5サイクルにPCの更新は行われていないので、次のADD命令の実行を開始すればよい。これをもう少しアグレッシブにすると、次の図4.14のように実行することもできる。

図4.14:アグレッシブな分岐命令の実行

図4.13との違いは、分岐方向が決まる前に、本来は実行されないADD命令のレジスタ読み込みを開始してしまう点にある。このため、分岐が起こらなかった場合には、図4.13より1サイクル早くADD命令の実行を完了できる。

しかし、この制御方法には問題がある。図4.11に書いたパイプラインの制御方法を思い出して貰うと、命令の実行が開始されるとパイプラインの制御情報がパイプラインを流れてしまい、レジスタリードに続いて演算とレジスタ書き込みまで実行されてしまう。これでは本来は実行されないADD命令が実行されてしまう。これを避けるためには、パイプラインをフラッシュ(命令をトイレにFlushするのである。)して命令の実行を中断してやればよい。このパイプラインフラッシュという手を使えば、図4.13で発生していたストールが無くなり、分岐しない場合はロスゼロとなる。

パイプラインフラッシュを行うためには、図4.11のパイプライン制御を、図4.15のように、グリーンとピンクの制御レジスタにValidビットを追加し、Validの場合だけ、各パイプステージの処理結果を書き込むように変更する。

図4.15:フラッシュ機能を持つパイプライン制御

そして、分岐が発生しないことが判明した第4サイクルの終了時点で、グリーンとピンクのレジスタのValidビットをクリアし、結果のFFやレジスタファイルへの書込みを抑止する。