16
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Verilog自作CPU】シリーズ 最終回!

Part タイトル 内容
Part1 レジスタファイルとALU 基本部品
Part2 命令デコーダ 命令解析
Part3 メモリとロード・ストア LW/SW命令
Part4 分岐とジャンプ BEQ/JAL命令
Part5 CPU統合と動作テスト 本記事(最終回)

はじめに

ついに最終回です!これまで作ってきた部品を全て統合して、動くRISC-V CPUを完成させます。

今回やること

  1. CPUトップモジュールの実装
  2. 全コンポーネントの接続
  3. テストプログラムの実行
  4. 動作確認

CPUトップモジュール

全ての部品を接続したトップモジュール:

src/riscv_cpu.v
// RISC-V Single-Cycle CPU
module riscv_cpu (
    input         clk,
    input         reset,
    output [31:0] pc_out,
    output [31:0] instr_out
);
    // PC
    reg [31:0] pc;
    wire [31:0] pc_next;
    
    // 命令
    wire [31:0] instr;
    
    // デコード結果
    wire [4:0]  rs1, rs2, rd;
    wire [31:0] imm;
    wire        reg_write;
    wire        mem_read, mem_write;
    wire        branch, jump;
    wire        alu_src;
    wire [3:0]  alu_op;
    wire        mem_to_reg;
    
    // レジスタファイル
    wire [31:0] rs1_data, rs2_data;
    
    // ALU
    wire [31:0] alu_a, alu_b, alu_result;
    wire        zero;
    
    // データメモリ
    wire [31:0] mem_data;
    
    // ライトバック
    wire [31:0] wb_data;
    
    //=========================================
    // 命令フェッチ
    //=========================================
    imem instruction_memory (
        .addr(pc),
        .data(instr)
    );
    
    //=========================================
    // デコード
    //=========================================
    decoder instruction_decoder (
        .instr(instr),
        .rs1(rs1),
        .rs2(rs2),
        .rd(rd),
        .imm(imm),
        .reg_write(reg_write),
        .mem_read(mem_read),
        .mem_write(mem_write),
        .branch(branch),
        .jump(jump),
        .alu_src(alu_src),
        .alu_op(alu_op),
        .mem_to_reg(mem_to_reg)
    );
    
    //=========================================
    // レジスタファイル
    //=========================================
    regfile registers (
        .clk(clk),
        .we(reg_write),
        .rs1(rs1),
        .rs2(rs2),
        .rd(rd),
        .wdata(wb_data),
        .rdata1(rs1_data),
        .rdata2(rs2_data)
    );
    
    //=========================================
    // ALU
    //=========================================
    // ALU入力選択
    assign alu_a = rs1_data;
    assign alu_b = alu_src ? imm : rs2_data;
    
    alu main_alu (
        .a(alu_a),
        .b(alu_b),
        .alu_op(alu_op),
        .result(alu_result),
        .zero(zero)
    );
    
    //=========================================
    // データメモリ
    //=========================================
    dmem data_memory (
        .clk(clk),
        .addr(alu_result),
        .write_data(rs2_data),
        .write_enable(mem_write),
        .read_data(mem_data)
    );
    
    //=========================================
    // ライトバック
    //=========================================
    assign wb_data = mem_to_reg ? mem_data : alu_result;
    
    //=========================================
    // PC制御
    //=========================================
    wire branch_taken = branch & zero;
    wire [31:0] branch_target = pc + imm;
    wire [31:0] jump_target = pc + imm;
    
    assign pc_next = jump ? jump_target :
                     branch_taken ? branch_target :
                     pc + 4;
    
    // PC更新
    always @(posedge clk or posedge reset) begin
        if (reset)
            pc <= 32'd0;
        else
            pc <= pc_next;
    end
    
    // デバッグ出力
    assign pc_out = pc;
    assign instr_out = instr;
    
endmodule

テストベンチ

実際にプログラムを実行するテストベンチ:

