順序回路のややこしさ

 順序回路では、「1クロックですべてが並列に変化する」という挙動が、(S/W言語に慣れている人間にとっては)思わぬバグの原因となります。ここでは例を使って説明します。

 試しに、0~2をループするカウンターcntと、cntが2のときに1になるisTwoを考えてみます。


reg cnt[1:0]; //ビット幅2bit
reg isTwo; //ビット幅1bit

always @(posedge CLK) begin
	if (cnt < 2) cnt <= cnt + 1; //cntは0~2をループ
	else cnt <= 0;
end

always @(posedge CLK) begin
	isTwo <= (cnt==2); //cntが2の時に1を代入する
end

 regはレジスター(信号線)の宣言で、always文の中身は指定した信号(今回はCLK)の立ち上がり(posedge)のたびに実行されます。

 正しく書けているように見えますが、実際は意図した通りには動いてくれません。

 この回路では、isTwoが1になるのは、cntが0のときになってしまうのです。なぜでしょうか。次のグラフを見てください。

タイミングチャート
[画像のクリックで拡大表示]

 順序回路では、評価の結果は次のクロックに反映されます。cnt==2が評価された次のクロックには、既にcntは0に戻ってしまっている、というわけです。これを正しく動作させるためには、cntがインクリメントされるのを先読みして、cnt==1と書かなければいけません。

 ちなみに、上のような図を「タイミングチャート」と呼びます。順序回路では、タイミングチャートで動作を全体的に眺めながら設計できます。

 このように、順序回路はS/Wの記述と比べて少し独特なので、慣れないとすぐに意図とは違う論理を書いてしまうのです。