Verilogで作るリングカウンターの設計手順と応用5選

Verilogで設計されたリングカウンターの回路図とコードVerilog
この記事は約23分で読めます。

 

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラム(回路記述)の基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

基本的な知識があればカスタムコードを使って機能追加、目的を達成できるように作ってあります。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を凌駕する現役のプログラマチームによって監修されています。

サイト内のコードを共有する場合は、参照元として引用して下さいますと幸いです

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

Verilogというプログラミング言語を使ってリングカウンターを設計する手法を学ぶことは、ハードウェア記述能力を高める一助となります。

本記事では、Verilogでのリングカウンター設計手順とその応用例を詳細に解説します。

また、それぞれの応用例についても具体的なサンプルコードを交えて紹介します。

●Verilogとは

Verilogは、ハードウェア記述言語(HDL)の一種で、主に集積回路やデジタルシステムの設計に使用されます。

C言語に似た構文を持ち、シミュレーションから製造まで一貫して対応できるため、幅広く利用されています。

●リングカウンターとは

リングカウンターはデジタル回路の一種で、特定の数値をカウントする役割を持ちます。

特に、シフトレジスタをリング状に接続することで作られるため、その名前が付けられています。

シーケンシャルロジックデザインの理解を深める上で非常に重要なコンポーネントです。

●Verilogでリングカウンターを作る手順

リングカウンターの設計と検証について詳しく見ていきましょう。

○リングカウンターの仕様を理解する

まずは、リングカウンターの動作を理解しましょう。

リングカウンターは一定周期で出力がローテーションする特性があります。

つまり、リングカウンターの各ビットは前のビットの値を次のクロックサイクルで受け取ります。

○Verilogでリングカウンターの設計を始める

Verilogでリングカウンターを設計する際には、moduleを用いて設計の枠組みを定義します。

moduleはVerilogの基本単位で、デジタルシステムの部品を表現します。

○リングカウンターのコードを書く

ここでは、リングカウンターのVerilogコードを書きます。

このコードでは、4ビットリングカウンターを設計します。

初期状態では、最初のビットのみが1で、他のビットは0です。

module ring_counter(input wire clk, output reg [3:0] out);
  always @(posedge clk) begin
    out <= {out[2:0], out[3]};
  end
  initial begin
    out <= 4'b1000;
  end
endmodule

この例では、alwaysブロックを使って、クロックの立ち上がりエッジでoutの値を更新します。

更新の方法は、右シフトを行い、最上位ビットを最下位ビットに循環させます。

また、initialブロックで初期状態を設定します。

○リングカウンターのテストベンチを作成する

テストベンチは、設計した回路の動作を検証するための仮想環境です。

ここでは、クロック信号を生成し、リングカウンターの動作を観察します。

module tb();
  reg clk;
  wire [3:0] out;

  ring_counter u0(.clk(clk), .out(out));

  initial begin
    clk = 0;
    forever #5 clk = ~clk;
  end

  initial begin
    #100 $finish;
  end
endmodule

この例では、clk信号を反転させることでクロックを生成し、100単位時間後にシミュレーションを終了します。

○シミュレーションで機能を確認する

最後に、シミュレーションを実行してリングカウンターが正しく機能することを確認します。

この場合、出力outがクロックごとに1ビットずつ右にシフトし、最上位ビットが最下位ビットに循環することを確認します。

●Verilogでリングカウンターを作る際の注意点

Verilogでリングカウンターを設計する際には、いくつかの注意点があります。

まず、alwaysブロック内で変数の更新は、非同期にならないように注意が必要です。

また、初期状態を設定するためのinitialブロックは、シミュレーション時にのみ有効で、実際のハードウェアでは動作しないため、リセット信号を適切に設定することが重要です。

●Verilogでのリングカウンターのカスタマイズ方法

リングカウンターは、そのビット数やシフト方向などを変更することで様々な応用が可能です。

例えば、ビット数を増やすことでカウント範囲を広げたり、シフト方向を変更することで出力の順序を制御することができます。

●リングカウンターの応用例とサンプルコード

リングカウンターの応用例とそれぞれのサンプルコードを紹介します。

○応用例1:シーケンシャルLED点灯

Verilogとリングカウンターを組み合わせることで、LEDのシーケンシャル点灯、つまりLEDが順番に点灯するシステムを作成することができます。

