Verilogゲートレベル記述入門!ステップバイステップの5つのサンプルコード

初心者のためのVerilogゲートレベル記述のサンプルコードVerilog
この記事は約20分で読めます。

 

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

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

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

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

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

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

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

はじめに

これから皆さんにVerilog言語を用いたゲートレベル記述の基礎について説明します。

具体的なサンプルコードを交えながらその基本から応用までを解説します。

この記事は初心者にとっては最適なガイドとなります。

この記事を通じて、あなた自身のプログラミングスキルの向上に繋がれば幸いです。

●Verilogとは

Verilogは、主にハードウェア記述言語(HDL)として使用されるプログラミング言語の一つです。

デジタルシステムや電子回路の設計・検証において利用されます。

特に、IC(集積回路)の設計やシミュレーションでは欠かせない存在となっています。

Verilogはその記述レベルにより、大まかにビヘイビアル記述とゲートレベル記述に分けられます。

ビヘイビアル記述は高レベルな抽象度を持ち、システムの振る舞いそのものを記述します。

一方、ゲートレベル記述は、回路の具体的な構成要素(ゲート)を表現します。

今回は、このゲートレベル記述の方法を主に解説します。

●ゲートレベル記述の基本

ゲートレベル記述では、回路がどのように動作するかではなく、その回路がどのように構成されているかを記述します。

具体的には、AND、OR、NOTなどの基本的な論理ゲートを用いて、電子回路を表現します。

ここでは、それぞれの論理ゲートがどのように表現されるかについて見ていきましょう。

●ゲートレベル記述の基本的な要素

Verilogのゲートレベル記述で用いる基本的な論理ゲートには以下のようなものがあります。

○ANDゲート

ANDゲートは、すべての入力が高レベル(1)であるときのみ、出力が高レベル(1)となるゲートです。

Verilogでの記述例を表します。

// ANDゲートの記述例
and(y, a, b); // y = a AND b

このコードでは、andゲートを使ってyの値をaとbの論理積とする記述を行っています。

aとb両方が1の場合のみyも1になります。

○ORゲート

ORゲートは、入力のいずれかが高レベル(1)であるとき、出力が高レベル(1)となるゲートです。

Verilogでの記述例を示します。

// ORゲートの記述例
or(y, a, b); // y = a OR b

このコードでは、orゲートを使ってyの値をaとbの論理和とする記述を行っています。

aとbのどちらかが1の場合、yは1になります。

○NOTゲート

次に、NOTゲートの操作について紹介します。

NOTゲートは反転ゲートとも呼ばれ、一つの入力信号を取り、それを反転させる役割を果たします。

具体的には、0を入力とすると、出力は1になり、1を入力とすると出力は0になります。

module not_gate(input wire a, output wire y);
    assign y = ~a;
endmodule

このコードでは、VerilogのビルトインのNOT演算子~を使ってNOTゲートを表現しています。

ここでは、aという名前の入力信号を受け取り、その信号を反転させた結果をyという名前の出力信号として出力しています。

○NANDゲート

NANDゲートはNOT ANDの略で、ANDゲートの出力を反転させたものになります。

NANDゲートの特性上、出力は入力が全て1である場合に限り、0になります。

それ以外の場合は出力は常に1になります。

module nand_gate(input wire a, input wire b, output wire y);
    assign y = ~(a & b);
endmodule

このコードでは、入力信号abをAND演算子&で結びつけ、その結果をNOT演算子~で反転させています。

これによりNANDゲートの挙動が再現されます。

○NORゲート

次に、NORゲートについて説明します。

NORゲートはNOT ORの略で、ORゲートの出力を反転させたものになります。

NORゲートの出力は、入力が全て0である場合に限り、1になります。それ以外の場合は出力は常に0になります。

module nor_gate(input wire a, input wire b, output wire y);
    assign y = ~(a | b);
endmodule

上記のコードでは、入力信号abをOR演算子|で結びつけ、その結果をNOT演算子~で反転させています。

これによりNORゲートの挙動が再現されます。

○XORゲート

XORゲートは排他的論理和とも呼ばれ、2つの入力信号が異なるときに限り、出力が1となります。

つまり、2つの入力信号が同じ(両方とも0または1)の場合、出力は0となります。

module xor_gate(input wire a, input wire b, output wire y);
    assign y = a ^ b;
endmodule

