// Copyright 2018 ETH Zurich and University of Bologna.
// Copyright and related rights are licensed under the Solderpad Hardware
// License, Version 0.51 (the "License"); you may not use this file except in
// compliance with the License.  You may obtain a copy of the License at
// http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law
// or agreed to in writing, software, hardware and materials distributed under
// this License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License.

////////////////////////////////////////////////////////////////////////////////
// Engineer:       Sven Stucki - svstucki@student.ethz.ch                     //
//                                                                            //
// Additional contributions by:                                               //
//                 Michael Gautschi - gautschi@iis.ee.ethz.ch                 //
//                                                                            //
// Design Name:    Compressed instruction decoder                             //
// Project Name:   RI5CY                                                      //
// Language:       SystemVerilog                                              //
//                                                                            //
// Description:    Decodes RISC-V compressed instructions into their RV32     //
//                 equivalent. This module is fully combinatorial.            //
//                 Float extensions added                                     //
//                                                                            //
////////////////////////////////////////////////////////////////////////////////

module cv32e40p_compressed_decoder #(
    parameter FPU   = 0,
    parameter ZFINX = 0
) (
    input  logic [31:0] instr_i,
    output logic [31:0] instr_o,
    output logic        is_compressed_o,
    output logic        illegal_instr_o
);

  import cv32e40p_pkg::*;

  //////////////////////////////////////////////////////////////////////////////////////////////////////
  //   ____                                                 _   ____                     _            //
  //  / ___|___  _ __ ___  _ __  _ __ ___  ___ ___  ___  __| | |  _ \  ___  ___ ___   __| | ___ _ __  //
  // | |   / _ \| '_ ` _ \| '_ \| '__/ _ \/ __/ __|/ _ \/ _` | | | | |/ _ \/ __/ _ \ / _` |/ _ \ '__| //
  // | |__| (_) | | | | | | |_) | | |  __/\__ \__ \  __/ (_| | | |_| |  __/ (_| (_) | (_| |  __/ |    //
  //  \____\___/|_| |_| |_| .__/|_|  \___||___/___/\___|\__,_| |____/ \___|\___\___/ \__,_|\___|_|    //
  //                      |_|                                                                         //
  //////////////////////////////////////////////////////////////////////////////////////////////////////

  always_comb begin
    illegal_instr_o = 1'b0;
    instr_o         = '0;

    unique case (instr_i[1:0])
      // C0
      2'b00: begin
        unique case (instr_i[15:13])
          3'b000: begin
            // c.addi4spn -> addi rd', x2, imm
            instr_o = {
              2'b0,
              instr_i[10:7],
              instr_i[12:11],
              instr_i[5],
              instr_i[6],
              2'b00,
              5'h02,
              3'b000,
              2'b01,
              instr_i[4:2],
              OPCODE_OPIMM
            };
            if (instr_i[12:5] == 8'b0) illegal_instr_o = 1'b1;
          end

          3'b001: begin
            // c.fld -> fld rd', imm(rs1')
            if (FPU == 1 && ZFINX == 0)
              // instr_i[12:10]-> offset[5:3],  instr_i[6:5]-> offset[7:6]
              instr_o = {
                4'b0,
                instr_i[6:5],
                instr_i[12:10],
                3'b000,
                2'b01,
                instr_i[9:7],
                3'b011,
                2'b01,
                instr_i[4:2],
                OPCODE_LOAD_FP
              };
            else illegal_instr_o = 1'b1;
          end

          3'b010: begin
            // c.lw -> lw rd', imm(rs1')
            instr_o = {
              5'b0,
              instr_i[5],
              instr_i[12:10],
              instr_i[6],
              2'b00,
              2'b01,
              instr_i[9:7],
              3'b010,
              2'b01,
              instr_i[4:2],
              OPCODE_LOAD
            };
          end

          3'b011: begin
            // c.flw -> flw rd', imm(rs1')
            if (FPU == 1 && ZFINX == 0)
              instr_o = {
                5'b0,
                instr_i[5],
                instr_i[12:10],
                instr_i[6],
                2'b00,
                2'b01,
                instr_i[9:7],
                3'b010,
                2'b01,
                instr_i[4:2],
                OPCODE_LOAD_FP
              };
            else illegal_instr_o = 1'b1;
          end

          3'b101: begin
            // c.fsd -> fsd rs2', imm(rs1')
            if (FPU == 1 && ZFINX == 0)
              // instr_i[12:10] -> offset[5:3], instr_i[6:5] -> offset[7:6]
              instr_o = {
                4'b0,
                instr_i[6:5],
                instr_i[12],
                2'b01,
                instr_i[4:2],
                2'b01,
                instr_i[9:7],
                3'b011,
                instr_i[11:10],
                3'b000,
                OPCODE_STORE_FP
              };
            else illegal_instr_o = 1'b1;
          end

          3'b110: begin
            // c.sw -> sw rs2', imm(rs1')
            instr_o = {
              5'b0,
              instr_i[5],
              instr_i[12],
              2'b01,
              instr_i[4:2],
              2'b01,
              instr_i[9:7],
              3'b010,
              instr_i[11:10],
              instr_i[6],
              2'b00,
              OPCODE_STORE
            };
          end

          3'b111: begin
            // c.fsw -> fsw rs2', imm(rs1')
            if (FPU == 1 && ZFINX == 0)
              instr_o = {
                5'b0,
                instr_i[5],
                instr_i[12],
                2'b01,
                instr_i[4:2],
                2'b01,
                instr_i[9:7],
                3'b010,
                instr_i[11:10],
                instr_i[6],
                2'b00,
                OPCODE_STORE_FP
              };
            else illegal_instr_o = 1'b1;
          end
          default: begin
            illegal_instr_o = 1'b1;
          end
        endcase
      end


      // C1
      2'b01: begin
        unique case (instr_i[15:13])
          3'b000: begin
            // c.addi -> addi rd, rd, nzimm
            // c.nop
            instr_o = {
              {6{instr_i[12]}},
              instr_i[12],
              instr_i[6:2],
              instr_i[11:7],
              3'b0,
              instr_i[11:7],
              OPCODE_OPIMM
            };
          end

          3'b001, 3'b101: begin
            // 001: c.jal -> jal x1, imm
            // 101: c.j   -> jal x0, imm
            instr_o = {
              instr_i[12],
              instr_i[8],
              instr_i[10:9],
              instr_i[6],
              instr_i[7],
              instr_i[2],
              instr_i[11],
              instr_i[5:3],
              {9{instr_i[12]}},
              4'b0,
              ~instr_i[15],
              OPCODE_JAL
            };
          end

          3'b010: begin
            if (instr_i[11:7] == 5'b0) begin
              // Hint -> addi x0, x0, nzimm
              instr_o = {
                {6{instr_i[12]}}, instr_i[12], instr_i[6:2], 5'b0, 3'b0, instr_i[11:7], OPCODE_OPIMM
              };
            end else begin
              // c.li -> addi rd, x0, nzimm
              instr_o = {
                {6{instr_i[12]}}, instr_i[12], instr_i[6:2], 5'b0, 3'b0, instr_i[11:7], OPCODE_OPIMM
              };
            end
          end

          3'b011: begin
            if ({instr_i[12], instr_i[6:2]} == 6'b0) begin
              illegal_instr_o = 1'b1;
            end else begin
              if (instr_i[11:7] == 5'h02) begin
                // c.addi16sp -> addi x2, x2, nzimm
                instr_o = {
                  {3{instr_i[12]}},
                  instr_i[4:3],
                  instr_i[5],
                  instr_i[2],
                  instr_i[6],
                  4'b0,
                  5'h02,
                  3'b000,
                  5'h02,
                  OPCODE_OPIMM
                };
              end else if (instr_i[11:7] == 5'b0) begin
                // Hint -> lui x0, imm
                instr_o = {{15{instr_i[12]}}, instr_i[6:2], instr_i[11:7], OPCODE_LUI};
              end else begin
                // c.lui -> lui rd, imm
                instr_o = {{15{instr_i[12]}}, instr_i[6:2], instr_i[11:7], OPCODE_LUI};
              end
            end
          end

          3'b100: begin
            unique case (instr_i[11:10])
              2'b00, 2'b01: begin
                // 00: c.srli -> srli rd, rd, shamt
                // 01: c.srai -> srai rd, rd, shamt
                if (instr_i[12] == 1'b1) begin
                  // Reserved for future custom extensions (instr_o don't care)
                  instr_o = {
                    1'b0,
                    instr_i[10],
                    5'b0,
                    instr_i[6:2],
                    2'b01,
                    instr_i[9:7],
                    3'b101,
                    2'b01,
                    instr_i[9:7],
                    OPCODE_OPIMM
                  };
                  illegal_instr_o = 1'b1;
                end else begin
                  if (instr_i[6:2] == 5'b0) begin
                    // Hint
                    instr_o = {
                      1'b0,
                      instr_i[10],
                      5'b0,
                      instr_i[6:2],
                      2'b01,
                      instr_i[9:7],
                      3'b101,
                      2'b01,
                      instr_i[9:7],
                      OPCODE_OPIMM
                    };
                  end else begin
                    instr_o = {
                      1'b0,
                      instr_i[10],
                      5'b0,
                      instr_i[6:2],
                      2'b01,
                      instr_i[9:7],
                      3'b101,
                      2'b01,
                      instr_i[9:7],
                      OPCODE_OPIMM
                    };
                  end
                end
              end

              2'b10: begin
                // c.andi -> andi rd, rd, imm
                instr_o = {
                  {6{instr_i[12]}},
                  instr_i[12],
                  instr_i[6:2],
                  2'b01,
                  instr_i[9:7],
                  3'b111,
                  2'b01,
                  instr_i[9:7],
                  OPCODE_OPIMM
                };
              end

              2'b11: begin
                unique case ({
                  instr_i[12], instr_i[6:5]
                })
                  3'b000: begin
                    // c.sub -> sub rd', rd', rs2'
                    instr_o = {
                      2'b01,
                      5'b0,
                      2'b01,
                      instr_i[4:2],
                      2'b01,
                      instr_i[9:7],
                      3'b000,
                      2'b01,
                      instr_i[9:7],
                      OPCODE_OP
                    };
                  end

                  3'b001: begin
                    // c.xor -> xor rd', rd', rs2'
                    instr_o = {
                      7'b0,
                      2'b01,
                      instr_i[4:2],
                      2'b01,
                      instr_i[9:7],
                      3'b100,
                      2'b01,
                      instr_i[9:7],
                      OPCODE_OP
                    };
                  end

                  3'b010: begin
                    // c.or  -> or  rd', rd', rs2'
                    instr_o = {
                      7'b0,
                      2'b01,
                      instr_i[4:2],
                      2'b01,
                      instr_i[9:7],
                      3'b110,
                      2'b01,
                      instr_i[9:7],
                      OPCODE_OP
                    };
                  end

                  3'b011: begin
                    // c.and -> and rd', rd', rs2'
                    instr_o = {
                      7'b0,
                      2'b01,
                      instr_i[4:2],
                      2'b01,
                      instr_i[9:7],
                      3'b111,
                      2'b01,
                      instr_i[9:7],
                      OPCODE_OP
                    };
                  end

                  3'b100, 3'b101, 3'b110, 3'b111: begin
                    // 100: c.subw
                    // 101: c.addw
                    illegal_instr_o = 1'b1;
                  end
                endcase
              end
            endcase
          end

          3'b110, 3'b111: begin
            // 0: c.beqz -> beq rs1', x0, imm
            // 1: c.bnez -> bne rs1', x0, imm
            instr_o = {
              {4{instr_i[12]}},
              instr_i[6:5],
              instr_i[2],
              5'b0,
              2'b01,
              instr_i[9:7],
              2'b00,
              instr_i[13],
              instr_i[11:10],
              instr_i[4:3],
              instr_i[12],
              OPCODE_BRANCH
            };
          end
        endcase
      end

      // C2
      2'b10: begin
        unique case (instr_i[15:13])
          3'b000: begin
            if (instr_i[12] == 1'b1) begin
              // Reserved for future extensions (instr_o don't care)
              instr_o = {7'b0, instr_i[6:2], instr_i[11:7], 3'b001, instr_i[11:7], OPCODE_OPIMM};
              illegal_instr_o = 1'b1;
            end else begin
              if ((instr_i[6:2] == 5'b0) || (instr_i[11:7] == 5'b0)) begin
                // Hint -> slli rd, rd, shamt 
                instr_o = {7'b0, instr_i[6:2], instr_i[11:7], 3'b001, instr_i[11:7], OPCODE_OPIMM};
              end else begin
                // c.slli -> slli rd, rd, shamt
                instr_o = {7'b0, instr_i[6:2], instr_i[11:7], 3'b001, instr_i[11:7], OPCODE_OPIMM};
              end
            end
          end

          3'b001: begin
            // c.fldsp -> fld rd, imm(x2)
            if (FPU == 1 && ZFINX == 0)
              // instr_i[6:5] -> offset[4:3], instr_i[4:2] -> offset[8:6], instr_i[12] -> offset[5]
              instr_o = {
                3'b0,
                instr_i[4:2],
                instr_i[12],
                instr_i[6:5],
                3'b000,
                5'h02,
                3'b011,
                instr_i[11:7],
                OPCODE_LOAD_FP
              };
            else illegal_instr_o = 1'b1;
          end

          3'b010: begin
            // c.lwsp -> lw rd, imm(x2)
            instr_o = {
              4'b0,
              instr_i[3:2],
              instr_i[12],
              instr_i[6:4],
              2'b00,
              5'h02,
              3'b010,
              instr_i[11:7],
              OPCODE_LOAD
            };
            if (instr_i[11:7] == 5'b0) illegal_instr_o = 1'b1;
          end

          3'b011: begin
            // c.flwsp -> flw rd, imm(x2)
            if (FPU == 1 && ZFINX == 0)
              instr_o = {
                4'b0,
                instr_i[3:2],
                instr_i[12],
                instr_i[6:4],
                2'b00,
                5'h02,
                3'b010,
                instr_i[11:7],
                OPCODE_LOAD_FP
              };
            else illegal_instr_o = 1'b1;
          end

          3'b100: begin
            if (instr_i[12] == 1'b0) begin
              if (instr_i[6:2] == 5'b0) begin
                // c.jr -> jalr x0, rd/rs1, 0
                instr_o = {12'b0, instr_i[11:7], 3'b0, 5'b0, OPCODE_JALR};
                // c.jr with rs1 = 0 is reserved
                if (instr_i[11:7] == 5'b0) illegal_instr_o = 1'b1;
              end else begin
                if (instr_i[11:7] == 5'b0) begin
                  // Hint -> add x0, x0, rs2
                  instr_o = {7'b0, instr_i[6:2], 5'b0, 3'b0, instr_i[11:7], OPCODE_OP};
                end else begin
                  // c.mv -> add rd, x0, rs2
                  instr_o = {7'b0, instr_i[6:2], 5'b0, 3'b0, instr_i[11:7], OPCODE_OP};
                end
              end
            end else begin
              if (instr_i[6:2] == 5'b0) begin
                if (instr_i[11:7] == 5'b0) begin
                  // c.ebreak -> ebreak
                  instr_o = {32'h00_10_00_73};
                end else begin
                  // c.jalr -> jalr x1, rs1, 0
                  instr_o = {12'b0, instr_i[11:7], 3'b000, 5'b00001, OPCODE_JALR};
                end
              end else begin
                if (instr_i[11:7] == 5'b0) begin
                  // Hint -> add x0, x0, rs2
                  instr_o = {7'b0, instr_i[6:2], instr_i[11:7], 3'b0, instr_i[11:7], OPCODE_OP};
                end else begin
                  // c.add -> add rd, rd, rs2
                  instr_o = {7'b0, instr_i[6:2], instr_i[11:7], 3'b0, instr_i[11:7], OPCODE_OP};
                end
              end
            end
          end

          3'b101: begin
            // c.fsdsp -> fsd rs2, imm(x2)
            if (FPU == 1 && ZFINX == 0)
              // instr_i[12:10] -> offset[5:3], instr_i[9:7] -> offset[8:6]
              instr_o = {
                3'b0,
                instr_i[9:7],
                instr_i[12],
                instr_i[6:2],
                5'h02,
                3'b011,
                instr_i[11:10],
                3'b000,
                OPCODE_STORE_FP
              };
            else illegal_instr_o = 1'b1;
          end
          3'b110: begin
            // c.swsp -> sw rs2, imm(x2)
            instr_o = {
              4'b0,
              instr_i[8:7],
              instr_i[12],
              instr_i[6:2],
              5'h02,
              3'b010,
              instr_i[11:9],
              2'b00,
              OPCODE_STORE
            };
          end

          3'b111: begin
            // c.fswsp -> fsw rs2, imm(x2)
            if (FPU == 1 && ZFINX == 0)
              instr_o = {
                4'b0,
                instr_i[8:7],
                instr_i[12],
                instr_i[6:2],
                5'h02,
                3'b010,
                instr_i[11:9],
                2'b00,
                OPCODE_STORE_FP
              };
            else illegal_instr_o = 1'b1;
          end
        endcase
      end

      default: begin
        // 32 bit (or more) instruction
        instr_o = instr_i;
      end
    endcase
  end

  assign is_compressed_o = (instr_i[1:0] != 2'b11);

endmodule
