// 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:       Matthias Baer - baermatt@student.ethz.ch                   //
//                                                                            //
// Additional contributions by:                                               //
//                 Andreas Traber - atraber@student.ethz.ch                   //
//                 Michael Gautschi - gautschi@iis.ee.ethz.ch                 //
//                 Halfdan Bechmann - halfdan.bechmann@silabs.com             //
//                                                                            //
// Design Name:    Multiplier                                                 //
// Project Name:   RI5CY                                                      //
// Language:       SystemVerilog                                              //
//                                                                            //
// Description:    Multiplier unit.                                           //
//                                                                            //
////////////////////////////////////////////////////////////////////////////////

module cv32e40s_mult import cv32e40s_pkg::*;
(
  input  logic        clk,
  input  logic        rst_n,

  input  logic        valid_i,
  input  mul_opcode_e operator_i,

  // integer and short multiplier
  input  logic [ 1:0] signed_mode_i,

  input  logic [31:0] op_a_i,
  input  logic [31:0] op_b_i,

  output logic [31:0] result_o,

  input  logic        halt_i,
  input  logic        kill_i,

  output logic        ready_o,
  output logic        valid_o,
  input  logic        ready_i
);

  ///////////////////////////////////////////////////////////////
  //  ___ _  _ _____ ___ ___ ___ ___   __  __ _   _ _  _____   //
  // |_ _| \| |_   _| __/ __| __| _ \ |  \/  | | | | ||_   _|  //
  //  | || .  | | | | _| (_ | _||   / | |\/| | |_| | |__| |    //
  // |___|_|\_| |_| |___\___|___|_|_\ |_|  |_|\___/|____|_|    //
  //                                                           //
  ///////////////////////////////////////////////////////////////

  // Multiplier Operands
  logic [31:0] op_a;
  logic [31:0] op_b;
  logic [33:0] int_result;

  // MULH control signals
  logic        mulh_shift;

  // MULH State variables
  mul_state_e  mulh_state;
  mul_state_e  mulh_state_next;

  // MULH Part select operands
  logic [16:0] mulh_al;
  logic [16:0] mulh_bl;
  logic [16:0] mulh_ah;
  logic [16:0] mulh_bh;

  // MULH Operands
  logic [16:0] mulh_a;
  logic [16:0] mulh_b;

  // MULH Intermediate Results
  logic [32:0] mulh_acc;
  logic [32:0] mulh_acc_next;
  logic [32:0] mulh_acc_res;

  // Result
  logic [33:0] result;
  logic [33:0] result_shifted;

  assign mulh_al[15:0] = op_a_i[15:0];
  assign mulh_bl[15:0] = op_b_i[15:0];
  assign mulh_ah[15:0] = op_a_i[31:16];
  assign mulh_bh[15:0] = op_b_i[31:16];

  // Lower halfwords are always multiplied as unsigned
  assign mulh_al[16] = 1'b0;
  assign mulh_bl[16] = 1'b0;

  // Sign extention for the upper halfword is decided by the instuction used.
  // MULH   :   signed x signed    : signed_mode_i == 'b00
  // MULHSU :   signed x unsigned  : signed_mode_i == 'b01
  // MULHU  : unsigned x unsigned  : signed_mode_i == 'b11
  assign mulh_ah[16] = signed_mode_i[0] && op_a_i[31];
  assign mulh_bh[16] = signed_mode_i[1] && op_b_i[31];

  ////////////////
  //  MULH FSM  //
  ////////////////

  always_comb
  begin
    mulh_shift       = 1'b0;
    mulh_a           = mulh_al;
    mulh_b           = mulh_bl;
    mulh_state_next  = mulh_state;
    ready_o          = 1'b0;
    valid_o          = 1'b0;
    mulh_acc_next    = mulh_acc;

    // Case statement assumes valid_i = 1, halt_i = 0 and kill_i = 0.
    // the valid_i = 0 / halt_i = 1 / kill_i = 1 scenarios
    // are handled after the case statement.
    case (mulh_state)
      MUL_ALBL: begin
        if (operator_i == MUL_H) begin
          // Multicycle multiplication
          mulh_shift      = 1'b1;
          mulh_state_next = MUL_ALBH;
          mulh_acc_next   = mulh_acc_res;
        end
        else begin
          // Single cycle multiplication
          valid_o         = 1'b1;

          if (ready_i) begin
            ready_o = 1'b1;
          end
        end
      end

      MUL_ALBH: begin
        mulh_state_next  = MUL_AHBL;
        mulh_acc_next    = mulh_acc_res;
        mulh_a           = mulh_al;
        mulh_b           = mulh_bh;
      end

      MUL_AHBL: begin
        mulh_state_next  = MUL_AHBH;
        mulh_acc_next    = mulh_acc_res;
        mulh_shift       = 1'b1;
        mulh_a           = mulh_ah;
        mulh_b           = mulh_bl;
      end

      MUL_AHBH: begin
        valid_o           = 1'b1;
        mulh_a            = mulh_ah;
        mulh_b            = mulh_bh;

        if (ready_i) begin
          ready_o         = 1'b1;
          mulh_state_next = MUL_ALBL;
          mulh_acc_next   = '0;
        end
      end
      default: ;
    endcase

    // Allow kill at any time
    if (!valid_i || kill_i) begin
      valid_o         = 1'b0;
      ready_o         = 1'b1;
      mulh_shift      = 1'b0;
      mulh_a          = mulh_al;
      mulh_b          = mulh_bl;
    end else begin
      if (halt_i) begin
        valid_o         = 1'b0;
        ready_o         = 1'b0;
        mulh_shift      = 1'b0;
        mulh_a          = mulh_al;
        mulh_b          = mulh_bl;
      end
    end

    if (kill_i) begin
      mulh_state_next = MUL_ALBL;
      mulh_acc_next   = '0;
    end
  end // always_comb

  always_ff @(posedge clk, negedge rst_n) begin
    if (rst_n == 1'b0) begin
      mulh_acc   <=  '0;
      mulh_state <= MUL_ALBL;
    end else begin
      // Update flops on valid input or when killed. No updates while halted.
      if ((valid_i && !halt_i) || kill_i) begin
        mulh_acc   <= mulh_acc_next;
        mulh_state <= mulh_state_next;
      end
    end
  end

  // MULH Shift Mux
  assign result_shifted = $signed(result) >>> 16;
  assign mulh_acc_res   = mulh_shift ? result_shifted[32:0] : result[32:0];

  ///////////////////////////
  //   32-bit multiplier   //
  ///////////////////////////

  assign op_a = (operator_i == MUL_M32) ? op_a_i : {{16{mulh_a[16]}}, mulh_a[15:0]};
  assign op_b = (operator_i == MUL_M32) ? op_b_i : {{16{mulh_b[16]}}, mulh_b[15:0]};

  assign int_result = $signed(op_a) * $signed(op_b);

  ////////////////////////////////////
  //   ____                 _ _     //
  //  |  _ \ ___  ___ _   _| | |_   //
  //  | |_) / _ \/ __| | | | | __|  //
  //  |  _ <  __/\__ \ |_| | | |_   //
  //  |_| \_\___||___/\__,_|_|\__|  //
  //                                //
  ////////////////////////////////////

  // 34bit Adder - mulh_acc is always 0 for the MUL instruction
  assign result   = $signed(int_result) + $signed(mulh_acc);

  assign result_o = result[31:0];

endmodule
