From 8ff5ec921d65430894a40e00e8dfdb38cacf30e2 Mon Sep 17 00:00:00 2001 From: Fabian Schuiki <fabian@schuiki.ch> Date: Wed, 7 Aug 2024 17:44:08 -0700 Subject: [PATCH] [Moore] Support four-valued integers in ConstantOp Extend Moore's `ConstantOp` to use the new `FVIntegerAttr`, which now allows us to materialize four-valued integer constants in the IR. Also adjust ImportVerilog to finally properly captured integer literals with X and Z bits. With this change, `circt-verilog` is now capable of fully parsing the Snitch RISC-V core used as a benchmark in the original LLHD paper at PLDI 2020, and to produce a corresponding blob of IR. Examples of the extended op: moore.constant 0 : i32 moore.constant 2 : i2 moore.constant -2 : i2 moore.constant h123456789ABCDEF0 : i64 moore.constant h123456789ABCDEF0XZ : l72 moore.constant b1010 : i8 moore.constant b1010XZ : l8 --- .../circt/Dialect/Moore/MooreAttributes.td | 6 +++ include/circt/Dialect/Moore/MooreOps.h | 1 + include/circt/Dialect/Moore/MooreOps.td | 4 +- lib/Conversion/ImportVerilog/Expressions.cpp | 42 +++++++++------ lib/Conversion/MooreToCore/MooreToCore.cpp | 7 ++- lib/Dialect/Moore/MooreOps.cpp | 53 ++++++++++++++----- test/Conversion/ImportVerilog/basic.sv | 6 +++ test/Conversion/ImportVerilog/errors.sv | 34 ------------ test/Dialect/Moore/basic.mlir | 16 ++++++ test/Dialect/Moore/errors.mlir | 11 ++-- 10 files changed, 111 insertions(+), 69 deletions(-) diff --git a/include/circt/Dialect/Moore/MooreAttributes.td b/include/circt/Dialect/Moore/MooreAttributes.td index d184553bf3c4..766bd64d2c08 100644 --- a/include/circt/Dialect/Moore/MooreAttributes.td +++ b/include/circt/Dialect/Moore/MooreAttributes.td @@ -19,4 +19,10 @@ def FVIntegerAttr : AttrDef<MooreDialect, "FVInteger"> { let hasCustomAssemblyFormat = 1; } +def FVIntAttr : Attr<CPred<"llvm::isa<FVIntegerAttr>($_self)">, + "arbitrary precision four-valued integer attribute"> { + let storageType = [{ circt::moore::FVIntegerAttr }]; + let returnType = [{ circt::FVInt }]; +} + #endif // CIRCT_DIALECT_MOORE_MOOREATTRIBUTES diff --git a/include/circt/Dialect/Moore/MooreOps.h b/include/circt/Dialect/Moore/MooreOps.h index 5a27457159d8..e9f2773eab66 100644 --- a/include/circt/Dialect/Moore/MooreOps.h +++ b/include/circt/Dialect/Moore/MooreOps.h @@ -14,6 +14,7 @@ #define CIRCT_DIALECT_MOORE_MOOREOPS_H #include "circt/Dialect/HW/HWTypes.h" +#include "circt/Dialect/Moore/MooreAttributes.h" #include "circt/Dialect/Moore/MooreDialect.h" #include "circt/Dialect/Moore/MooreTypes.h" #include "mlir/IR/RegionKindInterface.h" diff --git a/include/circt/Dialect/Moore/MooreOps.td b/include/circt/Dialect/Moore/MooreOps.td index a0e3018fb1b5..62a60ee18422 100644 --- a/include/circt/Dialect/Moore/MooreOps.td +++ b/include/circt/Dialect/Moore/MooreOps.td @@ -9,6 +9,7 @@ #ifndef CIRCT_DIALECT_MOORE_MOOREOPS #define CIRCT_DIALECT_MOORE_MOOREOPS +include "circt/Dialect/Moore/MooreAttributes.td" include "circt/Dialect/Moore/MooreDialect.td" include "circt/Dialect/Moore/MooreTypes.td" include "mlir/IR/OpAsmInterface.td" @@ -417,12 +418,13 @@ def EventOp : MooreOp<"wait_event", [ def ConstantOp : MooreOp<"constant", [Pure]> { let summary = "A constant integer value"; - let arguments = (ins APIntAttr:$value); + let arguments = (ins FVIntAttr:$value); let results = (outs IntType:$result); let hasCustomAssemblyFormat = 1; let hasVerifier = 1; let hasFolder = 1; let builders = [ + OpBuilder<(ins "IntType":$type, "const FVInt &":$value)>, OpBuilder<(ins "IntType":$type, "const APInt &":$value)>, OpBuilder<(ins "IntType":$type, "int64_t":$value)>, ]; diff --git a/lib/Conversion/ImportVerilog/Expressions.cpp b/lib/Conversion/ImportVerilog/Expressions.cpp index 79b23269f417..b9e26c8590ca 100644 --- a/lib/Conversion/ImportVerilog/Expressions.cpp +++ b/lib/Conversion/ImportVerilog/Expressions.cpp @@ -14,6 +14,19 @@ using namespace circt; using namespace ImportVerilog; using moore::Domain; +/// Convert a Slang `SVInt` to a CIRCT `FVInt`. +static FVInt convertSVIntToFVInt(const slang::SVInt &svint) { + if (svint.hasUnknown()) { + unsigned numWords = svint.getNumWords() / 2; + auto value = ArrayRef<uint64_t>(svint.getRawPtr(), numWords); + auto unknown = ArrayRef<uint64_t>(svint.getRawPtr() + numWords, numWords); + return FVInt(APInt(svint.getBitWidth(), value), + APInt(svint.getBitWidth(), unknown)); + } + auto value = ArrayRef<uint64_t>(svint.getRawPtr(), svint.getNumWords()); + return FVInt(APInt(svint.getBitWidth(), value)); +} + // NOLINTBEGIN(misc-no-recursion) namespace { struct RvalueExprVisitor { @@ -98,7 +111,7 @@ struct RvalueExprVisitor { auto type = context.convertType(*expr.type); if (!type) return {}; - return convertSVInt(constant.integer(), type); + return materializeSVInt(constant.integer(), type); } // Otherwise some other part of ImportVerilog should have added an MLIR @@ -411,20 +424,17 @@ struct RvalueExprVisitor { return {}; } - // Materialize a Slang integer literal as a constant op. - Value convertSVInt(const slang::SVInt &value, Type type) { - if (value.hasUnknown()) { - mlir::emitError(loc, "literals with X or Z bits not supported"); - return {}; - } - auto intType = - moore::IntType::get(context.getContext(), value.getBitWidth(), - value.hasUnknown() ? moore::Domain::FourValued + /// Materialize a Slang integer literal as a constant op. + Value materializeSVInt(const slang::SVInt &svint, Type type) { + auto fvint = convertSVIntToFVInt(svint); + bool typeIsFourValued = false; + if (auto unpackedType = dyn_cast<moore::UnpackedType>(type)) + typeIsFourValued = unpackedType.getDomain() == moore::Domain::FourValued; + auto intType = moore::IntType::get( + context.getContext(), fvint.getBitWidth(), + fvint.hasUnknown() || typeIsFourValued ? moore::Domain::FourValued : moore::Domain::TwoValued); - Value result = builder.create<moore::ConstantOp>( - loc, intType, - APInt(value.getBitWidth(), - ArrayRef<uint64_t>(value.getRawPtr(), value.getNumWords()))); + Value result = builder.create<moore::ConstantOp>(loc, intType, fvint); if (result.getType() != type) result = builder.create<moore::ConversionOp>(loc, type, result); return result; @@ -435,7 +445,7 @@ struct RvalueExprVisitor { auto type = context.convertType(*expr.type); if (!type) return {}; - return convertSVInt(expr.getValue(), type); + return materializeSVInt(expr.getValue(), type); } // Handle integer literals. @@ -443,7 +453,7 @@ struct RvalueExprVisitor { auto type = context.convertType(*expr.type); if (!type) return {}; - return convertSVInt(expr.getValue(), type); + return materializeSVInt(expr.getValue(), type); } // Handle concatenations. diff --git a/lib/Conversion/MooreToCore/MooreToCore.cpp b/lib/Conversion/MooreToCore/MooreToCore.cpp index 6ea5561eb685..47068bc76f69 100644 --- a/lib/Conversion/MooreToCore/MooreToCore.cpp +++ b/lib/Conversion/MooreToCore/MooreToCore.cpp @@ -183,8 +183,11 @@ struct ConstantOpConv : public OpConversionPattern<ConstantOp> { LogicalResult matchAndRewrite(ConstantOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override { - - rewriter.replaceOpWithNewOp<hw::ConstantOp>(op, op.getValueAttr()); + // Discard unknown bits and map them to 0 for now. + auto value = op.getValue().toAPInt(false); + auto type = rewriter.getIntegerType(value.getBitWidth()); + rewriter.replaceOpWithNewOp<hw::ConstantOp>( + op, type, rewriter.getIntegerAttr(type, value)); return success(); } }; diff --git a/lib/Dialect/Moore/MooreOps.cpp b/lib/Dialect/Moore/MooreOps.cpp index eae745de174c..cbd7ed7aa197 100644 --- a/lib/Dialect/Moore/MooreOps.cpp +++ b/lib/Dialect/Moore/MooreOps.cpp @@ -13,6 +13,7 @@ #include "circt/Dialect/Moore/MooreOps.h" #include "circt/Dialect/HW/CustomDirectiveImpl.h" #include "circt/Dialect/HW/ModuleImplementation.h" +#include "circt/Dialect/Moore/MooreAttributes.h" #include "circt/Support/CustomDirectiveImpl.h" #include "mlir/IR/Builders.h" #include "llvm/ADT/SmallString.h" @@ -385,19 +386,34 @@ void AssignedVarOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) { void ConstantOp::print(OpAsmPrinter &p) { p << " "; - p.printAttributeWithoutType(getValueAttr()); + const auto &value = getValue(); + if (value == FVInt(1, 0)) { + p << "false"; + } else if (value == FVInt(1, 1)) { + p << "true"; + } else { + printFVInt(p, value); + } p.printOptionalAttrDict((*this)->getAttrs(), /*elidedAttrs=*/{"value"}); p << " : "; p.printStrippedAttrOrType(getType()); } ParseResult ConstantOp::parse(OpAsmParser &parser, OperationState &result) { - // Parse the constant value without bit width. - APInt value; + // Parse the constant value. + FVInt value; auto valueLoc = parser.getCurrentLocation(); + if (succeeded(parser.parseOptionalKeyword("false"))) { + value = FVInt(1, 0); + } else if (succeeded(parser.parseOptionalKeyword("true"))) { + value = FVInt(1, 1); + } else { + if (parseFVInt(parser, value)) + return failure(); + } - if (parser.parseInteger(value) || - parser.parseOptionalAttrDict(result.attributes) || parser.parseColon()) + // Parse any optional attributes and the `:`. + if (parser.parseOptionalAttrDict(result.attributes) || parser.parseColon()) return failure(); // Parse the result type. @@ -417,16 +433,21 @@ ParseResult ConstantOp::parse(OpAsmParser &parser, OperationState &result) { unsigned neededBits = value.isNegative() ? value.getSignificantBits() : value.getActiveBits(); if (type.getWidth() < neededBits) - return parser.emitError(valueLoc, - "constant out of range for result type ") - << type; + return parser.emitError(valueLoc) + << "value requires " << neededBits + << " bits, but result type only has " << type.getWidth(); value = value.trunc(type.getWidth()); } - // Build the attribute and op. - auto attrType = IntegerType::get(parser.getContext(), type.getWidth()); - auto attrValue = IntegerAttr::get(attrType, value); + // If the constant contains any X or Z bits, the result type must be + // four-valued. + if (value.hasUnknown() && type.getDomain() != Domain::FourValued) + return parser.emitError(valueLoc) + << "value contains X or Z bits, but result type " << type + << " only allows two-valued bits"; + // Build the attribute and op. + auto attrValue = FVIntegerAttr::get(parser.getContext(), value); result.addAttribute("value", attrValue); result.addTypes(type); return success(); @@ -441,12 +462,18 @@ LogicalResult ConstantOp::verify() { return success(); } +void ConstantOp::build(OpBuilder &builder, OperationState &result, IntType type, + const FVInt &value) { + assert(type.getWidth() == value.getBitWidth() && + "FVInt width must match type width"); + build(builder, result, type, FVIntegerAttr::get(builder.getContext(), value)); +} + void ConstantOp::build(OpBuilder &builder, OperationState &result, IntType type, const APInt &value) { assert(type.getWidth() == value.getBitWidth() && "APInt width must match type width"); - build(builder, result, type, - builder.getIntegerAttr(builder.getIntegerType(type.getWidth()), value)); + build(builder, result, type, FVInt(value)); } /// This builder allows construction of small signed integers like 0, 1, -1 diff --git a/test/Conversion/ImportVerilog/basic.sv b/test/Conversion/ImportVerilog/basic.sv index 34ba203ebbfd..27ed4a208c90 100644 --- a/test/Conversion/ImportVerilog/basic.sv +++ b/test/Conversion/ImportVerilog/basic.sv @@ -630,6 +630,10 @@ module Expressions; c = '0; // CHECK: moore.constant -1 : i32 c = '1; + // CHECK: moore.constant hXXXXXXXX : l32 + f = 'X; + // CHECK: moore.constant hZZZZZZZZ : l32 + f = 'Z; // CHECK: moore.constant 42 : i32 c = 42; // CHECK: moore.constant 42 : i19 @@ -638,6 +642,8 @@ module Expressions; c = 19'sd42; // CHECK: moore.constant 123456789123456789123456789123456789 : i128 c = 128'd123456789123456789123456789123456789; + // CHECK: moore.constant h123XZ : l19 + f = 19'h123XZ; // CHECK: [[TMP1:%.+]] = moore.read %a // CHECK: [[TMP2:%.+]] = moore.read %b // CHECK: [[TMP3:%.+]] = moore.read %c diff --git a/test/Conversion/ImportVerilog/errors.sv b/test/Conversion/ImportVerilog/errors.sv index 73c1bfb2a9de..554c36028861 100644 --- a/test/Conversion/ImportVerilog/errors.sv +++ b/test/Conversion/ImportVerilog/errors.sv @@ -73,43 +73,9 @@ module Foo; initial if (x matches 42) x = y; endmodule -// ----- -module Foo; - // expected-error @below {{literals with X or Z bits not supported}} - logic a = 'x; -endmodule - -// ----- -module Foo; - // expected-error @below {{literals with X or Z bits not supported}} - logic a = 'z; -endmodule - // ----- module Foo; int a, b[3]; // expected-error @below {{unpacked arrays in 'inside' expressions not supported}} int c = a inside { b }; endmodule - -// ----- -module Foo; - logic a, b; - initial begin - casez (a) - // expected-error @below {{literals with X or Z bits not supported}} - 1'bz : b = 1'b1; - endcase - end -endmodule - -// ----- -module Foo; - logic a; - initial begin - // expected-error @below {{literals with X or Z bits not supported}} - casez (1'bz) - 1'bz : a = 1'b1; - endcase - end -endmodule diff --git a/test/Dialect/Moore/basic.mlir b/test/Dialect/Moore/basic.mlir index d1a2b3a9db1c..b2fb3a010abf 100644 --- a/test/Dialect/Moore/basic.mlir +++ b/test/Dialect/Moore/basic.mlir @@ -140,12 +140,28 @@ moore.module @Expressions( // CHECK-SAME: in [[REF_STRUCT1:%.+]] : !moore.ref<struct<{a: i32, b: i32}>> in %refStruct1: !moore.ref<struct<{a: i32, b: i32}>> ) { + // CHECK: moore.constant false : i1 + moore.constant false : i1 + // CHECK: moore.constant false : l1 + moore.constant false : l1 + // CHECK: moore.constant true : i1 + moore.constant true : i1 + // CHECK: moore.constant true : l1 + moore.constant true : l1 // CHECK: moore.constant 0 : i32 moore.constant 0 : i32 // CHECK: moore.constant -2 : i2 moore.constant 2 : i2 // CHECK: moore.constant -2 : i2 moore.constant -2 : i2 + // CHECK: moore.constant 1311768467463790320 : i64 + moore.constant h123456789ABCDEF0 : i64 + // CHECK: moore.constant h123456789ABCDEF0XZ : l72 + moore.constant h123456789ABCDEF0XZ : l72 + // CHECK: moore.constant 10 : i8 + moore.constant b1010 : i8 + // CHECK: moore.constant b1010XZ : l8 + moore.constant b1010XZ : l8 // CHECK: moore.conversion [[A]] : !moore.i32 -> !moore.l32 moore.conversion %a : !moore.i32 -> !moore.l32 diff --git a/test/Dialect/Moore/errors.mlir b/test/Dialect/Moore/errors.mlir index de561d2dcb07..09965a9d40ca 100644 --- a/test/Dialect/Moore/errors.mlir +++ b/test/Dialect/Moore/errors.mlir @@ -53,18 +53,23 @@ moore.module @Foo(out a: !moore.string) { // ----- -// expected-error @below {{constant out of range for result type '!moore.i1'}} +// expected-error @below {{value requires 6 bits, but result type only has 1}} moore.constant 42 : !moore.i1 // ----- -// expected-error @below {{constant out of range for result type '!moore.i1'}} +// expected-error @below {{value requires 2 bits, but result type only has 1}} moore.constant -2 : !moore.i1 // ----- +// expected-error @below {{value contains X or Z bits, but result type '!moore.i4' only allows two-valued bits}} +moore.constant b10XZ : !moore.i4 + +// ----- + // expected-error @below {{attribute width 9 does not match return type's width 8}} -"moore.constant" () {value = 42 : i9} : () -> !moore.i8 +"moore.constant" () {value = #moore.fvint<42 : 9>} : () -> !moore.i8 // -----