Verilog配列を完全解説|基本構文から多次元・メモリ活用・SystemVerilogの拡張まで

目次

1. はじめに

Verilog(ヴェリログ)は、ハードウェア記述言語として広く使用されており、FPGAやASICなどの回路設計において欠かせない存在です。Verilogを使って効率的な設計を行うためには、配列の理解が非常に重要です。

配列を活用することで、データの集まりを簡潔かつ直感的に扱えるようになり、回路記述の可読性と保守性が向上します。特に、複数の信号をグループ化したり、RAMなどのメモリ構造を表現したりする場面では、配列が非常に効果的です。

本記事では、「Verilog 配列」というキーワードに焦点を当てて、基本的な配列の定義方法から、実務に役立つ応用テクニックまでを幅広く解説していきます。配列の種類やSystemVerilogでの拡張機能、よくあるエラーやFAQまで網羅的にカバーすることで、読者の理解を深めることを目指します。

初心者の方にもわかりやすく、実践的なコード例を交えながら進めていきますので、ぜひ最後までご覧ください。

2. Verilogの基本データ型

Verilogで配列を扱う前に、まずは基本的なデータ型について理解しておく必要があります。Verilogでは、回路設計に必要な論理信号を扱うために、いくつかの主要なデータ型が用意されています。

reg型とwire型の違い

Verilogでは、最もよく使われるデータ型として「reg(レジスタ)」と「wire(ワイヤ)」があります。これらは、論理信号の振る舞いに応じて使い分ける必要があります。

  • wire型
    wireは、他のモジュールや回路からの接続線として使用されます。常に他の信号に駆動される必要があり、代入はassign文を使って行います。組み合わせ回路の出力に適しています。 例:
  wire a;
  assign a = b & c;
  • reg型
    regは、一時的に値を保持する変数として使われます。プロセスブロック(alwaysなど)の中で代入され、記憶素子(ラッチやフリップフロップ)を表現するのに使われます。 例:
  reg q;
  always @(posedge clk) begin
      q <= d;
  end

配列に使用できるデータ型

Verilogの配列は、基本的にreg型で定義されることが多いですが、wire型でも一部の用途に応じて使用可能です。ただし、初期のVerilogでは多次元配列に対応していないなどの制約がありました。後述のSystemVerilogではこの点が大きく改善されています。

以下に、配列として使える簡単な構文例を示します。

reg [7:0] data_array [0:15];  // 8ビットのデータを16個格納する配列

このように、データ型の基本を理解することで、配列の宣言や使用における混乱を避けることができます。特にregとwireの使い分けを誤ると、シミュレーションエラーや論理合成エラーの原因となるため注意が必要です。

3. 配列の基本概念

Verilogでは、同じ型の信号を複数まとめて扱いたい場合に「配列(array)」を使用します。配列を活用することで、信号を整理し、コードの可読性や再利用性を向上させることができます。

配列の宣言方法

Verilogでは、主に1次元配列がサポートされており、構文は以下のようになります。

reg [ビット幅] 配列名 [インデックス範囲];

具体例:

reg [7:0] data_array [0:15];  // 8ビットのデータを16個格納する配列

この例では、data_array0から15までの16個の要素を持ち、それぞれの要素が8ビット幅(1バイト)のデータを格納します。

配列要素へのアクセス方法

配列の各要素には、インデックス番号を指定してアクセスします。C言語などと同様に、0から始まる添字でアクセスできます。

data_array[0] = 8'hFF;   // 最初の要素に16進数FFを代入
data_array[1] = 8'd12;   // 2番目の要素に10進数12を代入

また、alwaysブロック内でループを用いて初期化や操作を行うこともできます。

integer i;
always @(posedge clk) begin
    for (i = 0; i < 16; i = i + 1) begin
        data_array[i] <= 8'd0;
    end
end

配列の利点

  • 一括処理が可能:for文と組み合わせることで、複数の信号に同じ処理を一度に適用できます。
  • 回路の構造化:複数のレジスタや信号を整理し、回路図的な構造を明確に保てます。
  • メモリモデルの実装:RAMやROMなど、簡易的なメモリ構造をコードで表現できます(次章で解説)。

注意点

Verilogでは、配列全体への直接代入data_array = 値)はサポートされていません。要素単位での操作が基本です。また、1次元配列のみが正式にサポートされていたため、多次元配列を使いたい場合はVerilog 2001以降やSystemVerilogを用いる必要があります。

4. 多次元配列の活用

