--  Routine to dump (for debugging purpose) a netlist.
--  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, write to the Free Software
--  Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston,
--  MA 02110-1301, USA.

with Simple_IO; use Simple_IO;
with Utils_IO; use Utils_IO;
with Name_Table;
with Files_Map;

with Netlists.Utils; use Netlists.Utils;
with Netlists.Iterators; use Netlists.Iterators;
with Netlists.Gates; use Netlists.Gates;
with Netlists.Locations;

package body Netlists.Dump is
   procedure Put_Width (W : Width) is
   begin
      Put_Trim (Width'Image (W));
   end Put_Width;

   procedure Dump_Name (N : Sname)
   is
      use Name_Table;
      Prefix : Sname;
   begin
      --  Do not crash on No_Name.
      if N = No_Sname then
         Put ("*nil*");
         return;
      end if;

      Prefix := Get_Sname_Prefix (N);
      if Prefix /= No_Sname then
         Dump_Name (Prefix);
         Put (".");
      end if;

      case Get_Sname_Kind (N) is
         when Sname_User =>
            Put ("\");
            Put (Image (Get_Sname_Suffix (N)));
         when Sname_Artificial =>
            Put ("$");
            Put (Image (Get_Sname_Suffix (N)));
         when Sname_Version =>
            Put ("%");
            Put_Uns32 (Get_Sname_Version (N));
      end case;
   end Dump_Name;

   procedure Dump_Input_Name (I : Input; With_Id : Boolean := False)
   is
      Inst : constant Instance := Get_Input_Parent (I);
      Idx : constant Port_Idx := Get_Port_Idx (I);
      M : constant Module := Get_Module (Inst);
   begin
      Dump_Name (Get_Instance_Name (Inst));
      Put ('.');
      if Is_Self_Instance (Inst) then
         Dump_Name (Get_Output_Desc (M, Idx).Name);
      else
         if Idx < Get_Nbr_Inputs (M) then
            Dump_Name (Get_Input_Desc (M, Idx).Name);
         else
            Put_Trim (Port_Nbr'Image (Idx));
         end if;
      end if;
      if With_Id then
         Put ("{p");
         Put_Trim (Input'Image (I));
         Put ('}');
      end if;
   end Dump_Input_Name;

   procedure Dump_Net_Name (N : Net; With_Id : Boolean := False)
   is
      Inst : constant Instance := Get_Net_Parent (N);
      Idx : constant Port_Idx := Get_Port_Idx (N);
   begin
      Dump_Name (Get_Instance_Name (Inst));
      Put ('.');
      if Is_Self_Instance (Inst) then
         Dump_Name (Get_Input_Desc (Get_Module (Inst), Idx).Name);
      else
         Dump_Name (Get_Output_Desc (Get_Module (Inst), Idx).Name);
      end if;
      if With_Id then
         Put ("{n");
         Put_Trim (Net'Image (N));
         Put ('w');
         Put_Width (Get_Width (N));
         Put ('}');
      end if;
   end Dump_Net_Name;

   procedure Dump_Parameter (Inst : Instance; Idx : Param_Idx)
   is
      Desc : constant Param_Desc := Get_Param_Desc (Inst, Idx);
   begin
      if Desc.Name /= No_Sname then
         --  Const_Bit/Log gates have anonymous parameters.
         Dump_Name (Desc.Name);
         Put ('=');
      end if;

      case Desc.Typ is
         when Param_Invalid =>
            Put ("invalid");
         when Param_Uns32 =>
            Put_Uns32 (Get_Param_Uns32 (Inst, Idx));
         when Param_Pval_Vector
           | Param_Pval_String
           | Param_Pval_Integer
           | Param_Pval_Real
           | Param_Pval_Time_Ps =>
            Put ("generic");
      end case;
   end Dump_Parameter;

   procedure Dump_Instance (Inst : Instance; Indent : Natural := 0)
   is
      Loc : constant Location_Type := Locations.Get_Location (Inst);
   begin
      if Loc /= No_Location then
         declare
            File : Name_Id;
            Line : Positive;
            Col : Natural;
         begin
            Put_Indent (Indent);
            Put ("# ");
            Files_Map.Location_To_Position (Loc, File, Line, Col);
            Put (Name_Table.Image (File));
            Put (':');
            Put_Uns32 (Uns32 (Line));
            Put (':');
            Put_Uns32 (Uns32 (Col));
            New_Line;
         end;
      end if;

      Put_Indent (Indent);
      Put ("instance ");
      Dump_Name (Get_Instance_Name (Inst));
      if Flag_Disp_Id then
         Put (" {i");
         Put_Trim (Instance'Image (Inst));
         Put ('}');
      end if;
      Put (": ");
      Dump_Name (Get_Module_Name (Get_Module (Inst)));
      New_Line;

      if Get_Nbr_Params (Inst) > 0 then
         Put_Indent (Indent + 1);
         Put ("parameters");
         for P in Params (Inst) loop
            pragma Warnings (Off, P);
            Put (' ');
            Dump_Parameter (Inst, Get_Param_Idx (P));
         end loop;
         New_Line;
      end if;

      if Get_Nbr_Inputs (Inst) > 0 then
         for I of Inputs (Inst) loop
            Put_Indent (Indent + 1);
            Put ("input ");
            Dump_Input_Name (I, True);
            Put (" <- ");
            declare
               N : constant Net := Get_Driver (I);
            begin
               if N /= No_Net then
                  Dump_Net_Name (N, True);
               end if;
            end;
            New_Line;
         end loop;
      end if;

      if Get_Nbr_Outputs (Inst) > 0 then
         Put_Indent (Indent + 1);
         Put ("outputs");
         for O of Outputs (Inst) loop
            Put (' ');
            Dump_Net_Name (O, True);
         end loop;
         New_Line;
      end if;
   end Dump_Instance;

   procedure Disp_Width (W : Width) is
   begin
      if W /= 1 then
         Put ('[');
         if W = 0 then
            Put ('?');
         else
            Put_Width (W - 1);
            Put (":0");
         end if;
         Put (']');
      end if;
   end Disp_Width;

   procedure Dump_Module_Port (Desc : Port_Desc; Dir : Port_Kind) is
   begin
      case Dir is
         when Port_In =>
            Put ("input");
         when Port_Out =>
            Put ("output");
         when Port_Inout =>
            raise Internal_Error;
      end case;
      Put (' ');
      Dump_Name (Desc.Name);
      Disp_Width (Desc.W);
      Put (';');
      New_Line;
   end Dump_Module_Port;

   procedure Dump_Module_Header (M : Module; Indent : Natural := 0) is
   begin
      --  Module id and name.
      Put_Indent (Indent);
      Put ("module ");
      if Flag_Disp_Id then
         Put ("{m");
         Put_Trim (Module'Image (M));
         Put ("} ");
      end if;
      Dump_Name (Get_Module_Name (M));
      New_Line;

      --  Parameters.
      for P of Params_Desc (M) loop
         Put_Indent (Indent + 1);
         Put ("parameter");
         Put (' ');
         Dump_Name (P.Name);
         Put (": ");
         case P.Typ is
            when Param_Invalid =>
               Put ("invalid");
            when Param_Uns32 =>
               Put ("uns32");
            when Param_Pval_Vector =>
               Put ("pval.vector");
            when Param_Pval_String =>
               Put ("pval.string");
            when Param_Pval_Integer =>
               Put ("pval.integer");
            when Param_Pval_Real =>
               Put ("pval.real");
            when Param_Pval_Time_Ps =>
               Put ("pval.time.ps");
         end case;
         New_Line;
      end loop;

      --  Ports.
      for I in 1 .. Get_Nbr_Inputs (M) loop
         Put_Indent (Indent + 1);
         Dump_Module_Port (Get_Input_Desc (M, I - 1), Port_In);
      end loop;
      for I in 1 .. Get_Nbr_Outputs (M) loop
         Put_Indent (Indent + 1);
         Dump_Module_Port (Get_Output_Desc (M, I - 1), Port_Out);
      end loop;
   end Dump_Module_Header;

   procedure Dump_Module (M : Module; Indent : Natural := 0) is
   begin
      Dump_Module_Header (M, Indent);

      for S of Sub_Modules (M) loop
         Dump_Module (S, Indent + 1);
      end loop;

      declare
         Self : constant Instance := Get_Self_Instance (M);
      begin
         if Self /= No_Instance then
            Dump_Instance (Self, Indent + 1);
         end if;
      end;

      for Inst of Instances (M) loop
         Dump_Instance (Inst, Indent + 1);
      end loop;

      for N of Nets (M) loop
         Put_Indent (Indent + 1);
         Put ("connect ");
         Dump_Net_Name (N, True);

         declare
            First : Boolean;
         begin
            First := True;
            for S of Sinks (N) loop
               if First then
                  Put (" -> ");
                  First := False;
               else
                  Put (", ");
               end if;
               Dump_Input_Name (S, True);
            end loop;
         end;
         New_Line;
      end loop;
   end Dump_Module;

   procedure Disp_Net_Name (N : Net) is
   begin
      if N = No_Net then
         Put ("?");
      else
         declare
            Inst : constant Instance := Get_Net_Parent (N);
            Idx : constant Port_Idx := Get_Port_Idx (N);
         begin
            if Is_Self_Instance (Inst) then
               Dump_Name (Get_Input_Desc (Get_Module (Inst), Idx).Name);
            else
               Dump_Name (Get_Instance_Name (Inst));
               Put (':');
               Dump_Name (Get_Output_Desc (Get_Module (Inst), Idx).Name);
            end if;
         end;
      end if;
   end Disp_Net_Name;

   procedure Put_Net_Width (N : Net) is
   begin
      Put ("{n");
      Put_Trim (Net'Image (N));
      Put ('w');
      Put_Uns32 (Get_Width (N));
      Put ('}');
   end Put_Net_Width;

   procedure Dump_Net_Name_And_Width (N : Net)
   is
      W : Width;
   begin
      if N = No_Net then
         Put ("?");
      else
         Disp_Net_Name (N);

         W := Get_Width (N);
         if Flag_Disp_Id then
            Put_Net_Width (N);
         else
            if W /= 1 then
               Put ('[');
               Put_Uns32 (W);
               Put (']');
            end if;
         end if;

      end if;
   end Dump_Net_Name_And_Width;

   procedure Disp_Instance_Assign (Inst : Instance; Indent : Natural := 0);

   function Can_Inline (Inst : Instance) return Boolean
   is
      O : Net;
      Inp : Input;
   begin
      case Get_Id (Inst) is
         when Id_Signal
           | Id_Output =>
            --  Cut loops.
            return False;
         when others =>
            null;
      end case;
      if Is_Self_Instance (Inst) then
         return False;
      end if;
      if Get_Nbr_Outputs (Inst) /= 1 then
         return False;
      end if;
      O := Get_Output (Inst, 0);
      Inp := Get_First_Sink (O);
      if Inp = No_Input or else Get_Next_Sink (Inp) /= No_Input then
         return False;
      end if;
      if Is_Self_Instance (Get_Input_Parent (Inp)) then
         return False;
      end if;

      return True;
   end Can_Inline;

   procedure Disp_Driver (Drv : Net; Indent : Natural)
   is
      Drv_Inst : Instance;
   begin
      if Drv = No_Net then
         Put ('?');
      else
         Drv_Inst := Get_Net_Parent (Drv);
         if Flag_Disp_Inline and then Can_Inline (Drv_Inst) then
            Disp_Instance_Assign (Drv_Inst, Indent);
         else
            Disp_Net_Name (Drv);
            if Flag_Disp_Id then
               Put_Net_Width (Drv);
            end if;
         end if;
      end if;
   end Disp_Driver;

   --  Debug routine: disp net driver
   procedure Debug_Net (N : Net) is
   begin
      if N = No_Net then
         Put ('?');
      else
         Disp_Instance (Get_Net_Parent (N), False, 0);
      end if;
      New_Line;
   end Debug_Net;

   pragma Unreferenced (Debug_Net);

   Xdigits : constant array (Uns32 range 0 ..15) of Character :=
     "0123456789abcdef";

   procedure Disp_Instance
     (Inst : Instance; With_Name : Boolean; Indent : Natural)
   is
      M : constant Module := Get_Module (Inst);
   begin
      if True then
         --  Pretty-print for some gates
         case Get_Id (M) is
            when Id_Const_UB32 =>
               declare
                  W : constant Width := Get_Width (Get_Output (Inst, 0));
                  V : Uns32;
                  I : Natural;
               begin
                  Put_Width (W);
                  Put ("'uh");
                  V := Get_Param_Uns32 (Inst, 0);
                  I := (Natural (W) + 3) / 4;
                  while I > 0 loop
                     I := I - 1;
                     Put (Xdigits (Shift_Right (V, I * 4) and 15));
                  end loop;
               end;
               return;

            when Id_Extract =>
               declare
                  W : constant Width := Get_Width (Get_Output (Inst, 0));
                  Off : constant Uns32 := Get_Param_Uns32 (Inst, 0);
               begin
                  Disp_Driver (Get_Input_Net (Inst, 0), Indent);
                  Put ('[');
                  if W > 1 then
                     Put_Uns32 (Off + W - 1);
                     Put (':');
                  end if;
                  Put_Uns32 (Off);
                  Put (']');
                  return;
               end;

            when others =>
               null;
         end case;
      end if;

      Dump_Name (Get_Module_Name (M));

      if Flag_Disp_Id then
         Put ("{i");
         Put_Trim (Instance'Image (Inst));
         Put ('}');
      end if;

      if Get_Nbr_Params (Inst) > 0 then
         declare
            First : Boolean;
         begin
            First := True;
            Put (" #(");
            for P in Params (Inst) loop
               pragma Warnings (Off, P);
               if not First then
                  Put (", ");
               end if;
               First := False;
               Dump_Parameter (Inst, Get_Param_Idx (P));
            end loop;
            Put (")");
         end;
      end if;

      if With_Name then
         Put (' ');
         Dump_Name (Get_Instance_Name (Inst));
      end if;

      declare
         Nbr_Inputs : constant Port_Nbr := Get_Nbr_Inputs (Inst);
         M : constant Module := Get_Module (Inst);
         Nbr_Fixed_Inputs : constant Port_Nbr := Get_Nbr_Inputs (M);
         Drv : Net;
         I : Input;
         Desc : Port_Desc;
      begin
         if Nbr_Inputs > 0 then
            Put (" (");
            for Idx in 0 .. Nbr_Inputs - 1 loop
               I := Get_Input (Inst, Idx);
               if Idx > 0 then
                  Put (",");
               end if;
               New_Line;
               Put_Indent (Indent);

               --  Input name.
               if Idx < Nbr_Fixed_Inputs then
                  Desc := Get_Input_Desc (M, Idx);
                  if Desc.Name /= No_Sname then
                     Put ('.');
                     Dump_Name (Desc.Name);
                     if Flag_Disp_Id then
                        Put ("{p");
                        Put_Trim (Input'Image (I));
                        Put ('}');
                     end if;
                     Put (": ");
                  end if;
               end if;

               --  Input value.
               Drv := Get_Driver (I);

               if Drv = No_Net then
                  Put ('?');
               else
                  Disp_Driver (Drv, Indent + 1);
               end if;
            end loop;
            Put (')');
         end if;
      end;
   end Disp_Instance;

   procedure Disp_Instance_Assign (Inst : Instance; Indent : Natural := 0) is
   begin
      case Get_Nbr_Outputs (Inst) is
         when 0 =>
            null;
         when 1 =>
            Dump_Net_Name_And_Width (Get_Output (Inst, 0));
            Put (" := ");
         when others =>
            declare
               First : Boolean;
            begin
               First := True;
               Put ('(');
               for O of Outputs (Inst) loop
                  if not First then
                     Put (", ");
                  end if;
                  First := False;
                  Dump_Net_Name_And_Width (O);
               end loop;
               Put (") := ");
            end;
      end case;

      Disp_Instance (Inst, False, Indent + 1);
   end Disp_Instance_Assign;

   procedure Disp_Module (M : Module; Indent : Natural := 0) is
   begin
      --  Name and ports.
      Dump_Module_Header (M, Indent);

      --  Submodules.
      for S of Sub_Modules (M) loop
         if Get_Id (S) >= Id_User_None then
            Disp_Module (S, Indent + 1);
         end if;
      end loop;

      for Inst of Instances (M) loop
         if not (Flag_Disp_Inline and then Can_Inline (Inst)) then
            Put_Indent (Indent + 1);
            Disp_Instance_Assign (Inst, Indent + 1);
            New_Line;
         end if;
      end loop;

      --  Assignments to outputs.
      declare
         Self : constant Instance := Get_Self_Instance (M);
         Drv : Net;
      begin
         if Self /= No_Instance then
            for I of Inputs (Self) loop
               Put_Indent (Indent + 1);
               Dump_Name (Get_Output_Desc (M, Get_Port_Idx (I)).Name);
               Put (" := ");
               Drv := Get_Driver (I);
               if False then
                  Disp_Driver (Drv, 0);
               else
                  Disp_Net_Name (Drv);
                  if Flag_Disp_Id and Drv /= No_Net then
                     Put_Net_Width (Drv);
                  end if;
               end if;
               New_Line;
            end loop;
         end if;
      end;
   end Disp_Module;
end Netlists.Dump;