初心者でも理解できる!Verilogと高位合成の手引き12ステップ

プログラミング初心者がVerilogと高位合成を学ぶガイドブックVerilog
この記事は約24分で読めます。

 

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

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

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

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

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

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

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

はじめに

あなたがこれからプログラミングの世界に足を踏み入れるなら、Verilogと高位合成の知識があると非常に便利です。

この記事では、これらの主題について初心者向けに詳細に解説します。

12のステップを通じて、基礎から応用まで学びましょう。

●Verilogとは

Verilogはハードウェア記述言語(HDL)の一つで、デジタル回路の設計やシミュレーションに広く使用されています。

高速で複雑なデジタルシステムの設計が可能で、それにより初心者でも短期間でデジタル回路設計を習得することができます。

○基本的な概念

Verilogには基本的に「モジュール」と「ワイヤ」の2つの主要な要素があります。

モジュールは回路の一部を表現し、ワイヤはこれらのモジュールを接続します。

モジュール内では、論理演算子を使用してゲートレベルの設計が可能です。

□サンプルコード1:基本的なVerilogコード

このコードは、単純なANDゲートを作成する基本的なVerilogコードです。

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

この例では、moduleキーワードを使って新たなモジュールand_gateを定義しています。

そして、assign文を使って、出力yに対してabの論理ANDを割り当てています。

●高位合成とは

高位合成とは、ソフトウェアからハードウェアを生成するプロセスです。

C、C++、SystemCなどの高位言語で記述されたアルゴリズムをVerilogやVHDLのようなハードウェア記述言語(HDL)に変換することで、デザインの生産性と再利用性を大幅に向上させます。

○高位合成の基本

高位合成は、手動での低水準設計からの脱却を可能にします。

その結果、設計者はアルゴリズムの開発により時間を費やすことができ、ハードウェア設計の複雑性を大幅に軽減します。

□サンプルコード2:高位合成を用いたVerilogコード

このコードは、高位合成を用いて畳み込み演算を行うVerilogコードです。

module convolution (input wire [7:0] x, h, output wire [15:0] y);
  integer i;
  assign y = 0;
  for (i=0; i<8; i=i+1) begin
    assign y = y + x[i]*h[i];
  end
endmodule

この例では、8ビットの入力xhを使用して、畳み込み演算を行っています。

この計算結果は16ビットの出力yに格納されます。このように、高位合成を使用すると、より複雑な計算も簡単に記述できます。

●Verilogでの高位合成の使い方

Verilogで高位合成を行うための手法を紹介します。

初心者の方でも安心して取り組めるように、基本的な手順から詳細な使い方までを順に説明します。

○基本的な手順

Verilogで高位合成を行うには、次の手順が一般的です。

  1. Verilogコードを書く
  2. 高位合成ツールを用いて、コードを合成する
  3. FPGAボードにダウンロードして実行する

□サンプルコード3:高位合成の基本的な使い方

ここでは簡単な加算器を作るVerilogコードを紹介します。

このコードは2つの4ビットの入力信号を加算し、結果を4ビットの出力信号として出力する機能を持ちます。

//加算器モジュールの定義
module adder (
  input [3:0] a, //4ビット入力a
  input [3:0] b, //4ビット入力b
  output [3:0] sum //4ビットの出力sum
);
  assign sum = a + b; //aとbの和を計算
endmodule

このコードは、「adder」というモジュールを定義しています。

モジュールとはVerilogでの基本的な構成要素で、特定の機能を持つ回路ブロックを表現します。

この「adder」モジュールは、二つの4ビットの入力信号「a」と「b」を加算し、その結果を4ビットの出力信号「sum」に割り当てることで、加算器の機能を実現します。

□サンプルコード4:高位合成の詳細な使い方

次に、より高度な機能を持つVerilogコードを紹介します。

このコードは、4ビットのカウンタを実装しています。