Verilogにおいて、配列は設計の簡素化や回路構造の整理に役立ちますが、多次元配列を使うことで、より複雑なデータ構造を効率的に扱うことができます。

ただし、注意すべき点として、古いVerilog(IEEE 1364-1995)では多次元配列はサポートされておらず、Verilog 2001以降で正式に導入されました。さらに柔軟な配列操作を行いたい場合は、SystemVerilogの使用が推奨されます。

多次元配列の宣言

Verilog 2001以降では、1つの変数に対して複数の添字を指定することで、多次元配列を定義できます。基本的な構文は次のとおりです。

reg [7:0] matrix [0:3][0:3];  // 4×4の8ビット行列を定義

この宣言により、matrixは16個の8ビット要素を持つ2次元配列になります。

要素へのアクセスと代入

多次元配列においても、インデックスを使って特定の要素にアクセスし、値を代入することが可能です。

matrix[0][0] = 8'hA5;
matrix[2][3] = 8'd255;

forループとの併用

多次元配列の操作は、ネストされたfor文を使うことで柔軟に行えます。以下は配列を初期化する例です。

integer i, j;
always @(posedge clk) begin
    for (i = 0; i < 4; i = i + 1) begin
        for (j = 0; j < 4; j = j + 1) begin
            matrix[i][j] <= 8'd0;
        end
    end
end

多次元配列の活用例

  • 行列演算やフィルタ処理のような、複雑なデータ構造を要する回路設計において有効。
  • 画像処理やデジタル信号処理(DSP)における、ピクセル単位のデータ操作にも応用可能。
  • ROM/RAMのブロック化アドレスとデータのペアの整理にも利用されることがあります。

注意点と制限

  • 多次元配列を使用する際は、合成ツールの対応状況に注意が必要です。ツールによっては一部サポートされていない可能性もあります。
  • インスタンシエーションやインターフェースとの組み合わせにおいて、制約が発生する場合があります。
  • SystemVerilogとの違いも把握しておくことで、互換性問題を防げます(後述のセクションで詳述)。

5. 配列を用いたメモリのモデル化

Verilogでは、配列を使って簡易的なメモリ構造をモデル化することが可能です。これにより、RAMやROMといったストレージ回路のシミュレーションや設計を、簡潔にかつ柔軟に表現できます。

特に、1次元配列を使ったメモリモデルは、CPU設計や通信システムなどにおいて頻繁に用いられます。

メモリモデルの基本構文

以下は、1ワード32ビット、アドレス数1024(0〜1023)のシンプルなRAMを配列で表現した例です。

reg [31:0] memory [0:1023];  // 32ビット×1024ワードのメモリ

この宣言により、memoryという名前の配列が作成され、各インデックス(アドレス)に32ビットのデータを格納できます。

メモリへの書き込みと読み出し

配列を使ったメモリへの読み書き操作は、次のように記述します。

// 書き込み
always @(posedge clk) begin
    if (we) begin
        memory[addr] <= data_in;
    end
end

// 読み出し
assign data_out = memory[addr];

ここでのポイント:

  • 書き込みはposedge clkで同期的に行い、条件付きで実行(we=write enable)。
  • 読み出しは組み合わせ回路としてassignで実装するのが一般的です(非同期読み出し)。

メモリ初期化の方法

テストベンチや初期状態設定のために、initialブロックで配列を初期化することも可能です。

integer i;
initial begin
    for (i = 0; i < 1024; i = i + 1) begin
        memory[i] = 32'd0;
    end
end

また、$readmemh$readmembを使えば、外部ファイルから初期値を読み込むこともできます。

initial begin
    $readmemh("rom_init.hex", memory);  // 16進数形式で初期化
end

実務での用途例

  • CPUやマイコンのレジスタファイル
  • FPGA内のブロックRAM(BRAM)の振る舞いシミュレーション
  • キャッシュメモリの動作検証
  • ROMに格納されたデータの読み出しシミュレーション

注意点

  • 配列のサイズが大きくなると、シミュレーション時間や合成時のリソースが増加します。
  • 読み出しタイミング(同期 or 非同期)は、設計仕様や使用ツールに応じて慎重に選定しましょう。
  • 合成を想定する場合、特定のメモリ推論ルールに従った記述が推奨されます。

6. SystemVerilogにおける配列の拡張

Verilog 2001までは配列機能が限定的で、設計が煩雑になることもありました。そこで、Verilogの後継であるSystemVerilogでは、配列機能が大幅に拡張され、より柔軟で強力な記述が可能になりました。

