初心者も楽々!Verilogを使った疑似乱数生成の12ステップ

Verilogを使って疑似乱数を生成する方法を学ぶステップバイステップの図Verilog
この記事は約20分で読めます。

 

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

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

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

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

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

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

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

はじめに

初心者でも楽々!

Verilogを使った疑似乱数生成の12ステップについて解説していきます。

Verilogで疑似乱数を生成する方法を初心者目線でわかりやすく解説し、12のステップでVerilogの基本から応用までを身につけることが目標です。

●Verilogとは

Verilogは、ハードウェア記述言語(HDL)の一つであり、主にデジタル回路の設計に使用されます。

C言語に似た構文を持ち、学習曲線が緩やかなことから、初心者にとって取り組みやすい言語となっています。

●Verilogでの疑似乱数生成とは

疑似乱数とは、一見ランダムに見えるが、実際にはあるアルゴリズムに基づいて生成される数値のことを指します。

Verilogでは、組み込み関数の一つである $random 関数を使うことで疑似乱数を生成することが可能です。

●Verilogの基本的な構文

Verilogのプログラムはいくつかの基本的な構文から成り立っています。

○モジュール定義

Verilogプログラムの中心的な部分はモジュールです。

これはデジタル回路の一部をモデル化したもので、入力、出力、内部動作を定義します。

モジュール定義の基本的な形を紹介します。

module ModuleName(input a, output b);
  // 内部動作の定義
endmodule

このコードでは、ModuleNameという名前のモジュールを定義しています。

入力aと出力bが定義されており、モジュール内部の動作はコメントで示されている部分に記述します。

○変数とデータ型

Verilogには、信号や状態を表すために使用されるいくつかの基本的なデータ型が存在します。

これらには、ビットベクトルを表す reg や wire、整数を表す integer などがあります。

reg [7:0] data;  // 8ビットのレジスタ
wire clk;        // ワイヤ(通常、外部からの信号を表す)
integer count;   // 整数

このコードでは、8ビットのレジスタdata、ワイヤ型のclk、整数型のcountを定義しています。

○演算子と制御文

Verilogでは、算術演算子、論理演算子、ビット演算子など、C言語と非常に似た多くの演算子を使用できます。

また、if文やfor文などの制御文も使用することが可能です。

reg a, b, c;
a = b & c;  // ビット単位のAND演算
if (a == b) begin
  // aとbが等しいときの処理
end

このコードでは、レジスタa, b, cのビット単位のAND演算を行い、aとbが等しいかどうかをチェックする制御文を使用しています。

●Verilogにおける疑似乱数生成の方法

Verilogで疑似乱数を生成するには、$random関数を使用します。

この関数は、-2147483648から2147483647までの整数をランダムに生成します。

○サンプルコード1:基本的な乱数生成

module rand_gen;
  reg [31:0] rand_num;  // 32ビットのレジスタ

  initial begin
    rand_num = $random;  // ランダムな値を生成
    $display("Random number: %d", rand_num);
  end
endmodule

このコードでは、rand_genという名前のモジュールを作成しています。

rand_numという名前の32ビットレジスタを定義し、$random関数を使ってランダムな数を生成し、その値を表示しています。

このコードを実行すると、「Random number:」の後にランダムに生成された数値が表示されます。

○サンプルコード2:範囲指定による乱数生成

module rand_range;
  reg [31:0] rand_num;  // 32ビットのレジスタ
  integer min, max;

  initial begin
    min = 0; max = 100;  // 範囲の指定
    rand_num = $random % (max - min + 1) + min;  // 指定範囲内のランダムな値を生成
    $display("Random number in range %d to %d: %d", min, max, rand_num);
  end
endmodule

このコードでは、minからmaxまでの範囲でランダムな数を生成しています。

範囲内のランダムな値は、$random関数で生成した値に対してmod演算子(%)を使い、範囲のサイズで割り、その余りに最小値を加えることで計算します。

このコードを実行すると、「Random number in range 0 to 100:」の後に0から100の範囲内でランダムに生成された数値が表示されます。

●疑似乱数の応用例

疑似乱数は多くの応用例があります。

○サンプルコード3:乱数を用いた信号生成

module rand_signal;
  reg [31:0] rand_num;  // 32ビットのレジスタ
  reg [7:0] signal;     // 8ビットの信号

  initial begin
    rand_num = $random;  // ランダムな値を生成
    signal = rand_num[7:0];  // ランダムな8ビット信号を生成
    $display("Random signal: %b", signal);
  end
endmodule

このコードでは、32ビットのレジスタrand_numからランダムな8ビット信号を生成しています。

ランダムな信号は、rand_numの下位8ビットを取り出すことで生成します。

このコードを実行すると、「Random signal:」の後にランダムに生成された8ビットの信号が表示されます。

