ブロッキング代入は何時おきるか


なお、ノンブロッキング代入が、ではない。継続代入でもない。
以下は ブロッキング代入とノンブロッキング代入 に対する感想でもあるが、この解説ページ、タイトルと違い「ブロッキング代入」に対する説明は少ない。ちょうどそのエアポケットに入りこんだ形になった。

「ブロッキング代入とノンブロッキング代入」の解説ページ

ノンブロッキング代入に対する疑問点が無かったこともあって最近まで読んでなかった (まあ VHDL の signal と同じだし)。 言語の semantics をいちいちシミュレータのスケジューラ層まで降りて解釈・説明しなければならないようではダメだろうってことで。

C 言語の解説で、たとえばポインタの説明で CPU のアドレスバスの動きから書いてるようなもん。 著者の頭ん中でこれできちんと応用がきくようなら、もうすこしまともな抽象があるはずで、 それならそっちで説明すればいい話。
... だったんだが Verilog 2001 の文法書自体がそもそもそうなってるぽいのが超びっくりだ。マジかよ。 SystemVerilog の文法書(の前半) はそんな酷くなかったような。 言語の semantics を具体的なマシンの動作で定義するのはいろいろ禍根を残すんだけどな。C 言語の型変換とか。

ノンブロッキング代入

よくある誤り方が示唆的なのでノンブロッキング代入の場合を先に扱う。 と説明されるケースがあるらしい。単純な例:
always @(posedge clk) begin
   A <= x;
   B <= y;
end
において、x, y の評価は 1ns くらい、わるくても 10ns を越えることはないだろうから、シーケンスポイントが最下部に到着するのは 10ns ないだろう。 しかし clk の立ち上がりから 10ns 後に A の代入が起きたりはしない。そんなコトになるのは clk が 100MHz あたりの場合だけである。

Verilog の semantics がいちいちもってまわって inactive event なるものを扱うのは 言葉で書くとさりげなく無視されるヒマな時間をどこに挿入するか明確にするためだろうから、 記述の正確さをみたければ回路をヒマにしたときに何が起きるかみるといい。

さて。上記は

の一文を加えると誤りでなくなる。

シーケンスポイントが最下部に到着したときに A, B への代入は 宿題に化ける。 その宿題を片付けるのは次の always のイベント、つまり次の posedge clk の直前になる。
.. 上でスケジューラ層に降りるなよとかモンクつけといてスケジューラのイベントスケジューリングを扱ってるような気がするのは気のせいだ(殴)。

宿題の遅延は always @(posedge ...) にのみあらわれる(... いちいち negedge でもとか以下でも言わない)。always といっても、

reg A, B, D, E;
always @(A or B) begin
   D <= A and B;
end
always @(posedge clk) begin
   E <= D;
end
の場合、D の更新は 次回の A, B の更新まで遅れたりはしないことが E の振る舞いで分かる。

ただ、この場合でもセンシティビティリストに欠けた変数があった時、夏休みの宿題由来の挙動があった ... ような気がしたんだけどうまく再現できんかった。 後日の宿題に残す。上例で C が欠けているのはセンシティビティリストに欠けた奴を作るためだったんだが。

VHDL における variable への代入

Verilog のブロッキング代入とよく似たものとして VHDL における variable への代入があり、これも「即時代入される」と説明される。 Verilog の場合とは異なり、これは誤解なく揺るぎなく正しい。
  signal A : bit;
  process
    variable B : bit;
    variable C : bit;
  begin
      B := 1;
      C := B;                -- これは即時代入される。
      A <= C;                -- 逆に、signal への代入は全てぎりぎりまで遅延される。
  end process;
... VHDL のこういうトコ(言語の明瞭性)は好きだ。書き方の抽象度も高い(レコードとか列挙型とか)し、 整数型とビットベクタの変換がもう少し気楽に出来れば今回も VHDL 使ったんだがなぁ。 そのわりには既に VHDL の文法を忘れつつあるが。

Verilog におけるブロッキング代入

本題に入る。

例:

reg x;
reg A, B, C;
always @(A or C) begin : PROBING
   event(A, C);
end
always @(posedge clk) begin : MAIN
   A = x;
   B = A;
   C <= x;
end
を考える。MAIN において A の代入が何時おきるだろうか。 clk が立ち上がってすぐ A = x に達し、A への代入が起き、したがって PROBING ブロックが実行されるのは clk が立ち上がってすぐ、C への代入の前 ... ではない。
これは
reg x;
reg A, B, C;
always @(A or C) begin : PROBING
   event(A, C);
end
reg tmp_A, tmp_B;
always @(posedge clk) begin : MAIN
   tmp_A = x;
   tmp_B = tmp_A;
   A <= tmp_A;
   B <= tmp_B;
   C <= x;
end
と等価であり、event(A, C) が起きるのは C への代入の直後である。宿題的に遅延されるのはノンブロッキング代入だけではない。

VHDL での表記:

signal A : bit;
process
  variable tmp_A : bit;
  variable tmp_B : bit;
  if(clk'event and clk = '1') then
      tmp_A := x;
      tmp_B := tmp_A;
      A <= tmp_A;
      B <= tmp_B;
      C <= x;
  end if;
end process;
逆に、VHDL では signal に即時/逐次代入することができないので、variable を経由した書き方しかできない。

VHDL で即時代入される variable tmp_A, tmp_B を当該 process の外から参照する方法がないのに対し、 Verilog では即時代入の対象の A, B をブロックの外で使うことができることに注意。 だからこそ「即時代入されていない」のを観察することができた。 両者ともども、結局は即時代入された内容をブロックの外で参照できない。

... 即時代入された variable を外で使うことができなかった VHDL と違い、 即時代入された reg を外で使えるじゃないかハラショーとばかりに喜々として使いまくったあげく今回のネタに気付いたともいう。 即時代入即時代入いうから信じたじゃないかコンチクショー。

「always文」というくくり

always 文では〜 という限定詞は雑すぎる。Verilog の動作が切り替わる (順序回路 vs 組合せ回路) のは always のありなしではなく、 always に posedge (or negedge) が付くか付かないかによって決まる。

これによって、解説ページ中で挙げられていたあんちょこルール

は以下のように書き換えられる。 いまや 1' と 3b' は矛盾しなくなった。 推奨するつもりはさらさらないが、やり玉に上がっていた元ルールほど混乱させるものでもない。まあ好きにしろレベルにはなっただろう。

VHDL の singal は必ずフリップフロップだが、Verilog の reg は必ずしもそうではない。 function や task で reg を使い、しかもそれがフリップフロップにならないのがとても奇妙だったのだが、always の区別さえ出来れば、

てな具合に明確になった。おっけーだ。

まとめ

reg への二つの代入 "=" と "<=" の間に、順序回路がどうこう組み合わせ回路がどうこう的な違いはない。両方ともどちらの使いかたもできる。 その区別をするのは always のリストの書き方。

実は同じ reg に "=" と "<=" を混ぜて使うこともできる。たいがいのシンセサイザ/コンパイラは発狂するようだが、 それはあくまで合成側の都合であって、シミュレータでビヘイビアをみるときは何の問題もおきないことから syntax 的にも semantics 的にも許容されていることが分かる。

References


[日記へ] [目次へ]