From e27da3e6286a0fcc6fb9f82374c722874717c926 Mon Sep 17 00:00:00 2001 From: Ben Blaxill Date: Wed, 21 Aug 2019 01:39:56 +0100 Subject: [PATCH 01/19] Initial TopEntityGen --- clash-prelude/clash-prelude.cabal | 6 +- .../src/Clash/Annotations/TopEntityGen.hs | 138 ++++++++++++++++++ .../tests/Clash/Tests/TopEntityGen.hs | 71 +++++++++ clash-prelude/tests/unittests.hs | 2 + 4 files changed, 216 insertions(+), 1 deletion(-) create mode 100644 clash-prelude/src/Clash/Annotations/TopEntityGen.hs create mode 100644 clash-prelude/tests/Clash/Tests/TopEntityGen.hs diff --git a/clash-prelude/clash-prelude.cabal b/clash-prelude/clash-prelude.cabal index 05c60917c1..c418bc9f7e 100644 --- a/clash-prelude/clash-prelude.cabal +++ b/clash-prelude/clash-prelude.cabal @@ -120,6 +120,7 @@ Library Exposed-modules: Clash.Prelude.Synchronizer Exposed-modules: Clash.Annotations.TopEntity + Clash.Annotations.TopEntityGen Clash.Annotations.Primitive Clash.Annotations.BitRepresentation Clash.Annotations.BitRepresentation.Deriving @@ -269,6 +270,7 @@ Library hashable >= 1.2.1.0 && < 1.4, half >= 0.2.2.3 && < 1.0, lens >= 4.9 && < 4.19, + recursion-schemes >= 5.1 && < 5.2, QuickCheck >= 2.7 && < 2.14, reflection >= 2 && < 2.2, singletons >= 1.0 && < 3.0, @@ -317,7 +319,8 @@ test-suite unittests base, hint >= 0.7 && < 0.10, tasty >= 1.2 && < 1.3, - tasty-hunit + tasty-hunit , + template-haskell >= 2.9.0.0 && < 2.14 Other-Modules: Clash.Tests.BitPack Clash.Tests.BitVector @@ -325,6 +328,7 @@ test-suite unittests Clash.Tests.DerivingDataReprTypes Clash.Tests.Signal Clash.Tests.NFDataX + Clash.Tests.TopEntityGen benchmark benchmark-clash-prelude diff --git a/clash-prelude/src/Clash/Annotations/TopEntityGen.hs b/clash-prelude/src/Clash/Annotations/TopEntityGen.hs new file mode 100644 index 0000000000..ec95fcd9e3 --- /dev/null +++ b/clash-prelude/src/Clash/Annotations/TopEntityGen.hs @@ -0,0 +1,138 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE DeriveFoldable #-} +{-# LANGUAGE DeriveFunctor #-} +{-# LANGUAGE DeriveLift #-} +{-# LANGUAGE DeriveTraversable #-} +{-# LANGUAGE GADTs #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE PatternSynonyms #-} +{-# LANGUAGE PolyKinds #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE StandaloneDeriving #-} +{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE TypeOperators #-} + +module Clash.Annotations.TopEntityGen + ( makeTopEntity + , makeTopEntityWithName + ) +where + +import Clash.Annotations.TopEntity (PortName(..), TopEntity(..)) +import Clash.NamedTypes + +import Language.Haskell.TH +import Language.Haskell.TH.Syntax + +import Data.Functor.Foldable (para) +import Data.Functor.Foldable.TH +import Data.Foldable (fold) + +$(makeBaseFunctor ''Type) + +-- A helper function for recursively walking a 'Type' tree and +-- building a list of 'PortName's +buildPorts :: Type -> TypeF (Type, Q [PortName]) -> Q [PortName] +-- Is there a ' ::: ' annotation? +buildPorts split (AppTF (AppT split' (LitT (StrTyLit name)), _) (_,c)) + | split' == split + -- We found our splitter. If: + -- - We only have no names from children: use split name as PortName + -- - We have children reporting names: use split name as name to PortProduct + -- - !! Inconsistent children names currently unhandled + = c >>= \case + [] -> return [PortName name] + xs -> return [PortProduct name xs] +-- Recur on types by reifying the name. +-- This allows us to find splitters defined in data types. +buildPorts split (ConTF name) = do + info <- reify name + case info of + TyConI (DataD _ _ _ _ [RecC _ xs] _) -> + buildFromConstructor $ map (\(_,_,c)->c) xs + TyConI (DataD _ _ _ _ [NormalC _ xs] _) -> + buildFromConstructor $ map snd xs + _ -> return [] + where buildFromConstructor = + fmap mconcat . sequence . fmap (para (buildPorts split)) +-- Just collect names +buildPorts _ f = do + f' <- mapM snd f + return $ fold f' + +-- matches a type `a -> b` +pattern ArrowTy :: Type -> Type -> Type +pattern ArrowTy a b = AppT (AppT ArrowT a) b + +-- | Given an multi-arity function type @f :: a -> b -> c -> ...@, get +-- the final return type. +getReturnTy :: Type -> Q Type +getReturnTy (ArrowTy _ b) = getReturnTy b +getReturnTy b = return b + +makeTopEntityWithName' :: Name -> Maybe String -> DecsQ +makeTopEntityWithName' n topName = reify n >>= \case + VarI nam typ _ -> do + -- helpers + let prag t = PragmaD (AnnP (valueAnnotation nam) t) + + -- get a Name for this type operator so we can check it + -- in the ArrowTy case + nty <- [t| (:::) |] + + -- examine the arguments + let examine ty = go 0 ty [] where + go i (ArrowTy x y) xs + = do + v <- go (i+1) y xs + name <- para (buildPorts nty) x + -- Fail if we didn't get a single PortName + case name of + (a:[]) -> return (a:v) + f -> fail $ "Failed to get a single name for argument " ++ show i + ++ "\n a.k.a. " ++ show x + ++ "\n got name " ++ show f + + go _ _ xs = return xs + + ins <- examine typ + out <- getReturnTy typ + >>= para (buildPorts nty) + >>= \case + [] -> fail "No return name specified!" + [x] -> return x + xs -> return $ PortProduct "" xs + + -- Return the annotation + let realName = case topName of + Just nn -> nn -- user specified name + Nothing -> nameBase nam -- auto-generated name + top <- lift $ Synthesize + { t_name = realName + , t_inputs = ins + , t_output = out + } + return [prag top] + + -- failure case: we weren't provided with the name of a binder + _ -> fail "makeTopEntity: invalid Name, must be a top-level binding!" + +-- | Automatically create a @'TopEntity'@ for a given @'Name'@, using the given +-- @'String'@ to specify the name of the generated RTL entity. +-- +-- The function arguments and return values of the function specified by the +-- given @'Name'@ must be annotated with @'(:::)'@. This annotation provides the +-- given name of the port. +makeTopEntityWithName :: Name -> String -> DecsQ +makeTopEntityWithName nam top = makeTopEntityWithName' nam (Just top) + +-- | Automatically create a @'TopEntity'@ for a given @'Name'@. The name of the +-- generated RTL entity will be the name of the function that has been +-- specified; e.g. @'makeTopEntity' 'foobar@ will generate a @foobar@ module. +-- +-- The function arguments and return values of the function specified by the +-- given @'Name'@ must be annotated with @'(:::)'@. This annotation provides the +-- given name of the port. +makeTopEntity :: Name -> DecsQ +makeTopEntity nam = makeTopEntityWithName' nam Nothing diff --git a/clash-prelude/tests/Clash/Tests/TopEntityGen.hs b/clash-prelude/tests/Clash/Tests/TopEntityGen.hs new file mode 100644 index 0000000000..aea0453eef --- /dev/null +++ b/clash-prelude/tests/Clash/Tests/TopEntityGen.hs @@ -0,0 +1,71 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE TypeOperators #-} + +module Clash.Tests.TopEntityGen where + +import Test.Tasty +import Test.Tasty.HUnit + +import Language.Haskell.TH + +import Clash.Prelude hiding (undefined, Exp) +import Clash.Annotations.TopEntityGen + +data Unnamed = Unnamed Int +data Simple = Simple ("simple1" ::: Int) ("simple2" ::: Bool) +data Named + = Named + { name1 :: "named1" ::: BitVector 3 + , name2 :: "named2" ::: BitVector 5 + } +data Embedded + = Embedded + { name3 :: "embedded1" ::: Simple + , name4 :: "embedded2" ::: Bool + } + +topEntity1 :: "in1" ::: Signal System Int + -> "in2" ::: Signal System Bool + -> "out" ::: Signal System Int +topEntity1 = undefined +makeTopEntity 'topEntity1 +-- ======> +-- {-# ANN topEntity1 ((Synthesize "topEntity1") +-- [PortName "in1", PortName "in2"]) +-- (PortName "out") #-} +topEntity1Splice :: String +topEntity1Splice = "[PragmaD (AnnP (ValueAnnotation Clash.Tests.TopEntityGen.topEntity1) (AppE (AppE (AppE (ConE Clash.Annotations.TopEntity.Synthesize) (ListE [LitE (CharL 't'),LitE (CharL 'o'),LitE (CharL 'p'),LitE (CharL 'E'),LitE (CharL 'n'),LitE (CharL 't'),LitE (CharL 'i'),LitE (CharL 't'),LitE (CharL 'y'),LitE (CharL '1')])) (ListE [AppE (ConE Clash.Annotations.TopEntity.PortName) (ListE [LitE (CharL 'i'),LitE (CharL 'n'),LitE (CharL '1')]),AppE (ConE Clash.Annotations.TopEntity.PortName) (ListE [LitE (CharL 'i'),LitE (CharL 'n'),LitE (CharL '2')])])) (AppE (ConE Clash.Annotations.TopEntity.PortName) (ListE [LitE (CharL 'o'),LitE (CharL 'u'),LitE (CharL 't')]))))]" + +topEntity2 :: "int" ::: Signal System Int + -> "tuple" ::: ("tup1" ::: Signal System (BitVector 7), "tup2" ::: Signal System (BitVector 9)) + -> "simple" ::: Signal System Simple + -> "named" ::: Signal System Named + -> "embedded" ::: Signal System Embedded + -> "out" ::: Signal System Bool +topEntity2 = undefined +makeTopEntity 'topEntity2 +-- ======> +-- {-# ANN topEntity2 ((Synthesize "topEntity2") +-- [PortName "int", +-- (PortProduct "tuple") [PortName "tup1", PortName "tup2"], +-- (PortProduct "simple") [PortName "simple1", PortName "simple2"], +-- (PortProduct "named") [PortName "named1", PortName "named2"], +-- (PortProduct "embedded") +-- [(PortProduct "embedded1") +-- [PortName "simple1", PortName "simple2"], +-- PortName "embedded2"]]) +-- (PortName "out") #-} +topEntity2Splice :: String +topEntity2Splice = "[PragmaD (AnnP (ValueAnnotation Clash.Tests.TopEntityGen.topEntity2) (AppE (AppE (AppE (ConE Clash.Annotations.TopEntity.Synthesize) (ListE [LitE (CharL 't'),LitE (CharL 'o'),LitE (CharL 'p'),LitE (CharL 'E'),LitE (CharL 'n'),LitE (CharL 't'),LitE (CharL 'i'),LitE (CharL 't'),LitE (CharL 'y'),LitE (CharL '2')])) (ListE [AppE (ConE Clash.Annotations.TopEntity.PortName) (ListE [LitE (CharL 'i'),LitE (CharL 'n'),LitE (CharL 't')]),AppE (AppE (ConE Clash.Annotations.TopEntity.PortProduct) (ListE [LitE (CharL 't'),LitE (CharL 'u'),LitE (CharL 'p'),LitE (CharL 'l'),LitE (CharL 'e')])) (ListE [AppE (ConE Clash.Annotations.TopEntity.PortName) (ListE [LitE (CharL 't'),LitE (CharL 'u'),LitE (CharL 'p'),LitE (CharL '1')]),AppE (ConE Clash.Annotations.TopEntity.PortName) (ListE [LitE (CharL 't'),LitE (CharL 'u'),LitE (CharL 'p'),LitE (CharL '2')])]),AppE (AppE (ConE Clash.Annotations.TopEntity.PortProduct) (ListE [LitE (CharL 's'),LitE (CharL 'i'),LitE (CharL 'm'),LitE (CharL 'p'),LitE (CharL 'l'),LitE (CharL 'e')])) (ListE [AppE (ConE Clash.Annotations.TopEntity.PortName) (ListE [LitE (CharL 's'),LitE (CharL 'i'),LitE (CharL 'm'),LitE (CharL 'p'),LitE (CharL 'l'),LitE (CharL 'e'),LitE (CharL '1')]),AppE (ConE Clash.Annotations.TopEntity.PortName) (ListE [LitE (CharL 's'),LitE (CharL 'i'),LitE (CharL 'm'),LitE (CharL 'p'),LitE (CharL 'l'),LitE (CharL 'e'),LitE (CharL '2')])]),AppE (AppE (ConE Clash.Annotations.TopEntity.PortProduct) (ListE [LitE (CharL 'n'),LitE (CharL 'a'),LitE (CharL 'm'),LitE (CharL 'e'),LitE (CharL 'd')])) (ListE [AppE (ConE Clash.Annotations.TopEntity.PortName) (ListE [LitE (CharL 'n'),LitE (CharL 'a'),LitE (CharL 'm'),LitE (CharL 'e'),LitE (CharL 'd'),LitE (CharL '1')]),AppE (ConE Clash.Annotations.TopEntity.PortName) (ListE [LitE (CharL 'n'),LitE (CharL 'a'),LitE (CharL 'm'),LitE (CharL 'e'),LitE (CharL 'd'),LitE (CharL '2')])]),AppE (AppE (ConE Clash.Annotations.TopEntity.PortProduct) (ListE [LitE (CharL 'e'),LitE (CharL 'm'),LitE (CharL 'b'),LitE (CharL 'e'),LitE (CharL 'd'),LitE (CharL 'd'),LitE (CharL 'e'),LitE (CharL 'd')])) (ListE [AppE (AppE (ConE Clash.Annotations.TopEntity.PortProduct) (ListE [LitE (CharL 'e'),LitE (CharL 'm'),LitE (CharL 'b'),LitE (CharL 'e'),LitE (CharL 'd'),LitE (CharL 'd'),LitE (CharL 'e'),LitE (CharL 'd'),LitE (CharL '1')])) (ListE [AppE (ConE Clash.Annotations.TopEntity.PortName) (ListE [LitE (CharL 's'),LitE (CharL 'i'),LitE (CharL 'm'),LitE (CharL 'p'),LitE (CharL 'l'),LitE (CharL 'e'),LitE (CharL '1')]),AppE (ConE Clash.Annotations.TopEntity.PortName) (ListE [LitE (CharL 's'),LitE (CharL 'i'),LitE (CharL 'm'),LitE (CharL 'p'),LitE (CharL 'l'),LitE (CharL 'e'),LitE (CharL '2')])]),AppE (ConE Clash.Annotations.TopEntity.PortName) (ListE [LitE (CharL 'e'),LitE (CharL 'm'),LitE (CharL 'b'),LitE (CharL 'e'),LitE (CharL 'd'),LitE (CharL 'd'),LitE (CharL 'e'),LitE (CharL 'd'),LitE (CharL '2')])])])) (AppE (ConE Clash.Annotations.TopEntity.PortName) (ListE [LitE (CharL 'o'),LitE (CharL 'u'),LitE (CharL 't')]))))]" + +tests :: TestTree +tests = + testGroup + "topEntityGen" + [ testCase "topEntity1" $ + $(stringE . show =<< makeTopEntity 'topEntity1) @?= topEntity1Splice + , testCase "topEntity2" $ + $(stringE . show =<< makeTopEntity 'topEntity2) @?= topEntity2Splice + ] + diff --git a/clash-prelude/tests/unittests.hs b/clash-prelude/tests/unittests.hs index 1631507a59..6b1d446e9f 100644 --- a/clash-prelude/tests/unittests.hs +++ b/clash-prelude/tests/unittests.hs @@ -7,6 +7,7 @@ import qualified Clash.Tests.BitVector import qualified Clash.Tests.DerivingDataRepr import qualified Clash.Tests.Signal import qualified Clash.Tests.NFDataX +import qualified Clash.Tests.TopEntityGen tests :: TestTree tests = testGroup "Unittests" @@ -15,6 +16,7 @@ tests = testGroup "Unittests" , Clash.Tests.DerivingDataRepr.tests , Clash.Tests.Signal.tests , Clash.Tests.NFDataX.tests + , Clash.Tests.TopEntityGen.tests ] main :: IO () From 8eb3b81a58d1c1e06de5186c33e675d30d67338c Mon Sep 17 00:00:00 2001 From: Ben Blaxill Date: Thu, 22 Aug 2019 14:53:05 +0100 Subject: [PATCH 02/19] Add no-warn-orphans option to TopEntityGen.hs --- clash-prelude/src/Clash/Annotations/TopEntityGen.hs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/clash-prelude/src/Clash/Annotations/TopEntityGen.hs b/clash-prelude/src/Clash/Annotations/TopEntityGen.hs index ec95fcd9e3..39307a7b00 100644 --- a/clash-prelude/src/Clash/Annotations/TopEntityGen.hs +++ b/clash-prelude/src/Clash/Annotations/TopEntityGen.hs @@ -1,3 +1,8 @@ +-- TODO: Doc + +{-# OPTIONS_GHC -fno-warn-orphans #-} +-- Required to 'makeBaseFunctor' of 'Language.Haskell.TH.Syntax.Type' + {-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveFoldable #-} {-# LANGUAGE DeriveFunctor #-} From 7ee61f7fa5c1108ea9e910e4b2ae90ad5602172b Mon Sep 17 00:00:00 2001 From: Ben Blaxill Date: Thu, 22 Aug 2019 16:03:04 +0100 Subject: [PATCH 03/19] Refactor TopEntityGen.hs --- .../src/Clash/Annotations/TopEntityGen.hs | 116 +++++++++--------- 1 file changed, 58 insertions(+), 58 deletions(-) diff --git a/clash-prelude/src/Clash/Annotations/TopEntityGen.hs b/clash-prelude/src/Clash/Annotations/TopEntityGen.hs index 39307a7b00..7fa4db9095 100644 --- a/clash-prelude/src/Clash/Annotations/TopEntityGen.hs +++ b/clash-prelude/src/Clash/Annotations/TopEntityGen.hs @@ -28,7 +28,7 @@ import Clash.Annotations.TopEntity (PortName(..), TopEntity(..)) import Clash.NamedTypes import Language.Haskell.TH -import Language.Haskell.TH.Syntax +-- import Language.Haskell.TH.Syntax import Data.Functor.Foldable (para) import Data.Functor.Foldable.TH @@ -38,20 +38,21 @@ $(makeBaseFunctor ''Type) -- A helper function for recursively walking a 'Type' tree and -- building a list of 'PortName's -buildPorts :: Type -> TypeF (Type, Q [PortName]) -> Q [PortName] --- Is there a ' ::: ' annotation? +buildPorts :: Type -- Type to split at + -> TypeF (Type, Q [PortName]) -> Q [PortName] -- Fold step buildPorts split (AppTF (AppT split' (LitT (StrTyLit name)), _) (_,c)) + -- Is there a ' ::: ' annotation? | split' == split - -- We found our splitter. If: + -- We found our split. If: -- - We only have no names from children: use split name as PortName -- - We have children reporting names: use split name as name to PortProduct -- - !! Inconsistent children names currently unhandled = c >>= \case [] -> return [PortName name] xs -> return [PortProduct name xs] --- Recur on types by reifying the name. --- This allows us to find splitters defined in data types. buildPorts split (ConTF name) = do + -- Recur on types by reifying the name. + -- This allows us to find splits defined in data types. info <- reify name case info of TyConI (DataD _ _ _ _ [RecC _ xs] _) -> @@ -60,9 +61,9 @@ buildPorts split (ConTF name) = do buildFromConstructor $ map snd xs _ -> return [] where buildFromConstructor = - fmap mconcat . sequence . fmap (para (buildPorts split)) --- Just collect names + fmap mconcat . mapM (para (buildPorts split)) buildPorts _ f = do + -- Just collect names f' <- mapM snd f return $ fold f' @@ -70,56 +71,55 @@ buildPorts _ f = do pattern ArrowTy :: Type -> Type -> Type pattern ArrowTy a b = AppT (AppT ArrowT a) b --- | Given an multi-arity function type @f :: a -> b -> c -> ...@, get --- the final return type. -getReturnTy :: Type -> Q Type -getReturnTy (ArrowTy _ b) = getReturnTy b -getReturnTy b = return b - -makeTopEntityWithName' :: Name -> Maybe String -> DecsQ -makeTopEntityWithName' n topName = reify n >>= \case - VarI nam typ _ -> do - -- helpers - let prag t = PragmaD (AnnP (valueAnnotation nam) t) - +-- Get the return 'PortName' from a splitter and function type +toReturnName :: Type -> Type -> Q PortName +toReturnName split (ArrowTy _ b) = toReturnName split b +toReturnName split b = + para (buildPorts split) b + >>= \case + [] -> fail "No return name specified!" + [x] -> return x + xs -> return $ PortProduct "" xs + +-- Get the argument 'PortName's from a splitter and function type +toArgNames :: Type -> Type -> Q [PortName] +toArgNames split ty = go (0::Int) ty [] + where + go i (ArrowTy x y) acc = do + v <- go (i+1) y acc + name <- para (buildPorts split) x + -- Fail if we didn't get a single PortName + case name of + [a] -> return (a:v) + f -> fail $ "Failed to get a single name for argument " ++ show i + ++ "\n a.k.a. " ++ show x + ++ "\n got name " ++ show f + go _ _ acc = return acc + +buildTopEntity :: Name -> Type -> Maybe String -> TExpQ TopEntity +buildTopEntity name ty topName = do -- get a Name for this type operator so we can check it -- in the ArrowTy case - nty <- [t| (:::) |] - - -- examine the arguments - let examine ty = go 0 ty [] where - go i (ArrowTy x y) xs - = do - v <- go (i+1) y xs - name <- para (buildPorts nty) x - -- Fail if we didn't get a single PortName - case name of - (a:[]) -> return (a:v) - f -> fail $ "Failed to get a single name for argument " ++ show i - ++ "\n a.k.a. " ++ show x - ++ "\n got name " ++ show f - - go _ _ xs = return xs - - ins <- examine typ - out <- getReturnTy typ - >>= para (buildPorts nty) - >>= \case - [] -> fail "No return name specified!" - [x] -> return x - xs -> return $ PortProduct "" xs - - -- Return the annotation - let realName = case topName of - Just nn -> nn -- user specified name - Nothing -> nameBase nam -- auto-generated name - top <- lift $ Synthesize - { t_name = realName - , t_inputs = ins - , t_output = out - } - return [prag top] - + split <- [t| (:::) |] + ins <- toArgNames split ty + out <- toReturnName split ty + + let outName = case topName of + Just name' -> name' -- user specified name + Nothing -> nameBase name -- auto-generated from haskell name + + [|| Synthesize + { t_name = outName + , t_inputs = ins + , t_output = out + } ||] + +-- Wrap a 'TopEntity' expression in an annotation pragma +makeTopEntityWithName' :: Name -> Maybe String -> DecQ +makeTopEntityWithName' n topName = reify n >>= \case + VarI name ty _ -> + let prag t = PragmaD (AnnP (valueAnnotation name) t) + in fmap (prag . unType) (buildTopEntity name ty topName) -- failure case: we weren't provided with the name of a binder _ -> fail "makeTopEntity: invalid Name, must be a top-level binding!" @@ -130,7 +130,7 @@ makeTopEntityWithName' n topName = reify n >>= \case -- given @'Name'@ must be annotated with @'(:::)'@. This annotation provides the -- given name of the port. makeTopEntityWithName :: Name -> String -> DecsQ -makeTopEntityWithName nam top = makeTopEntityWithName' nam (Just top) +makeTopEntityWithName nam top = pure <$> makeTopEntityWithName' nam (Just top) -- | Automatically create a @'TopEntity'@ for a given @'Name'@. The name of the -- generated RTL entity will be the name of the function that has been @@ -140,4 +140,4 @@ makeTopEntityWithName nam top = makeTopEntityWithName' nam (Just top) -- given @'Name'@ must be annotated with @'(:::)'@. This annotation provides the -- given name of the port. makeTopEntity :: Name -> DecsQ -makeTopEntity nam = makeTopEntityWithName' nam Nothing +makeTopEntity nam = pure <$> makeTopEntityWithName' nam Nothing From b5357dc52206520386c213cdb1ca55add84a077a Mon Sep 17 00:00:00 2001 From: Ben Blaxill Date: Thu, 22 Aug 2019 16:49:21 +0100 Subject: [PATCH 04/19] Add equality instances for TopEntity and PortName --- clash-prelude/src/Clash/Annotations/TopEntity.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clash-prelude/src/Clash/Annotations/TopEntity.hs b/clash-prelude/src/Clash/Annotations/TopEntity.hs index 9944f8f124..5ed6722b5a 100644 --- a/clash-prelude/src/Clash/Annotations/TopEntity.hs +++ b/clash-prelude/src/Clash/Annotations/TopEntity.hs @@ -253,7 +253,7 @@ data TopEntity -- g = ... -- @ | TestBench TH.Name - deriving (Data,Show,Generic) + deriving (Eq,Data,Show,Generic) instance Lift TopEntity where lift (Synthesize name inputs output) = @@ -355,7 +355,7 @@ data PortName -- 2. The prefix for any unnamed ports below the 'PortProduct' -- -- You can use an empty String ,\"\" , in case you want an auto-generated name. - deriving (Data,Show,Generic,Lift) + deriving (Eq,Data,Show,Generic,Lift) -- | Default 'Synthesize' annotation which has no specified names for the input -- and output ports. From e8ddcdb2773b8efe791b5cf59321ef551e80f70e Mon Sep 17 00:00:00 2001 From: Ben Blaxill Date: Thu, 22 Aug 2019 16:49:46 +0100 Subject: [PATCH 05/19] Proper TopEntityGen tests --- .../src/Clash/Annotations/TopEntityGen.hs | 27 +++++-- .../tests/Clash/Tests/TopEntityGen.hs | 73 ++++++++++++------- 2 files changed, 67 insertions(+), 33 deletions(-) diff --git a/clash-prelude/src/Clash/Annotations/TopEntityGen.hs b/clash-prelude/src/Clash/Annotations/TopEntityGen.hs index 7fa4db9095..8be6d4729d 100644 --- a/clash-prelude/src/Clash/Annotations/TopEntityGen.hs +++ b/clash-prelude/src/Clash/Annotations/TopEntityGen.hs @@ -21,6 +21,9 @@ module Clash.Annotations.TopEntityGen ( makeTopEntity , makeTopEntityWithName + , makeTopEntityWithName' + , buildTopEntity + , buildTopEntity' ) where @@ -96,8 +99,8 @@ toArgNames split ty = go (0::Int) ty [] ++ "\n got name " ++ show f go _ _ acc = return acc -buildTopEntity :: Name -> Type -> Maybe String -> TExpQ TopEntity -buildTopEntity name ty topName = do +buildTopEntity' :: Maybe String -> (Name, Type) -> TExpQ TopEntity +buildTopEntity' topName (name, ty) = do -- get a Name for this type operator so we can check it -- in the ArrowTy case split <- [t| (:::) |] @@ -114,14 +117,22 @@ buildTopEntity name ty topName = do , t_output = out } ||] +buildTopEntity :: Maybe String -> Name -> ExpQ +buildTopEntity topName name = + fmap unType $ getNameBinding name >>= buildTopEntity' topName + +getNameBinding :: Name -> Q (Name, Type) +getNameBinding n = reify n >>= \case + VarI name ty _ -> return (name, ty) + _ -> fail "getNameBinding: Invalid Name, must be a top-level binding!" + -- Wrap a 'TopEntity' expression in an annotation pragma makeTopEntityWithName' :: Name -> Maybe String -> DecQ -makeTopEntityWithName' n topName = reify n >>= \case - VarI name ty _ -> - let prag t = PragmaD (AnnP (valueAnnotation name) t) - in fmap (prag . unType) (buildTopEntity name ty topName) - -- failure case: we weren't provided with the name of a binder - _ -> fail "makeTopEntity: invalid Name, must be a top-level binding!" +makeTopEntityWithName' n topName = do + (name,ty) <- getNameBinding n + topEntity <- buildTopEntity' topName (name,ty) + let prag t = PragmaD (AnnP (valueAnnotation name) t) + return $ prag $ unType topEntity -- | Automatically create a @'TopEntity'@ for a given @'Name'@, using the given -- @'String'@ to specify the name of the generated RTL entity. diff --git a/clash-prelude/tests/Clash/Tests/TopEntityGen.hs b/clash-prelude/tests/Clash/Tests/TopEntityGen.hs index aea0453eef..1f190487c5 100644 --- a/clash-prelude/tests/Clash/Tests/TopEntityGen.hs +++ b/clash-prelude/tests/Clash/Tests/TopEntityGen.hs @@ -7,9 +7,7 @@ module Clash.Tests.TopEntityGen where import Test.Tasty import Test.Tasty.HUnit -import Language.Haskell.TH - -import Clash.Prelude hiding (undefined, Exp) +import Clash.Prelude hiding (undefined) import Clash.Annotations.TopEntityGen data Unnamed = Unnamed Int @@ -30,42 +28,67 @@ topEntity1 :: "in1" ::: Signal System Int -> "out" ::: Signal System Int topEntity1 = undefined makeTopEntity 'topEntity1 --- ======> --- {-# ANN topEntity1 ((Synthesize "topEntity1") --- [PortName "in1", PortName "in2"]) --- (PortName "out") #-} -topEntity1Splice :: String -topEntity1Splice = "[PragmaD (AnnP (ValueAnnotation Clash.Tests.TopEntityGen.topEntity1) (AppE (AppE (AppE (ConE Clash.Annotations.TopEntity.Synthesize) (ListE [LitE (CharL 't'),LitE (CharL 'o'),LitE (CharL 'p'),LitE (CharL 'E'),LitE (CharL 'n'),LitE (CharL 't'),LitE (CharL 'i'),LitE (CharL 't'),LitE (CharL 'y'),LitE (CharL '1')])) (ListE [AppE (ConE Clash.Annotations.TopEntity.PortName) (ListE [LitE (CharL 'i'),LitE (CharL 'n'),LitE (CharL '1')]),AppE (ConE Clash.Annotations.TopEntity.PortName) (ListE [LitE (CharL 'i'),LitE (CharL 'n'),LitE (CharL '2')])])) (AppE (ConE Clash.Annotations.TopEntity.PortName) (ListE [LitE (CharL 'o'),LitE (CharL 'u'),LitE (CharL 't')]))))]" + +expectedTopEntity1 :: TopEntity +expectedTopEntity1 = + Synthesize "topEntity1" + [PortName "in1", PortName "in2"] + (PortName "out") topEntity2 :: "int" ::: Signal System Int - -> "tuple" ::: ("tup1" ::: Signal System (BitVector 7), "tup2" ::: Signal System (BitVector 9)) + -> "tuple" ::: ( "tup1" ::: Signal System (BitVector 7) + , "tup2" ::: Signal System (BitVector 9)) -> "simple" ::: Signal System Simple -> "named" ::: Signal System Named -> "embedded" ::: Signal System Embedded -> "out" ::: Signal System Bool topEntity2 = undefined makeTopEntity 'topEntity2 --- ======> --- {-# ANN topEntity2 ((Synthesize "topEntity2") --- [PortName "int", --- (PortProduct "tuple") [PortName "tup1", PortName "tup2"], --- (PortProduct "simple") [PortName "simple1", PortName "simple2"], --- (PortProduct "named") [PortName "named1", PortName "named2"], --- (PortProduct "embedded") --- [(PortProduct "embedded1") --- [PortName "simple1", PortName "simple2"], --- PortName "embedded2"]]) --- (PortName "out") #-} -topEntity2Splice :: String -topEntity2Splice = "[PragmaD (AnnP (ValueAnnotation Clash.Tests.TopEntityGen.topEntity2) (AppE (AppE (AppE (ConE Clash.Annotations.TopEntity.Synthesize) (ListE [LitE (CharL 't'),LitE (CharL 'o'),LitE (CharL 'p'),LitE (CharL 'E'),LitE (CharL 'n'),LitE (CharL 't'),LitE (CharL 'i'),LitE (CharL 't'),LitE (CharL 'y'),LitE (CharL '2')])) (ListE [AppE (ConE Clash.Annotations.TopEntity.PortName) (ListE [LitE (CharL 'i'),LitE (CharL 'n'),LitE (CharL 't')]),AppE (AppE (ConE Clash.Annotations.TopEntity.PortProduct) (ListE [LitE (CharL 't'),LitE (CharL 'u'),LitE (CharL 'p'),LitE (CharL 'l'),LitE (CharL 'e')])) (ListE [AppE (ConE Clash.Annotations.TopEntity.PortName) (ListE [LitE (CharL 't'),LitE (CharL 'u'),LitE (CharL 'p'),LitE (CharL '1')]),AppE (ConE Clash.Annotations.TopEntity.PortName) (ListE [LitE (CharL 't'),LitE (CharL 'u'),LitE (CharL 'p'),LitE (CharL '2')])]),AppE (AppE (ConE Clash.Annotations.TopEntity.PortProduct) (ListE [LitE (CharL 's'),LitE (CharL 'i'),LitE (CharL 'm'),LitE (CharL 'p'),LitE (CharL 'l'),LitE (CharL 'e')])) (ListE [AppE (ConE Clash.Annotations.TopEntity.PortName) (ListE [LitE (CharL 's'),LitE (CharL 'i'),LitE (CharL 'm'),LitE (CharL 'p'),LitE (CharL 'l'),LitE (CharL 'e'),LitE (CharL '1')]),AppE (ConE Clash.Annotations.TopEntity.PortName) (ListE [LitE (CharL 's'),LitE (CharL 'i'),LitE (CharL 'm'),LitE (CharL 'p'),LitE (CharL 'l'),LitE (CharL 'e'),LitE (CharL '2')])]),AppE (AppE (ConE Clash.Annotations.TopEntity.PortProduct) (ListE [LitE (CharL 'n'),LitE (CharL 'a'),LitE (CharL 'm'),LitE (CharL 'e'),LitE (CharL 'd')])) (ListE [AppE (ConE Clash.Annotations.TopEntity.PortName) (ListE [LitE (CharL 'n'),LitE (CharL 'a'),LitE (CharL 'm'),LitE (CharL 'e'),LitE (CharL 'd'),LitE (CharL '1')]),AppE (ConE Clash.Annotations.TopEntity.PortName) (ListE [LitE (CharL 'n'),LitE (CharL 'a'),LitE (CharL 'm'),LitE (CharL 'e'),LitE (CharL 'd'),LitE (CharL '2')])]),AppE (AppE (ConE Clash.Annotations.TopEntity.PortProduct) (ListE [LitE (CharL 'e'),LitE (CharL 'm'),LitE (CharL 'b'),LitE (CharL 'e'),LitE (CharL 'd'),LitE (CharL 'd'),LitE (CharL 'e'),LitE (CharL 'd')])) (ListE [AppE (AppE (ConE Clash.Annotations.TopEntity.PortProduct) (ListE [LitE (CharL 'e'),LitE (CharL 'm'),LitE (CharL 'b'),LitE (CharL 'e'),LitE (CharL 'd'),LitE (CharL 'd'),LitE (CharL 'e'),LitE (CharL 'd'),LitE (CharL '1')])) (ListE [AppE (ConE Clash.Annotations.TopEntity.PortName) (ListE [LitE (CharL 's'),LitE (CharL 'i'),LitE (CharL 'm'),LitE (CharL 'p'),LitE (CharL 'l'),LitE (CharL 'e'),LitE (CharL '1')]),AppE (ConE Clash.Annotations.TopEntity.PortName) (ListE [LitE (CharL 's'),LitE (CharL 'i'),LitE (CharL 'm'),LitE (CharL 'p'),LitE (CharL 'l'),LitE (CharL 'e'),LitE (CharL '2')])]),AppE (ConE Clash.Annotations.TopEntity.PortName) (ListE [LitE (CharL 'e'),LitE (CharL 'm'),LitE (CharL 'b'),LitE (CharL 'e'),LitE (CharL 'd'),LitE (CharL 'd'),LitE (CharL 'e'),LitE (CharL 'd'),LitE (CharL '2')])])])) (AppE (ConE Clash.Annotations.TopEntity.PortName) (ListE [LitE (CharL 'o'),LitE (CharL 'u'),LitE (CharL 't')]))))]" + +expectedTopEntity2 :: TopEntity +expectedTopEntity2 = + Synthesize "topEntity2" + [ PortName "int" + , PortProduct "tuple" [PortName "tup1",PortName "tup2"] + , PortProduct "simple" [PortName "simple1",PortName "simple2"] + , PortProduct "named" [PortName "named1",PortName "named2"] + , PortProduct "embedded" + [ PortProduct "embedded1" + [ PortName "simple1" + , PortName "simple2"] + , PortName "embedded2"] + ] + (PortName "out") + +topEntity3 :: "tup1" ::: Signal System (Int, Bool) + -> "tup2" ::: (Signal System Int, Signal System Bool) + -> "tup3" ::: Signal System ("int":::Int, "bool":::Bool) + -> "tup4" ::: ("int":::Signal System Int, "bool":::Signal System Bool) + -> "custom" ::: Signal System Named + -> "outTup" ::: Signal System ("outint":::Int, "outbool":::Bool) +topEntity3 = undefined +makeTopEntity 'topEntity3 + +expectedTopEntity3 :: TopEntity +expectedTopEntity3 = + Synthesize "topEntity3" + [ PortName "tup1" + , PortName "tup2" + , PortProduct "tup3" [PortName "int",PortName "bool"] + , PortProduct "tup4" [PortName "int",PortName "bool"] + , PortProduct "custom" [PortName "named1",PortName "named2"] + ] + (PortProduct "outTup" [PortName "outint",PortName "outbool"]) tests :: TestTree tests = testGroup "topEntityGen" [ testCase "topEntity1" $ - $(stringE . show =<< makeTopEntity 'topEntity1) @?= topEntity1Splice + $(buildTopEntity Nothing 'topEntity1) @?= expectedTopEntity1 , testCase "topEntity2" $ - $(stringE . show =<< makeTopEntity 'topEntity2) @?= topEntity2Splice + $(buildTopEntity Nothing 'topEntity2) @?= expectedTopEntity2 + , testCase "topEntity3" $ + $(buildTopEntity Nothing 'topEntity3) @?= expectedTopEntity3 ] From 427a67022f7bc50f283266dad0b787bb4cbc9523 Mon Sep 17 00:00:00 2001 From: Ben Blaxill Date: Thu, 22 Aug 2019 16:54:16 +0100 Subject: [PATCH 06/19] Add some doc to TopEntityGen --- .../src/Clash/Annotations/TopEntityGen.hs | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/clash-prelude/src/Clash/Annotations/TopEntityGen.hs b/clash-prelude/src/Clash/Annotations/TopEntityGen.hs index 8be6d4729d..66fa910fe1 100644 --- a/clash-prelude/src/Clash/Annotations/TopEntityGen.hs +++ b/clash-prelude/src/Clash/Annotations/TopEntityGen.hs @@ -1,4 +1,37 @@ --- TODO: Doc +{-| + +This module can automatically generate TopEntity definitions from 'Clash.NamedTypes' +annotations. + +@ +import Clash.Annotations.TopEntityGen + +data Named + = Named + { name1 :: "named1" ::: BitVector 3 + , name2 :: "named2" ::: BitVector 5 + } + +topEntity :: "tup1" ::: Signal System (Int, Bool) + -> "tup2" ::: (Signal System Int, Signal System Bool) + -> "tup3" ::: Signal System ("int":::Int, "bool":::Bool) + -> "tup4" ::: ("int":::Signal System Int, "bool":::Signal System Bool) + -> "custom" ::: Signal System Named + -> "outTup" ::: Signal System ("outint":::Int, "outbool":::Bool) +topEntity = undefined +makeTopEntity 'topEntity +-- ===> +-- {-# ANN topEntity Synthesize "topEntity3" +-- [ PortName "tup1" +-- , PortName "tup2" +-- , PortProduct "tup3" [PortName "int",PortName "bool"] +-- , PortProduct "tup4" [PortName "int",PortName "bool"] +-- , PortProduct "custom" [PortName "named1",PortName "named2"] +-- ] +-- (PortProduct "outTup" [PortName "outint",PortName "outbool"]) +-- #-} +@ +-} {-# OPTIONS_GHC -fno-warn-orphans #-} -- Required to 'makeBaseFunctor' of 'Language.Haskell.TH.Syntax.Type' From 50cf753f68f9db0eb8cdb24be5c579daf3df04e0 Mon Sep 17 00:00:00 2001 From: Ben Blaxill Date: Thu, 22 Aug 2019 16:58:17 +0100 Subject: [PATCH 07/19] TopEntityGen export comments --- clash-prelude/src/Clash/Annotations/TopEntityGen.hs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/clash-prelude/src/Clash/Annotations/TopEntityGen.hs b/clash-prelude/src/Clash/Annotations/TopEntityGen.hs index 66fa910fe1..06e2ecfac6 100644 --- a/clash-prelude/src/Clash/Annotations/TopEntityGen.hs +++ b/clash-prelude/src/Clash/Annotations/TopEntityGen.hs @@ -52,9 +52,11 @@ makeTopEntity 'topEntity {-# LANGUAGE TypeOperators #-} module Clash.Annotations.TopEntityGen - ( makeTopEntity + ( -- * To create a Synthesize annotation pragma + makeTopEntity , makeTopEntityWithName , makeTopEntityWithName' + -- * To create a TopEntity value , buildTopEntity , buildTopEntity' ) @@ -64,7 +66,6 @@ import Clash.Annotations.TopEntity (PortName(..), TopEntity(..)) import Clash.NamedTypes import Language.Haskell.TH --- import Language.Haskell.TH.Syntax import Data.Functor.Foldable (para) import Data.Functor.Foldable.TH From f6e15e71271974ce71d91338b8cd26de82a757fc Mon Sep 17 00:00:00 2001 From: Ben Blaxill Date: Sun, 1 Sep 2019 18:32:00 +0100 Subject: [PATCH 08/19] Better module names --- clash-prelude/clash-prelude.cabal | 4 ++-- .../src/Clash/Annotations/{TopEntityGen.hs => TH.hs} | 2 +- .../Clash/Tests/{TopEntityGen.hs => TopEntityGeneration.hs} | 6 +++--- clash-prelude/tests/unittests.hs | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) rename clash-prelude/src/Clash/Annotations/{TopEntityGen.hs => TH.hs} (99%) rename clash-prelude/tests/Clash/Tests/{TopEntityGen.hs => TopEntityGeneration.hs} (96%) diff --git a/clash-prelude/clash-prelude.cabal b/clash-prelude/clash-prelude.cabal index c418bc9f7e..0d916349f6 100644 --- a/clash-prelude/clash-prelude.cabal +++ b/clash-prelude/clash-prelude.cabal @@ -120,13 +120,13 @@ Library Exposed-modules: Clash.Prelude.Synchronizer Exposed-modules: Clash.Annotations.TopEntity - Clash.Annotations.TopEntityGen Clash.Annotations.Primitive Clash.Annotations.BitRepresentation Clash.Annotations.BitRepresentation.Deriving Clash.Annotations.BitRepresentation.Internal Clash.Annotations.BitRepresentation.Util Clash.Annotations.SynthesisAttributes + Clash.Annotations.TH Clash.Class.BitPack Clash.Class.Exp @@ -328,7 +328,7 @@ test-suite unittests Clash.Tests.DerivingDataReprTypes Clash.Tests.Signal Clash.Tests.NFDataX - Clash.Tests.TopEntityGen + Clash.Tests.TopEntityGeneration benchmark benchmark-clash-prelude diff --git a/clash-prelude/src/Clash/Annotations/TopEntityGen.hs b/clash-prelude/src/Clash/Annotations/TH.hs similarity index 99% rename from clash-prelude/src/Clash/Annotations/TopEntityGen.hs rename to clash-prelude/src/Clash/Annotations/TH.hs index 06e2ecfac6..c3be13572e 100644 --- a/clash-prelude/src/Clash/Annotations/TopEntityGen.hs +++ b/clash-prelude/src/Clash/Annotations/TH.hs @@ -51,7 +51,7 @@ makeTopEntity 'topEntity {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-} -module Clash.Annotations.TopEntityGen +module Clash.Annotations.TH ( -- * To create a Synthesize annotation pragma makeTopEntity , makeTopEntityWithName diff --git a/clash-prelude/tests/Clash/Tests/TopEntityGen.hs b/clash-prelude/tests/Clash/Tests/TopEntityGeneration.hs similarity index 96% rename from clash-prelude/tests/Clash/Tests/TopEntityGen.hs rename to clash-prelude/tests/Clash/Tests/TopEntityGeneration.hs index 1f190487c5..582c211ba2 100644 --- a/clash-prelude/tests/Clash/Tests/TopEntityGen.hs +++ b/clash-prelude/tests/Clash/Tests/TopEntityGeneration.hs @@ -2,13 +2,13 @@ {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeOperators #-} -module Clash.Tests.TopEntityGen where +module Clash.Tests.TopEntityGeneration where import Test.Tasty import Test.Tasty.HUnit import Clash.Prelude hiding (undefined) -import Clash.Annotations.TopEntityGen +import Clash.Annotations.TH data Unnamed = Unnamed Int data Simple = Simple ("simple1" ::: Int) ("simple2" ::: Bool) @@ -83,7 +83,7 @@ expectedTopEntity3 = tests :: TestTree tests = testGroup - "topEntityGen" + "TopEntityGeneration" [ testCase "topEntity1" $ $(buildTopEntity Nothing 'topEntity1) @?= expectedTopEntity1 , testCase "topEntity2" $ diff --git a/clash-prelude/tests/unittests.hs b/clash-prelude/tests/unittests.hs index 6b1d446e9f..4b7d98a76b 100644 --- a/clash-prelude/tests/unittests.hs +++ b/clash-prelude/tests/unittests.hs @@ -7,7 +7,7 @@ import qualified Clash.Tests.BitVector import qualified Clash.Tests.DerivingDataRepr import qualified Clash.Tests.Signal import qualified Clash.Tests.NFDataX -import qualified Clash.Tests.TopEntityGen +import qualified Clash.Tests.TopEntityGeneration tests :: TestTree tests = testGroup "Unittests" @@ -16,7 +16,7 @@ tests = testGroup "Unittests" , Clash.Tests.DerivingDataRepr.tests , Clash.Tests.Signal.tests , Clash.Tests.NFDataX.tests - , Clash.Tests.TopEntityGen.tests + , Clash.Tests.TopEntityGeneration.tests ] main :: IO () From 365541f8023a9550fd78468da33085d2e5f74771 Mon Sep 17 00:00:00 2001 From: Ben Blaxill Date: Sun, 1 Sep 2019 21:35:21 +0100 Subject: [PATCH 09/19] Some style fixes --- clash-prelude/src/Clash/Annotations/TH.hs | 46 +++++++++++-------- .../tests/Clash/Tests/TopEntityGeneration.hs | 4 +- 2 files changed, 30 insertions(+), 20 deletions(-) diff --git a/clash-prelude/src/Clash/Annotations/TH.hs b/clash-prelude/src/Clash/Annotations/TH.hs index c3be13572e..2b7b78e4ad 100644 --- a/clash-prelude/src/Clash/Annotations/TH.hs +++ b/clash-prelude/src/Clash/Annotations/TH.hs @@ -62,21 +62,28 @@ module Clash.Annotations.TH ) where -import Clash.Annotations.TopEntity (PortName(..), TopEntity(..)) -import Clash.NamedTypes - +import Data.Foldable ( fold ) import Language.Haskell.TH -import Data.Functor.Foldable (para) -import Data.Functor.Foldable.TH -import Data.Foldable (fold) +import Data.Functor.Foldable ( para ) +import Data.Functor.Foldable.TH + +import Clash.Annotations.TopEntity ( PortName(..) + , TopEntity(..) + ) +import Clash.NamedTypes + $(makeBaseFunctor ''Type) --- A helper function for recursively walking a 'Type' tree and --- building a list of 'PortName's -buildPorts :: Type -- Type to split at - -> TypeF (Type, Q [PortName]) -> Q [PortName] -- Fold step +-- | A helper function for recursively walking a 'Type' tree and +-- building a list of 'PortName's. +buildPorts + :: Type + -- ^ Type to split at + -> TypeF (Type, Q [PortName]) + -- ^ Case under scrutiny, paramorphism style + -> Q [PortName] buildPorts split (AppTF (AppT split' (LitT (StrTyLit name)), _) (_,c)) -- Is there a ' ::: ' annotation? | split' == split @@ -97,18 +104,18 @@ buildPorts split (ConTF name) = do TyConI (DataD _ _ _ _ [NormalC _ xs] _) -> buildFromConstructor $ map snd xs _ -> return [] - where buildFromConstructor = - fmap mconcat . mapM (para (buildPorts split)) + where + buildFromConstructor = fmap mconcat . mapM (para (buildPorts split)) buildPorts _ f = do -- Just collect names f' <- mapM snd f return $ fold f' --- matches a type `a -> b` +-- | Matches a type `a -> b` pattern ArrowTy :: Type -> Type -> Type pattern ArrowTy a b = AppT (AppT ArrowT a) b --- Get the return 'PortName' from a splitter and function type +-- | Get the result 'PortName' from a function type toReturnName :: Type -> Type -> Q PortName toReturnName split (ArrowTy _ b) = toReturnName split b toReturnName split b = @@ -118,7 +125,7 @@ toReturnName split b = [x] -> return x xs -> return $ PortProduct "" xs --- Get the argument 'PortName's from a splitter and function type +-- | Get the argument 'PortName's from a function type toArgNames :: Type -> Type -> Q [PortName] toArgNames split ty = go (0::Int) ty [] where @@ -133,13 +140,14 @@ toArgNames split ty = go (0::Int) ty [] ++ "\n got name " ++ show f go _ _ acc = return acc +-- | Return a typed expression for a 'TopEntity' of a given @('Name', 'Type')@. buildTopEntity' :: Maybe String -> (Name, Type) -> TExpQ TopEntity buildTopEntity' topName (name, ty) = do -- get a Name for this type operator so we can check it -- in the ArrowTy case split <- [t| (:::) |] - ins <- toArgNames split ty - out <- toReturnName split ty + ins <- toArgNames split ty + out <- toReturnName split ty let outName = case topName of Just name' -> name' -- user specified name @@ -151,16 +159,18 @@ buildTopEntity' topName (name, ty) = do , t_output = out } ||] +-- | Return an untyped expression for a 'TopEntity' of a given 'Name'. buildTopEntity :: Maybe String -> Name -> ExpQ buildTopEntity topName name = fmap unType $ getNameBinding name >>= buildTopEntity' topName +-- | Turn the 'Name' of a value to a @('Name', 'Type')@ getNameBinding :: Name -> Q (Name, Type) getNameBinding n = reify n >>= \case VarI name ty _ -> return (name, ty) _ -> fail "getNameBinding: Invalid Name, must be a top-level binding!" --- Wrap a 'TopEntity' expression in an annotation pragma +-- | Wrap a 'TopEntity' expression in an annotation pragma makeTopEntityWithName' :: Name -> Maybe String -> DecQ makeTopEntityWithName' n topName = do (name,ty) <- getNameBinding n diff --git a/clash-prelude/tests/Clash/Tests/TopEntityGeneration.hs b/clash-prelude/tests/Clash/Tests/TopEntityGeneration.hs index 582c211ba2..9ee033a2ec 100644 --- a/clash-prelude/tests/Clash/Tests/TopEntityGeneration.hs +++ b/clash-prelude/tests/Clash/Tests/TopEntityGeneration.hs @@ -1,6 +1,6 @@ {-# LANGUAGE DataKinds #-} {-# LANGUAGE TemplateHaskell #-} -{-# LANGUAGE TypeOperators #-} +{-# LANGUAGE TypeOperators #-} module Clash.Tests.TopEntityGeneration where @@ -30,7 +30,7 @@ topEntity1 = undefined makeTopEntity 'topEntity1 expectedTopEntity1 :: TopEntity -expectedTopEntity1 = +expectedTopEntity1 = Synthesize "topEntity1" [PortName "in1", PortName "in2"] (PortName "out") From cf5d65721a8323901cb3493ebc1222c1689f1b82 Mon Sep 17 00:00:00 2001 From: Ben Blaxill Date: Wed, 11 Sep 2019 19:56:51 +0700 Subject: [PATCH 10/19] Remove template-haskell bounds --- clash-prelude/clash-prelude.cabal | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clash-prelude/clash-prelude.cabal b/clash-prelude/clash-prelude.cabal index 0d916349f6..2cc89b6248 100644 --- a/clash-prelude/clash-prelude.cabal +++ b/clash-prelude/clash-prelude.cabal @@ -319,8 +319,8 @@ test-suite unittests base, hint >= 0.7 && < 0.10, tasty >= 1.2 && < 1.3, - tasty-hunit , - template-haskell >= 2.9.0.0 && < 2.14 + tasty-hunit, + template-haskell Other-Modules: Clash.Tests.BitPack Clash.Tests.BitVector From 3133909e1cc9b9586bc5792142aaefefcb63ac65 Mon Sep 17 00:00:00 2001 From: Ben Blaxill Date: Wed, 11 Sep 2019 21:26:48 +0700 Subject: [PATCH 11/19] Use pprint in failure message --- clash-prelude/src/Clash/Annotations/TH.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clash-prelude/src/Clash/Annotations/TH.hs b/clash-prelude/src/Clash/Annotations/TH.hs index 2b7b78e4ad..2341051a52 100644 --- a/clash-prelude/src/Clash/Annotations/TH.hs +++ b/clash-prelude/src/Clash/Annotations/TH.hs @@ -136,7 +136,7 @@ toArgNames split ty = go (0::Int) ty [] case name of [a] -> return (a:v) f -> fail $ "Failed to get a single name for argument " ++ show i - ++ "\n a.k.a. " ++ show x + ++ "\n a.k.a. " ++ pprint x ++ "\n got name " ++ show f go _ _ acc = return acc From 1d624b137efee71888d55317d7dd65ef8999abfe Mon Sep 17 00:00:00 2001 From: Ben Blaxill Date: Wed, 11 Sep 2019 21:36:48 +0700 Subject: [PATCH 12/19] Add failure cases --- .../tests/Clash/Tests/TopEntityGeneration.hs | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/clash-prelude/tests/Clash/Tests/TopEntityGeneration.hs b/clash-prelude/tests/Clash/Tests/TopEntityGeneration.hs index 9ee033a2ec..f124e3ac59 100644 --- a/clash-prelude/tests/Clash/Tests/TopEntityGeneration.hs +++ b/clash-prelude/tests/Clash/Tests/TopEntityGeneration.hs @@ -4,6 +4,8 @@ module Clash.Tests.TopEntityGeneration where +import Language.Haskell.TH.Syntax (recover) + import Test.Tasty import Test.Tasty.HUnit @@ -80,15 +82,38 @@ expectedTopEntity3 = ] (PortProduct "outTup" [PortName "outint",PortName "outbool"]) +topEntityFailure1 + :: "int" ::: Signal System Int + -> "tuple" ::: ("tup1" ::: Signal System (BitVector 7), "tup2" ::: Signal System (BitVector 9)) + -> "simple" ::: Signal System Simple + -> "named" ::: Signal System Named + -> Signal System Embedded + -> "out" ::: Signal System Bool +topEntityFailure1 = undefined + +topEntityFailure2 + :: "int" ::: Signal System Int + -> "tuple" ::: ("tup1" ::: Signal System (BitVector 7), "tup2" ::: Signal System (BitVector 9)) + -> "simple" ::: Signal System Simple + -> "named" ::: Signal System Named + -> Signal System Int + -> "out" ::: Signal System Bool +topEntityFailure2 = undefined + tests :: TestTree tests = testGroup "TopEntityGeneration" [ testCase "topEntity1" $ - $(buildTopEntity Nothing 'topEntity1) @?= expectedTopEntity1 + $(recover ([| () |]) (buildTopEntity Nothing 'topEntity1)) @?= expectedTopEntity1 , testCase "topEntity2" $ - $(buildTopEntity Nothing 'topEntity2) @?= expectedTopEntity2 + $(recover ([| () |]) (buildTopEntity Nothing 'topEntity2)) @?= expectedTopEntity2 , testCase "topEntity3" $ - $(buildTopEntity Nothing 'topEntity3) @?= expectedTopEntity3 + $(recover ([| () |]) (buildTopEntity Nothing 'topEntity3)) @?= expectedTopEntity3 + + , testCase "topEntityFailure1" $ + $(recover ([| () |]) (buildTopEntity Nothing 'topEntityFailure1)) @?= () + , testCase "topEntityFailure2" $ + $(recover ([| () |]) (buildTopEntity Nothing 'topEntityFailure2)) @?= () ] From 903dd5ed4bab16ac7b6fe05a139030a4f4c271e0 Mon Sep 17 00:00:00 2001 From: Ben Blaxill Date: Wed, 11 Sep 2019 23:51:26 +0700 Subject: [PATCH 13/19] Broaden type support and tests Added support for: - newtypes - One step resolution of type/data family instances - all constructor types --- clash-prelude/src/Clash/Annotations/TH.hs | 231 ++++++++++++++++-- .../tests/Clash/Tests/TopEntityGeneration.hs | 88 ++++++- 2 files changed, 291 insertions(+), 28 deletions(-) diff --git a/clash-prelude/src/Clash/Annotations/TH.hs b/clash-prelude/src/Clash/Annotations/TH.hs index 2341051a52..f52216b81c 100644 --- a/clash-prelude/src/Clash/Annotations/TH.hs +++ b/clash-prelude/src/Clash/Annotations/TH.hs @@ -1,7 +1,9 @@ {-| This module can automatically generate TopEntity definitions from 'Clash.NamedTypes' -annotations. +annotations. Type/data families will resolve a single step (non-recusive). + +See 'clash-prelude/tests/Clash/Tests/TopEntityGeneration.hs' for more examples. @ import Clash.Annotations.TopEntityGen @@ -31,11 +33,13 @@ makeTopEntity 'topEntity -- (PortProduct "outTup" [PortName "outint",PortName "outbool"]) -- #-} @ + -} {-# OPTIONS_GHC -fno-warn-orphans #-} -- Required to 'makeBaseFunctor' of 'Language.Haskell.TH.Syntax.Type' +{-# LANGUAGE CPP #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveFoldable #-} {-# LANGUAGE DeriveFunctor #-} @@ -50,6 +54,7 @@ makeTopEntity 'topEntity {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-} +{-# LANGUAGE ViewPatterns #-} module Clash.Annotations.TH ( -- * To create a Synthesize annotation pragma @@ -62,55 +67,227 @@ module Clash.Annotations.TH ) where -import Data.Foldable ( fold ) +import Data.Foldable ( fold, find ) +import qualified Data.Set as Set import Language.Haskell.TH -import Data.Functor.Foldable ( para ) +import Data.Functor.Foldable ( cata, para, embed ) import Data.Functor.Foldable.TH +import Control.Lens ( view + , _3 + ) +import Language.Haskell.TH.Instances ( ) import Clash.Annotations.TopEntity ( PortName(..) , TopEntity(..) ) import Clash.NamedTypes - $(makeBaseFunctor ''Type) +-- | A datatype to track failing naming in a subtree. +data Naming a = Complete [a] | HasFail String + +instance Semigroup (Naming a) where + Complete a <> Complete b = Complete $ a ++ b + HasFail e1 <> HasFail e2 = HasFail $ e1 ++ e2 + _ <> HasFail e = HasFail e + HasFail e <> _ = HasFail e + +instance Monoid (Naming a) where + mempty = Complete [] + +-- | A template haskell helper function. Get the 'Type's from a 'Con'. +getTypes :: Con -> [Type] +getTypes (NormalC _ xs) = map snd xs +getTypes (RecC _ xs) = map (view _3) xs +getTypes (InfixC x _ y) = [snd x, snd y] +getTypes (ForallC _ _ c) = getTypes c +getTypes (GadtC _ xs _) = map snd xs +getTypes (RecGadtC _ xs _) = map (view _3) xs + +-- | A template haskell helper function. Get the 'Name' of a 'Con'. These names +-- are only used in type/data family resolution which is best effort, +-- so for Gadts we just take the first name. +getName :: Con -> Name +getName (NormalC n _) = n +getName (RecC n _) = n +getName (InfixC _ n _) = n +getName (ForallC _ _ c) = getName c +getName (GadtC (n : _) _ _) = n +getName (RecGadtC (n : _) _ _) = n +getName (GadtC [] _ _) = error "Encountered GADT with no constructors?" +getName (RecGadtC [] _ _) = error "Encountered GADT with no constructors?" + +-- | Flag constructors with partially named fields as failing. +namesFromConstructor + :: Set.Set Name + -> Type + -> Info + -> Con + -> Q (Naming PortName) +namesFromConstructor seen split info (getTypes -> xs) = + (mconcat <$> mapM (expandFamiliesAndGatherNames seen split) xs) + >>= \case + Complete names | length names > 0 && length names /= length xs -> + return $ HasFail $ "Partially named constructor arguments in " + ++ pprint info ++ "\n" + x -> return x + +-- | Flag sum types as failing if they have any constructors with names. +possibleNamedSum + :: Set.Set Name + -> Type + -> Info + -> [Con] + -> Q (Naming PortName) +possibleNamedSum seen split info xs = + (fold <$> mapM (namesFromConstructor seen split info) xs) >>= \case + Complete [] -> return $ Complete [] + x -> return $ x <> HasFail ("Sum types not supported!" + ++ "\n at " ++ pprint info ++ "\n") + +-- | Build a list of 'PortName's from a template haskell 'Name' +nameToPorts + :: Set.Set Name + -- ^ Locally track seen names to handle recursive datatypes + -> Type + -- ^ Type to split at + -> Name + -- ^ Name to investigate + -> Q (Naming PortName) +nameToPorts seen split name = do + if Set.member name seen + then return $ Complete [] + else do + let seen' = Set.insert name seen + -- Recur on types by reifying the name. + -- This allows us to find splits defined in data types. + info <- reify name + + case info of + TyConI (getConstructors -> [x]) -> + namesFromConstructor seen' split info x + TyConI (getConstructors -> xs) -> + possibleNamedSum seen split info xs + DataConI _ (AppT (AppT ArrowT ty) _) _ -> + expandFamiliesAndGatherNames seen split ty + PrimTyConI _ _ _ -> return $ Complete [] + _ -> return $ HasFail $ "Unhandled " ++ pprint info ++ "\n" + where + getConstructors (DataD _ _ _ _ x _) = x + getConstructors (NewtypeD _ _ _ _ x _) = [x] + getConstructors (DataInstD _ _ _ _ x _) = x + getConstructors (NewtypeInstD _ _ _ _ x _) = [x] + getConstructors _ = [] + -- | A helper function for recursively walking a 'Type' tree and -- building a list of 'PortName's. -buildPorts - :: Type +gatherNames + :: Set.Set Name + -- ^ Locally track seen names to handle recursive datatypes + -> Type -- ^ Type to split at - -> TypeF (Type, Q [PortName]) + -> TypeF (Type, Q (Naming PortName)) -- ^ Case under scrutiny, paramorphism style - -> Q [PortName] -buildPorts split (AppTF (AppT split' (LitT (StrTyLit name)), _) (_,c)) + -> Q (Naming PortName) +gatherNames _ split (AppTF (AppT split' (LitT (StrTyLit name)), _) (_,c)) -- Is there a ' ::: ' annotation? | split' == split -- We found our split. If: -- - We only have no names from children: use split name as PortName -- - We have children reporting names: use split name as name to PortProduct - -- - !! Inconsistent children names currently unhandled = c >>= \case - [] -> return [PortName name] - xs -> return [PortProduct name xs] -buildPorts split (ConTF name) = do - -- Recur on types by reifying the name. - -- This allows us to find splits defined in data types. - info <- reify name - case info of - TyConI (DataD _ _ _ _ [RecC _ xs] _) -> - buildFromConstructor $ map (\(_,_,c)->c) xs - TyConI (DataD _ _ _ _ [NormalC _ xs] _) -> - buildFromConstructor $ map snd xs - _ -> return [] - where - buildFromConstructor = fmap mconcat . mapM (para (buildPorts split)) -buildPorts _ f = do + Complete [] -> return $ Complete [PortName name] + Complete xs -> return $ Complete [PortProduct name xs] + HasFail err -> return $ HasFail err +gatherNames seen split (ConTF name) = nameToPorts seen split name +gatherNames _ _ f = do -- Just collect names f' <- mapM snd f return $ fold f' +-- | Helper algebra to expand first level (non-recursive) type/data family +-- instances in a best effort manner. Data family instances with sum type +-- constructors are ignored. +expandFamilies :: TypeF (Q Type) -> Q (Type) +expandFamilies (AppTF a b) = do + a' <- a + b' <- b + case unApp (AppT a' b') [] of + (ConT x : xs) -> do + info <- reify x + case info of + -- Closed type families have to be handled separately + FamilyI (ClosedTypeFamilyD (TypeFamilyHead _ bds _ _) eqs) _ -> + if length bds == length xs then + case find ((==) xs . tySynArgs) eqs of + Just (TySynEqn _ r) -> return r + _ -> return (AppT a' b') + -- ^ We didn't find a matching instance so give up. + else return (AppT a' b') + -- ^ We don't yet have all the arguments. + -- Now for open type families and data families + _ -> + case familyArity info of + Just i | i == length xs -> do + inst <- reifyInstances x xs + case inst of +#if MIN_VERSION_template_haskell(2,15,0) + [TySynInstD (TySynEqn _ _ t)] -> return t +#else + [TySynInstD _ (TySynEqn _ t)] -> return t +#endif + [NewtypeInstD _ _ _ _ c _] -> return $ ConT (getName c) + [DataInstD _ _ _ _ cs _] -> do + case cs of + [c] -> return $ ConT (getName c) + _ -> return $ PromotedTupleT 0 + -- ^ Ignore sum type in a data family by replacing with + -- empty tuple. We don't want to fail because this subtree + -- might not be relevant to naming. + z -> fail + $ "Encountered\n" + ++ pprint z + ++ "\nduring family application! Perhaps a missing instance?" + _ -> return (AppT a' b') + _ -> return (AppT a' b') + where + unApp (AppT l r) = unApp l . (r :) + unApp t = (t :) + + tySynArgs (TySynEqn args _) = args + + familyArity (FamilyI (OpenTypeFamilyD (TypeFamilyHead _ xs _ _)) _) = + Just (length xs) + familyArity (FamilyI (DataFamilyD _ xs _) _) = Just (length xs) + familyArity _ = Nothing +expandFamilies t = embed <$> sequence t + +-- | Runs 'expandFamilies' and then 'gatherNames' +expandFamiliesAndGatherNames + :: Set.Set Name + -- ^ Locally track seen names to handle recursive datatypes + -> Type + -- ^ Type to split at + -> Type + -- ^ Type to investigate + -> Q (Naming PortName) +expandFamiliesAndGatherNames seen split x = + cata expandFamilies x >>= para (gatherNames seen split) + +-- | Build a possible failing 'PortName' tree and unwrap the 'Naming' result. +buildPorts + :: Type + -- ^ Type to split at + -> Type + -- ^ Type to investigate + -> Q [PortName] +buildPorts split x = do + expandFamiliesAndGatherNames Set.empty split x >>= \case + Complete xs -> return xs + HasFail err -> fail err + -- | Matches a type `a -> b` pattern ArrowTy :: Type -> Type -> Type pattern ArrowTy a b = AppT (AppT ArrowT a) b @@ -119,7 +296,7 @@ pattern ArrowTy a b = AppT (AppT ArrowT a) b toReturnName :: Type -> Type -> Q PortName toReturnName split (ArrowTy _ b) = toReturnName split b toReturnName split b = - para (buildPorts split) b + buildPorts split b >>= \case [] -> fail "No return name specified!" [x] -> return x @@ -131,7 +308,7 @@ toArgNames split ty = go (0::Int) ty [] where go i (ArrowTy x y) acc = do v <- go (i+1) y acc - name <- para (buildPorts split) x + name <- buildPorts split x -- Fail if we didn't get a single PortName case name of [a] -> return (a:v) diff --git a/clash-prelude/tests/Clash/Tests/TopEntityGeneration.hs b/clash-prelude/tests/Clash/Tests/TopEntityGeneration.hs index f124e3ac59..02985d82aa 100644 --- a/clash-prelude/tests/Clash/Tests/TopEntityGeneration.hs +++ b/clash-prelude/tests/Clash/Tests/TopEntityGeneration.hs @@ -1,6 +1,8 @@ {-# LANGUAGE DataKinds #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeOperators #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE GADTs #-} module Clash.Tests.TopEntityGeneration where @@ -24,6 +26,27 @@ data Embedded { name3 :: "embedded1" ::: Simple , name4 :: "embedded2" ::: Bool } +newtype Single = Single ("s" ::: Int) + +data Gadt x where + Gadt :: ("gadt" ::: Int) -> Gadt Int + +type family CF x y z where + CF Int Int Int = ("cfiii" ::: Single) + CF Bool Int Int = ("cfbii" ::: Single) +type family OF x y z +type instance OF Int Int Int = ("ofiii" ::: Single) +type instance OF Bool Int Int = ("ofbii" ::: Single) + +data family X x y z +data instance X Int Int Int = X1 ("xiii" ::: Int) +newtype instance X Bool Int Int = X2 ("xbii" ::: Int) + +data Impossible = L ("left" ::: Int) | R ("right" ::: Bool) + +data FailureTy1 = TwoF1 ("one" ::: Int) Int +data FailureTy2 = TwoF2 ("one" ::: Int) Simple +data SuccessTy = TwoS ("one" ::: Int) Single topEntity1 :: "in1" ::: Signal System Int -> "in2" ::: Signal System Bool @@ -73,7 +96,7 @@ makeTopEntity 'topEntity3 expectedTopEntity3 :: TopEntity expectedTopEntity3 = - Synthesize "topEntity3" + Synthesize "topEntity3" [ PortName "tup1" , PortName "tup2" , PortProduct "tup3" [PortName "int",PortName "bool"] @@ -82,6 +105,44 @@ expectedTopEntity3 = ] (PortProduct "outTup" [PortName "outint",PortName "outbool"]) +topEntity4 :: Signal System (Gadt Int) + -> Signal System Single + -> Signal System (CF Int Int Int) + -> Signal System (CF Bool Int Int) + -> Signal System (OF Int Int Int) + -> Signal System (OF Bool Int Int) + -> Signal System (X Int Int Int) + -> Signal System (X Bool Int Int) + -> Signal System Single +topEntity4 = undefined +makeTopEntity 'topEntity4 + +expectedTopEntity4 :: TopEntity +expectedTopEntity4 = + Synthesize "topEntity4" + [ PortName "gadt" + , PortName "s" + , PortProduct "cfiii" [PortName "s"] + , PortProduct "cfbii" [PortName "s"] + , PortProduct "ofiii" [PortName "s"] + , PortProduct "ofbii" [PortName "s"] + , PortName "xiii" + , PortName "xbii" + ] + (PortName "s") + +topEntity5 :: "in1" ::: Signal System SuccessTy + -> "out" ::: Signal System Int +topEntity5 = undefined +makeTopEntity 'topEntity5 + +expectedTopEntity5 :: TopEntity +expectedTopEntity5 = + Synthesize "topEntity5" + [PortProduct "in1" [PortName "one", PortName "s"]] + (PortName "out") + + topEntityFailure1 :: "int" ::: Signal System Int -> "tuple" ::: ("tup1" ::: Signal System (BitVector 7), "tup2" ::: Signal System (BitVector 9)) @@ -100,6 +161,21 @@ topEntityFailure2 -> "out" ::: Signal System Bool topEntityFailure2 = undefined +topEntityFailure3 + :: "int" ::: Signal System Impossible + -> "out" ::: Signal System Bool +topEntityFailure3 = undefined + +topEntityFailure4 + :: "int" ::: Signal System FailureTy1 + -> "out" ::: Signal System Bool +topEntityFailure4 = undefined + +topEntityFailure5 + :: "int" ::: Signal System FailureTy2 + -> "out" ::: Signal System Bool +topEntityFailure5 = undefined + tests :: TestTree tests = testGroup @@ -110,10 +186,20 @@ tests = $(recover ([| () |]) (buildTopEntity Nothing 'topEntity2)) @?= expectedTopEntity2 , testCase "topEntity3" $ $(recover ([| () |]) (buildTopEntity Nothing 'topEntity3)) @?= expectedTopEntity3 + , testCase "topEntity4" $ + $(recover ([| () |]) (buildTopEntity Nothing 'topEntity4)) @?= expectedTopEntity4 + , testCase "topEntity5" $ + $(recover ([| () |]) (buildTopEntity Nothing 'topEntity5)) @?= expectedTopEntity5 , testCase "topEntityFailure1" $ $(recover ([| () |]) (buildTopEntity Nothing 'topEntityFailure1)) @?= () , testCase "topEntityFailure2" $ $(recover ([| () |]) (buildTopEntity Nothing 'topEntityFailure2)) @?= () + , testCase "topEntityFailure3" $ + $(recover ([| () |]) (buildTopEntity Nothing 'topEntityFailure3)) @?= () + , testCase "topEntityFailure4" $ + $(recover ([| () |]) (buildTopEntity Nothing 'topEntityFailure4)) @?= () + , testCase "topEntityFailure5" $ + $(recover ([| () |]) (buildTopEntity Nothing 'topEntityFailure5)) @?= () ] From dbeeea52a4811e4022a18ba9333aa14156c1f418 Mon Sep 17 00:00:00 2001 From: Ben Blaxill Date: Thu, 12 Sep 2019 14:04:44 +0700 Subject: [PATCH 14/19] Doc fix --- clash-prelude/src/Clash/Annotations/TH.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clash-prelude/src/Clash/Annotations/TH.hs b/clash-prelude/src/Clash/Annotations/TH.hs index f52216b81c..95efa2f573 100644 --- a/clash-prelude/src/Clash/Annotations/TH.hs +++ b/clash-prelude/src/Clash/Annotations/TH.hs @@ -3,7 +3,7 @@ This module can automatically generate TopEntity definitions from 'Clash.NamedTypes' annotations. Type/data families will resolve a single step (non-recusive). -See 'clash-prelude/tests/Clash/Tests/TopEntityGeneration.hs' for more examples. +See "Clash.Tests.TopEntityGeneration" for more examples. @ import Clash.Annotations.TopEntityGen From d26d3d1d6340a86ec4801499e331f5e410cc1467 Mon Sep 17 00:00:00 2001 From: Ben Blaxill Date: Thu, 12 Sep 2019 14:11:34 +0700 Subject: [PATCH 15/19] Semigroup fix --- clash-prelude/src/Clash/Annotations/TH.hs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/clash-prelude/src/Clash/Annotations/TH.hs b/clash-prelude/src/Clash/Annotations/TH.hs index 95efa2f573..774c75eb3c 100644 --- a/clash-prelude/src/Clash/Annotations/TH.hs +++ b/clash-prelude/src/Clash/Annotations/TH.hs @@ -67,8 +67,13 @@ module Clash.Annotations.TH ) where -import Data.Foldable ( fold, find ) +import Data.Foldable ( fold + , find + ) import qualified Data.Set as Set +#if !(MIN_VERSION_base(4,11,0)) +import Data.Semigroup as Semigroup +#endif import Language.Haskell.TH import Data.Functor.Foldable ( cata, para, embed ) @@ -96,6 +101,9 @@ instance Semigroup (Naming a) where instance Monoid (Naming a) where mempty = Complete [] +#if !(MIN_VERSION_base(4,11,0)) + mappend = (Semigroup.<>) +#endif -- | A template haskell helper function. Get the 'Type's from a 'Con'. getTypes :: Con -> [Type] From bd11b87fe6fb56a28d7f5fbcddca79559f2ff37d Mon Sep 17 00:00:00 2001 From: Ben Blaxill Date: Thu, 12 Sep 2019 14:17:58 +0700 Subject: [PATCH 16/19] Template haskell fix for 2.15+ --- clash-prelude/src/Clash/Annotations/TH.hs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/clash-prelude/src/Clash/Annotations/TH.hs b/clash-prelude/src/Clash/Annotations/TH.hs index 774c75eb3c..8f39382e6f 100644 --- a/clash-prelude/src/Clash/Annotations/TH.hs +++ b/clash-prelude/src/Clash/Annotations/TH.hs @@ -230,7 +230,11 @@ expandFamilies (AppTF a b) = do FamilyI (ClosedTypeFamilyD (TypeFamilyHead _ bds _ _) eqs) _ -> if length bds == length xs then case find ((==) xs . tySynArgs) eqs of +#if MIN_VERSION_template_haskell(2,15,0) + Just (TySynEqn _ _ r) -> return r +#else Just (TySynEqn _ r) -> return r +#else _ -> return (AppT a' b') -- ^ We didn't find a matching instance so give up. else return (AppT a' b') @@ -264,7 +268,11 @@ expandFamilies (AppTF a b) = do unApp (AppT l r) = unApp l . (r :) unApp t = (t :) +#if MIN_VERSION_template_haskell(2,15,0) + tySynArgs (TySynEqn _ args _) = tail (unApp args []) +#else tySynArgs (TySynEqn args _) = args +#endif familyArity (FamilyI (OpenTypeFamilyD (TypeFamilyHead _ xs _ _)) _) = Just (length xs) From 48b19ab8d155b0db8220cb7f1e2236475f1519fe Mon Sep 17 00:00:00 2001 From: Ben Blaxill Date: Thu, 12 Sep 2019 14:19:06 +0700 Subject: [PATCH 17/19] Pushed too soon --- clash-prelude/src/Clash/Annotations/TH.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clash-prelude/src/Clash/Annotations/TH.hs b/clash-prelude/src/Clash/Annotations/TH.hs index 8f39382e6f..a718163349 100644 --- a/clash-prelude/src/Clash/Annotations/TH.hs +++ b/clash-prelude/src/Clash/Annotations/TH.hs @@ -234,7 +234,7 @@ expandFamilies (AppTF a b) = do Just (TySynEqn _ _ r) -> return r #else Just (TySynEqn _ r) -> return r -#else +#endif _ -> return (AppT a' b') -- ^ We didn't find a matching instance so give up. else return (AppT a' b') From 5eb4d1185f861d6ae9a234e7e24e31ff8b3fc308 Mon Sep 17 00:00:00 2001 From: Ben Blaxill Date: Thu, 12 Sep 2019 14:33:53 +0700 Subject: [PATCH 18/19] Work with concrete types with outer constraint --- clash-prelude/src/Clash/Annotations/TH.hs | 17 ++++++++-- .../tests/Clash/Tests/TopEntityGeneration.hs | 32 ++++++++++++++++--- 2 files changed, 42 insertions(+), 7 deletions(-) diff --git a/clash-prelude/src/Clash/Annotations/TH.hs b/clash-prelude/src/Clash/Annotations/TH.hs index a718163349..2810b2812a 100644 --- a/clash-prelude/src/Clash/Annotations/TH.hs +++ b/clash-prelude/src/Clash/Annotations/TH.hs @@ -333,14 +333,27 @@ toArgNames split ty = go (0::Int) ty [] ++ "\n got name " ++ show f go _ _ acc = return acc +-- | Remove constraints from a type. Fail if there are free type variables. +removeConstraints :: Type -> Q Type +removeConstraints (ForallT [] _ x) = removeConstraints x +removeConstraints (ForallT xs _ x) = + fail $ "Found free type variables on TopEntity!\n" + ++ pprint xs + ++ "\nattached to\n" + ++ pprint x +removeConstraints x = return x + -- | Return a typed expression for a 'TopEntity' of a given @('Name', 'Type')@. buildTopEntity' :: Maybe String -> (Name, Type) -> TExpQ TopEntity buildTopEntity' topName (name, ty) = do -- get a Name for this type operator so we can check it -- in the ArrowTy case split <- [t| (:::) |] - ins <- toArgNames split ty - out <- toReturnName split ty + + ty' <- removeConstraints ty + + ins <- toArgNames split ty' + out <- toReturnName split ty' let outName = case topName of Just name' -> name' -- user specified name diff --git a/clash-prelude/tests/Clash/Tests/TopEntityGeneration.hs b/clash-prelude/tests/Clash/Tests/TopEntityGeneration.hs index 02985d82aa..ebb70d9a17 100644 --- a/clash-prelude/tests/Clash/Tests/TopEntityGeneration.hs +++ b/clash-prelude/tests/Clash/Tests/TopEntityGeneration.hs @@ -1,8 +1,9 @@ -{-# LANGUAGE DataKinds #-} -{-# LANGUAGE TemplateHaskell #-} -{-# LANGUAGE TypeOperators #-} -{-# LANGUAGE TypeFamilies #-} -{-# LANGUAGE GADTs #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE TypeOperators #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE GADTs #-} module Clash.Tests.TopEntityGeneration where @@ -142,6 +143,18 @@ expectedTopEntity5 = [PortProduct "in1" [PortName "one", PortName "s"]] (PortName "out") +topEntity6 :: (HiddenClockResetEnable System) + => "in1" ::: Signal System SuccessTy + -> "out" ::: Signal System Int +topEntity6 = undefined +makeTopEntity 'topEntity6 + +expectedTopEntity6 :: TopEntity +expectedTopEntity6 = + Synthesize "topEntity6" + [PortProduct "in1" [PortName "one", PortName "s"]] + (PortName "out") + topEntityFailure1 :: "int" ::: Signal System Int @@ -176,6 +189,11 @@ topEntityFailure5 -> "out" ::: Signal System Bool topEntityFailure5 = undefined +topEntityFailure6 + :: "int" ::: Signal System a + -> "out" ::: Signal System Bool +topEntityFailure6 = undefined + tests :: TestTree tests = testGroup @@ -190,6 +208,8 @@ tests = $(recover ([| () |]) (buildTopEntity Nothing 'topEntity4)) @?= expectedTopEntity4 , testCase "topEntity5" $ $(recover ([| () |]) (buildTopEntity Nothing 'topEntity5)) @?= expectedTopEntity5 + , testCase "topEntity6" $ + $(recover ([| () |]) (buildTopEntity Nothing 'topEntity6)) @?= expectedTopEntity6 , testCase "topEntityFailure1" $ $(recover ([| () |]) (buildTopEntity Nothing 'topEntityFailure1)) @?= () @@ -201,5 +221,7 @@ tests = $(recover ([| () |]) (buildTopEntity Nothing 'topEntityFailure4)) @?= () , testCase "topEntityFailure5" $ $(recover ([| () |]) (buildTopEntity Nothing 'topEntityFailure5)) @?= () + , testCase "topEntityFailure6" $ + $(recover ([| () |]) (buildTopEntity Nothing 'topEntityFailure6)) @?= () ] From b1df7b95493b9f4b0ef7b7b2c13694da7052c5ee Mon Sep 17 00:00:00 2001 From: Ben Blaxill Date: Thu, 12 Sep 2019 14:42:28 +0700 Subject: [PATCH 19/19] More haddock fixes --- clash-prelude/src/Clash/Annotations/TH.hs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/clash-prelude/src/Clash/Annotations/TH.hs b/clash-prelude/src/Clash/Annotations/TH.hs index 2810b2812a..a935e102f7 100644 --- a/clash-prelude/src/Clash/Annotations/TH.hs +++ b/clash-prelude/src/Clash/Annotations/TH.hs @@ -236,9 +236,9 @@ expandFamilies (AppTF a b) = do Just (TySynEqn _ r) -> return r #endif _ -> return (AppT a' b') - -- ^ We didn't find a matching instance so give up. + -- We didn't find a matching instance so give up. else return (AppT a' b') - -- ^ We don't yet have all the arguments. + -- We don't yet have all the arguments. -- Now for open type families and data families _ -> case familyArity info of @@ -255,7 +255,7 @@ expandFamilies (AppTF a b) = do case cs of [c] -> return $ ConT (getName c) _ -> return $ PromotedTupleT 0 - -- ^ Ignore sum type in a data family by replacing with + -- Ignore sum type in a data family by replacing with -- empty tuple. We don't want to fail because this subtree -- might not be relevant to naming. z -> fail