--  Netlist utilities (composed of a few calls).
--  Copyright (C) 2017 Tristan Gingold
--
--  This file is part of GHDL.
--
--  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>.

with Mutils;
with Types_Utils; use Types_Utils;

with Netlists.Gates; use Netlists.Gates;


package body Netlists.Utils is
   function Get_Nbr_Inputs (Inst : Instance) return Port_Nbr
   is
      M : constant Module := Get_Module (Inst);
   begin
      case Get_Id (M) is
         when Id_Concatn
           | Id_Pmux =>
            return Port_Nbr (Get_Param_Uns32 (Inst, 0));
         when others =>
            if Is_Self_Instance (Inst) then
               return Get_Nbr_Outputs (M);
            else
               return Get_Nbr_Inputs (M);
            end if;
      end case;
   end Get_Nbr_Inputs;

   function Get_Nbr_Outputs (Inst : Instance) return Port_Nbr
   is
      M : constant Module := Get_Module (Inst);
   begin
      if Is_Self_Instance (Inst) then
         return Get_Nbr_Inputs (M);
      else
         return Get_Nbr_Outputs (M);
      end if;
   end Get_Nbr_Outputs;

   function Get_Nbr_Params (Inst : Instance) return Param_Nbr
   is
      M : constant Module := Get_Module (Inst);
   begin
      case Get_Id (M) is
         when Id_Const_Bit =>
            return Param_Nbr ((Get_Width (Get_Output (Inst, 0)) + 31) / 32);
         when Id_Const_Log =>
            return Param_Nbr
              (2 * ((Get_Width (Get_Output (Inst, 0)) + 31) / 32));
         when others =>
            return Get_Nbr_Params (M);
      end case;
   end Get_Nbr_Params;

   function Get_Param_Desc
     (Inst : Instance; Param : Param_Idx) return Param_Desc is
   begin
      return Get_Param_Desc (Get_Module (Inst), Param);
   end Get_Param_Desc;

   function Get_Id (Inst : Instance) return Module_Id is
   begin
      return Get_Id (Get_Module (Inst));
   end Get_Id;

   function Get_Input_Name (M : Module; I : Port_Idx) return Sname is
   begin
      return Get_Input_Desc (M, I).Name;
   end Get_Input_Name;

   function Get_Output_Name (M : Module; I : Port_Idx) return Sname is
   begin
      return Get_Output_Desc (M, I).Name;
   end Get_Output_Name;

   function Get_Input_Width (M : Module; I : Port_Idx) return Width is
   begin
      return Get_Input_Desc (M, I).W;
   end Get_Input_Width;

   function Get_Output_Width (M : Module; I : Port_Idx) return Width is
   begin
      return Get_Output_Desc (M, I).W;
   end Get_Output_Width;

   function Get_Inout_Flag (M : Module; I : Port_Idx) return Boolean is
   begin
      return Get_Output_Desc (M, I).Dir = Port_Inout;
   end Get_Inout_Flag;

   function Get_Input_Net (Inst : Instance; Idx : Port_Idx) return Net is
   begin
      return Get_Driver (Get_Input (Inst, Idx));
   end Get_Input_Net;

   function Get_Input_Instance (Inst : Instance; Idx : Port_Idx)
                               return Instance is
   begin
      return Get_Net_Parent (Get_Input_Net (Inst, Idx));
   end Get_Input_Instance;

   function Get_Param_Name (M : Module; I : Param_Idx) return Sname is
   begin
      return Get_Param_Desc (M, I).Name;
   end Get_Param_Name;

   function Get_Param_Type (M : Module; I : Param_Idx) return Param_Type is
   begin
      return Get_Param_Desc (M, I).Typ;
   end Get_Param_Type;

   function Is_Const_Net (N : Net) return Boolean is
   begin
      if Get_Width (N) = 0 then
         return True;
      end if;
      return Get_Id (Get_Net_Parent (N)) in Constant_Module_Id;
   end Is_Const_Net;

   function Get_Net_Uns64 (N : Net) return Uns64
   is
      Inst : constant Instance := Get_Net_Parent (N);
   begin
      case Get_Id (Inst) is
         when Id_Const_UB32 =>
            declare
               Va : constant Uns32 := Get_Param_Uns32 (Inst, 0);
               Wd : constant Width := Get_Width (N);
            begin
               --  There must not be any garbage.
               pragma Assert (Shift_Right (Va, Natural (Wd)) = 0);
               return Uns64 (Va);
            end;
         when Id_Const_SB32 =>
            declare
               Va : constant Uns32 := Get_Param_Uns32 (Inst, 0);
               Wd : constant Natural := Natural (Get_Width (N));
               Res : Uns64;
            begin
               Res := Uns64 (Va);
               Res := Shift_Left (Res, 64 - Wd);
               Res := Shift_Right_Arithmetic (Res, 64 - Wd);
               return Res;
            end;
         when others =>
            if Get_Width (N) = 0 then
               return 0;
            end if;
            raise Internal_Error;
      end case;
   end Get_Net_Uns64;

   function Get_Net_Int64 (N : Net) return Int64 is
   begin
      return To_Int64 (Get_Net_Uns64 (N));
   end Get_Net_Int64;

   procedure Get_Net_Element
     (N : Net; Off : Uns32; Va : out Uns32; Zx : out Uns32)
   is
      Inst : constant Instance := Get_Net_Parent (N);
   begin
      case Get_Id (Inst) is
         when Id_Const_UB32 =>
            declare
               V : constant Uns32 := Get_Param_Uns32 (Inst, 0);
               Wd : constant Width := Get_Width (N);
            begin
               pragma Assert (Off < 32);
               Zx := 0;
               Va := Shift_Right (V, Natural (Wd - Off)) and 1;
            end;
         when others =>
            raise Internal_Error;
      end case;
   end Get_Net_Element;

   function Skip_Signal (N : Net) return Net
   is
      Inst : constant Instance := Get_Net_Parent (N);
   begin
      case Get_Id (Inst) is
         when Id_Signal =>
            return Get_Input_Net (Inst, 0);
         when others =>
            return N;
      end case;
   end Skip_Signal;

   function Is_Connected (O : Net) return Boolean is
   begin
      return Get_First_Sink (O) /= No_Input;
   end Is_Connected;

   function Has_One_Connection (O : Net) return Boolean
   is
      Inp : Input;
   begin
      Inp := Get_First_Sink (O);
      if Inp = No_Input then
         --  No connection.
         return False;
      end if;
      Inp := Get_Next_Sink (Inp);
      return Inp = No_Input;
   end Has_One_Connection;

   function Disconnect_And_Get (I : Input) return Net
   is
      N : Net;
   begin
      N := Get_Driver (I);
      Disconnect (I);
      return N;
   end Disconnect_And_Get;

   function Disconnect_And_Get (Inst : Instance; I : Port_Idx) return Net is
   begin
      return Disconnect_And_Get (Get_Input (Inst, I));
   end Disconnect_And_Get;

   function Same_Net (L, R : Net) return Boolean is
   begin
      if L = R then
         --  Obvious case.
         return True;
      end if;

      if Get_Width (L) /= Get_Width (R) then
         --  Must have the same width.
         return False;
      end if;

      declare
         Linst : constant Instance := Get_Net_Parent (L);
         Rinst : constant Instance := Get_Net_Parent (R);
      begin
         if Get_Id (Linst) /= Get_Id (Rinst) then
            return False;
         end if;
         case Get_Id (Linst) is
            when Id_Uextend =>
               --  When index is extended from a subtype.
               return Same_Net (Get_Input_Net (Linst, 0),
                                Get_Input_Net (Rinst, 0));
            when Id_Extract =>
               --  When index is extracted from a record.
               if Get_Param_Uns32 (Linst, 0) /= Get_Param_Uns32 (Rinst, 0) then
                  return False;
               end if;
               return Same_Net (Get_Input_Net (Linst, 0),
                                Get_Input_Net (Rinst, 0));
            when others =>
               return False;
         end case;
      end;
   end Same_Net;

   function Same_Clock (L, R : Net) return Boolean
   is
      Linst : constant Instance := Get_Net_Parent (L);
      Rinst : constant Instance := Get_Net_Parent (R);
   begin
      if Get_Id (Linst) /= Get_Id (Rinst) then
         return False;
      end if;
      pragma Assert (Get_Id (Linst) in Edge_Module_Id);
      return Same_Net (Get_Input_Net (Linst, 0),
                       Get_Input_Net (Rinst, 0));
   end Same_Clock;

   procedure Copy_Instance_Attributes (Dest : Instance; Src : Instance)
   is
      Attr : Attribute;
   begin
      Attr := Get_Instance_First_Attribute (Src);
      while Attr /= No_Attribute loop
         Set_Instance_Attribute (Dest,
                                 Get_Attribute_Name (Attr),
                                 Get_Attribute_Type (Attr),
                                 Get_Attribute_Pval (Attr));
         Attr := Get_Attribute_Next (Attr);
      end loop;
   end Copy_Instance_Attributes;

   function Clog2 (W : Width) return Width is
   begin
      return Uns32 (Mutils.Clog2 (Uns64 (W)));
   end Clog2;
end Netlists.Utils;