入門資料 演習2 サポート

Verilog HDL 入門

ハードウェア記述言語 Verilog で回路を「書く」ための基礎知識

1. Verilog とは?

Verilog HDL(Hardware Description Language)は、デジタル回路を「コード」で記述するための言語です。 プログラム言語(Java・Python 等)がソフトウェアの動作を記述するのに対し、Verilog は ハードウェアの構造・動作を記述します。

Verilog でできること

  • 論理ゲートやフリップフロップの記述
  • 加算器・ALU などの演算回路の設計
  • FSM(有限状態機械)の実装
  • CPU などの複雑なシステムの設計
  • シミュレーションによる動作検証
  • FPGA や ASIC への合成

プログラム言語との違い

  • コードは「手順」ではなく「回路の形」を表す
  • 複数の処理が並列に実行される
  • 時間(クロック)の概念がある
  • 信号の遅延・タイミングを扱える

2. モジュール:回路の基本単位

Verilog ではすべての回路を module(モジュール)として記述します。モジュールは「入出力ポート」を持つ部品です。

// ANDゲートのモジュール例
module and_gate (
    input  a,      // 入力ポート a
    input  b,      // 入力ポート b
    output y       // 出力ポート y
);

    assign y = a & b;   // 継続的代入

endmodule
ポイント: moduleendmodule の間に回路の動作を書きます。 入出力は input / output で宣言します。

3. 主なデータ型

用途
wire 組合せ回路の信号線(値を保持しない) wire [7:0] sum;
reg 順序回路の記憶要素(always 内で代入) reg [7:0] acc;
integer シミュレーション用の整数変数 integer i;
parameter 定数(ビット幅などに使う) parameter N = 8;
// ビット幅の指定方法
wire        x;          // 1ビット
wire [7:0]  data;      // 8ビット(MSB=7, LSB=0)
reg  [15:0] addr;      // 16ビット

4. 記述スタイル:assignalways

組合せ回路:assign

入力が変わるたびに出力が即座に更新される回路。
「常にこの式で駆動する」という意味。

module half_adder (
    input  a, b,
    output sum, carry
);
    assign sum   = a ^ b;  // XOR
    assign carry = a & b;  // AND
endmodule

順序回路:always

クロックの立ち上がりなどのイベントで動作するレジスタ。
DFF(フリップフロップ)の基本パターン。

module dff (
    input       clk, rst, d,
    output reg q
);
    always @(posedge clk) begin
        if (rst) q <= 1'b0;
        else     q <= d;
    end
endmodule

代入演算子の違い:

assign y = expr; → 継続的代入(wire に使う)

q <= d; → ノンブロッキング代入(always 内の順序回路に使う)

q = d; → ブロッキング代入(always 内の組合せ回路に使う)

5. よく使う演算子

ビット演算

&AND
|OR
^XOR
~NOT(反転)
~^XNOR

算術演算

+加算
-減算
*乗算
<<左シフト
>>右シフト

比較・連結

==等値比較
!=非等値
>, <大小比較
{a,b}連結(concat)
? :三項演算子
// 連結演算子の例:9ビット加算結果の取得
wire [8:0] result;
assign result = {1'b0, a} + {1'b0, b};  // キャリーを含む

// 三項演算子の例
assign y = (sel == 1'b1) ? a : b;   // 2入力マルチプレクサ

6. テストベンチ(シミュレーション)

テストベンチは、設計したモジュールに「入力を与えて、出力を確認する」ためのコードです。EDA Playground でのシミュレーションに不可欠です。

// half_adder のテストベンチ
module tb_half_adder;
    reg  a, b;
    wire sum, carry;

    // テスト対象モジュールのインスタンス化
    half_adder uut (.a(a), .b(b), .sum(sum), .carry(carry));

    initial begin
        $dumpfile("dump.vcd");
        $dumpvars(0, tb_half_adder);

        // 全パターンをテスト
        a = 0; b = 0; #10;
        a = 0; b = 1; #10;
        a = 1; b = 0; #10;
        a = 1; b = 1; #10;

        $display("テスト完了");
        $finish;
    end
endmodule

よく使うシステムタスク

  • $display("...") — コンソール出力
  • $monitor(...) — 信号変化を自動表示
  • $dumpvars() — 波形ファイルに記録
  • $finish — シミュレーション終了
  • #10 — 10 時間単位待機

インスタンス化の書き方

モジュール名 インスタンス名 (.ポート名(接続信号), ...);

例:half_adder uut (.a(a), .b(b), .sum(sum), .carry(carry));

7. よくある落とし穴

⚠️

wire と reg の混在

assign の左辺は必ず wirealways 内で代入する変数は必ず reg

⚠️

ビット幅の不一致

8ビット + 8ビット の結果はオーバーフローする可能性。[8:0] で9ビット確保するか {cout, sum} で受け取ること。

⚠️

ブロッキング vs ノンブロッキング代入の混在

順序回路の always ブロックでは必ず <= を使い、= と混在させないこと。

💡

case 文の default

ALU などの case 文では、漏れを防ぐために必ず default 節を書く習慣をつけましょう。

次のステップ