Home > Study > CPU Design > CODE > RV32I_R_Type

RV32I_R_Type
Code CPU Design

RV32I_R_Type


✅ DataPath.sv

`timescale 1ns / 1ps

module DataPath (
    input  logic        clk,
    input  logic        reset,
    input  logic [31:0] instrCode,
    input  logic        regFileWe,
    input  logic [ 3:0] aluControl,
    output logic [31:0] instrMemAddr
);

    logic [31:0] aluResult;
    logic [31:0] RFData1, RFData2;
    logic [31:0] PCSrcData, PCOutData;

    assign instrMemAddr = PCOutData;

    RegisterFile U_RegFile (
        .clk       (clk),
        .we        (regFileWe),
        .RA1       (instrCode[19:15]),
        .RA2       (instrCode[24:20]),
        .WA        (instrCode[11: 7]),
        .WD        (aluResult),
        .RD1       (RFData1),
        .RD2       (RFData2)
    );

    alu U_ALU (
        .aluControl(aluControl),
        .a         (RFData1),
        .b         (RFData2),
        .result    (aluResult)
    );

    register U_PC (
        .clk       (clk),
        .reset     (reset),
        .en        (1'b1),
        .d         (PCSrcData),
        .q         (PCOutData)   
    );

    adder U_PC_Adder(
        .a         (32'd4),
        .b         (PCOutData),
        .y         (PCSrcData)
    );

endmodule

module alu (
    input  logic  [ 3:0] aluControl,
    input  logic  [31:0] a,
    input  logic  [31:0] b,
    output logic  [31:0] result
    );

    always_comb begin
        result = 32'bx;
        case (aluControl)
            4'b0000: result = a + b;            // add
            4'b0001: result = a - b;            // sub
            4'b0010: result = a & b;            // and
            4'b0011: result = a | b;            // or
            4'b0100: result = a << b;           // sll
            4'b0101: result = a >> b;           // srl
            4'b0110: result = $signed(a) >>> b; // sra
            4'b0111: result = ($signed(a) < $signed(b)) ? 1 : 0;  // slt
            4'b1000: result = (a < b) ? 1 : 0;  // sltu
            4'b1001: result = a ^ b;            // xor
        endcase
    end
    
    endmodule

module RegisterFile (
    input  logic        clk,
    input  logic        we,
    input  logic [ 4:0] RA1,
    input  logic [ 4:0] RA2,
    input  logic [ 4:0] WA,
    input  logic [31:0] WD,
    output logic [31:0] RD1,
    output logic [31:0] RD2
    );
    
    logic [31:0] mem [0:2**5-1];

    always_ff @(posedge clk) begin
        if(we) begin
            mem[WA] <= WD;
        end
    end

    assign RD1 = (RA1 != 0) ? mem[RA1] : 32'b0;
    assign RD2 = (RA2 != 0) ? mem[RA2] : 32'b0;
    
    endmodule

module register (
    input  logic        clk,
    input  logic        reset,
    input  logic        en,
    input  logic [31:0] d,
    output logic [31:0] q    
    );

    always_ff @(posedge clk or posedge reset) begin
        if(reset) begin
            q <= 0;
        end
        else begin
            if(en) begin
                q <= d;
            end
        end
    end
    endmodule

module adder (
    input  logic [31:0] a,
    input  logic [31:0] b,
    output logic [31:0] y
    );
    
    assign y = a + b;

    endmodule

Test를 위한 regFile 수정

module RegisterFile (
    input  logic        clk,
    input  logic        we,
    input  logic [ 4:0] RA1,
    input  logic [ 4:0] RA2,
    input  logic [ 4:0] WA,
    input  logic [31:0] WD,
    output logic [31:0] RD1,
    output logic [31:0] RD2
    );
    
    logic [31:0] mem [0:2**5-1];

    initial begin   // for Simulation Test
        for (int i = 0; i < 32; i++) begin
            mem[i] = 10 + i;
        end
    end

    always_ff @(posedge clk) begin
        if(we) begin
            mem[WA] <= WD;
        end
    end

    assign RD1 = (RA1 != 0) ? mem[RA1] : 32'b0;
    assign RD2 = (RA2 != 0) ? mem[RA2] : 32'b0;
    
    endmodule

✅ ControlUnit.sv

Single-Cycle은 combinational logic으로 구현.

wire로 선언한 이유?

=> logic으로는 {instrCode[30], instrCode[14:12]}이게 안됌.

=> 사용하려면 assign으로 연결

logic [6:0] opcode;
logic [3:0] operator;
assign opcode = instrCode[6:0];
assign operator = {instrCode[30], instrCode[14:12]};
`timescale 1ns / 1ps

