実装の進捗だけでなく、ハマりどころも述べていくぞ!
「こうすればハマる」という学習も進捗の一つですからね~
実際にハマったのは僕ではないんですが、僕自身こういう現象に遭遇したことがなく、おそらく同じようなことをする人が多いかもしれませんので掲載しておきます。
今回はエッジ信号を生成する際に気をつけることです。結論を言うと、「クロック等、エッジ検出ポートに接続するための信号は組み合わせ回路で生成するな」です。
クロック分周回路でのハマり
単に組み合わせ回路での実装を否定するわけではありません。
ポイントは、組み合わせ回路を使う時は気をつけないとハザードによって誤動作する可能性があるということ。
ハザードとは
ハザード(Hazzard)は危険、危害という意味がありますね。つまり対象に悪影響を与える原因について使われる語です。
論理回路に関してはとりわけ論理波形のバタつきという意味を指すことが多いようです。
ハザード発生の例
具体的に考えてみましょう。以下に同タイミングで波形変化を起こす簡単な回路を用意しました。単なる AND なので、常に出力は 0 だとわかりますよね。
でも、現実の回路では異なります。実際は配線による信号伝達遅延がありますので(主に配線長の違いや、配線間の容量成分による波形なまり)、特に動作スピードが多くなればなるほどこの現象は問題になります。
具体的に、入力1に遅延が発生した際のタイミングチャートが以下のようになります。
ということで、この少しの遅延により与えられた入力パターンでは想定していなかった1出力が出てしまうことになります。
ハザード発生を観測しよう
ここまでは大学等の講義で学んだことのある人が多いことでしょう。では具体的にこの現象を観測したいと思います。題材は、クロック分周回路です。
分周回路の概要
今回の FPGA を対象にしますが、この FPGA からポートに直接供給するクロックは 125MHz と超高速です。例えば1秒おきにカウントアップしていって、LED を光らせていくという回路を作るにしても速すぎて常時点灯に見えてしまいますよね。
そのためにクロック周波数を落とす分周回路が必要になるわけです。
具体的な実装
いくつか実装パターンはあると思いますが、以下の素人感ある実装を御覧ください。
module clkdiv(
input wire nRST,
input wire CLK_IN, // 125MHz
output wire CLK_OUT // 1Hz
);
reg [26:0] cnt;
assign CLK_OUT = (cnt == 124_999_999) ? 1 : 0;
always @(posedge CLK_IN or negedge nRST) begin
if( ~nRST ) cnt <= 27'd0;
else if( cnt == 27'd124_999_999 ) cnt <= 27'd0;
else cnt <= cnt + 27'd1;
end
endmodule
中級者以上の方ならこれを見て「うわぁ...」と思うことでしょう。どこがアレなのかというと...
- Duty 比が 50% ではない
- 出力クロックの 0 である時間と 1 である時間が等しくない、汚いクロックだということです。
- 出力が
assign
文で規定されている- これによって組み合わせ回路になる。つまりクロックに同期しない動作になるため、波形変化がバタついたらその影響をもろに受ける。
この中でとりわけ後者に問題があることになります。結構重大です。
このハードウェア記述によって、超絶ざっくりですが以下のような回路が展開されることでしょう。
波形を見てみる
では、この回路の波形を考えていきましょう。まずは具体的な理想とした動作です。以下のような感じで汚い 1Hz クロックが発生するわけです。
でも実際の波形は...ハザードが入ってもっと汚いです。実際はだいたい下のような ジタバタ暴れた波形になります。
ちなみにこれは、FPGA くんの気分で起きたり起きなかったりするようなので必ずこうなるとは保証しません。
ハザードの原因
ハザードの原因はズバリ、遅延です。
先程の一番簡単な AND ゲートで示したような事態になっていると考えられます。つまり、カウンタを足し算していくうちに信号線の遅延のせいで、たまたま比較対象の定数値と同じビット配列の電圧値が現れる瞬間が存在してしまうことがハザードの原因です。
挙動
では、ハザード発生の挙動を見てみましょう。
実装は FPGA ボード上で、4つの LED が2進数で1ずつカウントしていきます。
これを防ぐために
そもそも何が問題だったのでしょう?信号遅延??いえ、信号遅延は絶対起きるものなので諦めてください。
信号遅延によって、出力信号がバタついたのが問題でした。ということは、バタつきを抑える機構が必要というわけです。
ということは、カウンタの結果が安定するまで待てばいいということになります。つまり一度レジスタに値を格納することで、波形変化後の値を次のクロックの立ち上がりエッジまで待たせることが可能なのです。
ハードウェア記述は次のようにしたほうがいいでしょう。
```verilog
module clkdiv(
input wire nRST,
input wire CLK_IN, // 125MHz
output reg CLK_OUT // 1Hz
);
reg [26:0] cnt;
always @(posedge CLK_IN or negedge nRST) begin
if( ~nRST ) cnt <= 27'd0;
else if( cnt == 27'd124_999_999 ) begin
cnt <= 27'd0;
CLK_OUT <= ~CLK_OUT;
end
else cnt <= cnt + 27'd1;
end
endmodule
```
すると出力段には以下のように、レジスタでクロック立ち上がり時の値がゲーティングされるわけです。めでたしめでたし。