この記事は書こうかどうか多少悩みました。ちゃんと裏が取れていないところが結構あります。原典のverilogの規格にあたるのがいいんでしょうが、そこまでできていません。色々と突っ込みどころがあると思いますのでコメントいただけるとありがたいです。
Verilogには2種類の代入文があります。私はノンブロッキング代入(<=)を順序回路に、ブロッキング代入(=)を組み合わせ回路に使っています。
今回の話は、同じ変数への複数のノンブロッキング代入があるときの動作とその応用についてです。
基本編(1)
まずは基本編です。同じ変数への複数のノンブロッキング代入があるときは、最後の一つだけが有効になります。
reg [3:0] a; always @(posedge clk) begin a <= 4'h0; // (1) 無視 a <= 4'h1; // (2) 無視 a <= 4'h2; // (3) 有効 end
この例だと、(1)と(2)は無視されて(3)だけが実行されるので、aは4'h2になります。
単なる無駄ですね。
基本編(2) ifがある場合
ifがあるときは条件に従って最後の一つが有効になります。
reg [3:0] a;
wire flag;
always @(posedge clk) begin
a <= 4'h0; // (1)
if(flag) a <= 4'h1; // (2)
end
この例だと、flagが1'b1のときは(2)が一番最後、flagが1'b0のときは(1)が一番最後になります。
この例はelseを省略したときのデフォルトの動作を(1)が決めていると考えることができます。
基本編(3) caseがある場合
caseの場合も同じように一番最後の一つが有効になります。
reg [3:0] a;
wire [1:0] c;
wire flag;
always @(posedge clk) begin
a <= 4'hf; // (1)
case(c)
2'h0 : a <= 4'h0; // (2)
2'h1 : ; // (3)
2'h2 : if(flag) a <= 4'h2; // (4)
default : a <= 4'h4; // (5)
endcase
end
この例だと次のようになります。
cが2'h0のときは(2)が最後のaへの代入なのでaは4'h0になります。
cが2'h1のときは(1)が最後のaへの代入なのでaは4'hfになります。
cが2'h2のときはflagが1'b1のときは(4)が最後のaへの代入なのでaは4'h2になります。flagが1'b0なら、(1)が最後のaへの代入なのでaは4'hfになります。
cが2'h3のときは(5)が最後のaへの代入なのでaは4'h4になります。
この例は、(3)や(4)のelseのように省略したときのデフォルトの動作を(1)で決めていると考えることができます。
基本編のまとめ
ここまでをまとめると、複数ノンブロッキング代入は省略時のデフォルト動作を決めることに使えるということが分かりました。
ただし、複雑になるのでソースが読みづらくなったりバグの元になりやすくなります。基本的には複数ノンブロッキング代入は使わない方がいいです。
ここから後では、複数ノンブロッキングも使いようによっては便利になる場合もある、ということについて考えてみたいと思います。
応用編(1) エラーの検出
基本編(3)は考えようによっては、(3)のようにaへの代入を書き忘れたり、(4)のようにelseを書き忘れたりといった、書き忘れた系のエラー検出を積極的に行うことに利用できます。書き忘れたときには(1)が実行されるのでaの値が4'hfになります。
応用編(2) ステートマシン記述の簡略化
今までの例題ではデフォルトの動作として定数の代入しかしていませんでしたが、何か式の代入をしてもいいです。
reg [3:0] s; // ステート
wire start;
reg [7:0] ctr;
always @(posedge clk, posedge reset) begin
if(reset)
s <= 4'h0;
else if(start) // start信号でステートマシン開始
s <= 4'h1;
else begin
s <= s + 4'h1; // デフォルトの動作 (1)
case(s)
4'h0 : s <= s; // アイドルステート s <= 4'h0 (2)
4'h1 : ; // s <= 4'h2を省略 (3)
4'h2 : ; // s <= 4'h3を省略
4'h3 : ctr <= 8'h00; // s <= 4'h4を省略
4'h4 : ; // s <= 4'h5を省略
4'h5 : ; // s <= 4'h6を省略
4'h6 : begin // 256回ループ (4)
ctr <= ctr + 8'h1;
if(ctr != 8'hff) s <= 4'h4;
end
4'h7 : ; // s <= 4'h8を省略
4'h8 : s <= 4'h0; // s <= 4'h0 (5)
endcase
end
end
この例では、デフォルトの動作としてステートを覚えておくレジスタsを1増やしています(1)。リセット直後はsは4'h0なので状態はアイドルステートになります(2)。アイドルステート(2)では、ずっとsの値が4'h0のままなので無限ループになっています。start信号が1'b1になったときは、sは4'h1になるのでステートマシンが動作を開始します(3)。ステート4'h1ではsへの代入文を省略しているのでsに関してはデフォルトの動作をします。つまりsに1を足してsは4'h2になります。
分岐したいときは条件に応じてsに代入する値を変更します。(4)ではctrの値が8'hffでないときはsに4'h4を代入することで256回ループを実現しています。ifの条件が成立しないときはデフォルトの動作を行うのでsに1を足して4'h7へ状態遷移します。(5)は単純にsに直接値を入れることで4'0に状態遷移しています。
結局、ステートの状態遷移のデフォルトの動作を決めることで、ステートマシンの記述を簡略化したという例となっています。
ちなみにデフォルトの動作はelse節の最初に書いています。論理合成の際の非同期リセットの記述はパターンマッチングで行われるので、alwaysの直後には必ずリセットに関するifがないといけません。
応用編(3)
デフォルトの動作にifを使うこともできます。
reg [3:0] s; // ステート
reg wr; // 制御信号
wire start;
reg [7:0] ctr;
always @(posedge clk, posedge reset) begin
if(reset) begin
s <= 4'h0;
wr <= 1'b0;
end
else if(start) begin // start信号でステートマシン開始
s <= 4'h1;
wr <= 1'b0;
end
else begin
s <= s + 4'h1; // sのデフォルトの動作
if(wr) wr <= 1'b0; // wrのデフォルトの動作(1)
case(s)
4'h0 : s <= s; // アイドルステート s <= 4'h0
4'h1 : ; // s <= 4'h2を省略
4'h2 : wr <= 1'b1; // s <= 4'h3を省略(2)
4'h3 : ctr <= 8'h00; // s <= 4'h4を省略、wr <= 1'b0を省略
4'h4 : // s <= 4'h5を省略
4'h5 : // s <= 4'h6を省略
4'h6 : begin // 256回ループ
wr <= 1'b1; // (3)
ctr <= ctr + 8'h1;
if(ctr != 8'hff) s <= 4'h4;
end
4'h7 : ; // s <= 4'h8を省略
4'h8 : s <= 4'h0; // s <= 4'h0 (5)
endcase
end
end
この例は応用編(2)の例にwrという制御信号を追加しています。wrに関するデフォルトの動作は(1)になります。wrが1'b1のときは次のクロックで必ず1'b0になるという動作になります。ステートの4'h2ではwrを1'b1にしています。次のステート4'h3ではwrは1'b0になります。また、(3)では次のステートはctrの値に依存して4'h4または4'h7のどちらかになります。どちらに分岐してもwrの値は自動的に1'b0になります。
この例ではデフォルトの動作に記述することで、wrがHになるのは1クロックだけであることを保証しています。
さらに応用すると、ifの代わりにwrに関するステートマシンを書くといったこともできます。
まとめ
というわけで、同じ変数への複数のノンブロッキング代入を利用することでマニアックな記述ができることを長々と紹介してきました。あまり凝りすぎると可読性が極端に落ちることに注意が必要です。
この話について紹介しているサイトを見つけることができなかったので、禁じ手なのかもしれません。