diff --git a/include/slang/ast/Compilation.h b/include/slang/ast/Compilation.h index 4cc0c8efa..48d4e44b3 100644 --- a/include/slang/ast/Compilation.h +++ b/include/slang/ast/Compilation.h @@ -724,7 +724,7 @@ class SLANG_EXPORT Compilation : public BumpAllocator { // module has ever been instantiated to know whether it should be considered top-level. flat_hash_set globalInstantiations; - struct DefinitionMetadata { + struct SyntaxMetadata { const syntax::SyntaxTree* tree = nullptr; const SourceLibrary* library = nullptr; const NetType* defaultNetType = nullptr; @@ -733,7 +733,7 @@ class SLANG_EXPORT Compilation : public BumpAllocator { }; // Map from syntax nodes to parse-time metadata about them. - flat_hash_map definitionMetadata; + flat_hash_map syntaxMetadata; // The name map for all module, interface, and program definitions. // The key is a combination of definition name + the scope in which it was declared. diff --git a/source/ast/Compilation.cpp b/source/ast/Compilation.cpp index 82356170a..a90ecbb93 100644 --- a/source/ast/Compilation.cpp +++ b/source/ast/Compilation.cpp @@ -208,8 +208,7 @@ void Compilation::addSyntaxTree(std::shared_ptr tree) { compilationUnits.push_back(unit); for (auto& [n, meta] : tree->getMetadata().nodeMap) { - auto decl = &n->as(); - DefinitionMetadata result; + SyntaxMetadata result; result.tree = tree.get(); result.library = meta.library; result.defaultNetType = &getNetType(meta.defaultNetType); @@ -227,7 +226,7 @@ void Compilation::addSyntaxTree(std::shared_ptr tree) { break; } - definitionMetadata[decl] = result; + syntaxMetadata[n] = result; } for (auto& name : tree->getMetadata().globalInstances) @@ -345,13 +344,21 @@ const RootSymbol& Compilation::getRoot(bool skipDefParamsAndBinds) { continue; for (auto def : defList) { - // TODO: allow these to include a library name - if (def->sourceLibrary) - continue; - if (def->definitionKind == DefinitionKind::Module || def->definitionKind == DefinitionKind::Program) { - if (auto it = tm.find(def->name); it != tm.end()) { + + // If this definition is in a library, it can only be targeted as + // a top module by the user including the library name in the string. + flat_hash_set::iterator it; + if (def->sourceLibrary) { + auto target = fmt::format("{}.{}", def->sourceLibrary->name, def->name); + it = tm.find(target); + } + else { + it = tm.find(def->name); + } + + if (it != tm.end()) { // Remove from the top modules set so that we know we visited it. tm.erase(it); @@ -368,20 +375,36 @@ const RootSymbol& Compilation::getRoot(bool skipDefParamsAndBinds) { } // Otherwise this definition might be unreferenced and not automatically - // instantiated. - if (globalInstantiations.find(def->name) == globalInstantiations.end()) + // instantiated (don't add library definitions to this list though). + if (globalInstantiations.find(def->name) == globalInstantiations.end() && + !def->sourceLibrary) { unreferencedDefs.push_back(def); + } } } // Any names still in the map were not found as module definitions. - for (auto& name : tm) { - // This might be a config block instead. - if (auto confIt = configBlocks.find(name); confIt != configBlocks.end()) { + for (auto& userProvidedName : tm) { + // This might be a config block instead. If it has a dot, assume that's + // the library designator. + std::string confLib; + std::string confName(userProvidedName); + if (auto pos = confName.find_first_of('.'); pos != std::string::npos) { + confLib = confName.substr(0, pos); + confName = confName.substr(pos + 1); + } + + // A trailing ':config' is stripped -- it's there to allow the user + // to disambiguate modules and config blocks. + constexpr std::string_view configSuffix = ":config"; + if (confName.ends_with(configSuffix)) + confName = confName.substr(0, confName.length() - configSuffix.length()); + + if (auto confIt = configBlocks.find(confName); confIt != configBlocks.end()) { const ConfigBlockSymbol* foundConf = nullptr; for (auto conf : confIt->second) { - // TODO: handle configs in libraries - if (!conf->sourceLibrary) { + if ((!conf->sourceLibrary && confLib.empty()) || + (conf->sourceLibrary && conf->sourceLibrary->name == confLib)) { foundConf = conf; break; } @@ -414,7 +437,7 @@ const RootSymbol& Compilation::getRoot(bool skipDefParamsAndBinds) { } } - root->addDiag(diag::InvalidTopModule, SourceLocation::NoLocation) << name; + root->addDiag(diag::InvalidTopModule, SourceLocation::NoLocation) << userProvidedName; } } @@ -582,7 +605,7 @@ void Compilation::createDefinition(const Scope& scope, LookupLocation location, // We can only be missing metadata if the definition is created programmatically // (i.e. not via the parser) so we just fill in the parent's default net type // so that it's not a null pointer. - auto& metadata = definitionMetadata[&syntax]; + auto& metadata = syntaxMetadata[&syntax]; if (!metadata.defaultNetType) metadata.defaultNetType = &scope.getDefaultNetType(); @@ -665,7 +688,7 @@ const PackageSymbol* Compilation::getPackage(std::string_view lookupName) const const PackageSymbol& Compilation::createPackage(const Scope& scope, const ModuleDeclarationSyntax& syntax) { - auto& metadata = definitionMetadata[&syntax]; + auto& metadata = syntaxMetadata[&syntax]; if (!metadata.defaultNetType) metadata.defaultNetType = &scope.getDefaultNetType(); @@ -687,8 +710,9 @@ const PackageSymbol& Compilation::createPackage(const Scope& scope, const ConfigBlockSymbol& Compilation::createConfigBlock(const Scope& scope, const ConfigDeclarationSyntax& syntax) { - // TODO: set sourcelibrary pointer + auto& metadata = syntaxMetadata[&syntax]; auto& config = ConfigBlockSymbol::fromSyntax(scope, syntax); + config.sourceLibrary = metadata.library; auto it = configBlocks.find(config.name); if (it == configBlocks.end()) { diff --git a/source/parsing/ParserMetadata.cpp b/source/parsing/ParserMetadata.cpp index 3aa399924..e409b4085 100644 --- a/source/parsing/ParserMetadata.cpp +++ b/source/parsing/ParserMetadata.cpp @@ -96,6 +96,11 @@ class MetadataVisitor : public SyntaxVisitor { meta.nodeMap[&syntax] = {library, defaultNetType, unconnectedDrive, timeScale}; } + void handle(const ConfigDeclarationSyntax& syntax) { + visitDefault(syntax); + meta.nodeMap[&syntax] = {library, defaultNetType, unconnectedDrive, timeScale}; + } + void visitToken(Token token) { // Look through the token's trivia for any preprocessor directives // that might need to be captured in the metadata for module decls. diff --git a/source/parsing/Parser_members.cpp b/source/parsing/Parser_members.cpp index 4f26c5ff1..695579b8d 100644 --- a/source/parsing/Parser_members.cpp +++ b/source/parsing/Parser_members.cpp @@ -3398,6 +3398,10 @@ ConfigDeclarationSyntax& Parser::parseConfigDeclaration(AttrList attributes) { auto name = expect(TokenKind::Identifier); auto semi1 = expect(TokenKind::Semicolon); + auto& pp = getPP(); + ParserMetadata::Node node{pp.getCurrentLibrary(), pp.getDefaultNetType(), + pp.getUnconnectedDrive(), pp.getTimeScale()}; + SmallVector localparams; while (peek(TokenKind::LocalParamKeyword)) { Token paramSemi; @@ -3465,9 +3469,11 @@ ConfigDeclarationSyntax& Parser::parseConfigDeclaration(AttrList attributes) { auto blockName = parseNamedBlockClause(); checkBlockNames(name, blockName); - return factory.configDeclaration(attributes, config, name, semi1, localparams.copy(alloc), - design, topCells.copy(alloc), semi2, rules.copy(alloc), - endconfig, blockName); + auto& result = factory.configDeclaration(attributes, config, name, semi1, + localparams.copy(alloc), design, topCells.copy(alloc), + semi2, rules.copy(alloc), endconfig, blockName); + meta.nodeMap[&result] = node; + return result; } MemberSyntax* Parser::parseLibraryMember() { diff --git a/tests/unittests/ast/ConfigTests.cpp b/tests/unittests/ast/ConfigTests.cpp index ce8a2612a..027fa9328 100644 --- a/tests/unittests/ast/ConfigTests.cpp +++ b/tests/unittests/ast/ConfigTests.cpp @@ -83,6 +83,33 @@ TEST_CASE("Driver library explicit ordering") { CHECK(m.getDefinition().sourceLibrary->name == "lib2"); } +TEST_CASE("Top module in a library") { + auto lib1 = std::make_unique("lib1", 1); + + auto tree1 = SyntaxTree::fromText(R"( +module mod; +endmodule +)", + SyntaxTree::getDefaultSourceManager(), "source", "", {}, + lib1.get()); + auto tree2 = SyntaxTree::fromText(R"( +module top; +endmodule +)"); + + CompilationOptions options; + options.topModules.emplace("lib1.mod"); + + Compilation compilation(options); + compilation.addSyntaxTree(tree1); + compilation.addSyntaxTree(tree2); + NO_COMPILATION_ERRORS; + + auto topInstances = compilation.getRoot().topInstances; + CHECK(topInstances.size() == 1); + CHECK(topInstances[0]->name == "mod"); +} + TEST_CASE("Config block top modules") { auto tree = SyntaxTree::fromText(R"( config cfg1; @@ -107,3 +134,53 @@ endmodule CHECK(topInstances.size() == 1); CHECK(topInstances[0]->name == "frob"); } + +TEST_CASE("Config in library, disambiguate with module name") { + auto lib1 = std::make_unique("lib1", 1); + auto lib2 = std::make_unique("lib2", 2); + + auto tree1 = SyntaxTree::fromText(R"( +module mod; +endmodule + +config cfg; + design mod; +endconfig +)", + SyntaxTree::getDefaultSourceManager(), "source", "", {}, + lib1.get()); + auto tree2 = SyntaxTree::fromText(R"( +module mod; +endmodule + +module cfg; +endmodule + +config cfg; + design mod; +endconfig +)", + SyntaxTree::getDefaultSourceManager(), "source", "", {}, + lib2.get()); + auto tree3 = SyntaxTree::fromText(R"( +module mod; +endmodule + +config cfg; + design mod; +endconfig +)"); + + CompilationOptions options; + options.topModules.emplace("lib2.cfg:config"); + + Compilation compilation(options); + compilation.addSyntaxTree(tree1); + compilation.addSyntaxTree(tree2); + compilation.addSyntaxTree(tree3); + NO_COMPILATION_ERRORS; + + auto top = compilation.getRoot().topInstances[0]; + CHECK(top->name == "mod"); + CHECK(top->getDefinition().sourceLibrary->name == "lib2"); +}