パイプラインの記述とその RTL シミュレーション


1. はじめに

今回はまずパイプラインの概念を理解する. そして,verilog-HDL を用いて単純なパイプラインを記述し, RTL シミュレーションを行う. 以下,2 節でパイプラインの概念を説明し, 3 節でパイプラインレジスタを構成する D-FF とその使用方法について述べる. 4 節で実際にパイプラインを記述し,5節で RTL シミュレーションを行う.

2. パイプラインとは?

2.1 パイプラインの概念

元々パイプラインとはその名の通り配管のことであって, 石油やガスの輸送管に使われる言葉である. 転じてパイプ状に流す機構を何でもパイプラインと呼ぶ. 計算機においては,データ(命令)を流す構造のことである. すなわち,データなり命令なりを順次送る機構であれば, それはパイプラインである. 単一の液体を送る場合と違って(現実のパイプラインにも時間的な切替で種類の違う液体やガスを送る仕組みはある), 複数の情報を送るためには個々の情報が区別できるようにしなければならない. そこで,図1 に示すように, パイプラインレジスタとよばれる記憶素子を設け, このパイプラインレジスタ間で順次データを受け渡していく.


図1 パイプラインの概念

2.2 クロック

パイプラインでデータを送るときは, 後から送ったデータが前のデータを書き潰さないようにしなければ ならない. そこで,なんらかの基準が必要である. 一般的にはシステム全体で基準となる信号を設け, 同一のタイミングで一斉にデータを送る. これがクロックである. クロックには様々な形式が有るが, 今回は単相クロックのみを対象とする.

3. D-FF

3.1 D-FF の動作

通常パイプラインレジスタには D-FF が用いられる. D-FF とは図2, 表1 のような素子で,クロックの立ち上がり (もしくは立ち下がり)で D 入力の値を取り込み Q (Q)へ出力し保持するものである.図2 中 (↑)はクロックの立ち上がりを示す. クロックの立ち上がり(もしくは立ち下がり)のみで信号が変化するため, データがパイプラインを突き抜ける(race する)事無く送られる.


図2. D-FF
表1. D-FF の真理値表
入力
D
クロック
CLK
出力
Q Q
0 1(↑) 0 1
1 1(↑) 1 0
X 0 Q Q

この D-FF を用いてパイプラインレジスタを構成し, 間に組み合わせ論理回路を挟んだものがパイプラインである. つまり,図1 をもう少し詳細化して書くと図3 のようになる.


図3 D-FF とクロックとパイプライン

3.2 set up time と hold time

この D-FF が正しく動くためには set up time と hold time を満たす必要がある. set up time はクロック立ち上がり(立ち下がり)の前に D-FF の 入力が確定しているべき時間で,hold time はクロックの立ち上がり(立ち下がり) 後に D-FF の入力を保持しているべき時間である. 今,クロックの位相は完全に揃っており 全ての D-FF が同時に動作したと考えると,次のような場合に 条件が満たされなくなる. 実際にはどのような LSI でも,clock skew(clock のずれ)を 完全に取り除くことはできない. したがって,set up time と hold time と clock skew を クロックサイクルから引いた残りが, 組み合わせ回路の状態が変化しても良い時間である.

4. パイプライン記述

ここでは,パイプラインをどのようにして verilog-HDL で記述するかを説明する. その例として単に 32 bit のデータを下流にに送り続けるものを考える. この間に何の処理も演算も行わないが, データを流すと言う意味ではこれも立派なパイプラインである. 以下,パイプラインを記述する場合のモジュールの基本構造, データの受け渡し,ストール信号について説明する. なお,計算機のパイプラインでは伝統的に上流を前,下流を後と表現する. 以下前段と言えば一つ上流側のステージ,後段と言えば一つ下流側のステージを指す.

4.1 パイプラインステージモジュール

パイプラインステージ用のモジュールと言っても特別な記述が有るわけでは無い. 基本はやはり,リスト1 の形式である.
    リスト1 モジュールの構造の概要
    module counter4(ポート名, ポート名,,, );
       ポート記述

       変数記述

       組み合わせ回路記述

       always @(posedge clk or rst)
         begin
           順序回路記述
         end
    endmodule
ここで,組み合わせ回路をどちら側に書くかによって, 図4 のようにパイプラインの分割法に三通りが考えられる. 図4 (A) はパイプラインレジスタとその入力に繋がる組み合わせ回路を 同一のモジュールに記述する方法である. (B) はパイプラインレジスタとその出力に繋がる組み合わせ回路を 一モジュールとして記述する方法である. (C) はパイプラインレジスタを含みその前後の組み合わせ回路を 一モジュールとして記述する方法である.

一応どれでも記述することは可能であるが,以下の理由で (A)を採ることにしたい.まず,(C) は論外である. (C)を採るとひとかたまりで有るべき組み合わせ回路が複数の モジュールにまたがってしまって判読不能になってしまう. 次に (A) か (B) かであるが,(B) を採ると入力はそのまま パイプラインレジスタに代入する必要があり, always 部の中に if 文も何も書けなくなってしまう. また,パイプラインステージの定義として, 「何か仕事をしてからデータを保持し,後段に送る」 と考えた方が解りやすい.