この章では、SystemVerilogで使用可能な代表的な3種類の配列、すなわち「動的配列」「連想配列」「キュー配列」について、それぞれの特徴と使いどころを解説します。

動的配列(Dynamic Arrays)

特徴

  • 実行時にサイズを変更できる。
  • 配列サイズが事前に決まっていない、もしくは可変の場合に有効。

宣言方法と使用例

int dyn_array[];             // 動的配列の宣言
dyn_array = new[10];         // 要素数10で初期化
dyn_array[0] = 100;

利用場面

  • テストベンチにおける一時的なデータ保持
  • サイズが可変なバッファ管理

連想配列(Associative Arrays)

特徴

  • インデックスに任意の値(整数・文字列など)を使用できる。
  • ハッシュテーブル的な役割を果たす。

宣言方法と使用例

int assoc_array[string];     // 文字列をキーとした連想配列
assoc_array["id_001"] = 42;

利用場面

  • パラメータごとの設定値を扱うとき
  • IDや名前などで値を検索・保持したいとき

キュー配列(Queues)

特徴

  • FIFO(First-In-First-Out)構造に近い。
  • 要素の追加・削除が容易で、動的に変化するデータ列に最適。

宣言方法と使用例

int queue_array[$];          // キュー型配列の宣言

queue_array.push_back(10);   // 末尾に追加
queue_array.push_front(5);   // 先頭に追加
int val = queue_array.pop_front();  // 先頭から取り出し

利用場面

  • データの一時保持(FIFOバッファ)
  • イベント処理やトランザクション管理

比較と使い分け

配列の種類サイズ変更インデックスの型使いどころ
動的配列可能整数サイズが決まっていないとき
連想配列可能任意(int, stringなど)ハッシュ的に使いたいとき
キュー配列可能自動(先頭・末尾)データ列の追加・削除を頻繁に行うとき

注意点

  • これらの拡張配列はSystemVerilogの機能であり、Verilogでは使用できません
  • 合成可能な範囲はツール依存であり、主にテストベンチ用途で使用されることが多いです。
  • 実装ターゲットがFPGAやASICの場合は、仕様上使えるかどうかを事前に確認することが大切です。

7. 配列操作のベストプラクティス

VerilogやSystemVerilogで配列を扱う際には、効率的で可読性の高いコーディングを意識することが、品質の高いハードウェア設計につながります。この章では、配列を安全かつ効果的に扱うためのベストプラクティスを紹介します。

コメントと命名で意図を明確にする

配列はスケーラブルで便利な一方、要素が何を表しているのか分かりづらくなることもあります。そのため、以下の点に注意しましょう:

  • 配列名は「意味のある単語」を使う:reg [7:0] sensor_data [0:7];
  • コメントで用途や単位を明記する:
  // 8つのセンサーからの8ビットデータを格納
  reg [7:0] sensor_data [0:7];

ループ処理は境界条件に注意

forループを使って配列を一括操作する際は、インデックスの範囲を正しく設定することが重要です。

  • 誤った上限 → 配列外アクセス(論理エラーやシミュレータの警告)
  • 境界に<<=どちらを使うか慎重に判断

例:

integer i;
always @(posedge clk) begin
    for (i = 0; i < 8; i = i + 1) begin
        sensor_data[i] <= 8'd0;
    end
end

初期化は明示的に行う

配列の初期値が未定義のまま使用されると、シミュレーション結果に影響を及ぼす恐れがあります。特にRAMやレジスタバンクを模した配列では、明示的な初期化を推奨します。

initial begin
    for (i = 0; i < 256; i = i + 1)
        mem[i] = 32'd0;
end

また、SystemVerilogでは、コンストラクタやforeachループを使ってより簡潔に初期化が可能です。

再利用可能なモジュール設計を意識する

配列を使った設計は、インスタンスの個数やビット幅が変わっても柔軟に対応できるという利点があります。そのため、以下のような工夫が有効です。

  • parameterを使って配列サイズを可変にする:
  parameter DEPTH = 16;
  reg [7:0] buffer [0:DEPTH-1];
  • 外部からサイズを渡せるようにすることで、モジュールの再利用性が高まります。

合成可能性を意識する

配列を使用する際は、合成ツール(FPGA/ASIC)の対応状況も考慮しましょう。

  • Verilogの1次元reg配列:一般的な合成ツールで問題なし
  • SystemVerilogの動的/連想配列/キュー:合成不可(基本的にシミュレーション専用)

そのため、合成を想定する回路部分では、古典的なreg配列を中心に設計することが望ましいです。

配列とモジュールの分割を使い分ける