//カウンタモジュールの定義
module counter (
  input clk, //クロック信号
  output [3:0] out //4ビットの出力信号
);
  reg [3:0] count = 0; //レジスタcountの初期化

  //クロックの立ち上がりエッジでカウンタをインクリメント
  always @(posedge clk) begin
    count = count + 1;
  end

  assign out = count; //出力信号にカウンタの値を割り当て
endmodule

このコードでは、「counter」という名前のモジュールを定義し、クロック信号の立ち上がりエッジでカウンタの値をインクリメントするようになっています。

また、4ビットの出力信号「out」には、レジスタ「count」の値が常に割り当てられています。

これにより、クロック信号に同期してカウントアップするカウンタを実装できます。

●Verilogの高位合成の応用例

Verilogと高位合成を駆使すれば、具体的なデジタルシステムの設計を効率的に行うことが可能になります。

このセクションでは、フィルタ設計、FFT処理、画像処理といった一般的な応用例を取り上げ、それぞれについて具体的なサンプルコードを用いて解説します。

○応用例1:フィルタ設計

Verilogと高位合成は、デジタルフィルタの設計に非常に有用です。

デジタルフィルタは、デジタル信号処理の中心的な役割を果たし、ノイズの除去や信号の特定の周波数成分の抽出などに使われます。

□サンプルコード5:フィルタ設計のVerilogコード

このコードでは、単純な移動平均フィルタの設計を紹介します。

この例ではVerilogを使って3点の移動平均フィルタを設計し、その入力と出力を定義しています。

module MovingAverageFilter(
  input wire clk,
  input wire reset,
  input wire [15:0] data_in,
  output reg [15:0] data_out
);
  reg [15:0] buffer [2:0]; 

  always @(posedge clk or posedge reset) begin
    if (reset) begin
      buffer[0] <= 16'd0; 
      buffer[1] <= 16'd0;
      buffer[2] <= 16'd0;
    end else begin
      buffer[0] <= data_in; 
      buffer[1] <= buffer[0]; 
      buffer[2] <= buffer[1];
    end
  end

  always @(posedge clk) begin
    data_out <= (buffer[0] + buffer[1] + buffer[2]) / 3;
  end
endmodule

このコードでは、data_inがフィルタの入力、data_outがフィルタの出力です。

また、buffer[0]buffer[1]buffer[2]は3つの連続した入力値を保存するためのレジスタで、それぞれ新しい入力値、1つ前の入力値、2つ前の入力値を保持します。

always @(posedge clk or posedge reset)ブロック内では、クロックの立ち上がりエッジまたはリセット信号の立ち上がりエッジに対して、bufferレジスタの更新を行います。

リセット信号がアクティブの場合はbufferレジスタを0でクリアし、それ以外の場合は新しい入力値をbufferレジスタにシフトします。

always @(posedge clk)ブロックでは、クロックの立ち上がりエッジで出力値data_outを計算します。

これは、bufferレジスタに保存された3つの値の平均値となります。

このコードの実行結果は、入力信号の移動平均値を出力するフィルタとなります。

このフィルタはノイズ除去などのアプリケーションで使用できます。

○応用例2:FFT処理

デジタル信号処理における重要な計算の一つが、FFT(高速フーリエ変換)です。

FFTは信号の周波数特性を解析するために使われます。

Verilogと高位合成を用いることで、FPGA上で高速にFFTを実行することが可能となります。

□サンプルコード6:FFT処理のVerilogコード

// FFTを計算するためのモジュール定義
module fft(input [7:0] real, input [7:0] imag, output [15:0] magnitude);

  // データ型宣言
  reg [15:0] real_sq;
  reg [15:0] imag_sq;

  // real_sq = real*real;
  always @(real) begin
    real_sq = real*real;
  end

  // imag_sq = imag*imag;
  always @(imag) begin
    imag_sq = imag*imag;
  end

  // magnitude = sqrt(real_sq + imag_sq);
  always @(real_sq, imag_sq) begin
    magnitude = sqrt(real_sq + imag_sq);
  end
endmodule

