clash-prelude-1.10.0: src/Clash/Explicit/Mealy.hs
{-|
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 NoGeneralizedNewtypeDeriving #-}
{-# LANGUAGE Safe #-}
module Clash.Explicit.Mealy
( -- * Mealy machines with explicit clock and reset ports
mealy
, mealyS
, mealyB
, mealySB
)
where
import Clash.Explicit.Signal
(KnownDomain, Bundle (..), Clock, Reset, Signal, Enable, register)
import Clash.XException (NFDataX)
import Control.Monad.State.Strict
(State, runState)
{- $setup
>>> :set -XDataKinds -XTypeApplications -XDeriveGeneric -XDeriveAnyClass
>>> import Clash.Explicit.Prelude as C
>>> import Clash.Explicit.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 clk rst en = mealy clk rst en 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 :: Clock System -> Reset System -> Enable System -> Signal System Int -> Signal System (Maybe Int)
delayTop clk rst en = mealyS clk rst en delayS initialDelayState
:}
-}
-- | Create a synchronous function from a combinational function describing
-- a mealy machine
--
-- @
-- import qualified Data.List as L
--
-- macT
-- :: Int -- Current state
-- -> (Int,Int) -- Input
-- -> (Int,Int) -- (Updated state, output)
-- macT s (x,y) = (s',s)
-- where
-- s' = x * y + s
--
-- mac
-- :: 'KnownDomain' dom
-- => 'Clock' dom
-- -> 'Reset' dom
-- -> 'Enable' dom
-- -> 'Signal' dom (Int, Int)
-- -> 'Signal' dom Int
-- mac clk rst en = 'mealy' clk rst en macT 0
-- @
--
-- >>> simulate (mac systemClockGen systemResetGen enableGen) [(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
-- :: 'KnownDomain' dom
-- => 'Clock' dom
-- -> 'Reset' dom
-- -> 'Enable' dom
-- -> ('Signal' dom Int, 'Signal' dom Int)
-- -> ('Signal' dom Int, 'Signal' dom Int)
-- -> 'Signal' dom Int
-- dualMac clk rst en (a,b) (x,y) = s1 + s2
-- where
-- s1 = 'mealy' clk rst en macT 0 ('bundle' (a,x))
-- s2 = 'mealy' clk rst en macT 0 ('bundle' (b,y))
-- @
mealy
:: ( KnownDomain dom
, NFDataX s )
=> Clock dom
-- ^ 'Clock' to synchronize to
-> Reset dom
-> Enable dom
-- ^ Global enable
-> (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 clk rst en f iS =
\i -> let (s',o) = unbundle $ f <$> s <*> i
s = register clk rst en iS s'
in o
{-# INLINABLE mealy #-}
-- | 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 ::'KnownDomain' dom
-- => 'Clock' dom
-- -> 'Reset' dom
-- -> 'Enable' dom
-- -> ('Signal' dom Int -> 'Signal' dom (Maybe Int))
-- delayTop clk rst en = 'mealyS' clk rst en delayS initialDelayState
-- @
--
-- >>> L.take 7 $ simulate (delayTop systemClockGen systemResetGen enableGen) [-100,1,2,3,4,5,6,7,8]
-- [Nothing,Nothing,Nothing,Nothing,Just 1,Just 2,Just 3]
--
mealyS
:: ( KnownDomain dom
, NFDataX s )
=> Clock dom
-- ^ 'Clock' to synchronize to
-> Reset dom
-> Enable dom
-- ^ Global enable
-> (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 clk rst en f iS =
\i -> let (o,s') = unbundle $ (runState . f) <$> i <*> s
s = register clk rst en iS s'
in o
{-# INLINABLE mealyS #-}
-- | 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 clk rst en a b c = (b1,b2,i2)
-- where
-- (i1,b1) = 'unbundle' (mealy clk rst en f 0 ('bundle' (a,b)))
-- (i2,b2) = 'unbundle' (mealy clk rst en f 3 ('bundle' (c,i1)))
-- @
--
-- Using 'mealyB' however we can write:
--
-- @
-- g clk rst en a b c = (b1,b2,i2)
-- where
-- (i1,b1) = 'mealyB' clk rst en f 0 (a,b)
-- (i2,b2) = 'mealyB' clk rst en f 3 (c,i1)
-- @
mealyB
:: ( KnownDomain dom
, NFDataX s
, Bundle i
, Bundle o )
=> Clock dom
-> Reset dom
-> Enable dom
-> (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 clk rst en f iS i = unbundle (mealy clk rst en f iS (bundle i))
{-# INLINE mealyB #-}
-- | A version of 'mealyS' that does automatic 'Bundle'ing, see 'mealyB' for details.
mealySB
:: ( KnownDomain dom
, NFDataX s
, Bundle i
, Bundle o )
=> Clock dom
-> Reset dom
-> Enable dom
-> (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 clk rst en f iS i = unbundle (mealyS clk rst en f iS (bundle i))
{-# INLINE mealySB #-}