18
0

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統合と動作テスト 完成

はじめに

前回はレジスタファイルとALUを作りました。今回は命令デコーダを実装します。

命令デコーダは、32ビットの機械語命令を受け取って、「どのレジスタを使うか」「どの演算をするか」「即値はいくつか」などの制御信号を生成する重要な部品です。


RISC-V命令フォーマット

RISC-Vの命令は全て32ビット固定長で、6つのタイプがあります。

命令タイプ一覧

R-type: レジスタ間演算(ADD, SUB, AND, OR...)
┌───────┬─────┬─────┬─────┬────┬─────────┐
│funct7 │ rs2 │ rs1 │func3│ rd │ opcode  │
│ 31-25 │24-20│19-15│14-12│11-7│  6-0    │
└───────┴─────┴─────┴─────┴────┴─────────┘

I-type: 即値演算・ロード(ADDI, LW, JALR...)
┌────────────┬─────┬─────┬────┬─────────┐
│  imm[11:0] │ rs1 │func3│ rd │ opcode  │
│   31-20    │19-15│14-12│11-7│  6-0    │
└────────────┴─────┴─────┴────┴─────────┘

S-type: ストア(SW, SH, SB)
┌────────┬─────┬─────┬─────┬────────┬─────────┐
│imm[11:5]│ rs2 │ rs1 │func3│imm[4:0]│ opcode  │
│  31-25  │24-20│19-15│14-12│  11-7  │  6-0    │
└────────┴─────┴─────┴─────┴────────┴─────────┘

B-type: 分岐(BEQ, BNE, BLT...)
┌───┬────────┬─────┬─────┬─────┬────────┬───┬─────────┐
│[12]│[10:5]  │ rs2 │ rs1 │func3│ [4:1]  │[11]│ opcode  │
│ 31 │ 30-25  │24-20│19-15│14-12│ 11-8   │ 7 │  6-0    │
└───┴────────┴─────┴─────┴─────┴────────┴───┴─────────┘

U-type: 上位即値(LUI, AUIPC)
┌──────────────────────┬────┬─────────┐
│      imm[31:12]      │ rd │ opcode  │
│        31-12         │11-7│  6-0    │
└──────────────────────┴────┴─────────┘

J-type: ジャンプ(JAL)
┌───┬──────────┬───┬────────┬────┬─────────┐
│[20]│ [10:1]   │[11]│[19:12] │ rd │ opcode  │
│ 31 │  30-21   │ 20 │ 19-12  │11-7│  6-0    │
└───┴──────────┴───┴────────┴────┴─────────┘

オペコード一覧

opcode 命令タイプ
0110011 R-type ADD, SUB, AND, OR
0010011 I-type ADDI, ANDI, ORI
0000011 Load LW, LH, LB
0100011 S-type SW, SH, SB
1100011 B-type BEQ, BNE, BLT
0110111 U-type LUI
0010111 U-type AUIPC
1101111 J-type JAL
1100111 I-type JALR

デコーダの実装

出力する制御信号

デコーダは以下の情報を出力します:

信号 説明
rs1, rs2, rd レジスタアドレス
imm 即値(符号拡張済み)
reg_write レジスタ書き込み有効
mem_read メモリ読み出し
mem_write メモリ書き込み
branch 分岐命令
jump ジャンプ命令
alu_src ALU第2オペランド選択(0:rs2, 1:imm)
alu_op ALU演算コード
mem_to_reg ライトバック選択(0:ALU結果, 1:メモリ)

Verilog実装

src/decoder.v
// RISC-V 命令デコーダ