このコードでは、FFTを用いて信号の大きさ(magnitude)を計算しています。

入力として実部(real)と虚部(imag)を受け取り、それぞれの二乗値を求めています。

その後、それらの和の平方根を計算して、結果の大きさを求めています。この例では、信号の大きさを求める簡易版のFFTを実装しています。

このコードを実行すると、入力となる実部と虚部から、信号の大きさを求めることができます。

結果は16ビットのデータとして出力されます。

次に、高位言語で記述されたFFTアルゴリズムをVerilogに変換するための手順について説明します。

  1. まず、高位言語(たとえばC++やPython)でFFTアルゴリズムを記述します。
    このアルゴリズムは通常、複素数の配列を入力として受け取り、その配列のFFTを計算します。
    この計算は、素数の入力データに対するバタフライ演算と呼ばれる計算を逐次適用するものです。
  2. 次に、この高位言語で記述されたFFTアルゴリズムをVerilogに変換します。
    これには、高位合成ツールが必要です。
    高位合成ツールは、高位言語で記述されたアルゴリズムをハードウェア記述言語(たとえばVerilog)に自動的に変換します。
  3. 生成されたVerilogコードを、FPGAなどのハードウェアにダウンロードして実行します。

このアプローチを使うと、FFTアルゴリズムのハードウェア実装を効率的に行うことができます。

また、同じアプローチは他の複雑なアルゴリズムのハードウェア実装にも適用可能です。

さらに発展させると、このFFTアルゴリズムは音声認識や画像処理などの領域で利用できます。

そのような場合、FFTの結果は、入力信号のスペクトル解析結果を示すデータとなります。

○応用例3:画像処理

Verilogと高位合成を使用して画像処理を行う事例を紹介します。

ここで言う画像処理とは、Verilogを使って画像のフィルタリング、エッジ検出など、画像に対する操作をプログラムで実現する技術のことを指します。

□サンプルコード7:画像処理のVerilogコード

画像処理の基本的なコードとして、2D畳み込みフィルタのサンプルコードを紹介します。

このコードでは、5×5のウィンドウ内での画像処理を行い、特定のパターンを検出します。

ウィンドウ内のピクセルに対して特定の重みをかけ、それを合計したものが出力となるのが一般的な2D畳み込みフィルタの動作です。

module Conv2D #(
    parameter WIDTH = 8,
    parameter HEIGHT = 8,
    parameter DEPTH = 8
) (
    input wire clk,
    input wire rst,
    input wire [WIDTH-1:0][HEIGHT-1:0][DEPTH-1:0] image,
    input wire [4:0][4:0] filter,
    output wire [WIDTH-1:0][HEIGHT-1:0][DEPTH-1:0] result
);

reg [DEPTH-1:0] line_buffer [0:WIDTH-1][0:4]; // 5 line buffer
integer x, y, i, j;

always @(posedge clk or posedge rst) begin
    if (rst) begin
        // initialize line buffer
        for (x = 0; x < WIDTH; x = x + 1)
            for (i = 0; i < 5; i = i + 1)
                line_buffer[x][i] <= 0;
    end else begin
        // update line buffer
        for (x = 0; x < WIDTH; x = x + 1)
            for (i = 0; i < 4; i = i + 1)
                line_buffer[x][i] <= line_buffer[x][i+1];
        for (x = 0; x < WIDTH; x = x + 1)
            line_buffer[x][4] <= image[x][0];
        // convolution
        for (x = 0; x < WIDTH; x = x + 1)
            for (y = 0; y < HEIGHT; y = y + 1) begin
                result[x][y] <= 0;
                for (i = 0; i < 5; i = i + 1)
                    for (j = 0; j < 5; j = j + 1)
                        if (x+i<WIDTH && y+j<HEIGHT)
                            result[x][y] <= result[x][y] + image[x+i][y+j] * filter[i][j];
            end
    end
end

endmodule

このコードは5×5の畳み込みフィルタを実装しています。画像データが入力として与えられ、それにフィルタが適用されて結果が出力されます。

