Sim's blog

電子工作はじめてみました

verilogのノンブロッキング代入

2011-08-17 21:36:02 | FPGA

この記事は書こうかどうか多少悩みました。ちゃんと裏が取れていないところが結構あります。原典の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に関するステートマシンを書くといったこともできます。

まとめ

というわけで、同じ変数への複数のノンブロッキング代入を利用することでマニアックな記述ができることを長々と紹介してきました。あまり凝りすぎると可読性が極端に落ちることに注意が必要です。

この話について紹介しているサイトを見つけることができなかったので、禁じ手なのかもしれません。

 


最新の画像もっと見る

11 コメント

コメント日が  古い順  |   新しい順
Unknown (Oh!石)
2011-08-17 23:13:01
買ったまま塩漬けのCQ Endeavor VerilogHDLによると、ノンブロッキング代入文は
「ひとつの信号に対する複数回の代入禁止」
とハッキリ書かれてあります(p223、O2-(6))。
(塩漬けなので該当箇所の説明を聞いてない^^;)

「動作がツールに依存」とも書いてありますので、合成結果や検証結果が保証されないのではないかと思われます。

#私はVHDL派
返信する
アクロバット的記述ですね (RST)
2011-08-18 01:06:55
かなり変わった記述のように思えました。コンパイラやシミュレータを変えても動作が変わらないかどうか確認してみると良いかと思います。
ちなみに、応用編(2)(3)の回路ですと、resetとstartの両方が0のとき、必ず最後のelse以下の分岐に入りますが、startはステートマシンを初期状態にする制御信号という位置づけなのでしょうか。
返信する
合成向けならアリ (windy)
2011-08-18 11:46:42
論理合成のためのHDLならこの記述はアリで、どんな環境でも同じ動作になるはずです。
CQの本は初心者向けなので単にダメだと書いているのでしょう。

今はどうか分かりませんが、数年前のXilinxのEDKに含まれているIPはVHDLでしたがこの記述でデフォルト値を設定していました。
だからといって積極的に使うかどうかは別ですが。
返信する
re:Unknown (Sim)
2011-08-18 16:51:37
こんにちは、Oh!石さん
情報ありがとうございます。本は持っていなかったので本屋さんに行って眺めてきました。確かにおっしゃられる記述がありました。あちゃー

RTL設計スタイルガイド Verilog HDL編という本によると、IEEE1364-1995というVerilog-1995より前では代入の順序は保証されていなかったそうです。今は規格としては順序が保証されているので今回のような話もありみたいです。ただ、規格があるのとツールがちゃんと対応しているのは別の話というEDA業界ではありがちな話があるので、CQ本のようなことになっているのではないかと思いました。
また、よろしくお願いします。
返信する
re:アクロバット的記述ですね (Sim)
2011-08-18 16:56:06
こんにちはRSTさん
コメントありがとうございます。自分で利用できる範囲のシミュレータ、論理合成ツールでは問題ありませんでした。たまたま動作しているのか、それとも標準規格に基づいて正しい動作なのかといったあたりが不安な点だったりします。IEEE1364-1995が見れる機会があればいいのですが^^
返信する
re:合成向けならアリ (Sim)
2011-08-18 17:03:00
こんにちは、windyさん
情報ありがとうございます。
CQ本のは、IEEE1364-1995より前の代入順序が保証されていなかった頃の名残りかもしれません。
VHDLでも似たようなテクニックが使える可能性があるんですね。調べてみようと思います。
返信する
アクロバチックなw (アプロ)
2011-09-27 10:24:13
lintツールでは、何か怒られそうですw
可読性が低下するというか、? となるので、読むのがストップするというかw
トラップに使うには最高な記述に感じました(笑)
返信する
re:アクロバチックなw (Sim)
2011-09-29 00:43:33
こんにちは、アプロさん

とにかく記述量を減らしたいといったあたりでしょうね。決まりきったことをひたすら書くのも、書き忘れとかがありますし、なんにせよ人間に優しくない言語ですw
最近はtaskを使って書き忘れ防止をしてたりします。
返信する
無視じゃないです (ブ~)
2012-02-05 02:48:16
reg [3:0] a;
always @(posedge clk) begin
a <= 4'h0; // (1) 無視
a <= 4'h1; // (2) 無視
a <= 4'h2; // (3) 有効
end

ノンブロッキング代入(NBA)を正しく理解していれば「無視」という考えには至らないはずです。誤解を招く表現なので「無視」ではなく、せめて「この場合は無効」とでもしてください。下のコードは上のコードを少し書き換えたものですが、aの内容がどうなるか考えてみてください。

reg [3:0] a;
always @(posedge clk) begin
a <= #2 4'h0; // (1) 無視
a <= #3 4'h1; // (2) 無視
a <= #1 4'h2; // (3) 有効
end
返信する
無視じゃないです(2) (ブ~)
2012-02-05 02:53:20
#途中で切れてしまったので全角記号<にしました

reg [3:0] a;
always @(posedge clk) begin
a <= 4'h0; // (1) 無視
a <= 4'h1; // (2) 無視
a <= 4'h2; // (3) 有効
end

ノンブロッキング代入(NBA)を正しく理解していれば「無視」という考えには至らないはずです。誤解を招く表現なので「無視」ではなく、せめて「この場合は無効」とでもしてください。下のコードは上のコードを少し書き換えたものですが、aの内容がどうなるか考えてみてください。

reg [3:0] a;
always @(posedge clk) begin
a <= #2 4'h0; // (1) 無視
a <= #3 4'h1; // (2) 無視
a <= #1 4'h2; // (3) 有効
end
返信する

コメントを投稿