このコードでは、入力信号abに対してXOR演算子^を適用しています。

これによりXORゲートの挙動が再現されます。

○XNORゲート

XNORゲートはXORゲートの出力を反転させたもので、2つの入力信号が同じときに限り、出力が1となります。

つまり、2つの入力信号が異なる(一方が0で他方が1)の場合、出力は0となります。

module xnor_gate(input wire a, input wire b, output wire y);
    assign y = ~(a ^ b);
endmodule

このコードでは、入力信号abに対してXOR演算子^を適用した結果をNOT演算子~で反転しています。

これによりXNORゲートの挙動が再現されます。

●ゲートレベル記述の使い方

さて、Verilogのゲートレベル記述の基本的な要素を学んだところで、次に進んでその具体的な使い方について学んでいきましょう。

このセクションでは、様々なシナリオでゲートレベル記述がどのように活用されるのかを、具体的なサンプルコードを交えて説明します。

○サンプルコード1:基本的なゲートの使用

まずは、基本的なゲート(AND、OR、NOT)の使用方法について見ていきましょう。

下記のサンプルコードでは、ANDゲート、ORゲート、およびNOTゲートを用いて特定の回路を構築しています。

module basic_gate(input A, B, output Y);
  wire w1, w2;
  and(w1, A, B);  // ANDゲートの作成
  not(w2, w1);    // NOTゲートの作成
  or(Y, A, w2);   // ORゲートの作成
endmodule

このコードではAとBの2つの入力と、Yという1つの出力を持つモジュールを定義しています。

そして、ワイヤw1w2を使用して、それぞれANDゲートとNOTゲートを形成しています。

最後に、これらの結果を組み合わせてORゲートを形成し、出力Yを生成しています。

このコードを実行すると、AとBの論理値に基づいてYの出力が計算されます。

具体的には、AとBが両方とも真であれば、ANDゲートの出力(w1)は真となります。

それがNOTゲートを通過すると偽になり、ORゲートの出力(Y)はA(真)とw2(偽)の間のOR操作の結果、真となります。

そのため、最終的な出力Yは真となります。

○サンプルコード2:複雑な回路の構築

次に、複数のゲートを組み合わせて複雑な回路を構築する方法について見ていきましょう。

下記のサンプルコードは、NANDゲート、NORゲート、およびXORゲートを使用して一種の組み合わせ回路を作成しています。

module complex_gate(input A, B, output Y);
  wire w1, w2, w3;
  nand(w1, A, B); // NANDゲートの作成
  nor(w2, A, w1); // NORゲートの作成
  xor(Y, w2, B);  // XORゲートの作成
endmodule

このコードでは、NANDゲートは入力AとBを受け取り、出力をワイヤw1に提供します。

次に、NORゲートは入力Aとワイヤw1の値を受け取り、出力をワイヤw2に提供します。

最後に、XORゲートはワイヤw2と入力Bの値を受け取り、最終的な出力Yを生成します。

このような複雑な回路は、具体的な計算処理や複雑なデジタルデバイスの構築に使用されます。

また、Verilogの強力なゲートレベル記述の機能を活用することで、更に複雑なロジックを表現することも可能になります。

○サンプルコード3:順序回路の構築

順序回路は、その名の通り順序が重要な回路で、過去の入力状態や現在の入力に応じて出力が決まる特徴を持っています。

一般的に順序回路にはフリップフロップやレジスタ等が用いられます。

この部分では、Verilogで順序回路をゲートレベルで記述する方法を、サンプルコードを用いて解説します。

// 順序回路のサンプルコード
module d_flip_flop (
    input wire d, clk, reset, // 入力線: D入力、クロック、リセット
    output reg q, q_bar       // 出力線: Q出力、Q反転出力
);
    always @(posedge clk or posedge reset) begin
        if (reset) begin
            q <= 0;
            q_bar <= 1;
        end else begin
            q <= d;
            q_bar <= ~d;
        end
    end
endmodule

このサンプルコードでは、D型フリップフロップの動作をシミュレートしています。

D型フリップフロップは、クロックの立ち上がりエッジ時にD入力がQ出力に伝達する順序回路です。

また、リセット信号が立ち上がった時点でQ出力を0に、Q反転出力を1にリセットします。

「always @(posedge clk or posedge reset)」という記述は、clkまたはresetの信号が立ち上がった時(立ち上がりエッジ時)に、ブロック内の処理を行うことを指定しています。

