Verilogで順序回路を書く

(1)ここで学ぶ内容

もふねこ

ここでは、順序回路(クロックに同期して動く回路)の書き方を学ぶよ🐾
C言語などのソフト開発にはない「always文」が登場するから、しっかり理解していこう!

概要
  • バイナリカウンタの記述を例に、簡単な順序回路の記述について学ぶ
  • always 文や if 文の基本を理解する

目標

以下の項目を理解し説明できる

  • reg 宣言
  • always 文の記述
  • if 文による条件分岐
  • reg 信号への代入
  • 定数の表現

修了判定

信号名を指定して、8ビットのダウンカウンタを記述する
条件:減算演算子を用いる

(2)バイナリカウンタの動作

4ビット・バイナリカウンタ

graph LR subgraph COUNTER4_A["4ビット・バイナリカウンタ"] CNT["counter

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ビット・バイナリカウンタ

graph LR subgraph COUNTER4_B["4ビット・バイナリカウンタ"] CNT["counter

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'h18'h01 のように幅を意識する
⚠️
「動いたように見える」だけで終わらせない カウンタが増えているように見えても、リセット直後、桁あふれ、ビット幅、クロック1周期ごとの変化を確認しないと、実際の回路として正しいとは言い切れません。波形を見るときは、値の変化だけでなく「変化したタイミング」も一緒に確認しましょう。

(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