[ImportVerilog] Add type conversion and basic variables (#6755)

Implement the conversion from SystemVerilog types in Slang's AST to the
corresponding MLIR types in the Moore dialect. To facilitate testing,
add a basic `moore.variable` op and convert module-level variable
declarations to this new op. This requires converting the variable's
type. We may choose to have variables return a special lvalue or pointer
wrapper type in the future once such a need arises to clearly demarcate
variable reading and assignment.

Since we don't support expressions yet, variables with an initializer
expression cause an error for the time being.
This commit is contained in:
Fabian Schuiki 2024-02-27 15:20:49 -08:00 committed by GitHub
parent 8afd534537
commit a249766958
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 496 additions and 1 deletions

View File

@ -11,6 +11,7 @@
include "circt/Dialect/Moore/MooreDialect.td"
include "circt/Dialect/Moore/MooreTypes.td"
include "mlir/IR/OpAsmInterface.td"
include "mlir/IR/OpBase.td"
include "mlir/IR/RegionKindInterface.td"
include "mlir/IR/SymbolInterfaces.td"
@ -72,4 +73,25 @@ def InstanceOp : MooreOp<"instance", [
}];
}
//===----------------------------------------------------------------------===//
// Declarations
//===----------------------------------------------------------------------===//
def VariableOp : MooreOp<"variable", [
DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmResultNames"]>,
OptionalTypesMatchWith<"initial value and variable types match",
"result", "initial", "$_self">,
]> {
let summary = "A variable declaration";
let description = [{
See IEEE 1800-2017 § 6.8 "Variable declarations".
}];
let arguments = (ins StrAttr:$name, Optional<UnpackedType>:$initial);
let results = (outs UnpackedType:$result);
let assemblyFormat = [{
`` custom<ImplicitSSAName>($name) ($initial^)? attr-dict
`:` type($result)
}];
}
#endif // CIRCT_DIALECT_MOORE_MOOREOPS

View File