そして、その中でresetがアクティブになっていたら、qとq_barをリセットします。

それ以外の場合、つまりクロックの立ち上がりエッジ時には、dの状態がqに伝達し、その反転がq_barに伝達します。

このコードを実行すると、クロックの立ち上がりエッジごとに、D入力がQ出力に伝達し、その反転がQ反転出力に伝達します。

また、リセット信号が立ち上がると、Q出力とQ反転出力はそれぞれ0と1にリセットされます。

○サンプルコード4:組み合わせ回路の構築

次に、組み合わせ回路について解説します。

組み合わせ回路は、その出力が現在の入力状態だけに依存する回路で、過去の状態を記憶しません。

これは、順序回路とは対照的な特徴です。

具体的には、論理ゲートの組み合わせだけで構成される回路がこれに該当します。

ここでは、Verilogでの組み合わせ回路の記述を、サンプルコードを通じて説明します。

// 組み合わせ回路のサンプルコード
module comb_circuit (
    input wire a, b, // 入力線
    output wire y    // 出力線
);
    assign y = (a & b) | (~a & ~b);
endmodule

このサンプルコードでは、入力aとbのANDと、入力aとbの否定のANDをORで組み合わせた結果を出力yに割り当てています。

これはXNOR(排他的論理和の否定)ゲートの動作をシミュレートしています。

このコードを実行すると、aとbが同じ場合(両方とも0または1)にyは1となり、aとbが異なる場合にyは0となります。

これはXNORゲートの特性を反映した結果です。

今回のような組み合わせ回路の記述においては、「assign」ステートメントが重要な役割を果たします。

これは、右辺の式の結果を左辺のワイヤに連続的に割り当てるもので、これにより現在の入力状態に基づいて出力が即座に反映されます。

○サンプルコード5:モジュールの利用

ここでは、Verilogのゲートレベル記述におけるモジュールの利用方法を具体的なサンプルコードとともに説明します。

モジュールはVerilogの一部分の回路を表す単位で、これを使うことによって複雑な回路をより小さな部品に分割して管理することが可能になります。

まず、基本的なモジュールの構文について解説します。

モジュールは’module’キーワードから始まり、その後にモジュール名が続きます。

入力と出力ポートは’input’および’output’キーワードを用いて定義します。

また、モジュールの本体は’begin’と’end’で囲まれます。

下記のサンプルコードでは、ANDゲート、ORゲート、NOTゲートを使って全加算器を作成するモジュールを紹介しています。

全加算器は二つの入力値と前の桁からの繰り上がりを加算し、結果と次の桁への繰り上がりを出力する回路です。

// モジュールの定義
module FullAdder(input wire a, input wire b, input wire cin, output wire sum, output wire cout);
    wire w1, w2, w3;

    // XORゲートを用いて和を計算
    xor(w1, a, b);
    xor(sum, w1, cin);

    // ANDゲートとORゲートを用いて繰り上がりを計算
    and(w2, a, b);
    and(w3, cin, w1);
    or(cout, w2, w3);
endmodule

このコードではまず、’module’キーワードを用いてモジュールを定義しています。

名前は’FullAdder’とし、5つのポート’a’, ‘b’, ‘cin’, ‘sum’, ‘cout’を定義しています。

また、内部的にはワイヤ’w1′, ‘w2’, ‘w3’を定義しています。

その中で、XORゲートを2つ用いて和を計算しています。

そして、ANDゲートとORゲートを組み合わせて繰り上がりを計算しています。

このようにモジュールを用いることで、複雑な回路を簡単に表現することが可能になります。

また、このモジュールを別のモジュール内で使用することで、さらに大きな回路を構築することも可能です。

例えば、次のコードでは上記のFullAdderモジュールを用いて、4ビット全加算器を構築しています。