フィルタ自体は任意の値を持つことが可能ですが、ここでは全てのピクセルに対して同じ重みを適用しています。

このコードを実行した結果、指定したフィルタに基づいた画像処理が可能となります。

例えば、エッジ検出フィルタを適用すれば、画像内のエッジ(境界)を検出することができます。

さて、このコードは理解する上でいくつかのポイントがあります。

まず、ラインバッファが5行分用意されていることです。

これは、5×5のフィルタを適用するために必要なデータ領域を保持するためです。

その上で、ラインバッファの更新と畳み込み処理が行われます。

これにより、各ピクセルに対するフィルタ処理が実行されるわけです。

このように、Verilogを使うことで、ハードウェアレベルでの高速な画像処理が可能となります。

また、このコードを基にして、さまざまなフィルタの設計や、畳み込みの大きさの変更など、カスタマイズも自由自在です。

●Verilogの高位合成の注意点と対処法

Verilogでの高位合成を行う際には、いくつか重要な注意点があります。

これらを理解し、適切に対処することで、高位合成の効率を向上させることが可能となります。

○注意点1:合成可能なコード

Verilogで記述されたコードがすべて高位合成できるわけではありません。

一部の高度な機能や特定の記述方法は、高位合成ツールによっては対応していない場合があります。

例えば、動的メモリ割り当て、複数のドライブを持つネット、初期値を持つ自動変数などは一般的に合成できません。

このため、合成を目的とした設計では、合成可能な範囲内でコードを記述することが重要となります。

○注意点2:タイミング制約

タイミング制約は、回路の設計において重要な要素です。

設計者は、高位合成ツールが生成するハードウェアが所望のタイミング特性を持つように、適切な制約を指定する必要があります。

タイミング制約を正しく指定しないと、合成された回路が期待通りの動作をしない場合があります。

例えば、クロックの周波数が高すぎると、データが正しく伝搬しきれずにセットアップ時間やホールド時間の違反が発生する可能性があります。

○注意点3:リソース消費

Verilogで記述されたコードが高位合成を通じてハードウェアに変換されるとき、その実装にはフィールドプログラマブルゲートアレイ(FPGA)などのリソースが消費されます。

そのリソース消費量は、Verilogのコードの記述方法や高位合成ツールの設定によって大きく変わることがあります。

適切なリソース消費量を確保するためには、コードの最適化や適切なリソース管理が求められます。

□対処法とサンプルコード8:リソース消費の管理

リソース消費を管理するための一つの対処法として、リソース消費を抑えるための記述方法を取り入れることがあります。

module Adder #(parameter WIDTH = 8) (
    input wire [WIDTH-1:0] a,
    input wire [WIDTH-1:0] b,
    output wire [WIDTH-1:0] sum
);
    assign sum = a + b;
endmodule

このコードでは、パラメータ化された幅の加算器を実装しています。

この例では、リソース消費を抑えるためにパラメータ化を活用しています。

パラメータ化により、同じ機能を持つモジュールを何度も書く代わりに、一度書いたモジュールを必要な幅で再利用することが可能となります。

このように、再利用可能なモジュールを設計することで、設計の効率化とリソース消費の削減が可能となります。

このコードを実行すると、パラメータに指定した幅の加算器が合成されます。

たとえば、Adder #(.WIDTH(16)) adder1 (.a(input1), .b(input2), .sum(output));と記述すると、16ビット幅の加算器が生成されます。

このようなコードの書き方は、リソースの節約だけでなく、コードの可読性や保守性の向上にも貢献します。

●Verilogの高位合成のカスタマイズ方法

ここでは、Verilogと高位合成を使用して更に高度なカスタマイズを行う方法を2つの視点から詳しく解説します。

それぞれの視点について具体的なサンプルコードとその詳細な説明を表し、実際の適用例を通して理解を深めます。

○カスタマイズ1:ユーザー定義プリミティブ

