verismith-1.1.0: src/Verismith/Tool/Internal.hs
{-# LANGUAGE DeriveFunctor #-}
-- |
-- Module : Verismith.Tool.Internal
-- Description : Class of the simulator.
-- Copyright : (c) 2018-2019, Yann Herklotz
-- License : GPL-3
-- Maintainer : yann [at] yannherklotz [dot] com
-- Stability : experimental
-- Portability : POSIX
--
-- Class of the simulator and the synthesize tool.
module Verismith.Tool.Internal
( ResultSh,
resultSh,
Tool (..),
Simulator (..),
Synthesiser (..),
Failed (..),
renameSource,
checkPresent,
checkPresentModules,
replace,
replaceMods,
rootPath,
timeout,
timeout_,
bsToI,
noPrint,
logger,
logCommand,
logCommand_,
execute,
execute_,
(<?>),
annotate,
)
where
import Control.Lens
import Control.Monad (forM, void)
import Control.Monad.Catch (throwM)
import Data.Bits (shiftL)
import Data.ByteString (ByteString)
import qualified Data.ByteString as B
import Data.Maybe (catMaybes)
import Data.Text (Text)
import qualified Data.Text as T
import Data.Time.Format (defaultTimeLocale, formatTime)
import Data.Time.LocalTime (getZonedTime)
import Shelly
import Shelly.Lifted (MonadSh, liftSh)
import System.FilePath.Posix (takeBaseName)
import Verismith.CounterEg (CounterEg)
import Verismith.Result
import Verismith.Utils
import Verismith.Verilog.AST
import Prelude hiding (FilePath)
-- | Tool class.
class Tool a where
toText :: a -> Text
-- | Simulation type class.
class (Tool a) => Simulator a where
runSim ::
(Show ann) =>
-- | Simulator instance
a ->
-- | Run information
SourceInfo ann ->
-- | Inputs to simulate
[ByteString] ->
-- | Returns the value of the hash at the output of the testbench.
ResultSh ByteString
runSimWithFile ::
a ->
FilePath ->
[ByteString] ->
ResultSh ByteString
data Failed
= EmptyFail
| EquivFail (Maybe CounterEg)
| EquivError
| SimFail ByteString
| SynthFail
| TimeoutError
deriving (Eq)
instance Show Failed where
show EmptyFail = "EmptyFail"
show (EquivFail _) = "EquivFail"
show EquivError = "EquivError"
show (SimFail bs) = "SimFail " <> T.unpack (T.take 10 $ showBS bs)
show SynthFail = "SynthFail"
show TimeoutError = "TimeoutError"
instance Semigroup Failed where
EmptyFail <> a = a
b <> _ = b
instance Monoid Failed where
mempty = EmptyFail
-- | Synthesiser type class.
class (Tool a) => Synthesiser a where
runSynth ::
(Show ann) =>
-- | Synthesiser tool instance
a ->
-- | Run information
SourceInfo ann ->
-- | does not return any values
ResultSh ()
synthOutput :: a -> FilePath
setSynthOutput :: a -> FilePath -> a
renameSource :: (Synthesiser a) => a -> SourceInfo ann -> SourceInfo ann
renameSource a src =
src & infoSrc . _Wrapped . traverse . modId . _Wrapped %~ (<> toText a)
-- | Type synonym for a 'ResultT' that will be used throughout 'Verismith'. This
-- has instances for 'MonadSh' and 'MonadIO' if the 'Monad' it is parametrised
-- with also has those instances.
type ResultSh = ResultT Failed Sh
resultSh :: ResultSh a -> Sh a
resultSh s = do
result <- runResultT s
case result of
Fail e -> throwM . RunFailed "" [] 1 $ showT e
Pass s' -> return s'
checkPresent :: FilePath -> Text -> Sh (Maybe Text)
checkPresent fp t = do
errExit False $ run_ "grep" [t, toTextIgnore fp]
i <- lastExitCode
if i == 0 then return $ Just t else return Nothing
-- | Checks what modules are present in the synthesised output, as some modules
-- may have been inlined. This could be improved if the parser worked properly.
checkPresentModules :: FilePath -> SourceInfo ann -> Sh [Text]
checkPresentModules fp (SourceInfo _ src) = do
vals <-
forM (src ^.. _Wrapped . traverse . modId . _Wrapped) $
checkPresent fp
return $ catMaybes vals
-- | Uses sed to replace a string in a text file.
replace :: FilePath -> Text -> Text -> Sh ()
replace fp t1 t2 = do
errExit False . noPrint $
run_
"sed"
["-i", "s/" <> t1 <> "/" <> t2 <> "/g", toTextIgnore fp]
-- | This is used because rename only renames the definitions of modules of
-- course, so instead this just searches and replaces all the module names. This
-- should find all the instantiations and definitions. This could again be made
-- much simpler if the parser works.
replaceMods :: FilePath -> Text -> SourceInfo ann -> Sh ()
replaceMods fp t (SourceInfo _ src) =
void
. forM (src ^.. _Wrapped . traverse . modId . _Wrapped)
$ (\a -> replace fp a (a <> t))
rootPath :: Sh FilePath
rootPath = do
current <- pwd
maybe current fromText <$> get_env "VERISMITH_ROOT"
timeout :: FilePath -> [Text] -> Sh Text
timeout = command1 "timeout" ["300"] . toTextIgnore
{-# INLINE timeout #-}
timeout_ :: FilePath -> [Text] -> Sh ()
timeout_ = command1_ "timeout" ["300"] . toTextIgnore
{-# INLINE timeout_ #-}
-- | Helper function to convert bytestrings to integers
bsToI :: ByteString -> Integer
bsToI = B.foldl' (\i b -> (i `shiftL` 8) + fromIntegral b) 0
{-# INLINE bsToI #-}
noPrint :: Sh a -> Sh a
noPrint = print_stdout False . print_stderr False
{-# INLINE noPrint #-}
logger :: Text -> Sh ()
logger t = do
fn <- pwd
currentTime <- liftIO getZonedTime
echo $
"Verismith "
<> T.pack (formatTime defaultTimeLocale "%H:%M:%S " currentTime)
<> bname fn
<> " - "
<> t
where
bname = T.pack . takeBaseName . T.unpack . toTextIgnore
logCommand :: FilePath -> Text -> Sh a -> Sh a
logCommand fp name =
log_stderr_with (l "_stderr.log")
. log_stdout_with (l ".log")
where
l s t = appendFile (file s) (T.unpack t) >> appendFile (file s) "\n"
file s = T.unpack (toTextIgnore $ fp </> fromText name) <> s
logCommand_ :: FilePath -> Text -> Sh a -> Sh ()
logCommand_ fp name = void . logCommand fp name
execute ::
(MonadSh m, Monad m) =>
Failed ->
FilePath ->
Text ->
FilePath ->
[Text] ->
ResultT Failed m Text
execute f dir name e cs = do
(res, exitCode) <- liftSh $ do
res <- errExit False . logCommand dir name $ timeout e cs
(,) res <$> lastExitCode
case exitCode of
0 -> ResultT . return $ Pass res
124 -> ResultT . return $ Fail TimeoutError
_ -> ResultT . return $ Fail f
execute_ ::
(MonadSh m, Monad m) =>
Failed ->
FilePath ->
Text ->
FilePath ->
[Text] ->
ResultT Failed m ()
execute_ a b c d = void . execute a b c d