packages feed

verismith-1.1.0: src/Verismith/OptParser.hs

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 Shelly (FilePath (..), fromText)
import Verismith.Config (SynthDescription (..), versionInfo)
import Prelude hiding (FilePath (..))
import Verismith.Verilog2005 (PrintingOpts (..))

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)
      }
  | EMIOpts
      { emiOutput :: Text,
        emiConfigFile :: !(Maybe FilePath),
        emiForced :: !Bool,
        emiKeepAll :: !Bool,
        emiNum :: {-# UNPACK #-} !Int,
        emiNoSim :: !Bool,
        emiNoEquiv :: !Bool,
        emiNoReduction :: !Bool,
        emiTopModule :: Text,
        emiInputFile :: FilePath
      }
  | Generate
      { generateFilename :: !(Maybe FilePath),
        generateConfigFile :: !(Maybe FilePath),
        generateValidSyntax :: !Bool,
        generatePrinting :: !PrintingOpts
      }
  | Parse
      { parseFilename :: !FilePath,
        parseOutput :: !(Maybe FilePath),
        parseStrict :: !Bool,
        parsePrinting :: !PrintingOpts
      }
  | Reduce
      { reduceFilename :: !FilePath,
        reduceTop :: !Text,
        reduceScript :: !(Maybe FilePath),
        reduceSynthesiserDesc :: ![SynthDescription],
        reduceRerun :: !Bool
      }
  | ConfigOpt
      { configOptWriteConfig :: !(Maybe FilePath),
        configOptConfigFile :: !(Maybe FilePath),
        configOptDoRandomise :: !Bool
      }
  | DistanceOpt
      { distanceOptVerilogA :: !FilePath,
        distanceOptVerilogB :: !FilePath
      }
  | ShuffleOpt
      { shuffleOptFilename :: !FilePath,
        shuffleOptTop :: !Text,
        shuffleOptOutput :: !(Maybe FilePath),
        shuffleOptShuffleLines :: !Bool,
        shuffleOptRenameVars :: !Bool,
        shuffleOptEquiv :: !Bool,
        shuffleOptEquivFolder :: !FilePath,
        shuffleOptChecker :: !(Maybe Text)
      }
  | Equiv
      { equivOutput :: !FilePath,
        equivFilenameA :: !FilePath,
        equivFilenameB :: !FilePath,
        equivFileTop :: !Text,
        equivChecker :: !(Maybe Text)
      }

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."
        )

emiOpts :: Parser Opts
emiOpts =
  EMIOpts
    <$> 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."
        )
    <*> textOption
      ( Opt.long "top"
          <> Opt.short 't'
          <> Opt.metavar "MODULE"
          <> Opt.help "Top module for the Verilog module."
          <> Opt.showDefault
          <> Opt.value "top"
      )
    <*> Opt.strArgument (Opt.metavar "FILE" <> Opt.help "Verilog input file to pass to EMI.")

printOpts :: Parser PrintingOpts
printOpts =
  PrintingOpts
    <$> ( Opt.switch $
            Opt.long "space-after-escaped"
              <> Opt.help "Always print a space after an escaped identifier."
        )
    <*> ( Opt.switch $
            Opt.long "spaces-in-primitive"
              <> Opt.help "Always print spaces in primitives table between levels."
        )
    <*> ( Opt.switch $
            Opt.long "edge-control-z"
              <> Opt.help "Use z instead of x in edge-control specifiers."
        )

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."
        )
    <*> ( Opt.switch $
            Opt.long "invalid"
              <> Opt.help
                "Generate invalid Verilog that is only syntactically allowed."
        )
    <*> printOpts

parseOpts :: Parser Opts
parseOpts =
  Parse
    <$> ( fromText . T.pack
            <$> Opt.strArgument
              (Opt.metavar "FILE" <> Opt.help "Verilog input file.")
        )
    <*> ( 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 "strict"
              <> Opt.help
                "Makes the parser comply strictly to the Verilog 2005 standard."
        )
    <*> printOpts

shuffleOpts :: Parser Opts
shuffleOpts =
  ShuffleOpt
    <$> ( 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 "no-shuffle-lines"
              <> Opt.help
                "Shuffle the lines in a Verilog file."
        )
    <*> ( Opt.switch $
            Opt.long "no-rename-vars"
              <> Opt.help
                "Rename the variables in a Verilog file."
        )
    <*> ( Opt.switch $
            Opt.long "noequiv"
              <> Opt.help
                "Do not check equivalence between input and output (currently only verismith generated Verilog is likely to pass this equivalence check)."
        )
    <*> ( Opt.strOption $
            Opt.long "equiv-output"
              <> Opt.short 'e'
              <> Opt.metavar "FOLDER"
              <> Opt.help "Output folder to write the equivalence checking files in."
              <> Opt.showDefault
              <> Opt.value "equiv"
        )
    <*> ( Opt.optional . textOption $
            Opt.long "checker"
              <> Opt.metavar "CHECKER"
              <> Opt.help "Define the checker to use."
        )

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."
        )

distanceOpts :: Parser Opts
distanceOpts =
  DistanceOpt
    <$> ( fromText . T.pack
            <$> Opt.strArgument
              (Opt.metavar "FILE" <> Opt.help "First verilog file.")
        )
    <*> ( fromText . T.pack
            <$> Opt.strArgument
              (Opt.metavar "FILE" <> Opt.help "Second verilog file.")
        )

equivOpts :: Parser Opts
equivOpts =
  Equiv
    <$> Opt.strOption
      ( Opt.long "output"
          <> Opt.short 'o'
          <> Opt.metavar "DIR"
          <> Opt.help "Output directory that the equivalence run takes place in."
          <> Opt.showDefault
          <> Opt.value "output"
      )
    <*> ( fromText . T.pack
            <$> Opt.strArgument
              (Opt.metavar "FILEA" <> Opt.help "First verilog file.")
        )
    <*> ( fromText . T.pack
            <$> Opt.strArgument
              (Opt.metavar "FILEB" <> Opt.help "Second verilog file.")
        )
    <*> textOption
      ( Opt.long "source-top"
          <> Opt.short 't'
          <> Opt.metavar "TOP"
          <> Opt.help "Define the top module to compare between the source files."
          <> Opt.showDefault
          <> Opt.value "top"
      )
    <*> ( Opt.optional . textOption $
            Opt.long "checker"
              <> Opt.metavar "CHECKER"
              <> Opt.help "Define the checker to use."
        )

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
          "emi"
          ( Opt.info
              emiOpts
              ( Opt.progDesc
                  "EMI testing using generated inputs, or existing Verilog designs."
              )
          )
          <> Opt.metavar "emi"
      )
    <|> 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
          "shuffle"
          ( Opt.info
              shuffleOpts
              ( Opt.progDesc
                  "Shuffle a Verilog file."
              )
          )
          <> Opt.metavar "shuffle"
      )
    <|> Opt.hsubparser
      ( Opt.command
          "config"
          ( Opt.info
              configOpts
              ( Opt.progDesc
                  "Print the current configuration of the fuzzer."
              )
          )
          <> Opt.metavar "config"
      )
    <|> Opt.hsubparser
      ( Opt.command
          "distance"
          ( Opt.info
              distanceOpts
              ( Opt.progDesc
                  "Calculate the distance between two different pieces of Verilog."
              )
          )
          <> Opt.metavar "distance"
      )
    <|> Opt.hsubparser
      ( Opt.command
          "equiv"
          ( Opt.info
              equivOpts
              ( Opt.progDesc
                  "Check two different pieces of Verilog are equivalent."
              )
          )
          <> Opt.metavar "equiv"
      )

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."
    )