{-# LANGUAGE DataKinds #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE Safe #-} {-# OPTIONS_HADDOCK show-extensions #-} {-| Copyright : (C) 2013-2015, University of Twente License : BSD2 (see the file LICENSE) Maintainer : Christiaan Baaij Self-synchronising circuits based on data-flow principles. -} module CLaSH.Prelude.DataFlow ( -- * Data types DataFlow , DataFlow' , df -- * Creating DataFlow circuits , liftDF , mealyDF , mooreDF -- * Composition combinators , idDF , seqDF , firstDF , swapDF , secondDF , parDF , loopDF -- * Lock-Step operation , lockStep , stepLock ) where import GHC.TypeLits (KnownNat, KnownSymbol) import CLaSH.Signal ((.&&.), regEn, unbundle) import CLaSH.Signal.Bundle (Bundle (..)) import CLaSH.Signal.Explicit (Clock (..), Signal', SystemClock, sclock) {- | Dataflow circuit with bidirectional synchronisation channels. In the /forward/ direction we assert /validity/ of the data. In the /backward/ direction we assert that the circuit is /ready/ to receive new data. A circuit adhering to the 'DataFlow' type should: * Not consume data when validity is deasserted. * Only update its output when readiness is asserted. The 'DataFlow'' type is defined as: @ newtype DataFlow' clk iEn oEn i o = DF { df :: 'Signal'' clk i -- Incoming data -> 'Signal'' clk iEn -- Flagged with /valid/ bits @iEn@. -> 'Signal'' clk oEn -- Incoming back-pressure, /ready/ edge. -> ( 'Signal'' clk o -- Outgoing data. , 'Signal'' clk oEn -- Flagged with /valid/ bits @oEn@. , 'Signal'' clk iEn -- Outgoing back-pressure, /ready/ edge. ) } @ where: * @clk@ is the clock to which the circuit is synchronised. * @iEn@ is the type of the bidirectional incoming synchronisation channel. * @oEn@ is the type of the bidirectional outgoing synchronisation channel. * @i@ is the incoming data type. * @o@ is the outgoing data type. We define several composition operators for our 'DataFlow' circuits: * 'seqDF' sequential composition. * 'parDF' parallel composition. * 'loopDF' add a feedback arc. * 'lockStep' proceed in lock-step. When you look at the types of the above operators it becomes clear why we parametrise in the types of the synchronisation channels. -} newtype DataFlow' clk iEn oEn i o = DF { -- | Create an ordinary circuit from a 'DataFlow' circuit df :: Signal' clk i -- Incoming data -> Signal' clk iEn -- Flagged with /valid/ bits @iEn@. -> Signal' clk oEn -- Incoming back-pressure, /ready/ edge. -> ( Signal' clk o -- Outgoing data. , Signal' clk oEn -- Flagged with /valid/ bits @oEn@. , Signal' clk iEn -- Outgoing back-pressure, /ready/ edge. ) } -- | Dataflow circuit synchronised to the 'SystemClock'. type DataFlow iEn oEn i o = DataFlow' SystemClock iEn oEn i o -- | Create a 'DataFlow' circuit from a circuit description with the appropriate -- type: -- -- @ -- 'Signal'' clk i -- Incoming data. -- -> 'Signal'' clk Bool -- Flagged with a single /valid/ bit. -- -> 'Signal'' clk Bool -- Incoming back-pressure, /ready/ bit. -- -> ( 'Signal'' clk o -- Outgoing data. -- , 'Signal'' clk oEn -- Flagged with a single /valid/ bit. -- , 'Signal'' clk iEn -- Outgoing back-pressure, /ready/ bit. -- ) -- @ -- -- A circuit adhering to the 'DataFlow' type should: -- -- * Not consume data when validity is deasserted. -- * Only update its output when readiness is asserted. liftDF :: (Signal' clk i -> Signal' clk Bool -> Signal' clk Bool -> (Signal' clk o, Signal' clk Bool, Signal' clk Bool)) -> DataFlow' clk Bool Bool i o liftDF = DF -- | Create a 'DataFlow' circuit from a Mealy machine description as those of -- "CLaSH.Prelude.Mealy" mealyDF :: (s -> i -> (s,o)) -> s -> DataFlow Bool Bool i o mealyDF f iS = DF (\i iV oR -> let en = iV .&&. oR (s',o) = unbundle (f <$> s <*> i) s = regEn iS en s' in (o,iV,oR)) -- | Create a 'DataFlow' circuit from a Moore machine description as those of -- "CLaSH.Prelude.Moore" mooreDF :: (s -> i -> s) -> (s -> o) -> s -> DataFlow Bool Bool i o mooreDF ft fo iS = DF (\i iV oR -> let en = iV .&&. oR s' = ft <$> s <*> i s = regEn iS en s' o = fo <$> s in (o,iV,oR)) -- | Identity circuit -- -- <> idDF :: DataFlow' clk en en a a idDF = DF (\a val rdy -> (a,val,rdy)) -- | Sequential composition of two 'DataFlow' circuits. -- -- <> seqDF :: DataFlow' clk aEn bEn a b -> DataFlow' clk bEn cEn b c -> DataFlow' clk aEn cEn a c (DF f) `seqDF` (DF g) = DF (\a aVal cRdy -> let (b,bVal,aRdy) = f a aVal bRdy (c,cVal,bRdy) = g b bVal cRdy in (c,cVal,aRdy)) -- | Apply the circuit to the first halve of the communication channels, leave -- the second halve unchanged. -- -- <> firstDF :: (KnownSymbol nm, KnownNat rate) => DataFlow' ('Clk nm rate) aEn bEn a b -> DataFlow' ('Clk nm rate) (aEn,cEn) (bEn,cEn) (a,c) (b,c) firstDF (DF f) = DF (\ac acV bcR -> let clk = sclock (a,c) = unbundle' clk ac (aV,cV) = unbundle' clk acV (bR,cR) = unbundle' clk bcR (b,bV,aR) = f a aV bR bc = bundle' clk (b,c) bcV = bundle' clk (bV,cV) acR = bundle' clk (aR,cR) in (bc,bcV,acR) ) -- | Swap the two communication channels. -- -- <> swapDF :: DataFlow' ('Clk nm rate) (aEn,bEn) (bEn,aEn) (a,b) (b,a) swapDF = DF (\ab abV baR -> (swap <$> ab, swap <$> abV, swap <$> baR)) where swap ~(a,b) = (b,a) -- | Apply the circuit to the second halve of the communication channels, leave -- the first halve unchanged. -- -- <> secondDF :: (KnownSymbol nm, KnownNat rate) => DataFlow' ('Clk nm rate) aEn bEn a b -> DataFlow' ('Clk nm rate) (cEn,aEn) (cEn,bEn) (c,a) (c,b) secondDF f = swapDF `seqDF` firstDF f `seqDF` swapDF -- | Compose two 'DataFlow' circuits in parallel. -- -- <> parDF :: (KnownSymbol nm, KnownNat rate) => DataFlow' ('Clk nm rate) aEn bEn a b -> DataFlow' ('Clk nm rate) cEn dEn c d -> DataFlow' ('Clk nm rate) (aEn,cEn) (bEn,dEn) (a,c) (b,d) f `parDF` g = firstDF f `seqDF` secondDF g -- | Feed back the second halve of the communication channel. -- -- Given: -- -- @ -- f \`@'seqDF'@\` ('loopDF' h) \`@'seqDF'@\` g -- @ -- -- The circuits @f@, @h@, and @g@, will operate in /lock-step/. Which means that -- there it only progress when all three circuits are producing /valid/ data -- and all three circuits are /ready/ to receive new data. The 'loopDF' function -- uses the 'lockStep' and 'stepLock' functions to achieve the /lock-step/ -- operation. -- -- <> loopDF :: forall nm rate a b d . (KnownSymbol nm, KnownNat rate) => DataFlow' ('Clk nm rate) Bool Bool (a,d) (b,d) -> DataFlow' ('Clk nm rate) Bool Bool a b loopDF f = loopDF' h where h :: DataFlow' ('Clk nm rate) (Bool,Bool) (Bool,Bool) (a,d) (b,d) h = lockStep `seqDF` f `seqDF` stepLock loopDF' :: DataFlow' ('Clk nm rate) (Bool,Bool) (Bool,Bool) (a,d) (b,d) -> DataFlow' ('Clk nm rate) Bool Bool a b loopDF' (DF f') = DF (\a aV bR -> let clk = sclock (bd,bdV,adR) = f' ad adV bdR (b,d) = unbundle' clk bd (bV,dV) = unbundle' clk bdV (aR,dR) = unbundle' clk adR ad = bundle' clk (a,d) adV = bundle' clk (aV,dV) bdR = bundle' clk (bR,dR) in (b,bV,aR) ) -- | Have parallel compositions operate in lock-step. class LockStep a b where -- | Reduce the synchronisation granularity to a single 'Bool'ean value. -- -- Given: -- -- @ -- __f__ :: 'DataFlow' Bool Bool a b -- __g__ :: 'DataFlow' Bool Bool c d -- __h__ :: 'DataFlow' Bool Bool (b,d) (p,q) -- @ -- -- We /cannot/ simply write: -- -- @ -- (f \`@'parDF'@\` g) \`@'seqDF'@\` h -- @ -- -- because, @f \`parDF\` g@, has type, @'DataFlow' (Bool,Bool) (Bool,Bool) (a,c) (b,d)@, -- which does not match the expected synchronisation granularity of @h@. We -- need a circuit in between that has the type: -- -- @ -- 'DataFlow' (Bool,Bool) Bool (b,d) (b,d) -- @ -- -- Simply '&&'-ing the /valid/ signals in the forward direction, and -- duplicating the /ready/ signal in the backward direction is however not -- enough. We also need to make sure that @f@ does not update its output when -- @g@'s output is invalid and visa versa, as @h@ can only consume its input -- when both @f@ and @g@ are producing valid data. @g@'s /ready/ port is hence -- only asserted when @h@ is ready and @f@ is producing /valid/ data. And @f@'s -- ready port is only asserted when @h@ is ready and @g@ is producing valid -- data. @f@ and @g@ will hence be proceeding in /lock-step/. -- -- The 'lockStep' function ensures that all synchronisation signals are -- properly connected: -- -- @ -- (f \`@'parDF'@\` g) \`@'seqDF'@\` 'lockStep' \`@'seqDF'@\` h -- @ -- -- <> -- -- Note that 'lockStep' works for arbitrarily nested tuples. That is: -- -- @ -- p :: 'DataFlow' Bool Bool ((b,d),d) z -- -- q :: 'DataFlow' ((Bool,Bool),Bool) ((Bool,Bool),Bool) ((a,c),c) ((b,d),d) -- q = f \`@'parDF'@\` g \`@'parDF'@\` g -- -- r = q \`@'seqDF'@\` 'lockStep' \`@'seqDF'@\` p -- @ -- -- Does the right thing. lockStep :: (KnownNat rate,KnownSymbol nm) => DataFlow' ('Clk nm rate) a Bool b b -- | Extend the synchronisation granularity from a single 'Bool'ean value. -- -- Given: -- -- @ -- __f__ :: 'DataFlow' Bool Bool a b -- __g__ :: 'DataFlow' Bool Bool c d -- __h__ :: 'DataFlow' Bool Bool (p,q) (a,c) -- @ -- -- We /cannot/ simply write: -- -- @ -- h \`@'seqDF'@\` (f \`@'parDF'@\` g) -- @ -- -- because, @f \`parDF\` g@, has type, @'DataFlow' (Bool,Bool) (Bool,Bool) (a,c) (b,d)@, -- which does not match the expected synchronisation granularity of @h@. We -- need a circuit in between that has the type: -- -- @ -- 'DataFlow' Bool (Bool,Bool) (a,c) (a,c) -- @ -- -- Simply '&&'-ing the /ready/ signals in the backward direction, and -- duplicating the /valid/ signal in the forward direction is however not -- enough. We need to make sure that @f@ does not consume values when @g@ is -- not /ready/ and visa versa, because @h@ cannot update the values of its -- output tuple independently. @f@'s /valid/ port is hence only asserted when -- @h@ is valid and @g@ is ready to receive new values. @g@'s /valid/ port is -- only asserted when @h@ is valid and @f@ is ready to receive new values. -- @f@ and @g@ will hence be proceeding in /lock-step/. -- -- The 'stepLock' function ensures that all synchronisation signals are -- properly connected: -- -- @ -- h \`@'seqDF'@\` 'stepLock' \`@'seqDF'@\` (f \`@'parDF'@\` g) -- @ -- -- <> -- -- Note that 'stepLock' works for arbitrarily nested tuples. That is: -- -- @ -- p :: 'DataFlow' Bool Bool z ((a,c),c) -- -- q :: 'DataFlow' ((Bool,Bool),Bool) ((Bool,Bool),Bool) ((a,c),c) ((b,d),d) -- q = f \`@'parDF'@\` g \`@'parDF'@\` g -- -- r = p \`@'seqDF'@\` 'lockStep' \`@'seqDF'@\` q -- @ -- -- Does the right thing. stepLock :: (KnownNat rate,KnownSymbol nm) => DataFlow' ('Clk nm rate) Bool a b b instance LockStep Bool c where lockStep = idDF stepLock = idDF instance (LockStep a x, LockStep b y) => LockStep (a,b) (x,y) where lockStep = (lockStep `parDF` lockStep) `seqDF` (DF (\xy xyV rdy -> let clk = sclock (xV,yV) = unbundle' clk xyV val = xV .&&. yV xR = yV .&&. rdy yR = xV .&&. rdy xyR = bundle' clk (xR,yR) in (xy,val,xyR))) stepLock = (DF (\xy val xyR -> let clk = sclock (xR,yR) = unbundle' clk xyR rdy = xR .&&. yR xV = val .&&. yR yV = val .&&. xR xyV = bundle' clk (xV,yV) in (xy,xyV,rdy))) `seqDF` (stepLock `parDF` stepLock)