概要
Verilog-HDLを使っていて___「ちょっとコードを修正しただけなのに突然うまくいかなくなった」「コードにちょっとミスがあるみたいなレベルじゃなくて明らかに挙動がおかしい」___みたいな事態に何度も遭遇したので、今回はその時の解決法を記録しておきたいと思います。
なお、前提として今回は基本的に順序回路(always文)のお話になります。
主な対処法3つ
不必要なif文(条件分岐)を減らす
そのまんまです。if文の使用をできる限り避けます。
「いや、if文使わないと複雑な機能作れないじゃん!?無茶言うな!!」と思ったそこのあなたは次を試してみてください。
bit数を必要最小限にする(16bit化、なんなら8bit化)
こちらもほぼそのまんまです。「ここには2桁の数字しか入らないけどなんとなーく32bitにしてる」みたいなことはありませんか?
実はそれ、あんまりよくないです。bit数を削減するだけでかなり動作が改善します。
一時的に大きな数字が登場する場合も、そのまま不用意にレジスタ(reg変数)に格納せず、計算を進めて数字が小さくなってからレジスタに格納します。
そうすることで、賢くレジスタのbit数を削減することができます。
ブロッキング代入の使用はできるだけ避ける
こちらは少々複雑なので、まずは「ブロッキング代入」から説明していきます。
当たり前ですが、C言語などのプログラミング言語では、上から順に処理が行われていきます。
/*今aに0が代入されているものとする
a = 1;
b = a;
例えばこのようなコードがあったとすると、まずaに1が代入されて、次にbにaの値(すなわち1)が代入されます。
一方、Verilog-HDLでは、基本的に___代入が同時並行に行われます___(これをノンブロッキング代入といいます)
どういうことかというと、例えばさっきと似たようなコードがあったとすると
/*今回もaに0が代入されているものとする
a <= 1;
b <= a;
aに1が代入される(ここまではさっきと同じ)一方、それと___同時に並列して___次の行(とさらにそれ以降の)代入が行われます。今回であればbにaの値(つまり0、1はまだ代入が完了していない)が___同時に___行われています。
なぜこのような仕様になっているかというと、順序回路としてはこちらのほうが自然だからですね。Verilog-HDLはあくまでプログラミング言語ではなく順序回路ということです。
ここで「ん、代入記号なんかおかしくない?イコールじゃなくて矢印みたいになってるじゃん。イコールに戻せば”普通の”代入に戻るんじゃない?」って思った方、その通りです。C言語のように「普通に」記述すれば「普通に」代入が行われます(ブロッキング代入)。
「なんだ騙された!いつも通りに書けばいつも通りに動いて何も問題ないじゃないか!!」と思うかもしれませんが、実は___それこそが罠です___。
「普通の」代入(ブロッキング代入)は多用すると動作がおかしくなります。
というわけで長くなってしまいましたが、always文では基本的に順序回路として自然な___ノンブロッキング代入を使いましょう。ブロッキング代入を使うのは、それを使わないと望みの動作が実現できそうにない!どうしても使いたい!!というときだけです___。
なんでこれでうまくいくの?
ここまで色々書いてきてアレなのですが、実は僕もいまいちよくわかっておりません。あくまでもやってみたらうまくいったという話です。
ただif文、多数のbitの演算、ブロッキング代入の三つには共通項があります。それは、___時間がかかる___ことです。
if文では、条件式に使われる変数の更新を待つためか、前の行に書いた文がすべて実行完了するまで待ちます。多bit演算は、どうやら桁上がり処理の計算に時間がかかっているようです(下の桁の計算が終わり、繰り上がりが起こるか否か判明して初めて次の桁の計算を開始できる)。ブロッキング代入は、先ほど説明したように上の文の代入完了を待ってから次の文の代入処理を行います。
これらの処理を「待っている」間にクロックが進み、まだ処理が全て終わり切っていないのに次の処理が始まってしまう。これこそがうまくいかない原因のようです。
(じゃあクロックさえ遅くすれば万事解決するの?と思いましたが、残念ながら僕の貧相な知識では明確な答えは出せません、ごめんなさい…)
その他の注意点
重複する代入は避ける
ブロッキング代入のところで詳しく説明しましたが、Verilog-HDLでは基本的に代入は同時並行に行われます。
ここで、次のようなコードを考えてみましょう。
/*変数の宣言は済んでいるものとする
a <= 0;
a <= 1;
もしこれが通常のC言語であれば、「最初の一行、完全に無意味になってるじゃん(笑)」で済みますが、Verilog-HDLではそれだけでは済みません、もっと重大な問題が起こります。
そうです、aに0と1が___同時に___代入されます。「一つの変数に二つの値を同時に代入?どうなるの?」答えは簡単です、不具合が起こります。
このようなおかしな記述は必ず取り除いておきましょう。
PS/2キーボードの厄介な仕様
FPGAにキーボードを接続してスキャンコードを読み取る場合、キーを押したときと離したときの間に一瞬だけ___謎のスキャンコードが送り込まれる___模様(詳細不明、押したときのものでも離したときのものでもないのは確か)
想定外のスキャンコードが読み込まれた際に余計なことをしないよう、プログラムを工夫しましょう。
コメントの全角文字
全角文字なんて使うわけないじゃん!と思うかもしれませんが、なんと___コメントでも___全角文字の使用はご法度とのこと(未確認)
コメントで日本語を使っている方は見直したほうがいいかもしれません。
まとめ
以上です。ブロッキング代入、ノンブロッキング代入はバグの元なのでしっかり説明していたら結構長くなってしまいました。
というわけでわかりやすいように最後に要点をまとめておきます。
if文はできるだけ使うな!
16bit(できれば8bit)で計算しろ!
基本はノンブロッキング代入!
おしまい!質問、間違いの指摘等大歓迎です!!
環境
コンパイラ:Quartus Prime Lite (Ver 20.1)
FPGA:DE1-SoC (tesasIC社)
参考
if文使いすぎるとうまくいかないよ!ということを主張する記事
https://www.cqpub.co.jp/dwm/contents/0043/dwm004301230.pdf