content: {-|
  Copyright       : (C) 2020, QBayLogic
  License         : BSD2 (see the file LICENSE)
  Maintainer      : QBayLogic B.V. <devops@qbaylogic.com>
  
  Data types and rendering for Edalize Metadata files (EDAM).
  -}
  
  {-# LANGUAGE CPP #-}
  {-# LANGUAGE LambdaCase #-}
  {-# LANGUAGE OverloadedStrings #-}
  
  module Clash.Edalize.Edam
    ( Edam(..)
    , EdamFile(..)
    , EdamFileType(..)
    , EdamTools(..)
    , GhdlOptions(..)
    , IcarusOptions(..)
    , ModelsimOptions(..)
    , QuartusOptions(..)
    , VivadoOptions(..)
  
    , pprEdam
    ) where
  
  import Data.Default
  import Data.Maybe
  import Data.Text (Text)
  
  #if MIN_VERSION_prettyprinter(1,7,0)
  import Prettyprinter
  #else
  import Data.Text.Prettyprint.Doc
  #endif
  
  -- | EDAM data structure to be given to an Edalize backend. This contains all
  -- information needed to generate a project scaffolding. Note that hooks and
  -- VPI modules are currently not specified by clash.
  --
  data Edam = Edam
    { edamProjectName :: Text
    , edamTopEntity   :: Text
    , edamFiles       :: [EdamFile]
    , edamToolOptions :: EdamTools
    }
  
  pprEdam :: Edam -> Doc ann
  pprEdam (Edam n te fs ts) = vsep
    [ pyPre
    , hsep ["edam", equals, manifest]
    , pyPost
    ]
   where
    manifest = pyRecord
      [ pyField "name" $ squotes (pretty n)
      , pyField "toplevel" $ squotes (pretty te)
      , pyField "files" $ pyList (fmap pprFile fs)
      , pyField "tool_options" $ pprEdamTools ts
      ]
  
  -- | Information about each file in the project. This does not include
  -- is_include_file or include_path, as these are not currently used by Clash.
  --
  data EdamFile = EdamFile
    { efName        :: FilePath
    , efType        :: EdamFileType
    , efLogicalName :: Text
    }
  
  pprFile :: EdamFile -> Doc ann
  pprFile (EdamFile n ty ln) =
    pyRecord
      [ pyField "name" $ joinPath n
      , pyField "file_type" $ squotes (pprFileType ty)
      , pyField "logical_name" $ squotes (pretty ln)
      ]
  
  -- | A subset of the file types recognized by Edalize. The supported formats
  -- are largely from IP-XACT 2014 (IEEE 1685-2014), although Edalize extends
  -- this with other types, e.g. QSYS.
  --
  -- Only file types which are generated by Clash are listed.
  --
  data EdamFileType
    = Unknown
      -- ^ Unknown file type.
    | VhdlSource
      -- ^ VHDL source.
    | VerilogSource
      -- ^ Verilog source.
    | SystemVerilogSource
      -- ^ SystemVerilog source.
    | TclSource
      -- ^ Tool Command Language source.
    | QSYS
      -- ^ QSys system source.
    | SDC
      -- ^ Synopsys Design Constraints source.
    deriving (Eq, Show)
  
  pprFileType :: EdamFileType -> Doc ann
  pprFileType = \case
    Unknown -> "unknown"
    VhdlSource -> "vhdlSource"
    VerilogSource -> "verilogSource"
    SystemVerilogSource -> "systemVerilogSource"
    TclSource -> "tclSource"
    QSYS -> "QSYS"
    SDC -> "SDC"
  
  -- | Tool-specific configuration used by Edalize.
  -- Currently only tools which are supported by Clash are provided.
  --
  data EdamTools = EdamTools
    { etGhdl      :: Maybe GhdlOptions
    , etIcarus    :: Maybe IcarusOptions
    , etModelsim  :: Maybe ModelsimOptions
    , etQuartus   :: Maybe QuartusOptions
    , etVivado    :: Maybe VivadoOptions
    }
  
  instance Default EdamTools where
    def = EdamTools def def def def def
  
  pprEdamTools :: EdamTools -> Doc ann
  pprEdamTools tools =
    pyRecord
      [ pyField "ghdl" $ pprGhdlOptions ghdl
      , pyField "icarus" $ pprIcarusOptions icarus
      , pyField "modelsim" $ pprModelsimOptions modelsim
      , pyDocField
          "quartus"
          "TODO Specify options if using Quartus"
          (pprQuartusOptions quartus)
  
      , pyDocField
          "vivado"
          "TODO Specify options if using Vivado"
          (pprVivadoOptions vivado)
      ]
   where
    ghdl     = fromMaybe def (etGhdl tools)
    icarus   = fromMaybe def (etIcarus tools)
    modelsim = fromMaybe def (etModelsim tools)
    quartus  = fromMaybe def (etQuartus tools)
    vivado   = fromMaybe def (etVivado tools)
  
  data GhdlOptions = GhdlOptions
    { ghdlAnalyseOpts :: [Text]
    , ghdlRunOpts     :: [Text]
    }
  
  instance Default GhdlOptions where
    def = GhdlOptions [] []
  
  pprGhdlOptions :: GhdlOptions -> Doc ann
  pprGhdlOptions (GhdlOptions aOpts rOpts) =
    pyRecord
      [ pyDocField
          "analyze_options"
          "Command line arguments for analysis"
          (flagList (fmap pretty aOpts))
  
      , pyDocField
          "run_options"
          "Command line arguments for simulation"
          (flagList (fmap pretty rOpts))
      ]
  
  data IcarusOptions = IcarusOptions
    { icarusOpts      :: [Text]
    , icarusTimeScale :: Text
    }
  
  instance Default IcarusOptions where
    def = IcarusOptions [] "100fs/100fs"
  
  pprIcarusOptions :: IcarusOptions -> Doc ann
  pprIcarusOptions (IcarusOptions opts ts) =
    pyRecord
      [ pyDocField
          "iverilog_options"
          "Command line options for iverilog"
          (flagList (fmap pretty opts))
  
      , pyDocField
          "timescale"
          "Default timescale for simulation"
          (squotes (pretty ts))
      ]
  
  data ModelsimOptions = ModelsimOptions
    { msVlogOpts :: [Text]
    , msVsimOpts :: [Text]
    }
  
  instance Default ModelsimOptions where
    def = ModelsimOptions [] []
  
  pprModelsimOptions :: ModelsimOptions -> Doc ann
  pprModelsimOptions (ModelsimOptions vlog vsim) =
    pyRecord
      [ pyDocField
          "vlog_options"
          "Command line arguments for vlog"
          (flagList (fmap pretty vlog))
  
      , pyDocField
          "vsim_options"
          "Command line arguments for vsim"
          (flagList (fmap pretty vsim))
      ]
  
  data QuartusOptions = QuartusOptions
    { quartusBoardDevIndex :: Int
    , quartusFamily        :: Text
    , quartusDevice        :: Text
    , quartusOpts          :: [Text]
    , quartusDseOpts       :: [Text]
    }
  
  instance Default QuartusOptions where
    def = QuartusOptions 1 "" "" [] []
  
  pprQuartusOptions :: QuartusOptions -> Doc ann
  pprQuartusOptions (QuartusOptions bdi fam dev opts dse) =
    pyRecord
      [ pyDocField
          "board_device_index"
          "Specify the FPGA's device number in the JTAG chain"
          (squotes (pretty bdi))
  
      , pyDocField
          "family"
          "FPGA family, e.g. Cyclone IV E"
          (squotes (pretty fam))
  
      , pyDocField
          "device"
          "Device identifier, e.g. EP4CE55F23C8"
          (squotes (pretty dev))
  
      , pyDocField
          "quartus_options"
          "Command line arguments for Quartus"
          (flagList (fmap pretty opts))
  
      , pyDocField
          "dse_options"
          "Command line arguments for Design Space Explorer"
          (flagList (fmap pretty dse))
      ]
  
  data VivadoOptions = VivadoOptions
    { vivadoPart :: Text
    }
  
  instance Default VivadoOptions where
    def = VivadoOptions ""
  
  pprVivadoOptions :: VivadoOptions -> Doc ann
  pprVivadoOptions (VivadoOptions part) =
    pyRecord
      [ pyDocField
          "part"
          "Specify target part by ID, e.g. xc7z020-1clg400c"
          (squotes (pretty part))
      ]
  
  -- Helpers; don't export
  
  pyPre :: Doc ann
  pyPre = vsep
    [ "import os"
    , ""
    , "edam_root = os.path.dirname(os.path.realpath(__file__))"
    , "work_root = 'build'"
    , ""
    , "# TODO Specify the EDA tool to use"
    , "tool = ''"
    , ""
    ]
  
  pyPost :: Doc ann
  pyPost = vsep
    [ ""
    , "if __name__ == '__main__':"
    , indent 4 $ vsep
        [ "from edalize import *"
        , ""
        , "tool = get_edatool(tool)(edam=edam, work_root=work_root)"
        , "os.makedirs(work_root)"
        , "tool.configure()"
        , "tool.build()"
        ]
    ]
  
  pyList :: [Doc ann] -> Doc ann
  pyList xs = vsep [lbracket, indent 4 (commaList xs), rbracket]
  
  pyRecord :: [Doc ann] -> Doc ann
  pyRecord xs = vsep [lbrace, indent 4 (commaList xs), rbrace]
  
  pyComment :: Text -> Doc ann
  pyComment x = hsep ["#", pretty x]
  
  pyField :: Text -> Doc ann -> Doc ann
  pyField n x = hcat [squotes (pretty n), colon, space, x]
  
  pyDocField :: Text -> Text -> Doc ann -> Doc ann
  pyDocField n d x = vsep [pyComment d, pyField n x]
  
  joinPath :: FilePath -> Doc ann
  joinPath x = hcat
    [ "os.path.join"
    , parens (hsep $ punctuate comma ["edam_root", squotes (pretty x)])
    ]
  
  flagList :: [Doc ann] -> Doc ann
  flagList = squotes . hsep
  
  commaList :: [Doc ann] -> Doc ann
  commaList = vsep . punctuate comma
  
  