clash-lib-0.99: src/Clash/Driver.hs
{-|
Copyright : (C) 2012-2016, University of Twente,
2016-2017, Myrtle Software Ltd,
2017 , QBayLogic, Google Inc.
License : BSD2 (see the file LICENSE)
Maintainer : Christiaan Baaij <christiaan.baaij@gmail.com>
Module that connects all the parts of the Clash compiler library
-}
{-# LANGUAGE NondecreasingIndentation #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TupleSections #-}
module Clash.Driver where
import qualified Control.Concurrent.Supply as Supply
import Control.DeepSeq
import Control.Exception (tryJust, bracket)
import Control.Lens ((^.), _5)
import Control.Monad (guard, when, unless)
import Control.Monad.State (evalState, get)
import Data.Hashable (hash)
import qualified Data.HashMap.Lazy as HML
import Data.HashMap.Strict (HashMap)
import qualified Data.HashMap.Strict as HM
import qualified Data.HashSet as HashSet
import Data.IntMap (IntMap)
import Data.Maybe (fromMaybe)
import Data.Semigroup.Monad
import Data.Text.Lazy (Text)
import qualified Data.Text.Lazy as Text
import qualified Data.Text.Lazy.IO as Text
import Data.Text.Prettyprint.Doc (pretty)
import Data.Text.Prettyprint.Doc.Extra
(Doc, LayoutOptions (..), PageWidth (..) , layoutPretty, renderLazy,
renderOneLine)
import qualified Data.Time.Clock as Clock
import qualified System.Directory as Directory
import System.FilePath ((</>), (<.>))
import qualified System.FilePath as FilePath
import qualified System.IO as IO
import System.IO.Error (isDoesNotExistError)
import qualified Text.PrettyPrint.ANSI.Leijen as ANSI
import Text.Trifecta.Result
import Text.Read (readMaybe)
import GHC.BasicTypes.Extra ()
import Clash.Annotations.TopEntity (TopEntity (..))
import Clash.Annotations.TopEntity.Extra ()
import Clash.Backend
import Clash.Core.Evaluator (PrimEvaluator)
import Clash.Core.Name (Name (..), name2String)
import Clash.Core.Term (Term, TmName, TmOccName)
import Clash.Core.Type (Type)
import Clash.Core.TyCon (TyCon, TyConName, TyConOccName)
import Clash.Driver.Types
import Clash.Netlist (genNetlist)
import Clash.Netlist.Util (genComponentName)
import Clash.Netlist.BlackBox.Parser (runParse)
import Clash.Netlist.BlackBox.Types (BlackBoxTemplate)
import Clash.Netlist.Types (Component (..), HWType)
import Clash.Normalize (checkNonRecursive, cleanupGraph,
normalize, runNormalization)
import Clash.Normalize.Util (callGraph)
import Clash.Primitives.Types
import Clash.Util (first, second)
-- | Create a set of target HDL files for a set of functions
generateHDL
:: forall backend . Backend backend
=> BindingMap
-- ^ Set of functions
-> Maybe backend
-> PrimMap (Text.Text)
-- ^ Primitive / BlackBox Definitions
-> HashMap TyConOccName TyCon
-- ^ TyCon cache
-> IntMap TyConName
-- ^ Tuple TyCon cache
-> (HashMap TyConOccName TyCon -> Bool -> Type -> Maybe (Either String HWType))
-- ^ Hardcoded 'Type' -> 'HWType' translator
-> PrimEvaluator
-- ^ Hardcoded evaluator (delta-reduction)
-> [( TmName
, Type
, Maybe TopEntity
, Maybe TmName
)]
-- ^ topEntity bndr
-- + (maybe) TopEntity annotation
-- + (maybe) testBench bndr
-> ClashOpts
-- ^ Debug information level for the normalization process
-> (Clock.UTCTime,Clock.UTCTime)
-> IO ()
generateHDL bindingsMap hdlState primMap tcm tupTcm typeTrans eval topEntities
opts (startTime,prepTime) = go prepTime [] topEntities where
primMap' = HM.map parsePrimitive primMap
-- No more TopEntities to process
go prevTime _ [] = putStrLn $ "Total compilation took " ++
show (Clock.diffUTCTime prevTime startTime)
-- Process the next TopEntity
go prevTime seen ((topEntity,_,annM,benchM):topEntities') = do
putStrLn $ "Compiling: " ++ name2String topEntity
-- Some initial setup
let modName = maybe (takeWhile (/= '.') (name2String topEntity)) t_name annM
iw = opt_intWidth opts
hdlsyn = opt_hdlSyn opts
hdlState' = setModName modName
$ fromMaybe (initBackend iw hdlsyn :: backend) hdlState
hdlDir = fromMaybe "." (opt_hdlDir opts) </>
Clash.Backend.name hdlState' </>
takeWhile (/= '.') (name2String topEntity)
mkId = evalState mkIdentifier hdlState'
extId = evalState extendIdentifier hdlState'
topName = genComponentName [] mkId topEntity
topNm = maybe topName
(Text.pack . t_name)
annM
unless (opt_cachehdl opts) $ putStrLn "Ignoring .manifest files"
-- Calculate the hash over the callgraph and the topEntity annotation
(sameTopHash,sameBenchHash,manifest) <- do
let topHash = hash (annM,callGraphBindings bindingsMap (nameOcc topEntity))
benchHashM = fmap (hash . (annM,) . callGraphBindings bindingsMap . nameOcc) benchM
manifestI = Manifest (topHash,benchHashM) [] [] [] [] []
manFile = maybe (hdlDir </> Text.unpack topNm <.> "manifest")
(\ann -> hdlDir </> t_name ann </> t_name ann <.> "manifest")
annM
manM <- if not (opt_cachehdl opts)
then return Nothing -- ignore manifest file because -fclash-nocache
else (>>= readMaybe) . either (const Nothing) Just <$>
tryJust (guard . isDoesNotExistError) (readFile manFile)
return (maybe (False,False,manifestI)
(\man -> (fst (manifestHash man) == topHash
,snd (manifestHash man) == benchHashM
,man {manifestHash = (topHash,benchHashM)}
))
manM)
(supplyN,supplyTB) <- Supply.splitSupply
. snd
. Supply.freshId
<$> Supply.newSupply
let topEntityNames = map (\(x,_,_,_) -> nameOcc x) topEntities
(topTime,manifest',seen') <- if sameTopHash
then do
putStrLn ("Using cached result for: " ++ name2String topEntity)
topTime <- Clock.getCurrentTime
return (topTime,manifest,componentNames manifest ++ seen)
else do
-- 1. Normalise topEntity
let transformedBindings = normalizeEntity bindingsMap primMap' tcm tupTcm
typeTrans eval topEntityNames opts supplyN
(nameOcc topEntity)
normTime <- transformedBindings `deepseq` Clock.getCurrentTime
let prepNormDiff = Clock.diffUTCTime normTime prevTime
putStrLn $ "Normalisation took " ++ show prepNormDiff
-- 2. Generate netlist for topEntity
(netlist,dfiles,seen') <- genNetlist transformedBindings topEntities primMap'
tcm typeTrans [] iw mkId extId seen
hdlDir (nameOcc topEntity)
netlistTime <- netlist `deepseq` Clock.getCurrentTime
let normNetDiff = Clock.diffUTCTime netlistTime normTime
putStrLn $ "Netlist generation took " ++ show normNetDiff
-- 3. Generate topEntity wrapper
let topComponent = snd . head $
filter (\(_,Component cName _ _ _) -> maybe
(Text.isSuffixOf (genComponentName [] mkId topEntity))
(\te n -> n == Text.pack (t_name te)) annM
cName)
netlist
(hdlDocs,manifest') = createHDL hdlState' modName netlist topComponent
(Text.unpack topNm, Right manifest)
dir = hdlDir </> maybe "" t_name annM
prepareDir (opt_cleanhdl opts) (extension hdlState') dir
mapM_ (writeHDL dir) hdlDocs
copyDataFiles (opt_importPaths opts) dir dfiles
topTime <- hdlDocs `seq` Clock.getCurrentTime
return (topTime,manifest',seen')
benchTime <- case benchM of
Just tb | not sameBenchHash -> do
putStrLn $ "Compiling: " ++ name2String tb
let modName' = Text.unpack (genComponentName [] mkId tb)
hdlState2 = setModName modName' hdlState'
-- 1. Normalise testBench
let transformedBindings = normalizeEntity bindingsMap primMap' tcm tupTcm
typeTrans eval topEntityNames opts supplyTB (nameOcc tb)
normTime <- transformedBindings `deepseq` Clock.getCurrentTime
let prepNormDiff = Clock.diffUTCTime normTime topTime
putStrLn $ "Testbench normalisation took " ++ show prepNormDiff
-- 2. Generate netlist for topEntity
(netlist,dfiles,_) <- genNetlist transformedBindings topEntities primMap'
tcm typeTrans [] iw mkId extId seen'
hdlDir (nameOcc tb)
netlistTime <- netlist `deepseq` Clock.getCurrentTime
let normNetDiff = Clock.diffUTCTime netlistTime normTime
putStrLn $ "Testbench netlist generation took " ++ show normNetDiff
-- 3. Write HDL
let (hdlDocs,_) = createHDL hdlState2 modName' netlist undefined
(Text.unpack topNm, Left manifest')
dir = hdlDir </> maybe "" t_name annM </> modName'
prepareDir (opt_cleanhdl opts) (extension hdlState2) dir
writeHDL (hdlDir </> maybe "" t_name annM) (head hdlDocs)
mapM_ (writeHDL dir) (tail hdlDocs)
copyDataFiles (opt_importPaths opts) dir dfiles
hdlDocs `seq` Clock.getCurrentTime
Just tb -> do
let tb' = name2String tb
putStrLn ("Compiling: " ++ tb')
putStrLn ("Using cached result for: " ++ tb')
return topTime
Nothing -> return topTime
go benchTime seen' topEntities'
parsePrimitive :: Primitive Text -> Primitive BlackBoxTemplate
parsePrimitive (BlackBox pNm oReg libM imps inc templT) =
case either (fmap Left . runParse) (fmap Right . runParse) templT of
Failure errInfo
-> error (ANSI.displayS (ANSI.renderCompact (_errDoc errInfo)) "")
Success templ
-> BlackBox pNm oReg (map parseBB libM) (map parseBB imps) inc' templ
where
inc' = case fmap (second runParse) inc of
Just (x,Success t) -> Just (x,t)
_ -> Nothing
parseBB :: Text -> BlackBoxTemplate
parseBB t = case runParse t of
Failure errInfo
-> error (ANSI.displayS (ANSI.renderCompact (_errDoc errInfo)) "")
Success templ
-> templ
parsePrimitive (Primitive pNm typ) = Primitive pNm typ
-- | Pretty print Components to HDL Documents
createHDL
:: Backend backend
=> backend
-- ^ Backend
-> String
-- ^ Module hierarchy root
-> [(SrcSpan,Component)]
-- ^ List of components
-> Component
-- ^ Top component
-> (String, Either Manifest Manifest)
-- ^ Name of the manifest file
-- + Either:
-- * Left manifest: Only write/update the hashes of the @manifest@
-- * Right manifest: Update all fields of the @manifest@
-> ([(String,Doc)],Manifest)
-- ^ The pretty-printed HDL documents
-- + The update manifest file
createHDL backend modName components top (topName,manifestE) = flip evalState backend $ getMon $ do
(hdlNmDocs,incs) <- unzip <$> mapM (uncurry (genHDL modName)) components
hwtys <- HashSet.toList <$> extractTypes <$> Mon get
typesPkg <- mkTyPackage modName hwtys
let hdl = map (first (<.> Clash.Backend.extension backend)) (typesPkg ++ hdlNmDocs)
qincs = concat incs
topFiles = hdl ++ qincs
manifest <- either return (\m -> do
let topName' = Text.pack topName
let topInNames = map fst (inputs top)
topInTypes <- mapM (fmap renderOneLine . hdlType (External topName') . snd) (inputs top)
let topOutNames = map (fst . snd) (outputs top)
topOutTypes <- mapM (fmap renderOneLine . hdlType (External topName') . snd . snd) (outputs top)
let compNames = map (componentName.snd) components
return (m { portInNames = topInNames
, portInTypes = topInTypes
, portOutNames = topOutNames
, portOutTypes = topOutTypes
, componentNames = compNames
})
) manifestE
let manDoc = ( topName <.> "manifest"
, pretty (Text.pack (show manifest)))
return (manDoc:topFiles,manifest)
-- | Prepares the directory for writing HDL files. This means creating the
-- dir if it does not exist and removing all existing .hdl files from it.
prepareDir :: Bool -- ^ Remove existing HDL files
-> String -- ^ File extension of the HDL files.
-> String
-> IO ()
prepareDir cleanhdl ext dir = do
-- Create the dir if needed
Directory.createDirectoryIfMissing True dir
-- Clean the directory when needed
when cleanhdl $ do
-- Find all HDL files in the directory
files <- Directory.getDirectoryContents dir
let to_remove = filter ((==ext) . FilePath.takeExtension) files
-- Prepend the dirname to the filenames
let abs_to_remove = map (FilePath.combine dir) to_remove
-- Remove the files
mapM_ Directory.removeFile abs_to_remove
-- | Writes a HDL file to the given directory
writeHDL :: FilePath -> (String, Doc) -> IO ()
writeHDL dir (cname, hdl) = do
let rendered = renderLazy (layoutPretty (LayoutOptions (AvailablePerLine 120 0.4)) hdl)
-- remove blank lines to keep things clean
clean = Text.unlines
. map (\t -> if Text.all (==' ') t then Text.empty else t)
. Text.lines
bracket (IO.openFile (dir </> cname) IO.WriteMode) IO.hClose $ \h -> do
Text.hPutStr h (clean rendered)
Text.hPutStr h (Text.pack "\n")
copyDataFiles :: [FilePath] -> FilePath -> [(String,FilePath)] -> IO ()
copyDataFiles idirs dir = mapM_ (copyFile' idirs)
where
copyFile' dirs (nm,old) = do
oldExists <- Directory.doesFileExist old
if oldExists
then Directory.copyFile old new
else goImports dirs
where
new = dir FilePath.</> nm
goImports [] = do
oldExists <- Directory.doesFileExist old
if oldExists
then Directory.copyFile old new
else unless (null old) (putStrLn ("WARNING: file " ++ show old ++ " does not exist"))
goImports (d:ds) = do
let old2 = d FilePath.</> old
old2Exists <- Directory.doesFileExist old2
if old2Exists
then Directory.copyFile old2 new
else goImports ds
-- | Get all the terms corresponding to a call graph
callGraphBindings
:: BindingMap
-- ^ All bindings
-> TmOccName
-- ^ Root of the call graph
-> [Term]
callGraphBindings bindingsMap tm =
map ((^. _5) . (bindingsMap HM.!)) (HM.keys cg)
where
cg = callGraph bindingsMap tm
-- | Normalize a complete hierarchy
normalizeEntity
:: BindingMap
-- ^ All bindings
-> PrimMap BlackBoxTemplate
-- ^ BlackBox HDL templates
-> HashMap TyConOccName TyCon
-- ^ TyCon cache
-> IntMap TyConName
-- ^ Tuple TyCon cache
-> (HashMap TyConOccName TyCon -> Bool -> Type -> Maybe (Either String HWType))
-- ^ Hardcoded 'Type' -> 'HWType' translator
-> PrimEvaluator
-- ^ Hardcoded evaluator (delta-reduction)
-> [TmOccName]
-- ^ TopEntities
-> ClashOpts
-- ^ Debug information level for the normalization process
-> Supply.Supply
-- ^ Unique supply
-> TmOccName
-- ^ root of the hierarchy
-> BindingMap
normalizeEntity bindingsMap primMap tcm tupTcm typeTrans eval topEntities
opts supply tm = transformedBindings
where
doNorm = do norm <- normalize [tm]
let normChecked = checkNonRecursive norm
cleanupGraph tm normChecked
transformedBindings = runNormalization opts supply bindingsMap
typeTrans tcm tupTcm eval primMap HML.empty
topEntities doNorm