Execution モジュールの作成


1. はじめに

今回は実行モジュールを作成する.実行モジュールは実際に 命令に示された演算を行う部分であり,複数の演算器から構成される. プロセッサによってその構成は異なるが,多くの場合 整数演算器,浮動小数点演算器,分岐,ロードストア 等の機能を実現する. ここでは各々の演算に必要なハードウエアの構成方法について考える.

2. 命令のグループ分け

ハードウエアで演算を行うため, 演算器によって実行可能な演算は異なる. そこで,実行モジュールは複数の演算器を備え, 全ての命令が実行できるように設計する. このため,実行モジュールを設計するには, まずどのような演算器が必要かを考え, 命令をグループ分けする事から始める.

金星プロセッサの場合,浮動小数点演算以外の 命令を表1 のようにグループ分けすることが できるはずである.

表1.命令のグループ分け
命令カテゴリ必要とする演算器
整数加減算加算器
整数乗算乗算器
整数除算除算器
シフト演算シフタ
論理演算論理演算回路
ロード・ストア加算器
分岐加算器
ここで,加算器が非常に多いことに気がつく. 金星プロセッサは一命令ずつ実行する単純なパイプラインであるから, 同時に実行ステージに存在する命令は一つだけである. したがって,これらの加算器を共有することが可能である. しかし,各々のグループ毎に別々のモジュールを作って独立した演算器を 持った方が見通しが良く,設計が楽である.

商用プロセッサの場合は,使用目的に合わせて設計を変えることが考えられる. 消費電力やチップ面積の制限が厳しいときには,ある程度共有して小さくする. そのような制限が無いときには設計の容易さを優先させた方が良い場合も考えられる. また,スーパースカラへの発展を考えている場合には各実行ユニットを 独立させた方が良いかも知れない.

今回は設計の容易さを採り,各カテゴリ毎に独立した演算器を用いることを勧める.

3. モジュール分割

命令のカテゴリ分けに従って例えば図1 の様にモジュール分割を考える. おおよそカテゴリ毎にモジュールを作れば良いであろう. これらに加えて, 後段へ出力するためのパイプラインレジスタを持つモジュールを設けることにする. パイプラインレジスタを独立したモジュールとするのは できるだけグルーロジックを生じさせないようにするためである.


図1 実行モジュールの構造

4. 各実行モジュール作成

必要な実行モジュールを作成する. 各々のモジュールは順序回路無しの組み合わせ回路として記述する. 以下各々について注意点を示す.

4.1 整数加減算

特に複雑なところは無いが,符号付き演算と即値に注意する. 符号付き演算も符号無し演算も,演算器の動作としては変わりないはずであるが, きちんと条件分岐ができるためにはフラグが正確に出力されることが必要である. また,符号の有無によって即値の拡張方法が異なる.

4.1.1 即値拡張

デコードステージから送られてくる即値が 32 bit に満たない場合は, ここで 32 bit に拡張する. 符号無し演算と符号有り演算があるので,符号有り演算の場合は MSB(Most Significant Bit:最上位ビットのこと)を拡張するようにする.

4.1.2 加算器の複製防止

verilog-HDL では "+" 演算子で加算を,"-" 演算子で減算を 行うことができる. そこで,たとえばリスト1(a)のように記述したくなるであろう. しかし,条件は論理合成においてセレクタと成ることが多いため, これでは二つの加算器が合成されてしまう場合がある. そこで,リスト1(b)のように先に 1 の補数をとり, 加算時に minus 信号を加えることにより, 加算器が複製されることを防ぐと良い. これにより,僅かな記述の追加で,大きな論理回路の複製を避けることができる. このような記述は大きな演算器を使う場合に良く用いられる.
    リスト1 加減算器の記述
    (a) 加算器が複製されてしまう例

    input [31:0] opr0;
    input [31:0] opr1;
    input minus;
    output [32:0] result;

    assign result = minus ? (opr0 - opr1): (opr0 + opr1);

    (b) 加算器の複製をしない記述
    input [31:0] opr0;
    input [31:0] opr1;
    input minus;
    output [32:0] result;

    wire [31: 0] tmp;

    assign tmp = minus ? ~opr1: opr1;
    assign result = opr0 + tmp + minus;