配列によってコード量は削減できますが、設計が複雑になる場合は機能ごとにモジュールを分割する方がメンテナンス性が高くなります。

  • 少数・同等の処理:配列 + forループ
  • 機能が異なる/規模が大きい:モジュール化して階層設計

8. よくある質問(FAQ)

VerilogやSystemVerilogで配列を使用していると、初学者から中級者まで多くの方が共通してつまずくポイントがあります。ここでは、「Verilog 配列」に関して特によく寄せられる質問を3つ取り上げ、実務経験や設計現場での知見も交えながら丁寧に解説します。

Q1. Verilogで多次元配列を使うとエラーになります。なぜですか?

A1.

Verilog 1995やVerilog 2001以前の環境では、多次元配列がサポートされていないか、制限された形でのみサポートされているためです。

例えば、次のような記述はVerilog 1995ではコンパイルエラーになります:

reg [7:0] matrix [0:3][0:3];  // Verilog 2001以降でサポート

対策:

  • 使用している開発環境がVerilog 2001以降に準拠しているかを確認
  • それでも制限がある場合は、1次元配列の「配列内配列」的な実装に書き換える
  reg [7:0] matrix_1d [0:15];  // 4x4 のように展開し、アクセス時に (i*4 + j) を使う

Q2. 配列を使ってRAMを記述した場合、実機で動作しますか?

A2.

はい、Verilogで配列を使ったRAM記述は多くの合成ツールで対応済みです。以下のような構文が使われます:

reg [31:0] mem [0:255];  // 32ビット×256ワードのRAM

ただし、注意点があります

  • 合成ツールがこの記述を実際のメモリブロック(Block RAM)として推論する条件を満たす必要がある
  • 読み出し/書き込みのタイミング(同期 or 非同期)や、アクセス方法が特定のテンプレートに合っていないとメモリとして推論されない可能性がある

対策:

  • 合成ガイド(各ベンダーの「推論可能なRAM構文」)に従うこと
  • シミュレーションと実機での挙動に差が出る場合は、ログを活用してトラブルシュート

Q3. SystemVerilogの動的配列・連想配列・キューは実機でも使えますか?

A3.

基本的に、動的配列・連想配列・キューは合成不可(シミュレーション専用)です。これらの構造は、柔軟な記述が可能な一方で、実機のハードウェア回路として直接マッピングすることができません

そのため、これらは以下のような目的で使用されます:

  • テストベンチでの一時的なデータ保持
  • 検証環境でのランダム化やスコアボードの実装
  • 複雑なトランザクションの記述

実装上の注意:

  • 動的配列や連想配列を使った設計部分は論理合成ツールで無視されるかエラーになります
  • 実機に展開する必要がある場合は、regや固定長の1次元配列に変換する必要があります

9. まとめ

本記事では、「Verilog 配列」というキーワードに焦点を当て、基礎から応用まで幅広く解説してきました。回路設計の現場で配列を正しく使いこなすことは、設計の効率化・可読性の向上・メンテナンス性の確保に直結します。

本記事の振り返りポイント

  • Verilogの基本データ型(regとwire)を正しく理解することで、配列使用時の誤りを防げる。
  • 1次元配列は、データの集まりやメモリモデルの実装において重要な構造。
  • 多次元配列はVerilog 2001以降でサポートされ、行列などの高度な構造も扱える。
  • SystemVerilogを用いることで、動的配列・連想配列・キューといった柔軟な構造を利用可能(ただし基本的に検証専用)。
  • 配列の扱いにおけるベストプラクティス(初期化、命名、再利用性、合成可否)を押さえることで、より良いコードが書ける。

実務におけるヒント

配列は非常に便利な構造ですが、「便利だから何でも配列で書けばいい」というわけではありません。特に合成可能なコードを書きたい場合や、他者と協業する場合は、制約やスタイルガイドに配慮することが求められます。

また、SystemVerilogの高度な配列機能も、シミュレーション用と割り切ることで、その力を最大限に発揮できます。目的に応じて、使い分けができることが良い設計者の条件です。

次に読むべきトピック

もしこの記事で配列の基礎を理解できたら、次は以下のようなトピックに進むことをおすすめします。

  • generate文を使った配列の動的な回路生成
  • interfaceと配列の組み合わせによるバス設計
  • FIFO、リングバッファ、ROMの実装と配列構造の最適化

配列を使いこなすことは、Verilog設計者としての第一歩です。着実に理解を深め、より複雑な回路設計にも対応できるスキルを身につけていきましょう。