{-|
Copyright  :  (C) 2020-2023, QBayLogic B.V.,
                  2022-2023, Google LLC
License    :  BSD2 (see the file LICENSE)
Maintainer :  QBayLogic B.V. <devops@qbaylogic.com>

Utilities to deal with resets.
-}

{-# LANGUAGE CPP #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TypeFamilies #-}

{-# OPTIONS_GHC -fplugin=GHC.TypeLits.Normalise #-}
{-# OPTIONS_GHC -fplugin=GHC.TypeLits.KnownNat.Solver #-}

module Clash.Explicit.Reset
  ( -- Defined in this module
    resetSynchronizer
  , resetGlitchFilter
  , resetGlitchFilterWithReset
  , unsafeResetGlitchFilter
  , holdReset
  , convertReset
  , noReset
  , andReset, unsafeAndReset
  , orReset, unsafeOrReset

    -- Re-exports
  , Reset
  , resetGen
  , resetGenN
  , resetKind
  , systemResetGen
  , unsafeToReset
  , unsafeFromReset
  , unsafeToActiveHigh
  , unsafeToActiveLow
  , unsafeFromActiveHigh
  , unsafeFromActiveLow

  -- * Deprecated
  , unsafeFromHighPolarity
  , unsafeFromLowPolarity
  , unsafeToHighPolarity
  , unsafeToLowPolarity
  ) where

import           Data.Type.Equality ((:~:)(Refl))

import           Clash.Class.Num (satSucc, SaturationMode(SatBound))
import           Clash.Explicit.Signal
import           Clash.Explicit.Synchronizer (dualFlipFlopSynchronizer)
import           Clash.Promoted.Nat
import           Clash.Signal.Internal
import           Clash.Sized.Index (Index)

import           GHC.Stack (HasCallStack)
import           GHC.TypeLits (type (+), type (<=))

{- $setup
>>> import Clash.Explicit.Prelude
-}

-- | A reset that is never asserted
noReset :: KnownDomain dom => Reset dom
noReset = unsafeFromActiveHigh (pure False)

-- | Output reset will be asserted when either one of the input resets is
-- asserted
orReset ::
  forall dom .
  HasSynchronousReset dom =>
  Reset dom ->
  Reset dom ->
  Reset dom
orReset = unsafeOrReset

-- | Output reset will be asserted when either one of the input resets is
-- asserted. This function is considered unsafe because it can be used on
-- domains with components with asynchronous resets, where use of this function
-- can introduce glitches triggering a reset.
unsafeOrReset :: forall dom. KnownDomain dom => Reset dom -> Reset dom -> Reset dom
unsafeOrReset (unsafeFromReset -> rst0) (unsafeFromReset -> rst1) =
  unsafeToReset $
    case resetPolarity @dom of
      SActiveHigh -> rst0 .||. rst1
      SActiveLow  -> rst0 .&&. rst1

-- | Output reset will be asserted when both input resets are asserted
andReset ::
  forall dom .
  HasSynchronousReset dom =>
  Reset dom ->
  Reset dom ->
  Reset dom
andReset = unsafeAndReset

-- | Output reset will be asserted when both input resets are asserted. This
-- function is considered unsafe because it can be used on domains with
-- components with asynchronous resets, where use of this function can introduce
-- glitches triggering a reset.
unsafeAndReset :: forall dom. KnownDomain dom => Reset dom -> Reset dom -> Reset dom
unsafeAndReset (unsafeFromReset -> rst0) (unsafeFromReset -> rst1) =
  unsafeToReset $
    case resetPolarity @dom of
      SActiveHigh -> rst0 .&&. rst1
      SActiveLow  -> rst0 .||. rst1

-- | The resetSynchronizer will synchronize an incoming reset according to
-- whether the domain is synchronous or asynchronous.
--
-- For asynchronous resets this synchronizer ensures the reset will only
-- be de-asserted synchronously but it can still be asserted asynchronously.
-- The reset assert is immediate, but reset de-assertion is delayed by two
-- cycles.
--
-- Normally, asynchronous resets can be both asynchronously asserted and
-- de-asserted. Asynchronous de-assertion can induce meta-stability in the
-- component which is being reset. To ensure this doesn't happen,
-- 'resetSynchronizer' ensures that de-assertion of a reset happens
-- synchronously. Assertion of the reset remains asynchronous.
--
-- Note that asynchronous assertion does not induce meta-stability in the
-- component whose reset is asserted. However, when a component \"A\" in another
-- clock or reset domain depends on the value of a component \"B\" being
-- reset, then asynchronous assertion of the reset of component \"B"\ can induce
-- meta-stability in component \"A\". To prevent this from happening you need
-- to use a proper synchronizer, for example one of the synchronizers in
-- "Clash.Explicit.Synchronizer".
--
-- For synchronous resets this function ensures that the reset is asserted and
-- de-asserted synchronously. Both the assertion and de-assertion of the reset
-- are delayed by two cycles.
--
-- === __Example 1__
-- The circuit below detects a rising bit (i.e., a transition from 0 to 1) in a
-- given argument. It takes a reset that is not synchronized to any of the other
-- incoming signals and synchronizes it using 'resetSynchronizer'.
--
-- @
-- topEntity
--   :: Clock  System
--   -> Reset  System
--   -> Signal System Bit
--   -> Signal System (BitVector 8)
-- topEntity clk asyncRst key1 =
--   withClockResetEnable clk rst enableGen leds
--  where
--   rst   = 'resetSynchronizer' clk asyncRst
--   key1R = isRising 1 key1
--   leds  = mealy blinkerT (1, False, 0) key1R
-- @
--
-- === __Example 2__
-- Similar to /Example 1/ this circuit detects a rising bit (i.e., a transition
-- from 0 to 1) in a given argument. It takes a clock that is not stable yet and
-- a reset signal that is not synchronized to any other signals. It stabilizes
-- the clock and then synchronizes the reset signal.
--
--
-- Note that the function 'Clash.Intel.ClockGen.altpllSync' provides this
-- functionality in a convenient form, obviating the need for
-- @resetSynchronizer@ for this use case.
--
-- @
-- topEntity
--   :: Clock  System
--   -> Reset  System
--   -> Signal System Bit
--   -> Signal System (BitVector 8)
-- topEntity clk rst key1 =
--     let  (pllOut,pllStable) = unsafeAltpll clk rst
--          rstSync            = 'resetSynchronizer' pllOut (unsafeFromActiveLow pllStable)
--     in   exposeClockResetEnable leds pllOut rstSync enableGen
--   where
--     key1R  = isRising 1 key1
--     leds   = mealy blinkerT (1, False, 0) key1R
-- @
--
-- === __Implementation details__
-- 'resetSynchronizer' implements the following circuit for asynchronous domains:
--
-- >                                   rst
-- >   --------------------------------------+
-- >                       |                 |
-- >                  +----v----+       +----v----+
-- >     deasserted   |         |       |         |
-- >   --------------->         +------->         +-------->
-- >                  |         |       |         |
-- >              +---|>        |   +---|>        |
-- >              |   |         |   |   |         |
-- >              |   +---------+   |   +---------+
-- >      clk     |                 |
-- >   -----------------------------+
--
-- This corresponds to figure 3d at <https://www.embedded.com/asynchronous-reset-synchronization-and-distribution-challenges-and-solutions/>
--
-- For synchronous domains two sequential dflipflops are used:
--
-- >                  +---------+       +---------+
-- >     rst          |         |       |         |
-- >   --------------->         +------->         +-------->
-- >                  |         |       |         |
-- >              +---|>        |   +---|>        |
-- >              |   |         |   |   |         |
-- >              |   +---------+   |   +---------+
-- >      clk     |                 |
-- >   -----------------------------+
--
resetSynchronizer
  :: forall dom
   . KnownDomain dom
  => Clock dom
  -> Reset dom
  -> Reset dom
resetSynchronizer clk rst = rstOut
 where
  isActiveHigh = case resetPolarity @dom of { SActiveHigh -> True; _ -> False }
  rstOut =
    case (resetKind @dom) of
      SAsynchronous -> unsafeToReset
                         $ register clk rst enableGen isActiveHigh
                         $ register clk rst enableGen isActiveHigh
                         $ pure (not isActiveHigh)
      SSynchronous  -> unsafeToReset
                         $ delay clk enableGen isActiveHigh
                         $ delay clk enableGen isActiveHigh
                         $ unsafeFromReset rst

-- | Filter glitches from reset signals by only triggering a reset after it has
-- been asserted for /glitchlessPeriod/ cycles. Similarly, it will stay
-- asserted until a /glitchlessPeriod/ number of deasserted cycles have been
-- observed.
--
-- This circuit can only be used on platforms supporting initial values. This
-- restriction can be worked around by using 'unsafeResetGlitchFilter' but this
-- is not recommended.
--
-- On platforms without initial values, you should instead use
-- 'resetGlitchFilterWithReset' with an additional power-on reset, or
-- 'holdReset' if filtering is only needed on deassertion.
--
-- At power-on, the reset will be asserted. If the filtered reset input remains
-- unasserted, the output reset will deassert after /glitchlessPeriod/ clock
-- cycles.
--
-- If @resetGlitchFilter@ is used in a domain with asynchronous resets
-- ('Asynchronous'), @resetGlitchFilter@ will first synchronize the reset input
-- with 'dualFlipFlopSynchronizer'.
--
-- === __Example 1__
-- >>> let sampleResetN n = sampleN n . unsafeToActiveHigh
-- >>> let resetFromList = unsafeFromActiveHigh . fromList
-- >>> let rst = resetFromList [True, True, False, False, True, False, False, True, True, False, True, True]
-- >>> sampleResetN 12 (resetGlitchFilter d2 (clockGen @XilinxSystem) rst)
-- [True,True,True,True,False,False,False,False,False,True,True,True]
resetGlitchFilter
  :: forall dom glitchlessPeriod
   . ( HasCallStack
     , HasDefinedInitialValues dom
     , 1 <= glitchlessPeriod
     )
  => SNat glitchlessPeriod
  -- ^ Consider a reset signal to be properly asserted after having seen the
  -- reset asserted for /glitchlessPeriod/ cycles straight.
  -> Clock dom
  -> Reset dom
  -> Reset dom
resetGlitchFilter = unsafeResetGlitchFilter
{-# INLINE resetGlitchFilter #-}

-- | Filter glitches from reset signals by only triggering a reset after it has
-- been asserted for /glitchlessPeriod/ cycles. Similarly, it will stay
-- asserted until a /glitchlessPeriod/ number of deasserted cycles have been
-- observed.
--
-- On platforms without initial values ('Unknown'), 'resetGlitchFilter' cannot
-- be used and you should use 'resetGlitchFilterWithReset' with an additional
-- power-on reset, or 'holdReset' if filtering is only needed on deassertion.
--
-- @unsafeResetGlitchFilter@ allows breaking the requirement of initial values,
-- but by doing so it is possible that the design starts up with a period of up
-- to /2 * glitchlessPeriod/ clock cycles where the reset output is unasserted
-- (or longer in the case of glitches on the filtered reset input). This can
-- cause a number of problems. The outputs\/tri-states of the design might
-- output random things, including coherent but incorrect streams of data. This
-- might have grave repercussions in the design's environment (sending network
-- packets, overwriting non-volatile memory, in extreme cases destroying
-- controlled equipment or causing harm to living beings, ...).
--
-- Without initial values, the synthesized result of @unsafeResetGlitchFilter@
-- eventually correctly outputs a filtered version of the reset input. However,
-- in simulation, it will indefinitely output an undefined value. This happens
-- both in Clash simulation and in HDL simulation. Therefore, simulation should
-- not include the @unsafeResetGlitchFilter@.
--
-- If @unsafeResetGlitchFilter@ is used in a domain with asynchronous resets
-- ('Asynchronous'), @unsafeResetGlitchFilter@ will first synchronize the reset
-- input with 'dualFlipFlopSynchronizer'.
unsafeResetGlitchFilter
  :: forall dom glitchlessPeriod
   . ( HasCallStack
     , KnownDomain dom
     , 1 <= glitchlessPeriod
     )
  => SNat glitchlessPeriod
  -- ^ Consider a reset signal to be properly asserted after having seen the
  -- reset asserted for /glitchlessPeriod/ cycles straight.
  -> Clock dom
  -> Reset dom
  -> Reset dom
unsafeResetGlitchFilter glitchlessPeriod clk =
  resetGlitchFilter# glitchlessPeriod reg dffSync
 where
  reg = delay clk enableGen
  dffSync = dualFlipFlopSynchronizer clk clk noReset enableGen
{-# INLINE unsafeResetGlitchFilter #-}

-- | Filter glitches from reset signals by only triggering a reset after it has
-- been asserted for /glitchlessPeriod/ cycles. Similarly, it will stay
-- asserted until a /glitchlessPeriod/ number of deasserted cycles have been
-- observed.
--
-- Compared to 'resetGlitchFilter', this function adds an additional power-on
-- reset input. As soon as the power-on reset asserts, the reset output will
-- assert, and after the power-on reset deasserts, the reset output will stay
-- asserted for another /glitchlessPeriod/ clock cycles. This is identical
-- behavior to 'holdReset' where it concerns the power-on reset, and differs
-- from the filtered reset, which will only cause an assertion after
-- /glitchlessPeriod/ cycles.
--
-- If @resetGlitchFilterWithReset@ is used in a domain with asynchronous resets
-- ('Asynchronous'), @resetGlitchFilterWithReset@ will first synchronize the
-- reset input with 'dualFlipFlopSynchronizer'.
resetGlitchFilterWithReset
  :: forall dom glitchlessPeriod
   . ( HasCallStack
     , KnownDomain dom
     , 1 <= glitchlessPeriod
     )
  => SNat glitchlessPeriod
  -- ^ Consider a reset signal to be properly asserted after having seen the
  -- reset asserted for /glitchlessPeriod/ cycles straight.
  -> Clock dom
  -> Reset dom
  -- ^ The power-on reset for the glitch filter itself
  -> Reset dom
  -- ^ The reset that will be filtered
  -> Reset dom
resetGlitchFilterWithReset glitchlessPeriod clk ownRst =
  resetGlitchFilter# glitchlessPeriod reg dffSync
 where
  reg = register clk ownRst enableGen
  dffSync = dualFlipFlopSynchronizer clk clk ownRst enableGen
{-# INLINE resetGlitchFilterWithReset #-}

resetGlitchFilter#
  :: forall dom glitchlessPeriod state
   . ( HasCallStack
     , KnownDomain dom
     , 1 <= glitchlessPeriod
     , state ~ (Bool, Index glitchlessPeriod)
     )
  => SNat glitchlessPeriod
  -> (   state
      -> Signal dom state
      -> Signal dom state
     )
  -> (   Bool
      -> Signal dom Bool
      -> Signal dom Bool
     )
  -> Reset dom
  -> Reset dom
resetGlitchFilter# SNat reg dffSync rstIn0 =
  let s' = go <$> s <*> rstIn2
      s  = reg (asserted, 0) s'
  in unsafeToReset $ fst <$> s
 where
  rstIn1 = unsafeFromReset rstIn0
  rstIn2 =
    case resetKind @dom of
      SAsynchronous -> dffSync asserted rstIn1
      SSynchronous -> rstIn1

  go :: state -> Bool -> state
  go (state, count) reset
    | reset == state    = (state,     0)
    | count == maxBound = (not state, 0)
    | otherwise         = (state,     count + 1)

  asserted :: Bool
  asserted =
    case resetPolarity @dom of
      SActiveHigh -> True
      SActiveLow -> False

-- | Hold reset for a number of cycles relative to an incoming reset signal.
--
-- Example:
--
-- >>> let sampleWithReset = sampleN 8 . unsafeToActiveHigh
-- >>> sampleWithReset (holdReset @System clockGen enableGen (SNat @2) (resetGenN (SNat @3)))
-- [True,True,True,True,True,False,False,False]
--
-- 'holdReset' holds the reset for an additional 2 clock cycles for a total
-- of 5 clock cycles where the reset is asserted. 'holdReset' also works on
-- intermediate assertions of the reset signal:
--
-- >>> let rst = fromList [True, False, False, False, True, False, False, False]
-- >>> sampleWithReset (holdReset @System clockGen enableGen (SNat @2) (unsafeFromActiveHigh rst))
-- [True,True,True,False,True,True,True,False]
--
holdReset
  :: forall dom n
   . KnownDomain dom
  => Clock dom
  -> Enable dom
  -- ^ Global enable
  -> SNat n
  -- ^ Hold for /n/ cycles, counting from the moment the incoming reset
  -- signal becomes deasserted.
  -> Reset dom
  -- ^ Reset to extend
  -> Reset dom
holdReset clk en SNat rst =
  unsafeFromActiveHigh ((/=maxBound) <$> counter)
 where
  counter :: Signal dom (Index (n+1))
  counter = register clk rst en 0 (satSucc SatBound <$> counter)

-- | Convert between different types of reset, adding a synchronizer when
-- the domains are not the same. See 'resetSynchronizer' for further details
-- about reset synchronization.
--
-- If @domA@ has 'Synchronous' resets, a flip-flop is inserted in @domA@ to
-- filter glitches. This adds one @domA@ clock cycle delay.
convertReset
  :: forall domA domB
   . ( KnownDomain domA
     , KnownDomain domB
     )
  => Clock domA
  -> Clock domB
  -> Reset domA
  -> Reset domB
convertReset clkA clkB rstA0 = rstB1
 where
  rstA1 = unsafeFromReset rstA0
  rstA2 =
    case (resetPolarity @domA, resetPolarity @domB) of
      (SActiveLow, SActiveLow)   -> rstA1
      (SActiveHigh, SActiveHigh) -> rstA1
      _                          -> not <$> rstA1
  rstA3 =
    case resetKind @domA of
      SSynchronous -> delay clkA enableGen assertedA rstA2
      _            -> rstA2
  rstB0 = unsafeToReset $ unsafeSynchronizer clkA clkB rstA3
  rstB1 =
    case (sameDomain @domA @domB) of
      Just Refl -> rstA0
      Nothing   -> resetSynchronizer clkB rstB0
  assertedA :: Bool
  assertedA =
    case resetPolarity @domA of
      SActiveHigh -> True
      SActiveLow  -> False
