パイプラインの記述とその 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 が同時に動作したと考えると,次のような場合に
条件が満たされなくなる.
- set up time が満たされない場合
D-FF の前の入力が確定していない.
つまり,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 発展
時間に余裕があれば、さらに次のようにパイプラインを拡張してみる.
- 演算回路を持つステージを作成
パイプラインを流れる 32 bit データをもう一つ増やし,
これらの加算を行うステージを設ける.
- 命令解釈の想定
複数種類の命令を実行することを実行することを考える.
命令種を示す信号を増設し,演算ステージでこの信号によって
演算を切り替えてみる.
- 複数サイクルをかけるステージを設ける
複数サイクルを要する命令を想定してみる.
別に特殊な命令であることを示す信号を増設し,
この信号が有効であるときのみ,特定ステージで複数のサイクルを
要するように変更する.