aboutsummaryrefslogtreecommitdiff
path: root/rv64cpu
diff options
context:
space:
mode:
authorRichard Qin <richard.qin.x@gmail.com>2026-03-16 02:06:11 +0800
committerRichard Qin <richard.qin.x@gmail.com>2026-03-16 02:06:11 +0800
commitf1a9abcf0862e0852c3d052bb3d712f293e2c994 (patch)
tree73c12a322433eb15f41f625de86d001097f769f7 /rv64cpu
parentbda2f04234d0ab81a11adfe639d09b298b7ed4b7 (diff)
downloadrcc-project-f1a9abcf0862e0852c3d052bb3d712f293e2c994.tar.gz
rcc-project-f1a9abcf0862e0852c3d052bb3d712f293e2c994.tar.xz
Decoder unit and test
Diffstat (limited to 'rv64cpu')
-rw-r--r--rv64cpu/rtl/core/decoder.sv390
-rw-r--r--rv64cpu/tb/CMakeLists.txt18
-rw-r--r--rv64cpu/tb/unit/tb_decoder.cc298
3 files changed, 697 insertions, 9 deletions
diff --git a/rv64cpu/rtl/core/decoder.sv b/rv64cpu/rtl/core/decoder.sv
new file mode 100644
index 0000000..8f35c47
--- /dev/null
+++ b/rv64cpu/rtl/core/decoder.sv
@@ -0,0 +1,390 @@
+// Richard CPU
+// Copyright (C) 2026 Richard Qin
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+// File path: rtl/core/decoder.sv
+// Description: Instruction decoder for RV64I base ISA + Zicsr + Zifencei
+// Translates 32-bit instructions into control signals for the pipeline.
+// M/A/F/D/C extensions are marked with TODO stubs.
+
+module decoder
+ import rv64_pkg::*;
+(
+ input logic [31:0] instr, // 32-bit instruction
+
+ // Register File
+ output logic [4:0] rs1_addr, // Source register 1 address
+ output logic [4:0] rs2_addr, // Source register 2 address
+ output logic [4:0] rd_addr, // Destination register address
+ output logic reg_write, // Register file write enable
+
+ // ALU Control
+ output alu_op_t alu_op, // ALU operation select
+ output logic alu_src1_sel, // 0 = rs1, 1 = PC
+ output logic alu_src2_sel, // 0 = rs2, 1 = immediate
+
+ // Immediate
+ output logic [XLEN-1:0] imm, // Sign-extended immediate
+
+ // Memory Access
+ output logic is_load, // MEM stage: read from memory
+ output logic is_store, // MEM stage: write to memory
+ output logic [2:0] mem_size, // funct3 → byte/half/word/double + sign
+
+ // Branch / Jump
+ output logic is_branch, // EX: conditional branch
+ output logic is_jump, // EX: unconditional jump (JAL/JALR)
+ output logic [2:0] branch_op, // funct3 → BEQ/BNE/BLT/BGE/BLTU/BGEU
+
+ // Writeback Source
+ output logic [1:0] wb_sel, // 0 = ALU result, 1 = MEM data, 2 = PC+4
+
+ // RV64 Word Operations
+ output logic is_word_op, // EX: operate on lower 32 bits, sext result
+
+ // System / Privilege
+ output logic is_csr, // CSR instruction
+ output logic [2:0] csr_op, // funct3 → CSRRW/CSRRS/CSRRC/CSRRWI/CSRRSI/CSRRCI
+ output logic is_ecall, // ECALL
+ output logic is_ebreak, // EBREAK
+ output logic is_mret, // MRET
+ output logic is_sret, // SRET
+ output logic is_wfi, // WFI
+ output logic is_sfence_vma, // SFENCE.VMA
+ output logic is_fence, // FENCE
+ output logic is_fence_i, // FENCE.I (Zifencei)
+
+ // Error
+ output logic illegal_instr // Unrecognised or malformed instruction
+);
+
+ // ============================
+ // Instruction Field Extraction
+ // ============================
+ logic [6:0] opcode;
+ logic [2:0] funct3;
+ logic [6:0] funct7;
+
+ assign opcode = instr[6:0];
+ assign rd_addr = instr[11:7];
+ assign funct3 = instr[14:12];
+ assign rs1_addr = instr[19:15];
+ assign rs2_addr = instr[24:20];
+ assign funct7 = instr[31:25];
+
+ // ============================
+ // Immediate Generation
+ // ============================
+ logic [XLEN-1:0] imm_i, imm_s, imm_b, imm_u, imm_j;
+
+ // I-type: instr[31:20]
+ assign imm_i = {{52{instr[31]}}, instr[31:20]};
+
+ // S-type: {instr[31:25], instr[11:7]}
+ assign imm_s = {{52{instr[31]}}, instr[31:25], instr[11:7]};
+
+ // B-type: {instr[31], instr[7], instr[30:25], instr[11:8], 1'b0}
+ assign imm_b = {{51{instr[31]}}, instr[31], instr[7], instr[30:25], instr[11:8], 1'b0};
+
+ // U-type: {instr[31:12], 12'b0}
+ assign imm_u = {{32{instr[31]}}, instr[31:12], 12'b0};
+
+ // J-type: {instr[31], instr[19:12], instr[20], instr[30:21], 1'b0}
+ assign imm_j = {{43{instr[31]}}, instr[31], instr[19:12], instr[20], instr[30:21], 1'b0};
+
+ // ============================
+ // Main Decode Logic
+ // ============================
+ always_comb begin
+ // Default values (all signals inactive)
+ alu_op = ALU_ADD;
+ alu_src1_sel = 1'b0; // rs1
+ alu_src2_sel = 1'b0; // rs2
+ reg_write = 1'b0;
+ imm = '0;
+ is_load = 1'b0;
+ is_store = 1'b0;
+ mem_size = 3'b0;
+ is_branch = 1'b0;
+ is_jump = 1'b0;
+ branch_op = 3'b0;
+ wb_sel = 2'b00; // ALU result
+ is_word_op = 1'b0;
+ is_csr = 1'b0;
+ csr_op = 3'b0;
+ is_ecall = 1'b0;
+ is_ebreak = 1'b0;
+ is_mret = 1'b0;
+ is_sret = 1'b0;
+ is_wfi = 1'b0;
+ is_sfence_vma = 1'b0;
+ is_fence = 1'b0;
+ is_fence_i = 1'b0;
+ illegal_instr = 1'b0;
+
+ case (opcode)
+
+ // LUI — rd = imm_u
+
+ OPCODE_LUI: begin
+ reg_write = 1'b1;
+ alu_src1_sel = 1'b0; // doesn't matter, ALU_ADD(0, imm_u)
+ alu_src2_sel = 1'b1; // immediate
+ imm = imm_u;
+ alu_op = ALU_ADD; // result = 0 + imm_u (rs1 forced to x0 externally or use alu_src1_sel trick)
+ // NOTE: EX stage should feed 0 to ALU operand 1 when alu_src1_sel==0 and opcode==LUI.
+ // Alternative: add a dedicated alu_src1_sel=2 for "zero". For now, the pipeline
+ // can detect LUI by opcode and zero out the operand.
+ end
+
+ // AUIPC — rd = PC + imm_u
+
+
+ OPCODE_AUIPC: begin
+ reg_write = 1'b1;
+ alu_src1_sel = 1'b1; // PC
+ alu_src2_sel = 1'b1; // immediate
+ imm = imm_u;
+ alu_op = ALU_ADD; // result = PC + imm_u
+ end
+
+ // -----------------
+ // JAL — rd = PC+4; PC = PC + imm_j
+ OPCODE_JAL: begin
+ reg_write = 1'b1;
+ is_jump = 1'b1;
+ alu_src1_sel = 1'b1; // PC
+ alu_src2_sel = 1'b1; // immediate
+ imm = imm_j;
+ alu_op = ALU_ADD; // target = PC + imm_j
+ wb_sel = 2'b10; // writeback PC+4
+ end
+
+ // ------------------
+ // JALR — rd = PC+4; PC = (rs1 + imm_i) & ~1
+
+ OPCODE_JALR: begin
+ reg_write = 1'b1;
+ is_jump = 1'b1;
+ alu_src1_sel = 1'b0; // rs1
+ alu_src2_sel = 1'b1; // immediate
+ imm = imm_i;
+ alu_op = ALU_ADD; // target = rs1 + imm_i (LSB cleared by EX stage)
+ wb_sel = 2'b10; // writeback PC+4
+ end
+
+ // -----------------
+ // Branch — compare rs1, rs2; if taken, PC = PC + imm_b
+ OPCODE_BRANCH: begin
+ reg_write = 1'b0; // branches never write rd
+ is_branch = 1'b1;
+ alu_src1_sel = 1'b1; // PC (ALU computes branch target)
+ alu_src2_sel = 1'b1; // immediate
+ imm = imm_b;
+ alu_op = ALU_ADD; // branch target = PC + imm_b
+ branch_op = funct3; // BEQ/BNE/BLT/BGE/BLTU/BGEU
+ end
+
+ // -----------------
+ // Load — rd = Mem[rs1 + imm_i]
+ OPCODE_LOAD: begin
+ reg_write = 1'b1;
+ is_load = 1'b1;
+ alu_src1_sel = 1'b0; // rs1
+ alu_src2_sel = 1'b1; // immediate
+ imm = imm_i;
+ alu_op = ALU_ADD; // effective address = rs1 + imm_i
+ mem_size = funct3; // LB/LH/LW/LD/LBU/LHU/LWU
+ wb_sel = 2'b01; // writeback from memory
+ end
+
+ // -----------------
+ // Store — Mem[rs1 + imm_s] = rs2
+ OPCODE_STORE: begin
+ reg_write = 1'b0;
+ is_store = 1'b1;
+ alu_src1_sel = 1'b0; // rs1
+ alu_src2_sel = 1'b1; // immediate (NOT rs2 — rs2 is store data)
+ imm = imm_s;
+ alu_op = ALU_ADD; // effective address = rs1 + imm_s
+ mem_size = funct3; // SB/SH/SW/SD
+ end
+
+ // -----------------
+ // OP-IMM (I-type ALU) — rd = rs1 OP imm
+ // ADDI, SLTI, SLTIU, XORI, ORI, ANDI, SLLI, SRLI, SRAI
+ OPCODE_OP_IMM: begin
+ reg_write = 1'b1;
+ alu_src1_sel = 1'b0; // rs1
+ alu_src2_sel = 1'b1; // immediate
+ imm = imm_i;
+
+ case (funct3)
+ 3'b000: alu_op = ALU_ADD; // ADDI
+ 3'b010: alu_op = ALU_SLT; // SLTI
+ 3'b011: alu_op = ALU_SLTU; // SLTIU
+ 3'b100: alu_op = ALU_XOR; // XORI
+ 3'b110: alu_op = ALU_OR; // ORI
+ 3'b111: alu_op = ALU_AND; // ANDI
+ 3'b001: alu_op = ALU_SLL; // SLLI
+ 3'b101: alu_op = (funct7[5]) ? ALU_SRA : ALU_SRL; // SRAI / SRLI
+ default: alu_op = ALU_ADD;
+ endcase
+ end
+
+ // -----------------
+ // OP-IMM-32 (I-type ALU, 32-bit word variants for RV64)
+ // ADDIW, SLLIW, SRLIW, SRAIW
+ OPCODE_OP_IMM_32: begin
+ reg_write = 1'b1;
+ is_word_op = 1'b1; // EX stage: operate on lower 32 bits, sext result
+ alu_src1_sel = 1'b0; // rs1
+ alu_src2_sel = 1'b1; // immediate
+ imm = imm_i;
+
+ case (funct3)
+ 3'b000: alu_op = ALU_ADD; // ADDIW
+ 3'b001: alu_op = ALU_SLL; // SLLIW
+ 3'b101: alu_op = (funct7[5]) ? ALU_SRA : ALU_SRL; // SRAIW / SRLIW
+ default: begin
+ alu_op = ALU_ADD;
+ illegal_instr = 1'b1;
+ end
+ endcase
+ end
+
+ // -----------------
+ // OP (R-type ALU) — rd = rs1 OP rs2
+ // ADD, SUB, SLL, SLT, SLTU, XOR, SRL, SRA, OR, AND
+ OPCODE_OP: begin
+ reg_write = 1'b1;
+ alu_src1_sel = 1'b0; // rs1
+ alu_src2_sel = 1'b0; // rs2
+
+ if (funct7 == 7'b0000001) begin
+ // M extension (MUL/DIV/REM)
+ // TODO: M extension — set alu_op to MUL/DIV variants
+ illegal_instr = 1'b1; // placeholder until M extension is implemented
+ end else begin
+ case (funct3)
+ 3'b000: alu_op = (funct7[5]) ? ALU_SUB : ALU_ADD; // SUB / ADD
+ 3'b001: alu_op = ALU_SLL; // SLL
+ 3'b010: alu_op = ALU_SLT; // SLT
+ 3'b011: alu_op = ALU_SLTU; // SLTU
+ 3'b100: alu_op = ALU_XOR; // XOR
+ 3'b101: alu_op = (funct7[5]) ? ALU_SRA : ALU_SRL; // SRA / SRL
+ 3'b110: alu_op = ALU_OR; // OR
+ 3'b111: alu_op = ALU_AND; // AND
+ default: alu_op = ALU_ADD;
+ endcase
+ end
+ end
+
+ // -----------------
+ // OP-32 (R-type ALU, 32-bit word variants for RV64)
+ // ADDW, SUBW, SLLW, SRLW, SRAW
+ OPCODE_OP_32: begin
+ reg_write = 1'b1;
+ is_word_op = 1'b1;
+ alu_src1_sel = 1'b0; // rs1
+ alu_src2_sel = 1'b0; // rs2
+
+ if (funct7 == 7'b0000001) begin
+ // M extension 32-bit (MULW/DIVW/REMW)
+ // TODO: M extension word variants
+ illegal_instr = 1'b1;
+ end else begin
+ case (funct3)
+ 3'b000: alu_op = (funct7[5]) ? ALU_SUB : ALU_ADD; // SUBW / ADDW
+ 3'b001: alu_op = ALU_SLL; // SLLW
+ 3'b101: alu_op = (funct7[5]) ? ALU_SRA : ALU_SRL; // SRAW / SRLW
+ default: begin
+ alu_op = ALU_ADD;
+ illegal_instr = 1'b1;
+ end
+ endcase
+ end
+ end
+
+ // -----------------
+ // MISC-MEM — FENCE / FENCE.I
+ OPCODE_MISC_MEM: begin
+ case (funct3)
+ 3'b000: is_fence = 1'b1; // FENCE (NOP in single-core in-order)
+ 3'b001: is_fence_i = 1'b1; // FENCE.I — flush I-Cache
+ default: illegal_instr = 1'b1;
+ endcase
+ end
+
+ // -----------------
+ // SYSTEM — ECALL / EBREAK / xRET / WFI / SFENCE.VMA / CSR*
+ OPCODE_SYSTEM: begin
+ if (funct3 == 3'b000) begin
+ // Privileged instructions (non-CSR)
+ reg_write = 1'b0;
+ case ({funct7, instr[24:20]}) // {funct7, rs2}
+ 12'b0000000_00000: is_ecall = 1'b1; // ECALL
+ 12'b0000000_00001: is_ebreak = 1'b1; // EBREAK
+ 12'b0011000_00010: is_mret = 1'b1; // MRET
+ 12'b0001000_00010: is_sret = 1'b1; // SRET
+ 12'b0001000_00101: is_wfi = 1'b1; // WFI
+ default: begin
+ if (funct7 == 7'b0001001)
+ is_sfence_vma = 1'b1; // SFENCE.VMA
+ else
+ illegal_instr = 1'b1;
+ end
+ endcase
+ end else begin
+ // CSR instructions
+ // funct3: 001=CSRRW, 010=CSRRS, 011=CSRRC,
+ // 101=CSRRWI, 110=CSRRSI, 111=CSRRCI
+ reg_write = 1'b1;
+ is_csr = 1'b1;
+ csr_op = funct3;
+ imm = imm_i; // CSR address = instr[31:20] (reuse imm_i path)
+ end
+ end
+
+ // -----------------
+ // AMO — A extension (LR/SC/AMO*)
+ OPCODE_AMO: begin
+ // TODO: A extension
+ illegal_instr = 1'b1;
+ end
+
+ // -----------------
+ // Floating-Point — F/D extensions
+ OPCODE_LOAD_FP,
+ OPCODE_STORE_FP,
+ OPCODE_MADD,
+ OPCODE_MSUB,
+ OPCODE_NMSUB,
+ OPCODE_NMADD,
+ OPCODE_OP_FP: begin
+ // TODO: F/D extension
+ illegal_instr = 1'b1;
+ end
+
+ // -----------------
+ // Unrecognised opcode
+ default: begin
+ illegal_instr = 1'b1;
+ end
+
+ endcase
+ end
+
+endmodule
diff --git a/rv64cpu/tb/CMakeLists.txt b/rv64cpu/tb/CMakeLists.txt
index 0643120..42ed2c0 100644
--- a/rv64cpu/tb/CMakeLists.txt
+++ b/rv64cpu/tb/CMakeLists.txt
@@ -38,15 +38,15 @@ add_verilator_target(
INCLUDE_DIRS ${RTL_COMMON_INCLUDES}
TB_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/unit/tb_alu.cc
)
-#
-# add_verilator_target(
-# NAME tb_decoder
-# TOP_MODULE decoder
-# SOURCES ${RTL_CORE_DIR}/include/rv64_pkg.sv
-# ${RTL_CORE_DIR}/decoder.sv
-# INCLUDE_DIRS ${RTL_COMMON_INCLUDES}
-# TB_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/unit/tb_decoder.cc
-# )
+
+add_verilator_target(
+ NAME tb_decoder
+ TOP_MODULE decoder
+ SOURCES ${RTL_CORE_DIR}/include/rv64_pkg.sv
+ ${RTL_CORE_DIR}/decoder.sv
+ INCLUDE_DIRS ${RTL_COMMON_INCLUDES}
+ TB_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/unit/tb_decoder.cc
+)
add_verilator_target(
NAME tb_regfile
diff --git a/rv64cpu/tb/unit/tb_decoder.cc b/rv64cpu/tb/unit/tb_decoder.cc
new file mode 100644
index 0000000..b1784d5
--- /dev/null
+++ b/rv64cpu/tb/unit/tb_decoder.cc
@@ -0,0 +1,298 @@
+// Richard CPU
+// Copyright (C) 2026 Richard Qin
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+// File path: tb/unit/tb_decoder.cc
+// Description: Decoder unit test
+
+#include <iostream>
+#include <iomanip>
+#include <cstdint>
+#include <cstdlib>
+#include <verilated.h>
+#include "Vdecoder.h"
+
+static int test_count = 0;
+static int fail_count = 0;
+
+#define CHECK(dut, signal, expected, test_name) \
+ do { \
+ uint64_t _got = (uint64_t)(dut->signal); \
+ uint64_t _exp = (uint64_t)(expected); \
+ if (_got != _exp) { \
+ std::cerr << "\033[31m[FAIL]\033[0m " << test_name \
+ << " | " << #signal << " expected: 0x" \
+ << std::hex << _exp << " got: 0x" << _got \
+ << std::dec << std::endl; \
+ fail_count++; \
+ } \
+ } while(0)
+
+void decode(Vdecoder* dut, uint32_t instr, const char* name) {
+ dut->instr = instr;
+ dut->eval();
+ test_count++;
+ std::cout << "[TEST " << std::setw(2) << test_count << "] " << name
+ << " (0x" << std::hex << std::setw(8) << std::setfill('0')
+ << instr << std::dec << std::setfill(' ') << ")" << std::endl;
+}
+
+int main(int argc, char** argv) {
+ Verilated::commandArgs(argc, argv);
+ Vdecoder* dut = new Vdecoder;
+
+ std::cout << "═══════════════════════════════════════════════" << std::endl;
+ std::cout << " Decoder Unit Test Suite" << std::endl;
+ std::cout << "═══════════════════════════════════════════════" << std::endl;
+
+ // 1. R-type: ADD x1, x2, x3
+ // funct7=0000000 rs2=00011 rs1=00010 funct3=000 rd=00001 op=0110011
+ decode(dut, 0x003100B3, "ADD x1, x2, x3");
+ CHECK(dut, rs1_addr, 2, "ADD");
+ CHECK(dut, rs2_addr, 3, "ADD");
+ CHECK(dut, rd_addr, 1, "ADD");
+ CHECK(dut, reg_write, 1, "ADD"); // R-type writes rd
+ CHECK(dut, alu_op, 0, "ADD"); // ALU_ADD = 0b0000
+ CHECK(dut, alu_src1_sel, 0, "ADD"); // operand1 = rs1
+ CHECK(dut, alu_src2_sel, 0, "ADD"); // operand2 = rs2
+ CHECK(dut, wb_sel, 0, "ADD"); // writeback from ALU
+ CHECK(dut, illegal_instr,0, "ADD");
+
+ // 2. R-type: SUB x3, x1, x2
+ // funct7=0100000 rs2=00010 rs1=00001 funct3=000 rd=00011 op=0110011
+ // Tests: funct7[5] distinguishes ADD vs SUB
+ decode(dut, 0x402081B3, "SUB x3, x1, x2");
+ CHECK(dut, alu_op, 8, "SUB"); // ALU_SUB = 0b1000
+ CHECK(dut, reg_write, 1, "SUB");
+ CHECK(dut, alu_src2_sel, 0, "SUB"); // operand2 = rs2
+
+ // 3. R-type: SRA x1, x2, x3 (vs SRL)
+ // funct7=0100000 rs2=00011 rs1=00010 funct3=101 rd=00001 op=0110011
+ // Tests: funct7[5] distinguishes SRL vs SRA for funct3=101
+ decode(dut, 0x403150B3, "SRA x1, x2, x3");
+ CHECK(dut, alu_op, 13, "SRA"); // ALU_SRA = 0b1101
+
+ // 4. R-type: SRL x1, x2, x3
+ // funct7=0000000 rs2=00011 rs1=00010 funct3=101 rd=00001 op=0110011
+ decode(dut, 0x003150B3, "SRL x1, x2, x3");
+ CHECK(dut, alu_op, 5, "SRL"); // ALU_SRL = 0b0101
+
+ // 5. I-type ALU: ADDI x1, x0, 42
+ // imm=000000101010 rs1=00000 funct3=000 rd=00001 op=0010011
+ // Tests: I-type sets alu_src2_sel=1 (immediate), reg_write=1
+ decode(dut, 0x02A00093, "ADDI x1, x0, 42");
+ CHECK(dut, reg_write, 1, "ADDI");
+ CHECK(dut, alu_op, 0, "ADDI"); // ALU_ADD
+ CHECK(dut, alu_src1_sel, 0, "ADDI"); // rs1
+ CHECK(dut, alu_src2_sel, 1, "ADDI"); // immediate
+ CHECK(dut, imm, 42, "ADDI");
+ CHECK(dut, rd_addr, 1, "ADDI");
+ CHECK(dut, rs1_addr, 0, "ADDI");
+
+ // 6. I-type ALU: ADDI x1, x0, -1 (negative immediate sign extension)
+ // imm=111111111111 rs1=00000 funct3=000 rd=00001 op=0010011
+ // Tests: 12-bit immediate sign-extended to 64-bit
+ decode(dut, 0xFFF00093, "ADDI x1, x0, -1");
+ CHECK(dut, imm, (uint64_t)-1, "ADDI -1"); // 0xFFFFFFFFFFFFFFFF
+
+ // 7. I-type ALU: SRAI x1, x2, 3
+ // imm=010000_000011 rs1=00010 funct3=101 rd=00001 op=0010011
+ // Tests: funct7[5]=1 selects SRA for funct3=101 in OP_IMM
+ decode(dut, 0x40315093, "SRAI x1, x2, 3");
+ CHECK(dut, alu_op, 13, "SRAI"); // ALU_SRA = 0b1101
+ CHECK(dut, alu_src2_sel, 1, "SRAI"); // immediate (shamt)
+
+ // 8. Load: LD x5, 8(x10)
+ // imm=000000001000 rs1=01010 funct3=011 rd=00101 op=0000011
+ // Tests: is_load, mem_size, wb_sel=MEM, alu computes address
+ decode(dut, 0x00853283, "LD x5, 8(x10)");
+ CHECK(dut, is_load, 1, "LD");
+ CHECK(dut, reg_write, 1, "LD");
+ CHECK(dut, alu_src2_sel, 1, "LD"); // imm for address calc
+ CHECK(dut, imm, 8, "LD");
+ CHECK(dut, mem_size, 3, "LD"); // funct3=011 → doubleword
+ CHECK(dut, wb_sel, 1, "LD"); // writeback from MEM
+ CHECK(dut, alu_op, 0, "LD"); // ALU_ADD for addr
+
+ // 9. Store: SD x5, 16(x10)
+ // imm[11:5]=0000000 rs2=00101 rs1=01010 funct3=011 imm[4:0]=10000 op=0100011
+ // Tests: is_store, reg_write=0, S-type immediate extraction
+ decode(dut, 0x00553823, "SD x5, 16(x10)");
+ CHECK(dut, is_store, 1, "SD");
+ CHECK(dut, reg_write, 0, "SD"); // stores don't write rd
+ CHECK(dut, alu_src2_sel, 1, "SD"); // imm for address calc (NOT rs2)
+ CHECK(dut, imm, 16, "SD"); // S-type immediate = 16
+ CHECK(dut, mem_size, 3, "SD"); // funct3=011 → doubleword
+ CHECK(dut, rs1_addr, 10, "SD"); // base register
+ CHECK(dut, rs2_addr, 5, "SD"); // data to store
+
+ // 10. Branch: BEQ x1, x2, -4
+ // Tests: is_branch, branch_op=funct3, negative B-type immediate
+ decode(dut, 0xFE208EE3, "BEQ x1, x2, -4");
+ CHECK(dut, is_branch, 1, "BEQ");
+ CHECK(dut, reg_write, 0, "BEQ");
+ CHECK(dut, alu_src1_sel, 1, "BEQ"); // PC for target calc
+ CHECK(dut, branch_op, 0, "BEQ"); // funct3=000 → BEQ
+ CHECK(dut, imm, (uint64_t)(-4), "BEQ"); // sign-extended -4
+
+ // 11. Branch: BNE x1, x2, +8
+ // imm[12]=0 imm[10:5]=000000 rs2=00010 rs1=00001 funct3=001
+ // imm[4:1]=0100 imm[11]=0 op=1100011
+ // Tests: branch_op correctly passes funct3 for BNE
+ decode(dut, 0x00209463, "BNE x1, x2, +8");
+ CHECK(dut, is_branch, 1, "BNE");
+ CHECK(dut, branch_op, 1, "BNE"); // funct3=001 → BNE
+ CHECK(dut, imm, 8, "BNE");
+
+ // 12. LUI x1, 0x12345
+ // imm[31:12]=0x12345 rd=00001 op=0110111
+ // Tests: U-type immediate, reg_write, alu_src2_sel=imm
+ decode(dut, 0x123450B7, "LUI x1, 0x12345");
+ CHECK(dut, reg_write, 1, "LUI");
+ CHECK(dut, alu_src2_sel, 1, "LUI"); // immediate
+ CHECK(dut, imm, 0x12345000ULL, "LUI"); // U-type: imm << 12
+ CHECK(dut, wb_sel, 0, "LUI"); // ALU result
+ CHECK(dut, rd_addr, 1, "LUI");
+
+ // 13. AUIPC x2, 0x00001
+ // imm[31:12]=0x00001 rd=00010 op=0010111
+ // Tests: alu_src1_sel=PC (distinguishes from LUI)
+ decode(dut, 0x00001117, "AUIPC x2, 0x1");
+ CHECK(dut, reg_write, 1, "AUIPC");
+ CHECK(dut, alu_src1_sel, 1, "AUIPC"); // PC ← key difference from LUI
+ CHECK(dut, alu_src2_sel, 1, "AUIPC"); // immediate
+ CHECK(dut, imm, 0x1000ULL, "AUIPC"); // 0x00001 << 12 = 0x1000
+
+ // 14. JAL x1, +8
+ // J-type imm encoding: imm[20|10:1|11|19:12]
+ // offset=8 → imm[20]=0 imm[10:1]=0000000100 imm[11]=0 imm[19:12]=00000000
+ // rd=00001 op=1101111
+ // Tests: is_jump, wb_sel=PC+4, alu_src1=PC
+ decode(dut, 0x008000EF, "JAL x1, +8");
+ CHECK(dut, is_jump, 1, "JAL");
+ CHECK(dut, reg_write, 1, "JAL");
+ CHECK(dut, alu_src1_sel, 1, "JAL"); // PC
+ CHECK(dut, alu_src2_sel, 1, "JAL"); // immediate
+ CHECK(dut, wb_sel, 2, "JAL"); // writeback PC+4
+ CHECK(dut, imm, 8, "JAL");
+
+ // 15. JALR x1, x5, 4
+ // imm=000000000100 rs1=00101 funct3=000 rd=00001 op=1100111
+ // Tests: is_jump, alu_src1=rs1 (distinguishes from JAL)
+ decode(dut, 0x004280E7, "JALR x1, x5, 4");
+ CHECK(dut, is_jump, 1, "JALR");
+ CHECK(dut, reg_write, 1, "JALR");
+ CHECK(dut, alu_src1_sel, 0, "JALR"); // rs1 ← key difference from JAL
+ CHECK(dut, alu_src2_sel, 1, "JALR"); // immediate
+ CHECK(dut, wb_sel, 2, "JALR"); // writeback PC+4
+ CHECK(dut, imm, 4, "JALR");
+ CHECK(dut, rs1_addr, 5, "JALR");
+
+ // 16. ADDIW x1, x2, 10 (RV64 word-width I-type)
+ // imm=000000001010 rs1=00010 funct3=000 rd=00001 op=0011011
+ // Tests: is_word_op flag for 32-bit operation
+ decode(dut, 0x00A1009B, "ADDIW x1, x2, 10");
+ CHECK(dut, reg_write, 1, "ADDIW");
+ CHECK(dut, is_word_op, 1, "ADDIW"); // 32-bit + sext
+ CHECK(dut, alu_op, 0, "ADDIW"); // ALU_ADD
+ CHECK(dut, alu_src2_sel, 1, "ADDIW"); // immediate
+ CHECK(dut, imm, 10, "ADDIW");
+
+ // 17. ADDW x3, x1, x2 (RV64 word-width R-type)
+ // funct7=0000000 rs2=00010 rs1=00001 funct3=000 rd=00011 op=0111011
+ // Tests: OP_32 path, is_word_op + R-type (alu_src2=rs2)
+ decode(dut, 0x002081BB, "ADDW x3, x1, x2");
+ CHECK(dut, reg_write, 1, "ADDW");
+ CHECK(dut, is_word_op, 1, "ADDW");
+ CHECK(dut, alu_op, 0, "ADDW"); // ALU_ADD
+ CHECK(dut, alu_src2_sel, 0, "ADDW"); // rs2 (R-type!)
+
+ // 18. SUBW x3, x1, x2 (RV64 word-width R-type with funct7[5]=1)
+ // funct7=0100000 rs2=00010 rs1=00001 funct3=000 rd=00011 op=0111011
+ decode(dut, 0x402081BB, "SUBW x3, x1, x2");
+ CHECK(dut, alu_op, 8, "SUBW"); // ALU_SUB
+ CHECK(dut, is_word_op, 1, "SUBW");
+
+ // 19. CSRRW x1, mstatus(0x300), x2
+ // imm=001100000000 rs1=00010 funct3=001 rd=00001 op=1110011
+ // Tests: is_csr, csr_op=funct3, reg_write=1
+ decode(dut, 0x300110F3, "CSRRW x1, mstatus, x2");
+ CHECK(dut, is_csr, 1, "CSRRW");
+ CHECK(dut, csr_op, 1, "CSRRW"); // funct3=001 → CSRRW
+ CHECK(dut, reg_write, 1, "CSRRW");
+ CHECK(dut, illegal_instr,0, "CSRRW");
+
+ // 20. ECALL
+ // all-zero fields + opcode=1110011
+ decode(dut, 0x00000073, "ECALL");
+ CHECK(dut, is_ecall, 1, "ECALL");
+ CHECK(dut, reg_write, 0, "ECALL");
+ CHECK(dut, illegal_instr,0, "ECALL");
+
+ // 21. EBREAK
+ // funct7=0000000 rs2=00001 rs1=00000 funct3=000 rd=00000 op=1110011
+ decode(dut, 0x00100073, "EBREAK");
+ CHECK(dut, is_ebreak, 1, "EBREAK");
+ CHECK(dut, reg_write, 0, "EBREAK");
+
+ // 22. MRET
+ // funct7=0011000 rs2=00010 rs1=00000 funct3=000 rd=00000 op=1110011
+ // Tests: privileged instruction decode from {funct7, rs2}
+ decode(dut, 0x30200073, "MRET");
+ CHECK(dut, is_mret, 1, "MRET");
+ CHECK(dut, reg_write, 0, "MRET");
+ CHECK(dut, illegal_instr,0, "MRET");
+
+ // 23. SRET
+ // funct7=0001000 rs2=00010 rs1=00000 funct3=000 rd=00000 op=1110011
+ decode(dut, 0x10200073, "SRET");
+ CHECK(dut, is_sret, 1, "SRET");
+ CHECK(dut, reg_write, 0, "SRET");
+
+ // 24. WFI
+ // funct7=0001000 rs2=00101 rs1=00000 funct3=000 rd=00000 op=1110011
+ decode(dut, 0x10500073, "WFI");
+ CHECK(dut, is_wfi, 1, "WFI");
+
+ // 25. FENCE
+ // pred=1111 succ=1111 rs1=00000 funct3=000 rd=00000 op=0001111
+ // Tests: MISC_MEM path, is_fence flag
+ decode(dut, 0x0FF0000F, "FENCE");
+ CHECK(dut, is_fence, 1, "FENCE");
+ CHECK(dut, illegal_instr,0, "FENCE");
+
+ // 26. FENCE.I (Zifencei)
+ // funct3=001 op=0001111
+ // Tests: is_fence_i flag (requires I-Cache flush in pipeline)
+ decode(dut, 0x0000100F, "FENCE.I");
+ CHECK(dut, is_fence_i, 1, "FENCE.I");
+
+ // 27. Illegal instruction (opcode=0000000)
+ // Tests: default case sets illegal_instr, reg_write stays 0
+ decode(dut, 0x00000000, "Illegal (all-zero)");
+ CHECK(dut, illegal_instr,1, "Illegal");
+ CHECK(dut, reg_write, 0, "Illegal");
+
+ // Results
+ std::cout << "═══════════════════════════════════════════════" << std::endl;
+ if (fail_count == 0) {
+ std::cout << "\033[32mAll " << test_count << " tests passed!\033[0m" << std::endl;
+ } else {
+ std::cout << "\033[31m" << fail_count << " check(s) FAILED out of "
+ << test_count << " tests.\033[0m" << std::endl;
+ }
+
+ delete dut;
+ return (fail_count > 0) ? 1 : 0;
+} \ No newline at end of file