Skip to content

Commit

Permalink
handle directives when writing to a directory
Browse files Browse the repository at this point in the history
  • Loading branch information
zachjs committed Jun 16, 2024
1 parent fdfa597 commit 6eda946
Show file tree
Hide file tree
Showing 6 changed files with 194 additions and 25 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
## Unreleased

### Bug Fixes

* Fixed `--write path/to/dir/` with directives like `` `default_nettype ``

### Other Enhancements

* `--write path/to/dir/` can now also be used with `--pass-through`

## v0.0.12

### Breaking Changes
Expand Down
6 changes: 1 addition & 5 deletions src/Job.hs
Original file line number Diff line number Diff line change
Expand Up @@ -139,11 +139,7 @@ readJob =
setWrite :: Job -> IO Job
setWrite job = do
w <- parseWrite $ writeRaw job
case (w, passThrough job) of
(Directory{}, True) -> do
hPutStr stderr "can't use --pass-through when writing to a dir"
exitFailure
_ -> return $ job { write = w }
return $ job { write = w }

setSuccinct :: Job -> Job
setSuccinct job | verbose job = job { exclude = Succinct : exclude job }
Expand Down
106 changes: 106 additions & 0 deletions src/Split.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
{- sv2v
- Author: Zachary Snow <zach@zachjs.com>
-
- Split descriptions into individual files
-}

module Split (splitDescriptions) where

import Data.List (isPrefixOf)

import Language.SystemVerilog.AST

splitDescriptions :: AST -> [PackageItem] -> ([(String, AST)], [PackageItem])
splitDescriptions (PackageItem item : ungrouped) itemsBefore =
(grouped, item : itemsAfter)
where
(grouped, itemsAfter) = splitDescriptions ungrouped (item : itemsBefore)
splitDescriptions (description : descriptions) itemsBefore =
((name, surrounded) : grouped, itemsAfter)
where
(grouped, itemsAfter) = splitDescriptions descriptions itemsBefore
name = case description of
Part _ _ _ _ x _ _ -> x
Package _ x _ -> x
Class _ x _ _ -> x
surrounded = surroundDescription itemsBefore description itemsAfter
splitDescriptions [] _ = ([], [])

data SurroundState = SurroundState
{ sKeptBefore :: [PackageItem]
, sKeptAfter :: [PackageItem]
, sCellDefine :: Bool
, sUnconnectedDrive :: Maybe PackageItem
, sDefaultNettype :: Maybe PackageItem
, sComment :: Maybe PackageItem
}

-- filter and include the surrounding package items for this description
surroundDescription :: [PackageItem] -> Description -> [PackageItem] -> AST
surroundDescription itemsBefore description itemsAfter =
map PackageItem itemsBefore' ++ description : map PackageItem itemsAfter'
where
itemsBefore' = extraBefore ++ reverse (sKeptBefore state2)
itemsAfter' = sKeptAfter state2 ++ reverse extraAfter

state0 = SurroundState [] [] False Nothing Nothing Nothing
state1 = foldr stepBefore state0 itemsBefore
state2 = foldr stepAfter state1 itemsAfter

(extraBefore, extraAfter) = foldr (<>) mempty $ map ($ state2) $
[ applyLeader sDefaultNettype
, applyCellDefine
, applyUnconnectedDrive
, applyLeader sComment
]

applyCellDefine :: SurroundState -> ([PackageItem], [PackageItem])
applyCellDefine state
| sCellDefine state =
([Directive "`celldefine"], [Directive "`endcelldefine"])
| otherwise = ([], [])

applyUnconnectedDrive :: SurroundState -> ([PackageItem], [PackageItem])
applyUnconnectedDrive state
| Just item <- sUnconnectedDrive state =
([item], [Directive "`nounconnected_drive"])
| otherwise = ([], [])

applyLeader :: (SurroundState -> Maybe PackageItem) -> SurroundState
-> ([PackageItem], [PackageItem])
applyLeader getter state
| Just item <- getter state = ([item], [])
| otherwise = ([], [])

-- update the state with a pre-description item
stepBefore :: PackageItem -> SurroundState -> SurroundState
stepBefore item@(Decl CommentDecl{}) state =
state { sComment = Just item }
stepBefore item@(Directive directive) state
| matches "celldefine" = state { sCellDefine = True }
| matches "endcelldefine" = state { sCellDefine = False }
| matches "unconnected_drive" = state { sUnconnectedDrive = Just item }
| matches "nounconnected_drive" = state { sUnconnectedDrive = Nothing }
| matches "default_nettype" = state { sDefaultNettype = Just item }
| matches "resetall" = state
{ sCellDefine = False
, sUnconnectedDrive = Nothing
, sDefaultNettype = Nothing
}
where matches = flip isPrefixOf directive . ('`' :)
stepBefore item state =
state { sKeptBefore = item : sKeptBefore state }

-- update the state with a post-description item
stepAfter :: PackageItem -> SurroundState -> SurroundState
stepAfter (Decl CommentDecl{}) state = state
stepAfter (Directive directive) state
| matches "celldefine" = state
| matches "endcelldefine" = state
| matches "unconnected_drive" = state
| matches "nounconnected_drive" = state
| matches "default_nettype" = state
| matches "resetall" = state
where matches = flip isPrefixOf directive . ('`' :)
stepAfter item state =
state { sKeptAfter = item : sKeptAfter state }
23 changes: 10 additions & 13 deletions src/sv2v.hs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import Convert (convert)
import Job (readJob, Job(..), Write(..))
import Language.SystemVerilog.AST
import Language.SystemVerilog.Parser (parseFiles, Config(..))
import Split (splitDescriptions)