module ControlUnit (
    input  logic [31:0] instrCode,
    output logic        regFileWe,
    output logic [ 3:0] aluControl
    ); 

    wire [6:0] opcode = instrCode[6:0];
    wire [3:0] operator = {instrCode[30], instrCode[14:12]}; // function

    always_comb begin
        regFileWe = 1'b0;
        case (opcode)
            7'b0110011: regFileWe = 1'b1;   // R-Type
        endcase
    end

    always_comb begin
        aluControl = 2'bx;
        case (opcode)
            7'b0110011: begin   // R-Type
                aluControl = 2'bx;
                case (operator)
                    4'b0000: aluControl = 4'b0000;    // ADD
                    4'b1000: aluControl = 4'b0001;    // SUB
                    4'b0111: aluControl = 4'b0010;    // AND
                    4'b0110: aluControl = 4'b0011;    // OR
                    4'b0001: aluControl = 4'b0100;    // SLL 
                    4'b0101: aluControl = 4'b0101;    // SRL 
                    4'b1101: aluControl = 4'b0110;    // SRA
                    4'b0010: aluControl = 4'b0111;    // SLT 
                    4'b0011: aluControl = 4'b1000;    // SLTU
                    4'b0100: aluControl = 4'b1001;    // XOR
                endcase
            end
        endcase
    end

endmodule

✅ ROM.sv

4의 배수로 나눈 값이랑 같다.

`timescale 1ns / 1ps

module ROM (
    input  logic [31:0] addr,
    output logic [31:0] data    
    );
    
    logic [31:0] rom[0:61];

    assign data = rom[addr[31:2]];

endmodule

어셈블리어에 대한 머신 코드이다.

`timescale 1ns / 1ps

module ROM (
    input  logic [31:0] addr,
    output logic [31:0] data    
    );
    
    logic [31:0] rom[0:61];

    initial begin   // for Simulation Test
        // rom[x] = 32'b funct7 _ rs2 _ rs1 _ funct3 _ rd _ op
        // 어셈블리어에 대한 머신 코드이다.
        rom[0] = 32'b0000000_00001_00010_000_01000_0110011; // add  x8, x2, x1
        rom[1] = 32'b0100000_00010_00001_000_01001_0110011; // sub  x9, x1, x2
        rom[2] = 32'b0000000_00100_00011_111_01010_0110011; // and  x10, x3, x4
        rom[3] = 32'b0000000_00011_00100_110_01011_0110011; // or   x11, x4, x3
        rom[4] = 32'b0000000_00101_00001_001_01100_0110011; // sll  x12, x1, x5
        rom[5] = 32'b0000000_00101_00100_101_01101_0110011; // srl  x13, x4, x5
        rom[6] = 32'b0100000_00101_00100_101_01110_0110011; // sra  x14, x4, x5
        rom[7] = 32'b0000000_00010_00100_010_01111_0110011; // slt  x15, x4, x2
        rom[8] = 32'b0000000_00000_00011_011_10000_0110011; // sltu x16, x3, x0
        rom[9] = 32'b0000000_00100_00011_100_10001_0110011; // xor  x17, x3, x4
    end

    assign data = rom[addr[31:2]];

endmodule

✅ MCU.sv

`timescale 1ns / 1ps

module MCU(
    input logic clk,
    input logic reset  
    );

    logic [31:0] instrMemAddr, instrCode;

    CPU_RV32I U_CPU_RV32I (
        .clk            (clk),
        .reset          (reset),
        .instrCode      (instrCode),
        .instrMemAddr   (instrMemAddr)
    );

    ROM U_ROM(
        .addr   (instrMemAddr),
        .data   (instrCode)
    );

endmodule

✅ TestBench

`timescale 1ns / 1ps

module tb_RV32I();

    logic clk;
    logic reset;

    MCU U_DUT (.*);

    always#5 clk = ~clk;

    initial begin
        clk = 0;
        reset = 1;
        #20;
        reset = 0;
        #60;
        $finish();
    end