そこで,各パイプラインステージは 図4 (A) のような構造とし, ステージの最後でパイプライレジスタに代入し, パイプラインレジスタの出力をモジュールの出力に直接 assign 文で繋ぐ. この assign 文は一見代入のように見えるが、 実際には配線の接続である. assign 文で繋がれた線は全て同電位となってしまう. したがって、複数の場所で別の値を設定する事ができない事に注意する.

(A)
(B)
(C)
図4 パイプラインステージのモジュール記述

4.2 データの受け渡し

パイプラインの各ステージは後段へデータを送るが, 常に処理すべきデータが有るわけではなく,空きが生じている場合がある. したがって,必要なときのみ後段へデータを送る, つまりパイプラインレジスタへ値を取り込む必要がある. これを判別するには,単純にデータが存在していることを示す信号を設ける. この信号が真であるときのみパイプラインレジスタに値を取り込む.

4.3 ストール信号

パイプラインレジスタの各ステージは, 後段がデータを受け入れ可能で無いときは,データを送信してはならない. このような場合にはパイプラインレジスタへ値を取り込まず, 待たなくてはならない. また,今行っている処理が一サイクル中に終わらない場合は, 前段のステージへ待ったをかける必要がある. これを行うには,ストール信号を用いる. 後段からストール信号が来ている場合は,パイプラインレジスタへ値を 取り込まない. もちろん,今処理すべきデータがあり,かつ後段からストール信号が 来ている場合は前段へストール信号を出す必要がある.

4.4 パイプラインステージモジュール

リスト2 にパイプラインステージモジュールの例を示す. 4.1 〜 4.3 節までの方針に沿って記述したモノである. この例では,後段からのストールが無いとき, 単純に前段からのデータを送っている. 同時に valid も送っているため,valid が有効でない場合は 意味のないデータを送っていることになる. しかし,valid によって意味がないと言うことが判るので, これで大丈夫である.
    リスト2 パイプラインステージモジュール
    module a_stage(clk, rst,
                   v_i,
                   v_o,
                   data_i,
                   data_o,
                   stall_i,
                   stall_o
                   );
    
       input  clk, rst;   // clock , reset
       input  v_i;   // valid in, if (v_i == 1) valid
       output v_o;   // valid out, if (v_o == 1) valid
       input [31:0] data_i;     // data in
       output [31:0] data_o;    // data out
       input         stall_i;   // stall in, if (stall_i == 1) stall
       output        stall_o;   // stall out, if (stall_o == 1) stall
    
       // pipeline registers
       reg           v_r;       // valid register for output
       reg [31:0]    data_r;    // valid register for output
    
       // connecting registers to output
       assign        v_o = v_r;
       assign        data_o = data_r;
    
       // stall to previous stage
       assign        stall_o = (v_r & stall_i);
    
    
       always @(posedge clk or negedge rst)
         begin
            if (~rst) // reset
              begin
                 v_r <= 0;    // reset valid reg
                 data_r <= 0; // reset data reg
              end
            else
              begin
                 if (~stall_i) // valid and not stall
                   begin // transfer to next stage
                      v_r <= v_i;
                      data_r <= data_i;
                   end
              end // else: !if(~rst)
         end // always @(posedge clk or negedge rst)
    endmodule // a_stage

5. パイプラインの RTL シミュレーション

パイプラインの RTL シミュレーションといっても, 特に変わるところはない.基本的な手法は前回用いたものが流用可能である. ただし,パイプラインのように観測すべき対象が多くなってくると, 表示に工夫を凝らさなければ非常に判読しにくくなる. 例えば次のように表示してみよう. この例では表示桁を揃え,サイクルの切れ目に現在のサイクル数と 目立つ文字を表示している. 同様にステージ数を増やしていけばパイプラインの動作が目で追えるようになる.
    リスト3 表示の工夫
    $display("####### cycle %h #################", i);
    $display("*** stage0 input  ***");
    $display("v data     stall");
    $display("%b %h %b", stage0.v_i, stage0.data_i, stage0.stall_i);
さらに,less 等で表示を行えば判りやすい. この時にウインドウの表示行数をイジって, 一サイクルの表示行数の整数倍になるようにすれば視線移動が少なくて見やすい. また,less のサーチを使うと良い. less ではサーチされた語が一定の行に表示される.

6. 課題と発展

6.1 パイプラインの構成

top モジュールを作成し, そこからパイプラインステージモジュールを 3 つ呼び出して 3 段のパイプラインを構成する.各パイプラインステージは同一の構造で良い. 複数モジュールを呼び出した場合の接続方法は verilog-HDL 入門 4.8 モジュール呼び出しと,モジュール間接続 を参照。

6.2 3 段パイプラインの RTL シミュレーション

6.1 で作成したパイプラインを RTL シミュレーションする. 以下の項目を確認する. このようなシミュレーションの場合 for 文で回しただけでは 必要なタイミングを作り出せない事がある. その場合は,単純に時系列に沿って信号の変化を書き並べて シミュレーションする.書き並べた信号の変化が長くなりすぎる時は 別ファイルとし,`include を使って読み込む事を考えてみる.

6.4 発展

時間に余裕があれば、さらに次のようにパイプラインを拡張してみる.