From 5aea0ee95ee365525a0d6bd8c1a3ae335ccc4f33 Mon Sep 17 00:00:00 2001 From: Zachary Snow Date: Thu, 28 Jan 2021 18:34:55 -0500 Subject: [PATCH] add write adjacent mode --- README.md | 5 +++ src/Job.hs | 12 ++++++++ src/sv2v.hs | 51 +++++++++++++++++++++++-------- test/error/run.sh | 16 ++++------ test/lib/functions.sh | 7 +++++ test/write/.gitignore | 1 + test/write/one.sv | 3 ++ test/write/run.sh | 71 +++++++++++++++++++++++++++++++++++++++++++ test/write/two.sv | 3 ++ 9 files changed, 147 insertions(+), 22 deletions(-) create mode 100644 test/write/.gitignore create mode 100644 test/write/one.sv create mode 100755 test/write/run.sh create mode 100644 test/write/two.sv diff --git a/README.md b/README.md index eea69d8..557f035 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,9 @@ running `stack install`, or copy over the executable manually. ## Usage sv2v takes in a list of files and prints the converted Verilog to `stdout`. +Using `--write=adjacent` will create a converted `.v` for every `.sv` input file +rather than printing to `stdout`. + Users may specify `include` search paths, define macros during preprocessing, and exclude some of the conversions. Specifying `-` as an input file will read from `stdin`. @@ -92,6 +95,8 @@ Conversion: -E --exclude=CONV Exclude a particular conversion (always, assert, interface, or logic) -v --verbose Retain certain conversion artifacts + -w --write=MODE How to write output; default is 'stdout'; use + 'adjacent' to create a .v file next to each input Other: --help Display help message --version Print version information diff --git a/src/Job.hs b/src/Job.hs index a6b9313..9c1aa99 100644 --- a/src/Job.hs +++ b/src/Job.hs @@ -21,6 +21,14 @@ data Exclude | Succinct deriving (Show, Typeable, Data, Eq) +data Write + = Stdout + | Adjacent + deriving (Show, Typeable, Data, Eq) + +instance Default Write where + def = Stdout + data Job = Job { files :: [FilePath] , incdir :: [FilePath] @@ -29,6 +37,7 @@ data Job = Job , skipPreprocessor :: Bool , exclude :: [Exclude] , verbose :: Bool + , write :: Write } deriving (Show, Typeable, Data) defaultJob :: Job @@ -47,6 +56,9 @@ defaultJob = Job ++ " or logic)") &= groupname "Conversion" , verbose = nam "verbose" &= help "Retain certain conversion artifacts" + , write = nam_ "write" &= name "w" &= typ "MODE" + &= help ("How to write output; default is 'stdout'; use 'adjacent' to" + ++ " create a .v file next to each input") } &= program "sv2v" &= summary ("sv2v " ++ giDescribe $$tGitInfoCwd) diff --git a/src/sv2v.hs b/src/sv2v.hs index 89c8df8..fb67bbc 100644 --- a/src/sv2v.hs +++ b/src/sv2v.hs @@ -4,12 +4,15 @@ - conversion entry point -} -import System.IO -import System.Exit +import System.Directory (doesFileExist) +import System.IO (hPrint, hPutStrLn, stderr, stdout) +import System.Exit (exitFailure, exitSuccess) + +import Control.Monad (filterM, when, zipWithM_) +import Data.List (elemIndex, intercalate) -import Data.List (elemIndex) -import Job (readJob, files, exclude, incdir, define, siloed, skipPreprocessor) import Convert (convert) +import Job (readJob, Job(..), Write(..)) import Language.SystemVerilog.AST import Language.SystemVerilog.Parser (parseFiles) @@ -32,15 +35,39 @@ emptyWarnings before after = if all null before || any (not . null) after then return () else if any (any isInterface) before then - hPutStr stderr $ "Warning: Source includes an interface but output is " - ++ "empty because there is no top-level module which has no ports " - ++ "which are interfaces." + hPutStrLn stderr $ "Warning: Source includes an interface but output is" + ++ " empty because there is no top-level module which has no ports" + ++ " which are interfaces." else if any (any isPackage) before then - hPutStr stderr $ "Warning: Source includes packages but no modules. " - ++ "Please convert packages alongside the modules that use them." + hPutStrLn stderr $ "Warning: Source includes packages but no modules." + ++ " Please convert packages alongside the modules that use them." else return () +rewritePath :: FilePath -> IO FilePath +rewritePath path = do + when (end /= ext) $ do + hPutStrLn stderr $ "Refusing to write adjacent to " ++ show path + ++ " because that path does not end in " ++ show ext + exitFailure + return $ base ++ ".v" + where + ext = ".sv" + (base, end) = splitAt (length path - length ext) path + +writeOutput :: Write -> [FilePath] -> [AST] -> IO () +writeOutput Stdout _ asts = + hPrint stdout $ concat asts +writeOutput Adjacent inPaths asts = do + outPaths <- mapM rewritePath inPaths + badPaths <- filterM doesFileExist outPaths + when (not $ null badPaths) $ do + hPutStrLn stderr $ "Refusing to write output because the following" + ++ " files would be overwritten: " ++ intercalate ", " badPaths + exitFailure + let results = map (++ "\n") $ map show asts + zipWithM_ writeFile outPaths results + main :: IO () main = do job <- readJob @@ -50,12 +77,12 @@ main = do (skipPreprocessor job) (files job) case result of Left msg -> do - hPutStr stderr $ msg ++ "\n" + hPutStrLn stderr msg exitFailure Right asts -> do -- convert the files let asts' = convert (exclude job) asts emptyWarnings asts asts' - -- print the converted files out - hPrint stdout $ concat asts' + -- write the converted files out + writeOutput (write job) (files job) asts' exitSuccess diff --git a/test/error/run.sh b/test/error/run.sh index fb7a478..28c9056 100755 --- a/test/error/run.sh +++ b/test/error/run.sh @@ -1,11 +1,11 @@ #!/bin/bash -validateOutput() { - stdout_len=`wc -l < $SHUNIT_TMPDIR/stdout` - assertEquals "stdout should be empty" 0 $stdout_len - stderr=`cat $SHUNIT_TMPDIR/stderr` +runErrorTest() { + runAndCapture $1.sv + assertFalse "conversion should have failed" $result + assertNull "stdout should be empty" "$stdout" assertNotNull "stderr should not be empty" "$stderr" - line=`head -n1 $1` + line=`head -n1 $1.sv` if [[ "$line" =~ \/\/\ pattern:\ .* ]]; then pattern=${line:12} if [[ ! "$stderr" =~ $pattern ]]; then @@ -16,11 +16,7 @@ validateOutput() { addTest() { test=$1 - eval "test_$test() { \ - $SV2V $test.sv 2> $SHUNIT_TMPDIR/stderr > $SHUNIT_TMPDIR/stdout; \ - assertFalse \"conversion should have failed\" \$?; \ - validateOutput $test.sv; \ - }" + eval "test_$test() { runErrorTest $test; }" suite_addTest test_$test } diff --git a/test/lib/functions.sh b/test/lib/functions.sh index 6e56858..e03eabf 100644 --- a/test/lib/functions.sh +++ b/test/lib/functions.sh @@ -131,3 +131,10 @@ runTest() { test=$1 simpleTest "${test}.sv" "${test}.v" "${test}_tb.v" } + +runAndCapture() { + $SV2V "$@" > "$SHUNIT_TMPDIR/stdout" 2> "$SHUNIT_TMPDIR/stderr" + result=$? + stdout=`cat $SHUNIT_TMPDIR/stdout` + stderr=`cat $SHUNIT_TMPDIR/stderr` +} diff --git a/test/write/.gitignore b/test/write/.gitignore new file mode 100644 index 0000000..39c60c5 --- /dev/null +++ b/test/write/.gitignore @@ -0,0 +1 @@ +*.v diff --git a/test/write/one.sv b/test/write/one.sv new file mode 100644 index 0000000..01345f8 --- /dev/null +++ b/test/write/one.sv @@ -0,0 +1,3 @@ +module one; + logic x; +endmodule diff --git a/test/write/run.sh b/test/write/run.sh new file mode 100755 index 0000000..1bcad88 --- /dev/null +++ b/test/write/run.sh @@ -0,0 +1,71 @@ +#!/bin/bash + +clearArtifacts() { + rm -f one.v two.v +} + +createArtifacts() { + touch one.v two.v +} + +test_prereq() { + for file in `ls *.sv`; do + assertConverts "$file" + done +} + +test_default() { + runAndCapture *.sv + assertTrue "default conversion should succeed" $result + assertNotNull "stdout should not be empty" "$stdout" + assertNull "stderr should be empty" "$stderr" +} + +test_stdout() { + runAndCapture --write=stdout *.sv + assertTrue "default conversion should succeed" $result + assertNotNull "stdout should not be empty" "$stdout" + assertNull "stderr should be empty" "$stderr" +} + +test_adjacent() { + runAndCapture --write=stdout *.sv + expected="$stdout" + + runAndCapture --write=adjacent *.sv + assertTrue "adjacent conversion should succeed" $result + assertNull "stdout should be empty" "$stdout" + assertNull "stderr should be empty" "$stderr" + + actual=`cat one.v two.v` + assertEquals "adjacent output should match combined" "$expected" "$actual" + clearArtifacts +} + +test_adjacent_exist() { + createArtifacts + runAndCapture --write=adjacent *.sv + clearArtifacts + + assertFalse "adjacent conversion should fail" $result + assertNull "stdout should be empty" "$stdout" + assertEquals "stderr should list existing files" \ + "Refusing to write output because the following files would be overwritten: one.v, two.v" \ + "$stderr" +} + +test_adjacent_extension() { + createArtifacts + runAndCapture --write=adjacent *.v + clearArtifacts + + assertFalse "adjacent conversion should fail" $result + assertNull "stdout should be empty" "$stdout" + assertEquals "stderr should list existing files" \ + "Refusing to write adjacent to \"one.v\" because that path does not end in \".sv\"" \ + "$stderr" +} + +source ../lib/functions.sh + +. shunit2 diff --git a/test/write/two.sv b/test/write/two.sv new file mode 100644 index 0000000..bae189c --- /dev/null +++ b/test/write/two.sv @@ -0,0 +1,3 @@ +module two; + logic x; +endmodule