module decoder (
    input  wire [31:0] instr,
    
    // レジスタアドレス
    output wire [4:0]  rs1,
    output wire [4:0]  rs2,
    output wire [4:0]  rd,
    
    // 即値
    output reg  [31:0] imm,
    
    // 制御信号
    output reg         reg_write,
    output reg         mem_read,
    output reg         mem_write,
    output reg         branch,
    output reg         jump,
    output reg         alu_src,
    output reg  [3:0]  alu_op,
    output reg         mem_to_reg
);

    // 命令フィールドの抽出
    wire [6:0] opcode = instr[6:0];
    wire [2:0] funct3 = instr[14:12];
    wire [6:0] funct7 = instr[31:25];
    
    assign rs1 = instr[19:15];
    assign rs2 = instr[24:20];
    assign rd  = instr[11:7];
    
    // オペコード定義
    localparam OP_R      = 7'b0110011;  // R-type
    localparam OP_I_ALU  = 7'b0010011;  // I-type ALU
    localparam OP_LOAD   = 7'b0000011;  // Load
    localparam OP_STORE  = 7'b0100011;  // Store
    localparam OP_BRANCH = 7'b1100011;  // Branch
    localparam OP_JAL    = 7'b1101111;  // JAL
    localparam OP_JALR   = 7'b1100111;  // JALR
    localparam OP_LUI    = 7'b0110111;  // LUI
    localparam OP_AUIPC  = 7'b0010111;  // AUIPC
    
    // ALU操作コード
    localparam ALU_ADD  = 4'b0000;
    localparam ALU_SUB  = 4'b0001;
    localparam ALU_AND  = 4'b0010;
    localparam ALU_OR   = 4'b0011;
    localparam ALU_XOR  = 4'b0100;
    localparam ALU_SLL  = 4'b0101;
    localparam ALU_SRL  = 4'b0110;
    localparam ALU_SRA  = 4'b0111;
    localparam ALU_SLT  = 4'b1000;
    localparam ALU_SLTU = 4'b1001;

    // 即値生成
    always @(*) begin
        case (opcode)
            OP_I_ALU, OP_LOAD, OP_JALR:
                // I-type: 符号拡張
                imm = {{20{instr[31]}}, instr[31:20]};
            
            OP_STORE:
                // S-type: 符号拡張
                imm = {{20{instr[31]}}, instr[31:25], instr[11:7]};
            
            OP_BRANCH:
                // B-type: 符号拡張、LSBは0
                imm = {{19{instr[31]}}, instr[31], instr[7], 
                       instr[30:25], instr[11:8], 1'b0};
            
            OP_LUI, OP_AUIPC:
                // U-type: 上位20ビット
                imm = {instr[31:12], 12'b0};
            
            OP_JAL:
                // J-type: 符号拡張、LSBは0
                imm = {{11{instr[31]}}, instr[31], instr[19:12], 
                       instr[20], instr[30:21], 1'b0};
            
            default:
                imm = 32'h0;
        endcase
    end

    // ALU操作コード生成
    always @(*) begin
        case (opcode)
            OP_R: begin
                case (funct3)
                    3'b000: alu_op = (funct7[5]) ? ALU_SUB : ALU_ADD;
                    3'b001: alu_op = ALU_SLL;
                    3'b010: alu_op = ALU_SLT;
                    3'b011: alu_op = ALU_SLTU;
                    3'b100: alu_op = ALU_XOR;
                    3'b101: alu_op = (funct7[5]) ? ALU_SRA : ALU_SRL;
                    3'b110: alu_op = ALU_OR;
                    3'b111: alu_op = ALU_AND;
                    default: alu_op = ALU_ADD;
                endcase
            end
            
            OP_I_ALU: begin
                case (funct3)
                    3'b000: alu_op = ALU_ADD;   // ADDI
                    3'b001: alu_op = ALU_SLL;   // SLLI
                    3'b010: alu_op = ALU_SLT;   // SLTI
                    3'b011: alu_op = ALU_SLTU;  // SLTIU
                    3'b100: alu_op = ALU_XOR;   // XORI
                    3'b101: alu_op = (funct7[5]) ? ALU_SRA : ALU_SRL;
                    3'b110: alu_op = ALU_OR;    // ORI
                    3'b111: alu_op = ALU_AND;   // ANDI
                    default: alu_op = ALU_ADD;
                endcase
            end
            
            OP_BRANCH: begin
                // 分岐は減算で比較
                alu_op = ALU_SUB;
            end
            
            default: begin
                // ロード/ストア/ジャンプはアドレス計算(ADD)
                alu_op = ALU_ADD;
            end
        endcase
    end

    // 制御信号生成
    always @(*) begin
        // デフォルト値
        reg_write  = 1'b0;
        mem_read   = 1'b0;
        mem_write  = 1'b0;
        branch     = 1'b0;
        jump       = 1'b0;
        alu_src    = 1'b0;
        mem_to_reg = 1'b0;
        
        case (opcode)
            OP_R: begin
                reg_write = 1'b1;
                alu_src   = 1'b0;  // rs2
            end
            
            OP_I_ALU: begin
                reg_write = 1'b1;
                alu_src   = 1'b1;  // 即値
            end
            
            OP_LOAD: begin
                reg_write  = 1'b1;
                mem_read   = 1'b1;
                alu_src    = 1'b1;
                mem_to_reg = 1'b1;
            end
            
            OP_STORE: begin
                mem_write = 1'b1;
                alu_src   = 1'b1;
            end
            
            OP_BRANCH: begin
                branch  = 1'b1;
                alu_src = 1'b0;
            end
            
            OP_JAL: begin
                reg_write = 1'b1;
                jump      = 1'b1;
            end
            
            OP_JALR: begin
                reg_write = 1'b1;
                jump      = 1'b1;
                alu_src   = 1'b1;
            end
            
            OP_LUI: begin
                reg_write = 1'b1;
                alu_src   = 1'b1;
            end
            
            OP_AUIPC: begin
                reg_write = 1'b1;
                alu_src   = 1'b1;
            end
        endcase
    end

endmodule

テストベンチ

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

module decoder_tb;
    reg  [31:0] instr;
    wire [4:0]  rs1, rs2, rd;
    wire [31:0] imm;
    wire        reg_write, mem_read, mem_write;
    wire        branch, jump, alu_src, mem_to_reg;
    wire [3:0]  alu_op;
    
    decoder dut (
        .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)
    );
    
    initial begin
        $display("=== Decoder Test ===\n");
        
        // Test 1: ADDI x1, x0, 5
        // 000000000101 00000 000 00001 0010011
        instr = 32'h00500093;
        #10;
        $display("Test 1: ADDI x1, x0, 5");
        $display("  rd=%d, rs1=%d, imm=%d", rd, rs1, imm);
        $display("  reg_write=%d, alu_src=%d, alu_op=%b", 
                 reg_write, alu_src, alu_op);
        
        // Test 2: ADD x3, x1, x2
        // 0000000 00010 00001 000 00011 0110011
        instr = 32'h002080b3;
        #10;
        $display("\nTest 2: ADD x3, x1, x2");
        $display("  rd=%d, rs1=%d, rs2=%d", rd, rs1, rs2);
        $display("  reg_write=%d, alu_src=%d, alu_op=%b",
                 reg_write, alu_src, alu_op);
        
        // Test 3: SUB x4, x2, x1
        // 0100000 00001 00010 000 00100 0110011
        instr = 32'h40110233;
        #10;
        $display("\nTest 3: SUB x4, x2, x1");
        $display("  rd=%d, rs1=%d, rs2=%d", rd, rs1, rs2);
        $display("  alu_op=%b (expected: 0001 for SUB)", alu_op);
        
        // Test 4: LW x5, 4(x1)
        // 000000000100 00001 010 00101 0000011
        instr = 32'h0040a283;
        #10;
        $display("\nTest 4: LW x5, 4(x1)");
        $display("  rd=%d, rs1=%d, imm=%d", rd, rs1, imm);
        $display("  mem_read=%d, mem_to_reg=%d", mem_read, mem_to_reg);
        
        // Test 5: SW x2, 8(x1)
        // 0000000 00010 00001 010 01000 0100011
        instr = 32'h0020a423;
        #10;
        $display("\nTest 5: SW x2, 8(x1)");
        $display("  rs1=%d, rs2=%d, imm=%d", rs1, rs2, imm);
        $display("  mem_write=%d", mem_write);
        
        // Test 6: BEQ x1, x2, 16
        // 0000000 00010 00001 000 1000 0 1100011
        instr = 32'h00208863;
        #10;
        $display("\nTest 6: BEQ x1, x2, offset=16");
        $display("  rs1=%d, rs2=%d, imm=%d", rs1, rs2, imm);
        $display("  branch=%d", branch);
        
        $display("\n*** ALL TESTS COMPLETED ***");
        $finish;
    end
endmodule

実行結果

$ iverilog -o decoder_test src/decoder.v test/decoder_tb.v
$ ./decoder_test
=== Decoder Test ===

Test 1: ADDI x1, x0, 5
  rd= 1, rs1= 0, imm=          5
  reg_write=1, alu_src=1, alu_op=0000

Test 2: ADD x3, x1, x2
  rd= 3, rs1= 1, rs2= 2
  reg_write=1, alu_src=0, alu_op=0000

Test 3: SUB x4, x2, x1
  rd= 4, rs1= 2, rs2= 1
  alu_op=0001 (expected: 0001 for SUB)

Test 4: LW x5, 4(x1)
  rd= 5, rs1= 1, imm=          4
  mem_read=1, mem_to_reg=1

Test 5: SW x2, 8(x1)
  rs1= 1, rs2= 2, imm=          8
  mem_write=1

Test 6: BEQ x1, x2, offset=16
  rs1= 1, rs2= 2, imm=         16
  branch=1

*** ALL TESTS COMPLETED ***

すべての命令が正しくデコードされています!


機械語の確認方法

「機械語ってどうやって作るの?」と思った方へ。

GCCを使う方法

# RISC-Vツールチェーンをインストール
sudo apt install gcc-riscv64-unknown-elf

# アセンブリから機械語を生成
echo "addi x1, x0, 5" | riscv64-unknown-elf-as -o test.o
riscv64-unknown-elf-objdump -d test.o

手動で計算する方法

例:ADDI x1, x0, 5 の場合

命令: I-type
opcode: 0010011 (I-type ALU)
rd:     00001   (x1)
funct3: 000     (ADDI)
rs1:    00000   (x0)
imm:    000000000101 (5)

結合: 000000000101_00000_000_00001_0010011
16進: 0x00500093

まとめ

Part2では、命令デコーダを実装しました:

  • RISC-Vの6つの命令フォーマット(R, I, S, B, U, J)に対応
  • 即値の符号拡張を正しく処理
  • ALU操作コードと制御信号を生成

次回Part3では、メモリモジュールを実装し、ロード・ストア命令が使えるようにします。

Part1 [完了] レジスタファイル + ALU
Part2 [完了] 命令デコーダ
Part3 [次回] メモリアクセス
Part4 [予定] 分岐・ジャンプ
Part5 [予定] 統合テスト

次回: 【Verilogで自作CPU】RISC-Vプロセッサを作る Part3 - メモリアクセス

18
0
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
18
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?