演習D ★★★ 上級 所要:45–60分

Accumulator CPU

アキュムレータ(累算器)型の小型 CPU を Verilog で設計し、実際にプログラムを実行させよう。

📚 概念説明:アキュムレータ CPU の構造

アキュムレータ型 CPU はもっともシンプルな CPU アーキテクチャの一つです。 演算の中間結果をすべて ACC(アキュムレータ)レジスタに保持します。

┌────────────────────────────────────────┐ │ Accumulator CPU │ │ │ │ ┌──────┐ addr ┌──────────────┐ │ │ │ PC │───────▶│ 命令メモリ │ │ │ │(4bit)│ │ (imem[0..15])│ │ │ └──────┘ └──────┬───────┘ │ │ ▲ │ instr[7:0] │ │ │ PC+1 ▼ │ │ │ ┌──────────┐ │ │ └─────────────│ 制御部 │ │ │ │ (Decoder)│ │ │ └────┬─────┘ │ │ │ │ │ ┌──────────────┼───────────┐ │ │ ▼ ▼ ▼ │ │ ┌──────────┐ ┌──────────┐ ┌──────┐ │ │ │ ACC │ │ ALU │ │ dmem │ │ │ │ (8 bit) │──▶│ (+/-/and)│ │(data)│ │ │ └──────────┘ └────┬─────┘ └──────┘ │ │ ▲ │ │ │ └─────────────┘ │ └────────────────────────────────────────┘

主要コンポーネント

  • PC(Program Counter):次に実行する命令のアドレス
  • ACC(Accumulator):演算の中間結果を保持するレジスタ
  • imem:命令メモリ(プログラムを格納)
  • dmem:データメモリ(変数を格納)
  • ALU:算術・論理演算を行う回路

命令フォーマット(8ビット)

[7:6] opcode(2ビット)
[5:0] address(6ビット,データメモリアドレス)
00 xxxxxx → LOAD ACC ← dmem[addr]
01 xxxxxx → STORE dmem[addr] ← ACC
10 xxxxxx → ADD ACC ← ACC + dmem[addr]
11 000000 → HALT 停止
実行サイクル(フェッチ → デコード → 実行):
1. Fetch:imem[PC] から命令を読み出す
2. Decode:opcode で命令を解釈する
3. Execute:ACC / dmem を更新し、PC を PC+1 にする

📖 サンプルプログラム

データメモリに dmem[0]=5, dmem[1]=3 を置き、足し算して dmem[2] に保存するプログラム:

アドレス 命令(2進数) ニーモニック 動作
000_000000LOAD 0ACC ← dmem[0] (= 5)
110_000001ADD 1ACC ← ACC + dmem[1] (= 5+3 = 8)
201_000010STORE 2dmem[2] ← ACC (= 8)
311_000000HALT停止

💻 Verilog テンプレート

Design(右パネル)
// ================================================
// Accumulator CPU(シングルサイクル実装)
// 命令:LOAD=00, STORE=01, ADD=10, HALT=11
// ================================================
module cpu_accum (
    input        clk,
    input        rst,
    output [3:0] pc_out,
    output [7:0] acc_out,
    output       halted
);

    // ---------- 命令メモリ(プログラム) ----------
    reg [7:0] imem [0:15];
    // ---------- データメモリ -----------------------
    reg [7:0] dmem [0:63];
    // ---------- レジスタ ---------------------------
    reg [3:0] pc;
    reg [7:0] acc;
    reg       halt_reg;

    assign pc_out  = pc;
    assign acc_out = acc;
    assign halted  = halt_reg;

    // ---------- 命令デコード ----------------------
    wire [7:0] instr  = imem[pc];
    wire [1:0] opcode = instr[7:6];
    wire [5:0] addr   = instr[5:0];

    // ---------- 初期化 ----------------------------
    initial begin
        // プログラム(5 + 3 を計算し dmem[2] に格納)
        imem[0] = 8'b00_000000; // LOAD  dmem[0]
        imem[1] = 8'b10_000001; // ADD   dmem[1]
        imem[2] = 8'b01_000010; // STORE dmem[2]
        imem[3] = 8'b11_000000; // HALT

        // データ初期値
        dmem[0] = 8'd5;
        dmem[1] = 8'd3;
        dmem[2] = 8'd0;
    end

    // ---------- 実行ループ(クロック同期) ---------
    always @(posedge clk or posedge rst) begin
        if (rst) begin
            pc       <= 4'd0;
            acc      <= 8'd0;
            halt_reg <= 1'b0;
        end else if (!halt_reg) begin
            case (opcode)
                2'b00: begin // LOAD
                    acc <= dmem[addr];
                    pc  <= pc + 1;
                end
                2'b01: begin // STORE
                    dmem[addr] <= acc;
                    pc         <= pc + 1;
                end
                2'b10: begin // ADD
                    acc <= acc + dmem[addr];
                    pc  <= pc + 1;
                end
                2'b11: begin // HALT
                    halt_reg <= 1'b1;
                end
            endcase
        end
    end

endmodule
Testbench(左パネル)
module tb_cpu_accum;
    reg        clk, rst;
    wire [3:0] pc_out;
    wire [7:0] acc_out;
    wire       halted;

    cpu_accum uut (.clk(clk), .rst(rst),
                    .pc_out(pc_out), .acc_out(acc_out), .halted(halted));

    // クロック生成(10ns周期)
    initial clk = 0;
    always #5 clk = ~clk;

    initial begin
        $dumpfile("dump.vcd");
        $dumpvars(0, tb_cpu_accum);
        $monitor("t=%0t | pc=%0d acc=%0d halted=%0d",
                  $time, pc_out, acc_out, halted);

        // リセット
        rst = 1; #12;
        rst = 0;

        // HALT になるまで待機(最大20クロック)
        repeat(20) @(posedge clk);

        $display("最終 ACC = %0d(期待値 = 8)", acc_out);
        $display("--- シミュレーション完了 ---");
        $finish;
    end
endmodule

▶ EDA Playground で開く

PC の変化・ACC の変化・HALT 信号の立ち上がりを波形で確認してみましょう。

🚀 EDA Playground を開く →

※ ログインに Gmail が必要です。 使い方ガイドを見る

🚀 発展課題

余裕のある人はぜひ挑戦してみよう。

① SUB 命令を追加する

opcode 3'b10 → ADD の代わりに SUB(ACC - dmem[addr])を実装してみよう。

② JMP 命令を追加する

指定したアドレスにジャンプする命令 JMP addr を追加。PC を addr にセットすると実現できる。

③ BEQ 命令を追加する(条件分岐)

ACC == 0 のとき指定アドレスにジャンプする BEQ 命令を実装しよう。ループプログラムが書けるようになる。

④ 乗算プログラムをアセンブリで書く

LOAD / STORE / ADD の命令だけを使って、3 × 4 = 12 を計算するプログラムを書いてみよう(ループを使う)。

🤔 考えてみよう

  1. シミュレーションで PC がどのように変化するか確認しよう。LOAD → ADD → STORE → HALT の順に pc_out が 0→1→2→3 になりますか?
  2. このCPUの「1クロック」で完了する処理のサイクルを「シングルサイクル」といいます。実際のCPU(マルチサイクル・パイプライン)と何が違うでしょうか?
  3. 現代の CPU(例:ARM Cortex-A)と比べて、このアキュムレータCPUに足りないものは何でしょうか?
  4. データメモリのアドレスが 6ビット(0〜63)に制限されているのはなぜですか?命令フォーマットと関係しています。
← 演習C:ALU 演習2 トップへ →