endmodule

✅ 자동 검증 TestBench

`timescale 1ns / 1ps

`include "../../sources_1/new/opcode.vh"
`include "../../sources_1/new/mem_path.vh"

module tb_RV32I();

    logic clk;
    logic reset;

    MCU U_MCU (
      .clk  (clk),
      .reset(reset)
    );

    always#5 clk = ~clk;

    task init;
        int i;
        for (int i = 0; i < 62; i++) begin
            `INSTR_PATH.rom[i] = 32'h00000000;
        end
        for (int i = 0; i < 32; i++) begin
            `RF_PATH.mem[i] = 32'h00000000;
        end
    endtask

    task reset_cpu; 
        repeat(3) begin
            @(posedge clk);
            reset = 1;
        end
        @(posedge clk);
        reset = 0;
    endtask

    logic [31:0] cycle;
    logic done;
    logic [31:0]  current_test_id = 0;
    logic [255:0] current_test_type;
    logic [31:0]  current_output;
    logic [31:0]  current_result;
    logic all_tests_passed = 0;

    wire [31:0] timeout_cycle = 25;

    initial begin
        while (all_tests_passed === 0) begin
        @(posedge clk);
            if (cycle === timeout_cycle) begin
                $display("[Failed] Timeout at [%d] test %s, expected_result = %h, got = %h", current_test_id, current_test_type, current_result, current_output);
                $finish();
            end
        end
    end

    always_ff @(posedge clk) begin
        if (done === 0) cycle <= cycle + 1;
        else cycle <= 0;
    end

    task check_result (input logic [4:0] addr, input [31:0] expect_value, input [255:0] test_type);
        done = 0;
        current_test_id   = current_test_id + 1;
        current_test_type = test_type;
        current_result    = expect_value;
        while (`RF_PATH.mem[addr] !== expect_value) begin
            current_output = `RF_PATH.mem[addr];
            @(posedge clk);
        end
        cycle = 0;
        done = 1;
        $display("[%d] Test %s passed!", current_test_id, test_type);
    endtask

    logic [ 4:0] RS0, RS1, RS2, RS3, RS4, RS5;
    logic [31:0] RD0, RD1, RD2, RD3, RD4, RD5;

    initial begin
        clk = 0;
        reset = 1;
        #10;
        reset = 0;
        #10;

        // R-Type TEST
        if(1) begin
            init();
            RS0 = 0; RD0 = 32'h0000_0000;
            RS1 = 1; RD1 = 32'h0000_0001;
            RS2 = 2; RD2 = 32'h7FFF_FFFF;
            RS3 = 3; RD3 = 32'hFFFF_FFFF;
            RS4 = 4; RD4 = 32'h8000_0000;
            RS5 = 5; RD5 = 32'h0000_001F;

            `RF_PATH.mem[RS1] = RD1; //x1 data
            `RF_PATH.mem[RS2] = RD2; //x2 data
            `RF_PATH.mem[RS3] = RD3; //x3 data
            `RF_PATH.mem[RS4] = RD4; //x4 data
            `RF_PATH.mem[RS5] = RD5; //x5 data

            // Format: {funct7, rs2, rs1, funct3, rd, opcode}
            `INSTR_PATH.rom[0] = {`FNC7_0, RS1, RS2, `FNC_ADD_SUB, 5'd8, `OPC_ARI_RTYPE};  // add  x8, x2, x1
            `INSTR_PATH.rom[1] = {`FNC7_1, RS2, RS1, `FNC_ADD_SUB, 5'd9, `OPC_ARI_RTYPE};  // sub  x9, x1, x2
            `INSTR_PATH.rom[2] = {`FNC7_0, RS4, RS3, `FNC_AND,     5'd10, `OPC_ARI_RTYPE}; // and  x10, x3, x4
            `INSTR_PATH.rom[3] = {`FNC7_0, RS3, RS4, `FNC_OR,      5'd11, `OPC_ARI_RTYPE}; // or   x11, x4, x3
            `INSTR_PATH.rom[4] = {`FNC7_0, RS5, RS1, `FNC_SLL,     5'd12, `OPC_ARI_RTYPE}; // sll  x12, x1, x5
            `INSTR_PATH.rom[5] = {`FNC7_0, RS5, RS4, `FNC_SRL_SRA, 5'd13, `OPC_ARI_RTYPE}; // srl  x13, x4, x5
            `INSTR_PATH.rom[6] = {`FNC7_1, RS5, RS4, `FNC_SRL_SRA, 5'd14, `OPC_ARI_RTYPE}; // sra  x14, x4, x5
            `INSTR_PATH.rom[7] = {`FNC7_0, RS2, RS4, `FNC_SLT,     5'd15, `OPC_ARI_RTYPE}; // slt  x15, x4, x2
            `INSTR_PATH.rom[8] = {`FNC7_0, RS0, RS3, `FNC_SLTU,    5'd16, `OPC_ARI_RTYPE}; // sltu x16, x3, x0
            `INSTR_PATH.rom[9] = {`FNC7_0, RS4, RS3, `FNC_XOR,     5'd17, `OPC_ARI_RTYPE}; // xor  x17, x3, x4

            reset_cpu();

            #10; check_result(8,  32'h8000_0000, "R-Type ADD");
            #10; check_result(9,  32'h8000_0002, "R-Type SUB");
            #10; check_result(10, 32'h8000_0000, "R-Type AND");
            #10; check_result(11, 32'hFFFF_FFFF, "R-Type OR");
            #10; check_result(12, 32'h8000_0000, "R-Type SLL");
            #10; check_result(13, 32'h0000_0001, "R-Type SRL");
            #10; check_result(14, 32'hFFFF_FFFF, "R-Type SRA");
            #10; check_result(15, 32'h0000_0001, "R-Type SLT");
            #10; check_result(16, 32'h0000_0000, "R-Type SLTU");
            #10; check_result(17, 32'h7FFF_FFFF, "R-Type XOR");
        end

        // I-Type TEST
        if(0) begin
            init();

            reset_cpu();
        end
    
    all_tests_passed = 1'b1;

    repeat(10) @(posedge clk);
    $display("All tests passed!");
    $finish();
    end

