From 15184f548103c8e5c377be9f4aa4607decbbd25c Mon Sep 17 00:00:00 2001 From: Andrew Nolte Date: Wed, 15 Jul 2026 12:38:32 -0400 Subject: [PATCH] Compilation: Add AllowInvalidTop flag This flag sets error types for unset top level params, allowing for better diagnostics when not in a full design context. stack-info: PR: https://github.com/MikePopoloski/slang/pull/1900, branch: AndrewNolte/stack/17 --- bindings/python/CompBindings.cpp | 1 + include/slang/ast/Compilation.h | 7 ++-- include/slang/ast/symbols/InstanceSymbols.h | 3 +- source/ast/Compilation.cpp | 2 ++ source/ast/symbols/InstanceSymbols.cpp | 26 ++++++++++----- tests/unittests/ast/HierarchyTests.cpp | 37 +++++++++++++++++++++ 6 files changed, 64 insertions(+), 12 deletions(-) diff --git a/bindings/python/CompBindings.cpp b/bindings/python/CompBindings.cpp index 9fd4599b4..df41521cd 100644 --- a/bindings/python/CompBindings.cpp +++ b/bindings/python/CompBindings.cpp @@ -70,6 +70,7 @@ void registerCompilation(py::module_& m, py::module_& ast, py::module_& driver) .value("AllowVirtualIfaceWithOverride", CompilationFlags::AllowVirtualIfaceWithOverride) .value("AllowArrayConcatAssignPattern", CompilationFlags::AllowArrayConcatAssignPattern) .value("AllowCrossAutoBinMax", CompilationFlags::AllowCrossAutoBinMax) + .value("AllowInvalidTop", CompilationFlags::AllowInvalidTop) .finalize(); py::classh(ast, "CompilationOptions") diff --git a/include/slang/ast/Compilation.h b/include/slang/ast/Compilation.h index 3d4691342..8af7511ec 100644 --- a/include/slang/ast/Compilation.h +++ b/include/slang/ast/Compilation.h @@ -141,9 +141,12 @@ enum class SLANG_EXPORT CompilationFlags { /// instead of a net. By default slang follows the LRM and treats such ports as nets, /// which for example allows them to be connected to `inout` ports. Some tools treat them /// as variables instead; enabling this flag selects that behavior. - InferInputPortsAsVars = 1 << 19 + InferInputPortsAsVars = 1 << 19, + + /// Allow top-level modules to be selected even when their parameters have no defaults. + AllowInvalidTop = 1 << 20, }; -SLANG_BITMASK(CompilationFlags, InferInputPortsAsVars) +SLANG_BITMASK(CompilationFlags, AllowInvalidTop) /// Contains various options that can control compilation behavior. struct SLANG_EXPORT CompilationOptions { diff --git a/include/slang/ast/symbols/InstanceSymbols.h b/include/slang/ast/symbols/InstanceSymbols.h index 282c70b9f..c80a0daa6 100644 --- a/include/slang/ast/symbols/InstanceSymbols.h +++ b/include/slang/ast/symbols/InstanceSymbols.h @@ -145,8 +145,7 @@ class SLANG_EXPORT InstanceSymbol final : public InstanceSymbolBase { static InstanceSymbol& createDefault( Compilation& compilation, const DefinitionSymbol& definition, const HierarchyOverrideNode* hierarchyOverrideNode = nullptr, - const ConfigBlockSymbol* configBlock = nullptr, const ConfigRule* configRule = nullptr, - SourceLocation locationOverride = {}); + const ConfigBlockSymbol* configBlock = nullptr, const ConfigRule* configRule = nullptr); /// Creates a placeholder instance for a virtual interface type declaration. static const InstanceSymbol& createVirtual( diff --git a/source/ast/Compilation.cpp b/source/ast/Compilation.cpp index a8b85ce97..d63531c23 100644 --- a/source/ast/Compilation.cpp +++ b/source/ast/Compilation.cpp @@ -316,6 +316,8 @@ const RootSymbol& Compilation::getRoot(bool skipDefParamsAndBinds) { auto guard = ScopeGuard([this] { finalizing = false; }); auto isValidTop = [&](auto& definition) { + if (hasFlag(CompilationFlags::AllowInvalidTop)) + return true; // All parameters must have defaults. for (auto& param : definition.parameters) { if (!param.hasDefault() && diff --git a/source/ast/symbols/InstanceSymbols.cpp b/source/ast/symbols/InstanceSymbols.cpp index b17303490..f68b32042 100644 --- a/source/ast/symbols/InstanceSymbols.cpp +++ b/source/ast/symbols/InstanceSymbols.cpp @@ -373,12 +373,11 @@ InstanceSymbol::InstanceSymbol(Compilation& compilation, std::string_view name, InstanceSymbol& InstanceSymbol::createDefault(Compilation& comp, const DefinitionSymbol& definition, const HierarchyOverrideNode* hierarchyOverrideNode, const ConfigBlockSymbol* configBlock, - const ConfigRule* configRule, - SourceLocation locationOverride) { - auto loc = locationOverride ? locationOverride : definition.location; - auto& body = InstanceBodySymbol::fromDefinition(comp, definition, loc, InstanceFlags::None, - hierarchyOverrideNode, configBlock, configRule); - auto& result = *comp.emplace(definition.name, loc, body, 0u); + const ConfigRule* configRule) { + auto& body = InstanceBodySymbol::fromDefinition(comp, definition, definition.location, + InstanceFlags::None, hierarchyOverrideNode, + configBlock, configRule); + auto& result = *comp.emplace(definition.name, definition.location, body, 0u); if (configBlock) { auto rc = comp.emplace(*configBlock, result); @@ -861,8 +860,15 @@ static Symbol* recurseDefaultIfaceInst(Compilation& comp, const InterfacePortSym std::span::iterator it, std::span::iterator end) { if (it == end) { - auto& result = InstanceSymbol::createDefault(comp, *port.interfaceDef, nullptr, nullptr, - nullptr, port.location); + auto& def = *port.interfaceDef; + ParameterBuilder paramBuilder(*def.getParentScope(), def.name, def.parameters); + if (comp.hasFlag(CompilationFlags::AllowInvalidTop)) { + paramBuilder.setSuppressErrors(true); + } + auto& body = InstanceBodySymbol::fromDefinition(comp, def, port.location, paramBuilder, + InstanceFlags::None); + + auto& result = *comp.emplace(port.name, port.location, body, 0u); if (!firstInst) firstInst = &result; @@ -964,6 +970,10 @@ InstanceBodySymbol& InstanceBodySymbol::fromDefinition( ParameterBuilder paramBuilder(*definition.getParentScope(), definition.name, definition.parameters); paramBuilder.setForceInvalidValues(flags.has(InstanceFlags::Uninstantiated)); + if (compilation.hasFlag(CompilationFlags::AllowInvalidTop) && + instanceLoc == definition.location) { + paramBuilder.setSuppressErrors(true); + } if (hierarchyOverrideNode) paramBuilder.setOverrides(hierarchyOverrideNode); diff --git a/tests/unittests/ast/HierarchyTests.cpp b/tests/unittests/ast/HierarchyTests.cpp index f272ae0ff..e667bb279 100644 --- a/tests/unittests/ast/HierarchyTests.cpp +++ b/tests/unittests/ast/HierarchyTests.cpp @@ -983,6 +983,43 @@ endmodule CHECK(unusedDefs[1]->name == "nottop"); } +TEST_CASE("Allow invalid top module parameters") { + auto text = R"( +module top #(parameter int p, parameter type t); +endmodule +)"; + + // Without the flag the explicitly requested top is rejected for having + // non-defaulted parameters. + { + CompilationOptions options; + options.topModules.emplace("top"sv); + + Compilation compilation(options); + compilation.addSyntaxTree(SyntaxTree::fromText(text)); + + auto& diags = compilation.getAllDiagnostics(); + REQUIRE(diags.size() == 1); + CHECK(diags[0].code == diag::InvalidTopModule); + } + + // With the flag it elaborates anyway; the missing parameters get error types + // rather than producing errors. + { + CompilationOptions options; + options.flags |= CompilationFlags::AllowInvalidTop; + options.topModules.emplace("top"sv); + + Compilation compilation(options); + compilation.addSyntaxTree(SyntaxTree::fromText(text)); + NO_COMPILATION_ERRORS; + + auto& top = *compilation.getRoot().topInstances[0]; + CHECK(top.body.find("p").getValue().bad()); + CHECK(top.body.find("t").targetType.getType().isError()); + } +} + TEST_CASE("No top warning") { auto tree = SyntaxTree::fromText(R"( )");