パタヘネを読んで
「コンピュータの構成と設計」(通称パタヘネ)第三版をあらためて読みなおしてみた。
6章には、少しばかり補足が必要ではないかと思う部分が何点かあったので備忘録も兼ねて残しておく。
目指しているアーキテクチャ
本書ではMIPSの例が多く出ているため、MIPSのアーキテクチャ(MIPS-IあるいはMIPS-IIあたり)の互換品あるいはそのサブセットを実現しようとしていると勘違いしてしまいそうだが、そうではない。
以下に述べるように、本書でのアーキテクチャのほぼ最終形である図6.41では、MIPS-Iとの決定的な違いが二つある。
実は、今回再読するにあたってIDTのR3000(MIPS-I)のマニュアルと対比させながら読み進めていったのだが、最初はこれらの違いに気づかず、ひどく混乱してしまった。
遅延分岐が非採用
本書のアーキテクチャでは、分岐成立時、分岐命令の次のアドレスにある命令はバブルとなり、実行されることはない。一方、MIPS-Iでは、分岐条件の成立、不成立に関わらず分岐命令の直後のアドレスの命令が実行される。
以下の例でbeqで分岐が発生した場合、本書では、「6.6分岐ハザード」で説明されているように、既にIF/IDステージに格納されてしまっている3行目のandを内部でバブル化して実行されないようにしている。
一方、MIPSではバブル化の処理を行わず、そのままand命令を実行する。つまり、分岐の成立、不成立に関わらず、分岐命令の次アドレスの命令を必ず実行するようになっている。これは遅延分岐と呼ばれている。
sub $10, $4, $8
beq $1, $3, 7
and $12, $2, $5
遅延ロードが非採用
本書では、ロードでハザードが生じた時に、「6.5データ・ハザードの制御:ストール」で説明されているように、ロードする値の準備ができるまでパイプラインを止める、いわゆるインターロックする仕様になっている。一方、MIPS系、特にMIPS-Iでは通常はインターロックしないことを設計思想としており、ロードハザードへの対応としては、遅延ロードで対応してる。遅延ロードに関しては本書では名前が出てくるだけで、ほとんど説明されていない。
これは、以下のようなコードに適用される。
lw $1, 0($2)
addu $1, 1
$1にメモリからロードした直後に$1に対して演算を行う場合、ロードハザードが発生する。
パタヘネでの実装ではインターロックで対応しているが、MIPS-Iでは、ハードウェアでの対処は行わず、以下のように、ロードの直後のインストラクションでは、そのレジスタを使用してはいけないというルールになっている。これは遅延ロードと呼ばれている。
lw $1, 0($2)
nop #load delay
addu $1, 1
ブロック図のあいまいさ
本書では、ブロック図のどこがシーケンシャルロジック(フリップフロップや同期型メモリなど、クロックに同期して動く回路)なのかがあいまいな部分がある。実際のところ一応説明されてはいるのだが、私にはすぐにわからなくなってしまう。
本書の図6.41に対して、シーケンシャルロジックだと思われる部品を明確にしたブロック図を以下に示す。青色で示した部分がシーケンシャルロジックになると思われる。組み合わせ回路については、データパス系の、ALU等の主要なもののみ記述している。
命令メモリ、及びデータメモリはクロック無しの非同期形式のRAMを想定していると考えられる。
レジスタは、書き込みは同期式、読み出しは非同期式だと考えられる。
IF/ID, ID/EX, EX/MEM, MEM/WBのパイプラインレジスタはもちろんFFになるはずだ。
PCもシーケンシャルロジックになる。なぜなら命令メモリへのアドレスを保持する必要があるからだ。そうでないと、PC=>+4の加算回路=>MUX=>PCの経路の間にFFが入らず、組み合わせ回路のループになってしまう。
ところで、本ブロック図をもとにHDLで実装する時の注意点を挙げておこう。まず、非同期のメモリは、現在ではあまり一般的ではないため、同期メモリを使用した回路に置き換える必要がある。
また、「6.4データ・ハザードとフォワーディング」では、同じサイクルで同一のレジスタにリードとライトを行った場合、ライト中のデータがリードデータとして読み出されるということになっている。これは、説明を簡単にするための方便だと考えたほうがいい。私はこのようなセルライブラリを見たことがないし、少なくとも一般的なものではないと思う。たとえ存在するにしても、物理レイアウトを考えると、配線が遠回りしてしまうため、使用するのは得策と言えない。
実装時は、MEM/WBパイプラインレジスタからのフォワーディング経路も考える必要がある。
PCに+4?
本書での相対アドレス分岐についての説明は、一貫して、PC+4にオペランドの値を加算するという仕様になっている。
なぜ素直にPCにオペランドの値を足す仕様にしないのか? このほうが、回路も仕様も簡潔になるはずだ。特に、例外発生時には、例外の発生した正確なアドレスをEPCにストアするために、パイプラインレジスタに格納されているPC+4から4を引くという、二度手間を行っている。
それにもかかわらず+4しているのは、MIPSの遅延分岐の仕様を中途半端に再現しようとしているためだと思われる。
MIPS-Iでは、分岐が発生した時は、必ず遅延スロットとなる次のアドレスの命令を実行した後、分岐先に飛ぶ仕様になっている。次のアドレスとは分岐命令+4のアドレスなので、これが分岐の起点となるのは直感的だし、素直な回路構造になる。
一方、本書では遅延分岐は仕様に含まれていない。にもかかわらず、分岐命令の計算方法だけはMIPSと同じ仕様にしてしまっているため、このように違和感を感じる仕様になっているのではないだろうか。
さて、あくまで想像ではあるが、実際のMIPS-Iアーキテクチャでは、PCは+4された値ではなく、PCの値そのものがIF/IDパイプラインレジスタに渡されているのだと思う。MIPS-Iでは遅延分岐があるので、こうしてしまえば、何も考えなくても、相対ジャンプ時のアドレスはPC+4+オペランドとなるし、EPCへの格納もそのままの値を代入すればよくなる。
最後に
本書を再読して、あらためてMIPSアーキテクチャのシンプルな構造に感銘を受けた。コンピュータアーキテクチャに少しでも興味がある人には是非読んでもらいたい。
この説明がパタヘネをよりよく理解するための一助となれば幸いだ。