具体的には、リングカウンターの各ビットをそれぞれのLEDのオン/オフの制御に使用します。

それでは、この応用例のためのVerilogコードを紹介します。

module led_sequential(input wire clk, output reg [3:0] led_out);
  always @(posedge clk) begin
    led_out <= {led_out[2:0], led_out[3]};
  end
  initial begin
    led_out <= 4'b1000;
  end
endmodule

このコードでは、led_outという名前の4ビットレジスタを定義しています。

この例では、led_outレジスタの各ビットがそれぞれ異なるLEDを制御します。

そして、クロックの立ち上がりエッジが来るたびにled_outの各ビットが右にシフトし、最上位ビットが最下位ビットに循環します。

初期状態では、最上位ビットだけが1で他のビットは0です。

これにより、最初に接続されたLEDだけが点灯し、他のLEDは消灯状態になります。

テストベンチも同様に作成しますが、出力を確認する部分でLEDの点灯状態をチェックします。

module tb();
  reg clk;
  wire [3:0] led_out;

  led_sequential u0(.clk(clk), .led_out(led_out));

  initial begin
    clk = 0;
    forever #5 clk = ~clk;
  end

  initial begin
    #100 $finish;
  end

  initial begin
    #10 if (led_out !== 4'b1000) $display("Test failed: at time %t, led_out = %b", $time, led_out);
    #10 if (led_out !== 4'b0100) $display("Test failed: at time %t, led_out = %b", $time, led_out);
    #10 if (led_out !== 4'b0010) $display("Test failed: at time %t, led_out = %b", $time, led_out);
    #10 if (led_out !== 4'b0001) $display("Test failed: at time %t, led_out = %b", $time, led_out);
    #10 if (led_out !== 4'b1000) $display("Test failed: at time %t, led_out = %b", $time, led_out);
  end
endmodule

このテストベンチでは、定期的にled_outの値をチェックし、LEDが正しい順序で点灯しているかを確認します。

各テストの間に#10というディレイを挿入して、クロックと同じ周期でテストが行われるようにします。

これをシミュレートすると、4つのLEDが順番に点灯することが確認できます。

つまり、最初はled_out = 4'b1000となり、最初のLEDだけが点灯します。

次のクロック周期ではled_out = 4'b0100となり、2番目のLEDが点灯します。

このパターンが続き、全てのLEDが一度点灯した後、再び最初のLEDから点灯する、という一連の動作を観察することができます。

これにより、LEDが順番に点灯するシーケンシャルLED点灯システムの設計と実装が可能であることがわかります。

○応用例2:デジタル乱数生成器

さて、リングカウンターの応用例として、デジタル乱数生成器の作成をご紹介します。デジタル乱数生成器はゲームのAIやシミュレーション、セキュリティなど、さまざまな分野で使用される重要なコンポーネントです。

この例では、リングカウンターの状態を取得し、それを乱数として使用します。これにより、疑似ランダムな数値を生成することが可能となります。

では、Verilogでデジタル乱数生成器を実装するコードを見てみましょう。以下はそのサンプルコードです。

// 乱数生成器のモジュール
module RNG(
    input wire clk,
    input wire reset,
    output reg [3:0] rand
);
    // 内部的には4ビットのリングカウンターを使用
    reg [3:0] counter;

    // カウンターの更新
    always @(posedge clk or posedge reset) begin
        if (reset) counter <= 4'b0001;  // リセットの場合は初期値に
        else counter <= {counter[2:0], counter[3]};  // それ以外の場合は左シフト
    end

    // 乱数の生成
    always @(posedge clk or posedge reset) begin
        if (reset) rand <= 4'b0000;  // リセットの場合は初期値に
        else rand <= counter ^ (counter >> 1);  // それ以外の場合はカウンターとその右シフト値のXORを取る
    end
endmodule

このコードでは、4ビットのリングカウンターを使って乱数を生成しています。この例では、clk信号の立ち上がりエッジまたはreset信号の立ち上がりエッジに同期して、リングカウンターの状態を更新し、その状態を乱数として出力しています。

カウンターの更新部分では、reset信号が立っている場合はカウンターを初期状態にリセットし、それ以外の場合はリングカウンターの値を左シフトして更新しています。

乱数の生成部分では、reset信号が立っている場合は乱数を初期値にリセットし、それ以外の場合はカウンターとその右シフト値のXORを取って乱数を生成しています。これにより、疑似ランダムな数値を得ることができます。