// 4ビット全加算器の定義
module FourBitAdder(input wire [3:0] a, input wire [3:0] b, output wire [3:0] sum, output wire cout);
    wire [3:0] c;

    // 1ビット目
    FullAdder fa0 (.a(a[0]), .b(b[0]), .cin(1'b0), .sum(sum[0]), .cout(c[0]));

    // 2ビット目
    FullAdder fa1 (.a(a[1]), .b(b[1]), .cin(c[0]), .sum(sum[1]), .cout(c[1]));

    // 3ビット目
    FullAdder fa2 (.a(a[2]), .b(b[2]), .cin(c[1]), .sum(sum[2]), .cout(c[2]));

    // 4ビット目
    FullAdder fa3 (.a(a[3]), .b(b[3]), .cin(c[2]), .sum(sum[3]), .cout(cout));
endmodule

上記のコードでは、4つのFullAdderモジュールを使用し、それぞれの桁の全加算器として動作させています。

また、各全加算器の繰り上がりは次の桁の全加算器へと渡され、最終的な繰り上がりは出力ポート’cout’に接続されます。

これらのサンプルコードからも分かるように、Verilogのモジュールは、複雑なデジタル回路の設計を簡易に行う上で非常に強力なツールと言えるでしょう。

●ゲートレベル記述の注意点と対処法

ゲートレベル記述に取り組む上で把握しておくべきいくつかの注意点が存在します。

それらを理解し、対策を練ることで、より効率的にVerilogプログラミングを進めることができます。

まず、Verilogのゲートレベル記述では、物理的なデジタル回路の動作を直接的に表現します。

そのため、論理ゲートの接続や配線、タイミングなどの具体的な要素を手動で制御する必要があります。

これは、より高レベルの記述方式に比べると、記述が複雑になり、またエラーを生じやすい一面も持っています。

例えば、ゲート間の配線を誤って記述すると、思わぬ動作を引き起こす可能性があります。

一方で、ゲートレベル記述は、その動作が直接的に回路の動作と一致するため、デバッグが容易であるという利点もあります。

次に、ゲートレベル記述では、すべてのゲートが同時に動作するというデジタル回路の性質を表現することが難しいという問題があります。

特に、順序回路や組み合わせ回路を扱う場合、信号の伝播遅延を考慮に入れなければならない場合があります。

この遅延を考慮しないと、正確な動作を模倣することが難しくなります。

たとえば、次のコードは2入力ANDゲートを表しています。

しかし、このコードでは、遅延が考慮されていません。

// 2入力ANDゲート
module and_gate(input wire a, b, output wire y);
  assign y = a & b;
endmodule

これを遅延を考慮に入れた記述にするためには、#記号を使用して遅延時間を指定します。

下記のコードでは、ANDゲートの遅延を10としました。

// 2入力ANDゲート(遅延あり)
module and_gate(input wire a, b, output wire y);
  assign #10 y = a & b;
endmodule

このように、遅延を考慮した記述を行うことで、リアルタイムの回路動作をより正確にシミュレートできます。

また、モジュール内部で状態を持つ順序回路をゲートレベルで記述する際には、その状態遷移を正確に表現するためには、適切なフリップフロップを用いることが必要です。

フリップフロップの設計や接続にミスがあると、順序回路の動作が不安定になる可能性があります。

●Verilogでのゲートレベル記述のカスタマイズ方法

Verilogを使用してゲートレベルの記述を行う際に、カスタマイズを行いたいと思うことはしばしばあります。

特に、特定の回路設計のニーズに応じて、標準のゲートレベル記述をカスタマイズすることは一般的なシナリオです。

それでは、Verilogを使ってゲートレベル記述をカスタマイズする方法をいくつか紹介します。

○カスタムゲートの作成

Verilogでは、複数のゲートを組み合わせて一つのカスタムゲートを作ることができます。

これは、特定のロジック操作を頻繁に使用する場合や、特定のロジック操作を一つの単位として扱いたい場合に有用です。

カスタムゲートはモジュールとして定義し、必要な場所でそのモジュールをインスタンス化します。

例えば、次のサンプルコードは、ANDゲートとORゲートを組み合わせたカスタムゲートを作成しています。

このカスタムゲートは入力信号A、B、Cを受け取り、出力信号Yを生成します。

この例では、(A AND B) OR Cというロジック演算を行います。

module custom_gate(A, B, C, Y);
  input A, B, C;
  output Y;
  wire AB;

  // AとBのAND
  and(AB, A, B);

  // (A AND B) OR C
  or(Y, AB, C);
endmodule

このコードでは、まず入力信号A、B、Cと出力信号Yを定義しています。

次に、ANDゲートの出力を一時的に保持するためのワイヤABを定義します。

そして、andゲートとorゲートを使って、所望のロジック演算を行っています。

このカスタムゲートは、他のVerilogコードから呼び出すことで使用することができます。

module top_module;
  reg A, B, C;
  wire Y;

  // カスタムゲートのインスタンス化
  custom_gate U1(A, B, C, Y);

  // 入力値の設定
  initial begin
    A = 0; B = 1; C = 0;
  end

  // 結果の表示
  initial begin
    #10;
    $display("Y = %b", Y);
  end
endmodule

このコードは、カスタムゲートcustom_gateをインスタンス化し、その入力と出力を設定しています。

また、初期値の設定と結果の表示も行っています。

このコードを実行すると、「Y = 0」という結果が得られます。

このように、Verilogでは独自のカスタムゲートを作成することで、特定のロジック操作を一つの単位として扱うことができます。

これにより、コードの再利用性が向上し、全体の設計がより理解しやすくなるでしょう。

○特定の動作をするカスタムモジュールの作成

Verilogでは、特定の動作をするモジュールをカスタム作成することも可能です。

これにより、特定の動作を繰り返す場合などに、その動作を一つのモジュールとして抽象化できます。

下記のサンプルコードでは、特定のビットパターンが入力されたときに特定の出力を生成するカスタムモジュールを作成します。

このカスタムモジュールは、4ビットの入力を受け取り、特定のビットパターンに対応した出力を生成します。

具体的には、「0001」が入力された場合は「1」を出力し、「0010」が入力された場合は「2」を出力し、その他のビットパターンが入力された場合は「0」を出力します。

module custom_module(input [3:0] A, output reg [1:0] Y);
  always @(A)
  case (A)
    4'b0001: Y <= 2'b01;
    4'b0010: Y <= 2'b10;
    default: Y <= 2'b00;
  endcase
endmodule

このカスタムモジュールは、alwaysブロックとcaseステートメントを用いて特定の動作を実現しています。

入力パターンに応じて異なる出力を生成するために、caseステートメントを使用しています。

入力Aの値が変化するたびに、このcaseステートメントが評価され、対応する出力Yが生成されます。

このカスタムモジュールを使用するためのトップモジュールのサンプルコードは次のとおりです。

module top_module;
  reg [3:0] A;
  wire [1:0] Y;

  // カスタムモジュールのインスタンス化
  custom_module U1(A, Y);

  // 入力値の設定

と結果の表示
  initial begin
    A = 4'b0001;
    #10;
    $display("Y = %b", Y);
    A = 4'b0010;
    #10;
    $display("Y = %b", Y);
    A = 4'b0110;
    #10;
    $display("Y = %b", Y);
  end
endmodule

このコードでは、カスタムモジュールcustom_moduleをインスタンス化し、その入力と出力を設定しています。また、初期値の設定と結果の表示も行っています。

このコードを実行すると、「Y = 01」、「Y = 10」、「Y = 00」という結果が順に表示されます。

Verilogにおけるゲートレベル記述のカスタマイズは、上記のような方法で行うことができます。

カスタムゲートやカスタムモジュールを作成することで、特定のロジック操作や特定の動作を一つの単位として抽象化し、再利用可能にすることが可能です。

これにより、全体の設計がより理解しやすく、管理しやすくなるでしょう。

ただし、カスタムゲートやカスタムモジュールの設計には、その振る舞いを正確に理解し、適切に使用することが重要です。

誤った設計や使用は、意図しない動作を引き起こす可能性があります。

したがって、カスタマイズを行う際には、詳細な計画と十分なテストが不可欠です。

この記事がVerilogのゲートレベル記述とそのカスタマイズ方法についての理解を深める一助になれば幸いです。

まとめ

Verilogのゲートレベル記述は、デジタル回路の設計とシミュレーションにおいて重要な役割を果たします。

本記事では、Verilogにおけるゲートレベル記述の基本的な要素とその使い方、注意点と対処法、さらにはカスタマイズ方法について説明しました。

具体的なサンプルコードを通じて、Verilogのゲートレベル記述がどのように動作するのか、また、どのようにカスタマイズして自分のニーズに合わせることができるのかを理解できたことでしょう。

Verilogは学習する価値のある重要なツールであり、今後もその使用頻度と重要性は増していくでしょう。

初心者の方も経験者の方も、本記事がVerilogのゲートレベル記述とその可能性について新たな洞察を提供し、次のステップに進むための手助けになることを願っています。