endmodule

경로 매크로 설정

// 레지스터 파일(Register File)
`define RF_PATH   U_MCU.U_CPU_RV32I.U_DataPath.U_RegFile

// 명령어 메모리(Instruction Memory)
`define INSTR_PATH U_MCU.U_ROM

명령어 및 함수 코드 매크로

// List of RISC-V opcodes and funct codes.
// Use `include "opcode.vh" to use these in the decoder

`ifndef OPCODE
`define OPCODE

// Arithmetic instructions
`define OPC_ARI_RTYPE   7'b0110011

// ***** 5-bit Opcodes *****
`define OPC_ARI_RTYPE_5 5'b01100

// Arithmetic R-type and I-type functions codes
`define FNC_ADD_SUB     3'b000
`define FNC_SLL         3'b001
`define FNC_SLT         3'b010
`define FNC_SLTU        3'b011
`define FNC_XOR         3'b100
`define FNC_OR          3'b110
`define FNC_AND         3'b111
`define FNC_SRL_SRA     3'b101

`define FNC7_0  7'b0000000 // ADD, SRL
`define FNC7_1  7'b0100000 // SUB, SRA
`endif //OPCODE

테스트 초기화 / 리셋

레지스터 파일(RF)과 명령어 메모리(ROM)를 모두 0으로 초기화
CPU reset

task init;
    int i;
    for (int i = 0; i < 62; i++) begin
        `INSTR_PATH.rom[i] = 32'h00000000;
    end
    for (int i = 0; i < 32; i++) begin
        `RF_PATH.mem[i] = 32'h00000000;
    end
endtask

task reset_cpu; 
    repeat(3) begin
        @(posedge clk);
        reset = 1;
    end
    @(posedge clk);
    reset = 0;
endtask

테스트 결과 검증 및 타임아웃 관리
각 테스트 결과를 자동 검증, 일정 Cycle내에 결과가 나오지 않으면 에러 메시지 출력 후 종료
실패 시 어떤 테스트에서 어떤 값이 잘못되었는지 상세 정보 출력

wire [31:0] timeout_cycle = 25;

initial begin
    while (all_tests_passed === 0) begin
    @(posedge clk);
        if (cycle === timeout_cycle) begin
            $display("[Failed] Timeout at [%d] test %s, expected_result = %h, got = %h", current_test_id, current_test_type, current_result, current_output);
            $finish();
        end
    end
end

always_ff @(posedge clk) begin
    if (done === 0) cycle <= cycle + 1;
    else cycle <= 0;
end

task check_result (input logic [4:0] addr, input [31:0] expect_value, input [255:0] test_type);
    done = 0;
    current_test_id   = current_test_id + 1;
    current_test_type = test_type;
    current_result    = expect_value;
    while (`RF_PATH.mem[addr] !== expect_value) begin
        current_output = `RF_PATH.mem[addr];
        @(posedge clk);
    end
    cycle = 0;
    done = 1;
    $display("[%d] Test %s passed!", current_test_id, test_type);
endtask

R-Type 명령어 테스트

// R-Type TEST
    if(1) begin
        init();

        RS0 = 0; RD0 = 32'h0000_0000;
        RS1 = 1; RD1 = 32'h0000_0001;
        RS2 = 2; RD2 = 32'h7FFF_FFFF;
        RS3 = 3; RD3 = 32'hFFFF_FFFF;
        RS4 = 4; RD4 = 32'h8000_0000;
        RS5 = 5; RD5 = 32'h0000_001F;

        `RF_PATH.mem[RS1] = RD1; //x1 data
        `RF_PATH.mem[RS2] = RD2; //x2 data
        `RF_PATH.mem[RS3] = RD3; //x3 data
        `RF_PATH.mem[RS4] = RD4; //x4 data
        `RF_PATH.mem[RS5] = RD5; //x5 data

        // Format: {funct7, rs2, rs1, funct3, rd, opcode}
        `INSTR_PATH.rom[0] = {`FNC7_0, RS1, RS2, `FNC_ADD_SUB, 5'd8, `OPC_ARI_RTYPE};  // add  x8, x2, x1
        `INSTR_PATH.rom[1] = {`FNC7_1, RS2, RS1, `FNC_ADD_SUB, 5'd9, `OPC_ARI_RTYPE};  // sub  x9, x1, x2
        `INSTR_PATH.rom[2] = {`FNC7_0, RS4, RS3, `FNC_AND,     5'd10, `OPC_ARI_RTYPE}; // and  x10, x3, x4
        `INSTR_PATH.rom[3] = {`FNC7_0, RS3, RS4, `FNC_OR,      5'd11, `OPC_ARI_RTYPE}; // or   x11, x4, x3
        `INSTR_PATH.rom[4] = {`FNC7_0, RS5, RS1, `FNC_SLL,     5'd12, `OPC_ARI_RTYPE}; // sll  x12, x1, x5
        `INSTR_PATH.rom[5] = {`FNC7_0, RS5, RS4, `FNC_SRL_SRA, 5'd13, `OPC_ARI_RTYPE}; // srl  x13, x4, x5
        `INSTR_PATH.rom[6] = {`FNC7_1, RS5, RS4, `FNC_SRL_SRA, 5'd14, `OPC_ARI_RTYPE}; // sra  x14, x4, x5
        `INSTR_PATH.rom[7] = {`FNC7_0, RS2, RS4, `FNC_SLT,     5'd15, `OPC_ARI_RTYPE}; // slt  x15, x4, x2
        `INSTR_PATH.rom[8] = {`FNC7_0, RS0, RS3, `FNC_SLTU,    5'd16, `OPC_ARI_RTYPE}; // sltu x16, x3, x0
        `INSTR_PATH.rom[9] = {`FNC7_0, RS4, RS3, `FNC_XOR,     5'd17, `OPC_ARI_RTYPE}; // xor  x17, x3, x4

        reset_cpu();

        #10; check_result(8,  32'h8000_0000, "R-Type ADD");
        #10; check_result(9,  32'h8000_0002, "R-Type SUB");
        #10; check_result(10, 32'h8000_0000, "R-Type AND");
        #10; check_result(11, 32'hFFFF_FFFF, "R-Type OR");
        #10; check_result(12, 32'h8000_0000, "R-Type SLL");
        #10; check_result(13, 32'h0000_0001, "R-Type SRL");
        #10; check_result(14, 32'hFFFF_FFFF, "R-Type SRA");
        #10; check_result(15, 32'h0000_0001, "R-Type SLT");
        #10; check_result(16, 32'h0000_0000, "R-Type SLTU");
        #10; check_result(17, 32'h7FFF_FFFF, "R-Type XOR");
    end

✅ 결과

[Failed]

[Passed]

[Simulation_Result]