○サンプルコード4:乱数を用いたモンテカルロ法

module montecarlo;
  reg [31:0] rand_x, rand_y;  // 32ビットのレジスタ
  integer count, hit, i;
  real pi_estimate;

  initial begin
    count = 10000; hit = 0;  // 初期化
    for (i = 0; i < count; i = i + 1) begin
      rand_x = $random; rand_y = $random;  // ランダムな値を生成
      if (rand_x*rand_x + rand_y*rand_y <= 2147483647*2147483647) hit = hit + 1;  // 円内にあるか判定
    end
    pi_estimate = 4.0 * hit / count;  // πの近似値を計算
    $display("Estimation of pi: %f", pi_estimate);
  end
endmodule

このコードは、モンテカルロ法を用いてπの近似値を計算しています。

ランダムに生成した2つの数(rand_xとrand_y)が円内にあるかどうかを判定し、円内にあった回数からπの近似値を計算しています。

このコードを実行すると、「Estimation of pi:」の後にπの近似値が表示されます。

●疑似乱数生成の注意点と対策

疑似乱数生成器はその名の通り、生成する数値は真のランダム性を持たないということを理解しておく必要があります。

疑似乱数生成器は決定的なアルゴリズムに基づいて動作するため、同じシード値を与えれば同じ数列を生成します。

また、乱数生成器の品質は重要であり、すべての乱数生成器が同じ品質の乱数を生成するわけではありません。

乱数の品質はその分布、周期、相関性などによって評価されます。

●Verilogにおけるカスタマイズ方法

Verilogの疑似乱数生成器はカスタマイズすることが可能です。

具体的には、乱数のシードを設定することで、生成される乱数列をコントロールすることができます。

○サンプルコード5:独自の乱数生成器

module custom_rand_gen;
  reg [31:0] state;  // 32ビットの状態

  initial begin
    state = 123456789;  // 初期状態(シード)
  end

  always @(posedge clk) begin
    state = state ^ (state << 13); 
    state = state ^ (state >> 17);
    state = state ^ (state << 5);  // 独自の乱数生成アルゴリズム
  end
endmodule

このコードは、独自の疑似乱数生成器を実装しています。

XORシフトと呼ばれるアルゴリズムを使用しており、シフトと排他的論理和を使って乱数を生成します。

○サンプルコード6:乱数シードのカスタマイズ

module custom_seed;
  reg [31:0] rand_num;  // 32ビットのレジスタ

  initial begin
    $random.seed(123456789);

  // シードの設定
    rand_num = $random;  // ランダムな値を生成
    $display("Random number: %d", rand_num);
  end
endmodule

このコードでは、乱数生成器のシードを設定して、それに基づいて乱数を生成しています。

シードを設定することで、同じシード値から始めれば常に同じ乱数列が得られます。

このコードを実行すると、「Random number:」の後に生成された乱数が表示されます。

●実際に乱数を使ったプロジェクト

ここでは、Verilogで乱数を活用したプロジェクトの例を紹介します。

具体的には、ソートアルゴリズムのパフォーマンステスト、テストベンチ作成、信号処理のシミュレーション、データエンコーディングのシミュレーション、暗号アルゴリズムのシミュレーション、そしてハードウェアトロイの検出の6つの例を取り上げます。

○サンプルコード7:ソートアルゴリズムのパフォーマンステスト

下記のコードは、ソートアルゴリズムのパフォーマンスをテストするための乱数生成の例です。

ランダムな配列を生成し、それをソートします。

この例では、バブルソートを使用しています。

module sort_test;
  reg [7:0] array [10:0];  // 8ビット値の配列
  integer i, j;

  initial begin
    // ランダムな配列を生成
    for (i = 0; i < 10; i = i + 1) array[i] = $random;

    // バブルソート
    for (i = 0; i < 10; i = i + 1)
      for (j = 0; j < 10 - i; j = j + 1)
        if (array[j] > array[j + 1]) begin
          reg [7:0] temp = array[j];
          array[j] = array[j + 1];
          array[j + 1] = temp;
        end

    // 結果の表示
    for (i = 0; i < 10; i = i + 1)
      $display("array[%d]: %d", i, array[i]);
  end
endmodule

このコードでは、初めにランダムな8ビット値を含む配列を生成しています。

次に、バブルソートアルゴリズムを使用して配列を昇順にソートします。

最後に、ソート後の配列の要素を表示します。

このコードを実行すると、「array[i]:」の後にソート後の配列の各要素が表示されます。

これにより、ソートアルゴリズムが正しく動作していることが確認できます。

○サンプルコード8:乱数を用いたテストベンチ作成

下記のコードは、デジタル回路のテストベンチを作成する際に乱数を用いた例です。

乱数を用いてランダムな信号パターンを生成し、それを回路に入力します。

