記述順序でハマる落とし穴
目次
HDLはプログラム言語とは違いますので、記述の順番に処理が実行されるとは限りません。しかし、記述によっては順番が影響することがあります。
1. ここで学ぶ内容
概要
シリアルパラレル変換を例に、代入文の記述順序が問題になる場合を学ぶ
目標
- ブロッキング代入文による記述の問題点
- 同一信号に対する複数回の代入
- 問題を起こさない記述順序
修了判定
不具合のある回路を論理合成し、生成された回路を確認して設問に答える
2. シリアルパラレル変換回路
このユニットで例題として使用するのは4ビットのシリアルパラレル変換回路です。この回路は単純なシフトレジスタです。
回路の構成:
- 4つのフリップフロップを直列に接続します。
- 最初のフリップフロップの出力を二つ目のフリップフロップの入力に接続し、その出力を三つ目のフリップフロップの入力に接続し、さらにその出力を四つ目のフリップフロップの入力に接続します。
- 入力
SIは最初のフリップフロップの入力に接続します。 - 出力
Qは4ビットの信号で、4つのフリップフロップの出力です。
この回路は入力 SI からのデータがクロックの立ち上がりのたびに次のフリップフロップに伝わります。SI からシリアルに入力されたデータを4ビットのパラレルデータとして出力 Q から取り出すことが出来ます。
タイミングチャート(期待される正常動作)
| クロック(CK)エッジ | RES | SI | Q[0] | Q[1] | Q[2] | Q[3] |
|---|---|---|---|---|---|---|
| - (初期状態) | 0 | 0 | 不定 | 不定 | 不定 | 不定 |
| 1 (リセット) | 1 | 0 | 0 | 0 | 0 | 0 |
| 2 | 0 | 0 | 0 | 0 | 0 | 0 |
| 3 (SI入力開始) | 0 | 1 | 1 | 0 | 0 | 0 |
| 4 | 0 | 1 | 1 | 1 | 0 | 0 |
| 5 | 0 | 0 | 0 | 1 | 1 | 0 |
| 6 | 0 | 1 | 1 | 0 | 1 | 1 |
| 7 | 0 | 0 | 0 | 1 | 0 | 1 |
| 8 | 0 | 0 | 0 | 0 | 1 | 0 |
3. 記述順を誤ったブロッキング代入
Q2] CLK[CLK] --> DFF DFF --> DOUT[DOUT] end style DFF fill:#ffebee,stroke:#c62828,stroke-width:2px;
Q1] CLK[CLK] --> DFF1 CLK --> DFF2 DFF1 --> DFF2[D-FF
Q2] DFF2 --> DOUT[DOUT] end style DFF1 fill:#e8f5e9,stroke:#388e3c,stroke-width:2px; style DFF2 fill:#e8f5e9,stroke:#388e3c,stroke-width:2px;
ブロッキング代入文(=)を使って、シリアルパラレル変換回路を記述してみます。まずは下位側の代入から記述した例です。
// 4ビット シリアルパラレル変換(NG)
module SERI_PARA_NG1( CK, RES, SI, Q );
input CK, RES, SI;
output [3:0] Q;
reg [3:0] Q;
always @( posedge CK or posedge RES ) begin
if ( RES==1'b1 )
Q = 4'h0;
else begin
Q[0] = SI;
Q[1] = Q[0];
Q[2] = Q[1];
Q[3] = Q[2];
end
end
endmodule
この記述の動作を考えてみましょう。ブロッキング代入文は実行されたとき、即座に値が更新されます。
- クロック
CKの立ち上がりでQ[0]にSIが代入されます。Q[0]の値はSIの値になります。 - 次の行で
Q[1]にQ[0]が代入されますが、このときQ[0]の値はすでにSIの値に更新されています。したがって、Q[1]にはSIが代入されることになります。 - 同様にして
Q[2]にもSIが代入され、Q[3]にもSIが代入されます。
ブロッキング代入文を用いて、下位側の代入を先に記述すると、SI が一度のクロックで全ての出力に伝わってしまい(データ突き抜け)、正しく動作しません。(参考:C4ユニット)
もふねこ:
順序回路(フリップフロップ)を書くときは、絶対にノンブロッキング代入(<=)を使ってね!
ブロッキング代入(=)を使って「データ突き抜け」を起こしちゃうのは、初心者あるあるのバグなんだよ🐾気をつけようね!
タイミングチャート(NG1:データ突き抜けの動作)
| クロック(CK)エッジ | RES | SI | Q[0] | Q[1] | Q[2] | Q[3] |
|---|---|---|---|---|---|---|
| - (初期状態) | 0 | 0 | 不定 | 不定 | 不定 | 不定 |
| 1 (リセット) | 1 | 0 | 0 | 0 | 0 | 0 |
| 2 | 0 | 0 | 0 | 0 | 0 | 0 |
| 3 (SIが1の時) | 0 | 1 | 1 | 1 | 1 | 1 |
| 4 | 0 | 1 | 1 | 1 | 1 | 1 |
| 5 (SIが0の時) | 0 | 0 | 0 | 0 | 0 | 0 |
| 6 | 0 | 1 | 1 | 1 | 1 | 1 |
4. 記述順が正しいブロッキング代入
代入文の順番を変え、逆に上位側の代入から記述してみましょう。
// 4ビット シリアルパラレル変換(OK)
module SERI_PARA_OK1( CK, RES, SI, Q );
input CK, RES, SI;
output [3:0] Q;
reg [3:0] Q;
always @( posedge CK or posedge RES ) begin
if ( RES==1'b1 )
Q = 4'h0;
else begin
Q[3] = Q[2];
Q[2] = Q[1];
Q[1] = Q[0];
Q[0] = SI;
end
end
endmodule
この記述では、Q[3] に Q[2] の値を代入してから、Q[2] に Q[1] の値を代入しています。そのあと Q[1] に Q[0] の値を代入し、最後に Q[0] に SI の値を代入します。
このような順番で記述すると、下位側から上位側への値の突き抜けはおこりません。正しいシリアルパラレル変換の動作になります。
ブロッキング代入文:記述の順番が動作に影響するので、間違えやすい
以上のように、ブロッキング代入文を使用すると記述の順番が動作に影響するので間違いを起こしやすくなります。したがって、回路記述ではブロッキング代入文を使用しません。
5. 記述順に無関係なノンブロッキング代入
シリアルパラレル変換回路をノンブロッキング代入文(<=)で記述してみましょう。
// 4ビット シリアルパラレル変換(OK版)
module SERI_PARA_OK2( CK, RES, SI, Q );
input CK, RES, SI;
output [3:0] Q;
reg [3:0] Q;
always @( posedge CK or posedge RES ) begin
if ( RES==1'b1 )
Q <= 4'h0;
else begin
Q[0] <= SI;
Q[1] <= Q[0];
Q[2] <= Q[1];
Q[3] <= Q[2];
end
end
endmodule
ノンブロッキング代入文は、全ての代入文の右辺を評価した後で、値の更新を行います。したがって記述の順番が変わっても動作は同じです。たとえば、代入の順番を上位からに変更しても同じ回路が合成されます。
else begin
Q[3] <= Q[2];
Q[2] <= Q[1];
Q[1] <= Q[0];
Q[0] <= SI; // 記述の順番が変わっても同じ動作
end
ノンブロッキング代入文による記述は、記述の順番が動作に影響しにくい
したがって回路記述ではノンブロッキング代入文を使います。
6. 同一信号に対する複数回の代入
次に、シフト演算子(<<)を使って記述した場合を考えてみます。
// 4ビット シリアルパラレル変換(シフト演算子)
module SERI_PARA_NG2( CK, RES, SI, Q );
input CK, RES, SI;
output [3:0] Q;
reg [3:0] Q;
always @( posedge CK or posedge RES ) begin
if ( RES==1'b1 )
Q <= 4'h0;
else begin
Q[0] <= SI;
Q <= Q << 1;
end
end
endmodule
シフトの初段には SI を代入し、そのほかのビットはシフト演算子をつかってシフトさせます。しかし、この記述では正しく動作しません。
シフト演算の代入文(Q <= Q << 1;)を分解すると、次のように4つの代入文になります。
else begin
Q[0] <= SI;
// 以下は Q <= Q << 1; の分解
Q[3] <= Q[2];
Q[2] <= Q[1];
Q[1] <= Q[0];
Q[0] <= 1'b0; // どちらが有効になるかわからない
end
結局 Q[0] に対する代入が2回行われています。
always 文の中で一つの信号に対する代入が複数回あると、どちらの代入が有効になるかわかりません。多くのツールではあとに書かれたものが有効になるように動作するため、この場合は Q[0] <= 1'b0; が有効となり、正しく動作しません。
タイミングチャート(NG2:Q[0]が常に0になる動作)
| クロック(CK)エッジ | RES | SI | Q[0] | Q[1] | Q[2] | Q[3] |
|---|---|---|---|---|---|---|
| - (初期状態) | 0 | 0 | 不定 | 不定 | 不定 | 不定 |
| 1 (リセット) | 1 | 0 | 0 | 0 | 0 | 0 |
| 2 | 0 | 0 | 0 | 0 | 0 | 0 |
| 3 (SIが1になってもQ[0]は0のまま) | 0 | 1 | 0 | 0 | 0 | 0 |
| 4 | 0 | 1 | 0 | 0 | 0 | 0 |
代入の順番を入れ替えて Q <= Q << 1; を先に書けば正しく動作するツールもありますが、その動作はツールに依存することになるので推奨できません。一つの信号に対して代入が1回だけ行われるように記述しましょう。
// 動作がツールに依存するため推奨されない
else begin
Q <= Q << 1;
Q[0] <= SI;
end
7. 確実な代入文の記述
前ページまでに紹介した問題は、同じ信号に対する代入文が1回であればおきません。シリアルパラレル変換回路は1ビットずつノンブロッキング代入文をつかえば問題なく記述できましたが、ビット数が増えると代入文が増えてくるので煩雑で間違いやすくなります。
そこで連接演算({})を利用して記述すると、1行で簡潔に記述でき非常にみやすくなります。
// 4ビット シリアルパラレル変換(連接演算子)
module SERI_PARA_OK3( CK, RES, SI, Q );
input CK, RES, SI;
output [3:0] Q;
reg [3:0] Q;
always @( posedge CK or posedge RES ) begin
if ( RES==1'b1 )
Q <= 4'h0;
else
Q <= { Q, SI };
end
endmodule
連接演算を利用して簡潔な記述をする
この場合 Q と SI を連接していますので、5ビットの信号になりますが、最上位ビットの Q[3] は代入するときに欠落し、正しくシフト動作が行われます。連接演算をうまく活用して簡潔な記述をしてください。
8. 修了判定
- 本文中の以下の記述は論理合成すると正しい回路が得られない。
- どのような回路が論理合成されるのか、正しく説明している文はどれか?
- 論理合成ツールで各記述を合成し、結果を確認して1〜4の番号で答える。
- FFが1個しか合成されない。
SIがFFに入力され、FFの出力はQ[3]に接続されている。Q[0]〜Q[2]ポートには何も接続されていない。 - FFが4つ合成され、直列に接続されている。
1段目のFFの入力はGNDに接続されている。各FFの出力がQ[0]〜Q[3]ポートに接続されている。SIポートはどこにも接続されていない。 - FFが4つ合成され、それぞれの出力がQ[0]〜Q[3]に接続されている。FFのすべての入力は共通でSIに接続されている。
- FFが4つ合成される。Q[1]〜Q[3]の3ビットでSIを入力とするシフトレジスタを構成している。
Q[0]ポートに接続されているFFの入力はGNDに接続されている。
よく
できました
論理合成結果の詳細(参考)
以下は、実際に各NG記述を論理合成ツールで合成した際に出力される回路構成と各種レポートです。
SERI_PARA_NG1 の合成結果
4つのD-フリップフロップが並列に配置され、すべての入力(D)に共通して SI が接続されています。各フリップフロップの出力(Q)がそれぞれ Q[0] 〜 Q[3] に直接出力されます。
面積レポート (area)
****************************************
レポート : area
回路 : SERI_PARA_NG1
****************************************
ポート数: 7
ネット数: 8
セル数: 5
セル種類: 2
組み合わせ回路: 1
非組み合わせ回路: 36
合計: 37
タイミングレポート (timing)
****************************************
レポート : timing
回路 : SERI_PARA_NG1
****************************************
ライブラリ: hd350s
配線遅延モデル: hd350s_05k
コンディション: MAX567
----------------------------------------
クロック(input port clock) (rise edge) 0.00 0.00
SI (in) 0.00 0.00
Q_reg[0]/D (FD2) 0.00 0.00
データ到着時刻 0.00
クロック CLK (rise edge) 20.00 20.00
クロックスキュー -1.00 19.00
ライブラリのセットアップタイム -0.80 18.20
データ到着要求時刻 18.20
----------------------------------------
データ到着要求時刻 18.20
データ到着時刻 0.00
----------------------------------------
タイミング余裕 18.20
SERI_PARA_NG2 の合成結果
4つのD-フリップフロップが直列に接続されていますが、1段目の入力(D)は GND(0)に接続されています。SI ポートはどこにも接続されておらず、単に0がシフトし続ける回路になります。
面積レポート (area)
****************************************
レポート : area
回路 : SERI_PARA_NG2
****************************************
ポート数: 7
ネット数: 8
セル数: 5
セル種類: 2
組み合わせ回路: 1
非組み合わせ回路: 36
合計: 37
タイミングレポート (timing)
****************************************
レポート : timing
回路 : SERI_PARA_NG2
****************************************
ライブラリ: hd350s
配線遅延モデル: hd350s_05k
コンディション: MAX567
----------------------------------------
クロック CK (rise edge) 0.00 0.00
Q_reg[1]/D (FD2) 0.00 1.42
データ到着時刻 1.42
クロック CK (rise edge) 20.00 20.00
クロックスキュー -1.00 19.00
ライブラリのセットアップタイム -0.80 18.20
データ到着要求時刻 18.20
----------------------------------------
データ到着要求時刻 18.20
データ到着時刻 -1.42
----------------------------------------
タイミング余裕 16.78