///////////////////////////////////////////
// fcmp.sv
//
// Written: me@KatherineParry.com
// Modified: 7/5/2022
//
// Purpose: Floating-point comparison unit
// 
// Documentation: RISC-V System on Chip Design
//
// A component of the CORE-V-WALLY configurable RISC-V project.
// https://github.com/openhwgroup/cvw
// 
// Copyright (C) 2021-23 Harvey Mudd College & Oklahoma State University
//
// SPDX-License-Identifier: Apache-2.0 WITH SHL-2.1
//
// Licensed under the Solderpad Hardware License v 2.1 (the “License”); you may not use this file 
// except in compliance with the License, or, at your option, the Apache License version 2.0. You 
// may obtain a copy of the License at
//
// https://solderpad.org/licenses/SHL-2.1/
//
// Unless required by applicable law or agreed to in writing, any work distributed under the 
// 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.
////////////////////////////////////////////////////////////////////////////////////////////////

// OpCtrl values
//    110   min
//    101   max
//    010   equal
//    001   less than
//    011   less than or equal

module fcmp import cvw::*;  #(parameter cvw_t P) (
  input  logic [P.FMTBITS-1:0]   Fmt,           // format of fp number
  input  logic [2:0]             OpCtrl,        // see above table
  input  logic                   Zfa,           // Zfa variants: fminm, fmaxm, fleq, fltq
  input  logic                   Xs, Ys,        // input signs
  input  logic [P.NE-1:0]        Xe, Ye,        // input exponents
  input  logic [P.NF:0]          Xm, Ym,        // input mantissa
  input  logic                   XZero, YZero,  // is zero
  input  logic                   XNaN, YNaN,    // is NaN
  input  logic                   XSNaN, YSNaN,  // is signaling NaN
  input  logic [P.FLEN-1:0]      X, Y,          // original inputs (before unpacker)
  output logic                   CmpNV,         // invalid flag
  output logic [P.FLEN-1:0]      CmpFpRes,      // compare floating-point result
  output logic [P.XLEN-1:0]      CmpIntRes      // compare integer result
);

  logic LTabs, LT, EQ;          // is X < or > or = Y
  logic [P.FLEN-1:0] NaNRes;    // NaN result
  logic BothZero;               // are both inputs zero
  logic EitherNaN, EitherSNaN;  // are either input a (signaling) NaN
  
  assign LTabs= {1'b0, Xe, Xm} < {1'b0, Ye, Ym}; // unsigned comparison, treating FP as integers
  assign LT = (Xs & ~Ys) | (Xs & Ys & ~LTabs & ~EQ) | (~Xs & ~Ys & LTabs); // signed comparison
  assign EQ = (X == Y);

  assign BothZero = XZero&YZero;
  assign EitherNaN = XNaN|YNaN;
  assign EitherSNaN = XSNaN|YSNaN;

  // flags
  //    Min/Max - if an input is a signaling NaN set invalid flag
  //    LT/LE - signaling - sets invalid if NaN input
  //    EQ - quiet - sets invalid if signaling NaN input
  always_comb begin
    casez (OpCtrl[2:0])
        3'b110: CmpNV = EitherSNaN; //min 
        3'b101: CmpNV = EitherSNaN; //max
        3'b010: CmpNV = EitherSNaN; //equal
        3'b0?1: if (P.ZFA_SUPPORTED) 
                  CmpNV = Zfa ? EitherSNaN : EitherNaN; // fltq,fleq / flt,fle perform CompareQuietLess / CompareSignalingLess differing on when to set invalid
                else CmpNV = EitherNaN;                 // flt, fle
        3'b100: CmpNV = 1'b0;
        default: CmpNV = 1'bx;
    endcase
  end 

  // fmin/fmax of two NaNs returns a quiet NaN of the appropriate size
  // for IEEE, return the payload of X
  // for RISC-V, return the canonical NaN

  // select the NaN result
  if (P.FPSIZES == 1)
    if(P.IEEE754) assign NaNRes = {Xs, {P.NE{1'b1}}, 1'b1, Xm[P.NF-2:0]};
    else          assign NaNRes = {1'b0, {P.NE{1'b1}}, 1'b1, {P.NF-1{1'b0}}};

  else if (P.FPSIZES == 2) 
    if(P.IEEE754) assign NaNRes = Fmt ? {Xs, {P.NE{1'b1}}, 1'b1, Xm[P.NF-2:0]} : {{P.FLEN-P.LEN1{1'b1}}, Xs, {P.NE1{1'b1}}, 1'b1, Xm[P.NF-2:P.NF-P.NF1]};
    else          assign NaNRes = Fmt ? {1'b0, {P.NE{1'b1}}, 1'b1, {P.NF-1{1'b0}}} : {{P.FLEN-P.LEN1{1'b1}}, 1'b0, {P.NE1{1'b1}}, 1'b1, (P.NF1-1)'(0)};
  
  else if (P.FPSIZES == 3)
    always_comb
          case (Fmt)
              P.FMT:  
                if(P.IEEE754) NaNRes = {Xs, {P.NE{1'b1}}, 1'b1, Xm[P.NF-2:0]};
                else         NaNRes = {1'b0, {P.NE{1'b1}}, 1'b1, {P.NF-1{1'b0}}};
              P.FMT1:
                if(P.IEEE754) NaNRes = {{P.FLEN-P.LEN1{1'b1}}, Xs, {P.NE1{1'b1}}, 1'b1, Xm[P.NF-2:P.NF-P.NF1]};
                else         NaNRes = {{P.FLEN-P.LEN1{1'b1}}, 1'b0, {P.NE1{1'b1}}, 1'b1, (P.NF1-1)'(0)};
              P.FMT2:
                if(P.IEEE754) NaNRes = {{P.FLEN-P.LEN2{1'b1}}, Xs, {P.NE2{1'b1}}, 1'b1, Xm[P.NF-2:P.NF-P.NF2]};
                else         NaNRes = {{P.FLEN-P.LEN2{1'b1}}, 1'b0, {P.NE2{1'b1}}, 1'b1, (P.NF2-1)'(0)};
              default:        NaNRes = {P.FLEN{1'bx}};
          endcase

  else if (P.FPSIZES == 4)
    always_comb
          case (Fmt)
              2'h3:  
                if(P.IEEE754) NaNRes = {Xs, {P.NE{1'b1}}, 1'b1, Xm[P.NF-2:0]};
                else         NaNRes = {1'b0, {P.NE{1'b1}}, 1'b1, {P.NF-1{1'b0}}};
              2'h1:  
                if(P.IEEE754) NaNRes = {{P.FLEN-P.D_LEN{1'b1}}, Xs, {P.D_NE{1'b1}}, 1'b1, Xm[P.NF-2:P.NF-P.D_NF]};
                else         NaNRes = {{P.FLEN-P.D_LEN{1'b1}}, 1'b0, {P.D_NE{1'b1}}, 1'b1, (P.D_NF-1)'(0)};
              2'h0: 
                if(P.IEEE754) NaNRes = {{P.FLEN-P.S_LEN{1'b1}}, Xs, {P.S_NE{1'b1}}, 1'b1, Xm[P.NF-2:P.NF-P.S_NF]};
                else         NaNRes = {{P.FLEN-P.S_LEN{1'b1}}, 1'b0, {P.S_NE{1'b1}}, 1'b1, (P.S_NF-1)'(0)};
              2'h2:
                if(P.IEEE754) NaNRes = {{P.FLEN-P.H_LEN{1'b1}}, Xs, {P.H_NE{1'b1}}, 1'b1, Xm[P.NF-2:P.NF-P.H_NF]};
                else         NaNRes = {{P.FLEN-P.H_LEN{1'b1}}, 1'b0, {P.H_NE{1'b1}}, 1'b1, (P.H_NF-1)'(0)};
          endcase

  // Min/Max
  //    - outputs the min/max of X and Y
  //    - -0 < 0
  //    - if both are NaN return quiet X
  //    - if one is a NaN output the non-NaN
  always_comb
    if(OpCtrl[0]) // MAX
        if (Zfa & P.ZFA_SUPPORTED) // fmaxm perform IEEE754 maxNum that produce NaN if either input is NaN
          if (XNaN | YNaN) CmpFpRes = NaNRes; // either input is NaN
          else
            if (LT) CmpFpRes = Y; // X < Y
            else    CmpFpRes = X; // X > Y
        else // fmax performs IEEE754 maxNumber that produces NaN if both inputs are NaN
          if(XNaN)
            if(YNaN)    CmpFpRes = NaNRes;   // X = NaN Y = NaN
            else        CmpFpRes = Y;        // X = NaN Y != NaN
          else
            if(YNaN)    CmpFpRes = X;        // X != NaN Y = NaN
            else // X,Y != NaN
                if(LT)  CmpFpRes = Y;        // X < Y
                else    CmpFpRes = X;        // X > Y
    else  // MIN
        if (Zfa & P.ZFA_SUPPORTED) // fminm perform IEEE754 minNum that produce NaN if either input is NaN
          if (XNaN | YNaN) CmpFpRes = NaNRes; // either input is NaN
          else
            if (LT) CmpFpRes = X; // X < Y
            else    CmpFpRes = Y; // X > Y
        else // fmin performs IEEE754 minNumber that produces NaN if both inputs are NaN
          if(XNaN)
            if(YNaN)    CmpFpRes = NaNRes;   // X = NaN Y = NaN
            else        CmpFpRes = Y;        // X = NaN Y != NaN
          else
            if(YNaN)    CmpFpRes = X;        // X != NaN Y = NaN
            else // X,Y != NaN
                if(LT)  CmpFpRes = X;        // X < Y
                else    CmpFpRes = Y;        // X > Y
                                  
  // LT/LE/EQ
  //    - -0 = 0
  //    - inf = inf and -inf = -inf
  //    - return 0 if comparison with NaN (unordered)
  assign CmpIntRes = {(P.XLEN-1)'(0), (((EQ|BothZero)&OpCtrl[1])|(LT&OpCtrl[0]&~BothZero))&~EitherNaN};
endmodule