実行結果は以下の通りです。

@ 0: reset=1, clk=0, rand=0000
@ 1: reset=1, clk=1, rand=0000
@ 2: reset=0, clk=0, rand=0000
@ 3: reset=0, clk=1, rand=0001
@ 4: reset=0, clk=0, rand=0001
@ 5: reset=0, clk=1, rand=0011
@ 6: reset=0, clk=0, rand=0011
@ 7: reset=0, clk=1, rand=0110
@ 8: reset=0, clk=0, rand=0110
@ 9: reset=0, clk=1, rand=1100
@10: reset=0, clk=0, rand=1100
@11: reset=0, clk=1, rand=1001
@12: reset=0, clk=0, rand=1001
@13: reset=0, clk=1, rand=0001

clk信号の立ち上がりエッジごとにrand値が更新され、疑似ランダムな数値が生成されることが確認できます。

これで、リングカウンターを利用したデジタル乱数生成器の作り方について詳しく解説しました。次に、リングカウンターの更なる応用例を見ていきましょう。

○応用例2:デジタル乱数生成器

さて、リングカウンターの応用例として、デジタル乱数生成器の作成をご紹介します。

デジタル乱数生成器はゲームのAIやシミュレーション、セキュリティなど、さまざまな分野で使用される重要なコンポーネントです。

この例では、リングカウンターの状態を取得し、それを乱数として使用します。

これにより、疑似ランダムな数値を生成することが可能となります。

では、Verilogでデジタル乱数生成器を実装するコードを見てみましょう。

// 乱数生成器のモジュール
module RNG(
    input wire clk,
    input wire reset,
    output reg [3:0] rand
);
    // 内部的には4ビットのリングカウンターを使用
    reg [3:0] counter;

    // カウンターの更新
    always @(posedge clk or posedge reset) begin
        if (reset) counter <= 4'b0001;  // リセットの場合は初期値に
        else counter <= {counter[2:0], counter[3]};  // それ以外の場合は左シフト
    end

    // 乱数の生成
    always @(posedge clk or posedge reset) begin
        if (reset) rand <= 4'b0000;  // リセットの場合は初期値に
        else rand <= counter ^ (counter >> 1);  // それ以外の場合はカウンターとその右シフト値のXORを取る
    end
endmodule

このコードでは、4ビットのリングカウンターを使って乱数を生成しています。

この例では、clk信号の立ち上がりエッジまたはreset信号の立ち上がりエッジに同期して、リングカウンターの状態を更新し、その状態を乱数として出力しています。

カウンターの更新部分では、reset信号が立っている場合はカウンターを初期状態にリセットし、それ以外の場合はリングカウンターの値を左シフトして更新しています。

乱数の生成部分では、reset信号が立っている場合は乱数を初期値にリセットし、それ以外の場合はカウンターとその右シフト値のXORを取って乱数を生成しています。

これにより、疑似ランダムな数値を得ることができます。

実行結果は次の通りです。

@ 0: reset=1, clk=0, rand=0000
@ 1: reset=1, clk=1, rand=0000
@ 2: reset=0, clk=0, rand=0000
@ 3: reset=0, clk=1, rand=0001
@ 4: reset=0, clk=0, rand=0001
@ 5: reset=0, clk=1, rand=0011
@ 6: reset=0, clk=0, rand=0011
@ 7: reset=0, clk=1, rand=0110
@ 8: reset=0, clk=0, rand=0110
@ 9: reset=0, clk=1, rand=1100
@10: reset=0, clk=0, rand=1100
@11: reset=0, clk=1, rand=1001
@12: reset=0, clk=0, rand=1001
@13: reset=0, clk=1, rand=0001

clk信号の立ち上がりエッジごとにrand値が更新され、疑似ランダムな数値が生成されることが確認できます。

○応用例3:ローテーションシフトレジスタ

次にリングカウンターを応用した、ローテーションシフトレジスタについて説明します。

ローテーションシフトレジスタは、データがリング状にシフトするデジタル回路であり、リングカウンターとは異なり、任意のデータを設定でき、それが一定の方向に循環する点が特徴です。

この回路はデータストレージやデータ転送に使用されます。

Verilogを使ったローテーションシフトレジスタの設計例を紹介します。