Verilogでは、ユーザー定義プリミティブ(UDP)を用いて、独自の論理ゲートを定義することが可能です。

UDPを利用することで、回路設計の柔軟性が大幅に向上します。

ここではUDPの基本的な構文と、それを利用したサンプルコードを紹介します。

□サンプルコード9:ユーザー定義プリミティブの作成

// ユーザー定義プリミティブ(UDP)の定義
primitive my_xor (output y, input a, b);
  // 初期状態
  initial y = 0;
  // 入力と出力の関係
  table
    // a: b: y:
    0  0  : 0;
    0  1  : 1;
    1  0  : 1;
    1  1  : 0;
  endtable
endprimitive

// テストベンチ
module test_my_xor;
  reg a, b;
  wire y;
  // UDPを使ったxorゲート
  my_xor u1(y, a, b);

  // シミュレーション
  initial begin
    $dumpfile("test_my_xor.vcd");
    $dumpvars(0, test_my_xor);
    #10 a = 0; b = 0;
    #10 a = 0; b = 1;
    #10 a = 1; b = 0;
    #10 a = 1; b = 1;
    #10 $finish;
  end
endmodule

このコードでは、独自のXORゲートをユーザー定義プリミティブとして定義し、その動作をテストベンチで確認しています。

具体的には、my_xorという名前でUDPを定義し、その中でaとbの入力に対する出力yの結果をテーブル形式で表しています。

テストベンチtest_my_xor内では、各種入力パターンを試すことで、このUDPが期待通りの動作をしていることを確認します。

このコードをシミュレートすると、aとbの各パターンに対して、yが期待通りの結果を出力します。

つまり、aとbのどちらか一方だけが1の時だけyが1になり、それ以外の場合はyが0になることを確認することができます。

○カスタマイズ2:パラメータ化

次に、パラメータ化について解説します。パラメータは定数の一種で、Verilog内で変更することはできませんが、モジュールの再利用性を向上させるための重要なツールです。

パラメータを使うことで、同じ構造を持つ異なるサイズのモジュールを一つの定義で生成できます。

□サンプルコード10:パラメータ化の利用

// パラメータ化されたシフトレジスタ
module shift_register #(parameter WIDTH = 8) (input wire clk, reset, din, output wire [WIDTH-1:0] dout);
  reg [WIDTH-1:0] data;

  always @(posedge clk or posedge reset) begin
    if (reset) 
      data <= 0;
    else 
      data <= {data[WIDTH-2:0], din};
  end

  assign dout = data;
endmodule

// テストベンチ
module test_shift_register;
  reg clk, reset, din;
  wire [7:0] dout8;
  wire [15:0] dout16;

  // 8ビットシフトレジスタ
  shift_register #(8) u1(clk, reset, din, dout8);
  // 16ビットシフトレジスタ
  shift_register #(16) u2(clk, reset, din, dout16);

  // シミュレーション
  initial begin
    $dumpfile("test_shift_register.vcd");
    $dumpvars(0, test_shift_register);
    #10 din = 1; reset = 1; clk = 0;
    #10 reset = 0;
    #20 din = 0;
    #10 clk = ~clk;
    #10 clk = ~clk;
    #10 clk = ~clk;
    #10 din = 1;
    #10 clk = ~clk;
    #10 clk = ~clk;
    #10 clk = ~clk;
    #10 $finish;
  end
endmodule

このコードでは、パラメータを使ってシフトレジスタを定義しています。

具体的には、WIDTHというパラメータでシフトレジスタのビット幅を指定し、そのビット幅に合わせたシフトレジスタを生成します。

テストベンチtest_shift_registerでは、8ビットと16ビットのシフトレジスタを生成し、それぞれが正しく動作するかを確認します。

このコードをシミュレーションすると、各シフトレジスタが期待通りに動作し、入力の値が正しくシフトされることを確認できます。

また、リセット信号が与えられたときには、シフトレジスタの内容が全て0にクリアされることも確認できます。

