[パタヘネ:読書メモ]第4章 プロセッサ その2

4.4 単純な実現方式


ここでは、まずシンプルな命令だけを実行できるCPUを作る
4.1でも書いたけど、サポートする命令を再掲しておく。

メモリ参照命令  : lw(load word), sw(store word)
算術論理演算命令: add, sub, and or, slt
ジャンプ命令    : beq, j



演算を行うためにはALUが必要だけど、今回使用するALUは、以下の演算を行う事が出来るものとする。
Cプログラマからするとアセンブリ命令は非常に限られている(=表現力が乏しい)けど、アセンブリからみるとALU回路が行える計算処理は非常に限られている。

ALU制御コード 機能                演算内容
------------- ------------------  --------------------
0000          AND                 F = A & B
0001          OR                  F = A | B
0010          ADD                 F = A + B
0110          SUB                 F = A - B
0111          SLT(set less than)  F = ( A < B ) ? 1:0
1100          NOR                 F = ~ ( A | B )


参考:CS281 MIPS ALU

上記表の”ALU制御入力”というのは、機械語のopcodeとは関係なくALU装置の信号線の値を指している。信号の値はMIPSの仕様を流用している(のかな…?詳細は不明)

アセンブリの命令は、最終的にALUの上記各機能のどれかを使用して実現される。

CPUの命令を実行するためには、アセンブリ命令からALU制御信号を生成する必要がある。
この為に、制御ユニットという回路が必要。どんな回路が必要か考えるために、アセンブリ命令とALU制御信号の対応表を作ってみる。

この辺は、MIPSの命令コード表を見ながら考えると分かりやすい

アセンブリ  opcode   ALUOp 機能コード ALU機能  ALU制御コード
lw          lw       00    XXXXXX     add      0010
sw          sw       00    XXXXXX     add      0010
beq         beq      01    XXXXXX     subtract 0110
add         R形式    10    100000     add      0010
sub         R形式    10    100010     subtract 0110
and         R形式    10    100100     and      0000
or          R形式    10    100101     or       0001
slt         R形式    10    101010     slt      0111



opcodeはMIPS命令の先頭6bitで、機能コードは末尾6bitを指す。

ALUOpというのは、どのパターンの制御を行うかの指定で、以下の意味を持つ…
(この部分余り分かってない。あとで再確認する。)

00 加算
01 減算
1X 命令どおりの演算



beqのALUOpは01なので減算処理になるけど、その理由は2つの値を引いた結果が0になるかを判定しているから。
(等しければ、結果としてALUのゼロ判定フラグがアクティブになる)


命令によっては、演算を行った結果をレジスタに保存する。
R形式や、I形式の複数命令フォーマットがあるので、保存先レジスタを指定しているビット位置は異なる。

なので、レジスタファイルのユニットに対する入力は、MUXをかましてビット位置を指定できるようにしておく。


というわけでMIPSにおける”演算処理”は、ALUOpの決定と、ALUによる演算という2ステップに分けて考えられている。
これはなぜかというと、制御を複数のステップに分け、小さな制御ユニットを複数使用したほうがHW的に高速化しやすいから。


単一サイクルの設計(CPI=1)に基づいてデータパスを設計しても正しく動作させる事は出来るが、パフォーマンスが悪いので実際には行われない。なぜかというと、クロック周波数が最も時間が掛かる命令に引っ張られるから。(通常はsw,lwのメモリ操作系が最も遅くなる)



4.5 パイプライン処理の概要


パイプライン処理を行う事で、複数の処理を平行して行えるので、見かけ上の処理速度を向上させる事ができる。但し、1命令の実行に掛かる速度は変わらない。

パイプライン処理を有効に活用するためには、以下の条件が満たされていると良い

各ステージの実行時間が等しい事
ステージ数が程よく多い事



各ステージの実行時間が大きく異なると、最も遅いステージの実行時間に全体が引っ張られてしまう。
またステージ数が少ないと、あまりり並列化ができないので効率向上させにくい。一方でステージ数が多すぎると、パイプライン処理がストールしたときにダメージが大きくなるのでよろしくない。

MIPSでパイプライン化を検討する際は、以下のステージ分けを行う

IF: 命令のフェッチステージ
ID: 命令のデコードステージ
EX: 実行ステージ
MEM:メモリアクセスステージ
WB: 書き戻しステージ(write back to register)



MIPSの場合は命令長がすべて32bitだから命令のフェッチは高速化させやすい(回路が簡単になるので)、x86だと命令長が1~17byteの可変長なのでフェッチを高速化させるのが難しくなる。
なので、最近のIntelのアーキテクチャでは、x86命令を1つ以上のマイクロオペレーションに分解し、これをパイプライン化させるという手間を踏んでいる。


パイプラインのハザード


綺麗にパイプライン化できれば勿論実行効率は良くなるが、実際はうまくはいかない。
パイプライン処理が失敗する事をパイプラインハザードと呼び、以下のような要因がある。

構造ハザード
    回路設計的にパイプラインとして一緒に実行できる命令の組み合わせに制限がある。
    この制限に引っかかった場合は、パイプラインを止める必要が出てくる。
 
    ただし、コンパイラが賢ければ、構造ハザードはかなり防ぐ事ができる。
 
データハザード
    他のステップが完了するのを、別のステップが待つ必要がある場合に発生する。
    例えば、一個前の命令で操作したレジスタを次の命令で使用する場合、
    レジスタ値の取得をやり直す必要がでるので、パイプラインが止まる。
 
    ただ、この手の処理はよくあるパターンなので、構造ハザードと異なりコンパイラで
    対応する事は困難になる。
 
    なので、計算結果をレジスタからではなくALUの演算結果から直接持ってくるなどの
    バイパス処理(forwarding, またはbypassing)を行って対応する事が多い。
 
制御ハザード
    分岐命令の場合、分岐判定が決まるまで、次に実行する命令が分からない。
    なので、分岐命令の結果が決まらないと、次の命令をフェッチする事が出来ない。



データハザードに関しては、完全に防ぐ事は出来ない。
待ちが出る場合は、パイプラインの処理を一時停止させる処置を行い、これをパイプラインストールと呼ぶ。また、ここで発生する無駄な待ち(ストール)のことをバブルと呼ぶ場合もある。


制御ハザードに関しては、分岐命令の発生する頻度は結構高く、配慮が必要なので、一般的には分岐予測を行う。
簡単な方法としては、最適化しがいのある分岐はループ処理で、ループ処理中での分岐は低位アドレスに飛ぶことが多いので、低位アドレスへの条件付分岐がある場合は、ジャンプするとみなして処理を進めてしまうという手法がある。また、過去の分岐履歴の統計値を取っておいて判断材料にするという方法もあり、このような方法を取る事で最近では90%ぐらいは的中させる事ができる。

制御ハザードの対策には、前に説明した遅延分岐の処理を行う手法もあり、MIPSでは実際に採用されている。これは、分岐が発生しないとみなして分岐先の命令を仮実行してしまう。遅延分岐の処理はハードウェア的に実行されるので、機械語レベルで意識する必要は無い。



最初にも書いたけど、命令の実行をパイプライン化しても、ある1命令の実行に掛かる時間は短くならないことに注意.
(レイテンシは改善しない)

4822284786
コンピュータの構成と設計(上)

関連記事

One Response to “[パタヘネ:読書メモ]第4章 プロセッサ その2”

  1. 匿名 より:

    超ためになりました!

コメントを残す

メールアドレスが公開されることはありません。