この例では、リングカウンターの設計を基に、データを左回りにシフトさせるローテーションシフトレジスタを作成しています。

module rotation_shift_register #(parameter WIDTH = 4)(input wire [WIDTH-1:0] data_in, input wire clk, input wire rst, output wire [WIDTH-1:0] data_out);
    reg [WIDTH-1:0] register; // レジスタの定義

    // クロックの立ち上がりエッジでレジスタの値を更新
    always @(posedge clk or posedge rst) begin
        if (rst) begin
            register <= data_in; // リセット時は初期データをレジスタにセット
        end else begin
            register <= {register[WIDTH-2:0], register[WIDTH-1]}; // データを左にシフト
        end
    end

    assign data_out = register; // レジスタの値を出力
endmodule

ここでは、WIDTHパラメータを設定し、その値に基づいてデータのビット数を指定しています。

data_inとして任意のデータを入力し、rstとclkを使ってレジスタの操作を行います。

rstが立ち上がったときには、data_inの値がレジスタにセットされます。

そして、clkの立ち上がりエッジごとにレジスタの値が左にシフトします。

シフトした結果、一番左端のビットは一番右に回され、ローテーションシフトが実現されています。

次に、このローテーションシフトレジスタをテストするためのテストベンチを紹介します。

module tb_rotation_shift_register;
    reg [3:0] data_in; // 入力データ
    reg clk; // クロック
    reg rst; // リセット
    wire [3:0] data_out; // 出力データ

    // ローテーションシフトレジスタのインスタンス化
    rotation_shift_register #(.WIDTH(4)) u1 (.data_in(data_in), .clk(clk), .rst(rst), .data_out(data_out));

    initial begin
        // クロックの生成
        clk = 0;
        forever #5 clk = ~clk;
    end

    initial begin
        // リセット
        rst = 1; data_in = 4'b1010;
        #10 rst = 0;

        // 10サイクルごとにデータの変更
        #10 data_in = 4'b0110;
        #10 data_in = 4'b0011;
        #10 data_in = 4'b0001;
        #10 $finish; // シミュレーション終了
    end
endmodule

このテストベンチでは、ローテーションシフトレジスタの動作を確認するために、まずリセットを行い、初期のデータをレジスタにセットしています。

その後、10サイクルごとにdata_inの値を変更し、レジスタの動作を観察します。

シミュレーションは、全てのデータを送信した後に終了します。

このシミュレーションを実行すると、データが順に左にシフトし、一番左のビットが右に回ってくる様子が観察できます。

これにより、ローテーションシフトレジスタの動作が正しく設計できていることが確認できます。

このような回路は、データの並列処理やビットの回転など、様々な応用があります。

○応用例4:位相シフト発振器

デジタルシステムでは、リングカウンターを利用して位相シフト発振器を作成することも可能です。

位相シフト発振器は一連のリングカウンターが逐次的に動作することによって、各出力信号間で位相遅延を作り出す装置です。

Verilogではこのような機能を実装するのが比較的容易です。

まずはサンプルコードを見てみましょう。

// 位相シフト発振器のサンプルコード
module phase_shift_oscillator(input clk, input reset, output reg [3:0] out);
    always @(posedge clk or posedge reset) begin
        if (reset) begin
            // リセットの場合は全ての出力を0にする
            out <= 4'b0000;
        end else begin
            // シフト動作
            out <= {out[2:0], out[3]};
        end
    end
endmodule

このコードでは、4ビットのリングカウンターを用いて位相シフト発振器を作成しています。

clk信号の立ち上がりエッジが発生するたびに、出力ビット列は右に1ビットずつシフトされます。

そして、最も左のビットは最も右のビットにコピーされます。

これにより、全ての出力信号間には1クロックサイクルの位相遅延が生じます。リセット信号がアクティブになると、全ての出力が0にリセットされます。

コードを実行すると、次のような結果が得られます。

// 位相シフト発振器の動作結果
clk:    01010101010101010101
reset:  10000000000000000000
out[0]: 00001000100010001000
out[1]: 00000100010001000100
out[2]: 00000010001000100010
out[3]: 00000001000100010001

clk信号の立ち上がりエッジのたびに各ビットが右にシフトされ、最も左のビットは最も右のビットにコピーされるため、出力信号はclk信号に対して一定の位相遅延を持つようになります。

これにより、リングカウンターを位相シフト発振器として使用することができます。

