Verilog

概要

Verilogのようなハードウェア記述言語は、ソフトウェアプログラミング言語に似ている。これは、信号の伝達時間や信号強度を記述するコードから作られているからである。Verilogには、ブロッキング代入とノン・ブロッキング代入という2種類の代入演算子代入演算子がある。ノン・ブロッキング代入文のおかげで、設計者はステートマシンの更新を一時変数を宣言せずに書くことができるようになった。これらの概念はVerilog言語のセマンティックの一部となっているため、設計者は大規模な回路の記述を比較的にコンパクトで簡潔な形式ですばやく書くことができる。Verilogが開発された当初（1984年）、回路設計者はグラフィカルな回路図入力ソフトウェアや、ドキュメントと電子回路シミュレータのためだけに書かれた特殊なソフトウェアを使っていたため、複雑な回路を簡潔に記述できるVerilogは、極めて大きな生産性の向上をもたらした。
Verilogの設計者は、言語の構文を、工学分野のソフトウェア開発で当時すでに広く普及していたC言語と似たものにしたいと考えた。そのため、C言語のように、Verilogの識別子は ケース・センシティブであり、基本的なプリプロセッサーを持っている（ただし、ANSI C/C++のものほどは洗練されていない）。制御フローのキーワード（if/else、for、while、caseなど）はC言語と同一であり、演算子の優先順位もC言語と互換性がある。構文の違いとしては、変数宣言にビット幅が必要であることや、手続きブロックの宣言の相違（カーリー・ブレイス{}の代わりにbegin/endを使用する）があり、その他多数の細かな違いがある。Verilogは変数に固定サイズを与えなければならないが、C言語の場合はサイズは変数の「型」から推定される（たとえば、integer型は8ビットであると）。
Verilogのコードはモジュールの階層から構成する。モジュールは設計の階層をカプセル化し、他のモジュールとは、宣言された入力、出力、必要に応じて双方向ポートを使用して通信する。モジュールの内部では、net/変数の宣言（wire、reg、integerなど）、並行または逐次のステートメントブロック、他のモジュールのインスタンス（サブ階層）などの、あらゆる組み合わせが記述できる。逐次的な文は、begin/endブロック内に配置し、ブロック内では逐次的に実行される。しかし、ブロック自体は並列に実行されるため、この特徴によりVerilogはデータフロー言語となっている。
次に、配線を示すwire、記憶素子を示すregとサブモジュールのリストなどを定義する。さらに、続いてその動作を規定するステートメントやステートメントをグループにしたブロック群を定義する。ブロックはbeginキーワードで始まり、endキーワードで終わる範囲で定義し、ブロック内はステートメントが並列に実行される。逐次実行したい場合は、ブロッキング代入を使うか、クロックのタイミングを待つ書き方をする。各ブロックは並列に実行される。Verilogの'wire'の概念は信号の値 (4状態："1、0、floating、undefined") と信号の強度（強い、弱いなど）からなる。このシステムのおかげで、共有信号線の抽象的なモデリングが可能になり、複数のソースで共通の回路網を駆動することが可能になった。wireが複数のドライバーを持つ時、wireの（読み取り可能な）値はソースドライバーの関数と強度で解決される。
Verilog言語の文のサブセットは、合成可能である。RTL（register-transfer level; レジスタ転送レベル）として知られる合成可能なコーディングスタイルを使用して書かれたVerilogのモジュールは、合成ソフトウェアを使用することで物理的に実装することができる。合成ソフトウェアは、アルゴリズムを用いて、（抽象的な）Verilogのソースをネットリストに変換する。ネットリストとは、特定のFPGAやVLSI技術で利用可能な基本ゲート（AND、OR、NOT、フリップフロップなど）のみからなるソースコードと論理的に等価な記述である。さらにネットリストに修正を加えることで、究極的には（ASICのフォトマスクのセットやFPGAのビットストリームなどの）電子回路製造の設計図を生成することが可能である。
「Verilog-HDL」という表記が用いることがあるが、IEEEなどの文書では「Verilog」と「HDL」との間にハイフンが入らない「Verilog HDL」のように表記されている。

始まり

Verilogは、ゲートウェイ・デザイン・オートメーション社が、ハードウェア・モデリング言語とそのためのシミュレータとして1984年頃開発した。その後、同社は1990年にケイデンス・デザイン・システムズが買収した。ケイデンスは、現在も大元のゲートウェイVerilogおよびVerilog-XL論理シミュレータの版権を持っている。

