//  *********************************************************************************************
//  Project Version  : v1.0
//  Project          : [BCDC] Microtec Academy Course: Building a RISC-V CPU with SystemVerilog
//  -----
//  Copyright (c)    : 2025 Fraunhofer IIS, Department IDS
//  Created          : 12.Jun.2025  by Lund University [commit 5b1e415]
//  Last Modified    : 23.Oct.2025  by Hussein Elzomor [commit 2f8f03d]
//  -----
//  HISTORY          : Date         By         Comments
//                     -----------  ---------  -------------------------------------------------
//                     15.Oct.2025  H.Elzomor  Moved PC and ALU logic to their respective files
//                     15.Oct.2025  H.Elzomor  Absorbed the branching logic into the decoder logic
//  *********************************************************************************************



module decoder (
  // PC
  input  logic[31:0] CurrentPC,
  output logic[31:0] JumpOrBranchPC,
  output logic       JumpOrBranch,
  // Memory
  output logic[31:0] DAddr,
  output logic[31:0] WData,
  input  logic[31:0] RData,
  input  logic[31:0] Instruction,
  output logic       WrMem,
  output logic[1:0]  DWidth,
  // Register File
  output logic[4:0]  Rs1,
  output logic[4:0]  Rs2,
  output logic[4:0]  Rd,
  input  logic[31:0] RRs1,
  input  logic[31:0] RRs2,
  output logic[31:0] WRd,
  output logic       WrReg,
  // Protection
  output logic       Illegal
);

  // Local Parameters
    // OpCode: set a local parameter for each operation
    localparam logic[6:0] opLd     = 7'b0000011;
    localparam logic[6:0] opAluImm = 7'b0010011;
    localparam logic[6:0] opUpPC   = 7'b0010111;
    localparam logic[6:0] opSt     = 7'b0100011;
    localparam logic[6:0] opAlu    = 7'b0110011;
    localparam logic[6:0] opUpImm  = 7'b0110111;
    localparam logic[6:0] opBranch = 7'b1100011;
    localparam logic[6:0] opJALR   = 7'b1100111;
    localparam logic[6:0] opJAL    = 7'b1101111;

    // Func7: set a local parameter for each function 7
    localparam logic[6:0] f7neg    = 7'b0100000;

    // Func3: set a local parameter for each function 3
      // Load/Store
      localparam logic[2:0] f3byte   = 3'b000;
      localparam logic[2:0] f3half   = 3'b001;
      localparam logic[2:0] f3word   = 3'b010;
      localparam logic[2:0] f3byteU  = 3'b100;
      localparam logic[2:0] f3halfU  = 3'b101;
      // ALU
      localparam logic[2:0] f3add    = 3'b000;
      localparam logic[2:0] f3sl     = 3'b001;
      localparam logic[2:0] f3slt    = 3'b010;
      localparam logic[2:0] f3sltU   = 3'b011;
      localparam logic[2:0] f3xor    = 3'b100;
      localparam logic[2:0] f3sr     = 3'b101;
      localparam logic[2:0] f3or     = 3'b110;
      localparam logic[2:0] f3and    = 3'b111;
      // Branch
      localparam logic[2:0] f3beq    = 3'b000;
      localparam logic[2:0] f3bne    = 3'b001;
      localparam logic[2:0] f3blt    = 3'b100;
      localparam logic[2:0] f3bge    = 3'b101;
      localparam logic[2:0] f3bltU   = 3'b110;
      localparam logic[2:0] f3bgeU   = 3'b111;

  // Local Signals
    // Instruction breakdown (excluding I/O)
    logic[6:0]  theOp;
    logic[2:0]  theFunct3;
    logic[6:0]  theFunct7;
    logic[31:0] i_imm;
    logic[31:0] s_imm;
    logic[31:0] b_imm;
    logic[31:0] u_imm;
    logic[31:0] j_imm;
    // ALU
    logic[2:0]  aluOp;
    logic       aluNegAr;
    logic       aluBypass;
    logic[31:0] op1;
    logic[31:0] op2;
    logic[31:0] result;
    logic       eqFlag;

  // Instruction breakdown: assign values
    // OpCode and functions 3/7
    assign theOp     = Instruction[6:0];
    assign theFunct3 = Instruction[14:12];
    assign theFunct7 = Instruction[31:25];
    // Registers
    assign Rs1   = Instruction[19:15];
    assign Rs2   = Instruction[24:20];
    assign Rd    = Instruction[11:7];
    // Immediates
    always_comb begin : Immediate_Generator
        i_imm = {{21{Instruction[31]}}, Instruction[30:20]};
        s_imm = {{21{Instruction[31]}}, Instruction[30:25], Instruction[11:7]};
        b_imm = {{20{Instruction[31]}}, Instruction[7], Instruction[30:25], Instruction[11:8], 1'b0};
        u_imm = {Instruction[31:12], 12'b0};
        j_imm = {{12{Instruction[31]}}, Instruction[19:12], Instruction[20], Instruction[30:21], 1'b0};
    end

  // Decoder Logic
  always_comb begin : Main_Decoder
    // Factored port/signal values
    JumpOrBranch = '0;
    JumpOrBranchPC  = '0;
    DAddr     = '0;
    WData     = RRs2;
    WrMem     = '0;
    DWidth    = f3word[1:0];
    WrReg     = '1;
    Illegal   = '0;
    aluOp     = theFunct3;
    aluNegAr  = '0;
    aluBypass = '0;
    op1       = RRs1;
    op2       = RRs2;
    // OpCode Cases
    case(theOp)
      opLd: begin
        DAddr = RRs1 + i_imm;
        DWidth = theFunct3[1:0];
        aluBypass = '1;
        op1 = RData;
        case(theFunct3)
          f3byte:  op1[31:8]  = {24{RData[7]}};
          f3byteU: op1[31:8]  = {24{1'b0}};
          f3half:  op1[31:16] = {16{RData[15]}};
          f3halfU: op1[31:16] = {16{1'b0}};
          f3word:  ;
          default: begin
            Illegal = '1;
            WrReg = '0;
            JumpOrBranch = '1;
            JumpOrBranchPC = CurrentPC;
          end
        endcase
      end
      opAluImm: begin
        op2 = i_imm;
        aluOp = theFunct3;
        aluNegAr = (theFunct7 == f7neg) & (theFunct3 == f3sr);
      end
      opUpPC: begin
        op1 = u_imm;
        op2 = CurrentPC;
        aluOp = f3add;
      end
      opSt: begin
        WrReg = '0;
        WrMem = '1;
        DAddr = RRs1 + s_imm;
        DWidth = theFunct3[1:0];
      end
      opAlu: begin
        aluOp = theFunct3;
        aluNegAr = (theFunct7 == f7neg) & ((theFunct3 == f3add) | (theFunct3 == f3sr));
      end
      opUpImm: begin
        op1 = u_imm;
        aluBypass = '1;
      end
      opBranch: begin
        WrReg = '0;
        JumpOrBranchPC = CurrentPC + b_imm;
        aluOp = f3slt;
        case(theFunct3)
          f3beq : begin JumpOrBranch = ( eqFlag)? '1 : '0; end
          f3bne : begin JumpOrBranch = (~eqFlag)? '1 : '0; end
          f3blt : begin JumpOrBranch = ( result[0])? '1 : '0; end
          f3bge : begin JumpOrBranch = (~result[0])? '1 : '0; end
          f3bltU: begin aluOp = f3sltU; JumpOrBranch = ( result[0])? '1 : '0; end
          f3bgeU: begin aluOp = f3sltU; JumpOrBranch = (~result[0])? '1 : '0; end
          default: begin
            Illegal = '1;
            JumpOrBranch = '1;
            JumpOrBranchPC = CurrentPC;
          end
        endcase
      end
      opJALR: begin
        JumpOrBranch = '1;
        JumpOrBranchPC = (RRs1 + i_imm) & 32'hFFFFFFFE;
        op1 = CurrentPC;
        op2 = 4;
        aluOp = f3add;
      end
      opJAL: begin
        JumpOrBranch = '1;
        JumpOrBranchPC = CurrentPC + j_imm;
        op1 = CurrentPC;
        op2 = 4;
        aluOp = f3add;
      end
      default: begin
        Illegal = '1;
        WrReg = '0;
        JumpOrBranch = '1;
        JumpOrBranchPC = CurrentPC;
      end
    endcase
  end

  // ALU module instantiation
  alu theALU (
      .aluOp(aluOp),
      .aluNegAr(aluNegAr),
      .aluBypass(aluBypass),
      .op1(op1),
      .op2(op2),
      .result(result),
      .eqFlag(eqFlag)
  );
  assign WRd = result;

endmodule
