diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..328589c --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,5 @@ +# Instructions for GitHub Copilot + +- Refer to `doc/compiler-design-reference.md` to learn about the design of the compiler. +- Other docs in that same directory may also be helpful for understanding the language itself. +- Use BUILDING.md to understand how to build the project and run tests. diff --git a/compiler/cpp/circt_util.cpp b/compiler/cpp/circt_util.cpp index d040af3..0a3769b 100644 --- a/compiler/cpp/circt_util.cpp +++ b/compiler/cpp/circt_util.cpp @@ -497,7 +497,13 @@ circt::hw::ArrayType GetPackedArrayTypeParameterizedSize(const mlir::Type &eleme circt::seq::ClockType GetClockType() { return circt::seq::ClockType::get(g_compiler->GetMlirContext()); } -mlir::Type ToMlirType(const Type *typeIn, bool signedness) +// Internal helper: converts a Kanagawa Type to an MLIR type, using the provided +// 'recurse' callback for recursive member/element type conversion. +// This avoids duplicating struct/union/array lowering logic between +// ToMlirType and ToMlirTypeAliased. +using TypeRecurseFn = std::function; + +static mlir::Type ToMlirTypeImpl(const Type *typeIn, bool signedness, const TypeRecurseFn &recurse) { const BoolType *boolType = dynamic_cast(typeIn); const ArrayType *arrayType = dynamic_cast(typeIn); @@ -511,7 +517,7 @@ mlir::Type ToMlirType(const Type *typeIn, bool signedness) } else if (arrayType) { - return GetPackedArrayType(ToMlirType(arrayType->_elementType, signedness), arrayType->_arraySize); + return GetPackedArrayType(recurse(arrayType->_elementType, signedness), arrayType->_arraySize); } else if (floatType) { @@ -523,17 +529,11 @@ mlir::Type ToMlirType(const Type *typeIn, bool signedness) { llvm::SmallVector fields; - const auto addField = [&](const std::string &name, const Type *const type) - { - fields.push_back( - circt::hw::StructType::FieldInfo{StringToStringAttr(name), ToMlirType(type, signedness)}); - }; - for (const StructUnionType::EntryType &member : structUnionType->_members) { const Type *const memberType = member.second->GetDeclaredType(); - const std::string memberName = member.first; - addField(memberName, memberType); + fields.push_back( + circt::hw::StructType::FieldInfo{StringToStringAttr(member.first), recurse(memberType, signedness)}); } std::reverse(fields.begin(), fields.end()); @@ -543,17 +543,11 @@ mlir::Type ToMlirType(const Type *typeIn, bool signedness) { llvm::SmallVector fields; - const auto addField = [&](const std::string &name, const Type *const type) - { - fields.push_back( - circt::hw::UnionType::FieldInfo{StringToStringAttr(name), ToMlirType(type, signedness)}); - }; - for (const StructUnionType::EntryType &member : structUnionType->_members) { const Type *const memberType = member.second->GetDeclaredType(); - const std::string memberName = member.first; - addField(memberName, memberType); + fields.push_back( + circt::hw::UnionType::FieldInfo{StringToStringAttr(member.first), recurse(memberType, signedness)}); } std::reverse(fields.begin(), fields.end()); @@ -579,6 +573,25 @@ mlir::Type ToMlirType(const Type *typeIn, bool signedness) } } +mlir::Type ToMlirType(const Type *typeIn, bool signedness) +{ + return ToMlirTypeImpl(typeIn, signedness, [](const Type *t, bool s) { return ToMlirType(t, s); }); +} + +mlir::Type ToMlirTypeAliased(const Type *typeIn, bool signedness, ModuleDeclarationHelper &helper) +{ + // Check if this type has a registered alias + auto alias = helper.GetTypeAlias(typeIn); + if (alias) + { + return *alias; + } + + // Delegate to the shared implementation with alias-aware recursion + return ToMlirTypeImpl(typeIn, signedness, + [&helper](const Type *t, bool s) { return ToMlirTypeAliased(t, s, helper); }); +} + // Used to avoid symbol name conflicts for elements like container ports // returns a symbol name which will be unique provided // that flattened container paths are unique @@ -1720,7 +1733,7 @@ void ModuleDeclarationHelper::AssertStructsMatch(const mlir::Type &circtTypeAlia _verbatimBuffer.Str() << "end"; } -mlir::Type ModuleDeclarationHelper::GetTypeAlias(const std::string &name, const mlir::Type &referencedType) +mlir::Type ModuleDeclarationHelper::CreateTypeAlias(const std::string &name, const mlir::Type &referencedType) { // AddTypedefs must be called first assert(_typeScopeOp); @@ -1731,10 +1744,114 @@ mlir::Type ModuleDeclarationHelper::GetTypeAlias(const std::string &name, const return circt::hw::TypeAliasType::get(symbolRefAttr, referencedType); } +void ModuleDeclarationHelper::RegisterNamedType(const Type *kanagawaType) +{ + assert(_typeScopeOp); + + // Skip if already registered + if (_typeAliasCache.count(kanagawaType)) + { + return; + } + + const StructUnionType *structUnionType = dynamic_cast(kanagawaType); + const EnumType *enumType = dynamic_cast(kanagawaType); + + std::string typeName; + if (structUnionType) + { + typeName = structUnionType->GetName(); + } + else if (enumType) + { + typeName = enumType->GetName(); + } + + // Only register types with a non-empty name. + if (typeName.empty()) + { + return; + } + + // Kanagawa type names typically contain '.' from namespacing. + // Normalize identifier the same way the SV backend does. + typeName = FixupString(typeName); + + // Check if a different Type* with the same name was already registered. + // Reuse the existing alias to prevent duplicate hw.typedecl symbols, + // but verify the underlying layout matches to catch conflicting definitions. + auto nameIt = _typeAliasByName.find(typeName); + if (nameIt != _typeAliasByName.end()) + { + circt::hw::TypeAliasType existingAlias = llvm::cast(nameIt->second); + mlir::Type newMlirType = ToMlirTypeAliased(kanagawaType, true, *this); + if (existingAlias.getInnerType() != newMlirType) + { + throw std::runtime_error("Conflicting named type definitions for '" + typeName + + "': existing alias has a different underlying layout"); + } + _typeAliasCache[kanagawaType] = nameIt->second; + return; + } + + // Note: we do NOT recursively register member types here. + // The caller (DeclareCore) iterates _exportedTypes which is already + // topologically sorted by SortExportedTypes(), so member types are + // registered before their containing structs. Recursing here would + // crash on non-hardware member types (ClassType, ReferenceType, etc.) + // that can't be converted to MLIR. + + // Verify the type can be converted to MLIR before attempting registration. + // Some exported types (e.g., structs containing callback function members) + // have members that ToMlirType cannot handle. Skip those silently. + if (structUnionType) + { + for (const StructUnionType::EntryType &member : structUnionType->_members) + { + const Type *memberType = member.second->GetDeclaredType(); + if (!dynamic_cast(memberType) && !dynamic_cast(memberType) && + !dynamic_cast(memberType) && !dynamic_cast(memberType) && + !dynamic_cast(memberType)) + { + // Member type is not MLIR-convertible — skip this type + return; + } + } + } + + // Build the MLIR type using the alias-aware conversion so inner named types use aliases. + // Use signedness=true to match the ESI wrapper consumer (the only caller of ToMlirTypeAliased), + // which passes signedness=true to produce signed/unsigned integer types. + mlir::Type mlirType = ToMlirTypeAliased(kanagawaType, true, *this); + + // Create the TypedeclOp in the TypeScope block + { + circt::OpBuilder::InsertionGuard g(_opb); + _opb.setInsertionPointToEnd(_typeScopeOp.getBodyBlock()); + circt::hw::TypedeclOp::create(_opb, _location, StringToStringAttr(typeName), mlirType, + StringToStringAttr(typeName)); + } + + // Create and cache the type alias + mlir::Type aliasType = CreateTypeAlias(typeName, mlirType); + _typeAliasCache[kanagawaType] = aliasType; + _typeAliasByName[typeName] = aliasType; +} + +std::optional ModuleDeclarationHelper::GetTypeAlias(const Type *kanagawaType) const +{ + auto it = _typeAliasCache.find(kanagawaType); + if (it != _typeAliasCache.end()) + { + return it->second; + } + return std::nullopt; +} + mlir::Type ModuleDeclarationHelper::GetInspectableTypeAlias() { assert(GetCodeGenConfig()._inspection); - return GetTypeAlias(InspectableValueName, GetInspectableStructType()); + return CreateTypeAlias(InspectableValueName, GetInspectableStructType()); } mlir::ModuleOp ModuleDeclarationHelper::MlirModule() { return _mlirModule; } @@ -1884,7 +2001,7 @@ void ModuleDeclarationHelper::EmitEsiWrapper(const std::string &circtDesignName) break; case EsiPortSemantics::Payload: - bundlePayloadTypes.push_back(ToMlirType(portInfo._origType, true)); + bundlePayloadTypes.push_back(ToMlirTypeAliased(portInfo._origType, true, *this)); payloadTypes.push_back(portInfo._hwPortInfo.type); payloadNames.push_back(portInfo._hwPortInfo.name.str()); payloadFieldNames.push_back(portInfo._fieldName); @@ -2035,7 +2152,7 @@ void ModuleDeclarationHelper::EmitEsiWrapper(const std::string &circtDesignName) payload.push_back(ReadContainerPort( _opb, _location, pathToContainer, GetFullyQualifiedStringAttr(ObjectPath(), portInfo._hwPortInfo.name.str()), - portInfo._hwPortInfo.type, ToMlirType(portInfo._origType, true))); + portInfo._hwPortInfo.type, ToMlirTypeAliased(portInfo._origType, true, *this))); } break; diff --git a/compiler/cpp/circt_util.h b/compiler/cpp/circt_util.h index 3272670..c505b71 100644 --- a/compiler/cpp/circt_util.h +++ b/compiler/cpp/circt_util.h @@ -86,6 +86,9 @@ circt::seq::ClockType GetClockType(); mlir::Type ToMlirType(const Type* typeIn, bool signedness = false); +class ModuleDeclarationHelper; +mlir::Type ToMlirTypeAliased(const Type* typeIn, bool signedness, ModuleDeclarationHelper& helper); + mlir::Value GetTypedZeros(circt::OpBuilder& opb, const mlir::Location& location, const mlir::Type& typeIn); mlir::Value PopCount(circt::OpBuilder& opb, const mlir::Location location, const mlir::ValueRange values, @@ -316,6 +319,10 @@ class ModuleDeclarationHelper void AddTypedefs(const std::string& typeScopeName); + void RegisterNamedType(const Type* kanagawaType); + + std::optional GetTypeAlias(const Type* kanagawaType) const; + mlir::Type GetInspectableTypeAlias(); mlir::ModuleOp MlirModule(); @@ -350,7 +357,7 @@ class ModuleDeclarationHelper const std::string& circtDesignName, const mlir::Type type); private: - mlir::Type GetTypeAlias(const std::string& name, const mlir::Type& referencedType); + mlir::Type CreateTypeAlias(const std::string& name, const mlir::Type& referencedType); private: void AssertStructsMatch(const mlir::Type& circtTypeAlias, const std::string& otherStructName); @@ -395,6 +402,13 @@ class ModuleDeclarationHelper circt::hw::TypeScopeOp _typeScopeOp; + // Maps Kanagawa Type* to hw::TypeAliasType for named types + std::map _typeAliasCache; + + // Maps type name to TypeAliasType to prevent duplicate TypedeclOps + // when distinct Type* pointers share the same name + std::map _typeAliasByName; + bool _finished; bool _exportVerilog; diff --git a/compiler/cpp/internal_tests.cpp b/compiler/cpp/internal_tests.cpp index c596d9a..b636bb4 100644 --- a/compiler/cpp/internal_tests.cpp +++ b/compiler/cpp/internal_tests.cpp @@ -2502,6 +2502,93 @@ void ArrayTypeStringTest() TestAssert("([[memory]] uint8[4])[64][32]" == mem_u8_64_32_4->GetName()); } +// Test that RegisterNamedType creates hw::TypeAliasType for named types +// and that ToMlirTypeAliased returns the alias for registered types +void TypeAliasTest() +{ + // Create a RedirectableSourceWriter for ModuleDeclarationHelper + class TestSourceWriter : public RedirectableSourceWriter + { + protected: + std::ostream& GetStreamImpl() override { return _str; } + + private: + std::ostringstream _str; + }; + + Location loc = {}; + const LeafType* const u8Type = g_compiler->GetLeafType(BaseType::Uint, 8, loc); + const LeafType* const u16Type = g_compiler->GetLeafType(BaseType::Uint, 16, loc); + + // Create an EnumType (no DeclareNode needed) + std::vector enumConstants = {{"Red", 0}, {"Green", 1}, {"Blue", 2}}; + EnumType testEnum("TestColor", u8Type, enumConstants, loc); + + // Create an MLIR module and ModuleDeclarationHelper + TestSourceWriter writer; + mlir::ModuleOp mlirModule = CreateMlirModuleAndDesign(GetUnknownLocation(), "TestDesign"); + + // Scope the helper so it is destroyed before we erase the module + { + ModuleDeclarationHelper helper(writer, "TestModule", GetUnknownLocation(), "TestDesign", &mlirModule); + helper.AddTypedefs("TestTypeScope"); + + // Before registration, ToMlirTypeAliased should not return an alias + mlir::Type aliasedBefore = ToMlirTypeAliased(&testEnum, false, helper); + TestAssert(!llvm::isa(aliasedBefore)); + + // Register the enum type + helper.RegisterNamedType(&testEnum); + + // After registration, GetTypeAlias should return a TypeAliasType + auto alias = helper.GetTypeAlias(&testEnum); + TestAssert(alias.has_value()); + TestAssert(llvm::isa(*alias)); + + // The alias should have the correct name + circt::hw::TypeAliasType aliasType = llvm::cast(*alias); + TestAssertEqual(std::string("TestColor"), aliasType.getRef().getLeafReference().str()); + + // The alias inner type should use signed integer types (signedness=true), + // matching the ESI wrapper which is the consumer of type aliases + mlir::Type expectedInnerType = ToMlirType(&testEnum, true); + TestAssert(aliasType.getInnerType() == expectedInnerType); + + // ToMlirTypeAliased should return the alias for BOTH signedness values. + // This is critical: EmitEsiWrapper calls with signedness=true, + // so aliases must be returned regardless of the signedness flag. + mlir::Type aliasedSignedFalse = ToMlirTypeAliased(&testEnum, false, helper); + TestAssert(llvm::isa(aliasedSignedFalse)); + + mlir::Type aliasedSignedTrue = ToMlirTypeAliased(&testEnum, true, helper); + TestAssert(llvm::isa(aliasedSignedTrue)); + + // Both should return the same alias + TestAssert(aliasedSignedFalse == aliasedSignedTrue); + + // Registering the same type twice should be a no-op + helper.RegisterNamedType(&testEnum); + auto alias2 = helper.GetTypeAlias(&testEnum); + TestAssert(alias2.has_value()); + TestAssert(*alias == *alias2); + + // A plain LeafType (not named) should not get an alias + helper.RegisterNamedType(u16Type); + auto noAlias = helper.GetTypeAlias(u16Type); + TestAssert(!noAlias.has_value()); + + // Verify the MLIR module contains the typedecl + std::string mlirStr; + llvm::raw_string_ostream os(mlirStr); + mlirModule.print(os); + TestAssert(mlirStr.find("hw.typedecl @TestColor") != std::string::npos); + TestAssert(mlirStr.find("TestTypeScope") != std::string::npos); + } + + // Clean up - helper is out of scope, safe to erase the module + mlirModule->erase(); +} + int InternalTests() { int result = -1; @@ -2538,6 +2625,8 @@ int InternalTests() ArrayTypeStringTest(); + TypeAliasTest(); + FixupLutTest(); LutOperationsProduceSameResultTest(); diff --git a/compiler/cpp/verilog.cpp b/compiler/cpp/verilog.cpp index 7e5fb98..c47b3c8 100644 --- a/compiler/cpp/verilog.cpp +++ b/compiler/cpp/verilog.cpp @@ -6562,8 +6562,9 @@ class VerilogCompiler coreModule.AddPort(port._name, port._input ? circt::hw::ModulePort::Direction::Input : circt::hw::ModulePort::Direction::Output, - ToMlirType(port._type), port._type, port._portSemantics, port._channelSemantics, - port._channelName, port._fieldName); + ToMlirType(port._type), port._type, + port._portSemantics, port._channelSemantics, port._channelName, + port._fieldName); } } @@ -6628,7 +6629,8 @@ class VerilogCompiler { const Type *type = functionNode->GetParameterType(i); coreModule.AddPort(prefix + "_" + functionNode->GetParameterName(i) + "_out", - circt::hw::ModulePort::Direction::Output, ToMlirType(type), type, + circt::hw::ModulePort::Direction::Output, + ToMlirType(type), type, EsiPortSemantics::Payload, EsiChannelSemantics::FromGeneratedHw, EsiChannelName::Arguments, functionNode->GetParameterName(i)); } @@ -6652,8 +6654,9 @@ class VerilogCompiler { const Type *type = functionNode->GetReturnType(); coreModule.AddPort(prefix + "_result_in", circt::hw::ModulePort::Direction::Input, - ToMlirType(type), type, EsiPortSemantics::Payload, - EsiChannelSemantics::ToGeneratedHw, EsiChannelName::Results); + ToMlirType(type), type, + EsiPortSemantics::Payload, EsiChannelSemantics::ToGeneratedHw, + EsiChannelName::Results); } if (!isNoBackpressure) @@ -7109,6 +7112,19 @@ class VerilogCompiler // to ensure that the generate `ifndef _TYPESCOPE_* macros all agree coreModule.AddTypedefs("CoreModuleTypeScope"); + // Register named types as type aliases in the CIRCT IR type scope + // so that port types and ESI channel payloads use named type aliases + // _exportedTypes is already topologically sorted by SortExportedTypes() + for (const Type *const type : _program._exportedTypes) + { + coreModule.RegisterNamedType(type); + } + + for (const auto &t : _program._exportedTypedefs) + { + coreModule.RegisterNamedType(t.second); + } + // Input and outputs of the KanagawaCore module DeclareCorePorts(coreModule, resetReplicas);