このようにパラメータを利用することで、一つのモジュール定義からさまざまなサイズのモジュールを生成することが可能となります。

この技術は設計の再利用性を高め、設計効率を向上させる大変重要な手法です。

●応用例の発展

ここでは、Verilogと高位合成を用いたさらに高度な応用例として、DSP(Digital Signal Processing)の設計とAI(Artificial Intelligence)アクセラレータの設計について説明します。

○応用例1:DSP設計

デジタル信号処理は、信号処理における最も重要な要素の一つで、音声、画像、無線信号など、様々なデジタル信号を操作します。

ここでは、Verilogを使ってデジタルフィルタを設計する例を見ていきましょう。

□サンプルコード11:DSP設計のVerilogコード

module FIR_filter #(parameter N = 8)
  (input wire clk, reset, 
  input wire [7:0] din, 
  output reg [7:0] dout);

  reg [7:0] delay_line [0:N-1];

  integer i;

  always @(posedge clk or posedge reset) begin
    if (reset) begin
      for (i = 0; i < N; i = i + 1) delay_line[i] <= 0;
    end else begin
      for (i = N - 1; i > 0; i = i - 1) delay_line[i] <= delay_line[i - 1];
      delay_line[0] <= din;
    end
  end

  always @(posedge clk) begin
    dout <= delay_line[0] + delay_line[N - 1];
  end
endmodule

このコードは、パラメータ化されたデジタルフィルタ(FIRフィルタ)の設計を行います。

このフィルタはディレイラインという概念を利用しており、入力信号を一定時間遅延させることで信号の各成分に対する応答を変化させます。

ディレイラインの各要素は、ディレイラインの前後の要素からの信号を取得し、その結果を出力として返します。

このコードをシミュレーションすると、入力信号がディレイラインを通過し、その過程で変形されて出力される様子を観察することができます。

FIRフィルタの設計はDSP設計の基本であり、Verilogを使って効率的に設計することができます。

○応用例2:AIアクセラレータ設計

AIアクセラレータは、機械学習の計算を高速化するための特殊なハードウェアです。

一般的には、大量の乗算と加算を並列に行う能力を持つことが求められます。

Verilogを使って、簡単なマトリックス乗算器を設計してみましょう。

□サンプルコード12:AIアクセラレータ設計のVerilogコード

module matrix_multiplier #(parameter N = 8)
  (input wire clk, reset, 
  input wire [7:0] A [0:N-1][0:N-1], B [0:N-1][0:N-1], 
  output reg [7:0] C [0:N-1][0:N-1]);

  integer i, j, k;

  always @(posedge clk or posedge reset) begin
    if (reset) begin
      for (i = 0; i < N; i = i + 1) 
        for (j = 0; j < N; j = j + 1)
          C[i][j] <= 0;
    end else begin
      for (i = 0; i < N; i = i + 1) 
        for (j = 0; j < N; j = j + 1) 
          for (k = 0; k < N; k = k + 1) 
            C[i][j] <= C[i][j] + A[i][k] * B[k][j];
    end
  end
endmodule

このコードでは、入力された2つのマトリックスAとBの乗算を行い、結果をマトリックスCに格納します。

ここで、乗算の計算は、三つのforループを使用して行われています。

これは、各行と列の要素を順に掛け合わせ、その結果を累積していくことで行列乗算を行っています。

このコードをシミュレーションすると、入力されたマトリックスAとBの乗算結果が正しくマトリックスCに格納されることが確認できます。

このようなマトリックス乗算器は、ニューラルネットワークのようなAIアルゴリズムにおいて重要な役割を果たします。

まとめ

Verilogと高位合成は、デジタルシステムの設計において重要な役割を果たします。

基本的な概念から応用例、注意点と対処法、カスタマイズ方法に至るまで、この記事で学んだ知識は、Verilogと高位合成の世界への入門として理想的なガイドとなるでしょう。

これらの知識を基に、Verilogと高位合成を使った効果的なデジタルシステムの設計を行うことが可能となります。