module testbench;
  reg [7:0] rand_input;  // ランダムな入力信号
  wire [7:0] output;  // 出力信号

  // テスト対象の回路
  circuit U1(.input(rand_input), .output(output));

  initial begin
    // ランダムな入力信号を生成
    rand_input = $random;

    // 出力信号の表示
    $monitor("input: %h, output: %h", rand_input, output);
  end
endmodule

このコードでは、回路の入力信号としてランダムな8ビット値を生成しています。

そして、この入力信号に基づいて回路が出力する信号を表示します。

このコードを実行すると、「input:」と「output:」の後にそれぞれ入力信号と出力信号が表示されます。

これにより、異なる入力信号に対する回路の挙動を確認することができます。

○サンプルコード9:信号処理のシミュレーション

下記のコードは、信号処理のシミュレーションに乱数を用いた例です。

ランダムな信号を生成し、それに対して信号処理を行います。

この例では、移動平均フィルタを使用しています。

module signal_processing;
  reg [7:0] rand_signal;  // ランダムな信号
  reg [7:0] buffer [3:0];  // バッファ
  integer i;
  wire [7:0] output;  // 出力信号

  // 移動平均フィルタ
  assign output = (buffer[0] + buffer[1] + buffer[2] + buffer[3]) / 4;

  initial begin
    // ランダムな信号を生成し、フィルタを適用
    for (i = 0; i < 100; i = i + 1) begin
      rand_signal = $random;
      buffer[i % 4] = rand_signal;
      $display("input: %h, output: %h", rand_signal, output);
    end
  end
endmodule

このコードでは、ランダムな8ビット信号を生成し、それをバッファに格納しています。

そして、バッ

ファ内の信号に対して移動平均フィルタを適用し、出力信号を生成します。

このコードを実行すると、「input:」と「output:」の後にそれぞれ入力信号とフィルタ適用後の出力信号が表示されます。

これにより、信号処理が正しく動作していることが確認できます。

○サンプルコード10:データエンコーディングのシミュレーション

今回のコードでは、データエンコーディングのシミュレーションに乱数を用いる具体的な例を紹介します。

この例では、パリティチェックを使ってデータエンコーディングとデコーディングを行っています。

エンコーダは、データとパリティビットを合わせて送信します。

デコーダは、受信したデータからパリティビットを確認し、エラーの有無をチェックします。

module data_encoding;
  reg [7:0] data;  // データ
  reg parity_bit;  // パリティビット
  wire [8:0] encoded_data;  // エンコードされたデータ
  wire [7:0] decoded_data;  // デコードされたデータ
  wire decode_error;  // デコードエラー

  // エンコーダ
  assign encoded_data = {data, parity_bit};

  // パリティビットの計算
  always @(data) begin
    parity_bit = ^data;
  end

  // デコーダ
  always @(encoded_data) begin
    decoded_data = encoded_data[8:1];
    decode_error = encoded_data[0] ^ ^decoded_data;
  end

  initial begin
    // ランダムなデータを生成
    data = $random;
    $display("original data: %h, encoded data: %h, decoded data: %h, decode error: %b", data, encoded_data, decoded_data, decode_error);
  end
endmodule

このコードでは、ランダムな8ビットデータを生成し、パリティビットを計算してそれをデータと一緒に送信します。

そして、受信したデータをデコードし、パリティチェックを行います。

このコードを実行すると、「original data:」、「encoded data:」、「decoded data:」、「decode error:」の後にそれぞれオリジナルのデータ、エンコードされたデータ、デコードされたデータ、デコードエラーの有無が表示されます。

この結果を確認することで、エンコーディングとデコーディングが正しく行われているか、またパリティチェックによるエラー検出が機能しているかを確認することができます。

このコードを応用することで、様々なエンコーディング手法のシミュレーションや、エラー検出・訂正手法のシミュレーションを行うことも可能です。

例えば、パリティビットの代わりにハミングコードを用いたエラー訂正コードのシミュレーションなどが考えられます。

その際には、エンコーダとデコーダの部分を適切に書き換えることにより、新たなシミュレーションを作成することができます。

○サンプルコード11:暗号アルゴリズムのシミュレーション

それでは、次に進んでVerilogでの疑似乱数生成を用いた暗号アルゴリズムのシミュレーションについて見てみましょう。

暗号アルゴリズムにおいては乱数が重要な役割を果たします。

そのため、Verilogで乱数を用いることで、暗号アルゴリズムのシミュレーションを実現できます。

下記のサンプルコードは、VerilogでXORシフト法を用いた疑似乱数生成器を暗号アルゴリズムの一部として使ったシミュレーションの例です。

