目的
Verilogで簡単な4ビットCPUを作成し、どんな回路が生成されるかを確認します。
作業はx86_64 Ubuntu上にて行います。
加算、減算とメモリの操作のみて、分岐等にはまだ対応していません。
実装
cpu4.v
//============================================================
// 4bit CPU
//============================================================
// このファイルは1つのCPUをまるごと定義しています。
// 命令は8bitで、上位4bitが命令コード、下位4bitが値やメモリアドレスです。
//============================================================
module cpu4 (
input clk, // クロック信号(時間の進みを表す。1回動くたびに1命令実行)
input reset, // リセット信号(最初に戻す)
output reg [3:0] acc, // アキュムレータ(計算結果を入れる4ビットレジスタ)
output reg halted // HLT命令を実行したら1になる(止まった状態)
);
//===== メモリ(16個 x 8bit) ===========================
reg [7:0] memory [0:15]; // メモリ。プログラムとデータを格納。
//===== プログラムカウンタ ==============================
reg [3:0] pc; // 次に実行する命令の場所(0〜15)
//===== 命令の内訳 =======================================
reg [7:0] instr; // 現在実行中の命令 8bit
wire [3:0] opcode = instr[7:4]; // 上位4ビット → 命令の種類
wire [3:0] operand = instr[3:0]; // 下位4ビット → 値またはアドレス
//===== メモリ初期化(外部ファイル読み込み) ===========
integer i;
initial begin
// メモリを0で初期化
for (i = 0; i < 16; i = i + 1)
memory[i] = 8'b00000000;
// 外部ファイルからプログラムを読み込む
// (別ファイル program.mem に命令を記述しておく)
$readmemb("program.mem", memory);
end
//===== CPU動作 =================================
always @(posedge clk) begin // クロック(clk)が立ち上がった瞬間(0→1になった瞬間)ここが実行される
if (reset) begin // resetが1の場合に実行
pc <= 0;
acc <= 0;
halted <= 0;
end else if (!halted) begin
//=======================
// 1. 命令を読み取る
//=======================
instr <= memory[pc];
//=======================
// 2. 命令を実行する
//=======================
case (opcode)
4'b0001: begin // LDI (即値をACCへ格納)
acc <= operand; // ACCに値を入れる
pc <= pc + 1; // 次の命令へ
end
4'b0010: begin // LD (指定したメモリアドレスの値(下位4ビット)を ACC に読み込む)
acc <= memory[operand][3:0]; // メモリの値を読み込む
pc <= pc + 1;
end
4'b0011: begin // ST(LDの逆)
memory[operand] <= {4'b0000, acc}; // ACCをメモリに書く(4ビットのみ対応。上位4ビットは常に0000)
pc <= pc + 1;
end
4'b0100: begin // ADD
acc <= acc + memory[operand][3:0]; // メモリの値を足す
pc <= pc + 1;
end
4'b0101: begin // SUB
acc <= acc - memory[operand][3:0]; // メモリの値を引く
pc <= pc + 1;
end
4'b1111: begin // HLT
halted <= 1; // 停止
end
default: begin
pc <= pc + 1; // 何もせず次へ
end
endcase
end
end
endmodule
//============================================================
// 実際にCPUを動かして、結果を見るためのコード
//============================================================
module testbench;
reg clk; // 入力
reg reset; // 入力
wire [3:0] acc; // 出力
wire halted; // 出力
// CPUを接続
cpu4 uut (
.clk(clk),
.reset(reset),
.acc(acc),
.halted(halted)
);
// クロックを作る(0→1→0→1と変化)
initial begin
clk = 0;
forever #5 clk = ~clk; // 5単位ごとに反転(周期10)
end
// テストシーケンス
initial begin
$dumpfile("cpu4.vcd"); // 波形ファイル出力
$dumpvars(0, uut);
// リセット
reset = 1;
#10;
reset = 0;
// 実行を少し待つ
#100;
// 結果表示
$display("ACC = %d", acc);
$finish;
end
endmodule
実行方法
iverilog -o cpu4.out cpu4.v
vvp cpu4.out
回路図生成
yosys
read_verilog cpu4_2.v
hierarchy -top cpu4
proc; opt
show -format dot -prefix cpu4
rsvg-convert cpu4.svg -o cpu4.png
dot -Tsvg cpu4.dot -o cpu4.svg
機械語
00010011 : LDI 3(ACC ← 3)
01000101 : ADD 5(ACC ← ACC + MEM[5])
00110110 : ST 6(MEM[6] ← ACC)
11110000 : HLT(停止)
00000000 : 使わない領域
00000010 : MEM[5] = 2
00010011
01000101
00110110
11110000
00000000
00000010
00000000
00000000
00000000
00000000
00000000
00000000
00000000
00000000
00000000
00000000
test@test-ThinkPad-X280:~/kaihatsu/cpu$ iverilog -o cpu4.out cpu4.v
test@test-ThinkPad-X280:~/kaihatsu/cpu$ vvp cpu4.out
VCD info: dumpfile cpu4.vcd opened for output.
ACC = 5
