content: {-|
    Copyright   :  (C) 2018     , Google Inc.,
                       2021-2023, QBayLogic B.V.,
                       2022     , Google Inc.
    License     :  BSD2 (see the file LICENSE)
    Maintainer  :  QBayLogic B.V. <devops@qbaylogic.com>
  
    Blackbox template functions for Clash.Intel.ClockGen
  -}
  
  {-# LANGUAGE OverloadedStrings #-}
  {-# LANGUAGE QuasiQuotes       #-}
  {-# LANGUAGE ViewPatterns      #-}
  
  module Clash.Primitives.Intel.ClockGen where
  
  import Control.Monad.State
  import Data.List (zip4)
  import Data.List.Infinite (Infinite(..), (...))
  import Data.Maybe (fromMaybe)
  import Data.Text.Prettyprint.Doc.Extra
  import Text.Show.Pretty (ppShow)
  
  import Clash.Backend
  import qualified Clash.Netlist.Id as Id
  import Clash.Netlist.Types
  import Clash.Netlist.Util
  import qualified Clash.Primitives.DSL as DSL
  import Clash.Signal (periodToHz)
  import Data.Text.Extra (showt)
  
  import qualified Data.String.Interpolate as I
  import qualified Data.Text as TextS
  import qualified Prettyprinter.Interpolate as I
  
  data Variant = Altpll | AlteraPll
  
  hdlUsed :: [Int]
  hdlUsed = [ clk, rst ]
   where
    _knownDomIn
      :< _clocksClass
      :< _clocksCxt
      :< _numOutClocks
      :< clk
      :< rst
      :< _ = (0...)
  
  hdlValid :: BlackBoxContext -> Bool
  hdlValid bbCtx | [(_,Product {})] <- bbResults bbCtx = True
  hdlValid _ = False
  
  qsysUsed :: [Int]
  qsysUsed = [ knownDomIn, clocksCxt ]
   where
    knownDomIn
      :< _clocksClass
      :< clocksCxt
      :< _ = (0...)
  
  altpllTF :: TemplateFunction
  altpllTF = TemplateFunction hdlUsed hdlValid (hdlTemplate Altpll)
  
  altpllQsysTF :: TemplateFunction
  altpllQsysTF = TemplateFunction qsysUsed valid altpllQsysTemplate
   where
    valid = const True
  
  alteraPllTF :: TemplateFunction
  alteraPllTF = TemplateFunction hdlUsed hdlValid (hdlTemplate AlteraPll)
  
  alteraPllQsysTF :: TemplateFunction
  alteraPllQsysTF = TemplateFunction qsysUsed valid alteraPllQsysTemplate
   where
    valid = const True
  
  hdlTemplate ::
    forall s .
    Backend s =>
    Variant ->
    BlackBoxContext ->
    State s Doc
  hdlTemplate variant bbCtx
    | [ _knownDomIn
      , _clocksClass
      , _clocksCxt
      , _numOutClocks
      , clk
      , rst
      ] <- map fst (DSL.tInputs bbCtx)
    , [DSL.ety -> resultTy] <- DSL.tResults bbCtx
    , Product _ _ (init -> pllOutTys) <- resultTy
    , [compName] <- bbQsysIncName bbCtx
    = do
      let
        stdName Altpll = "altpll"
        stdName AlteraPll = "altera_pll"
        pllOutName Altpll = "c"
        pllOutName AlteraPll = "outclk_"
        clkInName Altpll = "clk"
        clkInName AlteraPll = "refclk"
        rstName Altpll = "areset"
        rstName AlteraPll = "rst"
  
      instName <- Id.makeBasic $ fromMaybe (stdName variant) $ bbCtxName bbCtx
  
      -- TODO: unsafeMake is dubious here: I don't think we take names in
      -- TODO: bbQsysIncName into account when generating fresh ids
      let compNameId = Id.unsafeMake compName
  
      DSL.declarationReturn bbCtx (stdName variant <> "_block") $ do
  
        rstHigh <- DSL.unsafeToActiveHigh "reset" rst
        pllOuts <- DSL.declareN "pllOut" pllOutTys
        locked <- DSL.declare "locked" Bit
        pllLock <- DSL.boolFromBit "pllLock" locked
  
        let
          pllOutNames =
            map (\n -> pllOutName variant <> showt n)
              [0 .. length pllOutTys - 1]
          compInps =
            [ (clkInName variant, DSL.ety clk)
            , (rstName variant, DSL.ety rstHigh)
            ]
          compOuts = zip pllOutNames pllOutTys  <> [("locked", Bit)]
          inps =
            [ (clkInName variant, clk)
            , (rstName variant, rstHigh)
            ]
          outs = zip pllOutNames pllOuts <> [("locked", locked)]
  
        DSL.compInBlock compName compInps compOuts
        DSL.instDecl Empty compNameId instName [] inps outs
  
        pure [DSL.constructProduct resultTy (pllOuts <> [pllLock])]
    | otherwise
    = error $ ppShow bbCtx
  
  altpllQsysTemplate
    :: Backend s
    => BlackBoxContext
    -> State s Doc
  altpllQsysTemplate bbCtx
    |   (_,stripVoid -> (KnownDomain _ clkInPeriod _ _ _ _),_)
      : _clocksClass
      : (_,stripVoid -> Product _ _ (init -> kdOuts),_)
      : _ <- bbInputs bbCtx
    = let
      clkPeriod (KnownDomain _ p _ _ _ _) = p
      clkPeriod _ =
        error $ "Internal error: not a KnownDomain\n" <> ppShow bbCtx
  
      clkFreq p = periodToHz (fromInteger p) / 1e6 :: Double
  
      clkOutPeriods = map clkPeriod kdOuts
      clkLcms = map (lcm clkInPeriod) clkOutPeriods
      clkMults = zipWith quot clkLcms clkOutPeriods
      clkDivs = map (`quot` clkInPeriod) clkLcms
      clkOutFreqs = map clkFreq clkOutPeriods
  
      qsysParams = TextS.intercalate "\n  "
        [[I.__i|
          <parameter name="PORT_clk#{n}" value="PORT_USED" />
            <parameter name="CLK#{n}_MULTIPLY_BY" value="#{clkMult}" />
            <parameter name="CLK#{n}_DIVIDE_BY" value="#{clkDiv}" />
            <parameter name="CLK#{n}_DUTY_CYCLE" value="50" />
            <parameter name="CLK#{n}_PHASE_SHIFT" value="0" />
          |]
        | (clkMult, clkDiv, n) <- zip3 clkMults clkDivs [(0 :: Word)..]
        ]
  
      qsysConsts = TextS.intercalate "\n    "
        [[I.__i|
          CT\#PORT_clk#{n} PORT_USED
              CT\#CLK#{n}_MULTIPLY_BY #{clkMult}
              CT\#CLK#{n}_DIVIDE_BY #{clkDiv}
              CT\#CLK#{n}_DUTY_CYCLE 50
              CT\#CLK#{n}_PHASE_SHIFT 0
          |]
        | (clkMult, clkDiv, n) <- zip3 clkMults clkDivs [(0 :: Word)..]
        ]
  
      qsysPorts =
        TextS.intercalate "\n    "
          [[I.i|IF\#c#{n} {output 0}|] | n <- [0 .. length kdOuts - 1]]
  
      qsysPrivs = TextS.intercalate "\n    "
        [[I.__i|
          PT\#MULT_FACTOR#{n} #{clkMult}
              PT\#DIV_FACTOR#{n} #{clkDiv}
              PT\#EFF_OUTPUT_FREQ_VALUE#{n} #{clkOutFreq}
              PT\#DUTY_CYCLE#{n} 50.00000000
              PT\#PHASE_SHIFT0 0.00000000
          |]
        | (clkMult, clkDiv, clkOutFreq, n) <-
            zip4 clkMults clkDivs clkOutFreqs [(0 :: Word)..]
        ]
  
      -- Note [QSys file templates]
      -- This QSys file template was derived from a "full" QSys system with a single
      -- "altpll" IP. Module parameters were then stripped on a trial-and-error
      -- basis to get a template that has the minimal number of parameters, but
      -- still has the desired, working, configuration.
      bbText = [I.__di|
        <?xml version="1.0" encoding="UTF-8"?>
        <system name="$${FILENAME}">
          <module
            name="altpll0"
            kind="altpll"
            enabled="1"
            autoexport="1">
          <parameter name="AVALON_USE_SEPARATE_SYSCLK" value="NO" />
          <parameter name="BANDWIDTH" value="" />
          <parameter name="BANDWIDTH_TYPE" value="AUTO" />
          #{qsysParams}
          <parameter name="COMPENSATE_CLOCK" value="CLK0" />
          <parameter name="INCLK0_INPUT_FREQUENCY" value="#{clkInPeriod}" />
          <parameter name="OPERATION_MODE" value="NORMAL" />
          <parameter name="PLL_TYPE" value="AUTO" />
          <parameter name="PORT_ARESET" value="PORT_USED" />
          <parameter name="PORT_INCLK0" value="PORT_USED" />
          <parameter name="PORT_LOCKED" value="PORT_USED" />
          <parameter name="HIDDEN_IS_FIRST_EDIT" value="0" />
          <parameter name="HIDDEN_CONSTANTS">
            #{qsysConsts}
            CT\#WIDTH_CLOCK 5
            CT\#LPM_TYPE altpll
            CT\#PLL_TYPE AUTO
            CT\#OPERATION_MODE NORMAL
            CT\#COMPENSATE_CLOCK CLK0
            CT\#INCLK0_INPUT_FREQUENCY #{clkInPeriod}
            CT\#PORT_INCLK0 PORT_USED
            CT\#PORT_ARESET PORT_USED
            CT\#BANDWIDTH_TYPE AUTO
            CT\#PORT_LOCKED PORT_USED</parameter>
          <parameter name="HIDDEN_IF_PORTS">
            IF\#phasecounterselect {input 4}
            IF\#locked {output 0}
            IF\#reset {input 0}
            IF\#clk {input 0}
            IF\#phaseupdown {input 0}
            IF\#scandone {output 0}
            IF\#readdata {output 32}
            IF\#write {input 0}
            IF\#scanclk {input 0}
            IF\#phasedone {output 0}
            IF\#address {input 2}
            #{qsysPorts}
            IF\#writedata {input 32}
            IF\#read {input 0}
            IF\#areset {input 0}
            IF\#scanclkena {input 0}
            IF\#scandataout {output 0}
            IF\#configupdate {input 0}
            IF\#phasestep {input 0}
            IF\#scandata {input 0}</parameter>
          <parameter name="HIDDEN_MF_PORTS">
            MF\#areset 1
            MF\#clk 1
            MF\#locked 1
            MF\#inclk 1</parameter>
          <parameter name="HIDDEN_PRIVATES">
            #{qsysPrivs}</parameter>
          </module>
        </system>
        |]
      in
        pure bbText
    | otherwise
    = error $ ppShow bbCtx
  
  alteraPllQsysTemplate
    :: Backend s
    => BlackBoxContext
    -> State s Doc
  alteraPllQsysTemplate bbCtx
    |   (_,stripVoid -> kdIn,_)
      : _clocksClass
      : (_,stripVoid -> Product _ _ (init -> kdOuts),_)
      : _ <- bbInputs bbCtx
    = let
      clkFreq (KnownDomain _ p _ _ _ _)
        = periodToHz (fromIntegral p) / 1e6 :: Double
      clkFreq _ =
        error $ "Internal error: not a KnownDomain\n" <> ppShow bbCtx
  
      clkOuts = TextS.intercalate "\n"
        [[I.i|  <parameter name="gui_output_clock_frequency#{n}" value="#{f}"/>|]
        | (n,f) <- zip [(0 :: Word)..] (map clkFreq kdOuts)
        ]
  
      -- See Note [QSys file templates] on how this qsys template was derived.
      bbText = [I.__di|
        <?xml version="1.0" encoding="UTF-8"?>
        <system name="$${FILENAME}">
        <module
            name="pll_0"
            kind="altera_pll"
            enabled="1"
            autoexport="1">
          <parameter name="gui_feedback_clock" value="Global Clock" />
          <parameter name="gui_number_of_clocks" value="#{length kdOuts}" />
          <parameter name="gui_operation_mode" value="direct" />
        #{clkOuts}
          <parameter name="gui_pll_mode" value="Integer-N PLL" />
          <parameter name="gui_reference_clock_frequency" value="#{clkFreq kdIn}" />
          <parameter name="gui_use_locked" value="true" />
        </module>
        </system>
        |]
      in
        pure bbText
    | otherwise
    = error $ ppShow bbCtx
  