はじめに
デジタル回路の設計は、エレクトロニクス技術者にとって重要なスキルであり、この世界には多くの方法とツールが存在します。
その中でも、ハードウェア記述言語 (HDL) の一種であるVerilogは、特に一般的に使用されています。
Verilogを効果的に使いこなすための基本的な要素として、今日は「プリミティブ」に焦点を当て、その使用方法と実用的なコード例を10個紹介します。
○Verilogとは
Verilogは、デジタルシステムの設計と検証のために使用されるハードウェア記述言語 (HDL) です。
一般的に、集積回路やFPGAの設計に使用されます。
Verilogの特徴は、ソフトウェア言語に似た構文を持ちながら、ハードウェアの性質を正確に記述することができる点です。
○Verilogのプリミティブとは
Verilogのプリミティブは、基本的な論理ゲート(AND、OR、NOTなど)やその他のハードウェア要素を記述するための組み込みオブジェクトです。
これらのプリミティブを組み合わせて、より複雑なデジタル回路を設計することができます。
●Verilogプリミティブの基本
プリミティブはVerilogの基本的な構成要素であり、デジタルロジック設計の基礎となるゲートレベルの要素を直接記述することができます。
○プリミティブの構造
Verilogのプリミティブは、入力と出力を持つゲートを表現します。
一般的に、これらのゲートは1つの出力と複数の入力を持ちます。
プリミティブの基本的な形式は次のようになります。
たとえば、2入力ANDゲートのプリミティブは次のようになります。
このコードでは、and
プリミティブを使用して2入力ANDゲートを作成しています。
この例では、out
は出力、in1
とin2
は入力を表しています。
○プリミティブの種類
Verilogでは、基本的な論理ゲートからタイムディレイやワイヤなどの要素まで、さまざまなプリミティブが提供されています。
最も一般的なプリミティブには、and
、nand
、or
、nor
、xor
、xnor
、not
などの論理ゲートがあります。
●実用的なコード例
○サンプルコード1:ANDゲート
まずは最も基本的なゲートの一つであるANDゲートから始めます。
ANDゲートは、すべての入力が1のときにのみ出力が1になる論理ゲートです。
下記のコードは、2入力ANDゲートを作成するVerilogプリミティブの例です。
このコードでは、and
プリミティブを使用して、入力a
とb
のANDゲートを作成しています。
出力y
は、a
とb
が両方とも1のときのみ1になります。
このコードをシミュレーションすると、a
とb
の両方が1の場合に限り、出力y
が1になることが確認できます。
○サンプルコード2:ORゲート
ここでは、Verilogプリミティブを用いてORゲートを作成する方法を解説します。
ORゲートは基本的な論理ゲートの一つであり、入力されたすべての信号のうち、どれか一つでも”1″があれば出力が”1″となる特性を持ちます。
その動作を模倣するVerilogコードの例を紹介します。
このコードは、名前が’orgate’のモジュールを宣言しています。
このモジュールはORゲートの動作を行います。
モジュールの入力として’A’と’B’が定義され、出力として’Y’が定義されています。
その後、ORゲートのプリミティブ関数であるor()
関数が使用されています。
この関数は、第一引数に出力の名前を、以降の引数には入力の名前を指定します。
このコードを実行すると、入力’A’と’B’の論理ORをとった結果が出力’Y’として得られます。
つまり、’A’または’B’が”1″であれば、出力’Y’は”1″となります。’A’と’B’がともに”0″の場合のみ、出力’Y’は”0″となります。
○サンプルコード3:NOTゲート
次に、Verilogプリミティブを使用してNOTゲートを作成する方法を解説します。NOTゲートは入力の論理値を反転するゲートです。
つまり、入力が”0″なら出力は”1″となり、入力が”1″なら出力は”0″となります。
その動作を模倣するVerilogコードを紹介します。
このコードでは、名前が’notgate’のモジュールを宣言しています。
このモジュールはNOTゲートの動作を行います。モジュールの入力として’A’が定義され、出力として’Y’が定義されています。
その後、NOTゲートのプリミティブ関数であるnot()
関数が使用されています。
この関数も、第一引数に出力の名前を、第二引数に入力の名前を指定します。
このコードを実行すると、入力’A’の論理反転が出力’Y’として得られます。
つまり、’A’が”0″であれば、出力’Y’は”1″となります。逆に、’A’が”1″であれば、出力’Y’は”0″となります。
○サンプルコード4:NANDゲート
次に紹介するのは、NANDゲートを表現するVerilogのプリミティブの実用例です。
このコードでは、nandゲートプリミティブを使用して、単純なNANDゲートを表現するVerilogコードを書きます。
このコードでは、入力としてワイヤaとbを、出力としてワイヤyを指定しています。
その後、NANDゲートのプリミティブ(nand)を使用して、出力yと入力a, bの間にNANDゲートを作成します。
つまり、この例では、入力aとbが両方とも論理1(真)のときのみ出力yが論理0(偽)となり、それ以外の場合では出力yは論理1(真)となります。
このコードを実行すると、次のような結果が得られます。
このテストベンチでは、NANDゲートの振る舞いを確認するため、全ての可能な入力パターンについてその結果を表示します。
結果は次のようになります。
これらの結果は、NANDゲートの真理表と一致します。
それぞれの入力の組み合わせに対して、出力yが期待通りの結果を示していることがわかります。
つまり、このコードは正しくNANDゲートをシミュレートしていると言えます。
○サンプルコード5:NORゲート
我々が次に触れるのは、NORゲートです。
NORゲートはORゲートの否定を行う論理ゲートで、すべての入力がローである場合にのみ出力がハイになります。それ以外の場合、出力はローとなります。
この特性を用いて、次のサンプルコードを作成しました。
このコードはVerilogのプリミティブを使ってNORゲートを表現しています。
assign Y = ~(A | B);
という行では、入力AとBの論理ORの結果を否定し、それを出力Yに割り当てています。
その結果、AとBが両方ともローの時にのみYがハイとなる、NORゲートの振る舞いを模倣しています。
実行後の結果を確認するために、次のテストベンチを使ってみましょう。
テストベンチでは、2つの入力AとBに対して可能なすべての組み合わせを試しています。
各組み合わせに対するNORゲートの出力Yが表示されます。
このテストベンチを使用して、実装したNORゲートが正しく機能することを確認できます。
なお、このテストベンチのコードは、Verilogのシミュレーションのための基本的な構造を示しています。
initial begin
ブロック内で、各テストケースを順に設定し、結果を$display
関数を用いて表示しています。
また、#10;
という表記は、10単位時間待機することを表しています。
○サンプルコード6:XORゲート
次に進む前に、XORゲートについて確認しましょう。
XORは排他的論理和を意味し、入力のどちらか一方だけが1であるときにのみ出力が1となるゲートです。
VerilogでのXORゲートのプリミティブは次のように表現されます。
このコードでは、Verilogのプリミティブとして用意されているXORを使用してXORゲートを作成しています。
この例では、二つの入力信号aとbに対してXOR演算を行い、その結果を出力信号yに出力しています。
XORゲートの特性から、aとbの値が異なるときだけyは1になります。
このゲートは、同等な論理式により構築することも可能です。
たとえば、次のようなコードもXORゲートと同じ動作をします:
このコードでは、ANDゲートとNOTゲートを組み合わせてXORゲートの論理式を構築しています。
具体的には、aとbのNOT、bとaのNOT、それぞれをANDした後にORしています。
これは、XORの動作を表現しています。
○サンプルコード7:XNORゲート
私たちが調査してきた最後の基本的な論理ゲートがXNORゲートです。
XORゲートの振る舞いを反転したものと考えると理解しやすいでしょう。
具体的には、すべての入力が同じである場合にのみ、出力が高(1)になります。
それ以外のすべての場合では、出力は低(0)です。
これはNOTゲートを用いてXORゲートを反転させることで実現できますが、Verilogでは一つのプリミティブとして提供されています。
下記のサンプルコードでは、2入力XNORゲートを作成し、その動作をテストします。
このコードでは、モジュールxnor_gateは2つの入力aとbを受け取り、それらのXNORを計算して出力yに割り当てます。
テストベンチでは、異なる入力値を使用してゲートの動作をテストします。入力値が同じ場合にのみ出力が1になることを確認できます。
このサンプルコードを実行すると、次のような結果が得られます。
これにより、XNORゲートが正常に動作していることが確認できます。
それぞれの入力の組み合わせに対して出力yが予期した値になっています。
○サンプルコード8:ビット幅を指定したゲート
ビット幅を指定することにより、一度に多数のデータを扱うことが可能になります。
これは、大規模なデジタル回路で特に有効で、Verilogではこのようなシチュエーションでもシンプルに記述することが可能です。
このケースでは、2ビットANDゲートを例にします。
ここでは、2つの2ビット入力 ‘a’ と ‘b’ と1つの2ビット出力 ‘y’ を定義しています。
そして ‘y’ の値は ‘a’ と ‘b’ のAND演算結果になります。
この例では、一度に2ビットずつのデータを扱っています。
このコードの実行結果は、入力が ‘a = 11’, ‘b = 10’ の場合、出力 ‘y’ は ’10’ となります。
これは、入力ビットごとにAND演算が行われ、’1 AND 1′ は ‘1’ に、’1 AND 0′ は ‘0’ になるためです。
次に、このコードの一部を変更して4ビットANDゲートを作成しましょう。
このコードは先ほどのコードと似ていますが、入力と出力のビット幅が4ビットになっています。
この例では、一度に4ビットずつのデータを扱っています。
このコードの実行結果は、入力が ‘a = 1101’, ‘b = 1010’ の場合、出力 ‘y’ は ‘1000’ となります。
これは、入力ビットごとにAND演算が行われ、’1 AND 1′ は ‘1’ に、’0 AND 1′ や ‘1 AND 0’ は ‘0’ になるためです。
このように、Verilogではビット幅を指定することで、複数のデータを一度に扱うことが可能になり、大規模なデジタル回路の設計やシミュレーションを簡単に行うことができます。
○サンプルコード9:管理ゲート
Verilogで信号の流れを制御する方法を学ぶため、サンプルコード9では管理ゲートを使ったプログラムを作成します。
管理ゲートは、1つの入力信号によって他の信号の流れを制御する特別な種類のゲートで、バッファとトライステートバッファの2種類があります。
この例ではトライステートバッファを使ってみましょう。
このコードでは、control
が真であればdata_in
がdata_out
に出力され、そうでなければdata_out
はハイ・インピーダンス状態(z)になります。
このハイ・インピーダンス状態は、その出力が他の信号源によって上書き可能な状態を紹介します。
つまり、control
によってdata_in
の信号がdata_out
に伝えられるかどうかが制御されます。
では、次にこのコードがどのように動作するか確認してみましょう。
テストベンチの結果は次の通りです。
この結果を見ると、control
が1の時にdata_in
の値がdata_out
に正確に出力されていることが分かります。
逆に、control
が0の時には、data_out
はハイ・インピーダンス状態(z)となり、信号は出力されていません。
こうした管理ゲートは、一つのハードウェア資源(たとえばデータバス)を複数のデバイスで共有したいときに有用です。
それぞれのデバイスは、自分がデータを送信するべき時だけデータバスに接続し、それ以外の時間はバスから切り離します。
しかし、このような構造は適切に制御しなければなりません。
もし複数のデバイスが同時にバスにデータを送信しようとすれば、データの衝突が起きて不具合が生じます。
そのような状況を避けるために、バスに接続するための制御ロジックが必要になります。
この制御ロジックは、たとえばトライステートバッファのcontrol
入力を制御することで実現できます。
○サンプルコード10:ユーザ定義プリミティブ(UDP)
Verilogでは、プリミティブの機能を自分で定義することも可能です。
これをユーザ定義プリミティブ(UDP)と言います。
ユーザ定義プリミティブを使用して3入力のXORゲートを定義したサンプルコードを紹介します。
このコードでは、primitive
キーワードで新しいプリミティブを定義し、その後に入力と出力を指定しています。
その次にあるtable
とendtable
の間には、全ての可能な入力とそれに対応する出力を記述しています。
この例では3つの入力a, b, cがあり、それぞれに0または1を取ることができるため、全8パターンの組み合わせが考えられます。
それぞれの組み合わせに対するXORゲートの出力を表に記述しています。
なお、ユーザ定義プリミティブは、基本的な論理ゲートだけでなく、より複雑な動作をするモジュールを定義することも可能です。
ただし、ユーザ定義プリミティブはシンプルな動作しか記述できないという制限があります。
したがって、より複雑な動作をするモジュールを作成する場合は、通常のモジュールを用いることが推奨されます。
なお、UDPのコードは基本的にシミュレーションのみで使用され、一部のFPGAやASICでは実装できない場合があります。
これはハードウェアのリソースや実装方法によりますので、具体的な製品のマニュアル等を参照してください。
●注意点と対策
Verilogでデジタル回路を設計する際には、次のような問題が発生する可能性があります。
これらの問題を理解し、適切な対策を講じることが重要です。
○タイミング問題
デジタル回路設計においては、回路の動作タイミングが重要な役割を果たします。
特に、クロック同期のロジック設計においては、各回路の遅延時間を正確に把握し、それに基づいてクロック周期を設定することが必要です。
例えば、下記のコードはクロックエッジに同期するフリップフロップを表しています。
このコードの動作は、クロックの立ち上がりエッジ(posedge clk
)でDの値がQに格納されるというものです。
ただし、DからQへの値の伝播には一定の遅延時間があり、この遅延時間がクロック周期よりも長いと、期待した動作をしない可能性があります。
したがって、適切なクロック周期を設定するためには、このような遅延時間を正確に把握することが必要です。
○シミュレーションと実装の違い
Verilogで書かれたコードは、基本的にシミュレーションを行うためのものです。
したがって、シミュレーション上では期待通りの動作をするコードでも、実際のハードウェア上で動作させたときには異なる動作をする可能性があります。
これは、シミュレーションでは考慮されないハードウェアの特性や、物理的な制約等によるものです。
したがって、実際のハードウェアに実装する前には、複数の角度からシミュレーションを行い、可能な限り多くの状況を検証することが必要です。
また、FPGAやASICへの実装を行う際には、そのハードウェアの特性や制約を理解し、適切な設計や最適化を行うことが重要です。
まとめ
この記事では、Verilogのプリミティブの基本的な使い方から、ユーザ定義プリミティブ(UDP)の作成方法、さらには実用的なコード例までを詳細に解説しました。
それぞれのコード例では、具体的なコードの書き方とその動作を理解することができるように、詳細な説明とサンプルコードを交えて解説しました。
また、Verilogでのデジタル回路設計における注意点と対策も見てきました。
具体的には、回路の動作タイミングを考慮した設計や、シミュレーションと実装の違いについて理解し、適切な設計や最適化を行うことが重要であることを学びました。
これらの知識をもとに、あなたもVerilogのプロとして、効率的で高品質なデジタル回路設計を行うことができるようになることを期待しています。