{-# LANGUAGE TemplateHaskell #-} ----------------------------------------------------------------------------- -- | -- Module : ForSyDe.System.SysDef -- Copyright : (c) SAM Group, KTH/ICT/ECS 2007-2008 -- License : BSD-style (see the file LICENSE) -- -- Maintainer : forsyde-dev@ict.kth.se -- Stability : experimental -- Portability : portable -- -- This module provides the System Definition type ('SysDef') together with -- a Template Haskell constructor to build it. -- ----------------------------------------------------------------------------- module ForSyDe.System.SysDef (SysDef(..), PrimSysDef(..), SysDefVal(..), SysLogic(..), newSysDef, newSysDefTH, newSysDefTHName, Iface) where import ForSyDe.Ids import ForSyDe.Netlist import ForSyDe.Netlist.Traverse import ForSyDe.OSharing import ForSyDe.ForSyDeErr import ForSyDe.System.SysFun (checkSysFType, SysFun(..)) import Data.Maybe (isJust, fromJust) import Control.Monad.ST import Control.Monad.State import Data.Typeable import Language.Haskell.TH hiding (Loc) import Language.Haskell.TH.LiftInstances () -- | Interface, describes the input or output ports of the system. -- Each entry contains the name of the port and its type. type Iface = [(PortId, TypeRep)] -- | We add a phantom parameter to indicate the type of the system newtype SysDef a = SysDef {unSysDef :: PrimSysDef} -- | The Primitive System Definition. -- Instead of just providing the value, a reference is provided -- to allow sharing between instances. newtype PrimSysDef = PrimSysDef {unPrimSysDef :: URef SysDefVal} -- | Indicates wether a system is combinational or sequential -- In practice, a system is sequential if if contains a delay process or -- a sequential system instance, otherwise its combinational. data SysLogic = Combinational | Sequential deriving (Eq, Show) -- | The System Definition value data SysDefVal = SysDefVal {sid :: SysId, -- ^ Identifier of the System netlist :: Netlist [], -- ^ System netlist subSys :: [PrimSysDef], -- ^ List of all unique nested subsystems -- (i.e. flattened tree, without duplicates, -- of all the systems in lower levels of the -- hierarchy) logic :: SysLogic, -- ^ 'SysLogic' of the system iIface :: Iface, -- ^ Input interface oIface :: Iface, -- ^ Output interface loc :: Maybe Loc} -- ^ Location of the call to newSysDef -- which created this System definition -- (used for later error reporting) -- It will initialized or not depending -- on the newSysDef* function used -- | 'SysDef' constructor -- -- Builds a system definition out of a system function describing the system -- and its port identifers. newSysDef :: SysFun f => f -- ^ system function -> SysId -- ^ System identifier -> [PortId] -- ^ Input interface port identifiers -> [PortId] -- ^ Output interface port identifiers -> SysDef f newSysDef f sysId inIds outIds = either currError id eProneResult where currError = uError "newSysDef" eProneResult = newSysDefEProne f Nothing sysId inIds outIds -- | CURRENTLY BROKEN, do not use! -- -- 'SysDef' constructor, Template Haskell version -- -- Builds a system definition out of a system function, a system identifiers -- and its port identifers. -- -- For example @$(newSysDefTH mySysFun \"mysys\" [\"in1\"] [\"out1\"])@ creates a -- system definition from system funcion @mySysFun@ which should have -- one input and output signals. -- -- The advantage of 'newSysDefTH' over 'newSysDef' is that it -- reports errors (e.g duplicated port and process identifiers) earlier, -- at host-language (Haskell) compile-time. -- -- In addition, due to the use of Template Haskell, 'newSysDefTH' is -- aware of the source location at which it was called, making -- further error reports friendlier to the user. newSysDefTH :: SysFun f => f -- ^ system function -> SysId -- ^ System identifier -> [PortId] -- ^ Input interface port identifiers -> [PortId] -- ^ Output interface port identifiers -> ExpQ newSysDefTH f sysId inIds outIds = case eProneResult of Left err -> currError err -- unfortunately SysDef can't easily be an instance of Lift -- due to the unsafe, unmutable references used in observable sharing -- Right sysDef -> [| sysDef |] Right _ -> intError "newSysDefTH" (Other "Unimplemented") -- FIXME: Fix this function or remove (updating the documentation of -- newSysDef* in the latter case). {- Right _ -> do loc <- currentModule [| let iIds = inIds oIds = outIds (nlist, inTypes, outTypes) = applySysFun f iIds in SysDef $ PrimSysDef $ newURef $ SysDefVal sysId (Netlist nlist) (zip iIds inTypes) (zip oIds outTypes) (Just loc) |] -} where currError = qError "newSysDefTH" eProneResult = newSysDefEProne f Nothing sysId inIds outIds -- | 'SysDef' constructor, Template Haskell 'Name' version -- -- Builds a 'SysDef' out of the name of a system function -- and its port identifers. -- -- The system will later be identified by the basename -- (i.e. unqualified name) of the function. -- -- For example @$(newSysDefTHName \'mySysFun [\"in1\"] [\"out1\"])@ creates a -- system definition from system funcion @mySysFun@ which has one input and -- output signals. -- -- The advantage of 'newSysDefTHName' over 'newSysDefTH' is that it -- doesn't suffer from the Template Haskell bug , or in other words, it allows to declare the system -- defintion and system function in the same module. -- -- However, since it doesn't have acces to the system function itself, -- it can only give early error reports related to incorrect port identifiers -- (process identifier duplicate errors will be reported at runtime). newSysDefTHName :: Name -- ^ Name of the system function -> [PortId] -- ^ Input interface port identifiers -> [PortId] -- ^ Output interface port identifiers -> ExpQ newSysDefTHName sysFName inIds outIds = do sysFInfo <- reify sysFName -- Check that a function name was provided sysFType <- case sysFInfo of VarI _ t _ _ -> return t _ -> currError (NonVarName sysFName) -- Check that the function complies with the expected type -- and extract the port types ((inTypes,inN),(outTypes, outN)) <- recover (currError $ IncomSysF sysFName sysFType) (checkSysFType sysFType) -- Check the ports let portCheck = checkSysDefPorts (show sysFName) (inIds, inN) (outIds, outN) when (isJust portCheck) (currError (fromJust portCheck)) -- Build the system definition loc <- location let errInfo = loc_module loc -- Input arguments passed to the system function -- in order to get the netlist inArgs = [ [| Signal $ newInPort $(litE $ stringL id) |] | id <- inIds ] -- The system definition without type signature for the -- phantom parameter untypedSysDef = -- The huge let part of this quasiquote is not -- really necesary but generates clearer code [|let -- Generate the system netlist toList = $(signalTup2List outN) outNlSignals = toList $ $(appsE $ varE sysFName : inArgs) -- Rest of the system defintion inIface = $(genIface inIds inTypes) outIface = $(genIface outIds outTypes) errorInfo = errInfo nlist = Netlist outNlSignals (subSys,logic) = either (intError currFun) id (checkSysDef nlist) in SysDef $ PrimSysDef $ newURef $ SysDefVal (nameBase sysFName) nlist subSys logic inIface outIface (Just errorInfo) |] -- We are done, we simply specify the concrete type of the SysDef sigE untypedSysDef (return $ ConT ''SysDef `AppT` sysFType) where currError = qError currFun currFun = "newSysDef" ---------------------------- -- Internal Helper Functions ---------------------------- -- | Error prone version of 'newSysDef' newSysDefEProne :: SysFun f => f -- ^ system function -> Maybe Loc -- ^ Location where the originating -- call took place (if available) -> SysId -- ^ System function -> [PortId] -- ^ Input interface port identifiers -> [PortId] -- ^ Output interface port identifiers -> EProne (SysDef f) newSysDefEProne f mLoc sysId inIds outIds -- check the ports for problems | isJust portCheck = throwError (fromJust portCheck) | otherwise = do let nl = Netlist nlist (subSys, logic) <- checkSysDef nl return (SysDef $ PrimSysDef $ newURef $ SysDefVal sysId nl subSys logic (zip inIds inTypes) (zip outIds outTypes) mLoc) where (nlist, inTypes, outTypes) = applySysFun f inIds inN = length inIds outN = length outIds portCheck = checkSysDefPorts sysId (inIds, inN) (outIds, outN) -- | Check that the system definition ports match certain lengths and -- don't containt duplicates checkSysDefPorts :: SysId -- ^ System currently being checked -> ([PortId], Int) -- ^ input ports and expected length -> ([PortId], Int) -- ^ output ports and expected length -> Maybe ForSyDeErr checkSysDefPorts sysId (inIds, inN) (outIds, outN) | inN /= inIdsL = Just $ InIfaceLength (sysId, inN) (inIds, inIdsL) | outN /= outIdsL = Just $ OutIfaceLength (sysId, outN) (outIds, outIdsL) | isJust (maybeDup) = Just $ MultPortId (fromJust maybeDup) | otherwise = Nothing where inIdsL = length inIds outIdsL = length outIds maybeDup = findDup (inIds ++ outIds) -- | In order to check the system for identifier duplicates we keep track -- of the process identifiers and of the accumulated subsytem definitions data CheckState = CheckState {accumSubSys :: [PrimSysDef], accumProcIds :: [ProcId] , accumLogic :: SysLogic } -- Monad used to traverse the system in order to check that there are no -- duplicates type CheckSysM st a = TravSEST CheckState ForSyDeErr st a -- | Check that the system netlist does not contain process identifier -- duplicates (i.e. different processes with the same process -- identifier) or instances of different systems with the same identifier. -- In case there are no duplicates, the list of nested subsystems together -- with the the logic is returned. checkSysDef :: Netlist [] -> EProne ([PrimSysDef], SysLogic) checkSysDef nl = do endSt <- runST (runErrorT (execStateT (traverseSEST newCheckSys defineCheckSys nl) initState)) let finalSubSys = accumSubSys endSt -- we already checked all the delay processes of the system -- but the system can still be sequential if any of the subsystems is -- sequential finalLogic = if (accumLogic endSt == Sequential) || (any (\s -> (logic.readURef.unPrimSysDef) s == Sequential) finalSubSys) then Sequential else Combinational return (finalSubSys, finalLogic) where initState = CheckState [] [] Combinational defineCheckSys :: [(NlNodeOut, ())] -> NlNode () -> CheckSysM st () defineCheckSys _ _= return () newCheckSys :: NlNode NlSignal -> CheckSysM st [(NlNodeOut, ())] newCheckSys node = do st <- get let acIds = accumProcIds st acSys = accumSubSys st acLog = accumLogic st -- check the process Id of current node for duplicates acIds' <- case node of -- input ports don't count as process identifiers InPort _ -> return acIds Proc pid _ -> if pid `elem` acIds then throwError $ MultProcId pid else return (pid:acIds) -- If the node is a system instance, check that -- the system and all its subsytems are either: -- * already in the accumulated systems -- * not in the accumulated systems, but have a different system -- identifiers -- FIXME: in order to avoid making so many comparisons, it -- would probably be more efficient to also mark which -- subsystems belong to the first hierarchy level in -- SysVal (i.e. creating a tree-structure as a reult). -- Then, if the system to compare (psys) matches a -- root in the accumulated subsystems there would be -- no need to continue comparing the childs of psys. acSys' <- case node of Proc _ (SysIns pSys _) -> liftEither $ mergeSysIds (pSys:(subSys.readURef.unPrimSysDef) pSys) acSys _ -> return acSys let acLog' = case node of Proc _ (DelaySY _ _) -> Sequential _ -> acLog put $ CheckState acSys' acIds' acLog' -- return a phony value for each output of the node return $ map (\tag -> (tag,())) (outTags node) where mergeSysIds :: [PrimSysDef] -> [PrimSysDef] -> EProne [PrimSysDef] mergeSysIds xs [] = return xs mergeSysIds [] xs = return xs mergeSysIds (x:xs) ys = do shouldAdd <- addSysId x ys if shouldAdd then do rest <- mergeSysIds xs ys return (x:rest) else mergeSysIds xs ys -- should we add the Id to the accumulated ones? addSysId :: PrimSysDef -> [PrimSysDef] -> EProne Bool addSysId _ [] = return True addSysId psdef (x:xs) -- Both systems are equal | unx == unpsdef = return False -- Both systems are different, but their ids -- are equal | sdefid == sid xval = throwError (SubSysIdClash sdefid (loc sdefval) (loc xval)) | otherwise = addSysId psdef xs where unpsdef = unPrimSysDef psdef unx = unPrimSysDef x xval = readURef unx sdefval = readURef unpsdef sdefid = sid sdefval -- | Generate a lambda expression to transform a tuple of N 'Signal's into a -- a list of 'NlSignal's signalTup2List :: Int -- ^ size of the tuple -> ExpQ signalTup2List n = do -- Generate N signal variable paterns and -- variable expressions refering to the same names names <- replicateM n (newName "i") let tupPat = tupP [conP 'Signal [varP n] | n <- names] listExp = listE [varE n | n <- names] lamE [tupPat] listExp -- | Find a duplicate in a list findDup :: Eq a => [a] -> Maybe a findDup [] = Nothing findDup [_] = Nothing findDup (x:xs) | elem x xs = Just x | otherwise = findDup xs -- | Generate a TypeRep expression given a Template Haskell Type -- note that the use of typeOf cannot lead to errors since all the signal -- types in a system function are guaranteed to be Typeable by construction type2TypeRep :: Type -> ExpQ type2TypeRep t = [| typeOf $(sigE [| undefined |] (return t) ) |] -- | Generate an interface given its identifiers and Template Haskell Types genIface :: [PortId] -> [Type] -> ExpQ genIface [] _ = listE [] genIface _ [] = listE [] genIface (i:ix) (t:tx) = do ListE rest <- genIface ix tx tupExp <- tupE [[| i |], type2TypeRep t] return (ListE (tupExp:rest))