標準化

同種のハードウェア記述言語であるVHDLの台頭に対し、ケイデンスはVerilogの規格を公開し標準化する道をとり、OVI（Open Verilog International）と呼ばれる組織へ版権の一部を移譲した。その後、Verilogの規格がIEEEに提出され、IEEE 1364-1995として標準化された。この標準は、Verilog-1995と呼ばれる。なお、OVIはその後Accelleraという組織になっている。
標準化にともない、ケイデンス社以外の会社やフリーソフトのVerilogシミュレータが登場するようになった。

Verilog 2001

オリジナルのVerilog標準に対する不満を解消するために、Verilog-95に対する拡張がなされた。この拡張はVerilog 2001といいIEEE 1364-2001になった。VHDLにあったgenerate文に対応し、大規模設計が容易になった。

Superlog/System Verilog

やがてOpenVeraやVerisityのE言語のようなハイ・レベルの検証言語が登場する。このことはその種の機能を盛り込んだVerilog、すなわちコデザイン・オートメーション社によるSuperlogの開発を促すこととなった。コデザイン・オートメーション社は、シノプシスによってその後買収された。SuperlogとVeraの基本部分はAccelleraに寄贈され、次のIEEE標準としてSystemVerilogとVerilogとに分かれた。

Verilog-AMS

言語の最新のバージョンは、アナログ素子および回路の機能記述を行うための言語であるVerilog-Aを包含し、さらにアナログ／デジタルのミックス・シグナル・モデルへの対応もなされており、Verilog-AMSという。

Verilog 2005

IEEE 1364-2005 として規格化。その際、上位互換の SystemVerilog IEEE 1800-2005 も作った。

System Verilog 2011

IEEE/IEC 62530-2011 - SystemVerilog – Unified Hardware Design, Specification, and Verification Language
として、Verilog HDLとSystem Verilogの文書を一本化した。

例

簡単な2フリップフロップの例を以下に示す。
module toplevel(clock,reset);
  input clock;
  input reset;

  reg flop1;
  reg flop2;

  always @ (posedge reset or posedge clock)
    if (reset)
      begin
        flop1 <= 0;
        flop2 <= 1;
      end
    else
      begin
        flop1 <= flop2;
        flop2 <= flop1;
      endendmodule
Verilogにおける"<="演算子は、普通の手続き型言語とは異なる、ハードウェア記述言語としてのもう1つの側面である。この演算子は「ノンブロッキング」代入文として知られ、alwaysブロックが実行されるまでアクションは登録されない。つまり、代入文を書いた順序は無関係であり、必ず同じ結果が生じる。そのため、flop1とflop2はクロックごとに値がスワップされることになる。
もう1つの代入演算子である"="は、ブロッキング代入文と呼ばれる。"="による代入が行われると、代入先の値は即座に更新される。もし上の例で"<="の代わりにブロッキング演算子の"="を使用したとすると、flop1とflop2の値はスワップされず、手続き型プログラミング言語のように、コンパイラはflop1の値をflop2の値にセットするのだと解釈する（その結果、flop2とflop1の値にセットするという冗長な論理は無視されることになる）。
モジュールはキーワードmoduleで始まる。その後のDiv20xが名前を示し、続く括弧内は端子リストである。6個の端子があり、それぞれの端子の入出力方向はモジュール中のポート宣言（inputとoutput）で指定される。最後のendmoduleでモジュールの終了である。モジュール内には、パラメータ宣言、ポート宣言、レジスタ宣言、イベント宣言、ネット宣言、ステートメントなどを記述する。
// // 表題 イネーブル付20段カウンター// module Div20x (
    // ポート宣言（外部から本モジュールへの接続を定義する）
    input clock,  // クロック
    input reset,  // リセット（正論理, ハイアクティブ）
    input cet,    // カウンターとTC出力のイネーブル
    input cep,    // カウンターのみのイネーブル
    output reg [size - 1:0] count,   // 束線を示す。この場合5bit幅
                                     // alwaysまたはinitialブロックでドライブする信号は
                                     // reg型でなければならない
    output tc);                      // regと明記していない、他の信号はwire型。
                                     // ポート宣言したwire型信号のネット宣言は省略可能であり、省略することも多い。

    // パラメータ宣言
    parameter length = 20; // カウント段数
    parameter count_zero = 5'b00000; // 5bit幅,2進数の0を表す 
    parameter count_one  = 5'b00001; // 5bit幅,2進数の1を表す
    parameter size = 5;   // bit幅,基数の指定を省略すると32bit,10進数になる。

    // always文。resetやclock信号の変化に同期し並列に実行される
    always @ (posedge clock or posedge reset) begin                             
        if (reset) begin // 非同期リセット
            count <= count_zero;
        end else begin
            if (cet || cep) begin // イネーブル
                if (count == length - 1) begin
                    count <= count_zero;
                end else begin
                    count <= count + count_one;
                end
            end
        end
    end

    // assign文。tcの値は実行中、継続的に値が与る
    assign tc = (cet && (count == length - 1));endmodule