module xor_shift(
    input clk,
    output reg [31:0] random
);
  reg [31:0] x = 123456789;
  reg [31:0] y = 362436069;
  reg [31:0] z = 521288629;
  reg [31:0] w = 88675123;

  always @(posedge clk) begin
    reg [31:0] t;
    t = x ^ (x << 11); 
    x = y; y = z; z = w;
    w = w ^ (w >> 19) ^ (t ^ (t >> 8)); 
    random = w;
  end
endmodule

このコードでは、XORシフト法という乱数生成法を用いています。

XORシフト法は、その名の通りXOR演算とビットシフトを用いて乱数を生成します。

非常にシンプルながらも効率的な乱数生成法で、ハードウェアでの実装が容易です。

最初に定義した4つの変数xyzwは、乱数生成の元となるシード値です。

これらのシード値は任意に設定可能で、これによって生成される乱数列が決定されます。

always @(posedge clk) begin ... endという構文は、clkというクロック信号が立ち上がった時(正エッジ)に、その中のコードを実行するという意味です。

これにより、クロック周期ごとに乱数が生成されます。

XORシフト法のアルゴリズムは、次のように実装されています。

tは一時的な変数で、xの値を11ビット左シフトしたものとx自体とのXORを取ります。

次に、xyの値を、yzの値を、zwの値を代入します。

最後に、wに対して、wを19ビット右シフトしたものと、ttを8ビット右シフトしたものとのXORを取ったものと、w自体とのXORを取り、それを新たなwの値とします。

そして、そのwの値が新たな乱数となります。

このコードを実行すると、クロック周期ごとに新たな32ビットの乱数がrandomという出力に生成されます。

これを暗号アルゴリズムの一部として用いることで、様々な暗号シミュレーションが可能となります。

なお、このような暗号シミュレーションにおいては、セキュリティ上の観点から、生成される乱数列が予測不可能であること、すなわち乱数生成器の「品質」が重要となります。

そのため、暗号シミュレーションを行う際には、乱数生成器の選択やその品質についても注意深く検討する必要があります。

○サンプルコード12:ハードウェアトロイの検出

ここでは、ハードウェアトロイ(不正な変更や付加)を検出するシミュレーションの例を見ていきましょう。

Verilogで作成した疑似乱数を利用して、ICチップ内部で不正な動作を行う可能性のあるトロイの存在を探るのです。

これには、一貫性のあるテストパターンを生成することで、通常は観測できない内部状態を引き出すことが求められます。

module Trojan_Detection(input wire clk, input wire rst, output reg [31:0] out);
  reg [31:0] LFSR;
  always @(posedge clk or posedge rst) begin
    if (rst) begin
      LFSR <= 32'b00000000000000000000000000000001;  // 初期状態の定義
    end else begin
      LFSR <= {LFSR[30:0], LFSR[0] ^ LFSR[2] ^ LFSR[3] ^ LFSR[31]};  // 乱数生成
    end
    out <= LFSR;
  end
endmodule

このコードでは、乱数ジェネレータとして既にご紹介した線形フィードバックシフトレジスタ(LFSR)を使用しています。

LFSRは、初期状態によって生成されるパターンが異なるため、これをうまく利用することで、ICの内部状態を観察することができます。

次に、この乱数ジェネレータを利用してハードウェアトロイを検出するテストベンチを見てみましょう。

module Trojan_Detection_TB;
  reg clk;
  reg rst;
  wire [31:0] out;
  Trojan_Detection TD(.clk(clk), .rst(rst), .out(out));
  initial begin
    clk = 1'b0;
    rst = 1'b1;
    #5 rst = 1'b0;
    #10000 $finish;
  end
  always #1 clk = ~clk;
  initial begin
    $monitor("Time=%0dns, out=%b", $time, out);
  end
endmodule

こちらのテストベンチでは、まず初期化として、リセット信号(rst)を高に設定します。

そして、一定時間後にリセット信号を低にして乱数生成を開始します。

また、生成される乱数はモニターに出力されます。

これにより、特定のテストパターンがIC内部で異常な動作(例えば、予期せぬ出力やタイミングのずれ)を引き起こすかどうかを観察することができます。

異常な動作が観測された場合、それはハードウェアトロイの存在を示す可能性があります。

まとめ

以上、Verilogを用いた疑似乱数生成の12ステップについて詳しく見てきました。

これらのステップを一つ一つ踏むことで、Verilogの基本から応用まで、疑似乱数生成の手法とその応用が理解できたのではないでしょうか。

また、疑似乱数生成の実際の利用例として、ハードウェアトロイの検出などの具体的なプロジェクトについても触れました。

これらのプロジェクトは、実際のエンジニアリングの現場で直面する問題を解決するための一例です。

さらに深く学び、自分自身のプロジェクトで役立てることをぜひお勧めします。

これで、初心者も楽々!Verilogを使った疑似乱数生成の12ステップが終わります。

この情報があなたの学習に役立つことを願っています。