isComment :: Description -> Bool
isComment (PackageItem (Decl CommentDecl{})) = True
Expand Down Expand Up @@ -60,17 +61,6 @@ rewritePath path = do
ext = ".sv"
(base, end) = splitExtension path

splitModules :: FilePath -> AST -> [(FilePath, String)]
splitModules dir (PackageItem (Decl CommentDecl{}) : ast) =
splitModules dir ast
splitModules dir (description : ast) =
(path, output) : splitModules dir ast
where
Part _ _ Module _ name _ _ = description
path = combine dir $ name ++ ".v"
output = show description ++ "\n"
splitModules _ [] = []

writeOutput :: Write -> [FilePath] -> [AST] -> IO ()
writeOutput _ [] [] =
hPutStrLn stderr "Warning: No input files specified (try `sv2v --help`)"
Expand All @@ -82,9 +72,16 @@ writeOutput Adjacent inPaths asts = do
outPaths <- mapM rewritePath inPaths
let results = map (++ "\n") $ map show asts
zipWithM_ writeFile outPaths results
writeOutput (Directory d) _ asts = do
let (outPaths, outputs) = unzip $ splitModules d $ concat asts
writeOutput (Directory d) _ asts =
zipWithM_ writeFile outPaths outputs
where
(outPaths, outputs) =
unzip $ map prepare $ fst $ splitDescriptions (concat asts) []
prepare :: (String, AST) -> (FilePath, String)
prepare (name, ast) = (path, output)
where
path = combine d $ name ++ ".v"
output = concatMap (++ "\n") $ map show ast

main :: IO ()
main = do
Expand Down
1 change: 1 addition & 0 deletions sv2v.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ executable sv2v
Convert.Wildcard
-- sv2v CLI modules
Job
Split
Paths_sv2v
autogen-modules:
Paths_sv2v
Expand Down
73 changes: 66 additions & 7 deletions test/write/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -89,13 +89,6 @@ test_directory() {
rm -f dirout/*
mkdir -p dirout

runAndCapture --pass-through --write dirout *.sv
assertFalse "directory conversion should succeed" $result
assertNull "stdout should be empty" "$stdout"
assertEquals "stderr should have expected message" \
"can't use --pass-through when writing to a dir" \
"$stderr"

runAndCapture --write dirout *.sv
assertTrue "directory conversion should succeed" $result
assertNull "stdout should be empty" "$stdout"
Expand All @@ -104,12 +97,78 @@ test_directory() {
assertTrue "one.v should exist" "[ -f dirout/one.v ]"
assertTrue "two.v should exist" "[ -f dirout/two.v ]"
assertTrue "three.v should exist" "[ -f dirout/three.v ]"
assertFalse "P.v should not exist" "[ -f dirout/P.v ]"

actual=`cat dirout/*.v`
assertEquals "directory output should match combined" "$expected" "$actual"
clearArtifacts
}

test_directory_pass_through() {
rm -f dirout/*
mkdir -p dirout

runAndCapture --pass-through --write dirout *.sv
assertTrue "directory conversion should succeed" $result
assertNull "stdout should be empty" "$stdout"
assertNull "stderr should be empty" "$stderr"

assertTrue "one.v should exist" "[ -f dirout/one.v ]"
assertTrue "two.v should exist" "[ -f dirout/two.v ]"
assertTrue "three.v should exist" "[ -f dirout/three.v ]"
assertTrue "P.v should exist" "[ -f dirout/P.v ]"
clearArtifacts
}

test_directory_directives() {
module_inp="logic a, b;module example;wire x;endmodule logic c, d;"
module_out="logic a;
logic b;
module example;
wire x;
endmodule
logic c;
logic d;"

check_directory_example "$module_inp" "$module_out"
check_directory_example "\`default_nettype none\n$module_inp\`resetall" "\`default_nettype none\n$module_out"
check_directory_example "\`default_nettype none\n\`default_nettype wire\n$module_inp" "\`default_nettype wire\n$module_out"
check_directory_example "\`celldefine\n$module_inp" "\`celldefine\n$module_out\n\`endcelldefine"
check_directory_example "\`celldefine\n\`endcelldefine\n$module_inp" "$module_out"
check_directory_example "\`unconnected_drive pull0\n$module_inp" "\`unconnected_drive pull0\n$module_out\n\`nounconnected_drive"
check_directory_example "\`unconnected_drive pull0\n\`nounconnected_drive\n$module_inp" "$module_out"
check_directory_example "\`default_nettype none\n\`celldefine\n\`unconnected_drive pull1\n\`resetall\n$module_inp" "$module_out"
check_directory_example "\`default_nettype none
\`celldefine
\`unconnected_drive pull1
$module_inp" "\`default_nettype none
\`celldefine
\`unconnected_drive pull1
$module_out
\`nounconnected_drive
\`endcelldefine"
}

check_directory_example() {
tmp_inp=$SHUNIT_TMPDIR/example.sv
tmp_out=$SHUNIT_TMPDIR/example.v
tmp_ref=$SHUNIT_TMPDIR/example_ref.v

echo -e "$1" > $tmp_inp
echo -e "$2" | sed -E 's/ /\t/g' > $tmp_ref

rm -f $tmp_out
runAndCapture --pass-through --write $SHUNIT_TMPDIR $tmp_inp
assertTrue "directory conversion should succeed" $result
assertNull "stdout should be empty: $stdout" "$stdout"
assertNull "stderr should be empty: $stderr" "$stderr"

grep -v '//' < $tmp_out > $tmp_out.bak
mv $tmp_out.bak $tmp_out
output=`diff --unified $tmp_ref $tmp_out`
assertTrue "output doesn't match:\n$output" $?
}

test_unknown() {
runAndCapture --write=unknown *.sv
assertFalse "unknown write mode should fail" $result
Expand Down

0 comments on commit 6eda946

Please sign in to comment.