ポート宣言やレジスタ宣言での[a:b]のような形式は、束線（多くはバス）を示す。例えば[3:0]であれば配線が4本あることになる。always節は、括弧内の信号を指定した条件に変化があったときに実行される。posedgeは信号が0から1に変化した場合を指定する。orは括弧内の信号のどれか1つでも変化した場合を指定する。上記のように、resetに合わせてreg型を初期化するのが基本的なデザインパターンである。

ディレイの例は以下の通りである。
...reg a, b, c, d;wire e;...always @(b or e)
  begin
    a = b & e;
    b = a | b;
    #5 c = b;
    d = #6 c ^ e;
  end
上記のalways節は、もう1つの使い方を示している。たとえば、上の例では、リストに含まれる要素（bまたはe）が変化したタイミングでいつでもalways節が実行される。いずれかの変数が変化すると、即座に新しい値がaに代入される。ブロッキング代入文を使用しているため、bにはその代入後に（aの新しい値を用いて）新しい値が代入される。5単位時間のディレイの後、cにbの値が代入されると同時に、c ^ eの値が計算され、見えない保管場所に一時的に保存される。6単位時間後、保管されていた値がdに代入される。
プロセス内（initialまたはalwaysブロック内）で操作される信号は、reg型を持つ必要がある。また、プロセスの外で操作される信号はwire型を持たなければならない。この時使われるregという名前は、必ずしもハードウェアのレジスタを意味するわけではない。

回路として合成可能な構成

Verilogのいくつかの文は、たとえば$display文など、現実のハードウェアでは物理的に実装できないものもある。そのため、言語の多くの機能はハードウェアを記述するためには使用できない。以下に示す例では、直接物理的なゲートに対応する、合成可能な言語の典型的なサブセットを使用している。
// Mux の例 — 同じ動作をする例を3種類の方法で示す。// 1番目の例は連続束縛を使用している。wire out;assign out = sel ? a : b;// 2番目の例は同じことを行うために手続き的な表現を使用している。reg out;always @(a or b or sel)
  begin
    case(sel)
      1'b0: out = b;
      1'b1: out = a;
    endcase
  end// 3番目の例: 手続き構造の中で if/else 文を使用することもできる。reg out;always @(a or b or sel)
  if (sel)
    out = a;
  else
    out = b;
次の非常によく使われる例は、フリップフロップである。Verilogでは、D-フリップフロップが最も簡単に記述でき、以下のようにモデリングできる。
reg q;always @(posedge clk)
  q <= d;
例の中で注目すべき重要な点は、ノンブロッキング代入を使用していることである。基本的な経験則（rule of thumb）として、always節の中でposedgeまたはnegedge文を使用する場合には、<=を使用するとよい。
Dフリップフロップの変種として、非同期リセット機能を付加したものがある。慣習として、以下のようにリセット状態をチェックするコードを文内の最初のif節とすることが多い。
reg q;always @(posedge clk or posedge reset)
  if(reset)
    q <= 0;
  else
    q <= d;

initialとalwaysキーワード