test/riscv_cpu_tb.v
`timescale 1ns/1ps

module riscv_cpu_tb;
    reg         clk;
    reg         reset;
    wire [31:0] pc, instr;

    riscv_cpu dut (
        .clk(clk),
        .reset(reset),
        .pc_out(pc),
        .instr_out(instr)
    );

    // クロック生成
    initial clk = 0;
    always #5 clk = ~clk;

    initial begin
        $display("=== RISC-V CPU Test ===");
        $display("");
        $display("Testing simple program:");
        $display("  ADDI x1, x0, 5    ; x1 = 5");
        $display("  ADDI x2, x0, 10   ; x2 = 10");
        $display("  ADD  x3, x1, x2   ; x3 = 15");
        $display("  SUB  x4, x2, x1   ; x4 = 5");
        $display("  AND  x5, x1, x2   ; x5 = 0");
        $display("");
        
        // 命令メモリにプログラムをロード
        // ADDI x1, x0, 5
        dut.instruction_memory.memory[0] = 32'h00500093;
        // ADDI x2, x0, 10
        dut.instruction_memory.memory[1] = 32'h00a00113;
        // ADD x3, x1, x2
        dut.instruction_memory.memory[2] = 32'h002081b3;
        // SUB x4, x2, x1
        dut.instruction_memory.memory[3] = 32'h40110233;
        // AND x5, x1, x2
        dut.instruction_memory.memory[4] = 32'h0020f2b3;
        // NOP
        dut.instruction_memory.memory[5] = 32'h00000013;
        dut.instruction_memory.memory[6] = 32'h00000013;
        
        // リセット
        reset = 1;
        #20;
        reset = 0;
        
        $display("Executing instructions:");
        $display("");
        
        // 各命令を実行
        repeat (6) begin
            @(posedge clk);
            #1;
            $display("PC=0x%08x  Instr=0x%08x", pc, instr);
            $display("  x1=%d, x2=%d, x3=%d, x4=%d, x5=%d", 
                     dut.registers.registers[1],
                     dut.registers.registers[2],
                     dut.registers.registers[3],
                     dut.registers.registers[4],
                     dut.registers.registers[5]);
        end
        
        $display("");
        $display("=== Final Register State ===");
        $display("x1 = %d (expected: 5)", dut.registers.registers[1]);
        $display("x2 = %d (expected: 10)", dut.registers.registers[2]);
        $display("x3 = %d (expected: 15)", dut.registers.registers[3]);
        $display("x4 = %d (expected: 5)", dut.registers.registers[4]);
        $display("x5 = %d (expected: 0)", dut.registers.registers[5]);
        
        // 検証
        if (dut.registers.registers[1] == 5 &&
            dut.registers.registers[2] == 10 &&
            dut.registers.registers[3] == 15 &&
            dut.registers.registers[4] == 5 &&
            dut.registers.registers[5] == 0) begin
            $display("");
            $display("*** ALL TESTS PASSED! ***");
        end else begin
            $display("");
            $display("*** SOME TESTS FAILED ***");
        end
        
        #20;
        $finish;
    end

    // 波形出力
    initial begin
        $dumpfile("riscv_cpu_tb.vcd");
        $dumpvars(0, riscv_cpu_tb);
    end

endmodule

実行結果

$ cd ~/riscv-cpu
$ iverilog -o cpu_test src/regfile.v src/alu.v src/decoder.v \
           src/imem.v src/dmem.v src/riscv_cpu.v test/riscv_cpu_tb.v
$ ./cpu_test
=== RISC-V CPU Test ===

Testing simple program:
  ADDI x1, x0, 5    ; x1 = 5
  ADDI x2, x0, 10   ; x2 = 10
  ADD  x3, x1, x2   ; x3 = 15
  SUB  x4, x2, x1   ; x4 = 5
  AND  x5, x1, x2   ; x5 = 0

VCD info: dumpfile riscv_cpu_tb.vcd opened for output.
Executing instructions:

PC=0x00000004  Instr=0x00a00113
  x1=         5, x2=         0, x3=         0, x4=         0, x5=         0
PC=0x00000008  Instr=0x002081b3
  x1=         5, x2=        10, x3=         0, x4=         0, x5=         0
PC=0x0000000c  Instr=0x40110233
  x1=         5, x2=        10, x3=        15, x4=         0, x5=         0
PC=0x00000010  Instr=0x0020f2b3
  x1=         5, x2=        10, x3=        15, x4=         5, x5=         0
PC=0x00000014  Instr=0x00000013
  x1=         5, x2=        10, x3=        15, x4=         5, x5=         0
PC=0x00000018  Instr=0x00000013
  x1=         5, x2=        10, x3=        15, x4=         5, x5=         0

=== Final Register State ===
x1 =          5 (expected: 5)
x2 =         10 (expected: 10)
x3 =         15 (expected: 15)
x4 =          5 (expected: 5)
x5 =          0 (expected: 0)

*** ALL TESTS PASSED! ***

🎉 CPU完成!全テスト合格!


動作の解説

シミュレーション結果を詳しく見てみましょう:

サイクル1: ADDI x1, x0, 5

PC=0x00000004  Instr=0x00a00113
  x1=5, x2=0, x3=0, x4=0, x5=0
  • PC=0 の命令 ADDI x1, x0, 5 を実行
  • x0(=0) + 5 = 5 を x1 に書き込み
  • PC は次の命令 0x04 を指している

サイクル2: ADDI x2, x0, 10

PC=0x00000008  Instr=0x002081b3
  x1=5, x2=10, x3=0, x4=0, x5=0
  • x0 + 10 = 10 を x2 に書き込み

サイクル3: ADD x3, x1, x2

PC=0x0000000c  Instr=0x40110233
  x1=5, x2=10, x3=15, x4=0, x5=0
  • x1(=5) + x2(=10) = 15 を x3 に書き込み

サイクル4: SUB x4, x2, x1

PC=0x00000010  Instr=0x0020f2b3
  x1=5, x2=10, x3=15, x4=5, x5=0
  • x2(=10) - x1(=5) = 5 を x4 に書き込み

サイクル5: AND x5, x1, x2

PC=0x00000014
  x1=5, x2=10, x3=15, x4=5, x5=0
  • x1(=5) & x2(=10) = 0b0101 & 0b1010 = 0

全ての演算が正しく実行されました!


波形ビューア

GTKWaveで波形を見ることもできます:

$ gtkwave riscv_cpu_tb.vcd

波形イメージ

クロックに同期してPCが4ずつ増加し、レジスタの値が更新されていく様子が見えます。


完成したCPUの仕様

項目 仕様
アーキテクチャ RISC-V RV32I(サブセット)
データ幅 32ビット
レジスタ 32本(x0は常に0)
命令メモリ 1KB (256命令)
データメモリ 1KB
パイプライン なし(シングルサイクル)

サポート命令

カテゴリ 命令
算術演算 ADD, SUB, ADDI
論理演算 AND, OR, XOR, ANDI, ORI, XORI
シフト SLL, SRL, SRA, SLLI, SRLI, SRAI
比較 SLT, SLTU, SLTI, SLTIU
メモリ LW, SW
分岐 BEQ (※他の分岐命令も対応可能)
ジャンプ JAL, JALR

今後の拡張

このCPUをベースに、以下の拡張が可能です:

パイプライン化

IF → ID → EX → MEM → WB

5段パイプラインで性能向上。ただしハザード処理が必要。

キャッシュの追加

CPU ←→ L1 Cache ←→ Main Memory

メモリアクセスの高速化。

割り込み対応

CSR(Control and Status Registers)
例外処理機構

OS実装に必須。

FPGA実装

実際のFPGA(Xilinx, Intel/Alteraなど)で動かす!


まとめ

5回にわたるシリーズで、ゼロからRISC-V CPUを作りました!

Part1: レジスタファイル + ALU
Part2: 命令デコーダ
Part3: メモリアクセス
Part4: 分岐・ジャンプ
Part5: CPU統合・完成!

学んだこと

  1. CPUの基本構造 - PC, レジスタ, ALU, メモリ
  2. RISC-V命令セット - 6つの命令フォーマット
  3. Verilog記述 - モジュール設計、テストベンチ
  4. シミュレーション - iverilogによる動作確認

ファイル一覧

~/riscv-cpu/
├── src/
│   ├── regfile.v      # レジスタファイル
│   ├── alu.v          # ALU
│   ├── decoder.v      # 命令デコーダ
│   ├── imem.v         # 命令メモリ
│   ├── dmem.v         # データメモリ
│   └── riscv_cpu.v    # CPUトップモジュール
└── test/
    ├── regfile_tb.v   # テストベンチ
    ├── alu_tb.v
    ├── decoder_tb.v
    └── riscv_cpu_tb.v

おわりに

「CPUって難しそう」と思っていた方も、一つずつ部品を作って組み合わせていけば、ちゃんと動くものが作れることがわかったと思います。

このシリーズのコードは自由に使ってください。FPGAに載せたり、パイプライン化したり、自分なりの拡張を楽しんでください!

ここまで読んでいただきありがとうございました!🎉


参考資料


関連シリーズ

16
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?