warn on duplicate modules

This commit is contained in:
Zachary Snow 2024-12-15 11:29:36 -05:00
parent 576a804d90
commit aa0a885699
6 changed files with 40 additions and 12 deletions

View File

@ -30,6 +30,7 @@
### Other Enhancements ### Other Enhancements
* Improved error messages for invalid port or parameter bindings * Improved error messages for invalid port or parameter bindings
* Added warning for modules or interfaces defined more than once
* `--write path/to/dir/` can now also be used with `--pass-through` * `--write path/to/dir/` can now also be used with `--pass-through`
## v0.0.12 ## v0.0.12

View File

@ -19,9 +19,11 @@ 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)
import Language.SystemVerilog.Parser.Tokens (Position)
type Output = (FilePath, AST) type Output = (FilePath, AST)
type Strings = Set.Set String type Strings = Set.Set String
type Positions = Map.Map String Position
data Config = Config data Config = Config
{ cfDefines :: [String] { cfDefines :: [String]
@ -36,7 +38,7 @@ data Context = Context
{ ctConfig :: Config { ctConfig :: Config
, ctEnv :: Env , ctEnv :: Env
, ctUsed :: Strings , ctUsed :: Strings
, ctHave :: Strings , ctHave :: Positions
} }
-- parse CLI macro definitions into the internal macro environment format -- parse CLI macro definitions into the internal macro environment format
@ -70,7 +72,8 @@ parseFiles' context []
then return [] then return []
else parseFiles' context possibleFiles else parseFiles' context possibleFiles
where where
missingParts = Set.toList $ ctUsed context Set.\\ ctHave context missingParts = Set.toList $ ctUsed context Set.\\
(Map.keysSet $ ctHave context)
libdirs = cfLibraryPaths $ ctConfig context libdirs = cfLibraryPaths $ ctConfig context
lookupLibrary partName = ((partName, ) <$>) <$> lookupLibFile partName lookupLibrary partName = ((partName, ) <$>) <$> lookupLibFile partName
lookupLibFile = liftIO . findFile libdirs . (++ ".sv") lookupLibFile = liftIO . findFile libdirs . (++ ".sv")
@ -78,7 +81,7 @@ parseFiles' context []
-- load the files, but complain if an expected part is missing -- load the files, but complain if an expected part is missing
parseFiles' context ((part, path) : files) = do parseFiles' context ((part, path) : files) = do
(context', ast) <- parseFile context path (context', ast) <- parseFile context path
let misdirected = not $ null part || Set.member part (ctHave context') let misdirected = not $ null part || Map.member part (ctHave context')
when misdirected $ throwError $ when misdirected $ throwError $
"Expected to find module or interface " ++ show part ++ " in file " "Expected to find module or interface " ++ show part ++ " in file "
++ show path ++ " selected from the library path." ++ show path ++ " selected from the library path."
@ -89,9 +92,10 @@ parseFile :: Context -> FilePath -> ExceptT String IO (Context, AST)
parseFile context path = do parseFile context path = do
(context', contents) <- preprocessFile context path (context', contents) <- preprocessFile context path
tokens <- liftEither $ runExcept $ lexStr contents tokens <- liftEither $ runExcept $ lexStr contents
(ast, used, have) <- parse (cfOversizedNumbers config) tokens (ast, used, have') <-
parse (cfOversizedNumbers config) (ctHave context) tokens
let context'' = context' { ctUsed = used <> ctUsed context let context'' = context' { ctUsed = used <> ctUsed context
, ctHave = have <> ctHave context } , ctHave = have' }
return (context'', ast) return (context'', ast)
where config = ctConfig context where config = ctConfig context

View File

@ -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.Map.Strict as Map
import qualified Data.Set as Set import qualified Data.Set as Set
import Language.SystemVerilog.AST import Language.SystemVerilog.AST
import Language.SystemVerilog.Parser.ParseDecl import Language.SystemVerilog.Parser.ParseDecl
@ -1579,20 +1580,22 @@ data ParseData = ParseData
, pTokens :: [Token] , pTokens :: [Token]
, pOversizedNumbers :: Bool , pOversizedNumbers :: Bool
, pPartsUsed :: Strings , pPartsUsed :: Strings
, pPartsHave :: Strings , pPartsHave :: Positions
} }
type ParseState = StateT ParseData (ExceptT String IO) type ParseState = StateT ParseData (ExceptT String IO)
type Strings = Set.Set String type Strings = Set.Set String
type Positions = Map.Map String Position
parse :: Bool -> [Token] -> ExceptT String IO (AST, Strings, Strings) parse :: Bool -> Positions -> [Token]
parse _ [] = return mempty -> ExceptT String IO (AST, Strings, Positions)
parse oversizedNumbers tokens = do parse _ partsHave [] = return ([], Set.empty, partsHave)
parse oversizedNumbers partsHave tokens = do
(ast, finalState) <- runStateT parseMain initialState (ast, finalState) <- runStateT parseMain initialState
return (ast, pPartsUsed finalState, pPartsHave finalState) return (ast, pPartsUsed finalState, pPartsHave finalState)
where where
position = tokenPosition $ head tokens position = tokenPosition $ head tokens
initialState = ParseData position tokens oversizedNumbers mempty mempty initialState = ParseData position tokens oversizedNumbers mempty partsHave
positionKeep :: (Token -> ParseState a) -> ParseState a positionKeep :: (Token -> ParseState a) -> ParseState a
positionKeep cont = do positionKeep cont = do
@ -1885,8 +1888,14 @@ recordPartUsed item = return item
recordPartHave :: Identifier -> ParseState () recordPartHave :: Identifier -> ParseState ()
recordPartHave partName = do recordPartHave partName = do
currPos <- gets pPosition
partsHave <- gets pPartsHave partsHave <- gets pPartsHave
let partsHave' = Set.insert partName partsHave case Map.lookup partName partsHave of
Nothing -> do
let partsHave' = Map.insert partName currPos partsHave
modify' $ \s -> s { pPartsHave = partsHave' } modify' $ \s -> s { pPartsHave = partsHave' }
Just prevPos ->
parseWarning currPos $ "Redefinition of " ++ show partName
++ ". Previously defined at " ++ show prevPos ++ "."
} }

