computer system foundationCSF 2026
演習 E / ACCUMULATOR CPU

Accumulator CPU

A〜D で作った部品を組み合わせ、命令を1つずつ実行する最小の CPU を完成させる。あなたが書くのは「各命令が状態をどう変えるか」である。

難易度 ★★★ 所要 45–60分 前提 演習A・D
01

アキュムレータ型という設計

もっとも単純な CPU の形のひとつである。計算の途中結果を置く場所が ACC(アキュムレータ)ひとつしかなく、 すべての演算が「ACC と、メモリから読んだ値」の間で行われる。レジスタがたくさんある現代の CPU と違い、 状態が ACC と PC(次に実行する命令の番地)の2つだけなので、動きを全部追いかけられる。

PC 4 bit 命令メモリ imem [0:15] addr instr[7:0] 制御部(デコーダ) この演習で書く部分 next_acc / mem_we / halt PC + 1 制御信号 ACC 8 bit ALU 演習D で作った回路 dmem [0:63] acc next_acc mem_data STORE のとき ACC をメモリへ書き込む
データの流れ 制御信号 この演習で書く部分
02

4つの命令だけの命令セット

命令は8ビット。上位2ビットが opcode(何をするか)、下位6ビットが address(どのデータを使うか)である。

opcode命令動作ACC はどうなる
00LOADACC ← dmem[addr]メモリの値で置き換わる
01STOREdmem[addr] ← ACC変わらない(書き出すだけ)
10ADDACC ← ACC + dmem[addr]足された値になる
11HALT停止変わらない
1クロックで「命令を読む → 解読する → 実行する」を全部行う(シングルサイクル)。 現代の CPU がこれを何段にも分けているのが、第3章で学ぶパイプラインである。
03

動かすプログラム:5 + 3

dmem[0]=5, dmem[1]=3 を置いた状態で、次の4命令を順に実行すると dmem[2] に 8 が書き込まれる。

番地機械語ニーモニック実行後の ACC
000_000000LOAD 05
110_000001ADD 18
201_000010STORE 28(dmem[2] に 8 を書く)
311_000000HALT8(停止)
04

制御部を書いて、その場でチェック

CPU のうち、PC の更新やメモリの読み書きといった配線はすでにできている。残っているのは 制御部(デコーダ)——「いま読んだ opcode に対して、次の ACC は何になるか、メモリに書くか、止まるか」を決める部分である。 これは組み合わせ回路なので、ここでチェックできる。

opcode
いま実行中の命令の上位2ビット
acc
現在の ACC の値
mem_data
dmem[addr] から読んだ値
next_acc
次のクロックで ACC に入る値
mem_we
dmem に書き込むなら 1(STORE のときだけ)
halt
停止するなら 1
module cpu_decode (
    input      [1:0] opcode,
    input      [7:0] acc,
    input      [7:0] mem_data,
    output reg [7:0] next_acc,
    output reg       mem_we,
    output reg       halt
);
endmodule
Ctrl / ⌘ + Enter でも実行
答え合わせ(自分で試してから開く)
always @(*) begin
    // まず「何もしない」を既定値にしておく
    next_acc = acc;
    mem_we   = 1'b0;
    halt     = 1'b0;

    case (opcode)
        2'b00: next_acc = mem_data;            // LOAD
        2'b01: mem_we   = 1'b1;                // STORE
        2'b10: next_acc = acc + mem_data;      // ADD
        2'b11: halt     = 1'b1;                // HALT
    endcase
end

最初に既定値を書いておく書き方は、CPU の制御部でよく使われる。 「その命令が変えないものは、そのまま」という意味になり、case の各行が短くなる。

05

CPU 全体のコード(参考)

上で書いた制御部を、PC・メモリ・クロックと組み合わせると CPU 全体になる。 クロックで状態が変わる部分(always @(posedge clk))は、ブラウザのチェッカーでは扱えないため、 動かしてみたい人は EDA Playground を使ってください。

module cpu_accum (
    input clk, input rst,
    output [3:0] pc_out, output [7:0] acc_out, output halted
);
    reg [3:0] pc;
    reg [7:0] acc;
    reg [7:0] imem [0:15];   // 命令メモリ
    reg [7:0] dmem [0:63];   // データメモリ

    wire [7:0] instr  = imem[pc];
    wire [1:0] opcode = instr[7:6];
    wire [5:0] addr   = instr[5:0];

    // ← ここが 04 で書いた制御部(next_acc / mem_we / halt)
    wire [7:0] next_acc;
    wire mem_we, halt;
    cpu_decode dec (.opcode(opcode), .acc(acc), .mem_data(dmem[addr]),
                     .next_acc(next_acc), .mem_we(mem_we), .halt(halt));

    // クロックで状態を進める部分(順序回路)
    always @(posedge clk) begin
        if (rst) begin
            pc  <= 4'd0;
            acc <= 8'd0;
        end else if (!halt) begin
            acc <= next_acc;
            if (mem_we) dmem[addr] <= acc;
            pc <= pc + 4'd1;
        end
    end

    assign pc_out  = pc;
    assign acc_out = acc;
    assign halted  = halt;
endmodule
06

発展課題

  1. SUB 命令に変える:opcode 10 を ADD ではなく SUB(ACC − dmem[addr])にしてみましょう。 演習Bで作った引き算がそのまま使える。チェッカーは ADD を期待しているので失敗するが、 「どのテストがどう失敗するか」を見れば、命令を1つ変えると機械の意味がどう変わるかが分かる。
  2. JMP 命令を考える:PC を addr にセットする命令を足すには、制御部にどんな出力を追加すればよいでしょうか。 いまの制御部は next_acc / mem_we / halt しか出していない。
  3. 命令の下位6ビットがアドレスなので、データメモリは何番地まで使えますか。これは命令フォーマットのどこで決まっていますか。
  4. この CPU と、演習3のミニ CPU シミュレーター(レジスタが複数ある型)を比べて、どちらがどんな場面で有利でしょうか。
発展:本物のツール(EDA Playground)で CPU 全体を動かす 任意

クロックを含む CPU 全体は、EDA Playground で動かせる(Gmail アカウントが必要)。 上の 05 のコードと、下のテストベンチを貼り付けてください。手順は 使い方ガイド へ。

TESTBENCH(左パネル)
module tb_cpu_accum;
    reg clk = 0, rst = 1;
    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));

    always #5 clk = ~clk;

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

        // プログラムとデータを載せる
        uut.imem[0] = 8'b00_000000;  // LOAD 0
        uut.imem[1] = 8'b10_000001;  // ADD 1
        uut.imem[2] = 8'b01_000010;  // STORE 2
        uut.imem[3] = 8'b11_000000;  // HALT
        uut.dmem[0] = 8'd5;
        uut.dmem[1] = 8'd3;

        #12 rst = 0;
        wait (halted);
        #10;
        $display("ACC = %0d, dmem[2] = %0d", acc_out, uut.dmem[2]);
        $finish;
    end
endmodule