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
module ~ endmodule の間に回路の動作を書きます。
入出力は 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. 記述スタイル:assign と always
組合せ回路: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 の左辺は必ず wire。always 内で代入する変数は必ず reg。
ビット幅の不一致
8ビット + 8ビット の結果はオーバーフローする可能性。[8:0] で9ビット確保するか {cout, sum} で受け取ること。
ブロッキング vs ノンブロッキング代入の混在
順序回路の always ブロックでは必ず <= を使い、= と混在させないこと。
case 文の default
ALU などの case 文では、漏れを防ぐために必ず default 節を書く習慣をつけましょう。