-- | Common Atom functions. module Language.Atom.Common ( -- * Timers Timer , timer , startTimer , startTimerIf , timerDone -- * One Shots , oneShotRise , oneShotFall -- * Debouncing , debounce -- * Lookup Tables , lookupTable , linear -- * Hysteresis , hysteresis ) where import Data.Word import Language.Atom.Language -- | A Timer. data Timer = Timer (V Word64) -- | Creates a new timer. timer :: Name -> Atom Timer timer name = do timer <- word64 name 0 return $ Timer timer -- | Starts a Timer. A timer can be restarted at any time. startTimer :: Timer -> E Word64 -> Atom () startTimer t = startTimerIf t true -- | Conditionally start a Timer. startTimerIf :: Timer -> E Bool -> E Word64 -> Atom () startTimerIf (Timer t) a time = t <== mux a (clock + time) (value t) -- | 'True' when a timer has completed. timerDone :: Timer -> E Bool timerDone (Timer t) = value t <=. clock -- | One-shot on a rising transition. oneShotRise :: E Bool -> Atom (E Bool) oneShotRise a = do last <- bool "last" False last <== a return $ a &&. not_ (value last) -- | One-shot on a falling transition. oneShotFall :: E Bool -> Atom (E Bool) oneShotFall = oneShotRise . not_ -- | Debounces a boolean given an on and off time (ticks) and an initial state. debounce :: Name -> E Word64 -> E Word64 -> Bool -> E Bool -> Atom (E Bool) debounce name onTime offTime init a = atom name $ do last <- bool "last" init out <- bool "out" init timer <- timer "timer" atom "on" $ do cond $ a &&. not_ (value last) startTimer timer onTime last <== a atom "off" $ do cond $ not_ a &&. value last startTimer timer offTime last <== a atom "set" $ do cond $ a ==. value last cond $ timerDone timer out <== value last return $ value out -- | 1-D lookup table. X values out of table range are clipped at end Y values. -- Input table must be monotonically increasing in X. lookupTable :: FloatingE a => [(E a, E a)] -> E a -> E a lookupTable table x = mux (x >=. x1) y1 $ foldl f y0 table' where (_, y0) = head table (x1, y1) = last table table' = zip (init table) (tail table) f a ((x0,y0),(x1,y1)) = mux (x >=. x0) interp a where slope = (y1 - y0) / (x1 - x0) interp = (x - x0) * slope + y0 -- | Linear extrapolation and interpolation on a line with 2 points. -- The two x points must be different to prevent a divide-by-zero. linear :: FloatingE a => (E a, E a) -> (E a, E a) -> E a -> E a linear (x1, y1) (x2, y2) a = slope * a + inter where slope = y2 - y1 / x2 - x1 inter = y1 - slope * x1 -- | Hysteresis returns 'True' when the input exceeds @max@ and 'False' when -- the input is less than @min@. The state is held when the input is between -- @min@ and @max@. -- -- > hysteresis name min max input hysteresis :: OrdE a => E a -> E a -> E a -> Atom (E Bool) hysteresis a b u = do s <- bool "s" False s <== (mux (u >. max) true $ mux (u <. min) false $ value s) return $ value s where min = min_ a b max = max_ a b {- -- | A channel is a uni-directional communication link that ensures one read for every write. data Channel a = Channel a (V Bool) -- | Creates a new channel, with a given name and data. channel :: a -> Atom (Channel a) channel a = do hasData <- bool False return $ Channel a hasData -- | Write data to a 'Channel'. A write will only suceed if the 'Channel' is empty. writeChannel :: Channel a -> Action () writeChannel (Channel _ hasData) = do when $ not_ $ value hasData hasData <== true -- | Read data from a 'Channel'. A read will only suceed if the 'Channel' has data to be read. readChannel :: Channel a -> Action a readChannel (Channel a hasData) = do when $ value hasData hasData <== false return a -- | Fades one signal to another. module Language.Atom.Common.Fader ( Fader , FaderInit (..) , fader , fadeToA , fadeToB , fadeToCenter ) where import Language.Atom -- | Fader object. data Fader = Fader (V Int) -- | Fader initalization. data FaderInit = OnA | OnB | OnCenter toA = 0 toB = 1 toCenter = 2 -- | Fader construction. Name, fade rate, fader init, and signal A and B. fader :: Name -> Double -> FaderInit -> E Double -> E Double -> Atom (Fader, E Double) fader name rate init a b = scope name $ do --assert "positiveRate" $ rate >= 0 target <- int (case init of {OnA -> toA; OnB -> toB; OnCenter -> toCenter}) perA <- double (case init of {OnA -> 1; OnB -> 0; OnCenter -> 0.5}) rule "toA" $ do when $ value target ==. intC toA when $ value perA <. 1 perA <== mux (1 - value perA <. doubleC rate) 1 (value perA + doubleC rate) rule "toB" $ do when $ value target ==. intC toB when $ value perA >. 0 perA <== mux (value perA <. doubleC rate) 0 (value perA - doubleC rate) rule "toCenterFrom0" $ do when $ value target ==. intC toCenter when $ value perA <. 0.5 perA <== mux (0.5 - value perA <. doubleC rate) 0.5 (value perA + doubleC rate) rule "toCenterFrom1" $ do when $ value target ==. intC toCenter when $ value perA >. 0.5 perA <== mux (value perA - 0.5 <. doubleC rate) 0.5 (value perA - doubleC rate) return (Fader target, (a * value perA + b * (1 - value perA)) / 2) -- | Fade to signal A. fadeToA :: Fader -> Action () fadeToA (Fader target) = target <== intC toA -- | Fade to signal B. fadeToB :: Fader -> Action () fadeToB (Fader target) = target <== intC toB -- | Fade to center, ie average of signal A and B. fadeToCenter :: Fader -> Action () fadeToCenter (Fader target) = target <== intC toCenter module Language.Atom.Common.Process ( Process (..) , process ) where import Language.Atom data Process = Par [Process] | Seq [Process] | Alt [Process] | Rep Process | Act Action process :: Name -> Process -> Atom () -- | Time integrated threshold functions typically used in condition monitoring. module Language.Atom.Common.Threshold ( boolThreshold , floatingThreshold ) where import Language.Atom -- | Boolean thresholding over time. Output is set when internal counter hits limit, and cleared when counter is 0. boolThreshold :: Name -> Int -> Bool -> E Bool -> Atom (E Bool) boolThreshold name num init input = scope name $ do --assert "positiveNumber" $ num >= 0 state <- bool init count <- int (if init then num else 0) rule "update" $ do when $ value count >. 0 &&. value count <. num count <== value count + mux input 1 (-1) rule "low" $ do when $ value count ==. 0 state <== false rule "high" $ do when $ value count ==. intC num state <== true return $ value state -- | Integrating threshold. Output is set with integral reaches limit, and cleared when integral reaches 0. doubleThreshold :: Name -> Double -> E Double -> Atom (E Bool) doubleThreshold name lim input = scope name $ do --assert "positiveLimit" $ lim >= 0 state <- bool False sum <- double 0 (high,low) <- priority rule "update" sum <== value sum + input low rule "clear" $ do when $ value sum <=. 0 state <== false sum <== 0 high rule "set" $ do when $ value sum >=. doubleC lim state <== true sum <== doubleC lim high return $ value state -- | Capturing data that can either be valid or invalid. module Language.Atom.Common.ValidData ( ValidData , validData , getValidData , whenValid , whenInvalid ) where import Language.Atom -- | 'ValidData' captures the data and its validity condition. -- 'ValidData' is abstract to prevent rules from using invalid data. data ValidData a = ValidData a (E Bool) -- | Create 'ValidData' given the data and validity condition. validData :: a -> E Bool -> ValidData a validData = ValidData -- | Get a valid data. Action is disabled if data is invalid. getValidData :: ValidData a -> Action a getValidData (ValidData a v) = cond v >> return a -- | Action enabled if 'ValidData' is valid. whenValid :: ValidData a -> Action () whenValid (ValidData _ v) = cond v -- | Action enabled if 'ValidData' is not valid. whenInvalid :: ValidData a -> Action () whenInvalid (ValidData _ v) = cond $ not_ v -}