@ -31,6 +31,7 @@ endif ()
add_circt_translation_library(CIRCTImportVerilog
ImportVerilog.cpp
Structure.cpp
Types.cpp
DEPENDS
slang_slang

View File

@ -41,6 +41,13 @@ struct Context {
/// Convert a slang `SourceLocation` into an MLIR `Location`.
Location convertLocation(slang::SourceLocation loc);
/// Convert a slang type into an MLIR type. Returns null on failure. Uses the
/// provided location for error reporting, or tries to guess one from the
/// given type. Types tend to have unreliable location information, so it's
/// generally a good idea to pass in a location.
Type convertType(const slang::ast::Type &type, LocationAttr loc = {});
Type convertType(const slang::ast::DeclaredType &type);
/// Convert hierarchy and structure AST nodes to MLIR ops.
LogicalResult convertCompilation(slang::ast::Compilation &compilation);
moore::SVModuleOp

View File

@ -25,11 +25,21 @@ struct MemberVisitor {
MemberVisitor(Context &context, Location loc)
: context(context), loc(loc), builder(context.builder) {}
/// Skip semicolons.
// Skip empty members (stray semicolons).
LogicalResult visit(const slang::ast::EmptyMemberSymbol &) {
return success();
}
// Skip members that are implicitly imported from some other scope for the
// sake of name resolution, such as enum variant names.
LogicalResult visit(const slang::ast::TransparentMemberSymbol &) {
return success();
}
// Skip typedefs.
LogicalResult visit(const slang::ast::TypeAliasType &) { return success(); }
// Handle instances.
LogicalResult visit(const slang::ast::InstanceSymbol &instNode) {
auto targetModule = context.convertModuleHeader(&instNode.body);
if (!targetModule)
@ -42,6 +52,23 @@ struct MemberVisitor {
return success();
}
// Handle variables.
LogicalResult visit(const slang::ast::VariableSymbol &varNode) {
auto type = context.convertType(*varNode.getDeclaredType());
if (!type)
return failure();
Value initial;
if (const auto *initNode = varNode.getInitializer()) {
return mlir::emitError(loc,
"variable initializer expressions not supported");
}
builder.create<moore::VariableOp>(
loc, type, builder.getStringAttr(varNode.name), initial);
return success();
}
/// Emit an error for all other members.
template <typename T>
LogicalResult visit(T &&node) {

View File

@ -0,0 +1,227 @@
//===- Types.cpp - Slang type conversion ----------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "ImportVerilogInternals.h"
#include "slang/ast/ASTVisitor.h"
#include "slang/ast/Symbol.h"
#include "slang/ast/symbols/CompilationUnitSymbols.h"
#include "slang/ast/symbols/InstanceSymbols.h"
#include "slang/ast/symbols/VariableSymbols.h"
#include "slang/ast/types/AllTypes.h"
#include "slang/ast/types/Type.h"
#include "slang/syntax/SyntaxVisitor.h"
using namespace circt;
using namespace ImportVerilog;
namespace {
struct TypeVisitor {
Context &context;
Location loc;
TypeVisitor(Context &context, Location loc) : context(context), loc(loc) {}
// NOLINTBEGIN(misc-no-recursion)
Type visit(const slang::ast::ScalarType &type) {
moore::IntType::Kind kind;
switch (type.scalarKind) {
case slang::ast::ScalarType::Bit:
kind = moore::IntType::Bit;
break;
case slang::ast::ScalarType::Logic:
kind = moore::IntType::Logic;
break;
case slang::ast::ScalarType::Reg:
kind = moore::IntType::Reg;
break;
}
std::optional<moore::Sign> sign =
type.isSigned ? moore::Sign::Signed : moore::Sign::Unsigned;
if (sign == moore::IntType::getDefaultSign(kind))
sign = {};
return moore::IntType::get(context.getContext(), kind, sign);
}
Type visit(const slang::ast::FloatingType &type) {
moore::RealType::Kind kind;
switch (type.floatKind) {
case slang::ast::FloatingType::Real:
kind = moore::RealType::Real;
break;
case slang::ast::FloatingType::ShortReal:
kind = moore::RealType::ShortReal;
break;
case slang::ast::FloatingType::RealTime:
kind = moore::RealType::RealTime;
break;
}
return moore::RealType::get(context.getContext(), kind);
}
Type visit(const slang::ast::PredefinedIntegerType &type) {
moore::IntType::Kind kind;
switch (type.integerKind) {
case slang::ast::PredefinedIntegerType::Int:
kind = moore::IntType::Int;
break;
case slang::ast::PredefinedIntegerType::ShortInt:
kind = moore::IntType::ShortInt;
break;
case slang::ast::PredefinedIntegerType::LongInt:
kind = moore::IntType::LongInt;
break;
case slang::ast::PredefinedIntegerType::Integer:
kind = moore::IntType::Integer;
break;
case slang::ast::PredefinedIntegerType::Byte:
kind = moore::IntType::Byte;
break;
case slang::ast::PredefinedIntegerType::Time:
kind = moore::IntType::Time;
break;
}
std::optional<moore::Sign> sign =
type.isSigned ? moore::Sign::Signed : moore::Sign::Unsigned;
if (sign == moore::IntType::getDefaultSign(kind))
sign = {};
return moore::IntType::get(context.getContext(), kind, sign);
}
Type visit(const slang::ast::PackedArrayType &type) {
auto innerType = type.elementType.visit(*this);
if (!innerType)
return {};
// The Slang frontend guarantees the inner type to be packed.
auto packedInnerType = cast<moore::PackedType>(innerType);
return moore::PackedRangeDim::get(
packedInnerType, moore::Range(type.range.left, type.range.right));
}
Type visit(const slang::ast::QueueType &type) {
auto innerType = type.elementType.visit(*this);
if (!innerType)
return {};
return moore::UnpackedQueueDim::get(cast<moore::UnpackedType>(innerType),
type.maxBound);
}
Type visit(const slang::ast::AssociativeArrayType &type) {
auto innerType = type.elementType.visit(*this);
if (!innerType)
return {};
auto indexType = type.indexType->visit(*this);
if (!indexType)
return {};
return moore::UnpackedAssocDim::get(cast<moore::UnpackedType>(innerType),
cast<moore::UnpackedType>(indexType));
}
Type visit(const slang::ast::FixedSizeUnpackedArrayType &type) {
auto innerType = type.elementType.visit(*this);
if (!innerType)
return {};
return moore::UnpackedRangeDim::get(
cast<moore::UnpackedType>(innerType),
moore::Range(type.range.left, type.range.right));
}
Type visit(const slang::ast::DynamicArrayType &type) {
auto innerType = type.elementType.visit(*this);
if (!innerType)
return {};
return moore::UnpackedUnsizedDim::get(cast<moore::UnpackedType>(innerType));
}
// Handle type defs.
Type visit(const slang::ast::TypeAliasType &type) {
auto innerType = type.targetType.getType().visit(*this);
if (!innerType)
return {};
auto loc = context.convertLocation(type.location);
if (auto packedInnerType = dyn_cast<moore::PackedType>(innerType))
return moore::PackedNamedType::get(packedInnerType, type.name, loc);
return moore::UnpackedNamedType::get(cast<moore::UnpackedType>(innerType),
type.name, loc);
}
// Handle enums.
Type visit(const slang::ast::EnumType &type) {
auto baseType = type.baseType.visit(*this);
if (!baseType)
return {};
return moore::EnumType::get(StringAttr{}, loc,
cast<moore::PackedType>(baseType));
}
// Collect the members in a struct or union.
LogicalResult collectMembers(const slang::ast::Scope &structType,
SmallVectorImpl<moore::StructMember> &members,
bool enforcePacked) {
for (auto &field : structType.membersOfType<slang::ast::FieldSymbol>()) {
auto loc = context.convertLocation(field.location);
auto name = StringAttr::get(context.getContext(), field.name);
auto innerType = context.convertType(*field.getDeclaredType());
if (!innerType)
return failure();
// The Slang frontend guarantees the inner type to be packed if the struct
// is packed.
assert(!enforcePacked || isa<moore::PackedType>(innerType));
members.push_back({name, loc, cast<moore::UnpackedType>(innerType)});
}
return success();
}
// Handle packed and unpacked structs.
Type visit(const slang::ast::PackedStructType &type) {
auto loc = context.convertLocation(type.location);
SmallVector<moore::StructMember> members;
if (failed(collectMembers(type, members, true)))
return {};
return moore::PackedStructType::get(moore::StructKind::Struct, members,
StringAttr{}, loc);
}
Type visit(const slang::ast::UnpackedStructType &type) {
auto loc = context.convertLocation(type.location);
SmallVector<moore::StructMember> members;
if (failed(collectMembers(type, members, false)))
return {};
return moore::UnpackedStructType::get(moore::StructKind::Struct, members,
StringAttr{}, loc);
}
/// Emit an error for all other types.
template <typename T>
Type visit(T &&node) {
auto d = mlir::emitError(loc, "unsupported type: ")
<< slang::ast::toString(node.kind);
d.attachNote() << node.template as<slang::ast::Type>().toString();
return {};
}
// NOLINTEND(misc-no-recursion)
};
} // namespace
// NOLINTBEGIN(misc-no-recursion)
Type Context::convertType(const slang::ast::Type &type, LocationAttr loc) {
if (!loc)
loc = convertLocation(type.location);
return type.visit(TypeVisitor(*this, loc));
}
Type Context::convertType(const slang::ast::DeclaredType &type) {
LocationAttr loc;
if (auto *ts = type.getTypeSyntax())
loc = convertLocation(ts->sourceRange().start());
return convertType(type.getType(), loc);
}
// NOLINTEND(misc-no-recursion)

View File

@ -15,6 +15,7 @@ add_circt_dialect_library(CIRCTMoore
Support
LINK_LIBS PUBLIC
CIRCTSupport
MLIRIR
MLIRInferTypeOpInterface
)

View File

@ -11,6 +11,7 @@
//===----------------------------------------------------------------------===//
#include "circt/Dialect/Moore/MooreOps.h"
#include "circt/Support/CustomDirectiveImpl.h"
#include "mlir/IR/Builders.h"
using namespace circt;
@ -36,6 +37,14 @@ LogicalResult InstanceOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
return success();
}
//===----------------------------------------------------------------------===//
// VariableOp
//===----------------------------------------------------------------------===//
void VariableOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) {
setNameFn(getResult(), getName());
}
//===----------------------------------------------------------------------===//
// ConcatOp
//===----------------------------------------------------------------------===//

View File

@ -36,3 +36,9 @@ endmodule
module Parent;
Child child();
endmodule
// CHECK-LABEL: moore.module @Foo
module Foo;
// CHECK: %myVar = moore.variable
var myVar;
endmodule

View File

@ -50,3 +50,10 @@ endpackage
module Bar;
endmodule
// -----
module Foo;
// expected-error @below {{variable initializer expressions not supported}}
int a = 0;
endmodule

View File

@ -0,0 +1,11 @@
// RUN: circt-translate --import-verilog --verify-diagnostics --split-input-file %s
// REQUIRES: slang
// Internal issue in Slang v3 about jump depending on uninitialised value.
// UNSUPPORTED: valgrind
module Foo;
// expected-error @below {{unsupported type}}
// expected-note @below {{}}
union { bit a; logic b; } x;
endmodule

View File

@ -0,0 +1,173 @@
// RUN: circt-translate --import-verilog %s | FileCheck %s
// REQUIRES: slang
// Internal issue in Slang v3 about jump depending on uninitialised value.
// UNSUPPORTED: valgrind
// CHECK-LABEL: moore.module @Enums
module Enums;
typedef enum shortint { MAGIC } myEnum;
// CHECK-NEXT: %e0 = moore.variable : !moore.enum<int, loc(
// CHECK-NEXT: %e1 = moore.variable : !moore.enum<byte, loc(
// CHECK-NEXT: %e2 = moore.variable : !moore.packed<named<"myEnum", enum<shortint, loc(
enum { FOO, BAR } e0;
enum byte { HELLO = 0, WORLD = 1 } e1;
myEnum e2;
endmodule
// CHECK-LABEL: moore.module @IntAtoms
module IntAtoms;
// CHECK-NEXT: %d0 = moore.variable : !moore.logic
// CHECK-NEXT: %d1 = moore.variable : !moore.bit
// CHECK-NEXT: %d2 = moore.variable : !moore.reg
// CHECK-NEXT: %d3 = moore.variable : !moore.int
// CHECK-NEXT: %d4 = moore.variable : !moore.shortint
// CHECK-NEXT: %d5 = moore.variable : !moore.longint
// CHECK-NEXT: %d6 = moore.variable : !moore.integer
// CHECK-NEXT: %d7 = moore.variable : !moore.byte
// CHECK-NEXT: %d8 = moore.variable : !moore.time
logic d0;
bit d1;
reg d2;
int d3;
shortint d4;
longint d5;
integer d6;
byte d7;
time d8;
// CHECK-NEXT: %u0 = moore.variable : !moore.logic
// CHECK-NEXT: %u1 = moore.variable : !moore.bit
// CHECK-NEXT: %u2 = moore.variable : !moore.reg
// CHECK-NEXT: %u3 = moore.variable : !moore.int<unsigned>
// CHECK-NEXT: %u4 = moore.variable : !moore.shortint<unsigned>
// CHECK-NEXT: %u5 = moore.variable : !moore.longint<unsigned>
// CHECK-NEXT: %u6 = moore.variable : !moore.integer<unsigned>
// CHECK-NEXT: %u7 = moore.variable : !moore.byte<unsigned>
// CHECK-NEXT: %u8 = moore.variable : !moore.time
logic unsigned u0;
bit unsigned u1;
reg unsigned u2;
int unsigned u3;
shortint unsigned u4;
longint unsigned u5;
integer unsigned u6;
byte unsigned u7;
time unsigned u8;
// CHECK-NEXT: %s0 = moore.variable : !moore.logic<signed>
// CHECK-NEXT: %s1 = moore.variable : !moore.bit<signed>
// CHECK-NEXT: %s2 = moore.variable : !moore.reg<signed>
// CHECK-NEXT: %s3 = moore.variable : !moore.int
// CHECK-NEXT: %s4 = moore.variable : !moore.shortint
// CHECK-NEXT: %s5 = moore.variable : !moore.longint
// CHECK-NEXT: %s6 = moore.variable : !moore.integer
// CHECK-NEXT: %s7 = moore.variable : !moore.byte
// CHECK-NEXT: %s8 = moore.variable : !moore.time<signed>
logic signed s0;
bit signed s1;
reg signed s2;
int signed s3;
shortint signed s4;
longint signed s5;
integer signed s6;
byte signed s7;
time signed s8;
endmodule
// CHECK-LABEL: moore.module @MultiPackedRangeDim
module MultiPackedRangeDim;
// CHECK-NEXT: %v0 = moore.variable : !moore.packed<range<range<logic, 2:0>, 5:0>>
// CHECK-NEXT: %v1 = moore.variable : !moore.packed<range<range<logic, 2:0>, 0:5>>
logic [5:0][2:0] v0;
logic [0:5][2:0] v1;
endmodule
// CHECK-LABEL: moore.module @MultiUnpackedRangeDim
module MultiUnpackedRangeDim;
// CHECK-NEXT: %v0 = moore.variable : !moore.unpacked<range<range<logic, 2:0>, 5:0>>
// CHECK-NEXT: %v1 = moore.variable : !moore.unpacked<range<range<logic, 2:0>, 0:5>>
logic v0 [5:0][2:0];
logic v1 [0:5][2:0];
endmodule
// CHECK-LABEL: moore.module @MultiUnpackedUnsizedDim
module MultiUnpackedUnsizedDim;
// CHECK-NEXT: %v0 = moore.variable : !moore.unpacked<unsized<unsized<logic>>>
logic v0 [][];
endmodule
// CHECK-LABEL: moore.module @PackedRangeDim
module PackedRangeDim;
// CHECK-NEXT: %d0 = moore.variable : !moore.packed<range<logic, 2:0>>
// CHECK-NEXT: %d1 = moore.variable : !moore.packed<range<logic, 0:2>>
logic [2:0] d0;
logic [0:2] d1;
endmodule
// CHECK-LABEL: moore.module @RealType
module RealType;
// CHECK-NEXT: %d0 = moore.variable : !moore.real
// CHECK-NEXT: %d1 = moore.variable : !moore.realtime
// CHECK-NEXT: %d2 = moore.variable : !moore.shortreal
real d0;
realtime d1;
shortreal d2;
endmodule
// CHECK-LABEL: moore.module @Structs
module Structs;
typedef struct packed { byte a; int b; } myStructA;
typedef struct { byte x; int y; } myStructB;
// CHECK-NEXT: %s0 = moore.variable : !moore.packed<struct<{foo: bit loc({{.+}}), bar: logic loc({{.+}})}, loc({{.+}})>>
// CHECK-NEXT: %s1 = moore.variable : !moore.unpacked<struct<{many: assoc<bit, int> loc({{.+}})}, loc({{.+}})>>
// CHECK-NEXT: %s2 = moore.variable : !moore.packed<named<"myStructA", struct<{a: byte loc({{.+}}), b: int loc({{.+}})}, loc({{.+}})>, loc({{.+}})>>
// CHECK-NEXT: %s3 = moore.variable : !moore.unpacked<named<"myStructB", struct<{x: byte loc({{.+}}), y: int loc({{.+}})}, loc({{.+}})>, loc({{.+}})>>
struct packed { bit foo; logic bar; } s0;
struct { bit many[int]; } s1;
myStructA s2;
myStructB s3;
endmodule
// CHECK-LABEL: moore.module @Typedefs
module Typedefs;
typedef logic [2:0] myType1;
typedef logic myType2 [2:0];
// CHECK-NEXT: %v0 = moore.variable : !moore.packed<named<"myType1", range<logic, 2:0>, loc(
// CHECK-NEXT: %v1 = moore.variable : !moore.unpacked<named<"myType2", range<logic, 2:0>, loc(
myType1 v0;
myType2 v1;
endmodule
// CHECK-LABEL: moore.module @UnpackedAssocDim
module UnpackedAssocDim;
// CHECK-NEXT: %d0 = moore.variable : !moore.unpacked<assoc<logic, int>>
// CEECK-NEXT: %d1 = moore.variable : !moore.unpacked<assoc<logic, logic>>
logic d0 [int];
logic d1 [logic];
endmodule
// CHECK-LABEL: moore.module @UnpackedQueueDim
module UnpackedQueueDim;
//CHECK-NEXT: %d0 = moore.variable : !moore.unpacked<queue<logic, 0>>
//CHECK-NEXT: %d1 = moore.variable : !moore.unpacked<queue<logic, 2>>
logic d0[$];
logic d1[$:2];
endmodule
// CHECK-LABEL: moore.module @UnpackedRangeDim
module UnpackedRangeDim;
// CHECK-NEXT: %d0 = moore.variable : !moore.unpacked<range<logic, 2:0>>
// CHECK-NEXT: %d1 = moore.variable : !moore.unpacked<range<logic, 0:2>>
logic d0 [2:0];
logic d1 [0:2];
endmodule
// CHECK-LABEL: moore.module @UnpackedUnsizedDim
module UnpackedUnsizedDim;
// CHECK-NEXT: %d0 = moore.variable : !moore.unpacked<unsized<logic>>
logic d0 [];
endmodule

View File

@ -4,6 +4,10 @@
moore.module @Foo {
// CHECK: moore.instance "foo" @Foo
moore.instance "foo" @Foo
// CHECK: %myVar = moore.variable : !moore.bit
%myVar = moore.variable : !moore.bit
// CHECK: [[TMP:%.+]] = moore.variable name "myVar" : !moore.bit
moore.variable name "myVar" : !moore.bit
}
// CHECK-LABEL: moore.module @Bar