カウンタとシフトレジスタを書く

📌 このページの概要と目標

概要: カウンタやシフトレジスタは動きがあるので動作を理解するのが大変です。各回路の動作原理とVerilog記述を確実に習得してください。

目標:

  • N進カウンタのビット数設計と記述ができる
  • 2のN乗カウンタの記述の特徴を理解できる
  • アップダウンカウンタを記述できる
  • GRAYコードカウンタ(function+always)を記述できる
  • 分周回路(T-FF従属接続)を記述できる
  • シリアルパラレル変換回路を記述できる
  • レジスタ配列を宣言・記述できる

1. N進カウンタの記述

もふねこ

「カウンタ」や「シフトレジスタ」は、クロックごとに値が変わるから、頭の中で動きをイメージするのが少し難しいかも🐾
でも、デジタル回路のあちこちで使われる超重要パーツだから、ここでマスターしちゃおう!

6進カウンタを例にN進カウンタの記述について説明します。

C6 図1:6進カウンタのブロック図。RBとCKがCOUNT6に入り、3ビットのQ 3bitが出る。
C6 図1:6進カウンタのブロック図。横に長い図はスクロールして確認できます。

ステップ1:出力Qのビット数を決める

出力Qのビット数は以下の条件式を満たす最小のkの値になります:

条件式6進カウンタの場合
2^k ≥ N を満たす最小のk2^3 = 8 ≥ 6 なので k = 3(3ビット)
出力Qの範囲0 ≤ Q ≤ 2^k - 1 → 0 から 7

ステップ2:N進カウンタのVerilog記述

6進カウンタの場合は0から5までカウント後、再び0にするための記述が必要になります。

module COUNT6 ( CK, Q, RB );
input  CK, RB;
output [2:0] Q;
reg    [2:0] Q;