View File

@ -0,0 +1,3 @@
module top;
logic x;
endmodule

View File

@ -0,0 +1,3 @@
module top;
logic y;
endmodule

View File

@ -3,6 +3,7 @@
NO_FILES_WARNING="Warning: No input files specified (try \`sv2v --help\`)" NO_FILES_WARNING="Warning: No input files specified (try \`sv2v --help\`)"
INTERFACE_WARNING="Warning: Source includes an interface but the output is empty because there are no modules without any interface ports. Please convert interfaces alongside the modules that instantiate them." INTERFACE_WARNING="Warning: Source includes an interface but the output is empty because there are no modules without any interface ports. Please convert interfaces alongside the modules that instantiate them."
PORT_CONN_ATTR_WARNING="attr.sv:6:11: Warning: Ignored port connection attributes (* foo *)(* bar *)." PORT_CONN_ATTR_WARNING="attr.sv:6:11: Warning: Ignored port connection attributes (* foo *)(* bar *)."
DUPLICATE_MODULE_WARNING="module_dupe_2.sv:2:5: Warning: Redefinition of \"top\". Previously defined at module_dupe_1.sv:2:5."
test_default() { test_default() {
runAndCapture \ runAndCapture \
@ -28,6 +29,13 @@ test_port_conn_attr() {
assertEquals "stderr should should have warning" "$PORT_CONN_ATTR_WARNING" "$stderr" assertEquals "stderr should should have warning" "$PORT_CONN_ATTR_WARNING" "$stderr"
} }
test_duplicate_module() {
runAndCapture module_dupe_1.sv module_dupe_2.sv
assertTrue "conversion should succeed" $result
assertNotNull "stdout should not be empty" "$stdout"
assertEquals "stderr should should have warning" "$DUPLICATE_MODULE_WARNING" "$stderr"
}
no_modules_test() { no_modules_test() {
file=$1 file=$1
warning="$2" warning="$2"