--  This -*- vhdl -*- file is part of GHDL.
--  IEEE 1076.3 compliant numeric bit package body.
--  The implementation is based only on the specifications.
--  Copyright (C) 2015-2021 Tristan Gingold
--
--  This program is free software: you can redistribute it and/or modify
--  it under the terms of the GNU General Public License as published by
--  the Free Software Foundation, either version 2 of the License, or
--  (at your option) any later version.
--
--  This program is distributed in the hope that it will be useful,
--  but WITHOUT ANY WARRANTY; without even the implied warranty of
--  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
--  GNU General Public License for more details.
--
--  You should have received a copy of the GNU General Public License
--  along with this program.  If not, see <gnu.org/licenses>.

package body NUMERIC_BIT is
  constant NO_WARNING : Boolean := False;

  constant null_unsigned : unsigned (0 downto 1) := (others => '0');
  constant null_signed   :   signed (0 downto 1) := (others => '0');

  subtype nat1 is natural range 0 to 1;

  type nat1_to_sl_type is array (nat1) of bit;
  constant nat1_to_01 : nat1_to_sl_type := (0 => '0', 1 => '1');

  subtype sl_01 is bit;

  type carry_array is array (sl_01, sl_01, sl_01) of sl_01;
  constant compute_carry : carry_array :=
    ('0' => ('0' => ('0' => '0', '1' => '0'),
             '1' => ('0' => '0', '1' => '1')),
     '1' => ('0' => ('0' => '0', '1' => '1'),
             '1' => ('0' => '1', '1' => '1')));
  constant compute_sum : carry_array :=
    ('0' => ('0' => ('0' => '0', '1' => '1'),
             '1' => ('0' => '1', '1' => '0')),
     '1' => ('0' => ('0' => '1', '1' => '0'),
             '1' => ('0' => '0', '1' => '1')));

  type compare_type is (compare_unknown,
                        compare_lt,
                        compare_eq,
                        compare_gt);

  function MAX (L, R : natural) return natural is
  begin
    if L > R then
      return L;
    else
      return R;
    end if;
  end MAX;

  function TO_INTEGER (ARG : UNSIGNED) return NATURAL
  is
    variable res : natural := 0;
  begin
    if arg'length = 0 then
      assert NO_WARNING
        report "NUMERIC_BIT.TO_INTEGER: null array detected, returning 0"
        severity warning;
      return 0;
    end if;

    for i in arg'range loop
      res := res + res;
      if arg (i) = '1' then
        res := res + 1;
      end if;
    end loop;

    return res;
  end TO_INTEGER;

  function TO_INTEGER (ARG :   SIGNED) return INTEGER
  is
    alias argn : SIGNED (ARG'Length -1 downto 0) is arg;
    variable res : integer := 0;
    variable b : bit;
  begin
    if argn'length = 0 then
      assert NO_WARNING
        report "NUMERIC_BIT.TO_INTEGER: null array detected, returning 0"
        severity warning;
      return 0;
    end if;
    if argn (argn'left) = '1' then
      --  Negative value
      b := '0';
    else
      b := '1';
    end if;

    for i in argn'range loop
      res := res + res;
      if argn (i) = b then
        res := res + 1;
      end if;
    end loop;

    if b = '0' then
      --  Avoid overflow.
      res := -res - 1;
    end if;

    return res;
  end TO_INTEGER;

  function TO_UNSIGNED (ARG, SIZE : NATURAL) return UNSIGNED
  is
    variable res : UNSIGNED (SIZE - 1 downto 0);
    variable a : natural := arg;
    variable d : nat1;
  begin
    if size = 0 then
      return null_unsigned;
    end if;
    for i in res'reverse_range loop
      d := a rem 2;
      res (i) := nat1_to_01 (d);
      a := a / 2;
    end loop;
    if a /= 0 then
      assert NO_WARNING
        report "NUMERIC_BIT.TO_UNSIGNED: vector is truncated"
        severity warning;
    end if;
    return res;
  end TO_UNSIGNED;

  function TO_SIGNED (ARG : INTEGER; SIZE : NATURAL) return SIGNED
  is
    variable res : SIGNED (SIZE - 1 downto 0);
    variable v : integer := arg;
    variable b0, b1 : bit;
    variable d : nat1;
  begin
    if size = 0 then
      return null_signed;
    end if;
    if arg < 0 then
      --  Use one complement to avoid overflow:
      --   -v = (not v) + 1
      --   not v = -v - 1
      --   not v = -(v + 1)
      v := -(arg + 1);
      b0 := '1';
      b1 := '0';
    else
      v := arg;
      b0 := '0';
      b1 := '1';
    end if;

    for i in res'reverse_range loop
      d := v rem 2;
      v := v / 2;
      if d = 0 then
        res (i) := b0;
      else
        res (i) := b1;
      end if;
    end loop;
    if v /= 0 or res (res'left) /= b0 then
      assert NO_WARNING
        report "NUMERIC_BIT.TO_SIGNED: vector is truncated"
        severity warning;
    end if;
    return res;
  end TO_SIGNED;

  @ARITH

  @LOG

  function rising_edge (signal s : bit) return boolean is
  begin
    return s'event and s = '1';
  end rising_edge;

  function falling_edge (signal s : bit) return boolean is
  begin
    return s'event and s = '0';
  end falling_edge;
end NUMERIC_BIT;