4.2 整数乗算

乗算において問題となるのは, 例えば 32 bit x 32 bit の演算を行うと, 結果が最大 64 bit になってしまうことである. この結果を書き込むレジスタは 32 bit であるであろうから, 情報が失われることになる.

これに対処するため 二つのレジスタに結果を格納する,単純に 上位 32 bit を捨てる, といった幾つかの方法が考えられる. 各々一長一短があるが,金星プロセッサでは上位 32 bit を捨てる方式を 採ることにする. 確実に下位 32 bit の値を結果とするため,中間変数を 64 bit で定義し, 明示的に下位 32 bit を結果へ代入すると良い.

4.3 シフタ

シフト演算では右シフト左シフトあるいは算術シフトといった具合に, 命令によってシフト方式を換える必要がある. したがって,ここでも演算器の複製の問題が生じる. シフタは大きな論理回路であるため(加算器よりも大きくなる事が多い) できるだけこの複製を避けたいが, 加算器と違って入力を換えるだけではシフタを共有できない.
うまくシフタを共有するためにはかなり複雑な記述が必要になるため, 本演習の範囲を超える. ここでは単純に if 文等で条件を記述してシフト動作を指定するに留める. 余裕が有れば,自分でシフタを組むか,ライブラリ(DesignWare 等)を呼び出すことを考える.

4.4 ロードストア

ロードストアユニットでやるべき事は,アドレス計算とメモリのアクセスである. アドレス計算ではデコードステージから送られてきた 即値とベースになるレジスタの値を加算する. デコードステージで,アドレスのベースとなるレジスタを読み込んでオペランドとして 送っているはずであるから,ここではそのオペランドと即値を加算する. 即値は符号付きとして拡張する.

計算したアドレスをメモリへ出力しアクセスする. 簡単のために,金星プロセッサはデータ専用メモリを持ち, 命令メモリで用いたメモリモジュールをデータ専用メモリにも使うことにする. これは同期メモリであるから,読み出した結果は次のサイクルで出力される. 従って,このメモリから読み出される値を, 実行ステージのパイプラインレジスタで保持するより, 直接次のライトバックステージへ出力する方がよい. そうしないと,ロード命令のみ 1 サイクルよけいにかかることになる.

4.5 分岐

分岐モジュールの機能は, condition code とフラグを調べて分岐するか否かを判定することと, 分岐先アドレスを計算することである. 分岐判定を行って分岐することが確定したら, instruction fetch ステージと instruction decode ステージへ分岐確定の信号を送る.

また,分岐先アドレスを計算し instruction fetch ステージへ送る. 金星プロセッサには相対アドレス分岐があるから, プログラムカウンタの値と飛び先への差分を加算しなければならない. このプログラムカウンタの値とは,現在のプログラムカウンタの値ではなくて, 今実行している分岐命令が書かれていたアドレスの事である. したがって,instruction fetch ステージから instruction decode ステージを 通して,分岐命令が書かれていたアドレスが送られてきていなければならない. おそらくそのような記述をしていなかったであろうから, 前段の二つのステージを拡張して必要なアドレスを命令と組にして 実行ステージまで送るようにする. なお,ここで加算される即値は符号付きとして拡張する.

5. サブモジュールの結合

execute モジュールを作成し,サブモジュールを結合する. できるだけグルーロジックが出ないように,各モジュールを設計し, execute モジュールはこれらのサブモジュールを結合するのみとする. 各々の演算器モジュールの入力はバス結合で良いであろう. ほぼ同じようなオペランドと即値の入力を必要とするはずである. 演算器モジュールの出力はパイプラインレジスタモジュールへ送られ, ここでセレクタを用いて選択されることにする. ロード・ストアモジュールの出力のうち,メモリから読み出された値だけは パイプラインレジスタを介さずに直接出力に接続する.

6. 課題