//
// Copyright 2021 Ettus Research, A National Instruments Brand
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Package: PkgMath
//
// Description:
//
//   SystemVerilog supports many Math functions. This adds a few that it
//   doesn't have built in, as well as useful mathematical constants.
//
//   SystemVerilog has built-in support for the following:
//
//     $clog2   $asin
//     $ln      $acos
//     $log10   $atan
//     $exp     $atan2
//     $sqrt    $hypot
//     $pow     $sinh
//     $floor   $cosh
//     $ceil    $tanh
//     $sin     $asinh
//     $cos     $acosh
//     $tan     $atanh
//

package PkgMath;

  //---------------------------------------------------------------------------
  // Constants
  //---------------------------------------------------------------------------

  localparam real PI  = 2*$acos(0.0);
  localparam real TAU = 4*$acos(0.0);

  localparam real PHI = (1 + $sqrt(5.0)) / 2.0;
  localparam real E   = $exp(1);

  localparam byte     BYTE_MAX  =  8'sh7F;
  localparam byte     BYTE_MIN  =  8'sh80;
  localparam shortint SHORT_MAX = 16'sh7FFF;
  localparam shortint SHORT_MIN = 16'sh8000;
  localparam int      INT_MAX   = 32'sh7FFFFFFF;
  localparam int      INT_MIN   = 32'sh80000000;
  localparam longint  LONG_MAX  = 64'sh7FFFFFFFFFFFFFFF;
  localparam longint  LONG_MIN  = 64'sh8000000000000000;

  localparam byte     unsigned UBYTE_MAX  =  8'hFF;
  localparam byte     unsigned UBYTE_MIN  =  8'h00;
  localparam shortint unsigned USHORT_MAX = 16'hFFFF;
  localparam shortint unsigned USHORT_MIN = 16'h0000;
  localparam int      unsigned UINT_MAX   = 32'hFFFFFFFF;
  localparam int      unsigned UINT_MIN   = 32'h00000000;
  localparam longint  unsigned ULONG_MAX  = 64'hFFFFFFFFFFFFFFFF;
  localparam longint  unsigned ULONG_MIN  = 64'h0000000000000000;


  //---------------------------------------------------------------------------
  // Functions (For real data types)
  //---------------------------------------------------------------------------

  // Return the absolute value
  function automatic real abs(real num);
    if (num < 0) return -1.0*num;
    return num;
  endfunction : abs

  // ROUND_AWAY_FROM_ZERO: Round a float to the nearest whole number, rounding
  //   away from zero for 0.5 (same as C++ and default SystemVerilog behavior).
  // ROUND_HALF_UP: Always round toward +inf (according to axi_round module)
  typedef enum { ROUND_AWAY_FROM_ZERO, ROUND_HALF_UP } round_t;

  // Round a float according to the specified mode.
  function automatic real round(real num, round_t mode = ROUND_AWAY_FROM_ZERO);
    if (num >= 0 || mode == ROUND_HALF_UP) begin
      // Round toward +inf
      if (num - $floor(num) < 0.5) return $floor(num);
      return $ceil(num);
    end else begin
      // Round toward -inf
      if (num - $floor(num) <= 0.5) return $floor(num);
      return $ceil(num);
    end

  endfunction : round

  // Round a float to the nearest value having bits to the right of the binary
  // point. For example:
  //
  //   1.2265625 (0b1.0011101) --> 3 bits --> 1.25000 (0b1.0100000)
  //   1.2265625 (0b1.0011101) --> 5 bits --> 1.21875 (0b1.0011100)
  //
  function automatic real round_bits(real num, int unsigned bits);
    return round(num * 2.0**bits) / (2.0**bits);
  endfunction : round_bits

  // Return the sign of num as +1.0 or -1.0;
  function automatic real sign(real num);
    if (num < 0.0) return -1.0;
    return 1.0;
  endfunction : sign;

  // Return the modulus (remainder) of a / b, with the sign of the numerator.
  // This should match the C++ standard library std::fmod() behavior, as well
  // as SystemVerilog % operator with integer values.
  function automatic real fmod(real a, real b);
    a = abs(a);
    b = abs(b);
    return sign(b) * (a - ($floor(a / b) * b));
  endfunction : fmod

  // Return the (remainder) of a / b, where the quotient is rounded to the
  // nearest integer. This should approximate the C++ standard library
  // std::remainder() behavior.
  function automatic real remainder(real a, real b);
    return a - round(a/b)*b;
  endfunction : remainder

  // Return the maximum of a and b.
  function automatic real fmax(real a, real b);
    if (a > b) return a;
    return b;
  endfunction : fmax

  // Return the minimum of a and b.
  function automatic real fmin(real a, real b);
    if (a < b) return a;
    return b;
  endfunction : fmin

  // Coerce from real to 16-bit signed integer
  function automatic shortint coerce_to_int16(real num);
    if (num > SHORT_MAX) return SHORT_MAX;
    if (num < SHORT_MIN) return SHORT_MIN;
    return shortint'(num);
  endfunction : coerce_to_int16

  //---------------------------------------------------------------------------
  // Template Functions (For any data type)
  //---------------------------------------------------------------------------

  class Math #(type T);

    static function T abs(T num);
      if (num < 0) return -num;
      return num;
    endfunction : abs

    static function T sign(T num);
      if (num < 0) return -1;
      return 1;
    endfunction : sign

    static function T max(T a, T b);
      if (a > b) return a;
      else return b;
    endfunction : max

    static function T min(T a, T b);
      if (a < b) return a;
      else return b;
    endfunction : min

  endclass : Math

endpackage : PkgMath