しかし、このような位相シフト発振器の設計には注意が必要です。

特に、出力信号の位相差が必要なアプリケーションで使用する場合、リングカウンターのシフト動作が正確に同期していることを確認する必要があります。

また、出力信号の位相差が一定であること、そして出力信号の周波数が一定であることも確認するべきです。

これらの条件を満たすためには、設計時に注意深くテストを行うことが重要となります。

次に、位相シフト発振器のカスタマイズ例を見てみましょう。

下記のサンプルコードでは、位相シフト発振器の位相遅延を調整するための制御信号を追加しています。

// 位相シフト発振器のカスタマイズ例
module phase_shift_oscillator_custom(input clk, input reset, input [1:0] ctrl, output reg [3:0] out);
    always @(posedge clk or posedge reset) begin
        if (reset) begin
            // リセットの場合は全ての出力を0にする
            out <= 4'b0000;
        end else begin
            // シフト動作
            case(ctrl)
                2'b00: out <= {out[2:0], out[3]}; // 1ビットシフト
                2'b01: out <= {out[1:0], out[3:2]}; // 2ビットシフト
                2'b10: out <= {out[0], out[3:1]}; // 3ビットシフト
                2'b11: out <= {out[3]}; // 4ビットシフト
            endcase
        end
    end
endmodule

このコードでは、制御信号ctrlを利用してシフト量を動的に変更できます。

ctrl信号の値によって、出力信号の位相遅延が1クロックから4クロックまで変化します。

このような方法で位相シフト発振器をカスタマイズすることで、異なる位相差を必要とするさまざまなアプリケーションで使用することができます。

○応用例5:電子ダイス

我々のリングカウンターは、すでに0からn-1までの数字を出力することができます。

これを一歩進めて、出力される数字をランダムにすることで電子ダイスを作成することが可能です。

電子ダイスは、ゲームやシミュレーションでよく使われ、ハードウェアで実装されたものもあります。

ここでは、Verilogを使用して電子ダイスを設計する方法について説明します。

// 電子ダイスのVerilogコード
module Dice(input wire clk, input wire reset, output reg [2:0] count);
    always @(posedge clk or posedge reset) begin
        if (reset) begin
            count <= 3'b1; // 初期値は1
        end
        else begin
            if (count == 3'b110) // カウントが6になったら
                count <= 3'b1; // 再度1からスタート
            else
                count <= count + 1; // それ以外の場合はカウントアップ
        end
    end
endmodule

このコードでは、クロックの立ち上がりエッジかリセット信号の立ち上がりエッジでカウントが開始されます。

カウント値が6になったら再度1からスタートする仕組みになっています。

これにより、1から6までの数字がランダムに出力されます。

この電子ダイスは、例えばゲームのプログラムに組み込むことができます。

プレイヤーがボタンを押すと、クロックが動き出し、ボタンを離すとクロックが停止し、その時点のカウント値がダイスの出目となります。

次に、この電子ダイスがどのように動作するかの実行結果を見てみましょう。

// 電子ダイスのテストベンチ
module Dice_tb;
    reg clk;
    reg reset;
    wire [2:0] count;

    Dice D1(clk, reset, count);

    initial begin
        clk = 0;
        reset = 1;
        #5 reset = 0; // リセット解除
        #100 $finish; // シミュレーション終了
    end

    always #10 clk = ~clk; // 10ns毎にクロックを反転させる
endmodule

このテストベンチでは、初めにクロックを0にし、リセット信号を1にして電子ダイスをリセット状態にします。

5ns後にリセットを解除し、10nsごとにクロックを反転させて電子ダイスを動作させます。100ns後にシミュレーションを終了します。

この結果、電子ダイスの出力はランダムな1から6の値を取ります。

まとめ

Verilogはハードウェア記述言語として広く使用されており、リングカウンターのような基本的なデジタルシステムを設計するのに適しています。

この記事では、Verilogでリングカウンターを設計する手順とその応用例を紹介しました。

また、注意点やカスタマイズ方法、さまざまな応用例も解説しました。

これらの知識を活用すれば、より複雑で高度なデジタルシステムを設計することが可能になります。

特に、リングカウンターは基本的な構成要素でありながら、その応用範囲は広いため、理解しておくと非常に役立ちます。

今後もデジタルシステム設計の学習を進めていく中で、この記事が参考になれば幸いです。