always @( posedge CK or negedge RB )
begin
    if ( RB == 1'b0 )
        Q <= 3'b000;
    else if ( Q == 3'd5 )      // N-1(=5)に達したら0に戻す
        Q <= 3'b000;
    else
        Q <= Q + 3'b001;
end
endmodule
💡 N進カウンタのポイント
  • RBが0のとき → 非同期リセット(CKに関係なくQ=0)
  • CK立ち上がりでQ == N-1(=5)のとき → Q=0 に戻す
  • それ以外 → Q+1(カウントアップ)

2. 2のN乗カウンタの記述

2のN乗カウンタの場合は記述が異なります。2の3乗である8進カウンタを例に説明します。

比較項目6進カウンタ(一般N進)8進カウンタ(2の3乗)
リセット条件のelse必要(Q==5 でリセット)不要
オーバーフロー動作N-1の次は手動でリセット7+1=8 → 3ビットで自動的に0へ
module COUNT8 ( CK, Q, RB );
input  CK, RB;
output [2:0] Q;
reg    [2:0] Q;

always @( posedge CK or negedge RB )
begin
    if ( RB == 1'b0 )
        Q <= 3'b000;
    else
        Q <= Q + 3'b001;   // 7+1=8 → 最上位ビット欠落 → 0に戻る
end
endmodule
📌 2のN乗カウンタの原理 3ビットで7+1=8を表現できないため最上位ビットが欠落し、自動的に0になります。
2のN乗カウンタの記述ではリセット動作のelse項は不要です。

3. アップダウンカウンタの記述

入力UPの値によりカウントアップ・カウントダウンの両方に対応した回路です。

C6 図2:アップダウンカウンタのブロック図。RB、UP、CKがUPDOWN8に入り、3ビットのQ 3bitが出る。
C6 図2:アップダウンカウンタのブロック図。横に長い図はスクロールして確認できます。
UP入力動作
1カウントアップ(Q+1)
0カウントダウン(Q-1)
module UPDOWN8 ( CK, UP, Q, RB );
input  CK, UP, RB;
output [2:0] Q;
reg    [2:0] Q;

always @( posedge CK or negedge RB )
begin
    if ( RB == 1'b0 )
        Q <= 3'b000;
    else if ( UP == 1'b1 )
        Q <= Q + 3'b001;
    else
        Q <= Q - 3'b001;
end
endmodule

4. GRAYコードカウンタの記述

もふねこ

「グレイコード」って聞いたことある?🐾
普通のカウントと違って、1回に1ビットしか変化しないから、通信やセンサのノイズ対策(グリッチ防止)にすごく役立つ書き方なんだ!

GRAYコードカウンタは、クロックの立ち上がりごとに1ビットだけ変化するカウンタです。記述はfunctionとalways文で構成されます。

module GRAY_COUNT ( CLK, Q, RB );
input  CLK, RB;
output [2:0] Q;
reg    [2:0] Q;

// function GRAY: 現在のカウント値(IN)から次のカウント値を返す
function [2:0] GRAY;
input [2:0] IN;
begin
    case ( IN )
        3'b000: GRAY = 3'b001;   // 0 → 1
        3'b001: GRAY = 3'b011;   // 1 → 3
        3'b011: GRAY = 3'b010;   // 3 → 2
        3'b010: GRAY = 3'b110;   // 2 → 6
        3'b110: GRAY = 3'b111;   // 6 → 7
        3'b111: GRAY = 3'b101;   // 7 → 5
        3'b101: GRAY = 3'b100;   // 5 → 4
        3'b100: GRAY = 3'b000;   // 4 → 0
        default: GRAY = 3'b000;
    endcase
end
endfunction

always @( posedge CLK or negedge RB )
begin
    if ( RB == 1'b0 )
        Q <= 3'b000;
    else
        Q <= GRAY( Q );   // 現在のQを引数に渡し、次の値を取得
end
endmodule
💡 GRAYコードカウンタのポイント
  • function GRAY:引き数INは現在のカウント値、戻り値GRAYは次のカウント値
  • case文の内容を変えることで、任意パターンカウンタを記述可能
  • 各クロックで変化するビット数が1ビットのみ → グリッチが少ない

5. 分周回路の記述

分周回路はTフリップフロップを従属接続し、TフリップフロップのQの反転(~Q)を次のT-FFのクロックに入力します。

C6 図3:分周回路の図。CLKがT-FFに入り、その出力がQ 0 CLK/2となる。この出力は次のT-FFに入ってQ 1 CLK/4となり、さらに次のT-FFに入ってQ 2 CLK/8となる。
C6 図3:分周回路の図。横に長い図はスクロールして確認できます。
構成周波数
1段目 T-FF(CLK入力)CLK / 2
2段目 T-FF(1段目Q入力)CLK / 4
3段目 T-FF(2段目Q入力)CLK / 8
module DIVIDER3 ( CLK, Q, RB );
input  CLK, RB;
output [2:0] Q;
reg    [2:0] Q;

// assign文でビット選択を1ビット信号に変換(論理合成対応)
wire Q0, Q1;
assign Q0 = Q[0];
assign Q1 = Q[1];

// 各T-FFは共通のCLKではないため、3つのalways文で記述
always @( negedge CLK or negedge RB )
begin
    if ( RB == 1'b0 ) Q[0] <= 1'b0;
    else              Q[0] <= ~Q[0];
end

always @( negedge Q0 or negedge RB )
begin
    if ( RB == 1'b0 ) Q[1] <= 1'b0;
    else              Q[1] <= ~Q[1];
end

always @( negedge Q1 or negedge RB )
begin
    if ( RB == 1'b0 ) Q[2] <= 1'b0;
    else              Q[2] <= ~Q[2];
end
endmodule
⚠️ ビット選択をイベント式に使う際の注意 @(negedge Q[1]) のようにビット選択をイベント式に記述すると論理合成ツールでエラーになる場合があります。
assign Q1 = Q[1]; でワイヤ型の1ビット信号に置き換えてから使用してください。
もふねこ

🐾 もふねこの現場メモ:
カウンタでつまずきやすいのは「起動した直後の値」だよ。初期化していない reg は、実機だけでなくシミュレーションでも x から始まるんだ(Icarus Verilog 13.0 で reg [3:0] を読むと xxxx、1を足しても xxxx のまま)。だからカウンタには、起動時に既知の値へそろえる手当てが要るよ。
ここで分けて覚えてほしいのが、15の次が0に戻るのはリセットの仕事ではないということ。4ビットで受けていれば桁上げが捨てられて自然に 0000 へ折り返すよ(15 に初期化してから +1 すると、リセットが1つも無くても 0000 になるのを確かめた)。折り返さないときは幅の取り方を疑ってね。
リセットのかけ方も「非同期が必須」ではないよ。同期と非同期のどちらを使うかは、要件と対象デバイスで選ぶもの。デバイスによっては起動時の初期値を指定できるから、まず自分のツールの資料を見てから決めよう。

6. シフトレジスタ(シリアルパラレル変換)の記述

3ビットのシリアル入力パラレル出力(SIPO)シフトレジスタです。CLKの立ち上がりで1ビットのシリアルデータを入力し、3ビットのパラレルデータを出力します。

C6 図4:シリアルパラレル変換SIPOの図。DINがD-FF Q 0に入り、D-FF Q 1、D-FF Q 2へ順にシフトされる。各D-FFの出力はQ 0、Q 1、Q 2として取り出される。
C6 図4:シリアルパラレル変換SIPOの図。横に長い図はスクロールして確認できます。
クロック数DIN入力Q[2]Q[1]Q[0]
1回目 ↑××00
2回目 ↑△△×0
3回目 ↑○○△×
module SIPO3 ( CLK, DIN, Q, RB );
input  CLK, DIN, RB;
output [2:0] Q;
reg    [2:0] Q;

// CLKは各FFに共通なので、always文1つで記述可能
always @( posedge CLK or negedge RB )
begin
    if ( RB == 1'b0 )
        Q <= 3'b000;
    else
        Q <= { Q[1:0], DIN };   // 連接演算で1ビット左シフト
end
endmodule
💡 シリアルパラレル変換の連接演算
  • {Q[1:0], DIN} でQの下位2ビットとDINを連接 → 3ビット
  • 3ビットをQ(3ビット)に代入すると最上位ビットが欠落 → 1ビット左シフト
  • 連接演算を使うことでシフト動作を1行で記述できる
もふねこ

🐾 もふねこの現場メモ:
就職した学生から「シフトレジスタでデータ処理回路を作ったが、実機で時々おかしなデータが混ざる」という相談を受けたことがあるんだ。原因は制御信号とデータのタイミングが1クロックだけズレていたこと。テストベンチで「00」や「FF」のような変化のないデータばかり入力していたので、ズレに気づけなかった。テスト用データはランダムに変化させて検証しよう!
※データとvalidの歩調は このページの補足、変化のあるテストデータの作り方は 検証実験1の実習 にあるよ🐾

7. レジスタ配列の記述

レジスタとは複数のフリップフロップの集合体です。ここでは4ビット×2個のレジスタ配列を説明します。

C6 図5:レジスタ配列の図。DIN 4bitがFILE 0 4bitにはWE=1かつAIN=0のとき、FILE 1 4bitにはWE=1かつAIN=1のとき入る。FILE 0はAOUT=0、FILE 1はAOUT=1のときDOUT 4bitへ出る。
C6 図5:レジスタ配列の図。横に長い図はスクロールして確認できます。
操作条件動作
書き込みCLK↑かつWE=1AINで選択したFFにDINを書き込む
読み出し常時AOUTで選択したFFの値をDOUTに出力
module REGISTER2 ( CLK, DIN, DOUT, AIN, AOUT, WE );
input  CLK;
input  [3:0] DIN;
output [3:0] DOUT;
input  AIN, AOUT, WE;

// レジスタ配列の宣言: reg [ビット幅-1:0] 信号名 [配列数-1:0]
reg [3:0] FILE [1:0];

// 書き込み制御: always文
always @( posedge CLK )
begin
    if ( WE == 1'b1 )
        FILE[AIN] <= DIN;
end

// 読み出し: assign文(組み合わせ回路)
assign DOUT = FILE[AOUT];
endmodule
📌 レジスタ配列の記法
  • reg [3:0] FILE [1:0]; → 4ビット幅のFILEというレジスタが2組
  • 書き込み(順序回路動作):always文
  • 読み出し(組み合わせ回路動作):assign文

実習で確かめる、つまずきの補足

カウンタの折り返しとデータ・validの歩調

この教材のカウンタは共通クロックで更新する同期式だよ。前段Qを次段のクロックにするリップル式は各段の遅延や生成クロックの扱いが増える。同期式なら次のように、折り返しもデータ経路で決められるんだ。

if (enable) begin
    if (count == 4'd9) count <= 4'd0;
    else              count <= count + 4'd1;
end

組合せの終端判定をそのまま非同期リセット端子へ入れると、グリッチがリセットに影響する場合があるよ。一般のロジックでクロックをANDする代わりに、クロックイネーブルを使う設計を基本にしよう。AMDのFPGA向け設計ガイドもクロックイネーブルの利用を勧めているよ。非同期回路が一律に動かないという意味ではなく、この教材ではクロックとタイミングを管理しやすい構成を選んでいるんだ。

シフトレジスタには直列/並列変換と、データを指定サイクル遅らせる用途がある。データを2段遅らせるならvalidも2段へ。毎回同じデータを流すと1サイクルのずれを見逃すため、12→a5→3c のように変化させ、valid=0の隙間と途中リセットも試そう。

数を数える・LEDを一定周期で反転する・2段パイプラインの例は 実習集 からソースと自動判定付きで取得できるよ。

📝 C6 まとめ: カウンタとシフトレジスタの記述

回路種類記述の特徴ポイント
N進カウンタif(Q==N-1) Q<=0; else Q<=Q+1;ビット数: 2^k ≥ N の最小k
2のN乗カウンタelse Q<=Q+1;(リセット条件不要)オーバーフローで自動的に0に戻る
アップダウンカウンタif(UP) Q<=Q+1; else Q<=Q-1;UP入力で方向を切り替え
GRAYコードカウンタfunction + always文1クロックで1ビットのみ変化
分周回路T-FF従属接続(複数always文)Q[n]をQ[n+1]のクロックに
シリアルパラレル変換Q <= {Q[1:0], DIN};連接演算で1行シフト
レジスタ配列reg [3:0] FILE [1:0];書き込み=always / 読み出し=assign
💡 シフトレジスタを応用したカウンタ(シフトレジスタカウンタ)

シフトレジスタの出力を入力にフィードバックさせることで、カウンタとして動作させることができます。主に以下の2つが代表的です。

  • リングカウンタ(Ring Counter): 最上位ビットの出力をそのままシリアル入力にフィードバックする構成。N個のフリップフロップでNステップをカウントします。
  • ジョンソンカウンタ(Johnson Counter): 最上位ビットの反転出力をシリアル入力にフィードバックする構成。N個のフリップフロップで2Nステップをカウントできます。

これらは通常のバイナリカウンタ(Q <= Q + 1)のような加算ロジックが不要なため、デコード回路(組み合わせ回路)の遅延がなく、非常に高速(高周波)に動作するというメリットがあります。