Verilogで順序回路を書く
(1)ここで学ぶ内容
ここでは、順序回路(クロックに同期して動く回路)の書き方を学ぶよ🐾
C言語などのソフト開発にはない「always文」が登場するから、しっかり理解していこう!
- バイナリカウンタの記述を例に、簡単な順序回路の記述について学ぶ
- always 文や if 文の基本を理解する
目標
以下の項目を理解し説明できる
- reg 宣言
- always 文の記述
- if 文による条件分岐
- reg 信号への代入
- 定数の表現
修了判定
信号名を指定して、8ビットのダウンカウンタを記述する
条件:減算演算子を用いる
(2)バイナリカウンタの動作
4ビット・バイナリカウンタ
ck"] end RES["res"] --> CNT CK["ck"] --> CNT CNT -->|4| Q["q
4bit"] style CNT fill:#e8f5e9,stroke:#388e3c,stroke-width:2px;
| q[3] | q[2] | q[1] | q[0] |
|---|---|---|---|
| 0 | 0 | 1 | 0 |
ck ┐ ┌┐ ┌┐ │┌┐ ┌┐ ┌┐ ┌┐ └──┘└──┘└──┼┘└──┘└──┘└──┘└ │ res ┌──┐ │ └──┘ │ │ q | 0 | 1 │ 2 | 3 | 4 | 5 │ q[0] ██┐ ┌────┤ ┌────┐ ┌ └─────┘ └────┘ └────┘ │ q[1] ██┐ │┌────┐ ┌ └──────────┼┘ └────────┘ │ q[2] ██┐ │ ┌──── └──────────┴──────────┘ │ q[3] ██┐ │ └──────────┴─────────────── │
※赤色ブロック(██)はリセット前の不定(Undefined)状態を示します。
※赤い縦線は q=2 (0 0 1 0) の瞬間を示しています。
(3)バイナリカウンタの記述
4ビット・バイナリカウンタ
ck"] end RES["res"] --> CNT CK["ck"] --> CNT CNT -->|4| Q["q
4bit"] style CNT fill:#e8f5e9,stroke:#388e3c,stroke-width:2px;
加算演算子による4ビットカウンタ(非同期リセット)
module counter( ck, res, q );
input ck, res;
output [3:0] q;
reg [3:0] q;
always @( posedge ck or posedge res )
begin
if ( res==1'b1 )
q <= 4'h0;
else
q <= q + 4'h1;
end
endmodule
(4)ワンポイント・アドバイス
ここは超重要!『regとalways文はペア』『代入記号は <= (ノンブロッキング代入) を使う』🐾
これらはVerilogのルールの中でも特にバグを生みやすいポイントだから、しっかり覚えておいてね!
- 順序回路の記述には always 文を使う。
- 「regとalways文はペア」と覚えておこう。
regへの代入は、always文の中で行う。
wireへの代入は、always文の中では文法エラー。
- regへの代入記号は "<=" を使おう。
"=" も文法上使えるが、不具合の原因にもなる。(詳細はO2ユニット参照)
ただし、シミュレーション記述(テストベンチ)の initial 文で時間(#遅延)を進めながら入力を与える場合は "=" を使うのが基本。
(※クロックエッジ @(posedge ck) に同期してDUTへ入力を与える場合は、レース回避のため "<=" が安全だよ)
回路記述
reg Q;
always @( posedge CK ) begin
Q <= D;
end
シミュレーション記述(テストベンチ)
initial begin
reset = 0;
#STEP reset = 1;
#STEP reset = 0;
end
🐾 もふねこの現場メモ:
実習で、シフトレジスタを書こうとした学生が <= ではなく = を使ってしまったことがあったんだ。すると、データが2段遅延するはずが1クロックで素通りしてしまった。しかも行の順番を入れ替えるだけで動作が変わる…。「コードの順番で回路が変わる」のはハードウェア設計では致命的なバグの温床だよ!
※この話の詳しい解説は
noteのコラム
で書いているよ🐾
(5)つまずきやすいポイント
順序回路は、組み合わせ回路よりも「いつ値が変わるのか」を意識する必要があります。カウンタのような短い回路でも、クロック、リセット、代入記号、ビット幅のどれかを見落とすと、シミュレーション結果が期待とずれることがあります。
| 確認項目 | よくある間違い | 見るべきポイント |
|---|---|---|
| クロック | posedge ck を書き忘れる | カウント値がクロックの立ち上がりでだけ変わるか |
| リセット | 初期値を決めず、不定値のまま使う | リセット後に q が必ず 0 へ戻るか |
| 代入記号 | 順序回路で = を使ってしまう | レジスタ更新には基本的に <= を使う |
| ビット幅 | q と定数の幅が合っていない | 4'h1 や 8'h01 のように幅を意識する |
(6)動作確認の観点
このページのカウンタを自分で書いたら、次の観点で確認してみましょう。順序回路の検証では、コードを読むだけでなく、波形上で期待通りに変化しているかを見比べることが大切です。
- リセットを入れた直後に、出力
qが期待した初期値になっている。 - リセット解除後、クロックの立ち上がりごとに1ずつ増える、または減る。
- クロックが変化していない区間では、
qの値が勝手に変わらない。 - 4ビットなら
15の次に0へ戻るなど、桁あふれの動きが説明できる。 - ダウンカウンタに変えたとき、減算演算子と初期値の関係を説明できる。
次の 文法実験3: シミュレーションと論理合成を体験する では、書いた回路を実際に動かして確認する流れに進みます。このページで「どの信号を、どのタイミングで見るべきか」を押さえておくと、E3の内容がかなり読みやすくなります。
📝 練習問題:8ビットのダウンカウンタ
ここまで学んだ always 文、非同期リセット、ノンブロッキング代入を使って、8ビットのダウンカウンタを完成させてみましょう。4ビットのアップカウンタとの違いは、出力幅とカウント方向です。
信号名を指定して、8ビットのダウンカウンタを記述する。
条件:減算演算子を用いる。
module down_counter ;
input ck, res;
q;
reg [7:0] q;
always @( posedge ck or posedge res )
begin
if ( res==1'b1 )
;
else
;
end
endmodule