Verilogにおけるノンブロッキング代入の手法!初心者でも理解できる5つのステップ

ノンブロッキング代入の基本概念と使用例を説明する図 Verilog

 

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

このサービスはSSPによる協力の下、運営されています。

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

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

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

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

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

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

はじめに

ハードウェア記述言語の一つであるVerilogのノンブロッキング代入について、初心者の方でも理解できるように5つのステップでご紹介します。

本記事では、ノンブロッキング代入の基本概念から具体的な使用例、注意点、さらにはカスタマイズ方法までを詳しく解説します。

●Verilogとは

○Verilogの概要

Verilogは、デジタル回路の設計と検証を目的としたハードウェア記述言語です。

1980年代に登場し、その後のASICやFPGAの開発に広く利用されています。

VerilogはC言語に似た文法を持ち、イベント駆動型のシミュレーションを行うことが可能です。

○ノンブロッキング代入の基本理解

Verilogにおいて、ノンブロッキング代入とは、その名の通りブロックせずに代入を行う機能のことを指します。

これは、「=ではなく<=`を使用することで実現されます。詳細については、次のセクションで説明します。

●ノンブロッキング代入の理解

○ノンブロッキング代入とは

ノンブロッキング代入は、<=演算子を使用して表されます。

この代入方法は、すべてのノンブロッキング代入が同時に実行されることを保証します。

これにより、一度に多数の信号更新を行うことが可能となり、同時性を表現できます。

○ノンブロッキング代入の特徴

ノンブロッキング代入の主な特徴は、代入が全て同時に行われることと、代入の結果が次のタイミングで反映されることです。

これにより、代入の順序が結果に影響を及ぼさないため、複雑な信号更新を一度に行うことが可能です。

○ブロッキング代入との違い

ブロッキング代入(=)は、その行が実行されると、次の行へ進む前に代入が完了します。

一方、ノンブロッキング代入(<=)は、その行が実行された後、次の行へ進み、全ての代入が一度に行われます。

そのため、ブロッキング代入は順序依存性がありますが、ノンブロッキング代入は順序依存性がないという点が大きな違いです。

●ノンブロッキング代入の使い方

○基本的な使い方

ノンブロッキング代入は、主にシーケンシャルロジック(例えば、フリップフロップ)を記述する際に使用されます。

基本的な使い方を示すサンプルコードを記載します。

□サンプルコード1:基本的なノンブロッキング代入の使用例

module nonblocking_example(input wire clk, input wire d, output reg q);
    always @(posedge clk) begin
        q <= d;
    end
endmodule

このコードではクロックの立ち上がりエッジ(posedge clk)ごとに、入力dの値を出力qにノンブロッキングで代入しています。

この例ではクロックの立ち上がりエッジをトリガーとしてデータを更新しています。

クロックのエッジに合わせて信号を更新するため、フリップフロップの動作を模倣しています。

○応用的な使い方

次に進む前に、Verilogのノンブロッキング代入の更に応用的な使い方について理解を深めましょう。

基本的な使い方を押さえた後でなければ、これらの応用例を完全に理解することは難しいかもしれません。

しかし、基本を理解した上であれば、ここで紹介する使い方を学ぶことで、Verilogのノンブロッキング代入の真の力を引き出すことができるでしょう。

□サンプルコード2:応用的なノンブロッキング代入の使用例

ここでは、より複雑なハードウェアデザインの中でノンブロッキング代入がどのように使われるかを表す一例を見てみましょう。

module test;
    reg [3:0] a, b, c, d;

    always @(posedge clk) begin
        a <= b;
        b <= c;
        c <= d;
        d <= a;
    end
endmodule

このコードでは、4つのレジスタa, b, c, dが宣言されています。

そして、これらのレジスタの値はノンブロッキング代入を用いて順次更新されています。

つまり、クロックの立ち上がりエッジ(posedge)ごとに、aはbの値に、bはcの値に、cはdの値に、そしてdはaの値に更新されます。

この例では、4つのレジスタが循環的に連結されています。

したがって、クロックの一周期で各レジスタの値は一つ前のレジスタの値に更新され、結果としてa, b, c, dの値は一つずつずれていきます。

一見するとこのコードは複雑に見えますが、ノンブロッキング代入の特性を理解していれば、各クロックサイクルで何が起こるかを理解することができます。

そして、このような一見複雑なハードウェア動作もノンブロッキング代入を用いることで簡潔に記述することができるのです。

○特殊な使い方

ノンブロッキング代入はその特性上、一部特殊な使い方も可能です。

ここでは、その一つを紹介します。

具体的には、同じクロックサイクル中で複数のノンブロッキング代入が行われる場合の動作について説明します。

□サンプルコード3:特殊なノンブロッキング代入の使用例

module test;
    reg [3:0] a, b;

    always @(posedge clk) begin
        a <= b;
        a <= a + 1;
    end
endmodule

このコードでは、同一クロックサイクル中でaに対するノンブロッキング代入が2回行われています。

一つ目はa <= b;で、もう一つはa <= a + 1;です。同一クロックサイクル内で複数回のノンブロッキング代入が行われる場合、その順序はコードの記述順とは無関係になります。

代入はすべて同時に行われ、結果として最後に記述された代入文の結果がaに反映されます。

したがって、このコードではクロックの立ち上がりエッジ時にaa + 1に更新されます。

a <= b;は記述されていますが、これはa <= a + 1;により上書きされます。

これがノンブロッキング代入の一つの特殊な使い方で、これを理解していないと予期せぬバグを生む可能性があります。

そのため、複数のノンブロッキング代入を同一クロックサイクル内で使用する際には注意が必要です。

●ノンブロッキング代入の応用例

Verilogにおけるノンブロッキング代入は、信号処理やデータフローコントロールなど、幅広い応用が可能です。

ここでは、その一部を具体的なサンプルコードとともにご紹介します。

○実用的な応用例

まずは、基本的な信号処理としてのノンブロッキング代入の応用例を見ていきましょう。

具体的には、レジスタ間での値の転送を行う際のノンブロッキング代入の活用です。

□サンプルコード4:実用的なノンブロッキング代入の応用例

module reg_transfer(input clk, input [7:0] in_data, output reg [7:0] out_data);
  reg [7:0] buffer;

  always @(posedge clk) begin
    buffer <= in_data;  // コメント:入力データを一時的にbufferに保存
    out_data <= buffer;  // コメント:bufferの値を出力データに転送
  end
endmodule

このコードでは、ノンブロッキング代入を使ってin_dataからout_dataへの値の転送を行っています。

具体的には、in_dataの値を一時的にbufferに保存し、その後bufferの値をout_dataに転送しています。

bufferを介することで、同じクロックエッジでのin_dataからout_dataへの直接的な転送が可能となります。

コードを実行すると、in_dataの値がそのままout_dataに転送されるという結果が得られます。

ただし、その転送は次のクロックエッジまで遅延します。

○高度な応用例

次に、より高度なノンブロッキング代入の応用例を見ていきましょう。

具体的には、パイプライン処理の一部としてノンブロッキング代入を使用します。

□サンプルコード5:高度なノンブロッキング代入の応用例

module pipeline(input clk, input [7:0] in_data, output reg [7:0] out_data);
  reg [7:0] stage1, stage2;

  always @(posedge clk) begin
    stage1 <= in_data + 1;  // コメント:入力データに1を加えてstage1に保存
    stage2 <= stage1 + 1;  // コメント:stage1に1を加えてstage2に保存
    out_data <= stage2 + 1;  // コメント:stage2に1を加えて出力データに転送
  end
endmodule

このコードでは、ノンブロッキング代入を使ってパイプライン処理を実装しています。

具体的には、入力データに1を加える処理を3ステージで行っており、それぞれのステージでノンブロッキング代入を使って値を転送しています。

コードの実行結果としては、in_dataに3が加算された値がout_dataに出力されます。

ただし、その出力は3つのクロックエッジに分散され、各ステージの間にはクロックエッジが1つずつ存在します。

●注意点と対処法

それでは、Verilogにおけるノンブロッキング代入の注意点とそれに対する対処法を見ていきましょう。

○ノンブロッキング代入の注意点

ノンブロッキング代入には便利さがありますが、それと同時に注意しなければならない点も存在します。

特に、ノンブロッキング代入は、一連のステートメントの中で同時に評価され、すべての代入が終わった後に一度に更新されます。

しかし、これが意図しない挙動を引き起こすことがあります。

これは「競合状態」と呼ばれるもので、同じ信号に対して複数のノンブロッキング代入がある場合、その結果は不確定となります。

また、ノンブロッキング代入は基本的にはalwaysブロック内で使用されます。

しかし、alwaysブロック外で使用されると、意図しない結果を引き起こす可能性がありま

特に、ノンブロッキング代入がinitialブロック内で使用されると、初期化が予想外のタイミングで行われ、バグの原因となることがあります。

○対処法

以上のような問題を避けるためには、次のような対処法が有効です。

  1. 同じ信号に対するノンブロッキング代入は1つに限定する。
  2. ノンブロッキング代入は、alwaysブロック内で使用する。
  3. ノンブロッキング代入を使う際は、そのタイミングと順序を明確に理解する。

これらの対処法を用いることで、ノンブロッキング代入の利点を最大限に活用し、意図しないバグを防ぐことができます。

それでは、具体的なコードとその説明を見ていきましょう。

□サンプルコード6:注意点と対処法を示す使用例

module test;
  reg [7:0] a;
  reg [7:0] b;

  initial begin
    a <= 0;  // 初期化をノンブロッキング代入で行うのは良くない
    b = 0;   // 正しい初期化の方法はブロッキング代入を使用すること
  end

  always @(posedge clk) begin
    a <= a + 1;  // ノンブロッキング代入はalwaysブロック内で使用
    b = b + 1;   // alwaysブロック内でのブロッキング代入は推奨されない
  end
endmodule

このコードではノンブロッキング代入とブロッキング代入を比較しながら、その注意点と対処法を説明しています。

まず、初期化の際にノンブロッキング代入を使用すると、予期せぬタイミングで初期化が行われる可能性があるため、避けるべきです。

その代わりに、ブロッキング代入を使用して正確な初期化を行います。

また、alwaysブロック内ではノンブロッキング代入を使用します。

しかし、この例では意図的にブロッキング代入も同時に使用しています。

alwaysブロック内でブロッキング代入を行うと、その時点で即座に代入が行われ、以降のステートメントの挙動が変わる可能性があるため、推奨されません。

●カスタマイズ方法

Verilogのノンブロッキング代入は、その特性を理解し使いこなすことで、さまざまなカスタマイズが可能です。

ここでは、基本的なカスタマイズから高度なカスタマイズまで、2つの段階で説明します。

○ノンブロッキング代入のカスタマイズの基本

ノンブロッキング代入のカスタマイズは、複数のレジスタに対して同時に非同期に代入することから始めます。

これはノンブロッキング代入が「同時」に処理を行う特性を利用したものです。

例えば、次のサンプルコード7では、2つのレジスタaとbに対してノンブロッキング代入を行います。

ここでは、aとbの値を交換しています。

□サンプルコード7:基本的なカスタマイズ例

module top;
  reg [3:0] a = 4'b0001;
  reg [3:0] b = 4'b0010;

  initial begin
    a <= b;
    b <= a;
    #1;
    $display("a: %b, b: %b", a, b);
  end
endmodule

このコードでは、aとbの値をノンブロッキング代入を使用して交換しています。

この例では、最初の時刻でaとbの値が交換され、その結果が1タイムステップ後に表示されます。

このコードを実行すると次のようになります。

a: 0010, b: 0001

この結果から、aとbの値が正しく交換されていることがわかります。

○高度なカスタマイズ方法

基本的なカスタマイズから一歩進んだ、より高度なカスタマイズ方法を次に紹介します。

ここでは、複数のレジスタに対して異なるタイミングで値を更新する方法を見ていきます。

□サンプルコード8:高度なカスタマイズ例

module top;
  reg [3:0] a = 4'b0001;
  reg [3:0] b = 4'b0010;
  reg [3:0] c = 4'b0011;

  initial begin
    a <= b;
    #5 b <= c;
    #10 c <= a;
    #15;
    $display("a: %b, b: %b, c: %b", a, b, c);
  end
endmodule

このコードでは、まずaにbの値をノンブロッキング代入します。

次に、5タイムステップ後にbにcの値をノンブロッキング代入し、さらに5タイムステップ後にcにaの値をノンブロッキング代入します。

このコードを実行すると次のようになります。

a: 0010, b: 0011, c: 0010

これにより、異なるタイミングで複数のレジスタの値を更新することが可能であることがわかります。

まとめ

この記事では、Verilogにおけるノンブロッキング代入の基本的な理解から、その応用例、注意点と対処法、カスタマイズ方法までを詳しく解説しました。

初心者でも理解できるように、具体的なサンプルコードを交えて説明しました。

これを機に、Verilogのノンブロッキング代入を理解し、実践に活かしていただければと思います。