aboutsummaryrefslogtreecommitdiff
path: root/rv64cpu/rtl/core/execute
diff options
context:
space:
mode:
Diffstat (limited to 'rv64cpu/rtl/core/execute')
-rw-r--r--rv64cpu/rtl/core/execute/branch_unit.sv52
-rw-r--r--rv64cpu/rtl/core/execute/compressed_decoder.sv234
-rw-r--r--rv64cpu/rtl/core/execute/hazard_unit.sv200
-rw-r--r--rv64cpu/rtl/core/execute/instruction_decoder.sv672
-rw-r--r--rv64cpu/rtl/core/execute/integer_alu.sv123
5 files changed, 1281 insertions, 0 deletions
diff --git a/rv64cpu/rtl/core/execute/branch_unit.sv b/rv64cpu/rtl/core/execute/branch_unit.sv
new file mode 100644
index 0000000..c9662ce
--- /dev/null
+++ b/rv64cpu/rtl/core/execute/branch_unit.sv
@@ -0,0 +1,52 @@
+// 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/branch_unit.sv
+// Description: Branch Unit, responsible for determining whether conditional jump instructions are effective
+
+module branch_unit
+ import rv64_pkg::*;
+(
+ input logic is_branch, // Indicates whether the current instruction is a branch instruction
+ input logic [2:0] branch_op, // Specific branch types func3
+ input logic [XLEN-1:0] operand_a, // operand 1
+ input logic [XLEN-1:0] operand_b, // operand 2
+ output logic branch_taken // Branch taken signal
+);
+
+ always_comb begin
+ branch_taken = 1'b0;
+
+ if (is_branch) begin
+ case (branch_op)
+ // 3'b000: BEQ (Branch if Equal)
+ BRANCH_EQ: branch_taken = (operand_a == operand_b);
+ // 3'b001: BNE (Branch if Not Equal)
+ BRANCH_NE: branch_taken = (operand_a != operand_b);
+ // 3'b100: BLT (Branch if Less Than, Signed)
+ BRANCH_LT: branch_taken = ($signed(operand_a) < $signed(operand_b));
+ // 3'b101: BGE (Branch if Greater Than or Equal, Signed)
+ BRANCH_GE: branch_taken = ($signed(operand_a) >= $signed(operand_b));
+ // 3'b110: BLTU (Branch if Less Than, Unsigned)
+ BRANCH_LTU: branch_taken = (operand_a < operand_b);
+ // 3'b111: BGEU (Branch if Greater Than or Equal, Unsigned)
+ BRANCH_GEU: branch_taken = (operand_a >= operand_b);
+ default: branch_taken = 1'b0;
+ endcase
+ end
+ end
+
+endmodule
diff --git a/rv64cpu/rtl/core/execute/compressed_decoder.sv b/rv64cpu/rtl/core/execute/compressed_decoder.sv
new file mode 100644
index 0000000..ccbb043
--- /dev/null
+++ b/rv64cpu/rtl/core/execute/compressed_decoder.sv
@@ -0,0 +1,234 @@
+// Richard CPU
+// Copyright (C) 2026 Richard Qin
+
+// File path: rtl/core/compressed_decoder.sv
+// Description: RV64C/Zca decompressor. Produces an equivalent 32-bit instruction
+// for the existing RV64 pipeline and flags reserved encodings.
+
+module compressed_decoder
+ import rv64_pkg::*;
+(
+ input logic [15:0] compressed_instr,
+ output logic [31:0] expanded_instr,
+ output logic illegal_instr
+);
+
+ logic [1:0] quadrant;
+ logic [2:0] funct3;
+ logic [4:0] rd_rs1;
+ logic [4:0] rs2;
+ logic [4:0] rd_prime;
+ logic [4:0] rs1_prime;
+ logic [4:0] rs2_prime;
+
+ assign quadrant = compressed_instr[1:0];
+ assign funct3 = compressed_instr[15:13];
+ assign rd_rs1 = compressed_instr[11:7];
+ assign rs2 = compressed_instr[6:2];
+ assign rd_prime = {2'b01, compressed_instr[4:2]};
+ assign rs1_prime = {2'b01, compressed_instr[9:7]};
+ assign rs2_prime = {2'b01, compressed_instr[4:2]};
+
+ function automatic logic [31:0] r_instr(
+ input logic [6:0] funct7,
+ input logic [4:0] rs2_i,
+ input logic [4:0] rs1_i,
+ input logic [2:0] funct3_i,
+ input logic [4:0] rd_i,
+ input logic [6:0] opcode_i
+ );
+ return {funct7, rs2_i, rs1_i, funct3_i, rd_i, opcode_i};
+ endfunction
+
+ function automatic logic [31:0] i_instr(
+ input logic [11:0] imm_i,
+ input logic [4:0] rs1_i,
+ input logic [2:0] funct3_i,
+ input logic [4:0] rd_i,
+ input logic [6:0] opcode_i
+ );
+ return {imm_i, rs1_i, funct3_i, rd_i, opcode_i};
+ endfunction
+
+ function automatic logic [31:0] s_instr(
+ input logic [11:0] imm_i,
+ input logic [4:0] rs2_i,
+ input logic [4:0] rs1_i,
+ input logic [2:0] funct3_i,
+ input logic [6:0] opcode_i
+ );
+ return {imm_i[11:5], rs2_i, rs1_i, funct3_i, imm_i[4:0], opcode_i};
+ endfunction
+
+ function automatic logic [31:0] b_instr(
+ input logic [12:0] imm_i,
+ input logic [4:0] rs2_i,
+ input logic [4:0] rs1_i,
+ input logic [2:0] funct3_i
+ );
+ return {imm_i[12], imm_i[10:5], rs2_i, rs1_i, funct3_i,
+ imm_i[4:1], imm_i[11], OPCODE_BRANCH};
+ endfunction
+
+ function automatic logic [31:0] u_instr(
+ input logic [19:0] imm_i,
+ input logic [4:0] rd_i,
+ input logic [6:0] opcode_i
+ );
+ return {imm_i, rd_i, opcode_i};
+ endfunction
+
+ function automatic logic [31:0] j_instr(
+ input logic [20:0] imm_i,
+ input logic [4:0] rd_i
+ );
+ return {imm_i[20], imm_i[10:1], imm_i[11], imm_i[19:12], rd_i, OPCODE_JAL};
+ endfunction
+
+ always_comb begin
+ logic [11:0] ci_imm;
+ logic [11:0] addi4spn_imm;
+ logic [11:0] lw_imm;
+ logic [11:0] ld_imm;
+ logic [11:0] lwsp_imm;
+ logic [11:0] ldsp_imm;
+ logic [11:0] swsp_imm;
+ logic [11:0] sdsp_imm;
+ logic [12:0] branch_imm;
+ logic [20:0] jump_imm;
+ logic [5:0] shamt;
+ logic [19:0] lui_imm;
+
+ ci_imm = {{6{compressed_instr[12]}}, compressed_instr[12], compressed_instr[6:2]};
+ addi4spn_imm = {2'b00, compressed_instr[10:7], compressed_instr[12:11],
+ compressed_instr[5], compressed_instr[6], 2'b00};
+ lw_imm = {5'b00000, compressed_instr[5], compressed_instr[12:10],
+ compressed_instr[6], 2'b00};
+ ld_imm = {4'b0000, compressed_instr[6:5], compressed_instr[12:10], 3'b000};
+ lwsp_imm = {4'b0000, compressed_instr[3:2], compressed_instr[12],
+ compressed_instr[6:4], 2'b00};
+ ldsp_imm = {3'b000, compressed_instr[4:2], compressed_instr[12],
+ compressed_instr[6:5], 3'b000};
+ swsp_imm = {4'b0000, compressed_instr[8:7], compressed_instr[12:9], 2'b00};
+ sdsp_imm = {3'b000, compressed_instr[9:7], compressed_instr[12:10], 3'b000};
+ branch_imm = {{4{compressed_instr[12]}}, compressed_instr[12],
+ compressed_instr[6:5], compressed_instr[2],
+ compressed_instr[11:10], compressed_instr[4:3], 1'b0};
+ jump_imm = {{9{compressed_instr[12]}}, compressed_instr[12],
+ compressed_instr[8], compressed_instr[10:9], compressed_instr[6],
+ compressed_instr[7], compressed_instr[2], compressed_instr[11],
+ compressed_instr[5:3], 1'b0};
+ shamt = {compressed_instr[12], compressed_instr[6:2]};
+ lui_imm = {{14{compressed_instr[12]}}, compressed_instr[12], compressed_instr[6:2]};
+
+ expanded_instr = 32'h0000_0013;
+ illegal_instr = 1'b0;
+
+ unique case (quadrant)
+ 2'b00: begin
+ unique case (funct3)
+ 3'b000: begin // C.ADDI4SPN
+ expanded_instr = i_instr(addi4spn_imm, 5'd2, 3'b000, rd_prime, OPCODE_OP_IMM);
+ illegal_instr = (addi4spn_imm == 12'b0);
+ end
+ 3'b001: expanded_instr = i_instr(ld_imm, rs1_prime, 3'b011, rd_prime, OPCODE_LOAD_FP);
+ 3'b010: expanded_instr = i_instr(lw_imm, rs1_prime, 3'b010, rd_prime, OPCODE_LOAD);
+ 3'b011: expanded_instr = i_instr(ld_imm, rs1_prime, 3'b011, rd_prime, OPCODE_LOAD);
+ 3'b101: expanded_instr = s_instr(ld_imm, rs2_prime, rs1_prime, 3'b011, OPCODE_STORE_FP);
+ 3'b110: expanded_instr = s_instr(lw_imm, rs2_prime, rs1_prime, 3'b010, OPCODE_STORE);
+ 3'b111: expanded_instr = s_instr(ld_imm, rs2_prime, rs1_prime, 3'b011, OPCODE_STORE);
+ default: illegal_instr = 1'b1;
+ endcase
+ end
+
+ 2'b01: begin
+ unique case (funct3)
+ 3'b000: expanded_instr = i_instr(ci_imm, rd_rs1, 3'b000, rd_rs1, OPCODE_OP_IMM);
+ 3'b001: expanded_instr = i_instr(ci_imm, rd_rs1, 3'b000, rd_rs1, OPCODE_OP_IMM_32);
+ 3'b010: expanded_instr = i_instr(ci_imm, 5'd0, 3'b000, rd_rs1, OPCODE_OP_IMM);
+ 3'b011: begin
+ if (rd_rs1 == 5'd2) begin
+ expanded_instr = i_instr({{2{compressed_instr[12]}}, compressed_instr[12],
+ compressed_instr[4:3], compressed_instr[5],
+ compressed_instr[2], compressed_instr[6], 4'b0000},
+ 5'd2, 3'b000, 5'd2, OPCODE_OP_IMM);
+ illegal_instr = (compressed_instr[12:2] == 11'b0);
+ end else begin
+ expanded_instr = u_instr(lui_imm, rd_rs1, OPCODE_LUI);
+ illegal_instr = (rd_rs1 == 5'd0) || (lui_imm == 20'b0);
+ end
+ end
+ 3'b100: begin
+ unique case (compressed_instr[11:10])
+ 2'b00: begin
+ expanded_instr = i_instr({6'b000000, shamt}, rs1_prime, 3'b101, rs1_prime, OPCODE_OP_IMM);
+ illegal_instr = (shamt == 6'b0);
+ end
+ 2'b01: begin
+ expanded_instr = i_instr({6'b010000, shamt}, rs1_prime, 3'b101, rs1_prime, OPCODE_OP_IMM);
+ illegal_instr = (shamt == 6'b0);
+ end
+ 2'b10: expanded_instr = i_instr(ci_imm, rs1_prime, 3'b111, rs1_prime, OPCODE_OP_IMM);
+ 2'b11: begin
+ unique case ({compressed_instr[12], compressed_instr[6:5]})
+ 3'b000: expanded_instr = r_instr(7'b0100000, rs2_prime, rs1_prime, 3'b000, rs1_prime, OPCODE_OP);
+ 3'b001: expanded_instr = r_instr(7'b0000000, rs2_prime, rs1_prime, 3'b100, rs1_prime, OPCODE_OP);
+ 3'b010: expanded_instr = r_instr(7'b0000000, rs2_prime, rs1_prime, 3'b110, rs1_prime, OPCODE_OP);
+ 3'b011: expanded_instr = r_instr(7'b0000000, rs2_prime, rs1_prime, 3'b111, rs1_prime, OPCODE_OP);
+ 3'b100: expanded_instr = r_instr(7'b0100000, rs2_prime, rs1_prime, 3'b000, rs1_prime, OPCODE_OP_32);
+ 3'b101: expanded_instr = r_instr(7'b0000000, rs2_prime, rs1_prime, 3'b000, rs1_prime, OPCODE_OP_32);
+ default: illegal_instr = 1'b1;
+ endcase
+ end
+ endcase
+ end
+ 3'b101: expanded_instr = j_instr(jump_imm, 5'd0);
+ 3'b110: expanded_instr = b_instr(branch_imm, 5'd0, rs1_prime, 3'b000);
+ 3'b111: expanded_instr = b_instr(branch_imm, 5'd0, rs1_prime, 3'b001);
+ default: illegal_instr = 1'b1;
+ endcase
+ end
+
+ 2'b10: begin
+ unique case (funct3)
+ 3'b000: begin
+ expanded_instr = i_instr({6'b000000, shamt}, rd_rs1, 3'b001, rd_rs1, OPCODE_OP_IMM);
+ illegal_instr = (shamt == 6'b0);
+ end
+ 3'b001: begin
+ expanded_instr = i_instr(ldsp_imm, 5'd2, 3'b011, rd_rs1, OPCODE_LOAD_FP);
+ end
+ 3'b010: begin
+ expanded_instr = i_instr(lwsp_imm, 5'd2, 3'b010, rd_rs1, OPCODE_LOAD);
+ illegal_instr = (rd_rs1 == 5'd0);
+ end
+ 3'b011: begin
+ expanded_instr = i_instr(ldsp_imm, 5'd2, 3'b011, rd_rs1, OPCODE_LOAD);
+ illegal_instr = (rd_rs1 == 5'd0);
+ end
+ 3'b100: begin
+ if (!compressed_instr[12] && (rs2 == 5'd0)) begin
+ expanded_instr = i_instr(12'b0, rd_rs1, 3'b000, 5'd0, OPCODE_JALR);
+ illegal_instr = (rd_rs1 == 5'd0);
+ end else if (!compressed_instr[12]) begin
+ expanded_instr = r_instr(7'b0000000, rs2, 5'd0, 3'b000, rd_rs1, OPCODE_OP);
+ end else if (compressed_instr[12] && (rd_rs1 == 5'd0) && (rs2 == 5'd0)) begin
+ expanded_instr = 32'h0010_0073; // EBREAK
+ end else if (compressed_instr[12] && (rs2 == 5'd0)) begin
+ expanded_instr = i_instr(12'b0, rd_rs1, 3'b000, 5'd1, OPCODE_JALR);
+ end else begin
+ expanded_instr = r_instr(7'b0000000, rs2, rd_rs1, 3'b000, rd_rs1, OPCODE_OP);
+ end
+ end
+ 3'b101: expanded_instr = s_instr(sdsp_imm, rs2, 5'd2, 3'b011, OPCODE_STORE_FP);
+ 3'b110: expanded_instr = s_instr(swsp_imm, rs2, 5'd2, 3'b010, OPCODE_STORE);
+ 3'b111: expanded_instr = s_instr(sdsp_imm, rs2, 5'd2, 3'b011, OPCODE_STORE);
+ default: illegal_instr = 1'b1;
+ endcase
+ end
+
+ default: illegal_instr = 1'b1;
+ endcase
+ end
+
+endmodule
diff --git a/rv64cpu/rtl/core/execute/hazard_unit.sv b/rv64cpu/rtl/core/execute/hazard_unit.sv
new file mode 100644
index 0000000..ee90356
--- /dev/null
+++ b/rv64cpu/rtl/core/execute/hazard_unit.sv
@@ -0,0 +1,200 @@
+// 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/hazard_unit.sv
+// Description: Hazard Control Unit
+// Handles data hazards (forwarding, Load-Use stalls), control hazards
+// (branch misprediction flushes), and memory stalls (D-Cache misses).
+
+module hazard_unit
+ import rv64_pkg::*;
+(
+ // -----------------------
+ // Inputs from ID Stage (for Load-Use detection)
+ // -----------------------
+ input logic [4:0] id_rs1_addr,
+ input logic [4:0] id_rs2_addr,
+
+ // -----------------------
+ // Inputs from EX Stage (ID/EX register outputs)
+ // -----------------------
+ input logic [4:0] ex_rs1_addr,
+ input logic [4:0] ex_rs2_addr,
+ input logic [4:0] ex_rd_addr,
+ input logic ex_reg_write_en,
+ input logic ex_is_load,
+ input logic ex_is_csr,
+ input logic ex_branch_taken,
+ input logic [XLEN-1:0] ex_branch_target,
+
+ // -----------------------
+ // Inputs from MEM Stage (EX/MEM register outputs)
+ // -----------------------
+ input logic [4:0] mem_rd_addr,
+ input logic mem_reg_write_en,
+ input logic mem_result_late,
+ input logic mem_is_csr,
+ input logic mem_stall_req, // D-Cache miss or memory busy
+
+ // -----------------------
+ // Trap Controller Inputs
+ // -----------------------
+ input logic trap_flush_req,
+ input logic trap_pc_sel,
+ input logic [XLEN-1:0] trap_target_pc,
+
+ // -----------------------
+ // Inputs from WB Stage (MEM/WB register outputs)
+ // -----------------------
+ input logic [4:0] wb_rd_addr,
+ input logic wb_reg_write_en,
+
+ // -----------------------
+ // Outputs: EX-stage operand forwarding mux selects
+ // -----------------------
+ // 00: no forward (use regfile), 01: forward from MEM, 10: forward from WB
+ output logic [1:0] forward_a_sel,
+ output logic [1:0] forward_b_sel,
+
+ // -----------------------
+ // Outputs: Pipeline stall and flush controls
+ // -----------------------
+ output logic if_stall_en,
+ output logic if_id_stall_en,
+ output logic id_ex_stall_en,
+ output logic ex_mem_stall_en,
+ output logic if_id_flush_en,
+ output logic id_ex_flush_en,
+
+ // -----------------------
+ // Outputs: IF-stage PC redirect controls
+ // -----------------------
+ output logic pc_redirect_sel,
+ output logic [XLEN-1:0] pc_redirect_target,
+ output logic pc_redirect_is_trap
+);
+
+ // -----------------------
+ // Data Forwarding (EX-stage operand bypass)
+ // -----------------------
+ // Forwarding resolves RAW hazards by routing results from
+ // MEM or WB back to the EX ALU inputs. We compare EX-stage
+ // source register addresses (the instruction currently in EX)
+ // against the destination registers of instructions in MEM/WB.
+ //
+ // Priority: MEM > WB (MEM result is more recent).
+
+ // Operand A (ex_rs1_addr)
+ always_comb begin
+ if (mem_reg_write_en && !mem_result_late && (mem_rd_addr != 5'b0) && (mem_rd_addr == ex_rs1_addr)) begin
+ forward_a_sel = 2'b01; // Forward from MEM stage
+ end else if (wb_reg_write_en && (wb_rd_addr != 5'b0) && (wb_rd_addr == ex_rs1_addr)) begin
+ forward_a_sel = 2'b10; // Forward from WB stage
+ end else begin
+ forward_a_sel = 2'b00; // No forwarding
+ end
+ end
+
+ // Operand B (ex_rs2_addr)
+ always_comb begin
+ if (mem_reg_write_en && !mem_result_late && (mem_rd_addr != 5'b0) && (mem_rd_addr == ex_rs2_addr)) begin
+ forward_b_sel = 2'b01; // Forward from MEM stage
+ end else if (wb_reg_write_en && (wb_rd_addr != 5'b0) && (wb_rd_addr == ex_rs2_addr)) begin
+ forward_b_sel = 2'b10; // Forward from WB stage
+ end else begin
+ forward_b_sel = 2'b00; // No forwarding
+ end
+ end
+
+ // -----------------------
+ // Load-Use Hazard Detection
+ // -----------------------
+ // If the instruction in EX is a load, and the instruction in
+ // ID reads from the load's destination, we must stall for 1
+ // cycle because the load data is not available until MEM.
+ logic load_use_stall;
+ assign load_use_stall = ex_is_load && (ex_rd_addr != 5'b0) &&
+ ((ex_rd_addr == id_rs1_addr) || (ex_rd_addr == id_rs2_addr));
+
+ logic csr_use_stall;
+ assign csr_use_stall = ((ex_is_csr && ex_reg_write_en && (ex_rd_addr != 5'b0) &&
+ ((ex_rd_addr == id_rs1_addr) || (ex_rd_addr == id_rs2_addr))) ||
+ (mem_is_csr && mem_reg_write_en && (mem_rd_addr != 5'b0) &&
+ ((mem_rd_addr == id_rs1_addr) || (mem_rd_addr == id_rs2_addr))));
+
+ // -----------------------
+ // PC Redirect Selection (Trap has top priority)
+ // -----------------------
+ always_comb begin
+ pc_redirect_sel = 1'b0;
+ pc_redirect_target = '0;
+ pc_redirect_is_trap = 1'b0;
+
+ if (trap_pc_sel) begin
+ pc_redirect_sel = 1'b1;
+ pc_redirect_target = trap_target_pc;
+ pc_redirect_is_trap = 1'b1;
+ end else if (ex_branch_taken) begin
+ pc_redirect_sel = 1'b1;
+ pc_redirect_target = ex_branch_target;
+ end
+ end
+
+ // -----------------------
+ // Stall and Flush Signal Generation
+ // -----------------------
+ always_comb begin
+ // Defaults: no stalls, no flushes
+ if_stall_en = 1'b0;
+ if_id_stall_en = 1'b0;
+ id_ex_stall_en = 1'b0;
+ ex_mem_stall_en = 1'b0;
+ if_id_flush_en = 1'b0;
+ id_ex_flush_en = 1'b0;
+
+ if (trap_flush_req) begin
+ if_id_flush_en = 1'b1;
+ id_ex_flush_en = 1'b1;
+ end else begin
+ // (1) Memory stall (D-Cache miss): freeze the entire pipeline
+ if (mem_stall_req) begin
+ if_stall_en = 1'b1;
+ if_id_stall_en = 1'b1;
+ id_ex_stall_en = 1'b1;
+ ex_mem_stall_en = 1'b1;
+ end
+
+ // (2) Value-use stall: freeze IF and IF/ID, insert bubble into ID/EX.
+ // Loads and CSR reads both produce their register result after EX.
+ if ((load_use_stall || csr_use_stall) && !mem_stall_req) begin
+ if_stall_en = 1'b1;
+ if_id_stall_en = 1'b1;
+ id_ex_flush_en = 1'b1;
+ end
+
+ // (3) Branch/jump misprediction: flush IF/ID and ID/EX
+ // Branch flush overrides load-use stall (the stalled instruction is invalid anyway)
+ if (ex_branch_taken) begin
+ if_id_flush_en = 1'b1;
+ id_ex_flush_en = 1'b1;
+ // Cancel load-use stall if branch is also taken
+ if_stall_en = 1'b0;
+ if_id_stall_en = 1'b0;
+ end
+ end
+ end
+
+endmodule
diff --git a/rv64cpu/rtl/core/execute/instruction_decoder.sv b/rv64cpu/rtl/core/execute/instruction_decoder.sv
new file mode 100644
index 0000000..14d9559
--- /dev/null
+++ b/rv64cpu/rtl/core/execute/instruction_decoder.sv
@@ -0,0 +1,672 @@
+// 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 RV64IMAFD base ISA + Zicsr + Zifencei
+// Translates 32-bit instructions into control signals for the pipeline.
+// C is handled by the IF-stage decompressor before this decoder.
+
+module instruction_decoder
+ import rv64_pkg::*;
+(
+ input logic [31:0] instr, // 32-bit instruction
+ input logic [1:0] priv_mode, // Current privilege mode from CSR
+ input logic mstatus_tsr, // Trap SRET in S-mode
+ input logic mstatus_tw, // Trap WFI in S/U-mode
+ input logic mstatus_tvm, // Trap supervisor memory-management ops in S-mode
+
+ // 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_en, // 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
+ output logic is_amo, // A extension atomic memory operation
+ output amo_op_t amo_op, // LR/SC/AMO operation selector
+ output logic is_fp,
+ output logic fp_reg_write_en,
+ output logic fp_store_data_sel,
+ output fp_op_t fp_op,
+ output fp_fmt_t fp_fmt,
+ output logic [2:0] fp_rm,
+
+ // 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
+ // ============================
+ typedef enum logic [1:0] {
+ PRIV_MODE_U = 2'b00,
+ PRIV_MODE_S = 2'b01,
+ PRIV_MODE_M = 2'b11
+ } priv_mode_t;
+
+ logic [6:0] opcode;
+ logic [2:0] funct3;
+ logic [6:0] funct7;
+ logic [4:0] amo_funct5;
+
+ assign opcode = instr[6:0];
+ assign rd_addr = instr[11:7];
+ assign funct3 = instr[14:12];
+ assign rs1_addr = (opcode == OPCODE_LUI) ? 5'b00000 : instr[19:15];
+ assign rs2_addr = instr[24:20];
+ assign funct7 = instr[31:25];
+ assign amo_funct5 = instr[31:27];
+
+ // ============================
+ // Immediate Generation
+ // ============================
+ logic [XLEN-1:0] imm_i, imm_s, imm_b, imm_u, imm_j;
+ logic [11:0] csr_addr_field;
+
+ // 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};
+ assign csr_addr_field = instr[31:20];
+
+ function automatic int priv_rank(logic [1:0] mode);
+ case (mode)
+ PRIV_MODE_U: return 0;
+ PRIV_MODE_S: return 1;
+ 2'b10: return 2; // Reserved/H-mode, treat as higher than S
+ default: return 3; // Machine mode (or anything else) highest
+ endcase
+ endfunction
+
+ function automatic logic priv_ge(logic [1:0] cur, logic [1:0] req);
+ return priv_rank(cur) >= priv_rank(req);
+ endfunction
+
+ function automatic logic csr_access_allowed(logic [1:0] cur, logic [11:0] addr);
+ logic [1:0] req_priv;
+ req_priv = addr[9:8];
+ return priv_ge(cur, req_priv);
+ endfunction
+
+ // ============================
+ // 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_en = 1'b0;
+ imm = '0;
+ is_load = 1'b0;
+ is_store = 1'b0;
+ mem_size = 3'b0;
+ is_amo = 1'b0;
+ amo_op = AMO_NONE;
+ is_fp = 1'b0;
+ fp_reg_write_en = 1'b0;
+ fp_store_data_sel = 1'b0;
+ fp_op = FP_OP_NONE;
+ fp_fmt = FP_FMT_S;
+ fp_rm = funct3;
+ 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_en = 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_en = 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_en = 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_en = 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_en = 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_en = 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_en = 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_en = 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_en = 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_en = 1'b1;
+ alu_src1_sel = 1'b0; // rs1
+ alu_src2_sel = 1'b0; // rs2
+
+ if (funct7 == 7'b0000001) begin
+ case (funct3)
+ 3'b000: alu_op = ALU_MUL;
+ 3'b001: alu_op = ALU_MULH;
+ 3'b010: alu_op = ALU_MULHSU;
+ 3'b011: alu_op = ALU_MULHU;
+ 3'b100: alu_op = ALU_DIV;
+ 3'b101: alu_op = ALU_DIVU;
+ 3'b110: alu_op = ALU_REM;
+ 3'b111: alu_op = ALU_REMU;
+ default: illegal_instr = 1'b1;
+ endcase
+ 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_en = 1'b1;
+ is_word_op = 1'b1;
+ alu_src1_sel = 1'b0; // rs1
+ alu_src2_sel = 1'b0; // rs2
+
+ if (funct7 == 7'b0000001) begin
+ case (funct3)
+ 3'b000: alu_op = ALU_MUL;
+ 3'b100: alu_op = ALU_DIV;
+ 3'b101: alu_op = ALU_DIVU;
+ 3'b110: alu_op = ALU_REM;
+ 3'b111: alu_op = ALU_REMU;
+ default: illegal_instr = 1'b1;
+ endcase
+ 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_en = 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: begin // MRET
+ if (priv_mode == PRIV_MODE_M)
+ is_mret = 1'b1;
+ else
+ illegal_instr = 1'b1;
+ end
+ 12'b0001000_00010: begin // SRET
+ if (!priv_ge(priv_mode, PRIV_MODE_S))
+ illegal_instr = 1'b1;
+ else if ((priv_mode == PRIV_MODE_S) && mstatus_tsr)
+ illegal_instr = 1'b1;
+ else
+ is_sret = 1'b1;
+ end
+ 12'b0001000_00101: begin // WFI
+ if ((priv_mode != PRIV_MODE_M) && mstatus_tw)
+ illegal_instr = 1'b1;
+ else
+ is_wfi = 1'b1;
+ end
+ default: begin
+ if (funct7 == 7'b0001001) begin
+ if (!priv_ge(priv_mode, PRIV_MODE_S))
+ illegal_instr = 1'b1;
+ else if ((priv_mode == PRIV_MODE_S) && mstatus_tvm)
+ illegal_instr = 1'b1;
+ else
+ is_sfence_vma = 1'b1; // SFENCE.VMA
+ end else begin
+ illegal_instr = 1'b1;
+ end
+ end
+ endcase
+ end else begin
+ // CSR instructions
+ // funct3: 001=CSRRW, 010=CSRRS, 011=CSRRC,
+ // 101=CSRRWI, 110=CSRRSI, 111=CSRRCI
+ reg_write_en = 1'b1;
+ is_csr = 1'b1;
+ csr_op = funct3;
+ imm = imm_i; // CSR address = instr[31:20] (reuse imm_i path)
+ if (!csr_access_allowed(priv_mode, csr_addr_field)) begin
+ reg_write_en = 1'b0;
+ is_csr = 1'b0;
+ illegal_instr = 1'b1;
+ end
+ end
+ end
+
+ // -----------------
+ // AMO — A extension (LR/SC/AMO*)
+ OPCODE_AMO: begin
+ reg_write_en = 1'b1;
+ is_amo = 1'b1;
+ alu_src1_sel = 1'b0;
+ alu_src2_sel = 1'b1;
+ imm = '0;
+ alu_op = ALU_ADD;
+ wb_sel = 2'b01;
+
+ unique case (funct3)
+ 3'b010: mem_size = 3'b010; // AMO*.W
+ 3'b011: mem_size = 3'b011; // AMO*.D
+ default: illegal_instr = 1'b1;
+ endcase
+
+ unique case (amo_funct5)
+ 5'b00010: begin
+ amo_op = AMO_LR;
+ if (rs2_addr != 5'd0) begin
+ illegal_instr = 1'b1;
+ end
+ end
+ 5'b00011: amo_op = AMO_SC;
+ 5'b00001: amo_op = AMO_SWAP;
+ 5'b00000: amo_op = AMO_ADD;
+ 5'b00100: amo_op = AMO_XOR;
+ 5'b01100: amo_op = AMO_AND;
+ 5'b01000: amo_op = AMO_OR;
+ 5'b10000: amo_op = AMO_MIN;
+ 5'b10100: amo_op = AMO_MAX;
+ 5'b11000: amo_op = AMO_MINU;
+ 5'b11100: amo_op = AMO_MAXU;
+ default: begin
+ amo_op = AMO_NONE;
+ illegal_instr = 1'b1;
+ end
+ endcase
+
+ if (illegal_instr) begin
+ reg_write_en = 1'b0;
+ is_amo = 1'b0;
+ amo_op = AMO_NONE;
+ end
+ end
+
+ // -----------------
+ // Floating-Point — F/D extensions
+ OPCODE_LOAD_FP: begin
+ is_fp = 1'b1;
+ is_load = 1'b1;
+ fp_reg_write_en = 1'b1;
+ alu_src1_sel = 1'b0;
+ alu_src2_sel = 1'b1;
+ imm = imm_i;
+ alu_op = ALU_ADD;
+ wb_sel = 2'b01;
+ unique case (funct3)
+ 3'b010: begin
+ mem_size = 3'b010; // FLW
+ fp_fmt = FP_FMT_S;
+ end
+ 3'b011: begin
+ mem_size = 3'b011; // FLD
+ fp_fmt = FP_FMT_D;
+ end
+ default: begin
+ is_fp = 1'b0;
+ fp_reg_write_en = 1'b0;
+ illegal_instr = 1'b1;
+ end
+ endcase
+ end
+
+ OPCODE_STORE_FP: begin
+ is_fp = 1'b1;
+ is_store = 1'b1;
+ fp_store_data_sel = 1'b1;
+ alu_src1_sel = 1'b0;
+ alu_src2_sel = 1'b1;
+ imm = imm_s;
+ alu_op = ALU_ADD;
+ unique case (funct3)
+ 3'b010: begin
+ mem_size = 3'b010; // FSW
+ fp_fmt = FP_FMT_S;
+ end
+ 3'b011: begin
+ mem_size = 3'b011; // FSD
+ fp_fmt = FP_FMT_D;
+ end
+ default: begin
+ is_fp = 1'b0;
+ fp_store_data_sel = 1'b0;
+ illegal_instr = 1'b1;
+ end
+ endcase
+ end
+
+ OPCODE_MADD,
+ OPCODE_MSUB,
+ OPCODE_NMSUB,
+ OPCODE_NMADD: begin
+ is_fp = 1'b1;
+ fp_reg_write_en = 1'b1;
+ fp_rm = funct3;
+ unique case (instr[26:25])
+ 2'b00: fp_fmt = FP_FMT_S;
+ 2'b01: fp_fmt = FP_FMT_D;
+ default: illegal_instr = 1'b1;
+ endcase
+ unique case (opcode)
+ OPCODE_MADD: fp_op = FP_OP_MADD;
+ OPCODE_MSUB: fp_op = FP_OP_MSUB;
+ OPCODE_NMSUB: fp_op = FP_OP_NMSUB;
+ default: fp_op = FP_OP_NMADD;
+ endcase
+ if (illegal_instr) begin
+ is_fp = 1'b0;
+ fp_reg_write_en = 1'b0;
+ fp_op = FP_OP_NONE;
+ end
+ end
+
+ OPCODE_OP_FP: begin
+ is_fp = 1'b1;
+ fp_rm = funct3;
+ unique case (instr[26:25])
+ 2'b00: fp_fmt = FP_FMT_S;
+ 2'b01: fp_fmt = FP_FMT_D;
+ default: illegal_instr = 1'b1;
+ endcase
+
+ unique case (funct7)
+ 7'b0000000, 7'b0000001: begin fp_op = FP_OP_ADD; fp_reg_write_en = 1'b1; end
+ 7'b0000100, 7'b0000101: begin fp_op = FP_OP_SUB; fp_reg_write_en = 1'b1; end
+ 7'b0001000, 7'b0001001: begin fp_op = FP_OP_MUL; fp_reg_write_en = 1'b1; end
+ 7'b0001100, 7'b0001101: begin fp_op = FP_OP_DIV; fp_reg_write_en = 1'b1; end
+ 7'b0101100, 7'b0101101: begin
+ fp_op = FP_OP_SQRT;
+ fp_reg_write_en = 1'b1;
+ if (rs2_addr != 5'd0) illegal_instr = 1'b1;
+ end
+ 7'b0010000, 7'b0010001: begin
+ fp_reg_write_en = 1'b1;
+ unique case (funct3)
+ 3'b000: fp_op = FP_OP_SGNJ;
+ 3'b001: fp_op = FP_OP_SGNJN;
+ 3'b010: fp_op = FP_OP_SGNJX;
+ default: illegal_instr = 1'b1;
+ endcase
+ end
+ 7'b0010100, 7'b0010101: begin
+ fp_reg_write_en = 1'b1;
+ unique case (funct3)
+ 3'b000: fp_op = FP_OP_MIN;
+ 3'b001: fp_op = FP_OP_MAX;
+ default: illegal_instr = 1'b1;
+ endcase
+ end
+ 7'b0100000, 7'b0100001: begin
+ fp_op = FP_OP_CVT_F2F;
+ fp_reg_write_en = 1'b1;
+ end
+ 7'b1010000, 7'b1010001: begin
+ reg_write_en = 1'b1;
+ unique case (funct3)
+ 3'b010: fp_op = FP_OP_EQ;
+ 3'b001: fp_op = FP_OP_LT;
+ 3'b000: fp_op = FP_OP_LE;
+ default: illegal_instr = 1'b1;
+ endcase
+ end
+ 7'b1100000, 7'b1100001: begin
+ fp_op = FP_OP_CVT_F2I;
+ reg_write_en = 1'b1;
+ end
+ 7'b1101000, 7'b1101001: begin
+ fp_op = FP_OP_CVT_I2F;
+ fp_reg_write_en = 1'b1;
+ end
+ 7'b1110000, 7'b1110001: begin
+ if (rs2_addr == 5'd0 && funct3 == 3'b000) begin
+ fp_op = FP_OP_MV_F2X;
+ reg_write_en = 1'b1;
+ end else if (rs2_addr == 5'd0 && funct3 == 3'b001) begin
+ fp_op = FP_OP_CLASS;
+ reg_write_en = 1'b1;
+ end else begin
+ illegal_instr = 1'b1;
+ end
+ end
+ 7'b1111000, 7'b1111001: begin
+ if (rs2_addr == 5'd0 && funct3 == 3'b000) begin
+ fp_op = FP_OP_MV_X2F;
+ fp_reg_write_en = 1'b1;
+ end else begin
+ illegal_instr = 1'b1;
+ end
+ end
+ default: illegal_instr = 1'b1;
+ endcase
+
+ if (illegal_instr) begin
+ is_fp = 1'b0;
+ fp_reg_write_en = 1'b0;
+ reg_write_en = 1'b0;
+ fp_op = FP_OP_NONE;
+ end
+ end
+
+ // -----------------
+ // Unrecognised opcode
+ default: begin
+ illegal_instr = 1'b1;
+ end
+
+ endcase
+ end
+
+endmodule
diff --git a/rv64cpu/rtl/core/execute/integer_alu.sv b/rv64cpu/rtl/core/execute/integer_alu.sv
new file mode 100644
index 0000000..833f902
--- /dev/null
+++ b/rv64cpu/rtl/core/execute/integer_alu.sv
@@ -0,0 +1,123 @@
+// 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/alu.sv
+// Description: 64-bit Arithmetic Logic Unit (ALU)
+
+module integer_alu
+ import rv64_pkg::*;
+(
+ input alu_op_t alu_op, // ALU operation select (from decoder)
+ input logic [XLEN-1:0] operand_a, // Operand 1 (register rs1 value or PC)
+ input logic [XLEN-1:0] operand_b, // Operand 2 (register rs2 value or immediate)
+
+ output logic [XLEN-1:0] alu_result // ALU result
+);
+
+ // Shift amount: RV64I uses lower 6 bits of rs2 (shamt[5:0])
+ logic [5:0] shamt;
+ assign shamt = operand_b[5:0];
+
+ logic signed [XLEN-1:0] operand_a_signed;
+ logic signed [XLEN-1:0] operand_b_signed;
+ logic signed [127:0] mul_signed_signed;
+ logic signed [127:0] mul_signed_unsigned;
+ logic [127:0] mul_unsigned_unsigned;
+
+ assign operand_a_signed = operand_a;
+ assign operand_b_signed = operand_b;
+ assign mul_signed_signed = operand_a_signed * operand_b_signed;
+ assign mul_signed_unsigned = operand_a_signed * $signed({1'b0, operand_b});
+ assign mul_unsigned_unsigned = operand_a * operand_b;
+
+ always_comb begin
+ alu_result = '0;
+
+ case (alu_op)
+ ALU_ADD: alu_result = operand_a + operand_b;
+ ALU_SUB: alu_result = operand_a - operand_b;
+
+ // Logical left shift: the shift amount is the lower 6 bits of rs2
+ ALU_SLL: alu_result = operand_a << shamt;
+
+ // Set less than (signed)
+ ALU_SLT: alu_result = {63'b0, $signed(operand_a) < $signed(operand_b)};
+
+ // Set less than (unsigned)
+ ALU_SLTU: alu_result = {63'b0, operand_a < operand_b};
+
+ ALU_XOR: alu_result = operand_a ^ operand_b;
+
+ // Logical right shift: the shift amount is the lower 6 bits of rs2
+ ALU_SRL: alu_result = operand_a >> shamt;
+
+ // Arithmetic right shift: the shift amount is the lower 6 bits of rs2
+ ALU_SRA: alu_result = $unsigned($signed(operand_a) >>> shamt);
+
+ ALU_OR: alu_result = operand_a | operand_b;
+ ALU_AND: alu_result = operand_a & operand_b;
+
+ ALU_MUL: begin
+ alu_result = mul_signed_signed[XLEN-1:0];
+ end
+
+ ALU_MULH: begin
+ alu_result = mul_signed_signed[127:64];
+ end
+
+ ALU_MULHSU: begin
+ alu_result = mul_signed_unsigned[127:64];
+ end
+
+ ALU_MULHU: begin
+ alu_result = mul_unsigned_unsigned[127:64];
+ end
+
+ ALU_DIV: begin
+ if (operand_b == '0) begin
+ alu_result = {XLEN{1'b1}};
+ end else if ((operand_a == 64'h8000_0000_0000_0000) &&
+ (operand_b == 64'hFFFF_FFFF_FFFF_FFFF)) begin
+ alu_result = operand_a;
+ end else begin
+ alu_result = $unsigned(operand_a_signed / operand_b_signed);
+ end
+ end
+
+ ALU_DIVU: begin
+ alu_result = (operand_b == '0) ? {XLEN{1'b1}} : (operand_a / operand_b);
+ end
+
+ ALU_REM: begin
+ if (operand_b == '0) begin
+ alu_result = operand_a;
+ end else if ((operand_a == 64'h8000_0000_0000_0000) &&
+ (operand_b == 64'hFFFF_FFFF_FFFF_FFFF)) begin
+ alu_result = '0;
+ end else begin
+ alu_result = $unsigned(operand_a_signed % operand_b_signed);
+ end
+ end
+
+ ALU_REMU: begin
+ alu_result = (operand_b == '0) ? operand_a : (operand_a % operand_b);
+ end
+
+ default: alu_result = '0;
+ endcase
+ end
+
+endmodule