{-| Copyright : (C) 2013-2016, University of Twente, 2017 , Google Inc. 2019 , Myrtle Software Ltd 2023 , Alex Mason License : BSD2 (see the file LICENSE) Maintainer : Christiaan Baaij <christiaan.baaij@gmail.com> Whereas the output of a Moore machine depends on the /previous state/, the output of a Mealy machine depends on /current transition/. Mealy machines are strictly more expressive, but may impose stricter timing requirements. -} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE NoGeneralizedNewtypeDeriving #-} {-# LANGUAGE Safe #-} module Clash.Prelude.Mealy ( -- * Mealy machine synchronized to the system clock mealy , mealyS , mealyB , mealySB , (<^>) ) where import qualified Clash.Explicit.Mealy as E import Clash.Signal import Clash.XException (NFDataX) import Control.Monad.State.Strict (State) {- $setup >>> :set -XDataKinds -XTypeApplications -XDeriveGeneric -XDeriveAnyClass >>> import Clash.Prelude as C >>> import Clash.Prelude.Mealy (mealyS) >>> import qualified Data.List as L >>> import Control.Lens (Lens', (%=), (-=), uses, use) >>> import Control.Monad.State.Strict (State) >>> :{ let macT s (x,y) = (s',s) where s' = x * y + s mac = mealy macT 0 :} >>> :{ data DelayState = DelayState { _history :: Vec 4 Int , _untilValid :: Index 4 } deriving (Generic,NFDataX) :} >>> :{ history :: Lens' DelayState (Vec 4 Int) history f = \(DelayState d u) -> (`DelayState` u) <$> f d :} >>> :{ untilValid :: Lens' DelayState (Index 4) untilValid f = \(DelayState d u) -> DelayState d <$> f u :} >>> :{ delayS :: Int -> State DelayState (Maybe Int) delayS n = do history %= (n +>>) remaining <- use untilValid if remaining > 0 then do untilValid -= 1 return Nothing else do out <- uses history C.last return (Just out) :} >>> let initialDelayState = DelayState (C.repeat 0) maxBound >>> :{ delayTop :: HiddenClockResetEnable dom => Signal dom Int -> Signal dom (Maybe Int) delayTop = mealyS delayS initialDelayState :} -} -- | Create a synchronous function from a combinational function describing -- a mealy machine -- -- @ -- macT -- :: Int -- Current state -- -> (Int,Int) -- Input -- -> (Int,Int) -- (Updated state, output) -- macT s (x,y) = (s',s) -- where -- s' = x * y + s -- -- mac :: HiddenClockResetEnable dom => 'Signal' dom (Int, Int) -> 'Signal' dom Int -- mac = 'mealy' macT 0 -- @ -- -- >>> simulate @System mac [(0,0),(1,1),(2,2),(3,3),(4,4)] -- [0,0,1,5,14... -- ... -- -- Synchronous sequential functions can be composed just like their -- combinational counterpart: -- -- @ -- dualMac -- :: HiddenClockResetEnable dom -- => ('Signal' dom Int, 'Signal' dom Int) -- -> ('Signal' dom Int, 'Signal' dom Int) -- -> 'Signal' dom Int -- dualMac (a,b) (x,y) = s1 + s2 -- where -- s1 = 'mealy' macT 0 ('Clash.Signal.bundle' (a,x)) -- s2 = 'mealy' macT 0 ('Clash.Signal.bundle' (b,y)) -- @ mealy :: ( HiddenClockResetEnable dom , NFDataX s ) => (s -> i -> (s,o)) -- ^ Transfer function in mealy machine form: @state -> input -> (newstate,output)@ -> s -- ^ Initial state -> (Signal dom i -> Signal dom o) -- ^ Synchronous sequential function with input and output matching that -- of the mealy machine mealy :: (s -> i -> (s, o)) -> s -> Signal dom i -> Signal dom o mealy = (KnownDomain dom => Clock dom -> Reset dom -> Enable dom -> (s -> i -> (s, o)) -> s -> Signal dom i -> Signal dom o) -> (s -> i -> (s, o)) -> s -> Signal dom i -> Signal dom o forall (dom :: Domain) r. HiddenClockResetEnable dom => (KnownDomain dom => Clock dom -> Reset dom -> Enable dom -> r) -> r hideClockResetEnable KnownDomain dom => Clock dom -> Reset dom -> Enable dom -> (s -> i -> (s, o)) -> s -> Signal dom i -> Signal dom o forall (dom :: Domain) s i o. (KnownDomain dom, NFDataX s) => Clock dom -> Reset dom -> Enable dom -> (s -> i -> (s, o)) -> s -> Signal dom i -> Signal dom o E.mealy {-# INLINE mealy #-} -- | A version of 'mealy' that does automatic 'Bundle'ing -- -- Given a function @f@ of type: -- -- @ -- __f__ :: Int -> (Bool, Int) -> (Int, (Int, Bool)) -- @ -- -- When we want to make compositions of @f@ in @g@ using 'mealy', we have to -- write: -- -- @ -- g a b c = (b1,b2,i2) -- where -- (i1,b1) = 'Clash.Signal.unbundle' ('mealy' f 0 ('Clash.Signal.bundle' (a,b))) -- (i2,b2) = 'Clash.Signal.unbundle' ('mealy' f 3 ('Clash.Signal.bundle' (c,i1))) -- @ -- -- Using 'mealyB' however we can write: -- -- @ -- g a b c = (b1,b2,i2) -- where -- (i1,b1) = 'mealyB' f 0 (a,b) -- (i2,b2) = 'mealyB' f 3 (c,i1) -- @ mealyB :: ( HiddenClockResetEnable dom , NFDataX s , Bundle i , Bundle o ) => (s -> i -> (s,o)) -- ^ Transfer function in mealy machine form: @state -> input -> (newstate,output)@ -> s -- ^ Initial state -> (Unbundled dom i -> Unbundled dom o) -- ^ Synchronous sequential function with input and output matching that -- of the mealy machine mealyB :: (s -> i -> (s, o)) -> s -> Unbundled dom i -> Unbundled dom o mealyB = (KnownDomain dom => Clock dom -> Reset dom -> Enable dom -> (s -> i -> (s, o)) -> s -> Unbundled dom i -> Unbundled dom o) -> (s -> i -> (s, o)) -> s -> Unbundled dom i -> Unbundled dom o forall (dom :: Domain) r. HiddenClockResetEnable dom => (KnownDomain dom => Clock dom -> Reset dom -> Enable dom -> r) -> r hideClockResetEnable KnownDomain dom => Clock dom -> Reset dom -> Enable dom -> (s -> i -> (s, o)) -> s -> Unbundled dom i -> Unbundled dom o forall (dom :: Domain) s i o. (KnownDomain dom, NFDataX s, Bundle i, Bundle o) => Clock dom -> Reset dom -> Enable dom -> (s -> i -> (s, o)) -> s -> Unbundled dom i -> Unbundled dom o E.mealyB {-# INLINE mealyB #-} -- | Create a synchronous function from a combinational function describing -- a mealy machine using the state monad. This can be particularly useful -- when combined with lenses or optics to replicate imperative algorithms. -- -- @ -- data DelayState = DelayState -- { _history :: Vec 4 Int -- , _untilValid :: Index 4 -- } -- deriving (Generic, NFDataX) -- makeLenses ''DelayState -- -- initialDelayState = DelayState (repeat 0) maxBound -- -- delayS :: Int -> State DelayState (Maybe Int) -- delayS n = do -- history %= (n +>>) -- remaining <- use untilValid -- if remaining > 0 -- then do -- untilValid -= 1 -- return Nothing -- else do -- out <- uses history last -- return (Just out) -- -- delayTop :: HiddenClockResetEnable dom => 'Signal' dom Int -> 'Signal' dom (Maybe Int) -- delayTop = 'mealyS' delayS initialDelayState -- @ -- -- >>> L.take 7 $ simulate @System delayTop [1,2,3,4,5,6,7,8] -- [Nothing,Nothing,Nothing,Just 1,Just 2,Just 3,Just 4] -- ... -- mealyS :: ( HiddenClockResetEnable dom , NFDataX s ) => (i -> State s o) -- ^ Transfer function in mealy machine handling inputs using @Control.Monad.Strict.State s@. -> s -- ^ Initial state -> (Signal dom i -> Signal dom o) -- ^ Synchronous sequential function with input and output matching that -- of the mealy machine mealyS :: (i -> State s o) -> s -> Signal dom i -> Signal dom o mealyS = (KnownDomain dom => Clock dom -> Reset dom -> Enable dom -> (i -> State s o) -> s -> Signal dom i -> Signal dom o) -> (i -> State s o) -> s -> Signal dom i -> Signal dom o forall (dom :: Domain) r. HiddenClockResetEnable dom => (KnownDomain dom => Clock dom -> Reset dom -> Enable dom -> r) -> r hideClockResetEnable KnownDomain dom => Clock dom -> Reset dom -> Enable dom -> (i -> State s o) -> s -> Signal dom i -> Signal dom o forall (dom :: Domain) s i o. (KnownDomain dom, NFDataX s) => Clock dom -> Reset dom -> Enable dom -> (i -> State s o) -> s -> Signal dom i -> Signal dom o E.mealyS {-# INLINE mealyS #-} -- | A version of 'mealyS' that does automatic 'Bundle'ing, see 'mealyB' for details. mealySB :: ( HiddenClockResetEnable dom , NFDataX s , Bundle i , Bundle o ) => (i -> State s o) -- ^ Transfer function in mealy machine handling inputs using @Control.Monad.Strict.State s@. -> s -- ^ Initial state -> (Unbundled dom i -> Unbundled dom o) -- ^ Synchronous sequential function with input and output matching that -- of the mealy machine mealySB :: (i -> State s o) -> s -> Unbundled dom i -> Unbundled dom o mealySB = (KnownDomain dom => Clock dom -> Reset dom -> Enable dom -> (i -> State s o) -> s -> Unbundled dom i -> Unbundled dom o) -> (i -> State s o) -> s -> Unbundled dom i -> Unbundled dom o forall (dom :: Domain) r. HiddenClockResetEnable dom => (KnownDomain dom => Clock dom -> Reset dom -> Enable dom -> r) -> r hideClockResetEnable KnownDomain dom => Clock dom -> Reset dom -> Enable dom -> (i -> State s o) -> s -> Unbundled dom i -> Unbundled dom o forall (dom :: Domain) s i o. (KnownDomain dom, NFDataX s, Bundle i, Bundle o) => Clock dom -> Reset dom -> Enable dom -> (i -> State s o) -> s -> Unbundled dom i -> Unbundled dom o E.mealySB {-# INLINE mealySB #-} -- | Infix version of 'mealyB' (<^>) :: ( HiddenClockResetEnable dom , NFDataX s , Bundle i , Bundle o ) => (s -> i -> (s,o)) -- ^ Transfer function in mealy machine form: @state -> input -> (newstate,output)@ -> s -- ^ Initial state -> (Unbundled dom i -> Unbundled dom o) -- ^ Synchronous sequential function with input and output matching that -- of the mealy machine <^> :: (s -> i -> (s, o)) -> s -> Unbundled dom i -> Unbundled dom o (<^>) = (s -> i -> (s, o)) -> s -> Unbundled dom i -> Unbundled dom o forall (dom :: Domain) s i o. (HiddenClockResetEnable dom, NFDataX s, Bundle i, Bundle o) => (s -> i -> (s, o)) -> s -> Unbundled dom i -> Unbundled dom o mealyB {-# INLINE (<^>) #-}