Verilog とは何か
コンピュータに「何をするか」を伝える命令書。CPU が命令を上から順番に実行する。
# 2つの数を足す a = 5 b = 3 print(a + b) # 8
回路の「配線図」をコードで表す言語。すべての回路が同時に動くのが特徴である。
// 2つの数を足す回路 assign result = a + b; // a か b が変わると // result も即座に変わる
モジュール ― 回路の「箱」
Verilog の基本単位は module である。IC チップ(黒い四角いパーツ)をイメージすると分かりやすいだろう。 入力ピンと出力ピンを持つ「箱」である。
// ↓ モジュール名(IC チップの型番に相当) module and_gate ( input a, // ← 入力ピン a input b, // ← 入力ピン b output y // ← 出力ピン y ); assign y = a & b; // ← 回路の中身:AND 演算 endmodule // ← 箱の終わり
| キーワード | 意味 |
|---|---|
| module … endmodule | 箱の外枠。IC チップ全体を定義する |
| input / output | 入力・出力ピン。外部との接続口 |
| assign | 電線の結び方を書く。常時有効 |
wire と reg ― 信号を表す2つの型
名前のとおり「ただの電線」である。つながれた先の値をそのまま伝えるだけで、値を記憶しない。 assign 文と組み合わせて使う。
wire sum; assign sum = a + b; // a や b が変わると // sum もすぐ変わる
→ 組み合わせ回路(加算器など)で使う
always ブロックの中で代入するための変数である。reg と書くだけでは値を記憶しない。 クロックの立ち上がりで更新する回路を書くと、フリップフロップ(記憶素子)として合成される。
reg [7:0] count; always @(posedge clk) count <= count + 1; // clk の立ち上がりごとに +1
→ 順序回路でも組み合わせ回路でも、always 内の代入先に使う
信号が複数ビットの場合は [上位:下位] で幅を指定する。
| 宣言 | ビット幅 | 表せる値 |
|---|---|---|
| wire y | 1 ビット | 0 / 1 |
| wire [3:0] y | 4 ビット | 0〜15 |
| reg [7:0] y | 8 ビット | 0〜255 |
回路を書く3つの方法
「この出力はこの入力たちの演算結果」という常時有効な結線を記述する。 プログラムの行と違い、順番に実行されるわけではない。
assign y = a & b; // AND assign sum = a + b; // 加算 assign sel = (a > b) ? a : b; // 三項演算子(MUX)
クロックの立ち上がり(posedge)ごとに実行されるブロックである。 フリップフロップ(記憶素子)の動作を表す。
always @(posedge clk) begin if (rst) count <= 0; // リセット else count <= count + 1; // インクリメント end
assign では書きにくい if-else や case は always @(*) で書く。 * は「すべての入力変化に反応する」という意味である。演習D の ALU でこの形を使う。
always @(*) begin case (opcode) 2'b00: result = a + b; // ADD 2'b01: result = a - b; // SUB default: result = 0; endcase end
完全な例:1ビット全加算器
論理ゲートのページで学んだ全加算器(Full Adder)を Verilog で書いてみましょう。 入力 a, b, cin から出力 sum, cout を計算する。
| a | b | cin | sum | cout |
|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 |
| 0 | 0 | 1 | 1 | 0 |
| 0 | 1 | 0 | 1 | 0 |
| 0 | 1 | 1 | 0 | 1 |
| 1 | 0 | 0 | 1 | 0 |
| 1 | 0 | 1 | 0 | 1 |
| 1 | 1 | 0 | 0 | 1 |
| 1 | 1 | 1 | 1 | 1 |
この式をそのまま assign で書ける。真理値表と式と回路が1対1で対応しているのが、Verilog の分かりやすいところである。
module full_adder ( input a, b, cin, // 3本の入力ピン output sum, cout // 2本の出力ピン ); // sum = a XOR b XOR cin assign sum = a ^ b ^ cin; // cout = (a AND b) OR (cin AND (a XOR b)) assign cout = (a & b) | (cin & (a ^ b)); endmodule // わずか8行で1ビット全加算器が完成
テストベンチ ― 設計を検証する
テストベンチ(Testbench)は「作った回路を試す実験台」である。実際のチップを作る前に、 コンピュータ上でシミュレーションしてバグがないか確認する。
module tb_full_adder; reg a, b, cin; // 操作する入力(reg で宣言) wire sum, cout; // 観察する出力(wire で宣言) reg [1:0] expected; // 期待する {cout, sum} integer pattern, errors; // 設計した full_adder を接続する full_adder uut (.a(a), .b(b), .cin(cin), .sum(sum), .cout(cout)); initial begin $dumpfile("dump.vcd"); // 波形ファイル出力 $dumpvars(0, tb_full_adder); errors = 0; for (pattern = 0; pattern < 8; pattern = pattern + 1) begin {a, b, cin} = pattern; expected = a + b + cin; #10; if ({cout, sum} !== expected) begin errors = errors + 1; $display("FAIL: %b%b%b: expected=%b, actual=%b", a, b, cin, expected, {cout, sum}); end end if (errors == 0) $display("PASS: all 8 input patterns verified"); $finish; end endmodule
PASS: all 8 input patterns verified
Verilog は実際に何に使われるのか
「Verilog で回路を書いたあと、どうなるのか」。現実では大きく2つの方向に使われる。
Verilog の回路を論理合成して FPGA(書き換え可能な半導体)に書き込むと、実際に動くハードウェアになる。 チップを製造しなくても、設計をすぐ実機で試せる。
活用例:カメラの画像処理、通信機器、AI アクセラレータ、宇宙探査機の制御回路
実チップを作る前にテストベンチで動作検証したり、新しいアーキテクチャのアイデアを シミュレーションで性能評価する。研究では「この CPU 設計は速いか」を Verilog で試す。
活用例:CPU アーキテクチャ研究、新機能の性能予測、大学や企業の研究開発
次のステップ:演習2
ここまでの知識で、演習2に挑戦できる。ブラウザ内でその場で正誤が出るので、 インストールもアカウント登録も要らない。加算器 → 減算器 → 比較器 → ALU → 小型 CPU の順に、 前に作ったものを次で使い回しながら進む。
発展:本物のツール(EDA Playground)でも動かす 任意
実際の開発で使われるシミュレータ(Icarus Verilog)と波形ビューアを体験したい人は、 EDA Playground に上のコードを貼り付けて実行できる(Gmail アカウントが必要)。 手順は 使い方ガイド へ。
- Language:Verilog
- Simulator:Icarus Verilog 12.0
- 「Open EPWave after run」にチェック(波形表示)
- テストベンチ → 左パネル/設計コード → 右パネル