module Verismith.OptParser ( OptTool (..) , Opts (..) , opts ) where import Control.Applicative ((<|>)) import Data.Text (Text) import qualified Data.Text as T import Options.Applicative (Mod (..), OptionFields (..), Parser (..), ParserInfo (..), ReadM (..), (<**>)) import qualified Options.Applicative as Opt import Prelude hiding (FilePath (..)) import Shelly (FilePath (..), fromText) import Verismith.Config (SynthDescription (..), versionInfo) data OptTool = TYosys | TXST | TIcarus instance Show OptTool where show TYosys = "yosys" show TXST = "xst" show TIcarus = "icarus" data Opts = Fuzz { fuzzOutput :: Text , fuzzConfigFile :: !(Maybe FilePath) , fuzzForced :: !Bool , fuzzKeepAll :: !Bool , fuzzNum :: {-# UNPACK #-} !Int , fuzzNoSim :: !Bool , fuzzNoEquiv :: !Bool , fuzzNoReduction :: !Bool , fuzzExistingFile :: !(Maybe FilePath) , fuzzExistingFileTop :: !Text , fuzzCrossCheck :: !Bool , fuzzChecker :: !(Maybe Text) } | Generate { generateFilename :: !(Maybe FilePath) , generateConfigFile :: !(Maybe FilePath) } | Parse { parseFilename :: !FilePath , parseTop :: !Text , parseOutput :: !(Maybe FilePath) , parseRemoveConstInConcat :: !Bool } | Reduce { reduceFilename :: !FilePath , reduceTop :: !Text , reduceScript :: !(Maybe FilePath) , reduceSynthesiserDesc :: ![SynthDescription] , reduceRerun :: !Bool } | ConfigOpt { configOptWriteConfig :: !(Maybe FilePath) , configOptConfigFile :: !(Maybe FilePath) , configOptDoRandomise :: !Bool } textOption :: Mod OptionFields String -> Parser Text textOption = fmap T.pack . Opt.strOption optReader :: (String -> Maybe a) -> ReadM a optReader f = Opt.eitherReader $ \arg -> case f arg of Just a -> Right a Nothing -> Left $ "Cannot parse option: " <> arg parseSynth :: String -> Maybe OptTool parseSynth val | val == "yosys" = Just TYosys | val == "xst" = Just TXST | otherwise = Nothing parseSynthDesc :: String -> Maybe SynthDescription parseSynthDesc val | val == "yosys" = Just $ SynthDescription "yosys" Nothing Nothing Nothing | val == "vivado" = Just $ SynthDescription "vivado" Nothing Nothing Nothing | val == "xst" = Just $ SynthDescription "xst" Nothing Nothing Nothing | val == "quartus" = Just $ SynthDescription "quartus" Nothing Nothing Nothing | val == "identity" = Just $ SynthDescription "identity" Nothing Nothing Nothing | otherwise = Nothing parseSim :: String -> Maybe OptTool parseSim val | val == "icarus" = Just TIcarus | otherwise = Nothing fuzzOpts :: Parser Opts fuzzOpts = Fuzz <$> textOption ( Opt.long "output" <> Opt.short 'o' <> Opt.metavar "DIR" <> Opt.help "Output directory that the fuzz run takes place in." <> Opt.showDefault <> Opt.value "output") <*> ( Opt.optional . Opt.strOption $ Opt.long "config" <> Opt.short 'c' <> Opt.metavar "FILE" <> Opt.help "Config file for the current fuzz run.") <*> (Opt.switch $ Opt.long "force" <> Opt.short 'f' <> Opt.help "Overwrite the specified directory.") <*> (Opt.switch $ Opt.long "keep" <> Opt.short 'k' <> Opt.help "Keep all the directories.") <*> ( Opt.option Opt.auto $ Opt.long "num" <> Opt.short 'n' <> Opt.help "The number of fuzz runs that should be performed." <> Opt.showDefault <> Opt.value 1 <> Opt.metavar "INT") <*> (Opt.switch $ Opt.long "no-sim" <> Opt.help "Do not run simulation on the output netlist.") <*> (Opt.switch $ Opt.long "no-equiv" <> Opt.help "Do not run an equivalence check on the output netlist.") <*> (Opt.switch $ Opt.long "no-reduction" <> Opt.help "Do not run reduction on a failed testcase.") <*> ( Opt.optional . Opt.strOption $ Opt.long "source" <> Opt.short 's' <> Opt.metavar "FILE" <> Opt.help "Name of the top module.") <*> textOption ( Opt.long "source-top" <> Opt.short 't' <> Opt.metavar "TOP" <> Opt.help "Define the top module for the source file." <> Opt.showDefault <> Opt.value "top") <*> (Opt.switch $ Opt.long "crosscheck" <> Opt.help "Do not only compare against the original design, but also against other netlists.") <*> (Opt.optional . textOption $ Opt.long "checker" <> Opt.metavar "CHECKER" <> Opt.help "Define the checker to use.") genOpts :: Parser Opts genOpts = Generate <$> ( Opt.optional . Opt.strOption $ Opt.long "output" <> Opt.short 'o' <> Opt.metavar "FILE" <> Opt.help "Output to a verilog file instead." ) <*> ( Opt.optional . Opt.strOption $ Opt.long "config" <> Opt.short 'c' <> Opt.metavar "FILE" <> Opt.help "Config file for the generation run." ) parseOpts :: Parser Opts parseOpts = Parse <$> (fromText . T.pack <$> Opt.strArgument (Opt.metavar "FILE" <> Opt.help "Verilog input file.")) <*> textOption ( Opt.short 't' <> Opt.long "top" <> Opt.metavar "TOP" <> Opt.help "Name of top level module." <> Opt.showDefault <> Opt.value "top" ) <*> ( Opt.optional . Opt.strOption $ Opt.long "output" <> Opt.short 'o' <> Opt.metavar "FILE" <> Opt.help "Output file to write the parsed file to.") <*> (Opt.switch $ Opt.long "remove-const-in-concat" <> Opt.help "Remove constants in concatenation to simplify the Verilog.") reduceOpts :: Parser Opts reduceOpts = Reduce . fromText . T.pack <$> Opt.strArgument (Opt.metavar "FILE" <> Opt.help "Verilog input file.") <*> textOption ( Opt.short 't' <> Opt.long "top" <> Opt.metavar "TOP" <> Opt.help "Name of top level module." <> Opt.showDefault <> Opt.value "top" ) <*> ( Opt.optional . Opt.strOption $ Opt.long "script" <> Opt.metavar "SCRIPT" <> Opt.help "Script that determines if the current file is interesting, which is determined by the script returning 0." ) <*> ( Opt.many . Opt.option (optReader parseSynthDesc) $ Opt.short 's' <> Opt.long "synth" <> Opt.metavar "SYNTH" <> Opt.help "Specify synthesiser to use." ) <*> ( Opt.switch $ Opt.short 'r' <> Opt.long "rerun" <> Opt.help "Only rerun the current synthesis file with all the synthesisers." ) configOpts :: Parser Opts configOpts = ConfigOpt <$> ( Opt.optional . Opt.strOption $ Opt.long "output" <> Opt.short 'o' <> Opt.metavar "FILE" <> Opt.help "Output to a TOML Config file." ) <*> ( Opt.optional . Opt.strOption $ Opt.long "config" <> Opt.short 'c' <> Opt.metavar "FILE" <> Opt.help "Config file for the current fuzz run." ) <*> ( Opt.switch $ Opt.long "randomise" <> Opt.short 'r' <> Opt.help "Randomise the given default config, or the default config by randomly switchin on and off options." ) argparse :: Parser Opts argparse = Opt.hsubparser ( Opt.command "fuzz" (Opt.info fuzzOpts (Opt.progDesc "Run fuzzing on the specified simulators and synthesisers." ) ) <> Opt.metavar "fuzz" ) <|> Opt.hsubparser ( Opt.command "generate" (Opt.info genOpts (Opt.progDesc "Generate a random Verilog program.") ) <> Opt.metavar "generate" ) <|> Opt.hsubparser ( Opt.command "parse" (Opt.info parseOpts (Opt.progDesc "Parse a verilog file and output a pretty printed version." ) ) <> Opt.metavar "parse" ) <|> Opt.hsubparser ( Opt.command "reduce" (Opt.info reduceOpts (Opt.progDesc "Reduce a Verilog file by rerunning the fuzzer on the file." ) ) <> Opt.metavar "reduce" ) <|> Opt.hsubparser ( Opt.command "config" (Opt.info configOpts (Opt.progDesc "Print the current configuration of the fuzzer." ) ) <> Opt.metavar "config" ) version :: Parser (a -> a) version = Opt.infoOption versionInfo $ mconcat [Opt.long "version", Opt.short 'v', Opt.help "Show version information.", Opt.hidden] opts :: ParserInfo Opts opts = Opt.info (argparse <**> Opt.helper <**> version) ( Opt.fullDesc <> Opt.progDesc "Fuzz different simulators and synthesisers." <> Opt.header "Verismith - A hardware simulator and synthesiser Verilog fuzzer." )