Commit 51c90baf by Zachary Snow

support for loading from library directories

parent aa429204
## Unreleased ## Unreleased
### New Features
* Added `-y`/`--libdir` for specifying library directories from which to
automatically load modules and interfaces used in the design that are not
found in the provided input files
* The `string` data type is now dropped from parameters and localparams
* Added support for passing through `sequence` and `property` declarations
### Bug Fixes ### Bug Fixes
* Fixed crash when converting multi-dimensional arrays or arrays of structs or * Fixed crash when converting multi-dimensional arrays or arrays of structs or
...@@ -18,11 +26,6 @@ ...@@ -18,11 +26,6 @@
* Fixed keywords included in the "1364-2001" and "1364-2001-noconfig" * Fixed keywords included in the "1364-2001" and "1364-2001-noconfig"
`begin_keywords` version specifiers `begin_keywords` version specifiers
### New Features
* `string` data type is now dropped from parameters and localparams
* Added support for passing through `sequence` and `property` declarations
### Other Enhancements ### Other Enhancements
* Added elaboration for accesses to fields of struct constants, which can * Added elaboration for accesses to fields of struct constants, which can
......
...@@ -78,7 +78,9 @@ default. Users should typically pass all of their SystemVerilog source files to ...@@ -78,7 +78,9 @@ default. Users should typically pass all of their SystemVerilog source files to
sv2v at once so it can properly resolve packages, interfaces, type parameters, sv2v at once so it can properly resolve packages, interfaces, type parameters,
etc., across files. Using `--write=adjacent` will create a converted `.v` for etc., across files. Using `--write=adjacent` will create a converted `.v` for
every `.sv` input file rather than printing to `stdout`. `--write`/`-w` can also every `.sv` input file rather than printing to `stdout`. `--write`/`-w` can also
be used to specify a path to a `.v` output file. be used to specify a path to a `.v` output file. Undefined modules and
interfaces can be automatically loaded from library directories using
`--libdir`/`-y`.
Users may specify `include` search paths, define macros during preprocessing, Users may specify `include` search paths, define macros during preprocessing,
and exclude some of the conversions. Specifying `-` as an input file will read and exclude some of the conversions. Specifying `-` as an input file will read
...@@ -91,6 +93,8 @@ sv2v [OPTIONS] [FILES] ...@@ -91,6 +93,8 @@ sv2v [OPTIONS] [FILES]
Preprocessing: Preprocessing:
-I --incdir=DIR Add directory to include search path -I --incdir=DIR Add directory to include search path
-y --libdir=DIR Add a directory to the library search path used
when looking for undefined modules and interfaces
-D --define=NAME[=VALUE] Define a macro for preprocessing -D --define=NAME[=VALUE] Define a macro for preprocessing
--siloed Lex input files separately, so macros from --siloed Lex input files separately, so macros from
earlier files are not defined in later files earlier files are not defined in later files
......
...@@ -36,6 +36,7 @@ data Write ...@@ -36,6 +36,7 @@ data Write
data Job = Job data Job = Job
{ files :: [FilePath] { files :: [FilePath]
, incdir :: [FilePath] , incdir :: [FilePath]
, libdir :: [FilePath]
, define :: [String] , define :: [String]
, siloed :: Bool , siloed :: Bool
, skipPreprocessor :: Bool , skipPreprocessor :: Bool
...@@ -58,6 +59,9 @@ defaultJob = Job ...@@ -58,6 +59,9 @@ defaultJob = Job
, incdir = nam_ "I" &= name "incdir" &= typDir , incdir = nam_ "I" &= name "incdir" &= typDir
&= help "Add directory to include search path" &= help "Add directory to include search path"
&= groupname "Preprocessing" &= groupname "Preprocessing"
, libdir = nam_ "y" &= name "libdir" &= typDir
&= help ("Add a directory to the library search path used when looking"
++ " for undefined modules and interfaces")
, define = nam_ "D" &= name "define" &= typ "NAME[=VALUE]" , define = nam_ "D" &= name "define" &= typ "NAME[=VALUE]"
&= help "Define a macro for preprocessing" &= help "Define a macro for preprocessing"
, siloed = nam_ "siloed" &= help ("Lex input files separately, so" , siloed = nam_ "siloed" &= help ("Lex input files separately, so"
......
...@@ -3,28 +3,41 @@ ...@@ -3,28 +3,41 @@
- Author: Zachary Snow <zach@zachjs.com> - Author: Zachary Snow <zach@zachjs.com>
-} -}
module Language.SystemVerilog.Parser module Language.SystemVerilog.Parser
( initialEnv ( parseFiles
, parseFiles
, Config(..) , Config(..)
) where ) where
import Control.Monad.Except import Control.Monad.Except
import Data.List (elemIndex) import Data.List (elemIndex)
import Data.Maybe (catMaybes)
import System.Directory (findFile)
import qualified Data.Map.Strict as Map import qualified Data.Map.Strict as Map
import qualified Data.Set as Set
import Language.SystemVerilog.AST (AST) import Language.SystemVerilog.AST (AST)
import Language.SystemVerilog.Parser.Lex (lexStr) import Language.SystemVerilog.Parser.Lex (lexStr)
import Language.SystemVerilog.Parser.Parse (parse) import Language.SystemVerilog.Parser.Parse (parse)
import Language.SystemVerilog.Parser.Preprocess (preprocess, annotate, Env, Contents) import Language.SystemVerilog.Parser.Preprocess (preprocess, annotate, Env, Contents)
type Output = (FilePath, AST)
type Strings = Set.Set String
data Config = Config data Config = Config
{ cfEnv :: Env { cfDefines :: [String]
, cfIncludePaths :: [FilePath] , cfIncludePaths :: [FilePath]
, cfLibraryPaths :: [FilePath]
, cfSiloed :: Bool , cfSiloed :: Bool
, cfSkipPreprocessor :: Bool , cfSkipPreprocessor :: Bool
, cfOversizedNumbers :: Bool , cfOversizedNumbers :: Bool
} }
data Context = Context
{ ctConfig :: Config
, ctEnv :: Env
, ctUsed :: Strings
, ctHave :: Strings
}
-- parse CLI macro definitions into the internal macro environment format -- parse CLI macro definitions into the internal macro environment format
initialEnv :: [String] -> Env initialEnv :: [String] -> Env
initialEnv = Map.map (, []) . Map.fromList . map splitDefine initialEnv = Map.map (, []) . Map.fromList . map splitDefine
...@@ -38,26 +51,58 @@ splitDefine str = ...@@ -38,26 +51,58 @@ splitDefine str =
where (name, rest) = splitAt idx str where (name, rest) = splitAt idx str
-- parse a list of files according to the given configuration -- parse a list of files according to the given configuration
parseFiles :: Config -> [FilePath] -> ExceptT String IO [AST] parseFiles :: Config -> [FilePath] -> ExceptT String IO [Output]
parseFiles _ [] = return [] parseFiles config = parseFiles' context . zip (repeat "")
parseFiles config (path : paths) = do where
(config', ast) <- parseFile config path context = Context config env mempty mempty
fmap (ast :) $ parseFiles config' paths env = initialEnv $ cfDefines config
-- parse an individual file, potentially updating the configuration -- parse files, keeping track of which parts are defined and used
parseFile :: Config -> FilePath -> ExceptT String IO (Config, AST) parseFiles' :: Context -> [(String, FilePath)] -> ExceptT String IO [Output]
parseFile config path = do
(config', contents) <- preprocessFile config path -- look for missing parts in libraries if any library paths were provided
parseFiles' context []
| null libdirs = return []
| otherwise = do
possibleFiles <- catMaybes <$> mapM lookupLibrary missingParts
if null possibleFiles
then return []
else parseFiles' context possibleFiles
where
missingParts = Set.toList $ ctUsed context Set.\\ ctHave context
libdirs = cfLibraryPaths $ ctConfig context
lookupLibrary partName = ((partName, ) <$>) <$> lookupLibFile partName
lookupLibFile = liftIO . findFile libdirs . (++ ".sv")
-- load the files, but complain if an expected part is missing
parseFiles' context ((part, path) : files) = do
(context', ast) <- parseFile context path
let misdirected = not $ null part || Set.member part (ctHave context')
when misdirected $ throwError $
"Expected to find module or interface " ++ show part ++ " in file "
++ show path ++ " selected from the library path."
((path, ast) :) <$> parseFiles' context' files
-- parse an individual file, updating the context
parseFile :: Context -> FilePath -> ExceptT String IO (Context, AST)
parseFile context path = do
(context', contents) <- preprocessFile context path
tokens <- liftEither $ runExcept $ lexStr contents tokens <- liftEither $ runExcept $ lexStr contents
ast <- parse (cfOversizedNumbers config) tokens (ast, used, have) <- parse (cfOversizedNumbers config) tokens
return (config', ast) let context'' = context' { ctUsed = used <> ctUsed context
, ctHave = have <> ctHave context }
-- preprocess an individual file, potentially updating the configuration return (context'', ast)
preprocessFile :: Config -> FilePath -> ExceptT String IO (Config, Contents) where config = ctConfig context
preprocessFile config path | cfSkipPreprocessor config =
fmap (config, ) $ annotate path -- preprocess an individual file, potentially updating the environment
preprocessFile config path = do preprocessFile :: Context -> FilePath -> ExceptT String IO (Context, Contents)
preprocessFile context path
| cfSkipPreprocessor config =
(context, ) <$> annotate path
| otherwise = do
(env', contents) <- preprocess (cfIncludePaths config) env path (env', contents) <- preprocess (cfIncludePaths config) env path
let config' = config { cfEnv = if cfSiloed config then env else env' } let context' = context { ctEnv = if cfSiloed config then env else env' }
return (config', contents) return (context', contents)
where env = cfEnv config where
config = ctConfig context
env = ctEnv context
...@@ -20,6 +20,7 @@ import Control.Monad.Except ...@@ -20,6 +20,7 @@ import Control.Monad.Except
import Control.Monad.State.Strict import Control.Monad.State.Strict
import Data.Maybe (catMaybes, fromMaybe) import Data.Maybe (catMaybes, fromMaybe)
import System.IO (hPutStrLn, stderr) import System.IO (hPutStrLn, stderr)
import qualified Data.Set as Set
import Language.SystemVerilog.AST import Language.SystemVerilog.AST
import Language.SystemVerilog.Parser.ParseDecl import Language.SystemVerilog.Parser.ParseDecl
import Language.SystemVerilog.Parser.Tokens import Language.SystemVerilog.Parser.Tokens
...@@ -554,7 +555,7 @@ Part(begin, end) :: { Description } ...@@ -554,7 +555,7 @@ Part(begin, end) :: { Description }
PartHeader :: { [Attr] -> Bool -> PartKW -> [ModuleItem] -> Identifier -> ParseState Description } PartHeader :: { [Attr] -> Bool -> PartKW -> [ModuleItem] -> Identifier -> ParseState Description }
: Lifetime Identifier PackageImportDeclarations Params PortDecls ";" : Lifetime Identifier PackageImportDeclarations Params PortDecls ";"
{ \attrs extern kw items label -> checkTag $2 label $ Part attrs extern kw $1 $2 (fst $5) ($3 ++ $4 ++ (snd $5) ++ items) } {% recordPartHave $2 >> return \attrs extern kw items label -> checkTag $2 label $ Part attrs extern kw $1 $2 (fst $5) ($3 ++ $4 ++ (snd $5) ++ items) }
ModuleKW :: { PartKW } ModuleKW :: { PartKW }
: "module" { Module } : "module" { Module }
...@@ -701,7 +702,7 @@ ModuleItem :: { [ModuleItem] } ...@@ -701,7 +702,7 @@ ModuleItem :: { [ModuleItem] }
| "generate" GenItems endgenerate { [Generate $2] } | "generate" GenItems endgenerate { [Generate $2] }
NonGenerateModuleItem :: { [ModuleItem] } NonGenerateModuleItem :: { [ModuleItem] }
-- This item covers module instantiations and all declarations -- This item covers module instantiations and all declarations
: ModuleDeclTokens(";") { parseDTsAsModuleItems $1 } : ModuleDeclTokens(";") {% mapM recordPartUsed $ parseDTsAsModuleItems $1 }
| ParameterDecl(";") { map (MIPackageItem . Decl) $1 } | ParameterDecl(";") { map (MIPackageItem . Decl) $1 }
| "defparam" LHSAsgns ";" { map (uncurry Defparam) $2 } | "defparam" LHSAsgns ";" { map (uncurry Defparam) $2 }
| "assign" AssignOption LHSAsgns ";" { map (uncurry $ Assign $2) $3 } | "assign" AssignOption LHSAsgns ";" { map (uncurry $ Assign $2) $3 }
...@@ -1549,25 +1550,29 @@ data ParseData = ParseData ...@@ -1549,25 +1550,29 @@ data ParseData = ParseData
{ pPosition :: Position { pPosition :: Position
, pTokens :: [Token] , pTokens :: [Token]
, pOversizedNumbers :: Bool , pOversizedNumbers :: Bool
, pPartsUsed :: Strings
, pPartsHave :: Strings
} }
type ParseState = StateT ParseData (ExceptT String IO) type ParseState = StateT ParseData (ExceptT String IO)
type Strings = Set.Set String
parse :: Bool -> [Token] -> ExceptT String IO AST parse :: Bool -> [Token] -> ExceptT String IO (AST, Strings, Strings)
parse _ [] = return [] parse _ [] = return mempty
parse oversizedNumbers tokens = parse oversizedNumbers tokens = do
evalStateT parseMain initialState (ast, finalState) <- runStateT parseMain initialState
return (ast, pPartsUsed finalState, pPartsHave finalState)
where where
position = tokenPosition $ head tokens position = tokenPosition $ head tokens
initialState = ParseData position tokens oversizedNumbers initialState = ParseData position tokens oversizedNumbers mempty mempty
positionKeep :: (Token -> ParseState a) -> ParseState a positionKeep :: (Token -> ParseState a) -> ParseState a
positionKeep cont = do positionKeep cont = do
ParseData _ tokens oversizedNumbers <- get tokens <- gets pTokens
case tokens of case tokens of
[] -> cont TokenEOF [] -> cont TokenEOF
tok : toks -> do tok : toks -> do
put $ ParseData (tokenPosition tok) toks oversizedNumbers modify' $ \s -> s { pPosition = tokenPosition tok, pTokens = toks }
cont tok cont tok
parseErrorTok :: Token -> ParseState a parseErrorTok :: Token -> ParseState a
...@@ -1842,4 +1847,19 @@ portBindingAttrs (pos, attrs) = parseWarning pos msg ...@@ -1842,4 +1847,19 @@ portBindingAttrs (pos, attrs) = parseWarning pos msg
where msg = "Ignored port connection attributes " where msg = "Ignored port connection attributes "
++ concatMap show attrs ++ "." ++ concatMap show attrs ++ "."
recordPartUsed :: ModuleItem -> ParseState ModuleItem
recordPartUsed item@(Instance partName _ _ _ _) = do
partsUsed <- gets pPartsUsed
when (Set.notMember partName partsUsed) $ do
let partsUsed' = Set.insert partName partsUsed
modify' $ \s -> s { pPartsUsed = partsUsed' }
return item
recordPartUsed item = return item
recordPartHave :: Identifier -> ParseState ()
recordPartHave partName = do
partsHave <- gets pPartsHave
let partsHave' = Set.insert partName partsHave
modify' $ \s -> s { pPartsHave = partsHave' }
} }
...@@ -13,7 +13,7 @@ import Control.Monad.Except (runExceptT) ...@@ -13,7 +13,7 @@ import Control.Monad.Except (runExceptT)
import Convert (convert) import Convert (convert)
import Job (readJob, Job(..), Write(..)) import Job (readJob, Job(..), Write(..))
import Language.SystemVerilog.AST import Language.SystemVerilog.AST
import Language.SystemVerilog.Parser (initialEnv, parseFiles, Config(..)) import Language.SystemVerilog.Parser (parseFiles, Config(..))
isInterface :: Description -> Bool isInterface :: Description -> Bool
isInterface (Part _ _ Interface _ _ _ _ ) = True isInterface (Part _ _ Interface _ _ _ _ ) = True
...@@ -69,8 +69,9 @@ main = do ...@@ -69,8 +69,9 @@ main = do
job <- readJob job <- readJob
-- parse the input files -- parse the input files
let config = Config let config = Config
{ cfEnv = initialEnv (define job) { cfDefines = define job
, cfIncludePaths = incdir job , cfIncludePaths = incdir job
, cfLibraryPaths = libdir job
, cfSiloed = siloed job , cfSiloed = siloed job
, cfSkipPreprocessor = skipPreprocessor job , cfSkipPreprocessor = skipPreprocessor job
, cfOversizedNumbers = oversizedNumbers job , cfOversizedNumbers = oversizedNumbers job
...@@ -80,12 +81,13 @@ main = do ...@@ -80,12 +81,13 @@ main = do
Left msg -> do Left msg -> do
hPutStrLn stderr msg hPutStrLn stderr msg
exitFailure exitFailure
Right asts -> do Right inputs -> do
let (inPaths, asts) = unzip inputs
-- convert the files if requested -- convert the files if requested
asts' <- if passThrough job asts' <- if passThrough job
then return asts then return asts
else convert (dumpPrefix job) (exclude job) asts else convert (dumpPrefix job) (exclude job) asts
emptyWarnings (concat asts) (concat asts') emptyWarnings (concat asts) (concat asts')
-- write the converted files out -- write the converted files out
writeOutput (write job) (files job) asts' writeOutput (write job) inPaths asts'
exitSuccess exitSuccess
...@@ -73,6 +73,7 @@ Many of these suites test a particular feature of the sv2v CLI. ...@@ -73,6 +73,7 @@ Many of these suites test a particular feature of the sv2v CLI.
* `help` ensures the `--help` output in the README is up to date * `help` ensures the `--help` output in the README is up to date
* `keyword` tests `begin_keywords` version specifiers * `keyword` tests `begin_keywords` version specifiers
* `number` generates and tests short number literals * `number` generates and tests short number literals
* `search` tests `-y`/`--libdir`
* `siloed` tests `--siloed` and default compilation unit behavior * `siloed` tests `--siloed` and default compilation unit behavior
* `truncate` tests number literal truncation and `--oversized-numbers` * `truncate` tests number literal truncation and `--oversized-numbers`
* `warning` tests conversion warnings * `warning` tests conversion warnings
......
module apple;
initial $display("apple");
endmodule
module surprise;
initial $display("This isn't what you're looking for!");
endmodule
interface orange;
initial $display("orange");
endinterface
#!/bin/bash
evaluate() {
design_v=$SHUNIT_TMPDIR/search_design.v
output_log=$SHUNIT_TMPDIR/search.log
touch $output_log
simulate /dev/null $output_log top <(echo "$1") /dev/null
tail -n1 $output_log
}
search() {
top_sv=$SHUNIT_TMPDIR/search_top.sv
echo "module top; $mod m(); endmodule" > $top_sv
runAndCapture "$@" $top_sv
}
searchAndEvaluate() {
search "$@"
assertTrue "$mod conversion should succeed" $result
assertNotNull "$mod stdout should not be empty" "$stdout"
assertNull "$mod stderr should be empty" "$stderr"
output=`evaluate "$stdout"`
}
checkFound() {
searchAndEvaluate "$@"
assertEquals "simulation output should match" $mod "$output"
}
checkNotFound() {
searchAndEvaluate "$@"
assertContains "iverilog should fail" "$output" "$mod referenced 1 times"
}
test_found() {
for mod in apple orange; do
checkFound -y.
checkFound -y../base -y.
checkFound -y. -y../base
done
}
test_not_found_default() {
for mod in apple orange; do
checkNotFound
done
}
test_not_found_missing() {
for mod in apple orange doesnt_exist; do
checkNotFound -y../base
done
}
test_misdirect() {
mod=misdirect
runAndCapture -y. <(echo "module top; $mod m(); endmodule")
assertFalse "conversion should not succeed" $result
assertNull "stdout should be empty" "$stdout"
assertContains "stderr should match expected error" "$stderr" \
'Expected to find module or interface "misdirect" in file "./misdirect.sv" selected from the library path.'
}
test_found_write_adjacent() {
files=(apple.v orange.v top.v)
for file in "${files[@]}"; do
assertTrue "$file should not exist" "[ ! -f $file ]"
done
runAndCapture -y. -wadj top.sv
assertTrue "conversion should succeed" $result
assertNull "stdout should be empty" "$stdout"
assertNull "stderr should be empty" "$stderr"
for file in "${files[@]}"; do
assertTrue "$file should exist" "[ -f $file ]"
rm -f $file
done
}
source ../lib/functions.sh
. shunit2
module top;
apple a();
orange o();
endmodule
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment