カウンタとシフトレジスタを書く
📌 このページの概要と目標
概要: カウンタやシフトレジスタは動きがあるので動作を理解するのが大変です。各回路の動作原理とVerilog記述を確実に習得してください。
目標:
- N進カウンタのビット数設計と記述ができる
- 2のN乗カウンタの記述の特徴を理解できる
- アップダウンカウンタを記述できる
- GRAYコードカウンタ(function+always)を記述できる
- 分周回路(T-FF従属接続)を記述できる
- シリアルパラレル変換回路を記述できる
- レジスタ配列を宣言・記述できる
1. N進カウンタの記述
「カウンタ」や「シフトレジスタ」は、クロックごとに値が変わるから、頭の中で動きをイメージするのが少し難しいかも🐾
でも、デジタル回路のあちこちで使われる超重要パーツだから、ここでマスターしちゃおう!
6進カウンタを例にN進カウンタの記述について説明します。
graph LR
subgraph 6進カウンタ
CNT[COUNT6
► CK] end RB[RB] --> CNT CK[CK] --> CNT CNT -->|3| Q[Q
3bit] style CNT fill:#e8f5e9,stroke:#388e3c,stroke-width:2px;
► CK] end RB[RB] --> CNT CK[CK] --> CNT CNT -->|3| Q[Q
3bit] style CNT fill:#e8f5e9,stroke:#388e3c,stroke-width:2px;
ステップ1:出力Qのビット数を決める
出力Qのビット数は以下の条件式を満たす最小のkの値になります:
| 条件式 | 6進カウンタの場合 |
|---|---|
2^k ≥ N を満たす最小のk | 2^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項は不要です。
2のN乗カウンタの記述ではリセット動作のelse項は不要です。
3. アップダウンカウンタの記述
入力UPの値によりカウントアップ・カウントダウンの両方に対応した回路です。
graph LR
subgraph アップダウンカウンタ
CNT[UPDOWN8
► CK] end RB[RB] --> CNT UP[UP] --> CNT CK[CK] --> CNT CNT -->|3| Q[Q
3bit] style CNT fill:#e8f5e9,stroke:#388e3c,stroke-width:2px;
► CK] end RB[RB] --> CNT UP[UP] --> CNT CK[CK] --> CNT CNT -->|3| Q[Q
3bit] style CNT fill:#e8f5e9,stroke:#388e3c,stroke-width:2px;
| 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のクロックに入力します。
graph LR
subgraph 分周回路
direction LR
T0[T-FF
► CLK] T1[T-FF
► Q0_n] T2[T-FF
► Q1_n] CLK[CLK] --> T0 T0 -->|Q0| Q0[Q 0
CLK/2] T0 -.->|~Q0| T1 T1 -->|Q1| Q1[Q 1
CLK/4] T1 -.->|~Q1| T2 T2 -->|Q2| Q2[Q 2
CLK/8] end style T0 fill:#e3f2fd,stroke:#1976d2; style T1 fill:#e3f2fd,stroke:#1976d2; style T2 fill:#e3f2fd,stroke:#1976d2;
► CLK] T1[T-FF
► Q0_n] T2[T-FF
► Q1_n] CLK[CLK] --> T0 T0 -->|Q0| Q0[Q 0
CLK/2] T0 -.->|~Q0| T1 T1 -->|Q1| Q1[Q 1
CLK/4] T1 -.->|~Q1| T2 T2 -->|Q2| Q2[Q 2
CLK/8] end style T0 fill:#e3f2fd,stroke:#1976d2; style T1 fill:#e3f2fd,stroke:#1976d2; style T2 fill:#e3f2fd,stroke:#1976d2;
| 構成 | 周波数 |
|---|---|
| 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ビット信号に置き換えてから使用してください。
6. シフトレジスタ(シリアルパラレル変換)の記述
3ビットのシリアル入力パラレル出力(SIPO)シフトレジスタです。CLKの立ち上がりで1ビットのシリアルデータを入力し、3ビットのパラレルデータを出力します。
graph LR
subgraph シリアルパラレル変換 SIPO
direction LR
DFF0[D-FF
Q 0] DFF1[D-FF
Q 1] DFF2[D-FF
Q 2] DIN[DIN] --> DFF0 DFF0 -->|シフト| DFF1 DFF1 -->|シフト| DFF2 DFF0 --> Q0[Q 0] DFF1 --> Q1[Q 1] DFF2 --> Q2[Q 2] end style DFF0 fill:#fff3e0,stroke:#f57c00; style DFF1 fill:#fff3e0,stroke:#f57c00; style DFF2 fill:#fff3e0,stroke:#f57c00;
Q 0] DFF1[D-FF
Q 1] DFF2[D-FF
Q 2] DIN[DIN] --> DFF0 DFF0 -->|シフト| DFF1 DFF1 -->|シフト| DFF2 DFF0 --> Q0[Q 0] DFF1 --> Q1[Q 1] DFF2 --> Q2[Q 2] end style DFF0 fill:#fff3e0,stroke:#f57c00; style DFF1 fill:#fff3e0,stroke:#f57c00; style DFF2 fill:#fff3e0,stroke:#f57c00;
| クロック数 | DIN入力 | Q[2] | Q[1] | Q[0] |
|---|---|---|---|---|
| 1回目 ↑ | × | × | 0 | 0 |
| 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行で記述できる
7. レジスタ配列の記述
レジスタとは複数のフリップフロップの集合体です。ここでは4ビット×2個のレジスタ配列を説明します。
graph TD
subgraph レジスタ配列
direction TB
FILE0[FILE 0
4bit] FILE1[FILE 1
4bit] end DIN[DIN 4bit] -->|WE=1 & AIN=0| FILE0 DIN -->|WE=1 & AIN=1| FILE1 FILE0 -->|AOUT=0| DOUT[DOUT 4bit] FILE1 -->|AOUT=1| DOUT style FILE0 fill:#f3e5f5,stroke:#9c27b0; style FILE1 fill:#f3e5f5,stroke:#9c27b0;
4bit] FILE1[FILE 1
4bit] end DIN[DIN 4bit] -->|WE=1 & AIN=0| FILE0 DIN -->|WE=1 & AIN=1| FILE1 FILE0 -->|AOUT=0| DOUT[DOUT 4bit] FILE1 -->|AOUT=1| DOUT style FILE0 fill:#f3e5f5,stroke:#9c27b0; style FILE1 fill:#f3e5f5,stroke:#9c27b0;
| 操作 | 条件 | 動作 |
|---|---|---|
| 書き込み | CLK↑かつWE=1 | AINで選択した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文
📝 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 |