Varilogの処理を宣言する方法には、alwaysキーワードとinitialキーワードという異なる2つの方法がある。alwaysキーワードは、自由に実行される処理を表す。initialキーワードは、ちょうど1回だけ実行される処理を表す。2つの処理はともに、シミュレーター時刻0に実行が開始され、ともにブロックの終わりまで実行される。alwaysブロックの場合、処理がブロックの最後に到達すると、実行が再度スケジュールされる。よくある間違いは、initialブロックは常にalwaysブロックの前に最初に実行されるというものである。実際には、initialブロックは、1回目の実行時に終了する特殊なalways-ブロックであると考えるとよい。
// 例:initial
  begin
    a = 1; // reg a の値を 0 に束縛する
    #1; // 1 単位時間待つ
    b = a; // reg a の値を reg b の値に束縛する
  endalways @(a or b) // a または b が *変更された* タイミングで処理が行われるbegin
  if (a)
    c = b;
  else
    d = ~b;end // このブロックが終了すると、先頭から処理が再開される (例: @ event-control)always @(posedge a) // reg a が low から high に変更されるたびに実行される
  a <= b;
上記2つは、これら2つのキーワードの典型的な使用例であるが、更に2つの特徴的な使用例がある。最もよく使うのが、信号強度を書いたリスト@(...)なしでalwaysキーワード使用する方法である。alwaysは以下のように使用することもできる。
always
 begin // always は時刻0に実行が始まり、*決して* 終了しない
   clk = 0; // clk を 0 にセットする
   #1; // 1 単位時間待つ
   clk = 1; // clk を 1 にセットする
   #1; // 1 単位時間待つ
 end // 実行を続ける — したがって、begin から実行が再開する
alwaysキーワードは、C言語のwhile(1) {..}構造と同じように無限ループとして振る舞う。
もう1つの特徴的な使用例は、initialキーワードにforeverキーワードを追加するというものである。

次の例は、上のalwaysの例と同等である。
initial forever // 時刻 0 で実行が開始され、begin/end の間の処理を永遠に (forever) 繰り替える
 begin
   clk = 0; // clk を 0 にセットする
   #1; // 1 単位時間待つ
   clk = 1; // clk を 1 にセットする
   #1; // 1 単位時間待つ
 end

Fork/join

fork/joinのペアは、Verilog内で並列処理を作るために使われる。fork/joinのペアの間にある文（またはブロック）は、実行フローでforkに到達したタイミングで同時に実行が開始され、forkとjoinの間で最も長い時間実行される文またはブロックの終了後、残りのコードの実行が再開する。
initial
 fork
   $write("A"); // 文字 A を出力する
   $write("B"); // 文字 B を出力する
   begin
     #1; // 1 単位時間待つ
     $write("C");// 文字 C を出力する
   end
 join
上のように書いた場合、出力は"ABC"または"BAC"のいずれにもなる可能性がある。最初の$writeと2番目の$writeのシミュレーションの実行順序は、シミュレータの実装に依存するためである。シミュレータによっては、意図的に順序をランダム化していることもある。そのため、偶然競合状態が起きたり、意図的な非決定的な振る舞いをする場合がある。
VHDLは、Verilogとは違い、複数のプロセスを動的に生成することはできない。

競合状態

Verilogでは、実行の順序は常に保証されるわけではない。これは、典型的な例で最もよく示すことができる。次のコードスニペットについて考えてみる。
initial
  a = 0;initial
  b = a;initial
  begin
    #1;
    $display("Value a=%d Value of b=%d",a,b);
  end
aおよびbの値として出力されるのはどんな値だろうか？initialブロックの実行順序によって、それぞれ0と0になる場合と、0と何らかの初期化されていない値となる場合の2パターンがありえる。ただし、$display文は#1ディレイを挟んでいるため、2つの代入ブロックより後に実行されることが常に保証されている。

4値理論

IEEE 1364標準では、0、1、Z（ハイ・インピーダンス）、X（論理値が未知）の4つの状態を使用する4値理論が定義されている。一方、Verilogの競合であるVHDLの標準IEEE 1164では、9つのレベルを持つ多値理論が定義されている。

プログラム言語インターフェイス

PLI（Program Language Interface）は、プログラマーに対して、VerilogからC言語で書かれたプログラムに制御を移すことを可能にするメカニズムを提供する。IEEE Std 1364-2005において、PLIは新しいVerilog Procedural Interfaceに完全に置き換えられて公式に非推奨となった。
PLI（現在はVPI）を利用することで、Verilogは、たとえば、テストハーネス、マイクロプロセッサの命令セットシミュレータ、デバッガなど、C言語で書かれた他のプログラムと連携することができる。たとえば、PLIはC言語の関数tf_putlongp()およびtf_getlongp()を提供しており、この関数を利用すると、現在のVerilogタスクや関数の引数の値を読み書きすることができる。