/*!\file decode.sv
 * RS5 VERSION - 1.1.0 - Pipeline Simplified and Core Renamed
 *
 * Distribution:  July 2023
 *
 * Willian Nunes    <willian.nunes@edu.pucrs.br>
 * Angelo Dal Zotto <angelo.dalzotto@edu.pucrs.br>
 * Marcos Sartori   <marcos.sartori@acad.pucrs.br>
 * Ney Calazans     <ney.calazans@ufsc.br>
 * Fernando Moraes  <fernando.moraes@pucrs.br>
 * GAPH - Hardware Design Support Group
 * PUCRS - Pontifical Catholic University of Rio Grande do Sul <https://pucrs.br/>
 *
 * \brief
 * Decoder Unit is the second stage of RS5 processor core.
 *
 * \detailed
 * The decoder unit is the second stage of the RS5 processor core and
 * is responsible for identifying the instruction format and operation,
 * fetching the operands in the register bank, and calculating the immediate
 * operand. It contains the mechanism of hazard detection, if a hazard is
 * detected (e.g. one operand is a locked register) a bubble is issued, which
 * consists in a NOP (NO Operation) instruction.
 */

`include "RS5_pkg.sv"

module decode
    import RS5_pkg::*;
#(
    parameter mul_e         MULEXT       = MUL_M,
    parameter atomic_e      AMOEXT       = AMO_A,
    parameter bit           COMPRESSED   = 1'b1,
    parameter bit           ZKNEEnable   = 1'b0,
    parameter bit           ZBKBEnable   = 1'b0,
    parameter bit           ZKNHEnable   = 1'b0,
    parameter bit           XKYBEREnable = 1'b0,
    parameter bit           ZICONDEnable = 1'b0,
    parameter bit           VEnable      = 1'b0,
    parameter bit           BRANCHPRED   = 1'b1,
    parameter bit           FORWARDING   = 1'b1
)
(
    input   logic           clk,
    input   logic           reset_n,
    input   logic           enable,
    input   logic           sys_reset,
    input   logic           valid_i,

    input   logic [31:0]    instruction_i,
    input   logic [31:0]    pc_i,
    input   logic [31:0]    rs1_data_read_i,
    input   logic [31:0]    rs2_data_read_i,
    input   logic [31:0]    mem_access_result_i,
    input   logic [31:0]    writeback_i,
    input   logic [ 4:0]    rd_mem_access_i,
    input   logic [ 4:0]    rd_retire_i,
    input   logic           mem_access_we_i,
    input   logic           regbank_we_i,
    input   logic           compressed_i,
    input   logic           should_jump_i,

    /* Not used without forwarding */
    /* verilator lint_off UNUSEDSIGNAL */
    input   logic [31:0]    result_i,
    input   logic           execute_we_i,
    /* verilator lint_on UNUSEDSIGNAL */

    output  logic  [4:0]    rs1_o,
    output  logic  [4:0]    rs2_o,
    output  logic  [4:0]    rd_o,
    output  logic  [4:0]    instr_rs1_o,
    output  logic [11:0]    csr_address_o,
    output  logic [31:0]    rs1_data_o,
    output  logic [31:0]    rs2_data_o,
    output  logic [31:0]    second_operand_o,
    output  logic [31:0]    pc_o,
    output  logic [31:0]    instruction_o,
    output  logic [31:0]    jump_imm_target_o,

    output  logic           compressed_o,
    output  iType_e         instruction_operation_o,
    output  iTypeAtomic_e   atomic_operation_o,
    output  iTypeVector_e   vector_operation_o,
    output  logic           dec_hazard_o,
    output  logic           hazard_o,
    output  logic           killed_o,

    /* Not used without BP */
    /* verilator lint_off UNUSEDSIGNAL */
    input   logic           jump_rollback_i,
    /* verilator lint_on UNUSEDSIGNAL */
    input   logic           jumping_i,
    /* Not used without C */
    /* verilator lint_off UNUSEDSIGNAL */
    input   logic           jump_misaligned_i,
    /* verilator lint_off UNUSEDSIGNAL */
    output  logic           bp_take_o,
    output  logic           bp_taken_o,
    output  logic [31:0]    bp_target_o,

    input   logic           exc_inst_access_fault_i,
    output  logic           exc_inst_access_fault_o,
    output  logic           exc_ilegal_inst_o
);

//////////////////////////////////////////////////////////////////////////////
// Find out the type of the instruction
//////////////////////////////////////////////////////////////////////////////

    logic   killed;
    logic   hazard;
    iType_e instruction_operation;

    logic [2:0] funct3;
    logic [6:0] funct7;
    logic [4:0] opcode;

    assign funct3 = instruction_i[14:12];
    assign funct7 = instruction_i[31:25];
    assign opcode = instruction_i[6:2];

    iType_e decode_branch;
    always_comb begin
        unique case (funct3)
            3'b000:     decode_branch = BEQ;
            3'b001:     decode_branch = BNE;
            3'b100:     decode_branch = BLT;
            3'b101:     decode_branch = BGE;
            3'b110:     decode_branch = BLTU;
            3'b111:     decode_branch = BGEU;
            default:    decode_branch = INVALID;
        endcase
    end

    iType_e decode_load;
    always_comb begin
        unique case (funct3)
            3'b000:     decode_load = LB;
            3'b001:     decode_load = LH;
            3'b010:     decode_load = LW;
            3'b100:     decode_load = LBU;
            3'b101:     decode_load = LHU;
            default:    decode_load = INVALID;
        endcase
    end

    iType_e decode_store;
    always_comb begin
        unique case (funct3)
            3'b000:     decode_store = SB;
            3'b001:     decode_store = SH;
            3'b010:     decode_store = SW;
            default:    decode_store = INVALID;
        endcase
    end

    iType_e sha2_type;
    always_comb begin 
        unique case (instruction_i[26:20])
            7'b000_0010: sha2_type = SIG0; // sha256sig0
            7'b000_0011: sha2_type = SIG1; // sha256sig1
            7'b000_0000: sha2_type = SUM0; // sha256sum0
            7'b000_0001: sha2_type = SUM1; // sha256sum1
            default: sha2_type = INVALID;
        endcase
    end


    iType_e brev_op;
    always_comb begin
        unique case (instruction_i[26:20])
            7'b000_0111: brev_op = ALU_BREV8;  // brev8: imm[6:0] = 0000111 (imm = 0x687)
            7'b001_1000: brev_op = ALU_REV8;   // rev8:  imm[6:0] = 0011000 (imm = 0x698)
            default:     brev_op = INVALID;
        endcase
    end


    iType_e decode_op_imm;
    always_comb begin
        unique case ({funct7, funct3}) inside
            10'b???????000:     decode_op_imm = ADD;    /* ADDI */
            10'b0000000001:     decode_op_imm = SLL;    /* SLLI */
            10'b???????010:     decode_op_imm = SLT;    /* SLTI */
            10'b???????011:     decode_op_imm = SLTU;   /* SLTIU */
            10'b???????100:     decode_op_imm = XOR;    /* XORI */
            10'b0000000101:     decode_op_imm = SRL;    /* SRLI */
            10'b0100000101:     decode_op_imm = SRA;    /* SRAI */
            10'b???????110:     decode_op_imm = OR;     /* ORI */
            10'b???????111:     decode_op_imm = AND;    /* ANDI */
            10'b0001000001:     decode_op_imm = (ZKNHEnable) ? sha2_type  : INVALID; 
            10'b01100??101:     decode_op_imm = (ZBKBEnable) ? ALU_ROR    : INVALID;  
            10'b0000100001:     decode_op_imm = (ZBKBEnable) ? ALU_ZIP    : INVALID;
            10'b01101??101:     decode_op_imm = (ZBKBEnable) ? brev_op    : INVALID;
            10'b00001??101:     decode_op_imm = (ZBKBEnable) ? ALU_UNZIP  : INVALID;
            default:            decode_op_imm = INVALID;
        endcase
    end

    iType_e decode_op;
    always_comb begin
        unique case ({funct7, funct3}) inside
            10'b0000000000:     decode_op = ADD;
            10'b0100000000:     decode_op = SUB;
            10'b0000000001:     decode_op = SLL;
            10'b0000000010:     decode_op = SLT;
            10'b0000000011:     decode_op = SLTU;
            10'b0000000100:     decode_op = XOR;
            10'b0000000101:     decode_op = SRL;
            10'b0100000101:     decode_op = SRA;
            10'b0000000110:     decode_op = OR;
            10'b0000000111:     decode_op = AND;
            10'b0000001000:     decode_op = (MULEXT != MUL_OFF) ? MUL    : INVALID;
            10'b0000001001:     decode_op = (MULEXT != MUL_OFF) ? MULH   : INVALID;
            10'b0000001010:     decode_op = (MULEXT != MUL_OFF) ? MULHSU : INVALID;
            10'b0000001011:     decode_op = (MULEXT != MUL_OFF) ? MULHU  : INVALID;
            10'b0000001100:     decode_op = (MULEXT == MUL_M  ) ? DIV    : INVALID;
            10'b0000001101:     decode_op = (MULEXT == MUL_M  ) ? DIVU   : INVALID;
            10'b0000001110:     decode_op = (MULEXT == MUL_M  ) ? REM    : INVALID;
            10'b0000001111:     decode_op = (MULEXT == MUL_M  ) ? REMU   : INVALID;
            10'b??10001000:     decode_op = ZKNEEnable   ? AES32ESI  : INVALID;
            10'b??10011000:     decode_op = ZKNEEnable   ? AES32ESMI : INVALID;
            10'b0000111101:     decode_op = ZICONDEnable ? CZERO_EQZ : INVALID;
            10'b0000111111:     decode_op = ZICONDEnable ? CZERO_NEZ : INVALID;

            10'b0101110000:     decode_op = ZKNHEnable   ? SIG0H : INVALID; //sha512sig0h 
            10'b0101010000:     decode_op = ZKNHEnable   ? SIG0L : INVALID; //sha512sig0l 
            10'b0101111000:     decode_op = ZKNHEnable   ? SIG1H : INVALID; //sha512sig1h
            10'b0101011000:     decode_op = ZKNHEnable   ? SIG1L : INVALID; //sha512sig1l
            10'b0101000000:     decode_op = ZKNHEnable   ? SUM0R : INVALID; //sha512sum0r
            10'b0101001000:     decode_op = ZKNHEnable   ? SUM1R : INVALID; //sha512sum1r
            // ZBKB ALU Operations
            10'b0110000001:     decode_op = (ZBKBEnable) ? ALU_ROL   : INVALID;
            10'b0110000101:     decode_op = (ZBKBEnable) ? ALU_ROR   : INVALID; 
            10'b0000100100:     decode_op = (ZBKBEnable) ? ALU_PACK  : INVALID;
            10'b0000100111:     decode_op = (ZBKBEnable) ? ALU_PACKH : INVALID; 
            10'b0100000100:     decode_op = (ZBKBEnable) ? ALU_XNOR  : INVALID;
            10'b0100000110:     decode_op = (ZBKBEnable) ? ALU_ORN   : INVALID;
            10'b0100000111:     decode_op = (ZBKBEnable) ? ALU_ANDN  : INVALID;
            default:            decode_op = INVALID;
        endcase
    end

    iType_e decode_xkyber;
    always_comb begin
        
        unique case (funct7)

          7'd0: decode_xkyber = KYBER_ADD;
          7'd1: decode_xkyber = KYBER_SUB;
          7'd2: decode_xkyber = KYBER_MUL;
          7'd3: decode_xkyber = KYBER_COMPRESS;
          7'd4: decode_xkyber = (instruction_i[24:20] == 5'd3) ? KYBER_CBD3 : KYBER_CBD2;
          default: decode_xkyber = SLTU;

        endcase

    end

    iType_e decode_misc_mem;
    always_comb begin
        unique case (funct3)
            3'b000:     decode_misc_mem = NOP;  /* FENCE */
            default:    decode_misc_mem = INVALID;
        endcase
    end

    iType_e decode_system;
    always_comb begin
        unique case (instruction_i[31:7]) inside
            25'b0000000000000000000000000:  decode_system = ECALL;
            25'b0000000000010000000000000:  decode_system = EBREAK;
            25'b0001000000100000000000000:  decode_system = SRET;
            25'b0011000000100000000000000:  decode_system = MRET;
            25'b0001000001010000000000000:  decode_system = WFI;
            25'b?????????????????001?????:  decode_system = CSRRW;
            25'b?????????????????010?????:  decode_system = CSRRS;
            25'b?????????????????011?????:  decode_system = CSRRC;
            25'b?????????????????101?????:  decode_system = CSRRWI;
            25'b?????????????????110?????:  decode_system = CSRRSI;
            25'b?????????????????111?????:  decode_system = CSRRCI;
            default:                        decode_system = INVALID;
        endcase
    end

    logic         amo_invalid;
    iType_e       decode_atomic;
    if (AMOEXT != AMO_OFF) begin : gen_amo_on
        always_comb begin
            unique case (funct7[6:2])
                5'b00010: decode_atomic = (AMOEXT != AMO_ZAAMO)  ? LR_W  : INVALID;
                5'b00011: decode_atomic = (AMOEXT != AMO_ZAAMO)  ? SC_W  : INVALID;
                default:  decode_atomic = (AMOEXT != AMO_ZALRSC) ? AMO_W : INVALID;
            endcase
        end

        if (AMOEXT != AMO_ZALRSC) begin : gen_zaamo_on
            iTypeAtomic_e decode_amo;
            always_comb begin
                unique case (funct7[6:2])
                    5'b00000: decode_amo = AMOADD;
                    5'b00001: decode_amo = AMOSWAP;
                    5'b00100: decode_amo = AMOXOR;
                    5'b01000: decode_amo = AMOOR;
                    5'b01100: decode_amo = AMOAND;
                    5'b10000: decode_amo = AMOMIN;
                    5'b10100: decode_amo = AMOMAX;
                    5'b11000: decode_amo = AMOMINU;
                    5'b11100: decode_amo = AMOMAXU;
                    default:  decode_amo = AMONOP;
                endcase
            end

            assign amo_invalid = (instruction_operation == AMO_W) && (decode_amo == AMONOP);

            always_ff @(posedge clk or negedge reset_n) begin
                if (!reset_n)
                    atomic_operation_o <= AMONOP;
                else if (enable)
                    atomic_operation_o <= decode_amo;
            end
        end
        else begin : gen_zaamo_off
            assign amo_invalid        = 1'b0;
            assign atomic_operation_o = AMONOP;
        end
    end
    else begin : gen_amo_off
        assign decode_atomic      = INVALID;
        assign amo_invalid        = 1'b0;
        assign atomic_operation_o = AMONOP;
    end

    always_comb begin
        unique case (opcode)
            5'b01101: instruction_operation = LUI;
            5'b00101: instruction_operation = AUIPC;
            5'b11011: instruction_operation = JAL;
            5'b11001: instruction_operation = JALR;
            5'b11000: instruction_operation = decode_branch;              /* BRANCH */
            5'b00000: instruction_operation = decode_load;                /* LOAD */
            5'b01000: instruction_operation = decode_store;               /* STORE */
            5'b00100: instruction_operation = decode_op_imm;              /* OP-IMM */
            5'b01100: instruction_operation = decode_op;                  /* OP */
            5'b00011: instruction_operation = decode_misc_mem;            /* MISC-MEM */
            5'b11100: instruction_operation = decode_system;              /* SYSTEM */
            5'b10101: instruction_operation = VEnable ? VECTOR : INVALID; /* OP-V */
            5'b00001: instruction_operation = VEnable ? VLOAD  : INVALID; /* LOAD-FP */
            5'b01001: instruction_operation = VEnable ? VSTORE : INVALID; /* STORE-FP */
            5'b01011: instruction_operation = decode_atomic;
            5'b01010: instruction_operation = XKYBEREnable ? decode_xkyber : INVALID; 
            default:  instruction_operation = INVALID;
        endcase
    end

//////////////////////////////////////////////////////////////////////////////
//  Decode Vector Instruction
//////////////////////////////////////////////////////////////////////////////

    iTypeVector_e vector_operation;

    if (VEnable) begin : v_enable_decode_gen_on
        iTypeVector_e decode_vector_opcfg;
        iTypeVector_e decode_vector_opi;
        iTypeVector_e decode_vector_opm;

        opCat_e opCat;

        assign opCat = opCat_e'(funct3);

        always_comb begin
            unique case (instruction_i[31:30]) inside
                2'b0?:      decode_vector_opcfg = VSETVLI;
                2'b11:      decode_vector_opcfg = VSETIVLI;
                2'b10:      decode_vector_opcfg = VSETVL;
                default:    decode_vector_opcfg = VNOP;
            endcase
        end

        always_comb begin
            unique case (funct7[6:1]) inside
                6'b000000:     decode_vector_opi = VADD;
                6'b000010:     decode_vector_opi = VSUB;
                6'b000011:     decode_vector_opi = VRSUB;
                6'b000100:     decode_vector_opi = VMINU;
                6'b000101:     decode_vector_opi = VMIN;
                6'b000110:     decode_vector_opi = VMAXU;
                6'b000111:     decode_vector_opi = VMAX;
                6'b001001:     decode_vector_opi = VAND;
                6'b001010:     decode_vector_opi = VOR;
                6'b001011:     decode_vector_opi = VXOR;
                6'b010111:     decode_vector_opi = (funct7[0]) ? VMV : VMERGE;
                6'b011000:     decode_vector_opi = VMSEQ;
                6'b011001:     decode_vector_opi = VMSNE;
                6'b011010:     decode_vector_opi = VMSLTU;
                6'b011011:     decode_vector_opi = VMSLT;
                6'b011100:     decode_vector_opi = VMSLEU;
                6'b011101:     decode_vector_opi = VMSLE;
                6'b011110:     decode_vector_opi = VMSGTU;
                6'b011111:     decode_vector_opi = VMSGT;
                6'b100101:     decode_vector_opi = VSLL;
                6'b100111:     decode_vector_opi = VMVR;
                6'b101000:     decode_vector_opi = VSRL;
                6'b101001:     decode_vector_opi = VSRA;
                default:       decode_vector_opi = VNOP;
            endcase
        end

        always_comb begin
            unique case (funct7[6:1]) inside
                6'b000000:     decode_vector_opm = VREDSUM;
                6'b000001:     decode_vector_opm = VREDAND;
                6'b000010:     decode_vector_opm = VREDOR;
                6'b000011:     decode_vector_opm = VREDXOR;
                6'b000100:     decode_vector_opm = VREDMINU;
                6'b000101:     decode_vector_opm = VREDMIN;
                6'b000110:     decode_vector_opm = VREDMAXU;
                6'b000111:     decode_vector_opm = VREDMAX;
                6'b001110:     decode_vector_opm = VSLIDE1UP;
                6'b001111:     decode_vector_opm = VSLIDE1DOWN;
                6'b010000:     decode_vector_opm = (opCat == OPMVX)
                                                ? VMVSX
                                                : VMVXS;
                6'b100000:     decode_vector_opm = VDIVU;
                6'b100001:     decode_vector_opm = VDIV;
                6'b100010:     decode_vector_opm = VREMU;
                6'b100011:     decode_vector_opm = VREM;
                6'b100100:     decode_vector_opm = VMULHU;
                6'b100101:     decode_vector_opm = VMUL;
                6'b100110:     decode_vector_opm = VMULHSU;
                6'b100111:     decode_vector_opm = VMULH;
                6'b101101:     decode_vector_opm = VMACC;
                6'b101111:     decode_vector_opm = VNMSAC;
                6'b101001:     decode_vector_opm = VMADD;
                6'b101011:     decode_vector_opm = VNMSUB;
                6'b111000:     decode_vector_opm = VWMULU;
                6'b111010:     decode_vector_opm = VWMULSU;
                6'b111011:     decode_vector_opm = VWMUL;
                default:       decode_vector_opm = VNOP;
            endcase
        end

        always_comb begin
            if (instruction_operation == VECTOR) begin
                unique case (opCat) inside
                    OPCFG:               vector_operation = decode_vector_opcfg;
                    OPIVV, OPIVX, OPIVI: vector_operation = decode_vector_opi;
                    OPMVV, OPMVX:        vector_operation = decode_vector_opm;
                    default:             vector_operation = VNOP;
                endcase
            end
            else begin
                vector_operation = VNOP;
            end
        end

        always_ff @(posedge clk)
            if (instruction_operation_o == VECTOR && vector_operation_o == VNOP)
                $display("%0t - INVALID VECTOR INST!!! - %h", $time, instruction_o);

        always_ff @(posedge clk or negedge reset_n) begin
            if (!reset_n) begin
                vector_operation_o <= VNOP;
            end
            else if (enable) begin
                if (hazard || killed)
                    vector_operation_o <= VNOP;
                else
                    vector_operation_o <= vector_operation;
            end
        end
    end
    else begin : v_enable_decode_gen_on
        assign vector_operation = VNOP;
        assign vector_operation_o = VNOP;
    end

//////////////////////////////////////////////////////////////////////////////
//  Decodes the instruction format
//////////////////////////////////////////////////////////////////////////////

    formatType_e instruction_format;
    always_comb begin
        unique case (opcode)
            5'b11001, 5'b00000, 5'b00100:   instruction_format = I_TYPE;        /* JALR, LOAD, OP-IMM */
            5'b01000:                       instruction_format = S_TYPE;        /* STORE */
            5'b11000:                       instruction_format = B_TYPE;        /* BRANCH */
            5'b01101, 5'b00101:             instruction_format = U_TYPE;        /* LUI, AUIPC */
            5'b11011:                       instruction_format = J_TYPE;        /* JAL */
            default:                        instruction_format = R_TYPE;
        endcase
    end

//////////////////////////////////////////////////////////////////////////////
// Extract the immediate based on instruction format
//////////////////////////////////////////////////////////////////////////////

    logic [31:0] imm_i;
    logic [31:0] imm_s;
    logic [31:0] imm_b;
    logic [31:0] imm_u;
    logic [31:0] imm_j;

    assign imm_i = {{21{instruction_i[31]}}, instruction_i[30:20]};
    assign imm_s = {{21{instruction_i[31]}}, instruction_i[30:25], instruction_i[11:7]};
    assign imm_b = {{20{instruction_i[31]}}, instruction_i[7], instruction_i[30:25], instruction_i[11:8], 1'b0};
    assign imm_u = {instruction_i[31:12], 12'b0};
    assign imm_j = {{12{instruction_i[31]}}, instruction_i[19:12], instruction_i[20], instruction_i[30:25], instruction_i[24:21], 1'b0};

    logic [31:0] immediate;
    always_comb begin
        unique case (instruction_format)
            I_TYPE:     immediate = imm_i;
            S_TYPE:     immediate = imm_s;
            B_TYPE:     immediate = imm_b;
            U_TYPE:     immediate = imm_u;
            J_TYPE:     immediate = imm_j;
            default:    immediate = '0;    /* R_TYPE */
        endcase
    end

////////////////////////////////////////////////////////////////////////////////
// Branch Prediction
////////////////////////////////////////////////////////////////////////////////

    logic jump_confirmed;
    
    /* We add this adder here regardless of using BP or not */
    /* Because if we don't add here, we have to add in exec */
    assign bp_target_o = pc_i + immediate;
    always_ff @(posedge clk or negedge reset_n) begin
        if (!reset_n)
            jump_imm_target_o <= '0;
        else if (enable)
            jump_imm_target_o <= bp_target_o;
    end

    if (BRANCHPRED) begin : gen_bp_on
        logic bp_branch_taken;
        logic bp_jump_taken;

        /* We really can't add jalr here */
        assign bp_branch_taken = (opcode == 5'b11000 && imm_b[31]);
        assign bp_jump_taken   = (opcode == 5'b11011);

        assign bp_take_o   = (bp_jump_taken || bp_branch_taken) && !jumping_i && !killed;

        assign jump_confirmed = should_jump_i || (jumping_i && !jump_rollback_i);
        
        always_ff @(posedge clk or negedge reset_n) begin
            if (!reset_n)
                bp_taken_o <= 1'b0;
            else if (sys_reset)
                bp_taken_o <= 1'b0;
            else if (enable && !hazard)
                bp_taken_o <= bp_take_o;
        end
    end
    else begin : gen_bp_off
        assign bp_take_o   = 1'b0;
        assign bp_taken_o  = 1'b0;
        assign jump_confirmed = should_jump_i || jumping_i;
    end

//////////////////////////////////////////////////////////////////////////////
// Registe Lock Queue (RLQ)
//////////////////////////////////////////////////////////////////////////////

    logic is_load;
    assign is_load  = (opcode == 5'b00000) || (instruction_operation == LR_W);

    logic is_store;
    assign is_store = (opcode == 5'b01000) || (instruction_operation inside {SC_W, AMO_W});

    logic           locked_memory;

    logic  [4:0]    locked_register;
    logic  [4:0]    locked_register_r;
    logic  [4:0]    rd;

    assign rd = instruction_i[11:7];

    always_ff @(posedge clk or negedge reset_n) begin
        if (!reset_n) begin
            locked_register <= '0;
        end
        else if (enable) begin
            if (hazard || killed)
                locked_register <= '0;
            else    // Read-after-write on LOAD
                locked_register <= (is_load || !FORWARDING) ? rd : '0;
        end
    end

    always_ff @(posedge clk or negedge reset_n) begin
        if (!reset_n)
            locked_register_r <= '0;
        else if (enable)
            locked_register_r <= locked_register;
    end

    always_ff @(posedge clk or negedge reset_n) begin
        if (!reset_n) begin
            locked_memory   <= '0;
        end
        else if (enable) begin
            if (hazard || killed)
                locked_memory <= '0;
            else    // Read-after-write on STORE
                locked_memory <= is_store;
        end
    end

    always_ff @(posedge clk or negedge reset_n) begin
        if (!reset_n)
            rd_o <= '0;
        else if (enable)
            rd_o <= rd;
    end

//////////////////////////////////////////////////////////////////////////////
// Addresses to RegBank
//////////////////////////////////////////////////////////////////////////////

    assign rs1_o = instruction_i[19:15];
    assign rs2_o = instruction_i[24:20];

    logic [11:0] csr_address;
    assign csr_address = instruction_i[31:20];

    always_ff @(posedge clk or negedge reset_n) begin
        if (!reset_n)
            csr_address_o <= '0;
        else if (enable)
            csr_address_o <= csr_address;
    end

    always_ff @(posedge clk or negedge reset_n) begin
        if (!reset_n)
            instr_rs1_o <= '0;
        else if (enable)
            instr_rs1_o <= rs1_o;
    end

//////////////////////////////////////////////////////////////////////////////
// Hazard signal generation
//////////////////////////////////////////////////////////////////////////////
    
    logic exception;

    logic use_rs1;
    logic use_rs2;

    logic locked_rs1;
    logic locked_rs2;

    logic hazard_mem;
    logic hazard_rs1;
    logic hazard_rs2;

    always_comb begin
        unique case (instruction_format)
            R_TYPE, B_TYPE, S_TYPE:
            /**
             * This does NOT account for SYSTEM (R_TYPE) CSRR_I instructions
             * where funct3[2] is 1, and therefore WILL generate a hazard
             * but this is rare to occur
             */
                        begin
                            use_rs1         = 1'b1;
                            use_rs2         = 1'b1;
                        end
            I_TYPE:     begin
                            use_rs1         = 1'b1;
                            use_rs2         = 1'b0;
                        end
            default:    begin /* U_TYPE and J_TYPE */
                            use_rs1         = 1'b0;
                            use_rs2         = 1'b0;
                        end
        endcase
    end

    /* I don't know why we should have this hazard                  */
    /* But removing this breaks the processor                       */
    /* It also breaks if we limit the hazard to same address access */
    assign hazard_mem = locked_memory && is_load;

    assign locked_rs1 = (locked_register != '0 && locked_register == rs1_o) || (locked_register_r != '0 && locked_register_r == rs1_o);
    assign locked_rs2 = (locked_register != '0 && locked_register == rs2_o) || (locked_register_r != '0 && locked_register_r == rs2_o);

    assign hazard_rs1 = locked_rs1 && use_rs1;
    assign hazard_rs2 = locked_rs2 && use_rs2;

    logic invalid;
    assign invalid      = jump_confirmed || jump_misaligned_i || !valid_i;
    assign killed       = invalid || exception;
    assign hazard       = (hazard_mem || hazard_rs1 || hazard_rs2) && !killed;
    assign dec_hazard_o = hazard;

    always_ff @(posedge clk or negedge reset_n) begin
        if (!reset_n)
            killed_o <= 1'b0;
        else if (enable)
            killed_o <= killed;
    end

    always_ff @(posedge clk or negedge reset_n) begin
        if (!reset_n)
            hazard_o <= 1'b0;
        else if (enable)
            hazard_o <= hazard;
    end

//////////////////////////////////////////////////////////////////////////////
// Exception Detection
//////////////////////////////////////////////////////////////////////////////

    logic invalid_inst;
    assign invalid_inst = ((instruction_i[1:0] != '1) || instruction_operation == INVALID || amo_invalid) && !invalid;

    logic exc_inst_access_fault;
    assign exc_inst_access_fault = exc_inst_access_fault_i && !invalid;

    assign exception = exc_inst_access_fault || invalid_inst;

    always_ff @(posedge clk or negedge reset_n) begin
        if (!reset_n)
            exc_ilegal_inst_o <= 1'b0;
        else if (enable)
            exc_ilegal_inst_o <= invalid_inst;
    end

    always_ff @(posedge clk or negedge reset_n) begin
        if (!reset_n)
            exc_inst_access_fault_o <= 1'b0;
        else if (enable)
            exc_inst_access_fault_o <= exc_inst_access_fault;
    end

//////////////////////////////////////////////////////////////////////////////
// Control of the operands based on format
//////////////////////////////////////////////////////////////////////////////

    logic [31:0] rs1_data;
    logic [31:0] rs2_data;

    if (FORWARDING) begin : gen_forwarding_on
        always_comb begin
            if (rs1_o == rd_o && execute_we_i)  // Forwarding from execute
                rs1_data = result_i;
            else if (rs1_o == rd_mem_access_i && mem_access_we_i) // Forwarding from mem access
                rs1_data = mem_access_result_i;
            else if (rs1_o == rd_retire_i && regbank_we_i)  // Forwarding from retire on LOAD
                rs1_data = writeback_i;
            else
                rs1_data = rs1_data_read_i;
        end

        always_comb begin
            if (rs2_o == rd_o && execute_we_i)  // Forwarding from execute
                rs2_data = result_i;
            else if (rs2_o == rd_mem_access_i && mem_access_we_i) // Forwarding from mem access
                rs2_data = mem_access_result_i;
            else if (rs2_o == rd_retire_i && regbank_we_i)  // Forwarding from retire on LOAD
                rs2_data = writeback_i;
            else
                rs2_data = rs2_data_read_i;
        end
    end
    else begin : gen_forwarding_off
        always_comb begin
            if (rs1_o == rd_retire_i && regbank_we_i)  // Forwarding from retire on LOAD
                rs1_data = writeback_i;
            else
                rs1_data = rs1_data_read_i;
        end

        always_comb begin
            if (rs2_o == rd_retire_i && regbank_we_i)  // Forwarding from retire on LOAD
                rs2_data = writeback_i;
            else
                rs2_data = rs2_data_read_i;
        end
    end

    always_ff @(posedge clk or negedge reset_n) begin
        if (!reset_n)
            rs1_data_o <= '0;
        else if (enable)
            rs1_data_o <= rs1_data;
    end

    always_ff @(posedge clk or negedge reset_n) begin
        if (!reset_n)
            rs2_data_o <= '0;
        else if (enable)
            rs2_data_o <= rs2_data;
    end

    logic [31:0] second_operand;
    always_comb begin
        unique case (instruction_format)
            I_TYPE,
            S_TYPE,
            U_TYPE:  second_operand = immediate;
            default: second_operand =  rs2_data;
        endcase
    end

    always_ff @(posedge clk or negedge reset_n) begin
        if (!reset_n)
            second_operand_o <= '0;
        else if (enable)
            second_operand_o <= second_operand;
    end

//////////////////////////////////////////////////////////////////////////////
// Outputs to execution unit
//////////////////////////////////////////////////////////////////////////////

    always_ff @(posedge clk or negedge reset_n) begin
        if (!reset_n)
            pc_o <= '0;
        else if (enable)
            pc_o <= pc_i;
    end

    always_ff @(posedge clk or negedge reset_n) begin
        if (!reset_n)
            instruction_o <= '0;
        else if (enable)
            instruction_o <= (!COMPRESSED && compressed_i) ? {16'h0000, instruction_i[15:0]} : instruction_i;
    end

    always_ff @(posedge clk or negedge reset_n) begin
        if (!reset_n)
            compressed_o <= 1'b0;
        else if (enable)
            compressed_o <= compressed_i;
    end

    always_ff @(posedge clk or negedge reset_n) begin
        if (!reset_n) begin
            instruction_operation_o <= NOP;
        end
        else if (sys_reset) begin
            instruction_operation_o <= NOP;
        end
        else if (enable) begin
            if (hazard || killed)
                instruction_operation_o <= NOP;
            else
                instruction_operation_o <= instruction_operation;
        end
    end

endmodule
