Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -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.
161 changes: 139 additions & 22 deletions compiler/cpp/circt_util.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<mlir::Type(const Type *, bool)>;

static mlir::Type ToMlirTypeImpl(const Type *typeIn, bool signedness, const TypeRecurseFn &recurse)
{
const BoolType *boolType = dynamic_cast<const BoolType *>(typeIn);
const ArrayType *arrayType = dynamic_cast<const ArrayType *>(typeIn);
Expand All @@ -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)
{
Expand All @@ -523,17 +529,11 @@ mlir::Type ToMlirType(const Type *typeIn, bool signedness)
{
llvm::SmallVector<circt::hw::StructType::FieldInfo> 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());
Expand All @@ -543,17 +543,11 @@ mlir::Type ToMlirType(const Type *typeIn, bool signedness)
{
llvm::SmallVector<circt::hw::UnionType::FieldInfo> 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());
Expand All @@ -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
Expand Down Expand Up @@ -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);
Expand All @@ -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<const StructUnionType *>(kanagawaType);
const EnumType *enumType = dynamic_cast<const EnumType *>(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<circt::hw::TypeAliasType>(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<const BoolType *>(memberType) && !dynamic_cast<const LeafType *>(memberType) &&
!dynamic_cast<const ArrayType *>(memberType) && !dynamic_cast<const FloatType *>(memberType) &&
!dynamic_cast<const StructUnionType *>(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;
}
Comment thread
teqdruid marked this conversation as resolved.

std::optional<mlir::Type> 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; }
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;

Expand Down
16 changes: 15 additions & 1 deletion compiler/cpp/circt_util.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -316,6 +319,10 @@ class ModuleDeclarationHelper

void AddTypedefs(const std::string& typeScopeName);

void RegisterNamedType(const Type* kanagawaType);

std::optional<mlir::Type> GetTypeAlias(const Type* kanagawaType) const;

mlir::Type GetInspectableTypeAlias();

mlir::ModuleOp MlirModule();
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -395,6 +402,13 @@ class ModuleDeclarationHelper

circt::hw::TypeScopeOp _typeScopeOp;

// Maps Kanagawa Type* to hw::TypeAliasType for named types
std::map<const Type*, mlir::Type> _typeAliasCache;

// Maps type name to TypeAliasType to prevent duplicate TypedeclOps
// when distinct Type* pointers share the same name
std::map<std::string, mlir::Type> _typeAliasByName;

bool _finished;

bool _exportVerilog;
Expand Down
89 changes: 89 additions & 0 deletions compiler/cpp/internal_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<EnumType::EntryType> 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<circt::hw::TypeAliasType>(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<circt::hw::TypeAliasType>(*alias));

// The alias should have the correct name
circt::hw::TypeAliasType aliasType = llvm::cast<circt::hw::TypeAliasType>(*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<circt::hw::TypeAliasType>(aliasedSignedFalse));

mlir::Type aliasedSignedTrue = ToMlirTypeAliased(&testEnum, true, helper);
TestAssert(llvm::isa<circt::hw::TypeAliasType>(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;
Expand Down Expand Up @@ -2538,6 +2625,8 @@ int InternalTests()

ArrayTypeStringTest();

TypeAliasTest();

FixupLutTest();

LutOperationsProduceSameResultTest();
Expand Down
Loading
Loading