[Moore] Add SystemVerilog types (#2699)

Add an implementation of the SystemVerilog type system to the Moore
dialect, modeled after the one in Moore's Rust codebase:

https://github.com/fabianschuiki/moore/blob/master/src/svlog/ty.rs

This is the first step towards migrating a larger chunk of the Moore
codebase into CIRCT, as it allows Moore's codegen to start emitting
higher-level operations (e.g., `moore.mir.concat`, to be added later)
instead of directly dropping to LLHD/HW. Doing so will allow us to
eventually move the codegen over into CIRCT, and start work on
implementing the type checking and type inference on the higher-level
operations.

My hope is that we might eventually be able to reconcile the Moore types
and some of the higher-level operations with the SV dialect, since both
work with SystemVerilog, albeit for two diametrically opposed purposes.

The types are designed to very clearly distinguish between packed and
unpacked types, and provide a certain level of guarantees about the
structure of nested types through C++ types. For example, struct
aggregate types and typedefs/decltype constructs come in a packed and
unpacked flavor to enforce proper nesting of these types.

Where user-defined types are involved, for example through structs and
typedefs/decltype, the MLIR types aim to capture enough information to
faithfully reconstruct the type as it was originally formulated by the
user. This helps provide good and understandable diagnostics. As a
concrete example, integer types capture whether their sign was provided
explicitly by the user, in order to distinguish `int` and `int signed`,
despite those two types being semantically identical.

This commit also adds a `unittests` directory as seen in LLVM and MLIR,
to test the human-readable serialization of the Moore types and other
type attributes.
This commit is contained in:
Fabian Schuiki 2022-03-04 08:48:45 +01:00 committed by GitHub
parent ff4e615642
commit 4c74e87023
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 3217 additions and 35 deletions

View File

@ -74,6 +74,46 @@ endif ()
set(CIRCT_BUILT_STANDALONE 1)
set(BACKEND_PACKAGE_STRING "LLVM ${LLVM_PACKAGE_VERSION}")
# Handle unittests when building out-of-tree against an installed version of
# LLVM/MLIR (not a build tree). Adapted from `llvm/flang/CMakeLists.txt`.
set(CIRCT_GTEST_AVAILABLE 0)
set(UNITTEST_DIR ${LLVM_BUILD_MAIN_SRC_DIR}/utils/unittest)
if (NOT EXISTS ${UNITTEST_DIR}/googletest/include/gtest/gtest.h)
set(UNITTEST_DIR ${CMAKE_CURRENT_SOURCE_DIR}/llvm/llvm/utils/unittest)
endif()
if (EXISTS ${UNITTEST_DIR}/googletest/include/gtest/gtest.h)
if (NOT TARGET gtest)
find_package(Threads)
add_llvm_library(gtest
${UNITTEST_DIR}/googletest/src/gtest-all.cc
${UNITTEST_DIR}/googlemock/src/gmock-all.cc
LINK_LIBS pthread
LINK_COMPONENTS Support # llvm::raw_ostream
BUILDTREE_ONLY
)
target_include_directories(gtest
PUBLIC
"${UNITTEST_DIR}/googletest/include"
"${UNITTEST_DIR}/googlemock/include"
PRIVATE
"${UNITTEST_DIR}/googletest"
"${UNITTEST_DIR}/googlemock"
)
add_llvm_library(gtest_main
${UNITTEST_DIR}/UnitTestMain/TestMain.cpp
LINK_LIBS gtest
LINK_COMPONENTS Support # llvm::cl
BUILDTREE_ONLY
)
endif()
set(CIRCT_GTEST_AVAILABLE 1)
else()
message(WARNING "Skipping unittests since LLVM install does not include \
gtest headers and libraries")
set(CIRCT_GTEST_AVAILABLE 0)
endif()
else()
# CMake library generation settings.
set(BUILD_SHARED_LIBS OFF CACHE BOOL "Default to building a static mondo-lib")
@ -369,7 +409,9 @@ endif()
add_subdirectory(include/circt)
add_subdirectory(lib)
add_subdirectory(tools)
#add_subdirectory(unittests)
if (CIRCT_GTEST_AVAILABLE)
add_subdirectory(unittests)
endif()
add_subdirectory(test)
add_subdirectory(integration_test)
add_subdirectory(frontends)

View File

@ -26,7 +26,12 @@ def MooreDialect : Dialect {
let extraClassDeclaration = [{
/// Register all Moore types.
void registerTypes();
/// Type parsing and printing.
Type parseType(DialectAsmParser &parser) const override;
void printType(Type, DialectAsmPrinter &) const override;
}];
let useDefaultTypePrinterParser = 0;
}
#endif // CIRCT_DIALECT_MOORE_MOOREDIALECT

File diff suppressed because it is too large Load Diff

View File

@ -30,12 +30,3 @@ class WrapperTypeBase<string name, string mnemo> : MooreType<name> {
def LValueTypeImpl : WrapperTypeBase<"LValue", "lvalue">;
def RValueTypeImpl : WrapperTypeBase<"RValue", "rvalue">;
//===----------------------------------------------------------------------===//
// Integer atom types
//===----------------------------------------------------------------------===//
def IntTypeImpl : MooreType<"Int"> {
let summary = "System-Verilog int type";
let mnemonic = "sv.int";
}

View File

@ -44,8 +44,8 @@ struct AssignOpConv;
static Type convertMooreType(Type type) {
return TypeSwitch<Type, Type>(type)
.Case<moore::IntType>([](moore::IntType ty) {
return IntegerType::get(ty.getContext(), 32);
.Case<moore::IntType>([](auto type) {
return IntegerType::get(type.getContext(), type.getBitSize());
})
.Case<moore::RValueType>(
[](auto type) { return convertMooreType(type.getNestedType()); })

File diff suppressed because it is too large Load Diff

View File

@ -8,6 +8,12 @@ configure_lit_site_cfg(
MAIN_CONFIG
${CMAKE_CURRENT_SOURCE_DIR}/lit.cfg.py
)
configure_lit_site_cfg(
${CMAKE_CURRENT_SOURCE_DIR}/Unit/lit.site.cfg.py.in
${CMAKE_CURRENT_BINARY_DIR}/Unit/lit.site.cfg.py
MAIN_CONFIG
${CMAKE_CURRENT_SOURCE_DIR}/Unit/lit.cfg.py
)
set(CIRCT_TEST_DEPENDS
FileCheck count not
@ -22,6 +28,10 @@ set(CIRCT_TEST_DEPENDS
mlir-cpu-runner
)
if (CIRCT_GTEST_AVAILABLE)
list(APPEND CIRCT_TEST_DEPENDS CIRCTUnitTests)
endif()
if(CIRCT_LLHD_SIM_ENABLED)
list(APPEND CIRCT_TEST_DEPENDS llhd-sim)
list(APPEND CIRCT_TEST_DEPENDS circt-llhd-signals-runtime-wrappers)

View File

@ -2,12 +2,12 @@
// CHECK-LABEL: llhd.entity @test1
llhd.entity @test1() -> () {
// CHECK-NEXT: %c5_i32 = hw.constant 5 : i32
%0 = moore.mir.constant 5 : !moore.rvalue<!moore.sv.int>
// CHECK-NEXT: %c3_i32 = hw.constant 3 : i32
// CHECK-NEXT: [[SIG:%.*]] = llhd.sig "varname" %c3_i32 : i32
%1 = moore.mir.vardecl "varname" = 3 : !moore.lvalue<!moore.sv.int>
// CHECK-NEXT: [[TIME:%.*]] = llhd.constant_time <0s, 0d, 1e>
// CHECK-NEXT: llhd.drv [[SIG]], %c5_i32 after [[TIME]] : !llhd.sig<i32>
moore.mir.assign %1, %0 : !moore.lvalue<!moore.sv.int>, !moore.rvalue<!moore.sv.int>
// CHECK-NEXT: %c5_i32 = hw.constant 5 : i32
%0 = moore.mir.constant 5 : !moore.rvalue<!moore.int>
// CHECK-NEXT: %c3_i32 = hw.constant 3 : i32
// CHECK-NEXT: [[SIG:%.*]] = llhd.sig "varname" %c3_i32 : i32
%1 = moore.mir.vardecl "varname" = 3 : !moore.lvalue<!moore.int>
// CHECK-NEXT: [[TIME:%.*]] = llhd.constant_time <0s, 0d, 1e>
// CHECK-NEXT: llhd.drv [[SIG]], %c5_i32 after [[TIME]] : !llhd.sig<i32>
moore.mir.assign %1, %0 : !moore.lvalue<!moore.int>, !moore.rvalue<!moore.int>
}

View File

@ -2,10 +2,10 @@
// CHECK-LABEL: llhd.entity @test1
llhd.entity @test1() -> () {
// CHECK-NEXT: [[CONST:%.*]] = moore.mir.constant 5 : !moore.rvalue<!moore.sv.int>
// CHECK-NEXT: [[VAR:%.*]] = moore.mir.vardecl "varname" = 3 : !moore.lvalue<!moore.sv.int>
// CHECK-NEXT: moore.mir.assign [[VAR]], [[CONST]] : !moore.lvalue<!moore.sv.int>, !moore.rvalue<!moore.sv.int>
%0 = moore.mir.constant 5 : !moore.rvalue<!moore.sv.int>
%1 = moore.mir.vardecl "varname" = 3 : !moore.lvalue<!moore.sv.int>
moore.mir.assign %1, %0 : !moore.lvalue<!moore.sv.int>, !moore.rvalue<!moore.sv.int>
// CHECK-NEXT: [[CONST:%.*]] = moore.mir.constant 5 : !moore.rvalue<!moore.int>
// CHECK-NEXT: [[VAR:%.*]] = moore.mir.vardecl "varname" = 3 : !moore.lvalue<!moore.int>
// CHECK-NEXT: moore.mir.assign [[VAR]], [[CONST]] : !moore.lvalue<!moore.int>, !moore.rvalue<!moore.int>
%0 = moore.mir.constant 5 : !moore.rvalue<!moore.int>
%1 = moore.mir.vardecl "varname" = 3 : !moore.lvalue<!moore.int>
moore.mir.assign %1, %0 : !moore.lvalue<!moore.int>, !moore.rvalue<!moore.int>
}

View File

@ -0,0 +1,33 @@
// RUN: circt-opt --verify-diagnostics --split-input-file %s
// expected-error @+1 {{ambiguous packing; wrap `named` in `packed<...>` or `unpacked<...>` to disambiguate}}
func @Foo(%arg0: !moore.named<"foo", bit, loc(unknown)>) { return }
// -----
// expected-error @+1 {{ambiguous packing; wrap `ref` in `packed<...>` or `unpacked<...>` to disambiguate}}
func @Foo(%arg0: !moore.ref<bit, loc(unknown)>) { return }
// -----
// expected-error @+1 {{ambiguous packing; wrap `unsized` in `packed<...>` or `unpacked<...>` to disambiguate}}
func @Foo(%arg0: !moore.unsized<bit>) { return }
// -----
// expected-error @+1 {{ambiguous packing; wrap `range` in `packed<...>` or `unpacked<...>` to disambiguate}}
func @Foo(%arg0: !moore.range<bit, 3:0>) { return }
// -----
// expected-error @+1 {{ambiguous packing; wrap `struct` in `packed<...>` or `unpacked<...>` to disambiguate}}
func @Foo(%arg0: !moore.struct<{}, loc(unknown)>) { return }
// -----
// expected-error @+1 {{unpacked struct cannot have a sign}}
func @Foo(%arg0: !moore.unpacked<struct<unsigned, {}, loc(unknown)>>) { return }
func @Bar(%arg0: !moore.packed<struct<unsigned, {}, loc(unknown)>>) { return }
// -----
// expected-error @+1 {{unpacked type '!moore.string' where only packed types are allowed}}
func @Foo(%arg0: !moore.packed<struct<{a: string}, loc(unknown)>>) { return }
// -----
// expected-error @+1 {{unpacked type '!moore.string' where only packed types are allowed}}
func @Foo(%arg0: !moore.packed<unsized<string>>) { return }

View File

@ -0,0 +1,153 @@
// RUN: circt-opt %s | circt-opt | FileCheck %s
// CHECK-LABEL: func @UnitTypes(
func @UnitTypes(
// CHECK-SAME: %arg0: !moore.void
// CHECK-SAME: %arg1: !moore.string
// CHECK-SAME: %arg2: !moore.chandle
// CHECK-SAME: %arg3: !moore.event
%arg0: !moore.void,
%arg1: !moore.string,
%arg2: !moore.chandle,
%arg3: !moore.event
) { return }
// CHECK-LABEL: func @IntTypes(
func @IntTypes(
// CHECK-SAME: %arg0: !moore.bit
// CHECK-SAME: %arg1: !moore.logic
// CHECK-SAME: %arg2: !moore.reg
// CHECK-SAME: %arg3: !moore.byte
// CHECK-SAME: %arg4: !moore.shortint
// CHECK-SAME: %arg5: !moore.int
// CHECK-SAME: %arg6: !moore.longint
// CHECK-SAME: %arg7: !moore.integer
// CHECK-SAME: %arg8: !moore.time
%arg0: !moore.bit,
%arg1: !moore.logic,
%arg2: !moore.reg,
%arg3: !moore.byte,
%arg4: !moore.shortint,
%arg5: !moore.int,
%arg6: !moore.longint,
%arg7: !moore.integer,
%arg8: !moore.time,
// CHECK-SAME: %arg9: !moore.bit<unsigned>
// CHECK-SAME: %arg10: !moore.logic<unsigned>
// CHECK-SAME: %arg11: !moore.reg<unsigned>
// CHECK-SAME: %arg12: !moore.byte<unsigned>
// CHECK-SAME: %arg13: !moore.shortint<unsigned>
// CHECK-SAME: %arg14: !moore.int<unsigned>
// CHECK-SAME: %arg15: !moore.longint<unsigned>
// CHECK-SAME: %arg16: !moore.integer<unsigned>
// CHECK-SAME: %arg17: !moore.time<unsigned>
%arg9: !moore.bit<unsigned>,
%arg10: !moore.logic<unsigned>,
%arg11: !moore.reg<unsigned>,
%arg12: !moore.byte<unsigned>,
%arg13: !moore.shortint<unsigned>,
%arg14: !moore.int<unsigned>,
%arg15: !moore.longint<unsigned>,
%arg16: !moore.integer<unsigned>,
%arg17: !moore.time<unsigned>,
// CHECK-SAME: %arg18: !moore.bit<signed>
// CHECK-SAME: %arg19: !moore.logic<signed>
// CHECK-SAME: %arg20: !moore.reg<signed>
// CHECK-SAME: %arg21: !moore.byte<signed>
// CHECK-SAME: %arg22: !moore.shortint<signed>
// CHECK-SAME: %arg23: !moore.int<signed>
// CHECK-SAME: %arg24: !moore.longint<signed>
// CHECK-SAME: %arg25: !moore.integer<signed>
// CHECK-SAME: %arg26: !moore.time<signed>
%arg18: !moore.bit<signed>,
%arg19: !moore.logic<signed>,
%arg20: !moore.reg<signed>,
%arg21: !moore.byte<signed>,
%arg22: !moore.shortint<signed>,
%arg23: !moore.int<signed>,
%arg24: !moore.longint<signed>,
%arg25: !moore.integer<signed>,
%arg26: !moore.time<signed>
) { return }
// CHECK-LABEL: func @RealTypes(
func @RealTypes(
// CHECK-SAME: %arg0: !moore.shortreal
// CHECK-SAME: %arg1: !moore.real
// CHECK-SAME: %arg2: !moore.realtime
%arg0: !moore.shortreal,
%arg1: !moore.real,
%arg2: !moore.realtime
) { return }
// CHECK-LABEL: func @EnumType(
func @EnumType(
// CHECK-SAME: %arg0: !moore.enum<loc("foo.sv":42:9001)>
// CHECK-SAME: %arg1: !moore.enum<int, loc("foo.sv":42:9001)>
// CHECK-SAME: %arg2: !moore.enum<"Foo", loc("foo.sv":42:9001)>
// CHECK-SAME: %arg3: !moore.enum<"Foo", int, loc("foo.sv":42:9001)>
%arg0: !moore.enum<loc("foo.sv":42:9001)>,
%arg1: !moore.enum<int, loc("foo.sv":42:9001)>,
%arg2: !moore.enum<"Foo", loc("foo.sv":42:9001)>,
%arg3: !moore.enum<"Foo", int, loc("foo.sv":42:9001)>
) { return }
// CHECK-LABEL: func @IndirectTypes(
func @IndirectTypes(
// CHECK-SAME: %arg0: !moore.packed<named<"Foo", bit, loc("foo.sv":42:9001)>>
// CHECK-SAME: %arg1: !moore.packed<ref<bit, loc("foo.sv":42:9001)>>
%arg0: !moore.packed<named<"Foo", bit, loc("foo.sv":42:9001)>>,
%arg1: !moore.packed<ref<bit, loc("foo.sv":42:9001)>>,
// CHECK-SAME: %arg2: !moore.unpacked<named<"Foo", bit, loc("foo.sv":42:9001)>>
// CHECK-SAME: %arg3: !moore.unpacked<named<"Foo", string, loc("foo.sv":42:9001)>>
// CHECK-SAME: %arg4: !moore.unpacked<ref<bit, loc("foo.sv":42:9001)>>
// CHECK-SAME: %arg5: !moore.unpacked<ref<string, loc("foo.sv":42:9001)>>
%arg2: !moore.unpacked<named<"Foo", bit, loc("foo.sv":42:9001)>>,
%arg3: !moore.unpacked<named<"Foo", string, loc("foo.sv":42:9001)>>,
%arg4: !moore.unpacked<ref<bit, loc("foo.sv":42:9001)>>,
%arg5: !moore.unpacked<ref<string, loc("foo.sv":42:9001)>>
) { return }
// CHECK-LABEL: func @DimTypes(
func @DimTypes(
// CHECK-SAME: %arg0: !moore.packed<unsized<bit>>,
// CHECK-SAME: %arg1: !moore.packed<range<bit, 4:-5>>,
%arg0: !moore.packed<unsized<bit>>,
%arg1: !moore.packed<range<bit, 4:-5>>,
// CHECK-SAME: %arg2: !moore.unpacked<unsized<bit>>,
// CHECK-SAME: %arg3: !moore.unpacked<array<bit, 42>>,
// CHECK-SAME: %arg4: !moore.unpacked<range<bit, 4:-5>>,
// CHECK-SAME: %arg5: !moore.unpacked<assoc<bit>>,
// CHECK-SAME: %arg6: !moore.unpacked<assoc<bit, string>>,
// CHECK-SAME: %arg7: !moore.unpacked<queue<bit>>,
// CHECK-SAME: %arg8: !moore.unpacked<queue<bit, 9001>>
%arg2: !moore.unpacked<unsized<bit>>,
%arg3: !moore.unpacked<array<bit, 42>>,
%arg4: !moore.unpacked<range<bit, 4:-5>>,
%arg5: !moore.unpacked<assoc<bit>>,
%arg6: !moore.unpacked<assoc<bit, string>>,
%arg7: !moore.unpacked<queue<bit>>,
%arg8: !moore.unpacked<queue<bit, 9001>>
) {
return
}
// CHECK-LABEL: func @StructTypes(
func @StructTypes(
// CHECK-SAME: %arg0: !moore.packed<struct<{}, loc("foo.sv":42:9001)>>
// CHECK-SAME: %arg1: !moore.packed<struct<"Foo", {}, loc("foo.sv":42:9001)>>
// CHECK-SAME: %arg2: !moore.packed<struct<unsigned, {}, loc("foo.sv":42:9001)>>
// CHECK-SAME: %arg3: !moore.packed<struct<"Foo", signed, {}, loc("foo.sv":42:9001)>>
// CHECK-SAME: %arg4: !moore.packed<struct<{foo: bit loc("foo.sv":1:2), bar: int loc("foo.sv":3:4)}, loc("foo.sv":42:9001)>>
%arg0: !moore.packed<struct<{}, loc("foo.sv":42:9001)>>,
%arg1: !moore.packed<struct<"Foo", {}, loc("foo.sv":42:9001)>>,
%arg2: !moore.packed<struct<unsigned, {}, loc("foo.sv":42:9001)>>,
%arg3: !moore.packed<struct<"Foo", signed, {}, loc("foo.sv":42:9001)>>,
%arg4: !moore.packed<struct<{foo: bit loc("foo.sv":1:2), bar: int loc("foo.sv":3:4)}, loc("foo.sv":42:9001)>>,
// CHECK-SAME: %arg5: !moore.unpacked<struct<{}, loc("foo.sv":42:9001)>>
// CHECK-SAME: %arg6: !moore.unpacked<struct<"Foo", {}, loc("foo.sv":42:9001)>>
// CHECK-SAME: %arg7: !moore.unpacked<struct<{foo: string loc("foo.sv":1:2), bar: event loc("foo.sv":3:4)}, loc("foo.sv":42:9001)>>
%arg5: !moore.unpacked<struct<{}, loc("foo.sv":42:9001)>>,
%arg6: !moore.unpacked<struct<"Foo", {}, loc("foo.sv":42:9001)>>,
%arg7: !moore.unpacked<struct<{foo: string loc("foo.sv":1:2), bar: event loc("foo.sv":3:4)}, loc("foo.sv":42:9001)>>
) { return }

39
test/Unit/lit.cfg.py Normal file
View File

@ -0,0 +1,39 @@
# -*- Python -*-
# Configuration file for the 'lit' test runner.
import os
import subprocess
import lit.formats
# name: The name of this test suite.
config.name = 'CIRCT-Unit'
# suffixes: A list of file extensions to treat as test files.
config.suffixes = []
# test_source_root: The root path where tests are located.
# test_exec_root: The root path where tests should be run.
config.test_exec_root = os.path.join(config.circt_obj_root, 'unittests')
config.test_source_root = config.test_exec_root
# testFormat: The test format to use to interpret tests.
config.test_format = lit.formats.GoogleTest(config.llvm_build_mode, 'Tests')
# Propagate the temp directory. Windows requires this because it uses \Windows\
# if none of these are present.
if 'TMP' in os.environ:
config.environment['TMP'] = os.environ['TMP']
if 'TEMP' in os.environ:
config.environment['TEMP'] = os.environ['TEMP']
# Propagate HOME as it can be used to override incorrect homedir in passwd
# that causes the tests to fail.
if 'HOME' in os.environ:
config.environment['HOME'] = os.environ['HOME']
# Propagate path to symbolizer for ASan/MSan.
for symbolizer in ['ASAN_SYMBOLIZER_PATH', 'MSAN_SYMBOLIZER_PATH']:
if symbolizer in os.environ:
config.environment[symbolizer] = os.environ[symbolizer]

View File

@ -0,0 +1,27 @@
@LIT_SITE_CFG_IN_HEADER@
import sys
config.llvm_src_root = "@LLVM_SOURCE_DIR@"
config.llvm_obj_root = "@LLVM_BINARY_DIR@"
config.llvm_tools_dir = "@LLVM_TOOLS_DIR@"
config.llvm_build_mode = "@LLVM_BUILD_MODE@"
config.enable_shared = @ENABLE_SHARED@
config.shlibdir = "@SHLIBDIR@"
config.circt_src_root = "@CIRCT_SOURCE_DIR@"
config.circt_obj_root = "@CIRCT_BINARY_DIR@"
config.circt_tools_dir = "@CIRCT_TOOLS_DIR@"
# Support substitution of the tools_dir and build_mode with user parameters.
# This is used when we can't determine the tool dir at configuration time.
try:
config.llvm_tools_dir = config.llvm_tools_dir % lit_config.params
config.llvm_build_mode = config.llvm_build_mode % lit_config.params
config.shlibdir = config.shlibdir % lit_config.params
except KeyError:
e = sys.exc_info()[1]
key, = e.args
lit_config.fatal("unable to find %r parameter, use '--param=%s=VALUE'" % (key,key))
# Let the main config do the real work.
lit_config.load_config(config, "@CIRCT_SOURCE_DIR@/test/Unit/lit.cfg.py")

8
unittests/CMakeLists.txt Normal file
View File

@ -0,0 +1,8 @@
add_custom_target(CIRCTUnitTests)
set_target_properties(CIRCTUnitTests PROPERTIES FOLDER "CIRCT Tests")
function(add_circt_unittest test_dirname)
add_unittest(CIRCTUnitTests ${test_dirname} ${ARGN})
endfunction()
add_subdirectory(Dialect)

View File

@ -0,0 +1 @@
add_subdirectory(Moore)

View File

@ -0,0 +1,8 @@
add_circt_unittest(CIRCTMooreTests
TypesTest.cpp
)
target_link_libraries(CIRCTMooreTests
PRIVATE
CIRCTMoore
)

View File

@ -0,0 +1,402 @@
//===- TypesTest.cpp - Moore type unit tests ------------------------------===//
//
// 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 "circt/Dialect/Moore/MooreDialect.h"
#include "circt/Dialect/Moore/MooreTypes.h"
#include "gtest/gtest.h"
using namespace mlir;
using namespace circt;
using namespace moore;
namespace {
TEST(TypesTest, UnitTypes) {
MLIRContext context;
context.loadDialect<MooreDialect>();
auto voidType = VoidType::get(&context);
auto stringType = StringType::get(&context);
auto chandleType = ChandleType::get(&context);
auto eventType = EventType::get(&context);
ASSERT_EQ(voidType.toString(), "void");
ASSERT_EQ(stringType.toString(), "string");
ASSERT_EQ(chandleType.toString(), "chandle");
ASSERT_EQ(eventType.toString(), "event");
ASSERT_EQ(voidType.getBitSize(), 0u);
ASSERT_EQ(stringType.getBitSize(), llvm::None);
ASSERT_EQ(chandleType.getBitSize(), llvm::None);
ASSERT_EQ(eventType.getBitSize(), llvm::None);
ASSERT_EQ(voidType.getDomain(), Domain::TwoValued);
ASSERT_EQ(stringType.getDomain(), Domain::TwoValued);
ASSERT_EQ(chandleType.getDomain(), Domain::TwoValued);
ASSERT_EQ(eventType.getDomain(), Domain::TwoValued);
ASSERT_EQ(voidType.getSign(), Sign::Unsigned);
ASSERT_EQ(stringType.getSign(), Sign::Unsigned);
ASSERT_EQ(chandleType.getSign(), Sign::Unsigned);
ASSERT_EQ(eventType.getSign(), Sign::Unsigned);
}
TEST(TypesTest, Ranges) {
Range a(42);
Range b(32, RangeDir::Down, -5);
Range c(16, RangeDir::Up, -3);
ASSERT_EQ(a.toString(), "41:0");
ASSERT_EQ(b.toString(), "26:-5");
ASSERT_EQ(c.toString(), "-3:12");
ASSERT_EQ(a.left(), 41);
ASSERT_EQ(a.right(), 0);
ASSERT_EQ(a.low(), 0);
ASSERT_EQ(a.high(), 41);
ASSERT_EQ(a.increment(), -1);
ASSERT_EQ(b.left(), 26);
ASSERT_EQ(b.right(), -5);
ASSERT_EQ(b.low(), -5);
ASSERT_EQ(b.high(), 26);
ASSERT_EQ(b.increment(), -1);
ASSERT_EQ(c.left(), -3);
ASSERT_EQ(c.right(), 12);
ASSERT_EQ(c.low(), -3);
ASSERT_EQ(c.high(), 12);
ASSERT_EQ(c.increment(), 1);
}
TEST(TypesTest, PackedInt) {
MLIRContext context;
context.loadDialect<MooreDialect>();
std::tuple<IntType::Kind, StringRef, Domain, Sign> pairs[] = {
{IntType::Bit, "bit", Domain::TwoValued, Sign::Unsigned},
{IntType::Logic, "logic", Domain::FourValued, Sign::Unsigned},
{IntType::Reg, "reg", Domain::FourValued, Sign::Unsigned},
{IntType::Byte, "byte", Domain::TwoValued, Sign::Signed},
{IntType::ShortInt, "shortint", Domain::TwoValued, Sign::Signed},
{IntType::Int, "int", Domain::TwoValued, Sign::Signed},
{IntType::LongInt, "longint", Domain::TwoValued, Sign::Signed},
{IntType::Integer, "integer", Domain::FourValued, Sign::Signed},
{IntType::Time, "time", Domain::TwoValued, Sign::Unsigned},
};
for (auto pair : pairs) {
auto kind = std::get<0>(pair);
auto keyword = std::get<1>(pair);
auto type = IntType::get(&context, kind);
auto unsignedType = IntType::get(&context, kind, Sign::Unsigned);
auto signedType = IntType::get(&context, kind, Sign::Signed);
// Check the formatting.
ASSERT_EQ(type.toString(), keyword);
ASSERT_EQ(unsignedType.toString(), std::string(keyword) + " unsigned");
ASSERT_EQ(signedType.toString(), std::string(keyword) + " signed");
// Check the domain.
ASSERT_EQ(type.getDomain(), std::get<2>(pair));
ASSERT_EQ(unsignedType.getDomain(), std::get<2>(pair));
ASSERT_EQ(signedType.getDomain(), std::get<2>(pair));
// Check the sign.
ASSERT_EQ(type.getSign(), std::get<3>(pair));
ASSERT_EQ(unsignedType.getSign(), Sign::Unsigned);
ASSERT_EQ(signedType.getSign(), Sign::Signed);
ASSERT_FALSE(type.isSignExplicit());
ASSERT_TRUE(unsignedType.isSignExplicit());
ASSERT_TRUE(signedType.isSignExplicit());
}
}
TEST(TypesTest, Reals) {
MLIRContext context;
context.loadDialect<MooreDialect>();
auto t0 = RealType::get(&context, RealType::ShortReal);
auto t1 = RealType::get(&context, RealType::Real);
auto t2 = RealType::get(&context, RealType::RealTime);
ASSERT_EQ(t0.toString(), "shortreal");
ASSERT_EQ(t1.toString(), "real");
ASSERT_EQ(t2.toString(), "realtime");
ASSERT_EQ(t0.getDomain(), Domain::TwoValued);
ASSERT_EQ(t1.getDomain(), Domain::TwoValued);
ASSERT_EQ(t2.getDomain(), Domain::TwoValued);
ASSERT_EQ(t0.getBitSize(), 32u);
ASSERT_EQ(t1.getBitSize(), 64u);
ASSERT_EQ(t2.getBitSize(), 64u);
ASSERT_EQ(t0.getSign(), Sign::Unsigned);
ASSERT_EQ(t1.getSign(), Sign::Unsigned);
ASSERT_EQ(t2.getSign(), Sign::Unsigned);
}
TEST(TypesTest, PackedDim) {
MLIRContext context;
context.loadDialect<MooreDialect>();
auto bitType = IntType::get(&context, IntType::Bit);
auto arrayType1 = PackedRangeDim::get(bitType, 3);
auto arrayType2 = PackedRangeDim::get(arrayType1, 2);
auto arrayType3 = PackedUnsizedDim::get(arrayType2);
ASSERT_EQ(arrayType1.toString(), "bit [2:0]");
ASSERT_EQ(arrayType2.toString(), "bit [1:0][2:0]");
ASSERT_EQ(arrayType3.toString(), "bit [][1:0][2:0]");
ASSERT_EQ(arrayType1.getRange(), Range(3));
ASSERT_EQ(arrayType3.getRange(), llvm::None);
ASSERT_EQ(arrayType1.getSize(), 3u);
ASSERT_EQ(arrayType3.getSize(), llvm::None);
}
TEST(TypesTest, UnpackedDim) {
MLIRContext context;
context.loadDialect<MooreDialect>();
auto stringType = StringType::get(&context);
auto arrayType1 = UnpackedUnsizedDim::get(stringType);
auto arrayType2 = UnpackedArrayDim::get(arrayType1, 42);
auto arrayType3 = UnpackedRangeDim::get(arrayType2, 2);
auto arrayType4 = UnpackedAssocDim::get(arrayType3);
auto arrayType5 = UnpackedAssocDim::get(arrayType4, stringType);
auto arrayType6 = UnpackedQueueDim::get(arrayType5);
auto arrayType7 = UnpackedQueueDim::get(arrayType6, 9);
ASSERT_EQ(arrayType1.toString(), "string $ []");
ASSERT_EQ(arrayType2.toString(), "string $ [42][]");
ASSERT_EQ(arrayType3.toString(), "string $ [1:0][42][]");
ASSERT_EQ(arrayType4.toString(), "string $ [*][1:0][42][]");
ASSERT_EQ(arrayType5.toString(), "string $ [string][*][1:0][42][]");
ASSERT_EQ(arrayType6.toString(), "string $ [$][string][*][1:0][42][]");
ASSERT_EQ(arrayType7.toString(), "string $ [$:9][$][string][*][1:0][42][]");
ASSERT_EQ(arrayType2.getSize(), 42);
ASSERT_EQ(arrayType3.getRange(), Range(2));
ASSERT_EQ(arrayType4.getIndexType(), UnpackedType{});
ASSERT_EQ(arrayType5.getIndexType(), stringType);
ASSERT_EQ(arrayType6.getBound(), llvm::None);
ASSERT_EQ(arrayType7.getBound(), 9u);
}
TEST(TypesTest, UnpackedFormattingAroundStuff) {
MLIRContext context;
context.loadDialect<MooreDialect>();
auto bitType = IntType::get(&context, IntType::Bit);
auto arrayType1 = PackedRangeDim::get(bitType, 3);
auto arrayType2 = UnpackedArrayDim::get(arrayType1, 42);
// Packed type formatting with custom separator.
ASSERT_EQ(arrayType1.toString(), "bit [2:0]");
ASSERT_EQ(arrayType1.toString("foo"), "bit [2:0] foo");
ASSERT_EQ(arrayType1.toString([](auto &os) { os << "bar"; }),
"bit [2:0] bar");
// Unpacked type formatting with custom separator.
ASSERT_EQ(arrayType2.toString(), "bit [2:0] $ [42]");
ASSERT_EQ(arrayType2.toString("foo"), "bit [2:0] foo [42]");
ASSERT_EQ(arrayType2.toString([](auto &os) { os << "bar"; }),
"bit [2:0] bar [42]");
}
TEST(TypesTest, Resolution) {
MLIRContext context;
context.loadDialect<MooreDialect>();
auto loc = UnknownLoc::get(&context);
auto t0 = IntType::get(&context, IntType::Bit);
auto t1 = PackedRangeDim::get(t0, 3);
auto t2 = PackedNamedType::get(t1, "foo", loc);
ASSERT_EQ(t2.toString(), "foo");
ASSERT_EQ(t2.resolved().toString(), "bit [2:0]");
ASSERT_EQ(t2.fullyResolved().toString(), "bit [2:0]");
auto t3 = PackedRangeDim::get(t2, 2);
auto t4 = PackedNamedType::get(t3, "bar", loc);
ASSERT_EQ(t4.toString(), "bar");
ASSERT_EQ(t4.resolved().toString(), "foo [1:0]");
ASSERT_EQ(t4.fullyResolved().toString(), "bit [1:0][2:0]");
auto t5 = UnpackedArrayDim::get(t4, 4);
auto t6 = UnpackedNamedType::get(t5, "tony", loc);
ASSERT_EQ(t6.toString(), "tony");
ASSERT_EQ(t6.resolved().toString(), "bar $ [4]");
ASSERT_EQ(t6.fullyResolved().toString(), "bit [1:0][2:0] $ [4]");
auto t7 = UnpackedAssocDim::get(t6);
auto t8 = UnpackedNamedType::get(t7, "ada", loc);
ASSERT_EQ(t8.toString(), "ada");
ASSERT_EQ(t8.resolved().toString(), "tony $ [*]");
ASSERT_EQ(t8.fullyResolved().toString(), "bit [1:0][2:0] $ [*][4]");
// Type references
auto r0 = PackedRefType::get(t2, loc);
ASSERT_EQ(r0.toString(), "type(foo)");
ASSERT_EQ(r0.resolved().toString(), "foo");
ASSERT_EQ(r0.fullyResolved().toString(), "bit [2:0]");
auto r1 = UnpackedRefType::get(t8, loc);
ASSERT_EQ(r1.toString(), "type(ada)");
ASSERT_EQ(r1.resolved().toString(), "ada");
ASSERT_EQ(r1.fullyResolved().toString(), "bit [1:0][2:0] $ [*][4]");
}
TEST(TypesTest, NamedStructFormatting) {
MLIRContext context;
context.loadDialect<MooreDialect>();
auto loc = UnknownLoc::get(&context);
auto foo = StringAttr::get(&context, "Foo");
auto s0 = UnpackedStructType::get(StructKind::Struct, {}, foo, loc);
auto s1 = UnpackedStructType::get(StructKind::Union, {}, foo, loc);
auto s2 = UnpackedStructType::get(StructKind::TaggedUnion, {}, foo, loc);
auto s3 = PackedStructType::get(StructKind::Struct, {}, foo, loc);
auto s4 = PackedStructType::get(StructKind::Union, {}, foo, loc);
auto s5 = PackedStructType::get(StructKind::TaggedUnion, {}, foo, loc);
auto s6 =
PackedStructType::get(StructKind::Struct, {}, foo, loc, Sign::Unsigned);
auto s7 =
PackedStructType::get(StructKind::Union, {}, foo, loc, Sign::Unsigned);
auto s8 = PackedStructType::get(StructKind::TaggedUnion, {}, foo, loc,
Sign::Unsigned);
auto s9 =
PackedStructType::get(StructKind::Struct, {}, foo, loc, Sign::Signed);
auto s10 =
PackedStructType::get(StructKind::Union, {}, foo, loc, Sign::Signed);
auto s11 = PackedStructType::get(StructKind::TaggedUnion, {}, foo, loc,
Sign::Signed);
ASSERT_EQ(s0.toString(), "struct Foo");
ASSERT_EQ(s1.toString(), "union Foo");
ASSERT_EQ(s2.toString(), "union tagged Foo");
ASSERT_EQ(s3.toString(), "struct packed Foo");
ASSERT_EQ(s4.toString(), "union packed Foo");
ASSERT_EQ(s5.toString(), "union tagged packed Foo");
ASSERT_EQ(s6.toString(), "struct packed unsigned Foo");
ASSERT_EQ(s7.toString(), "union packed unsigned Foo");
ASSERT_EQ(s8.toString(), "union tagged packed unsigned Foo");
ASSERT_EQ(s9.toString(), "struct packed signed Foo");
ASSERT_EQ(s10.toString(), "union packed signed Foo");
ASSERT_EQ(s11.toString(), "union tagged packed signed Foo");
}
TEST(TypesTest, Structs) {
MLIRContext context;
context.loadDialect<MooreDialect>();
auto loc = UnknownLoc::get(&context);
auto foo = StringAttr::get(&context, "foo");
auto bar = StringAttr::get(&context, "bar");
auto bitType = IntType::get(&context, IntType::Bit);
auto logicType = IntType::get(&context, IntType::Logic);
auto bit8Type = PackedRangeDim::get(bitType, 8);
auto bitDynArrayType = PackedUnsizedDim::get(bitType);
auto s0 = UnpackedStructType::get(StructKind::Struct,
{StructMember{foo, loc, bitType}}, {}, loc);
auto s1 = UnpackedStructType::get(
StructKind::Struct,
{StructMember{foo, loc, bitType}, StructMember{bar, loc, bit8Type}}, {},
loc);
auto s2 = UnpackedStructType::get(
StructKind::Struct,
{StructMember{foo, loc, bitType}, StructMember{bar, loc, logicType}}, {},
loc);
auto s3 = UnpackedStructType::get(StructKind::Struct,
{StructMember{foo, loc, bitType},
StructMember{bar, loc, bitDynArrayType}},
{}, loc);
// Member formatting
ASSERT_EQ(s0.toString(), "struct { bit foo; }");
ASSERT_EQ(s1.toString(), "struct { bit foo; bit [7:0] bar; }");
// Value domain
ASSERT_EQ(s1.getDomain(), Domain::TwoValued);
ASSERT_EQ(s2.getDomain(), Domain::FourValued);
// Bit size
ASSERT_EQ(s0.getBitSize(), 1u);
ASSERT_EQ(s1.getBitSize(), 9u);
ASSERT_EQ(s2.getBitSize(), 2u);
ASSERT_EQ(s3.getBitSize(), llvm::None);
}
TEST(TypesTest, Enums) {
MLIRContext context;
context.loadDialect<MooreDialect>();
auto loc = UnknownLoc::get(&context);
auto foo = StringAttr::get(&context, "Foo");
auto intType = IntType::getInt(&context);
auto bitType = IntType::get(&context, IntType::Bit);
auto bit8Type = PackedRangeDim::get(bitType, 8);
auto slogicType = IntType::get(&context, IntType::Logic, Sign::Signed);
auto slogic8Type = PackedRangeDim::get(slogicType, 8);
auto e0 = EnumType::get({}, loc);
auto e1 = EnumType::get(foo, loc);
auto e2 = EnumType::get({}, loc, bit8Type);
auto e3 = EnumType::get(foo, loc, bit8Type);
auto e4 = EnumType::get({}, loc, slogic8Type);
auto e5 = EnumType::get(foo, loc, slogic8Type);
// Formatting
ASSERT_EQ(e0.toString(), "enum");
ASSERT_EQ(e1.toString(), "enum Foo");
ASSERT_EQ(e2.toString(), "enum bit [7:0]");
ASSERT_EQ(e3.toString(), "enum Foo");
ASSERT_EQ(e4.toString(), "enum logic signed [7:0]");
ASSERT_EQ(e5.toString(), "enum Foo");
// Base types
ASSERT_EQ(e0.getBase(), intType);
ASSERT_EQ(e1.getBase(), intType);
ASSERT_EQ(e2.getBase(), bit8Type);
ASSERT_EQ(e3.getBase(), bit8Type);
ASSERT_EQ(e4.getBase(), slogic8Type);
ASSERT_EQ(e5.getBase(), slogic8Type);
// Sign
ASSERT_EQ(e0.getSign(), Sign::Signed); // implicit int
ASSERT_EQ(e1.getSign(), Sign::Signed); // implicit int
ASSERT_EQ(e2.getSign(), Sign::Unsigned);
ASSERT_EQ(e3.getSign(), Sign::Unsigned);
ASSERT_EQ(e4.getSign(), Sign::Signed);
ASSERT_EQ(e5.getSign(), Sign::Signed);
// Value domain
ASSERT_EQ(e0.getDomain(), Domain::TwoValued);
ASSERT_EQ(e1.getDomain(), Domain::TwoValued);
ASSERT_EQ(e2.getDomain(), Domain::TwoValued);
ASSERT_EQ(e3.getDomain(), Domain::TwoValued);
ASSERT_EQ(e4.getDomain(), Domain::FourValued);
ASSERT_EQ(e5.getDomain(), Domain::FourValued);
// Bit size
ASSERT_EQ(e0.getBitSize(), 32u);
ASSERT_EQ(e1.getBitSize(), 32u);
ASSERT_EQ(e2.getBitSize(), 8u);
ASSERT_EQ(e3.getBitSize(), 8u);
ASSERT_EQ(e4.getBitSize(), 8u);
ASSERT_EQ(e5.getBitSize(), 8u);
}
} // namespace