From 272af6c1581b2a0ba309dd062911e0cfc3d11974 Mon Sep 17 00:00:00 2001 From: Fabian Schuiki Date: Fri, 2 Aug 2024 11:33:44 -0700 Subject: [PATCH 001/119] [ImportVerilog] Add missing conversions, various fixes Add missing conversions for cases where the Slang AST has expressions operating directly on packed structs/arrays, but the Moore IR expects a conversion to a simple bit vector type. Also clean up a few things and remove an invalid `struct_inject` conversion. --- lib/Conversion/ImportVerilog/Expressions.cpp | 61 +++++++++++-------- .../ImportVerilog/ImportVerilogInternals.h | 3 +- lib/Conversion/ImportVerilog/Structure.cpp | 22 +++---- test/Conversion/ImportVerilog/basic.sv | 33 ++++++---- 4 files changed, 69 insertions(+), 50 deletions(-) diff --git a/lib/Conversion/ImportVerilog/Expressions.cpp b/lib/Conversion/ImportVerilog/Expressions.cpp index 48b268bb7649..798a6be06951 100644 --- a/lib/Conversion/ImportVerilog/Expressions.cpp +++ b/lib/Conversion/ImportVerilog/Expressions.cpp @@ -28,10 +28,21 @@ struct RvalueExprVisitor { Value convertToSimpleBitVector(Value value) { if (!value) return {}; - if (isa(value.getType()) || - isa( - dyn_cast(value.getType()).getNestedType())) + if (isa(value.getType())) return value; + + // Some operations in Slang's AST, for example bitwise or `|`, don't cast + // packed struct/array operands to simple bit vectors but directly operate + // on the struct/array. Since the corresponding IR ops operate only on + // simple bit vectors, insert a conversion in this case. + if (auto packed = dyn_cast(value.getType())) { + if (auto bits = packed.getBitSize()) { + auto sbvType = + moore::IntType::get(value.getContext(), *bits, packed.getDomain()); + return builder.create(loc, sbvType, value); + } + } + mlir::emitError(loc, "expression of type ") << value.getType() << " cannot be cast to a simple bit vector"; return {}; @@ -55,16 +66,14 @@ struct RvalueExprVisitor { Value visit(const slang::ast::LValueReferenceExpression &expr) { assert(!context.lvalueStack.empty() && "parent assignments push lvalue"); auto lvalue = context.lvalueStack.back(); - return builder.create( - loc, cast(lvalue.getType()).getNestedType(), lvalue); + return builder.create(loc, lvalue); } // Handle named values, such as references to declared variables. Value visit(const slang::ast::NamedValueExpression &expr) { if (auto value = context.valueSymbols.lookup(&expr.symbol)) { - if (auto refType = dyn_cast(value.getType())) - value = - builder.create(loc, refType.getNestedType(), value); + if (isa(value.getType())) + value = builder.create(loc, value); return value; } @@ -92,19 +101,20 @@ struct RvalueExprVisitor { auto type = context.convertType(*expr.type); if (!type) return {}; - auto operand = context.convertRvalueExpression(expr.operand()); - if (!operand) - return {}; - return builder.create(loc, type, operand); + return context.convertRvalueExpression(expr.operand(), type); } // Handle blocking and non-blocking assignments. Value visit(const slang::ast::AssignmentExpression &expr) { auto lhs = context.convertLvalueExpression(expr.left()); + if (!lhs) + return {}; + context.lvalueStack.push_back(lhs); - auto rhs = context.convertRvalueExpression(expr.right()); + auto rhs = context.convertRvalueExpression( + expr.right(), cast(lhs.getType()).getNestedType()); context.lvalueStack.pop_back(); - if (!lhs || !rhs) + if (!rhs) return {}; if (expr.timingControl) { @@ -135,12 +145,7 @@ struct RvalueExprVisitor { // Helper function to create pre and post increments and decrements. Value createIncrement(Value arg, bool isInc, bool isPost) { - auto preValue = convertToSimpleBitVector(arg); - if (!preValue) - return {}; - preValue = builder.create( - loc, cast(preValue.getType()).getNestedType(), - preValue); + auto preValue = builder.create(loc, arg); auto one = builder.create( loc, cast(preValue.getType()), 1); auto postValue = @@ -220,8 +225,10 @@ struct RvalueExprVisitor { template Value createBinary(Value lhs, Value rhs) { lhs = convertToSimpleBitVector(lhs); + if (!lhs) + return {}; rhs = convertToSimpleBitVector(rhs); - if (!lhs || !rhs) + if (!rhs) return {}; return builder.create(loc, lhs, rhs); } @@ -229,8 +236,10 @@ struct RvalueExprVisitor { // Handle binary operators. Value visit(const slang::ast::BinaryExpression &expr) { auto lhs = context.convertRvalueExpression(expr.left()); + if (!lhs) + return {}; auto rhs = context.convertRvalueExpression(expr.right()); - if (!lhs || !rhs) + if (!rhs) return {}; using slang::ast::BinaryOperator; @@ -904,9 +913,13 @@ struct LvalueExprVisitor { }; } // namespace -Value Context::convertRvalueExpression(const slang::ast::Expression &expr) { +Value Context::convertRvalueExpression(const slang::ast::Expression &expr, + Type requiredType) { auto loc = convertLocation(expr.sourceRange); - return expr.visit(RvalueExprVisitor(*this, loc)); + auto value = expr.visit(RvalueExprVisitor(*this, loc)); + if (value && requiredType && value.getType() != requiredType) + value = builder.create(loc, requiredType, value); + return value; } Value Context::convertLvalueExpression(const slang::ast::Expression &expr) { diff --git a/lib/Conversion/ImportVerilog/ImportVerilogInternals.h b/lib/Conversion/ImportVerilog/ImportVerilogInternals.h index 5b9d7fb29398..96534a03147a 100644 --- a/lib/Conversion/ImportVerilog/ImportVerilogInternals.h +++ b/lib/Conversion/ImportVerilog/ImportVerilogInternals.h @@ -88,7 +88,8 @@ struct Context { LogicalResult convertStatement(const slang::ast::Statement &stmt); // Convert an expression AST node to MLIR ops. - Value convertRvalueExpression(const slang::ast::Expression &expr); + Value convertRvalueExpression(const slang::ast::Expression &expr, + Type requiredType = {}); Value convertLvalueExpression(const slang::ast::Expression &expr); // Convert a slang timing control into an MLIR timing control. diff --git a/lib/Conversion/ImportVerilog/Structure.cpp b/lib/Conversion/ImportVerilog/Structure.cpp index 6f5b2fac14fe..ea8772418302 100644 --- a/lib/Conversion/ImportVerilog/Structure.cpp +++ b/lib/Conversion/ImportVerilog/Structure.cpp @@ -364,7 +364,7 @@ struct ModuleVisitor : public BaseVisitor { Value initial; if (const auto *init = varNode.getInitializer()) { - initial = context.convertRvalueExpression(*init); + initial = context.convertRvalueExpression(*init, loweredType); if (!initial) return failure(); } @@ -384,7 +384,8 @@ struct ModuleVisitor : public BaseVisitor { Value assignment; if (netNode.getInitializer()) { - assignment = context.convertRvalueExpression(*netNode.getInitializer()); + assignment = context.convertRvalueExpression(*netNode.getInitializer(), + loweredType); if (!assignment) return failure(); } @@ -413,21 +414,14 @@ struct ModuleVisitor : public BaseVisitor { const auto &expr = assignNode.getAssignment().as(); - auto lhs = context.convertLvalueExpression(expr.left()); - auto rhs = context.convertRvalueExpression(expr.right()); - if (!lhs || !rhs) + if (!lhs) return failure(); - if (auto refOp = lhs.getDefiningOp()) { - auto input = refOp.getInput(); - if (isa(input.getDefiningOp()->getParentOp())) { - builder.create(loc, input.getType(), input, - refOp.getFieldNameAttr(), rhs); - refOp->erase(); - return success(); - } - } + auto rhs = context.convertRvalueExpression( + expr.right(), cast(lhs.getType()).getNestedType()); + if (!rhs) + return failure(); builder.create(loc, lhs, rhs); return success(); diff --git a/test/Conversion/ImportVerilog/basic.sv b/test/Conversion/ImportVerilog/basic.sv index 8a795d8cbeab..1d5effeaa2c8 100644 --- a/test/Conversion/ImportVerilog/basic.sv +++ b/test/Conversion/ImportVerilog/basic.sv @@ -214,6 +214,21 @@ module Basic; string s1; assign s1 = "Hello World"; + typedef struct packed { bit x; bit y; } MyStruct; + // CHECK: [[VAR_S2:%.+]] = moore.variable : > + MyStruct s2; + // CHECK: [[TMP1:%.+]] = moore.read [[VAR_S2]] + // CHECK: [[TMP2:%.+]] = moore.conversion [[TMP1]] : !moore.struct<{x: i1, y: i1}> -> !moore.i2 + // CHECK: [[TMP3:%.+]] = moore.not [[TMP2]] : i2 + // CHECK: [[TMP4:%.+]] = moore.conversion [[TMP3]] : !moore.i2 -> !moore.struct<{x: i1, y: i1}> + // CHECK: moore.assign [[VAR_S2]], [[TMP4]] + assign s2 = ~s2; + // CHECK: [[TMP1:%.+]] = moore.read [[VAR_S2]] + // CHECK: [[TMP2:%.+]] = moore.conversion [[TMP1]] : !moore.struct<{x: i1, y: i1}> -> !moore.i2 + // CHECK: [[TMP3:%.+]] = moore.not [[TMP2]] : i2 + // CHECK: [[TMP4:%.+]] = moore.conversion [[TMP3]] : !moore.i2 -> !moore.struct<{x: i1, y: i1}> + // CHECK: [[VAR_S3:%.+]] = moore.variable [[TMP4]] : > + MyStruct s3 = ~s2; endmodule // CHECK-LABEL: moore.module @Statements @@ -594,8 +609,7 @@ module Expressions; c = -a; // CHECK: [[TMP1:%.+]] = moore.read %v // CHECK: [[TMP2:%.+]] = moore.conversion [[TMP1]] : !moore.array<2 x i4> -> !moore.i32 - // CHECK: [[TMP3:%.+]] = moore.neg [[TMP2]] : i32 - // CHECK: [[TMP4:%.+]] = moore.conversion [[TMP3]] : !moore.i32 -> !moore.i32 + // CHECK: moore.neg [[TMP2]] : i32 c = -v; // CHECK: [[TMP1:%.+]] = moore.read %a // CHECK: moore.not [[TMP1]] : i32 @@ -665,10 +679,9 @@ module Expressions; // CHECK: moore.add [[TMP1]], [[TMP2]] : i32 c = a + b; // CHECK: [[TMP1:%.+]] = moore.read %a - // CHECK: [[TMP2:%.+]] = moore.conversion [[TMP1]] : !moore.i32 -> !moore.i32 - // CHECK: [[TMP3:%.+]] = moore.read %v - // CHECK: [[TMP4:%.+]] = moore.conversion [[TMP3]] : !moore.array<2 x i4> -> !moore.i32 - // CHECK: moore.add [[TMP2]], [[TMP4]] : i32 + // CHECK: [[TMP2:%.+]] = moore.read %v + // CHECK: [[TMP3:%.+]] = moore.conversion [[TMP2]] : !moore.array<2 x i4> -> !moore.i32 + // CHECK: moore.add [[TMP1]], [[TMP3]] : i32 c = a + v; // CHECK: [[TMP1:%.+]] = moore.read %a // CHECK: [[TMP2:%.+]] = moore.read %b @@ -1080,11 +1093,9 @@ module Conversion; // Sign conversion. // CHECK: [[TMP1:%.+]] = moore.read %b - // CHECK: [[TMP2:%.+]] = moore.conversion [[TMP1]] : !moore.i32 -> !moore.i32 - // CHECK: %d1 = moore.variable [[TMP2]] - // CHECK: [[TMP3:%.+]] = moore.read %b - // CHECK: [[TMP4:%.+]] = moore.conversion [[TMP3]] : !moore.i32 -> !moore.i32 - // CHECK: %d2 = moore.variable [[TMP4]] + // CHECK: %d1 = moore.variable [[TMP1]] + // CHECK: [[TMP2:%.+]] = moore.read %b + // CHECK: %d2 = moore.variable [[TMP2]] bit signed [31:0] d1 = signed'(b); bit [31:0] d2 = unsigned'(b); From 58f6daa42575c561f8d52b191849d607a0fdc59a Mon Sep 17 00:00:00 2001 From: Fabian Schuiki Date: Fri, 2 Aug 2024 11:35:58 -0700 Subject: [PATCH 002/119] [ImportVerilog] Ignore type parameters Ignore type parameter AST nodes since these are already handled during Slang's type checking pass. Also tweak the diagnostics on unsupported SV constructs to be a bit more descriptive. --- lib/Conversion/ImportVerilog/Structure.cpp | 15 ++++++++++++--- test/Conversion/ImportVerilog/basic.sv | 2 +- test/Conversion/ImportVerilog/errors.sv | 4 ++-- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/lib/Conversion/ImportVerilog/Structure.cpp b/lib/Conversion/ImportVerilog/Structure.cpp index ea8772418302..a72c381ec1d7 100644 --- a/lib/Conversion/ImportVerilog/Structure.cpp +++ b/lib/Conversion/ImportVerilog/Structure.cpp @@ -97,6 +97,9 @@ struct PackageVisitor : public BaseVisitor { // Ignore parameters. These are materialized on-the-fly as `ConstantOp`s. LogicalResult visit(const slang::ast::ParameterSymbol &) { return success(); } LogicalResult visit(const slang::ast::SpecparamSymbol &) { return success(); } + LogicalResult visit(const slang::ast::TypeParameterSymbol &) { + return success(); + } // Handle functions and tasks. LogicalResult visit(const slang::ast::SubroutineSymbol &subroutine) { @@ -106,7 +109,7 @@ struct PackageVisitor : public BaseVisitor { /// Emit an error for all other members. template LogicalResult visit(T &&node) { - mlir::emitError(loc, "unsupported construct: ") + mlir::emitError(loc, "unsupported package member: ") << slang::ast::toString(node.kind); return failure(); } @@ -192,6 +195,12 @@ struct ModuleVisitor : public BaseVisitor { return success(); } + // Ignore type parameters. These have already been handled by Slang's type + // checking. + LogicalResult visit(const slang::ast::TypeParameterSymbol &) { + return success(); + } + // Handle instances. LogicalResult visit(const slang::ast::InstanceSymbol &instNode) { using slang::ast::ArgumentDirection; @@ -515,7 +524,7 @@ struct ModuleVisitor : public BaseVisitor { /// Emit an error for all other members. template LogicalResult visit(T &&node) { - mlir::emitError(loc, "unsupported construct: ") + mlir::emitError(loc, "unsupported module member: ") << slang::ast::toString(node.kind); return failure(); } @@ -632,7 +641,7 @@ Context::convertModuleHeader(const slang::ast::InstanceBodySymbol *module) { // only minor differences in semantics. if (module->getDefinition().definitionKind != slang::ast::DefinitionKind::Module) { - mlir::emitError(loc) << "unsupported construct: " + mlir::emitError(loc) << "unsupported definition: " << module->getDefinition().getKindString(); return {}; } diff --git a/test/Conversion/ImportVerilog/basic.sv b/test/Conversion/ImportVerilog/basic.sv index 1d5effeaa2c8..2bf40daf82cf 100644 --- a/test/Conversion/ImportVerilog/basic.sv +++ b/test/Conversion/ImportVerilog/basic.sv @@ -32,7 +32,7 @@ endmodule // CHECK: moore.module private @DedupB(in %a : !moore.i32) // CHECK: moore.module private @DedupB_0(in %a : !moore.i16) // CHECK-NOT: @DedupB -module DedupB #(parameter int W)(input bit [W-1:0] a); +module DedupB #(parameter int W, parameter type T = bit [W-1:0])(input T a); endmodule // CHECK-LABEL: moore.module @Dedup() diff --git a/test/Conversion/ImportVerilog/errors.sv b/test/Conversion/ImportVerilog/errors.sv index 0d161459b5b3..73c1bfb2a9de 100644 --- a/test/Conversion/ImportVerilog/errors.sv +++ b/test/Conversion/ImportVerilog/errors.sv @@ -28,13 +28,13 @@ endmodule // ----- module Foo; parameter a = 1; - // expected-error @below {{unsupported construct}} + // expected-error @below {{unsupported module member}} defparam a = 2; endmodule // ----- module Foo; - // expected-error @below {{unsupported construct}} + // expected-error @below {{unsupported module member}} nettype real x; endmodule From f470dac1ed0639e31e5e55cee3adeca7f03952ff Mon Sep 17 00:00:00 2001 From: Will Dietz Date: Fri, 2 Aug 2024 13:40:14 -0500 Subject: [PATCH 003/119] [FIRRTL][Dedup] Improve diagnostic for failing to dedup due to public. (#7426) --- lib/Dialect/FIRRTL/Transforms/Dedup.cpp | 35 +++++++++++++--- test/Dialect/FIRRTL/dedup-errors.mlir | 53 +++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 6 deletions(-) diff --git a/lib/Dialect/FIRRTL/Transforms/Dedup.cpp b/lib/Dialect/FIRRTL/Transforms/Dedup.cpp index 1fb2ad2fe9aa..ca0fecb251a0 100644 --- a/lib/Dialect/FIRRTL/Transforms/Dedup.cpp +++ b/lib/Dialect/FIRRTL/Transforms/Dedup.cpp @@ -46,6 +46,20 @@ using namespace circt; using namespace firrtl; using hw::InnerRefAttr; +//===----------------------------------------------------------------------===// +// Utility function for classifying a Symbol's dedup-ability. +//===----------------------------------------------------------------------===// + +static bool checkVisibility(mlir::SymbolOpInterface symbol) { + // If module has symbol (name) that must be preserved even if unused, + // skip it. All symbol uses must be supported, which is not true if + // non-private. + // Special handling for class-like's and if symbol reports cannot be + // discarded. + return symbol.isPrivate() && + (symbol.canDiscardOnUseEmpty() || isa(*symbol)); +} + //===----------------------------------------------------------------------===// // Hashing //===----------------------------------------------------------------------===// @@ -788,6 +802,20 @@ struct Equivalence { diag.attachNote(b->getLoc()) << "module marked NoDedup"; return; } + auto aSymbol = cast(a); + auto bSymbol = cast(b); + if (!checkVisibility(aSymbol)) { + diag.attachNote(a->getLoc()) + << "module is " + << (aSymbol.isPrivate() ? "private but not discardable" : "public"); + return; + } + if (!checkVisibility(bSymbol)) { + diag.attachNote(b->getLoc()) + << "module is " + << (bSymbol.isPrivate() ? "private but not discardable" : "public"); + return; + } auto aGroup = dyn_cast_or_null(a->getDiscardableAttr(dedupGroupAttrName)); auto bGroup = dyn_cast_or_null( @@ -1674,13 +1702,8 @@ class DedupPass : public circt::firrtl::impl::DedupBase { ext && !ext.getDefname().has_value()) return success(); - // If module has symbol (name) that must be preserved even if unused, - // skip it. All symbol uses must be supported, which is not true if - // non-private. - if (!module.isPrivate() || - (!module.canDiscardOnUseEmpty() && !isa(*module))) { + if (!checkVisibility(module)) return success(); - } StructuralHasher hasher(hasherConstants); // Calculate the hash of the module and referred module names. diff --git a/test/Dialect/FIRRTL/dedup-errors.mlir b/test/Dialect/FIRRTL/dedup-errors.mlir index 19355c0e4a38..1e13dc4518fb 100644 --- a/test/Dialect/FIRRTL/dedup-errors.mlir +++ b/test/Dialect/FIRRTL/dedup-errors.mlir @@ -630,3 +630,56 @@ firrtl.circuit "MustDedup" attributes {annotations = [{ firrtl.instance test1 @Test1(in in : !firrtl.vector, 2>) } } + +// ----- + +// Modules instantiating equivalent public modules do not dedup. +// Check diagnostic. +// expected-error @below {{module "Test1Parent" not deduplicated with "Test0Parent"}} +// expected-note @below {{in instance "test0" of "Test0", and instance "test1" of "Test1"}} +firrtl.circuit "InstOfEquivPublic" attributes {annotations = [{ + class = "firrtl.transforms.MustDeduplicateAnnotation", + modules = ["~InstOfEquivPublic|Test0Parent", "~InstOfEquivPublic|Test1Parent"] + }]} { + // expected-note @below {{module is public}} + firrtl.module public @Test0() { } + firrtl.module public @Test1() { } + firrtl.module private @Test0Parent() { + firrtl.instance test0 @Test0() + } + firrtl.module private @Test1Parent() { + firrtl.instance test1 @Test1() + } + + firrtl.module @InstOfEquivPublic() { + firrtl.instance test0p @Test0Parent() + firrtl.instance test1p @Test1Parent() + } +} + +// ----- + +// Modules instantiating mixed public/private do not dedup. +// Check diagnostic. +// expected-error @below {{module "Test1Parent" not deduplicated with "Test0Parent"}} +// expected-note @below {{in instance "test0" of "Test0", and instance "test1" of "Test1"}} +firrtl.circuit "InstOfPublicPrivate" attributes {annotations = [{ + class = "firrtl.transforms.MustDeduplicateAnnotation", + modules = ["~InstOfPublicPrivate|Test0Parent", "~InstOfPublicPrivate|Test1Parent"] + }]} { + firrtl.module private @Test0() { } + // expected-note @below {{module is public}} + firrtl.module public @Test1() { } + + firrtl.module private @Test0Parent() { + firrtl.instance test0 @Test0() + } + firrtl.module private @Test1Parent() { + firrtl.instance test1 @Test1() + } + + firrtl.module @InstOfPublicPrivate() { + firrtl.instance test0p @Test0Parent() + firrtl.instance test1p @Test1Parent() + } +} From 6eda5a2b5eb951e156e1cb385501998e69428d2a Mon Sep 17 00:00:00 2001 From: Fabian Schuiki Date: Sat, 3 Aug 2024 10:10:18 -0700 Subject: [PATCH 004/119] [circt-verilog] Fix lint only mode, refactor pass population If circt-verilog is run in lint-only mode, actually make the tool exit out after the initial parsing through Slang. All diagnostics have been reported at that point, and we can just exit out without bothering with any IR modifications. Also refactor the way circt-verilog populates its pass pipeline a bit. --- .../ImportVerilog/ImportVerilog.cpp | 5 ++ tools/circt-verilog/circt-verilog.cpp | 86 +++++++++++++------ 2 files changed, 64 insertions(+), 27 deletions(-) diff --git a/lib/Conversion/ImportVerilog/ImportVerilog.cpp b/lib/Conversion/ImportVerilog/ImportVerilog.cpp index 6d8865ad9fbc..e84a222438f1 100644 --- a/lib/Conversion/ImportVerilog/ImportVerilog.cpp +++ b/lib/Conversion/ImportVerilog/ImportVerilog.cpp @@ -258,6 +258,11 @@ LogicalResult ImportDriver::importVerilog(ModuleOp module) { return failure(); compileTimer.stop(); + // If we were only supposed to lint the input, return here. This leaves the + // module empty, but any Slang linting messages got reported as diagnostics. + if (options.mode == ImportVerilogOptions::Mode::OnlyLint) + return success(); + // Traverse the parsed Verilog AST and map it to the equivalent CIRCT ops. mlirContext->loadDialect(); diff --git a/tools/circt-verilog/circt-verilog.cpp b/tools/circt-verilog/circt-verilog.cpp index 36c84f67f9d1..33d7353a91a6 100644 --- a/tools/circt-verilog/circt-verilog.cpp +++ b/tools/circt-verilog/circt-verilog.cpp @@ -15,6 +15,7 @@ #include "circt/Conversion/ImportVerilog.h" #include "circt/Conversion/MooreToCore.h" #include "circt/Dialect/Moore/MoorePasses.h" +#include "circt/Support/Passes.h" #include "circt/Support/Version.h" #include "mlir/IR/BuiltinOps.h" #include "mlir/Pass/PassManager.h" @@ -216,27 +217,58 @@ struct CLOptions { static CLOptions opts; -/// Parse specified files that had been translated into Moore dialect IR. After -/// that simplify these files like deleting local variables, and then emit the -/// resulting Moore dialect IR . -static LogicalResult populateMooreTransforms(mlir::PassManager &pm) { - auto &modulePM = pm.nest(); - modulePM.addPass(moore::createLowerConcatRefPass()); - modulePM.addPass(moore::createSimplifyProceduresPass()); +//===----------------------------------------------------------------------===// +// Pass Pipeline +//===----------------------------------------------------------------------===// - pm.addPass(mlir::createSROA()); - pm.addPass(mlir::createMem2Reg()); +/// Optimize and simplify the Moore dialect IR. +static void populateMooreTransforms(PassManager &pm) { + { + // Perform an initial cleanup and preprocessing across all + // modules/functions. + auto &anyPM = pm.nestAny(); + anyPM.addPass(mlir::createCSEPass()); + anyPM.addPass(createSimpleCanonicalizerPass()); + } - // TODO: like dedup pass. + { + // Perform module-specific transformations. + auto &modulePM = pm.nest(); + modulePM.addPass(moore::createLowerConcatRefPass()); + modulePM.addPass(moore::createSimplifyProceduresPass()); + } - return success(); + { + // Perform a final cleanup across all modules/functions. + auto &anyPM = pm.nestAny(); + anyPM.addPass(mlir::createSROA()); + anyPM.addPass(mlir::createMem2Reg()); + anyPM.addPass(mlir::createCSEPass()); + anyPM.addPass(createSimpleCanonicalizerPass()); + } } /// Convert Moore dialect IR into core dialect IR -static LogicalResult populateMooreToCoreLowering(mlir::PassManager &pm) { +static void populateMooreToCoreLowering(PassManager &pm) { + // Perform the conversion. pm.addPass(createConvertMooreToCorePass()); - return success(); + { + // Conversion to the core dialects likely uncovers new canonicalization + // opportunities. + auto &anyPM = pm.nestAny(); + anyPM.addPass(mlir::createCSEPass()); + anyPM.addPass(createSimpleCanonicalizerPass()); + } +} + +/// Populate the given pass manager with transformations as configured by the +/// command line options. +static void populatePasses(PassManager &pm) { + populateMooreTransforms(pm); + if (opts.loweringMode == LoweringMode::OutputIRMoore) + return; + populateMooreToCoreLowering(pm); } //===----------------------------------------------------------------------===// @@ -308,23 +340,23 @@ static LogicalResult executeWithSources(MLIRContext *context, if (failed(importVerilog(sourceMgr, context, ts, module.get(), &options))) return failure(); - PassManager pm(context); - if (opts.loweringMode == LoweringMode::OutputIRMoore || - opts.loweringMode == LoweringMode::OutputIRHW) { - - // Simplify the Moore dialect IR. - if (failed(populateMooreTransforms(pm))) + // If the user requested for the files to be only linted, the module remains + // empty and there is nothing left to do. + if (opts.loweringMode == LoweringMode::OnlyLint) + return success(); + + // If the user requested anything besides simply parsing the input, run the + // appropriate transformation passes according to the command line options. + if (opts.loweringMode != LoweringMode::OnlyParse) { + PassManager pm(context); + pm.enableVerifier(true); + if (failed(applyPassManagerCLOptions(pm))) + return failure(); + populatePasses(pm); + if (failed(pm.run(module.get()))) return failure(); - - if (opts.loweringMode == LoweringMode::OutputIRHW) - // Convert Moore IR into core IR. - if (failed(populateMooreToCoreLowering(pm))) - return failure(); } - if (failed(pm.run(module.get()))) - return failure(); - // Print the final MLIR. module->print(outputFile->os()); outputFile->keep(); From 17545bc101e5b697f684ed2ae1ee19c431a91ded Mon Sep 17 00:00:00 2001 From: elhewaty Date: Sat, 3 Aug 2024 21:07:03 +0300 Subject: [PATCH 005/119] [Arc] Fix crash in the arc canonicalizer produced by the KeepOneVecOp optimization (#7429) --- .../Arc/Transforms/ArcCanonicalizer.cpp | 22 +++++++++---------- test/Dialect/Arc/arc-canonicalizer.mlir | 19 ++++++++++++++++ 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/lib/Dialect/Arc/Transforms/ArcCanonicalizer.cpp b/lib/Dialect/Arc/Transforms/ArcCanonicalizer.cpp index 5be87596a845..b03ef1647fdc 100644 --- a/lib/Dialect/Arc/Transforms/ArcCanonicalizer.cpp +++ b/lib/Dialect/Arc/Transforms/ArcCanonicalizer.cpp @@ -700,29 +700,27 @@ struct DenseMapInfo> { LogicalResult KeepOneVecOp::matchAndRewrite(VectorizeOp vecOp, PatternRewriter &rewriter) const { - BitVector argsToRemove(vecOp.getInputs().size(), false); DenseMap, unsigned> inExists; auto ¤tBlock = vecOp.getBody().front(); SmallVector newOperands; - unsigned shuffledBy = 0; - bool changed = false; - for (auto [argIdx, inputVec] : llvm::enumerate(vecOp.getInputs())) { - auto input = SmallVector(inputVec.begin(), inputVec.end()); + BitVector argsToRemove(vecOp.getInputs().size(), false); + for (size_t argIdx = 0; argIdx < vecOp.getInputs().size(); ++argIdx) { + auto input = SmallVector(vecOp.getInputs()[argIdx].begin(), + vecOp.getInputs()[argIdx].end()); if (auto in = inExists.find(input); in != inExists.end()) { - argsToRemove.set(argIdx); - rewriter.replaceAllUsesWith(currentBlock.getArgument(argIdx - shuffledBy), + rewriter.replaceAllUsesWith(currentBlock.getArgument(argIdx), currentBlock.getArgument(in->second)); - currentBlock.eraseArgument(argIdx - shuffledBy); - ++shuffledBy; - changed = true; + argsToRemove.set(argIdx); continue; } inExists[input] = argIdx; - newOperands.insert(newOperands.end(), inputVec.begin(), inputVec.end()); + newOperands.insert(newOperands.end(), input.begin(), input.end()); } - if (!changed) + if (argsToRemove.none()) return failure(); + + currentBlock.eraseArguments(argsToRemove); return updateInputOperands(vecOp, newOperands); } diff --git a/test/Dialect/Arc/arc-canonicalizer.mlir b/test/Dialect/Arc/arc-canonicalizer.mlir index 29c793458805..0da5216ace5e 100644 --- a/test/Dialect/Arc/arc-canonicalizer.mlir +++ b/test/Dialect/Arc/arc-canonicalizer.mlir @@ -491,6 +491,25 @@ hw.module @Repeated_input(in %b: i8, in %e: i8, in %h: i8, in %k: i8, in %en: i1 // CHECK-NEXT: hw.output // CHECK-NEXT: } + +hw.module @Repeated_again(in %clock : !seq.clock, in %0 : i1, in %in1: i1, in %in2: i1, out oh: i1) { + %2:2 = arc.vectorize (%in1, %in2), (%in1, %in2), (%0, %0), (%0, %0) : (i1, i1, i1, i1, i1, i1, i1, i1) -> (i1, i1) { + ^bb0(%arg0: i1, %arg1: i1, %arg2: i1, %arg3: i1): + %4 = comb.or %arg0, %arg1, %arg2, %arg3 : i1 + arc.vectorize.return %4 : i1 + } + hw.output %2: i1 +} + +// CHECK-LABEL: hw.module @Repeated_again(in %clock : !seq.clock, in %0 "" : i1, in %in1 : i1, in %in2 : i1, out oh : i1) { +// CHECK-NEXT: [[VEC:%.+]]:2 = arc.vectorize (%in1, %in2), (%0, %0) : (i1, i1, i1, i1) -> (i1, i1) { +// CHECK-NEXT: ^[[BLOCK:[[:alnum:]]+]](%arg0: i1, %arg1: i1): +// CHECK-NEXT: [[OR:%.+]] = comb.or %arg0, %arg1 : i1 +// CHECK-NEXT: arc.vectorize.return [[OR]] : i1 +// CHECK-NEXT: } +// CHECK-NEXT: hw.output [[VEC]]#0 : i1 +// CHECK-NEXT: } + hw.module @Repeated_input_1(in %b: i8, in %e: i8, in %h: i8, in %k: i8, in %en: i1, in %clock: !seq.clock) { %R:4 = arc.vectorize(%b, %e, %h, %k), (%b, %e, %h, %k): (i8, i8, i8, i8, i8, i8, i8, i8) -> (i8, i8, i8, i8) { ^bb0(%arg0: i8, %arg1: i8): From 17e85f15c744175d5b6a84bbbe89977cef0e279e Mon Sep 17 00:00:00 2001 From: elhewaty Date: Sun, 4 Aug 2024 08:51:13 +0300 Subject: [PATCH 006/119] [Arc] Make the canonicalizer shuffle the input vector elements before merging (#7394) --- .../Arc/Transforms/ArcCanonicalizer.cpp | 50 +++++++++++++++-- test/Dialect/Arc/arc-canonicalizer.mlir | 53 ++++++++++++++----- 2 files changed, 84 insertions(+), 19 deletions(-) diff --git a/lib/Dialect/Arc/Transforms/ArcCanonicalizer.cpp b/lib/Dialect/Arc/Transforms/ArcCanonicalizer.cpp index b03ef1647fdc..3f64a68bde1f 100644 --- a/lib/Dialect/Arc/Transforms/ArcCanonicalizer.cpp +++ b/lib/Dialect/Arc/Transforms/ArcCanonicalizer.cpp @@ -619,18 +619,58 @@ MergeVectorizeOps::matchAndRewrite(VectorizeOp vecOp, // Ensure that the input vector matches the output of the `otherVecOp` // Make sure that the results of the otherVecOp have only one use auto otherVecOp = inputVec[0].getDefiningOp(); - if (!otherVecOp || inputVec != otherVecOp.getResults() || - otherVecOp == vecOp || + if (!otherVecOp || otherVecOp == vecOp || !llvm::all_of(otherVecOp.getResults(), - [](auto result) { return result.hasOneUse(); })) { + [](auto result) { return result.hasOneUse(); }) || + !llvm::all_of(inputVec, [&](auto result) { + return result.template getDefiningOp() == otherVecOp; + })) { newOperands.insert(newOperands.end(), inputVec.begin(), inputVec.end()); continue; } + + // Here, all elements are from the same `VectorizeOp`. + // If all elements of the input vector come from the same `VectorizeOp` + // sort the vectors by their indices + DenseMap resultIdxMap; + for (auto [resultIdx, result] : llvm::enumerate(otherVecOp.getResults())) + resultIdxMap[result] = resultIdx; + + SmallVector tempVec(inputVec.begin(), inputVec.end()); + llvm::sort(tempVec, [&](Value a, Value b) { + return resultIdxMap[a] < resultIdxMap[b]; + }); + + // Check if inputVec matches the result after sorting. + if (tempVec != SmallVector(otherVecOp.getResults().begin(), + otherVecOp.getResults().end())) { + newOperands.insert(newOperands.end(), inputVec.begin(), inputVec.end()); + continue; + } + + DenseMap fromRealIdxToSortedIdx; + for (auto [inIdx, in] : llvm::enumerate(inputVec)) + fromRealIdxToSortedIdx[inIdx] = resultIdxMap[in]; + // If this flag is set that means we changed the IR so we cannot return // failure canBeMerged = true; - newOperands.insert(newOperands.end(), otherVecOp.getOperands().begin(), - otherVecOp.getOperands().end()); + + // If the results got shuffled, then shuffle the operands before merging. + if (inputVec != otherVecOp.getResults()) { + for (auto otherVecOpInputVec : otherVecOp.getInputs()) { + // use the tempVec again instead of creating another one. + tempVec = SmallVector(inputVec.size()); + for (auto [realIdx, opernad] : llvm::enumerate(otherVecOpInputVec)) + tempVec[realIdx] = + otherVecOpInputVec[fromRealIdxToSortedIdx[realIdx]]; + + newOperands.insert(newOperands.end(), tempVec.begin(), tempVec.end()); + } + + } else + newOperands.insert(newOperands.end(), otherVecOp.getOperands().begin(), + otherVecOp.getOperands().end()); auto &otherBlock = otherVecOp.getBody().front(); for (auto &otherArg : otherBlock.getArguments()) { diff --git a/test/Dialect/Arc/arc-canonicalizer.mlir b/test/Dialect/Arc/arc-canonicalizer.mlir index 0da5216ace5e..eae501a4e17d 100644 --- a/test/Dialect/Arc/arc-canonicalizer.mlir +++ b/test/Dialect/Arc/arc-canonicalizer.mlir @@ -453,22 +453,47 @@ in %clock: !seq.clock, in %o: i8, in %v: i8, in %q: i8, in %s: i8) { } // CHECK-LABEL: hw.module @Needs_Shuffle(in %b : i8, in %e : i8, in %h : i8, in %k : i8, in %c : i8, in %f : i8, in %i : i8, in %l : i8, in %n : i8, in %p : i8, in %r : i8, in %t : i8, in %en : i1, in %clock : !seq.clock, in %o : i8, in %v : i8, in %q : i8, in %s : i8) { -// CHECK-NEXT: [[VEC0:%.+]]:4 = arc.vectorize (%b, %e, %h, %k), (%c, %f, %i, %l) : (i8, i8, i8, i8, i8, i8, i8, i8) -> (i8, i8, i8, i8) { -// CHECK-NEXT: ^[[BLOCK:[[:alnum:]]+]](%arg0: i8, %arg1: i8): -// CHECK-NEXT: [[OUT:%.+]] = comb.add %arg0, %arg1 : i8 -// CHECK-NEXT: arc.vectorize.return [[OUT]] : i8 -// CHECK-NEXT: } -// CHECK-NEXT: [[VEC1:%.+]]:4 = arc.vectorize ([[VEC0]]#1, [[VEC0]]#0, [[VEC0]]#2, [[VEC0]]#3), (%n, %p, %r, %t) : (i8, i8, i8, i8, i8, i8, i8, i8) -> (i8, i8, i8, i8) { -// CHECK-NEXT: ^[[BLOCK:[[:alnum:]]+]](%arg0: i8, %arg1: i8): -// CHECK-NEXT: [[OUT:%.+]] = comb.and %arg0, %arg1 : i8 -// CHECK-NEXT: arc.vectorize.return [[OUT]] : i8 +// CHECK-NEXT: [[VEC:%.+]]:4 = arc.vectorize (%b, %e, %h, %k), (%c, %f, %i, %l), (%p, %n, %r, %t), (%o, %v, %q, %s) : (i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8) -> (i8, i8, i8, i8) { +// CHECK-NEXT: ^[[BLOCK:[[:alnum:]]+]](%arg0: i8, %arg1: i8, %arg2: i8, %arg3: i8): +// CHECK-NEXT: [[ADD:%.+]] = comb.add %arg0, %arg1 : i8 +// CHECK-NEXT: [[AND:%.+]] = comb.and [[ADD]], %arg2 : i8 +// CHECK-NEXT: [[CALL:%.+]] = arc.call @Just_A_Dummy_Func([[AND]], %arg3) : (i8, i8) -> i8 +// CHECK-NEXT: arc.vectorize.return [[CALL]] : i8 // CHECK-NEXT: } -// CHECK-NEXT: [[VEC2:%.+]]:4 = arc.vectorize ([[VEC1]]#1, [[VEC1]]#0, [[VEC1]]#2, [[VEC1]]#3), (%o, %v, %q, %s) : (i8, i8, i8, i8, i8, i8, i8, i8) -> (i8, i8, i8, i8) { -// CHECK-NEXT: ^[[BLOCK:[[:alnum:]]+]](%arg0: i8, %arg1: i8): -// CHECK-NEXT: [[OUT:%.+]] = arc.call @Just_A_Dummy_Func(%arg0, %arg1) : (i8, i8) -> i8 -// CHECK-NEXT: arc.vectorize.return [[OUT]] : i8 +// CHECK-NEXT: [[STATE:%.+]] = arc.state @FooMux(%en, [[VEC]]#0, [[STATE]]) clock %clock latency 1 : (i1, i8, i8) -> i8 +// CHECK-NEXT: hw.output +// CHECK-NEXT: } + +hw.module @Needs_Shuffle_2(in %b: i8, in %e: i8, in %h: i8, in %k: i8, in %c: i8, in %f: i8, +in %i: i8, in %l: i8, in %n: i8, in %p: i8, in %r: i8, in %t: i8, in %en: i1, +in %clock: !seq.clock, in %o: i8, in %v: i8, in %q: i8, in %s: i8) { + %R:4 = arc.vectorize(%b, %e, %h, %k), (%c, %f, %i, %l) : (i8, i8, i8, i8, i8, i8, i8, i8) -> (i8, i8, i8, i8) { + ^bb0(%arg0: i8, %arg1: i8): + %ret = comb.add %arg0, %arg1: i8 + arc.vectorize.return %ret: i8 + } + %L:4 = arc.vectorize(%R#3, %R#2, %R#1, %R#0), (%n, %p, %r, %t): (i8, i8, i8, i8, i8, i8, i8, i8) -> (i8, i8, i8, i8) { + ^bb0(%arg0: i8, %arg1: i8): + %ret = comb.and %arg0, %arg1: i8 + arc.vectorize.return %ret: i8 + } + %C:4 = arc.vectorize(%L#1, %L#0, %L#2, %L#3), (%o, %v, %q, %s) : (i8, i8, i8, i8, i8, i8, i8, i8) -> (i8, i8, i8, i8) { + ^bb0(%arg0 : i8, %arg1: i8): + %1692 = arc.call @Just_A_Dummy_Func(%arg0, %arg1) : (i8, i8) -> i8 + arc.vectorize.return %1692 : i8 + } + %4 = arc.state @FooMux(%en, %C#0, %4) clock %clock latency 1 : (i1, i8, i8) -> i8 +} + +// CHECK-LABEL: hw.module @Needs_Shuffle_2(in %b : i8, in %e : i8, in %h : i8, in %k : i8, in %c : i8, in %f : i8, in %i : i8, in %l : i8, in %n : i8, in %p : i8, in %r : i8, in %t : i8, in %en : i1, in %clock : !seq.clock, in %o : i8, in %v : i8, in %q : i8, in %s : i8) { +// CHECK-NEXT: [[VEC:%.+]]:4 = arc.vectorize (%h, %k, %e, %b), (%i, %l, %f, %c), (%p, %n, %r, %t), (%o, %v, %q, %s) : (i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8) -> (i8, i8, i8, i8) { +// CHECK-NEXT: ^[[BLOCK:[[:alnum:]]+]](%arg0: i8, %arg1: i8, %arg2: i8, %arg3: i8): +// CHECK-NEXT: [[ADD:%.+]] = comb.add %arg0, %arg1 : i8 +// CHECK-NEXT: [[AND:%.+]] = comb.and [[ADD]], %arg2 : i8 +// CHECK-NEXT: [[CALL:%.+]] = arc.call @Just_A_Dummy_Func([[AND]], %arg3) : (i8, i8) -> i8 +// CHECK-NEXT: arc.vectorize.return [[CALL]] : i8 // CHECK-NEXT: } -// CHECK-NEXT: [[STATE:%.+]] = arc.state @FooMux(%en, [[VEC2]]#0, [[STATE:%.+]]) clock %clock latency 1 : (i1, i8, i8) -> i8 +// CHECK-NEXT: [[STATE:%.+]] = arc.state @FooMux(%en, [[VEC]]#0, [[STATE]]) clock %clock latency 1 : (i1, i8, i8) -> i8 // CHECK-NEXT: hw.output // CHECK-NEXT: } From e95e92dd19b1c3a7b3a782686361c4db2b06a478 Mon Sep 17 00:00:00 2001 From: Fabian Schuiki Date: Sun, 4 Aug 2024 11:01:00 -0700 Subject: [PATCH 007/119] [ImportVerilog] Add rvalue assignment pattern support (#7428) Add support for assignment patterns like `'{42, 9001}` in rvalue position. These are roughly SystemVerilog's equivalent of `struct_create` and `array_create`. This commit also adds an `array_create` op to support assignment patterns for arrays. --- include/circt/Dialect/Moore/MooreOps.td | 14 +++++ include/circt/Dialect/Moore/MooreTypes.td | 6 ++ lib/Conversion/ImportVerilog/Expressions.cpp | 64 +++++++++++++++++++ lib/Dialect/Moore/MooreOps.cpp | 33 ++++++++++ test/Conversion/ImportVerilog/basic.sv | 66 ++++++++++++++++++++ test/Dialect/Moore/basic.mlir | 3 + 6 files changed, 186 insertions(+) diff --git a/include/circt/Dialect/Moore/MooreOps.td b/include/circt/Dialect/Moore/MooreOps.td index b4af35ad13f5..436c64176688 100644 --- a/include/circt/Dialect/Moore/MooreOps.td +++ b/include/circt/Dialect/Moore/MooreOps.td @@ -1037,6 +1037,20 @@ def DynExtractRefOp : MooreOp<"dyn_extract_ref", [Pure]> { }]; } +//===----------------------------------------------------------------------===// +// Array Manipulation +//===----------------------------------------------------------------------===// + +def ArrayCreateOp : MooreOp<"array_create", [Pure, SameTypeOperands]> { + let summary = "Create an array value from individual elements"; + let arguments = (ins Variadic:$elements); + let results = (outs AnyStaticArrayType:$result); + let assemblyFormat = [{ + $elements attr-dict `:` type($elements) `->` type($result) + }]; + let hasVerifier = 1; +} + //===----------------------------------------------------------------------===// // Struct Manipulation //===----------------------------------------------------------------------===// diff --git a/include/circt/Dialect/Moore/MooreTypes.td b/include/circt/Dialect/Moore/MooreTypes.td index 5830daab8aae..1217bc356713 100644 --- a/include/circt/Dialect/Moore/MooreTypes.td +++ b/include/circt/Dialect/Moore/MooreTypes.td @@ -406,6 +406,12 @@ def BitType : MooreType, + "packed or unpacked static array type", + "moore::UnpackedType">; + /// A packed or unpacked struct type. def AnyStructType : MooreType< Or<[StructType.predicate, UnpackedStructType.predicate]>, diff --git a/lib/Conversion/ImportVerilog/Expressions.cpp b/lib/Conversion/ImportVerilog/Expressions.cpp index 798a6be06951..353d921c4713 100644 --- a/lib/Conversion/ImportVerilog/Expressions.cpp +++ b/lib/Conversion/ImportVerilog/Expressions.cpp @@ -727,6 +727,70 @@ struct RvalueExprVisitor { return builder.create(loc, type, expr.getValue()); } + /// Handle assignment patterns. + Value visitAssignmentPattern( + const slang::ast::AssignmentPatternExpressionBase &expr, + unsigned replCount = 1) { + auto type = context.convertType(*expr.type); + + // Convert the individual elements first. + auto elementCount = expr.elements().size(); + SmallVector elements; + elements.reserve(replCount * elementCount); + for (auto elementExpr : expr.elements()) { + auto value = context.convertRvalueExpression(*elementExpr); + if (!value) + return {}; + elements.push_back(value); + } + for (unsigned replIdx = 1; replIdx < replCount; ++replIdx) + for (unsigned elementIdx = 0; elementIdx < elementCount; ++elementIdx) + elements.push_back(elements[elementIdx]); + + // Handle packed structs. + if (auto structType = dyn_cast(type)) { + assert(structType.getMembers().size() == elements.size()); + return builder.create(loc, structType, elements); + } + + // Handle unpacked structs. + if (auto structType = dyn_cast(type)) { + assert(structType.getMembers().size() == elements.size()); + return builder.create(loc, structType, elements); + } + + // Handle packed arrays. + if (auto arrayType = dyn_cast(type)) { + assert(arrayType.getSize() == elements.size()); + return builder.create(loc, arrayType, elements); + } + + // Handle unpacked arrays. + if (auto arrayType = dyn_cast(type)) { + assert(arrayType.getSize() == elements.size()); + return builder.create(loc, arrayType, elements); + } + + mlir::emitError(loc) << "unsupported assignment pattern with type " << type; + return {}; + } + + Value visit(const slang::ast::SimpleAssignmentPatternExpression &expr) { + return visitAssignmentPattern(expr); + } + + Value visit(const slang::ast::StructuredAssignmentPatternExpression &expr) { + return visitAssignmentPattern(expr); + } + + Value visit(const slang::ast::ReplicatedAssignmentPatternExpression &expr) { + slang::ast::EvalContext evalContext(context.compilation, + slang::ast::EvalFlags::CacheResults); + auto count = expr.count().eval(evalContext).integer().as(); + assert(count && "Slang guarantees constant non-zero replication count"); + return visitAssignmentPattern(expr, *count); + } + /// Emit an error for all other expressions. template Value visit(T &&node) { diff --git a/lib/Dialect/Moore/MooreOps.cpp b/lib/Dialect/Moore/MooreOps.cpp index 5c365648c62c..eae745de174c 100644 --- a/lib/Dialect/Moore/MooreOps.cpp +++ b/lib/Dialect/Moore/MooreOps.cpp @@ -512,6 +512,39 @@ LogicalResult ConcatRefOp::inferReturnTypes( return success(); } +//===----------------------------------------------------------------------===// +// ArrayCreateOp +//===----------------------------------------------------------------------===// + +static std::pair getArrayElements(Type type) { + if (auto arrayType = dyn_cast(type)) + return {arrayType.getSize(), arrayType.getElementType()}; + if (auto arrayType = dyn_cast(type)) + return {arrayType.getSize(), arrayType.getElementType()}; + assert(0 && "expected ArrayType or UnpackedArrayType"); + return {}; +} + +LogicalResult ArrayCreateOp::verify() { + auto [size, elementType] = getArrayElements(getType()); + + // Check that the number of operands matches the array size. + if (getElements().size() != size) + return emitOpError() << "has " << getElements().size() + << " operands, but result type requires " << size; + + // Check that the operand types match the array element type. We only need to + // check one of the operands, since the `SameTypeOperands` trait ensures all + // operands have the same type. + if (size > 0) { + auto value = getElements()[0]; + if (value.getType() != elementType) + return emitOpError() << "operands have type " << value.getType() + << ", but array requires " << elementType; + } + return success(); +} + //===----------------------------------------------------------------------===// // StructCreateOp //===----------------------------------------------------------------------===// diff --git a/test/Conversion/ImportVerilog/basic.sv b/test/Conversion/ImportVerilog/basic.sv index 2bf40daf82cf..ac8fe6d94041 100644 --- a/test/Conversion/ImportVerilog/basic.sv +++ b/test/Conversion/ImportVerilog/basic.sv @@ -493,6 +493,10 @@ module Expressions; struct packed { int a, b; } struct0; + // CHECK: %ustruct0 = moore.variable : > + struct { + int a, b; + } ustruct0; // CHECK: %struct1 = moore.variable : , d: struct<{a: i32, b: i32}>}>> struct packed { struct packed { @@ -512,6 +516,10 @@ module Expressions; // CHECK: %r1 = moore.variable : // CHECK: %r2 = moore.variable : real r1,r2; + // CHECK: %arrayInt = moore.variable : > + bit [1:0][31:0] arrayInt; + // CHECK: %uarrayInt = moore.variable : > + bit [31:0] uarrayInt [2]; initial begin // CHECK: moore.constant 0 : i32 @@ -1058,6 +1066,64 @@ module Expressions; // CHECK: [[TMP2:%.+]] = moore.struct_extract [[TMP1]], "b" : struct<{a: i32, b: i32}> -> i32 // CHECK: moore.blocking_assign %b, [[TMP2]] b = struct0.b; + + //===------------------------------------------------------------------===// + // Assignment Patterns + + // CHECK: [[TMP0:%.+]] = moore.constant 17 + // CHECK: [[TMP1:%.+]] = moore.constant 17 + // CHECK: moore.struct_create [[TMP0]], [[TMP1]] : !moore.i32, !moore.i32 -> struct<{a: i32, b: i32}> + struct0 = '{default: 17}; + + // CHECK: [[TMP0:%.+]] = moore.constant 1337 + // CHECK: [[TMP1:%.+]] = moore.constant 1337 + // CHECK: moore.struct_create [[TMP0]], [[TMP1]] : !moore.i32, !moore.i32 -> struct<{a: i32, b: i32}> + struct0 = '{int: 1337}; + + // CHECK: [[TMP0:%.+]] = moore.constant 420 + // CHECK: moore.struct_create [[TMP0]], [[TMP0]] : !moore.i32, !moore.i32 -> struct<{a: i32, b: i32}> + struct0 = '{2{420}}; + + // CHECK: [[TMP0:%.+]] = moore.constant 42 + // CHECK: [[TMP1:%.+]] = moore.constant 9001 + // CHECK: moore.struct_create [[TMP0]], [[TMP1]] : !moore.i32, !moore.i32 -> struct<{a: i32, b: i32}> + struct0 = '{a: 42, b: 9001}; + + // CHECK: [[TMP0:%.+]] = moore.constant 43 + // CHECK: [[TMP1:%.+]] = moore.constant 9002 + // CHECK: moore.struct_create [[TMP0]], [[TMP1]] : !moore.i32, !moore.i32 -> struct<{a: i32, b: i32}> + struct0 = '{43, 9002}; + + // CHECK: [[TMP0:%.+]] = moore.constant 44 + // CHECK: [[TMP1:%.+]] = moore.constant 9003 + // CHECK: moore.struct_create [[TMP0]], [[TMP1]] : !moore.i32, !moore.i32 -> ustruct<{a: i32, b: i32}> + ustruct0 = '{44, 9003}; + + // CHECK: [[TMP0:%.+]] = moore.constant 1 + // CHECK: [[TMP1:%.+]] = moore.constant 2 + // CHECK: [[TMP2:%.+]] = moore.struct_create [[TMP0]], [[TMP1]] : !moore.i32, !moore.i32 -> struct<{a: i32, b: i32}> + // CHECK: [[TMP0:%.+]] = moore.constant 3 + // CHECK: [[TMP1:%.+]] = moore.constant 4 + // CHECK: [[TMP3:%.+]] = moore.struct_create [[TMP0]], [[TMP1]] : !moore.i32, !moore.i32 -> struct<{a: i32, b: i32}> + // CHECK: moore.struct_create [[TMP2]], [[TMP3]] : !moore.struct<{a: i32, b: i32}>, !moore.struct<{a: i32, b: i32}> -> struct<{c: struct<{a: i32, b: i32}>, d: struct<{a: i32, b: i32}>}> + struct1 = '{c: '{a: 1, b: 2}, d: '{a: 3, b: 4}}; + + // CHECK: [[TMP0:%.+]] = moore.constant 42 + // CHECK: [[TMP1:%.+]] = moore.constant 9001 + // CHECK: moore.array_create [[TMP0]], [[TMP1]] : !moore.i32, !moore.i32 -> array<2 x i32> + arrayInt = '{42, 9001}; + + // CHECK: [[TMP0:%.+]] = moore.constant 43 + // CHECK: [[TMP1:%.+]] = moore.constant 9002 + // CHECK: moore.array_create [[TMP0]], [[TMP1]] : !moore.i32, !moore.i32 -> uarray<2 x i32> + uarrayInt = '{43, 9002}; + + // CHECK: [[TMP0:%.+]] = moore.constant 1 + // CHECK: [[TMP1:%.+]] = moore.constant 2 + // CHECK: [[TMP2:%.+]] = moore.constant 3 + // CHECK: [[TMP3:%.+]] = moore.array_create [[TMP0]], [[TMP1]], [[TMP2]], [[TMP0]], [[TMP1]], [[TMP2]] : !moore.i4, !moore.i4, !moore.i4, !moore.i4, !moore.i4, !moore.i4 -> uarray<6 x i4> + // CHECK: moore.array_create [[TMP3]], [[TMP3]], [[TMP3]] : !moore.uarray<6 x i4>, !moore.uarray<6 x i4>, !moore.uarray<6 x i4> -> uarray<3 x uarray<6 x i4>> + arr = '{3{'{2{4'd1, 4'd2, 4'd3}}}}; //===------------------------------------------------------------------===// // Builtin Functions diff --git a/test/Dialect/Moore/basic.mlir b/test/Dialect/Moore/basic.mlir index e6f372df8167..f201138ebf4a 100644 --- a/test/Dialect/Moore/basic.mlir +++ b/test/Dialect/Moore/basic.mlir @@ -275,6 +275,9 @@ moore.module @Expressions( moore.yield %b : i32 } + // CHECK: moore.array_create [[A]], [[B]] : !moore.i32, !moore.i32 -> array<2 x i32> + moore.array_create %a, %b : !moore.i32, !moore.i32 -> array<2 x i32> + // CHECK: moore.struct_create [[A]], [[B]] : !moore.i32, !moore.i32 -> struct<{a: i32, b: i32}> moore.struct_create %a, %b : !moore.i32, !moore.i32 -> struct<{a: i32, b: i32}> // CHECK: moore.struct_extract [[STRUCT1]], "a" : struct<{a: i32, b: i32}> -> i32 From c26d7705c329658ef6c9775fa5d671d736540a29 Mon Sep 17 00:00:00 2001 From: Schuyler Eldridge Date: Sun, 4 Aug 2024 14:03:21 -0400 Subject: [PATCH 008/119] [OM] Add IntegerAttr -> Python int conversion (#7430) Add a missing conversion for OM to Python conversion where only OM integers and not arbitrary MLIR integers would be converted to Python. This could result in failures in Python scripts that needed to consume OM. This fixes a bug introduced in: 17c036f87c4c9e103160b207e18ad595911945e8 Signed-off-by: Schuyler Eldridge --- lib/Bindings/Python/OMModule.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/Bindings/Python/OMModule.cpp b/lib/Bindings/Python/OMModule.cpp index 5122c88e8f44..2d42186d366b 100644 --- a/lib/Bindings/Python/OMModule.cpp +++ b/lib/Bindings/Python/OMModule.cpp @@ -366,6 +366,15 @@ Map::dunderGetItem(std::variant key) { // Convert a generic MLIR Attribute to a PythonValue. This is basically a C++ // fast path of the parts of attribute_to_var that we use in the OM dialect. static PythonPrimitive omPrimitiveToPythonValue(MlirAttribute attr) { + if (mlirAttributeIsAInteger(attr)) { + MlirType type = mlirAttributeGetType(attr); + if (mlirTypeIsAIndex(type) || mlirIntegerTypeIsSignless(type)) + return py::int_(mlirIntegerAttrGetValueInt(attr)); + if (mlirIntegerTypeIsSigned(type)) + return py::int_(mlirIntegerAttrGetValueSInt(attr)); + return py::int_(mlirIntegerAttrGetValueUInt(attr)); + } + if (omAttrIsAIntegerAttr(attr)) { auto strRef = omIntegerAttrToString(attr); return py::int_(py::str(strRef.data, strRef.length)); From 7df593d0813ffffd43b72b874b398e550e709fd2 Mon Sep 17 00:00:00 2001 From: Fabian Schuiki Date: Sun, 4 Aug 2024 12:29:20 -0700 Subject: [PATCH 009/119] [ImportVerilog] Fix value domain mismatch for logic/ternary ops Fix an issue in the mapping of the logical `&&`, `||`, `->`, and `<->` operators, where the left and right-hand side could have different value domains in the AST (one `logic`, one `bit`). Similarly, fix an issue with the `?:` ternary operator where the true and false expressions could have different but cast-compatible types. --- lib/Conversion/ImportVerilog/Expressions.cpp | 79 +++++++++++++++----- test/Conversion/ImportVerilog/basic.sv | 29 +++++++ 2 files changed, 89 insertions(+), 19 deletions(-) diff --git a/lib/Conversion/ImportVerilog/Expressions.cpp b/lib/Conversion/ImportVerilog/Expressions.cpp index 353d921c4713..79b23269f417 100644 --- a/lib/Conversion/ImportVerilog/Expressions.cpp +++ b/lib/Conversion/ImportVerilog/Expressions.cpp @@ -12,6 +12,7 @@ using namespace circt; using namespace ImportVerilog; +using moore::Domain; // NOLINTBEGIN(misc-no-recursion) namespace { @@ -62,6 +63,18 @@ struct RvalueExprVisitor { return {}; } + /// Helper function to convert a value to its "truthy" boolean value and + /// convert it to the given domain. + Value convertToBool(Value value, Domain domain) { + value = convertToBool(value); + if (!value) + return {}; + auto type = moore::IntType::get(context.getContext(), 1, domain); + if (value.getType() == type) + return value; + return builder.create(loc, type, value); + } + // Handle references to the left-hand side of a parent assignment. Value visit(const slang::ast::LValueReferenceExpression &expr) { assert(!context.lvalueStack.empty() && "parent assignments push lvalue"); @@ -242,6 +255,12 @@ struct RvalueExprVisitor { if (!rhs) return {}; + // Determine the domain of the result. + Domain domain = Domain::TwoValued; + if (expr.type->isFourState() || expr.left().type->isFourState() || + expr.right().type->isFourState()) + domain = Domain::FourValued; + using slang::ast::BinaryOperator; switch (expr.op) { case BinaryOperator::Add: @@ -322,35 +341,45 @@ struct RvalueExprVisitor { // See IEEE 1800-2017 ยง 11.4.7 "Logical operators". case BinaryOperator::LogicalAnd: { - // TODO: This should short-circuit. Put the RHS code into an scf.if. - lhs = convertToBool(lhs); - rhs = convertToBool(rhs); - if (!lhs || !rhs) + // TODO: This should short-circuit. Put the RHS code into a separate + // block. + lhs = convertToBool(lhs, domain); + if (!lhs) + return {}; + rhs = convertToBool(rhs, domain); + if (!rhs) return {}; return builder.create(loc, lhs, rhs); } case BinaryOperator::LogicalOr: { - // TODO: This should short-circuit. Put the RHS code into an scf.if. - lhs = convertToBool(lhs); - rhs = convertToBool(rhs); - if (!lhs || !rhs) + // TODO: This should short-circuit. Put the RHS code into a separate + // block. + lhs = convertToBool(lhs, domain); + if (!lhs) + return {}; + rhs = convertToBool(rhs, domain); + if (!rhs) return {}; return builder.create(loc, lhs, rhs); } case BinaryOperator::LogicalImplication: { // `(lhs -> rhs)` equivalent to `(!lhs || rhs)`. - lhs = convertToBool(lhs); - rhs = convertToBool(rhs); - if (!lhs || !rhs) + lhs = convertToBool(lhs, domain); + if (!lhs) + return {}; + rhs = convertToBool(rhs, domain); + if (!rhs) return {}; auto notLHS = builder.create(loc, lhs); return builder.create(loc, notLHS, rhs); } case BinaryOperator::LogicalEquivalence: { // `(lhs <-> rhs)` equivalent to `(lhs && rhs) || (!lhs && !rhs)`. - lhs = convertToBool(lhs); - rhs = convertToBool(rhs); - if (!lhs || !rhs) + lhs = convertToBool(lhs, domain); + if (!lhs) + return {}; + rhs = convertToBool(rhs, domain); + if (!rhs) return {}; auto notLHS = builder.create(loc, lhs); auto notRHS = builder.create(loc, rhs); @@ -620,12 +649,20 @@ struct RvalueExprVisitor { auto type = context.convertType(*expr.type); // Handle condition. - Value cond = convertToSimpleBitVector( - context.convertRvalueExpression(*expr.conditions.begin()->expr)); - cond = convertToBool(cond); - if (!cond) + if (expr.conditions.size() > 1) { + mlir::emitError(loc) + << "unsupported conditional expression with more than one condition"; + return {}; + } + const auto &cond = expr.conditions[0]; + if (cond.pattern) { + mlir::emitError(loc) << "unsupported conditional expression with pattern"; + return {}; + } + auto value = convertToBool(context.convertRvalueExpression(*cond.expr)); + if (!value) return {}; - auto conditionalOp = builder.create(loc, type, cond); + auto conditionalOp = builder.create(loc, type, value); // Create blocks for true region and false region. conditionalOp.getTrueRegion().emplaceBlock(); @@ -638,6 +675,8 @@ struct RvalueExprVisitor { auto trueValue = context.convertRvalueExpression(expr.left()); if (!trueValue) return {}; + if (trueValue.getType() != type) + trueValue = builder.create(loc, type, trueValue); builder.create(loc, trueValue); // Handle right expression. @@ -645,6 +684,8 @@ struct RvalueExprVisitor { auto falseValue = context.convertRvalueExpression(expr.right()); if (!falseValue) return {}; + if (falseValue.getType() != type) + falseValue = builder.create(loc, type, falseValue); builder.create(loc, falseValue); return conditionalOp.getResult(); diff --git a/test/Conversion/ImportVerilog/basic.sv b/test/Conversion/ImportVerilog/basic.sv index ac8fe6d94041..66de36168ea8 100644 --- a/test/Conversion/ImportVerilog/basic.sv +++ b/test/Conversion/ImportVerilog/basic.sv @@ -858,6 +858,16 @@ module Expressions; // CHECK: [[NOT_BOTH:%.+]] = moore.and [[NOT_A]], [[NOT_B]] : i1 // CHECK: moore.or [[BOTH]], [[NOT_BOTH]] : i1 c = a <-> b; + // CHECK: [[TMP:%.+]] = moore.read %x : + // CHECK: [[Y:%.+]] = moore.read %y : + // CHECK: [[X:%.+]] = moore.conversion [[TMP]] : !moore.i1 -> !moore.l1 + // CHECK: moore.and [[X]], [[Y]] : l1 + y = x && y; + // CHECK: [[Y:%.+]] = moore.read %y : + // CHECK: [[TMP:%.+]] = moore.read %x : + // CHECK: [[X:%.+]] = moore.conversion [[TMP]] : !moore.i1 -> !moore.l1 + // CHECK: moore.and [[Y]], [[X]] : l1 + y = y && x; // CHECK: [[TMP1:%.+]] = moore.read %a // CHECK: [[TMP2:%.+]] = moore.read %b @@ -1591,3 +1601,22 @@ function void funcArgs2(); funcArgs1(42, x, y, z, w); // CHECK: return endfunction + +// CHECK-LABEL: func.func private @ConvertConditionalExprsToResultType( +function void ConvertConditionalExprsToResultType(bit [15:0] x, struct packed { bit [15:0] a; } y, bit z); + bit [15:0] r; + // CHECK: moore.conditional %arg2 : i1 -> i16 { + // CHECK: moore.yield %arg0 + // CHECK: } { + // CHECK: [[TMP:%.+]] = moore.conversion %arg1 + // CHECK: moore.yield [[TMP]] + // CHECK: } + r = z ? x : y; + // CHECK: moore.conditional %arg2 : i1 -> i16 { + // CHECK: [[TMP:%.+]] = moore.conversion %arg1 + // CHECK: moore.yield [[TMP]] + // CHECK: } { + // CHECK: moore.yield %arg0 + // CHECK: } + r = z ? y : x; +endfunction From 461c63146ef105a076d1fb53def82bc5687f5ef8 Mon Sep 17 00:00:00 2001 From: Hideto Ueno Date: Mon, 5 Aug 2024 12:21:05 +0900 Subject: [PATCH 010/119] [FIRRTL][LowerDPI] Lower FIRRTL vector to an open array type (#7305) Previously a FIRRTL vector was lowered into a packed array and there was no way to generate an open array. This PR changes to use unpacked open array which is supported by several tools (at least verilator and vcs) by default. --- docs/Dialects/FIRRTL/FIRRTLIntrinsics.md | 6 +- lib/Dialect/FIRRTL/Transforms/LowerDPI.cpp | 92 ++++++++++++++++++---- test/Dialect/FIRRTL/lower-intrinsics.mlir | 2 +- test/firtool/dpi.fir | 8 +- 4 files changed, 87 insertions(+), 21 deletions(-) diff --git a/docs/Dialects/FIRRTL/FIRRTLIntrinsics.md b/docs/Dialects/FIRRTL/FIRRTLIntrinsics.md index 1ba3aacf4eb4..4ca5cdbec72c 100644 --- a/docs/Dialects/FIRRTL/FIRRTLIntrinsics.md +++ b/docs/Dialects/FIRRTL/FIRRTLIntrinsics.md @@ -252,7 +252,8 @@ Function Declaration: Types: * Operand and result types must be passive. -* Aggregate types are lowered into corresponding verilog aggregate. +* A vector is lowered to an unpacked open array type, e.g. `a: Vec<4, UInt<8>>` to `byte a []`. +* A bundle is lowered to a packed struct. * Integer types are lowered into into 2-state types. * Small integer types (< 64 bit) must be compatible to C-types and arguments are passed by values. Users are required to use specific integer types for small integers shown in the table below. Large integers are lowered to `bit` and passed by a reference. @@ -267,12 +268,13 @@ Types: Example SV output: ```firrtl -node result = intrinsic(circt_dpi_call : UInt<64>, clock, enable, uint_8_value, uint_32_value) +node result = intrinsic(circt_dpi_call : UInt<64>, clock, enable, uint_8_value, uint_32_value, uint_8_vector) ``` ```verilog import "DPI-C" function void dpi_func( input byte in_0, int in_1, + byte in_2[], output longint out_0 ); diff --git a/lib/Dialect/FIRRTL/Transforms/LowerDPI.cpp b/lib/Dialect/FIRRTL/Transforms/LowerDPI.cpp index c0ae5dc03e7c..b8a5b3c760cc 100644 --- a/lib/Dialect/FIRRTL/Transforms/LowerDPI.cpp +++ b/lib/Dialect/FIRRTL/Transforms/LowerDPI.cpp @@ -16,6 +16,7 @@ #include "circt/Dialect/FIRRTL/FIRRTLUtils.h" #include "circt/Dialect/FIRRTL/Namespace.h" #include "circt/Dialect/FIRRTL/Passes.h" +#include "circt/Dialect/SV/SVOps.h" #include "circt/Dialect/Sim/SimOps.h" #include "mlir/IR/BuiltinOps.h" #include "mlir/IR/Threading.h" @@ -91,6 +92,71 @@ void LowerDPI::collectIntrinsics() { funcNameToCallSites[dpi.getFunctionNameAttr()].push_back(dpi); } +// Lower FIRRTL type to core dialect types in which array type is replaced with +// an open array type. +static Type lowerDPIArgumentType(Type type) { + auto loweredType = lowerType(type); + return loweredType.replace([](hw::ArrayType array) { + return sv::UnpackedOpenArrayType::get(array.getElementType()); + }); +} + +static Value convertToUnpackedArray(ImplicitLocOpBuilder &builder, + Value loweredValue) { + if (isa(loweredValue.getType())) + return loweredValue; + + auto array = dyn_cast(loweredValue.getType()); + if (!array) { + // TODO: Support bundle or enum types. + return {}; + } + + SmallVector values; + auto length = array.getNumElements(); + auto width = llvm::Log2_64_Ceil(length); + + // Create an unpacked array from a packed array. + for (int i = length - 1; i >= 0; --i) { + auto index = builder.create(APInt(width, i)); + auto elem = convertToUnpackedArray( + builder, builder.create(loweredValue, index)); + if (!elem) + return {}; + values.push_back(elem); + } + + return builder.create( + hw::UnpackedArrayType::get(lowerType(array.getElementType()), length), + values); +} + +static Value getLowered(ImplicitLocOpBuilder &builder, Value value) { + // Insert an unrealized conversion to cast FIRRTL type to HW type. + if (!value) + return value; + + auto type = lowerType(value.getType()); + Value result = builder.create(type, value) + ->getResult(0); + + // We may need to cast the lowered value to a DPI specific type (e.g. open + // array). Get a DPI type and check if they are same. + auto dpiType = lowerDPIArgumentType(value.getType()); + if (type == dpiType) + return result; + + // Cast into unpacked open array type. + result = convertToUnpackedArray(builder, result); + if (!result) { + mlir::emitError(value.getLoc()) + << "contains a type that currently not supported"; + return {}; + } + + return builder.create(dpiType, result); +} + LogicalResult LowerDPI::lower() { for (auto [name, calls] : funcNameToCallSites) { auto firstDPICallop = calls.front(); @@ -103,25 +169,21 @@ LogicalResult LowerDPI::lower() { ImplicitLocOpBuilder builder(firstDPICallop.getLoc(), circuitOp.getOperation()); auto lowerCall = [&](DPICallIntrinsicOp dpiOp) { - auto getLowered = [&](Value value) -> Value { - // Insert an unrealized conversion to cast FIRRTL type to HW type. - if (!value) - return value; - auto type = lowerType(value.getType()); - return builder.create(type, value) - ->getResult(0); - }; builder.setInsertionPoint(dpiOp); - auto clock = getLowered(dpiOp.getClock()); - auto enable = getLowered(dpiOp.getEnable()); + auto clock = getLowered(builder, dpiOp.getClock()); + auto enable = getLowered(builder, dpiOp.getEnable()); SmallVector inputs; inputs.reserve(dpiOp.getInputs().size()); - for (auto input : dpiOp.getInputs()) - inputs.push_back(getLowered(input)); + for (auto input : dpiOp.getInputs()) { + inputs.push_back(getLowered(builder, input)); + if (!inputs.back()) + return failure(); + } SmallVector outputTypes; if (dpiOp.getResult()) - outputTypes.push_back(lowerType(dpiOp.getResult().getType())); + outputTypes.push_back( + lowerDPIArgumentType(dpiOp.getResult().getType())); auto call = builder.create( outputTypes, firstDPIDecl.getSymNameAttr(), clock, enable, inputs); @@ -188,7 +250,7 @@ sim::DPIFuncOp LowerDPI::getOrCreateDPIFuncDecl(DPICallIntrinsicOp op) { port.dir = hw::ModulePort::Direction::Input; port.name = inputNames ? cast(inputNames[idx]) : builder.getStringAttr(Twine("in_") + Twine(idx)); - port.type = lowerType(inType); + port.type = lowerDPIArgumentType(inType); ports.push_back(port); } @@ -198,7 +260,7 @@ sim::DPIFuncOp LowerDPI::getOrCreateDPIFuncDecl(DPICallIntrinsicOp op) { port.dir = hw::ModulePort::Direction::Output; port.name = outputName ? outputName : builder.getStringAttr(Twine("out_") + Twine(idx)); - port.type = lowerType(outType); + port.type = lowerDPIArgumentType(outType); ports.push_back(port); } diff --git a/test/Dialect/FIRRTL/lower-intrinsics.mlir b/test/Dialect/FIRRTL/lower-intrinsics.mlir index fb0199c7f470..a9ce815628d3 100644 --- a/test/Dialect/FIRRTL/lower-intrinsics.mlir +++ b/test/Dialect/FIRRTL/lower-intrinsics.mlir @@ -155,7 +155,7 @@ firrtl.circuit "Foo" { firrtl.int.generic "circt_fpga_probe" %data, %clock : (!firrtl.uint<32>, !firrtl.clock) -> () } - // CHECK-LABEL: firrtl.module private @DPIIntrinsicTest(in %clock: !firrtl.clock, in %enable: !firrtl.uint<1>, in %in1: !firrtl.uint<8>, in %in2: !firrtl.uint<8>) + // CHECK-LABEL: firrtl.module private @DPIIntrinsicTest firrtl.module private @DPIIntrinsicTest(in %clock : !firrtl.clock, in %enable : !firrtl.uint<1>, in %in1: !firrtl.uint<8>, in %in2: !firrtl.uint<8>) { // CHECK-NEXT: %0 = firrtl.int.dpi.call "clocked_result"(%in1, %in2) clock %clock enable %enable {inputNames = ["foo", "bar"], outputName = "baz"} : (!firrtl.uint<8>, !firrtl.uint<8>) -> !firrtl.uint<8> %0 = firrtl.int.generic "circt_dpi_call" %clock, %enable, %in1, %in2 : (!firrtl.clock, !firrtl.uint<1>, !firrtl.uint<8>, !firrtl.uint<8>) -> !firrtl.uint<8> diff --git a/test/firtool/dpi.fir b/test/firtool/dpi.fir index 1e5af1ae57b4..b7a353b2c77c 100644 --- a/test/firtool/dpi.fir +++ b/test/firtool/dpi.fir @@ -10,7 +10,8 @@ circuit DPI: ; CHECK-LABEL: import "DPI-C" context function void clocked_void( ; CHECK-NEXT: input byte in_0, -; CHECK-NEXT: in_1 +; CHECK-NEXT: in_1, +; CHECK-NEXT: in_2[] ; CHECK-NEXT: ); ; CHECK-LABEL: import "DPI-C" context function void unclocked_result( @@ -22,11 +23,12 @@ circuit DPI: ; CHECK-LABEL: module DPI( ; CHECK: logic [7:0] [[TMP:_.+]]; ; CHECK-NEXT: reg [7:0] [[RESULT1:_.+]]; +; CHECK-NEXT: wire [7:0] [[OPEN_ARRAY:_.+]][0:1] = '{in_0, in_1}; ; CHECK-NEXT: always @(posedge clock) begin ; CHECK-NEXT: if (enable) begin ; CHECK-NEXT: clocked_result(in_0, in_1, [[TMP]]); ; CHECK-NEXT: [[RESULT1]] <= [[TMP]]; -; CHECK-NEXT: clocked_void(in_0, in_1); +; CHECK-NEXT: clocked_void(in_0, in_1, [[OPEN_ARRAY]]); ; CHECK-NEXT: end ; CHECK-NEXT: end // always @(posedge) ; CHECK-NEXT: reg [7:0] [[RESULT2:_.+]]; @@ -47,7 +49,7 @@ circuit DPI: output out : UInt<8>[2] node result1 = intrinsic(circt_dpi_call : UInt<8>, clock, enable, in[0], in[1]) - intrinsic(circt_dpi_call, clock, enable, in[0], in[1]) + intrinsic(circt_dpi_call, clock, enable, in[0], in[1], in) node result2 = intrinsic(circt_dpi_call : UInt<8>, enable, in[0], in[1]) out[0] <= result1 From cbdee94d962d2901d67cb5bf6b62ec485fbc6f0c Mon Sep 17 00:00:00 2001 From: Fabian Schuiki Date: Mon, 5 Aug 2024 09:42:26 -0700 Subject: [PATCH 011/119] [Support] Add FVInt, a four-valued arbitrary precision integer (#7422) Add the `FVInt` class to CIRCT's support library. This class can represent arbitrary precision integers where each bit can be one of the four values 0, 1, X, and Z. The name intends to suggest a *four-valued APInt*, with the option to also introduce a *nine-valued NVInt* in the future. Internally, `FVInt` uses two `APInt`s to store its data: `value` stores whether a bit is 0/X or 1/Z, and `unknown` stores whether a bit is known (0 or 1) or unknown (X or Z). Together they allocate 2 bits of storage for each of the `FVInt`'s digits, which allows for four different values per digit. This representation as `value` and `unknown` makes many of the logical and arithmetic operations pretty straightforward to implement. Most four-valued operations can be trivially implemented by performing the equivalent two-valued operation on `value`, and then accounting for X and Z bits through a few logic operations on `unknown`. Note that Slang defines its own version of this (`SVInt`). But since Slang is an optional dependency of CIRCT, it makes sense to have a CIRCT equivalent that is built around LLVM's `APInt`, for use in our dialects. This first version of `FVInt` has a rather incomplete set of operations, but it covers basic AND, OR, XOR, NOT, negation, addition, subtraction, and multiplication as a proof-of-concept. The remaining operations will be added in future commits. We are also going to need a four-valued equivalent of `IntegerAttr` based on `FVInt`. This commit is motivated by the Slang frontend, which now supports enough of SystemVerilog to make some test designs start to hit the lack of number literals with X and Z. --- include/circt/Support/FVInt.h | 592 +++++++++++++++++++++++++++++++ lib/Support/CMakeLists.txt | 8 +- lib/Support/FVInt.cpp | 139 ++++++++ unittests/Support/CMakeLists.txt | 1 + unittests/Support/FVIntTest.cpp | 134 +++++++ 5 files changed, 872 insertions(+), 2 deletions(-) create mode 100644 include/circt/Support/FVInt.h create mode 100644 lib/Support/FVInt.cpp create mode 100644 unittests/Support/FVIntTest.cpp diff --git a/include/circt/Support/FVInt.h b/include/circt/Support/FVInt.h new file mode 100644 index 000000000000..e657f65f10d2 --- /dev/null +++ b/include/circt/Support/FVInt.h @@ -0,0 +1,592 @@ +//===- FVInt.h - Four-valued integer ----------------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// This file implements a class to represent arbitrary precision integers where +// each bit can be one of four values. This corresponds to SystemVerilog's +// four-valued `logic` type (originally defined in IEEE 1364, later merged into +// IEEE 1800). +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_SUPPORT_FVINT_H +#define CIRCT_SUPPORT_FVINT_H + +#include "circt/Support/LLVM.h" +#include "llvm/ADT/APInt.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/raw_ostream.h" + +namespace circt { + +/// Four-valued arbitrary precision integers. +/// +/// Each bit of the integer can be 0, 1, X, or Z. Internally the bits are stored +/// in a pair of `APInt`s, one of which specifies the value of each bit (0/X or +/// 1/Z), and the other whether the bit is unknown (X or Z). +class FVInt { +public: + /// Construct an `FVInt` from a 64-bit value. The result has no X or Z bits. + FVInt(unsigned numBits, uint64_t value, bool isSigned = false) + : FVInt(APInt(numBits, value, isSigned)) {} + + /// Construct an `FVInt` from an `APInt`. The result has no X or Z bits. + FVInt(const APInt &value) + : value(value), unknown(APInt::getZero(value.getBitWidth())) {} + + /// Construct an `FVInt` from two `APInt`s used internally to store the bit + /// data. The first argument specifies whether each bit is 0/X or 1/Z. The + /// second argument specifies whether each bit is 0/1 or X/Z. Both `APInt`s + /// must have the same bitwidth. The two arguments correspond to the results + /// of `getRawValue()` and `getRawUnknown()`. + FVInt(APInt &&rawValue, APInt &&rawUnknown) + : value(rawValue), unknown(rawUnknown) { + assert(rawValue.getBitWidth() == rawUnknown.getBitWidth()); + } + + /// Construct an `FVInt` with all bits set to 0. + static FVInt getZero(unsigned numBits) { + return FVInt(APInt::getZero(numBits)); + } + + /// Construct an `FVInt` with all bits set to 1. + static FVInt getAllOnes(unsigned numBits) { + return FVInt(APInt::getAllOnes(numBits)); + } + + /// Construct an `FVInt` with all bits set to X. + static FVInt getAllX(unsigned numBits) { + return FVInt(APInt::getZero(numBits), APInt::getAllOnes(numBits)); + } + + /// Construct an `FVInt` with all bits set to Z. + static FVInt getAllZ(unsigned numBits) { + return FVInt(APInt::getAllOnes(numBits), APInt::getAllOnes(numBits)); + } + + /// Return the number of bits this integer has. + unsigned getBitWidth() const { return value.getBitWidth(); } + + /// Return the underlying `APInt` used to store whether a bit is 0/X or 1/Z. + const APInt &getRawValue() const { return value; } + + /// Return the underlying `APInt` used to store whether a bit is unknown (X or + /// Z). + const APInt &getRawUnknown() const { return unknown; } + + /// Convert the four-valued `FVInt` to a two-valued `APInt` by mapping X and Z + /// bits to either 0 or 1. + APInt toAPInt(bool unknownBitMapping) const { + auto v = value; + if (unknownBitMapping) + v |= unknown; // set unknown bits to 1 + else + v &= ~unknown; // set unknown bits to 0 + return v; + } + + //===--------------------------------------------------------------------===// + // Resizing + //===--------------------------------------------------------------------===// + + /// Zero-extend the integer to a new bit width. The additional high-order bits + /// are filled in with zero. + FVInt zext(unsigned bitWidth) const { + return FVInt(value.zext(bitWidth), unknown.zext(bitWidth)); + } + + /// Sign-extend the integer to a new bit width. The additional high-order bits + /// are filled in with the sign bit (top-most bit) of the original number, + /// also when that sign bit is X or Z. Zero-width integers are extended with + /// zeros. + FVInt sext(unsigned bitWidth) const { + return FVInt(value.sext(bitWidth), unknown.sext(bitWidth)); + } + + //===--------------------------------------------------------------------===// + // Value Tests + //===--------------------------------------------------------------------===// + + /// Determine if any bits are X or Z. + bool hasUnknown() const { return !unknown.isZero(); } + + /// Determine if all bits are 0. This is true for zero-width values. + bool isZero() const { return value.isZero() && unknown.isZero(); } + + /// Determine if all bits are 1. This is true for zero-width values. + bool isAllOnes() const { return value.isAllOnes() && unknown.isZero(); } + + /// Determine if all bits are X. This is true for zero-width values. + bool isAllX() const { return value.isZero() && unknown.isAllOnes(); } + + /// Determine if all bits are Z. This is true for zero-width values. + bool isAllZ() const { return value.isAllOnes() && unknown.isAllOnes(); } + + //===--------------------------------------------------------------------===// + // Bit Manipulation + //===--------------------------------------------------------------------===// + + /// The value of an individual bit. Can be 0, 1, X, or Z. + enum Bit { V0 = 0b00, V1 = 0b01, X = 0b10, Z = 0b11 }; + + /// Get the value of an individual bit. + Bit getBit(unsigned index) const { + return static_cast(value[index] | unknown[index] << 1); + } + + /// Set the value of an individual bit. + void setBit(unsigned index, Bit bit) { + value.setBitVal(index, (bit >> 0) & 1); + unknown.setBitVal(index, (bit >> 1) & 1); + } + + /// Compute a mask of all the 0 bits in this integer. + APInt getZeroBits() const { return ~value & ~unknown; } + + /// Compute a mask of all the 1 bits in this integer. + APInt getOneBits() const { return value & ~unknown; } + + /// Compute a mask of all the X bits in this integer. + APInt getXBits() const { return ~value & unknown; } + + /// Compute a mask of all the Z bits in this integer. + APInt getZBits() const { return value & unknown; } + + /// Set the value of all bits in the mask to 0. + template + void setZeroBits(const T &mask) { + value &= ~mask; + unknown &= ~mask; + } + + /// Set the value of all bits in the mask to 1. + template + void setOneBits(const T &mask) { + value |= mask; + unknown &= ~mask; + } + + /// Set the value of all bits in the mask to X. + template + void setXBits(const T &mask) { + value &= ~mask; + unknown |= mask; + } + + /// Set the value of all bits in the mask to Z. + template + void setZBits(const T &mask) { + value |= mask; + unknown |= mask; + } + + /// Set all bits to 0. + void setAllZero() { + value.clearAllBits(); + unknown.clearAllBits(); + } + + /// Set all bits to 1. + void setAllOne() { + value.setAllBits(); + unknown.clearAllBits(); + } + + /// Set all bits to X. + void setAllX() { + value.clearAllBits(); + unknown.setAllBits(); + } + + /// Set all bits to Z. + void setAllZ() { + value.setAllBits(); + unknown.setAllBits(); + } + + /// Replace all Z bits with X. This is useful since most logic operations will + /// treat X and Z bits the same way and produce an X bit in the output. By + /// mapping Z bits to X, these operations can then just handle 0, 1, and X + /// bits. + void replaceZWithX() { + // Z bits have value and unknown set to 1. X bits have value set to 0 and + // unknown set to 1. To convert between the two, make sure that value is 0 + // wherever unknown is 1. + value &= ~unknown; + } + + /// If any bits are X or Z, set the entire integer to X. + void setAllXIfAnyUnknown() { + if (hasUnknown()) + setAllX(); + } + + /// If any bits in this integer or another integer are X or Z, set the entire + /// integer to X. This is useful for binary operators which want to set their + /// result to X if either of the two inputs contained an X or Z bit. + void setAllXIfAnyUnknown(const FVInt &other) { + if (hasUnknown() || other.hasUnknown()) + setAllX(); + } + + //===--------------------------------------------------------------------===// + // Shift Operators + //===--------------------------------------------------------------------===// + + /// Perform a logical left-shift. If any bits in the shift amount are unknown, + /// the entire result is X. + FVInt &operator<<=(const FVInt &amount) { + if (amount.hasUnknown()) { + setAllX(); + } else { + value <<= amount.value; + unknown <<= amount.value; + } + return *this; + } + + /// Perform a logical left-shift by a two-valued amount. + template + FVInt &operator<<=(const T &amount) { + value <<= amount; + unknown <<= amount; + return *this; + } + + //===--------------------------------------------------------------------===// + // Logic Operators + //===--------------------------------------------------------------------===// + + /// Compute the logical NOT of this integer. This implements the following + /// bit-wise truth table: + /// ``` + /// 0 | 1 + /// 1 | 0 + /// X | X + /// Z | X + /// ``` + void flipAllBits() { + value = ~value; + replaceZWithX(); + } + + /// Compute the logical NOT. + FVInt operator~() const { + auto v = *this; + v.flipAllBits(); + return v; + } + + /// Compute the logical AND of this integer and another. This implements the + /// following bit-wise truth table: + /// ``` + /// 0 1 X Z + /// +-------- + /// 0 | 0 0 0 0 + /// 1 | 0 1 X X + /// X | 0 X X X + /// Z | 0 X X X + /// ``` + FVInt &operator&=(const FVInt &other) { + auto zeros = getZeroBits() | other.getZeroBits(); + value &= other.value; + unknown |= other.unknown; + unknown &= ~zeros; + replaceZWithX(); + return *this; + } + + /// Compute the logical AND of this integer and a two-valued integer. + template + FVInt &operator&=(T other) { + value &= other; + unknown &= other; // make 0 bits known + replaceZWithX(); + return *this; + } + + /// Compute the logical AND. + template + FVInt operator&(const T &other) const { + auto v = *this; + v &= other; + return v; + } + + /// Compute the logical OR of this integer and another. This implements the + /// following bit-wise truth table: + /// ``` + /// 0 1 X Z + /// +-------- + /// 0 | 0 1 X X + /// 1 | 1 1 1 1 + /// X | X 1 X X + /// Z | X 1 X X + /// ``` + FVInt &operator|=(const FVInt &other) { + auto ones = getOneBits() | other.getOneBits(); + value |= other.value; + unknown |= other.unknown; + unknown &= ~ones; + replaceZWithX(); + return *this; + } + + /// Compute the logical OR of this integer and a two-valued integer. + template + FVInt &operator|=(T other) { + value |= other; + unknown &= ~other; // make 1 bits known + replaceZWithX(); + return *this; + } + + /// Compute the logical OR. + template + FVInt operator|(const T &other) const { + auto v = *this; + v |= other; + return v; + } + + /// Compute the logical XOR of this integer and another. This implements the + /// following bit-wise truth table: + /// ``` + /// 0 1 X Z + /// +-------- + /// 0 | 0 1 X X + /// 1 | 1 0 X X + /// X | X X X X + /// Z | X X X X + /// ``` + FVInt &operator^=(const FVInt &other) { + value ^= other.value; + unknown |= other.unknown; + replaceZWithX(); + return *this; + } + + /// Compute the logical XOR of this integer and a two-valued integer. + template + FVInt &operator^=(const T &other) { + value ^= other; + replaceZWithX(); + return *this; + } + + /// Compute the logical XOR. + template + FVInt operator^(const T &other) const { + auto v = *this; + v ^= other; + return v; + } + + //===--------------------------------------------------------------------===// + // Arithmetic Operators + //===--------------------------------------------------------------------===// + + /// Compute the negation of this integer. If any bits are unknown, the entire + /// result is X. + void negate() { + value.negate(); + setAllXIfAnyUnknown(); + } + + /// Compute the negation of this integer. + FVInt operator-() const { + auto v = *this; + v.negate(); + return v; + } + + /// Compute the addition of this integer and another. If any bits in either + /// integer are unknown, the entire result is X. + FVInt &operator+=(const FVInt &other) { + value += other.value; + setAllXIfAnyUnknown(other); + return *this; + } + + /// Compute the addition of this integer and a two-valued integer. If any bit + /// in the integer is unknown, the entire result is X. + template + FVInt &operator+=(const T &other) { + value += other; + setAllXIfAnyUnknown(); + return *this; + } + + /// Compute an addition. + template + FVInt operator+(const T &other) const { + auto v = *this; + v += other; + return v; + } + + /// Compute the subtraction of this integer and another. If any bits in either + /// integer are unknown, the entire result is X. + FVInt &operator-=(const FVInt &other) { + value -= other.value; + setAllXIfAnyUnknown(other); + return *this; + } + + /// Compute the subtraction of this integer and a two-valued integer. If any + /// bit in the integer is unknown, the entire result is X. + template + FVInt &operator-=(const T &other) { + value -= other; + setAllXIfAnyUnknown(); + return *this; + } + + /// Compute an subtraction. + template + FVInt operator-(const T &other) const { + auto v = *this; + v -= other; + return v; + } + + /// Compute the multiplication of this integer and another. If any bits in + /// either integer are unknown, the entire result is X. + FVInt &operator*=(const FVInt &other) { + value *= other.value; + setAllXIfAnyUnknown(other); + return *this; + } + + /// Compute the multiplication of this integer and a two-valued integer. If + /// any bit in the integer is unknown, the entire result is X. + template + FVInt &operator*=(const T &other) { + value *= other; + setAllXIfAnyUnknown(); + return *this; + } + + /// Compute a multiplication. + template + FVInt operator*(const T &other) const { + auto v = *this; + v *= other; + return v; + } + + //===--------------------------------------------------------------------===// + // Comparison + //===--------------------------------------------------------------------===// + + /// Determine whether this integer is equal to another. Note that this + /// corresponds to SystemVerilog's `===` operator. + bool operator==(const FVInt &other) const { + return value == other.value && unknown == other.unknown; + } + + /// Determine whether this integer is equal to a two-valued integer. Note that + /// this corresponds to SystemVerilog's `===` operator. + template + bool operator==(const T &other) const { + return value == other && !hasUnknown(); + } + + /// Determine whether this integer is not equal to another. + bool operator!=(const FVInt &other) const { return !((*this) == other); } + + /// Determine whether this integer is not equal to a two-valued integer. + template + bool operator!=(const T &other) const { + return !((*this) == other); + } + + //===--------------------------------------------------------------------===// + // String Conversion + //===--------------------------------------------------------------------===// + + /// Convert a string into an `FVInt`. + /// + /// The radix can be 2, 8, 10, or 16. For radix 2, the input string may + /// contain the characters `x` or `X` to indicate an unknown X bit, and `z` or + /// `Z` to indicate an unknown Z bit. For radix 8, each X or Z counts as 3 + /// bits. For radix 16, each X and Z counts as 4 bits. When radix is 10 the + /// input cannot contain any X or Z. + /// + /// Returns the parsed integer if the string is non-empty and a well-formed + /// number, otherwise returns none. + static std::optional tryFromString(StringRef str, unsigned radix = 10); + + /// Convert a string into an `FVInt`. Same as `tryFromString`, but aborts if + /// the string is malformed. + static FVInt fromString(StringRef str, unsigned radix = 10) { + auto v = tryFromString(str, radix); + assert(v.has_value() && "string is not a well-formed FVInt"); + return *v; + } + + /// Convert an `FVInt` to a string. + /// + /// The radix can be 2, 8, 10, or 16. For radix 8 or 16, the integer can only + /// contain unknown bits in groups of 3 or 4, respectively, such that a `X` or + /// `Z` can be printed for the entire group of bits. For radix 10, the integer + /// cannot contain any unknown bits. In case the output contains letters, + /// `uppercase` specifies whether they are printed as uppercase letters. + /// + /// Appends the output characters to `str` and returns true if the integer + /// could be printed with the given configuration. Otherwise returns false and + /// leaves `str` in its original state. Always succeeds for radix 2. + bool tryToString(SmallVectorImpl &str, unsigned radix = 10, + bool uppercase = true) const; + + /// Convert an `FVInt` to a string. Same as `tryToString`, but directly + /// returns the string and aborts if the conversion is unsuccessful. + SmallString<16> toString(unsigned radix = 10, bool uppercase = true) const { + SmallString<16> str; + bool success = tryToString(str, radix, uppercase); + assert(success && "radix cannot represent FVInt"); + return str; + } + + /// Print an `FVInt` to an output stream. + void print(raw_ostream &os) const; + +private: + APInt value; + APInt unknown; +}; + +inline FVInt operator&(uint64_t a, const FVInt &b) { return b & a; } +inline FVInt operator|(uint64_t a, const FVInt &b) { return b | a; } +inline FVInt operator^(uint64_t a, const FVInt &b) { return b ^ a; } +inline FVInt operator+(uint64_t a, const FVInt &b) { return b + a; } +inline FVInt operator*(uint64_t a, const FVInt &b) { return b * a; } + +inline FVInt operator&(const APInt &a, const FVInt &b) { return b & a; } +inline FVInt operator|(const APInt &a, const FVInt &b) { return b | a; } +inline FVInt operator^(const APInt &a, const FVInt &b) { return b ^ a; } +inline FVInt operator+(const APInt &a, const FVInt &b) { return b + a; } +inline FVInt operator*(const APInt &a, const FVInt &b) { return b * a; } + +inline FVInt operator-(uint64_t a, const FVInt &b) { + return FVInt(b.getBitWidth(), a) - b; +} + +inline FVInt operator-(const APInt &a, const FVInt &b) { return FVInt(a) - b; } + +inline bool operator==(uint64_t a, const FVInt &b) { return b == a; } +inline bool operator!=(uint64_t a, const FVInt &b) { return b != a; } + +inline raw_ostream &operator<<(raw_ostream &os, const FVInt &value) { + value.print(os); + return os; +} + +} // namespace circt + +#endif // CIRCT_SUPPORT_FVINT_H diff --git a/lib/Support/CMakeLists.txt b/lib/Support/CMakeLists.txt index 08b9d250ad35..1a38545425ab 100644 --- a/lib/Support/CMakeLists.txt +++ b/lib/Support/CMakeLists.txt @@ -11,6 +11,7 @@ add_circt_library(CIRCTSupport CustomDirectiveImpl.cpp Debug.cpp FieldRef.cpp + FVInt.cpp JSON.cpp LoweringOptions.cpp Naming.cpp @@ -33,13 +34,15 @@ add_circt_library(CIRCTSupport #------------------------------------------------------------------------------- # Generate Version.cpp #------------------------------------------------------------------------------- + find_first_existing_vc_file("${CIRCT_SOURCE_DIR}" CIRCT_GIT_LOGS_HEAD) set(GEN_VERSION_SCRIPT "${CIRCT_SOURCE_DIR}/cmake/modules/GenVersionFile.cmake") if (CIRCT_RELEASE_TAG_ENABLED) add_custom_command(OUTPUT "${VERSION_CPP}" DEPENDS "${CIRCT_GIT_LOGS_HEAD}" "${GEN_VERSION_SCRIPT}" - COMMAND ${CMAKE_COMMAND} -DIN_FILE="${CMAKE_CURRENT_SOURCE_DIR}/Version.cpp.in" + COMMAND ${CMAKE_COMMAND} + -DIN_FILE="${CMAKE_CURRENT_SOURCE_DIR}/Version.cpp.in" -DOUT_FILE="${VERSION_CPP}" -DRELEASE_PATTERN=${CIRCT_RELEASE_TAG}* -DDRY_RUN=OFF -DSOURCE_ROOT="${CIRCT_SOURCE_DIR}" -P "${GEN_VERSION_SCRIPT}") @@ -48,7 +51,8 @@ else () # cmake configuration. add_custom_command(OUTPUT "${VERSION_CPP}" DEPENDS "${GEN_VERSION_SCRIPT}" - COMMAND ${CMAKE_COMMAND} -DIN_FILE="${CMAKE_CURRENT_SOURCE_DIR}/Version.cpp.in" + COMMAND ${CMAKE_COMMAND} + -DIN_FILE="${CMAKE_CURRENT_SOURCE_DIR}/Version.cpp.in" -DOUT_FILE="${VERSION_CPP}" -DDRY_RUN=ON -DSOURCE_ROOT="${CIRCT_SOURCE_DIR}" -P "${GEN_VERSION_SCRIPT}") endif() diff --git a/lib/Support/FVInt.cpp b/lib/Support/FVInt.cpp new file mode 100644 index 000000000000..e70323cb8936 --- /dev/null +++ b/lib/Support/FVInt.cpp @@ -0,0 +1,139 @@ +//===- FVInt.cpp - Four-valued integer --------------------------*- C++ -*-===// +// +// 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/Support/FVInt.h" +#include "llvm/ADT/StringExtras.h" + +#define DEBUG_TYPE "fvint" + +using namespace circt; + +std::optional FVInt::tryFromString(StringRef str, unsigned radix) { + assert(radix == 2 || radix == 8 || radix == 10 || radix == 16); + if (str.empty()) + return {}; + + // Overestimate the number of bits that will be needed to hold all digits. + unsigned radixLog2 = 0; + for (unsigned r = radix - 1; r > 0; r >>= 1) + ++radixLog2; + bool radixIsPow2 = (radix == (1U << radixLog2)); + + // Parse the string. + auto result = FVInt::getZero(str.size() * radixLog2); + while (!str.empty()) { + unsigned digit = llvm::toLower(str[0]); + str = str.drop_front(); + + // Handle X and Z digits. + if (digit == 'x' || digit == 'z') { + if (!radixIsPow2) + return {}; + result <<= radixLog2; + result.unknown.setLowBits(radixLog2); + if (digit == 'z') + result.value.setLowBits(radixLog2); + continue; + } + + // Determine the value of the current digit. + if (digit >= '0' && digit <= '9') + digit = digit - '0'; + else if (digit >= 'a' && digit <= 'z') + digit = digit - 'a' + 10; + else + return {}; + if (digit >= radix) + return {}; + + // Add the digit to the result. + if (radixIsPow2) { + result <<= radixLog2; + result.value |= digit; + } else { + result.value *= radix; + result.value += digit; + } + } + + return result; +} + +bool FVInt::tryToString(SmallVectorImpl &str, unsigned radix, + bool uppercase) const { + size_t strBaseLen = str.size(); + assert(radix == 2 || radix == 8 || radix == 10 || radix == 16); + + // Determine if the radix is a power of two. + unsigned radixLog2 = 0; + for (unsigned r = radix - 1; r > 0; r >>= 1) + ++radixLog2; + bool radixIsPow2 = (radix == (1U << radixLog2)); + unsigned radixMask = (1U << radixLog2) - 1; + + // If the number has no X or Z bits, take the easy route and print the `APInt` + // directly. + if (!hasUnknown()) { + value.toString(str, radix, /*Signed=*/false, /*formatAsCLiteral=*/false, + uppercase); + return true; + } + + // We can only print with non-power-of-two radices if there are no X or Z + // bits. So at this point we require radix be a power of two. + if (!radixIsPow2) + return false; + + // Otherwise chop off digits at the bottom and print them to the string. This + // prints the digits in reverse order, with the least significant digit as the + // first character. + APInt value = this->value; + APInt unknown = this->unknown; + + char chrA = uppercase ? 'A' : 'a'; + char chrX = uppercase ? 'X' : 'x'; + char chrZ = uppercase ? 'Z' : 'z'; + + while (!value.isZero() || !unknown.isZero()) { + unsigned digitValue = value.getRawData()[0] & radixMask; + unsigned digitUnknown = unknown.getRawData()[0] & radixMask; + value.lshrInPlace(radixLog2); + unknown.lshrInPlace(radixLog2); + + // Handle unknown bits. Since we only get to print a single X or Z character + // to the string, either all bits in the digit have to be X, or all have to + // be Z. But we cannot represent the case where X, Z and 0/1 bits are mixed. + if (digitUnknown != 0) { + if (digitUnknown != radixMask || + (digitValue != 0 && digitValue != radixMask)) { + str.resize(strBaseLen); + return false; + } + str.push_back(digitValue == 0 ? chrX : chrZ); + continue; + } + + // Handle known bits. + if (digitValue < 10) + str.push_back(digitValue + '0'); + else + str.push_back(digitValue - 10 + chrA); + } + + // Reverse the digits. + std::reverse(str.begin() + strBaseLen, str.end()); + return true; +} + +void FVInt::print(raw_ostream &os) const { + SmallString<32> buffer; + if (!tryToString(buffer)) + if (!tryToString(buffer, 16)) + tryToString(buffer, 2); + os << buffer; +} diff --git a/unittests/Support/CMakeLists.txt b/unittests/Support/CMakeLists.txt index 48431537442c..005e2389e396 100644 --- a/unittests/Support/CMakeLists.txt +++ b/unittests/Support/CMakeLists.txt @@ -1,4 +1,5 @@ add_circt_unittest(CIRCTSupportTests + FVIntTest.cpp JSONTest.cpp PrettyPrinterTest.cpp ) diff --git a/unittests/Support/FVIntTest.cpp b/unittests/Support/FVIntTest.cpp new file mode 100644 index 000000000000..c61311797418 --- /dev/null +++ b/unittests/Support/FVIntTest.cpp @@ -0,0 +1,134 @@ +//===- FVIntTest.cpp - Four-valued integer 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 +// +//===----------------------------------------------------------------------===// +// +// Unit tests for the `FVInt` class. +// +//===----------------------------------------------------------------------===// + +#include "circt/Support/FVInt.h" +#include "gtest/gtest.h" + +using namespace circt; + +namespace { + +TEST(FVIntTest, Resizing) { + ASSERT_EQ(FVInt::fromString("01", 2).zext(5), FVInt::fromString("00001", 2)); + ASSERT_EQ(FVInt::fromString("01", 2).sext(5), FVInt::fromString("00001", 2)); + ASSERT_EQ(FVInt::fromString("10", 2).zext(5), FVInt::fromString("00010", 2)); + ASSERT_EQ(FVInt::fromString("10", 2).sext(5), FVInt::fromString("11110", 2)); + ASSERT_EQ(FVInt::fromString("X1", 2).zext(5), FVInt::fromString("000X1", 2)); + ASSERT_EQ(FVInt::fromString("X1", 2).sext(5), FVInt::fromString("XXXX1", 2)); + ASSERT_EQ(FVInt::fromString("Z1", 2).zext(5), FVInt::fromString("000Z1", 2)); + ASSERT_EQ(FVInt::fromString("Z1", 2).sext(5), FVInt::fromString("ZZZZ1", 2)); +} + +TEST(FVIntTest, Basics) { + ASSERT_TRUE(FVInt::getZero(42).isZero()); + ASSERT_TRUE(FVInt::getAllOnes(42).isAllOnes()); + ASSERT_TRUE(FVInt::getAllX(42).isAllX()); + ASSERT_TRUE(FVInt::getAllZ(42).isAllZ()); + + ASSERT_FALSE(FVInt::getZero(42).hasUnknown()); + ASSERT_FALSE(FVInt::getAllOnes(42).hasUnknown()); + ASSERT_TRUE(FVInt::getAllX(42).hasUnknown()); + ASSERT_TRUE(FVInt::getAllZ(42).hasUnknown()); + + auto x = FVInt::fromString("01XZ", 2); + ASSERT_EQ(x.toAPInt(false), 0b0100); + ASSERT_EQ(x.toAPInt(true), 0b0111); + ASSERT_EQ(x.getBit(0), FVInt::Z); + ASSERT_EQ(x.getBit(1), FVInt::X); + ASSERT_EQ(x.getBit(2), FVInt::V1); + ASSERT_EQ(x.getBit(3), FVInt::V0); + ASSERT_EQ(FVInt::V1, 1); + ASSERT_EQ(FVInt::V0, 0); + + ASSERT_EQ(FVInt(32, 9001), FVInt(32, 9001)); + ASSERT_EQ(FVInt(32, 9001), 9001); + ASSERT_EQ(9001, FVInt(32, 9001)); + + ASSERT_NE(FVInt(32, 9001), FVInt(32, 1337)); + ASSERT_NE(FVInt(32, 9001), 1337); + ASSERT_NE(9001, FVInt(32, 1337)); +} + +TEST(FVIntTest, StringConversion) { + auto v = FVInt::fromString("ZX1001XZ", 2); + ASSERT_EQ(v.getZeroBits(), 0b00011000); + ASSERT_EQ(v.getOneBits(), 0b00100100); + ASSERT_EQ(v.getXBits(), 0b01000010); + ASSERT_EQ(v.getZBits(), 0b10000001); + + ASSERT_EQ(FVInt::getZero(0).toString(2), StringRef("0")); + ASSERT_EQ(FVInt::getZero(0).toString(8), StringRef("0")); + ASSERT_EQ(FVInt::getZero(0).toString(10), StringRef("0")); + ASSERT_EQ(FVInt::getZero(0).toString(16), StringRef("0")); + + // Parsing/printing without unknown values. + ASSERT_EQ(FVInt::fromString("10101100", 2).toString(2), + StringRef("10101100")); + ASSERT_EQ(FVInt::fromString("1234567", 8).toString(8), StringRef("1234567")); + ASSERT_EQ(FVInt::fromString("1234567890", 10).toString(10), + StringRef("1234567890")); + ASSERT_EQ(FVInt::fromString("1234567890ABCDEF", 16).toString(16), + "1234567890ABCDEF"); + ASSERT_EQ(FVInt::fromString("1234567890abcdef", 16).toString(16, false), + "1234567890abcdef"); + + // Parsing/printing with unknown values. + ASSERT_EQ(FVInt::fromString("10XZ1XZ0", 2).toString(2), + StringRef("10XZ1XZ0")); + ASSERT_EQ(FVInt::fromString("10xz1xz0", 2).toString(2, false), + StringRef("10xz1xz0")); + ASSERT_EQ(FVInt::fromString("1234XZ567", 8).toString(8), + StringRef("1234XZ567")); + ASSERT_EQ(FVInt::fromString("1234xz567", 8).toString(8, false), + StringRef("1234xz567")); + ASSERT_EQ(FVInt::fromString("12345XZ67890ABCDEF", 16).toString(16), + StringRef("12345XZ67890ABCDEF")); + ASSERT_EQ(FVInt::fromString("12345xz67890abcdef", 16).toString(16, false), + StringRef("12345xz67890abcdef")); +} + +TEST(FVIntTest, LogicOps) { + auto a = FVInt::fromString("01XZ01XZ01XZ01XZ", 2); + auto b = FVInt::fromString("00001111XXXXZZZZ", 2); + auto c = FVInt::fromString("01XZ", 2); + + ASSERT_EQ(~c, FVInt::fromString("10XX", 2)); + ASSERT_EQ(a & b, FVInt::fromString("000001XX0XXX0XXX", 2)); + ASSERT_EQ(a | b, FVInt::fromString("01XX1111X1XXX1XX", 2)); + ASSERT_EQ(a ^ b, FVInt::fromString("01XX10XXXXXXXXXX", 2)); +} + +TEST(FVIntTest, ArithmeticOps) { + auto a = FVInt::fromString("123").zext(32); + auto b = FVInt::fromString("234").zext(32); + auto c = FVInt::fromString("1XZ", 16).zext(32); + + ASSERT_EQ(-a, uint32_t(-123)); + ASSERT_TRUE((-c).isAllX()); + + ASSERT_EQ(a + 1, FVInt::fromString("124").zext(32)); + ASSERT_EQ(1 + b, FVInt::fromString("235").zext(32)); + ASSERT_EQ(a + b, FVInt::fromString("357").zext(32)); + ASSERT_TRUE((a + c).isAllX()); + + ASSERT_EQ(a - 1, FVInt::fromString("122").zext(32)); + ASSERT_EQ(234 - a, FVInt::fromString("111").zext(32)); + ASSERT_EQ(b - a, FVInt::fromString("111").zext(32)); + ASSERT_TRUE((a - c).isAllX()); + + ASSERT_EQ(a * 2, FVInt::fromString("246").zext(32)); + ASSERT_EQ(2 * b, FVInt::fromString("468").zext(32)); + ASSERT_EQ(a * b, FVInt::fromString("28782").zext(32)); + ASSERT_TRUE((a * c).isAllX()); +} + +} // namespace From bec0deab4bfa79785e3027bc95548435f58bd294 Mon Sep 17 00:00:00 2001 From: Will Dietz Date: Mon, 5 Aug 2024 12:21:19 -0500 Subject: [PATCH 012/119] [Python][OM] Handle BoolAttr's before IntegerAttr's. (#7438) BoolAttr's are IntegerAttr's, check them first. IntegerAttr's that happen to have the characteristics of BoolAttr will accordingly become Python boolean values. Unclear where these come from but we do lower booleans to MLIR bool constants so make sure to handle that. Add test for object model IR with bool constants. --- .../Bindings/Python/dialects/om.py | 12 +++++++++++- lib/Bindings/Python/OMModule.cpp | 19 ++++++++++--------- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/integration_test/Bindings/Python/dialects/om.py b/integration_test/Bindings/Python/dialects/om.py index fd6dcf00306e..e7afceb1ddc3 100644 --- a/integration_test/Bindings/Python/dialects/om.py +++ b/integration_test/Bindings/Python/dialects/om.py @@ -68,6 +68,11 @@ %map = om.map_create %entry1, %entry2: !om.string, !om.integer om.class.field @map_create, %map : !om.map + + %true = om.constant true + om.class.field @true, %true : i1 + %false = om.constant false + om.class.field @false, %false : i1 } om.class @Child(%0: !om.integer) { @@ -157,7 +162,7 @@ # CHECK: 14 print(obj.child.foo) -# CHECK: loc("-":60:7) +# CHECK: loc("-":65:7) print(obj.child.get_field_loc("foo")) # CHECK: ('Root', 'x') print(obj.reference) @@ -224,6 +229,11 @@ # CHECK-NEXT: Y 15 print(k, v) +# CHECK: True +print(obj.true) +# CHECK: False +print(obj.false) + obj = evaluator.instantiate("Client") object_dict: Dict[om.Object, str] = {} for field_name, data in obj: diff --git a/lib/Bindings/Python/OMModule.cpp b/lib/Bindings/Python/OMModule.cpp index 2d42186d366b..16fa03873ea1 100644 --- a/lib/Bindings/Python/OMModule.cpp +++ b/lib/Bindings/Python/OMModule.cpp @@ -366,15 +366,6 @@ Map::dunderGetItem(std::variant key) { // Convert a generic MLIR Attribute to a PythonValue. This is basically a C++ // fast path of the parts of attribute_to_var that we use in the OM dialect. static PythonPrimitive omPrimitiveToPythonValue(MlirAttribute attr) { - if (mlirAttributeIsAInteger(attr)) { - MlirType type = mlirAttributeGetType(attr); - if (mlirTypeIsAIndex(type) || mlirIntegerTypeIsSignless(type)) - return py::int_(mlirIntegerAttrGetValueInt(attr)); - if (mlirIntegerTypeIsSigned(type)) - return py::int_(mlirIntegerAttrGetValueSInt(attr)); - return py::int_(mlirIntegerAttrGetValueUInt(attr)); - } - if (omAttrIsAIntegerAttr(attr)) { auto strRef = omIntegerAttrToString(attr); return py::int_(py::str(strRef.data, strRef.length)); @@ -389,10 +380,20 @@ static PythonPrimitive omPrimitiveToPythonValue(MlirAttribute attr) { return py::str(strRef.data, strRef.length); } + // BoolAttr's are IntegerAttr's, check this first. if (mlirAttributeIsABool(attr)) { return py::bool_(mlirBoolAttrGetValue(attr)); } + if (mlirAttributeIsAInteger(attr)) { + MlirType type = mlirAttributeGetType(attr); + if (mlirTypeIsAIndex(type) || mlirIntegerTypeIsSignless(type)) + return py::int_(mlirIntegerAttrGetValueInt(attr)); + if (mlirIntegerTypeIsSigned(type)) + return py::int_(mlirIntegerAttrGetValueSInt(attr)); + return py::int_(mlirIntegerAttrGetValueUInt(attr)); + } + if (omAttrIsAReferenceAttr(attr)) { auto innerRef = omReferenceAttrGetInnerRef(attr); auto moduleStrRef = From 5cc69bd6b1b439ef95ad9516ed57f34e5ca7b900 Mon Sep 17 00:00:00 2001 From: Fabian Schuiki Date: Mon, 5 Aug 2024 17:10:19 -0700 Subject: [PATCH 013/119] [ImportVerilog] Switch from SCF to CF dialect for control flow (#7432) Instead of using the SCF dialect and its `scf.if` and `scf.while` operations for control flow, switch over to the CF dialect and its `cf.br` and `cf.cond_br`. This allows us to support SystemVerilog's `continue`, `break`, and `return` statements which don't map well to the Structured Control Flow dialect. --- include/circt/Dialect/Moore/MooreOps.td | 30 +- .../ImportVerilog/ImportVerilog.cpp | 4 +- .../ImportVerilog/ImportVerilogInternals.h | 18 +- lib/Conversion/ImportVerilog/Statements.cpp | 389 ++++++++----- lib/Conversion/ImportVerilog/Structure.cpp | 17 +- .../Moore/Transforms/SimplifyProcedures.cpp | 9 +- test/Conversion/ImportVerilog/basic.sv | 512 +++++++++++------- test/Dialect/Moore/basic.mlir | 13 +- test/Dialect/Moore/lower-concatref.mlir | 2 + test/Dialect/Moore/mem2reg.mlir | 1 + test/Dialect/Moore/simplify-procedures.mlir | 4 + 11 files changed, 643 insertions(+), 356 deletions(-) diff --git a/include/circt/Dialect/Moore/MooreOps.td b/include/circt/Dialect/Moore/MooreOps.td index 436c64176688..a0e3018fb1b5 100644 --- a/include/circt/Dialect/Moore/MooreOps.td +++ b/include/circt/Dialect/Moore/MooreOps.td @@ -131,8 +131,6 @@ def ProcedureKindAttr: I32EnumAttr<"ProcedureKind", "Procedure kind", } def ProcedureOp : MooreOp<"procedure", [ - SingleBlock, - NoTerminator, NoRegionArguments, RecursiveMemoryEffects, RecursivelySpeculatable @@ -172,15 +170,22 @@ def ProcedureOp : MooreOp<"procedure", [ See IEEE 1800-2017 ยง 9.2 "Structured procedures". }]; - let regions = (region SizedRegion<1>:$bodyRegion); let arguments = (ins ProcedureKindAttr:$kind); let results = (outs); + let regions = (region AnyRegion:$body); let assemblyFormat = [{ - $kind attr-dict-with-keyword $bodyRegion + $kind attr-dict-with-keyword $body }]; } +def ReturnOp : MooreOp<"return", [ + Pure, Terminator, HasParent<"ProcedureOp"> +]> { + let summary = "Return from a procedure"; + let assemblyFormat = [{ attr-dict }]; +} + //===----------------------------------------------------------------------===// // Declarations //===----------------------------------------------------------------------===// @@ -812,6 +817,16 @@ class CaseEqOpBase : MooreOp : MooreOp { let summary = "Case equality"; } def CaseNeOp : CaseEqOpBase<"case_ne"> { let summary = "Case inequality"; } +def CaseZEqOp : CaseEqOpBase<"casez_eq"> { + let summary = "Case equality with Z as wildcard"; +} +def CaseXZEqOp : CaseEqOpBase<"casexz_eq"> { + let summary = "Case equality with X and Z as wildcard"; +} class WildcardEqOpBase : MooreOploadDialect(); + mlirContext->loadDialect(); auto conversionTimer = ts.nest("Verilog to dialect mapping"); Context context(*compilation, module, driver.sourceManager, bufferFilePaths); if (failed(context.convertCompilation())) diff --git a/lib/Conversion/ImportVerilog/ImportVerilogInternals.h b/lib/Conversion/ImportVerilog/ImportVerilogInternals.h index 96534a03147a..e490b48b03c0 100644 --- a/lib/Conversion/ImportVerilog/ImportVerilogInternals.h +++ b/lib/Conversion/ImportVerilog/ImportVerilogInternals.h @@ -13,8 +13,8 @@ #include "circt/Conversion/ImportVerilog.h" #include "circt/Dialect/HW/HWOps.h" #include "circt/Dialect/Moore/MooreOps.h" +#include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h" #include "mlir/Dialect/Func/IR/FuncOps.h" -#include "mlir/Dialect/SCF/IR/SCF.h" #include "slang/ast/ASTVisitor.h" #include "llvm/ADT/ScopedHashTable.h" #include "llvm/Support/Debug.h" @@ -46,6 +46,15 @@ struct FunctionLowering { mlir::func::FuncOp op; }; +/// Information about a loops continuation and exit blocks relevant while +/// lowering the loop's body statements. +struct LoopFrame { + /// The block to jump to from a `continue` statement. + Block *continueBlock; + /// The block to jump to from a `break` statement. + Block *breakBlock; +}; + /// A helper class to facilitate the conversion from a Slang AST to MLIR /// operations. Keeps track of the destination MLIR module, builders, and /// various worklists and utilities needed for conversion. @@ -136,6 +145,13 @@ struct Context { /// side. This allows expressions to resolve the opaque /// `LValueReferenceExpression`s in the AST. SmallVector lvalueStack; + + /// A stack of loop continuation and exit blocks. Each loop will push the + /// relevant info onto this stack, lower its loop body statements, and pop the + /// info off the stack again. Continue and break statements encountered as + /// part of the loop body statements will use this information to branch to + /// the correct block. + SmallVector loopStack; }; } // namespace ImportVerilog diff --git a/lib/Conversion/ImportVerilog/Statements.cpp b/lib/Conversion/ImportVerilog/Statements.cpp index d5a6ed443845..f3fa376012cf 100644 --- a/lib/Conversion/ImportVerilog/Statements.cpp +++ b/lib/Conversion/ImportVerilog/Statements.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include "ImportVerilogInternals.h" +#include "llvm/ADT/ScopeExit.h" using namespace mlir; using namespace circt; @@ -22,6 +23,16 @@ struct StmtVisitor { StmtVisitor(Context &context, Location loc) : context(context), loc(loc), builder(context.builder) {} + bool isTerminated() const { return !builder.getInsertionBlock(); } + void setTerminated() { builder.clearInsertionPoint(); } + + Block &createBlock() { + assert(builder.getInsertionBlock()); + auto block = std::make_unique(); + block->insertAfter(builder.getInsertionBlock()); + return *block.release(); + } + // Skip empty statements (stray semicolons). LogicalResult visit(const slang::ast::EmptyStatement &) { return success(); } @@ -33,9 +44,15 @@ struct StmtVisitor { // which in turn has a single body statement, which then commonly is a list of // statements. LogicalResult visit(const slang::ast::StatementList &stmts) { - for (auto *stmt : stmts.list) + for (auto *stmt : stmts.list) { + if (isTerminated()) { + auto loc = context.convertLocation(stmt->sourceRange); + mlir::emitWarning(loc, "unreachable code"); + break; + } if (failed(context.convertStatement(*stmt))) return failure(); + } return success(); } @@ -104,81 +121,123 @@ struct StmtVisitor { allConds = builder.create(loc, builder.getI1Type(), allConds); - // Generate the if operation. - auto ifOp = - builder.create(loc, allConds, stmt.ifFalse != nullptr); - OpBuilder::InsertionGuard guard(builder); + // Create the blocks for the true and false branches, and the exit block. + Block &exitBlock = createBlock(); + Block *falseBlock = stmt.ifFalse ? &createBlock() : nullptr; + Block &trueBlock = createBlock(); + builder.create(loc, allConds, &trueBlock, + falseBlock ? falseBlock : &exitBlock); - // Generate the "then" body. - builder.setInsertionPoint(ifOp.thenYield()); + // Generate the true branch. + builder.setInsertionPointToEnd(&trueBlock); if (failed(context.convertStatement(stmt.ifTrue))) return failure(); + if (!isTerminated()) + builder.create(loc, &exitBlock); - // Generate the "else" body if present. + // Generate the false branch if present. if (stmt.ifFalse) { - builder.setInsertionPoint(ifOp.elseYield()); + builder.setInsertionPointToEnd(falseBlock); if (failed(context.convertStatement(*stmt.ifFalse))) return failure(); + if (!isTerminated()) + builder.create(loc, &exitBlock); } + // If control never reaches the exit block, remove it and mark control flow + // as terminated. Otherwise we continue inserting ops in the exit block. + if (exitBlock.hasNoPredecessors()) { + exitBlock.erase(); + setTerminated(); + } else { + builder.setInsertionPointToEnd(&exitBlock); + } return success(); } // Handle case statements. LogicalResult visit(const slang::ast::CaseStatement &caseStmt) { + using slang::ast::CaseStatementCondition; auto caseExpr = context.convertRvalueExpression(caseStmt.expr); if (!caseExpr) return failure(); - auto items = caseStmt.items; - // Used to generate the condition of the default case statement. - SmallVector defaultConds; - // Traverse the case items. - for (auto item : items) { - // One statement will be matched with multi-conditions. - // Like case(cond) 0, 1 : y = x; endcase. - SmallVector allConds; + // Check each case individually. This currently ignores the `unique`, + // `unique0`, and `priority` modifiers which would allow for additional + // optimizations. + auto &exitBlock = createBlock(); + + for (const auto &item : caseStmt.items) { + // Create the block that will contain the main body of the expression. + // This is where any of the comparisons will branch to if they match. + auto &matchBlock = createBlock(); + + // The SV standard requires expressions to be checked in the order + // specified by the user, and for the evaluation to stop as soon as the + // first matching expression is encountered. for (const auto *expr : item.expressions) { - auto itemExpr = context.convertRvalueExpression(*expr); - if (!itemExpr) + auto value = context.convertRvalueExpression(*expr); + if (!value) return failure(); - - auto newEqOp = builder.create(loc, caseExpr, itemExpr); - allConds.push_back(newEqOp); - } - // Bound all conditions of an item into one. - auto cond = allConds.back(); - allConds.pop_back(); - while (!allConds.empty()) { - cond = builder.create(loc, allConds.back(), cond); - allConds.pop_back(); + auto itemLoc = value.getLoc(); + + // Generate the appropriate equality operator. + Value cond; + switch (caseStmt.condition) { + case CaseStatementCondition::Normal: + cond = builder.create(itemLoc, caseExpr, value); + break; + case CaseStatementCondition::WildcardXOrZ: + cond = builder.create(itemLoc, caseExpr, value); + break; + case CaseStatementCondition::WildcardJustZ: + cond = builder.create(itemLoc, caseExpr, value); + break; + case CaseStatementCondition::Inside: + mlir::emitError(loc, "unsupported set membership case statement"); + return failure(); + } + cond = builder.create(itemLoc, builder.getI1Type(), + cond); + + // If the condition matches, branch to the match block. Otherwise + // continue checking the next expression in a new block. + auto &nextBlock = createBlock(); + builder.create(itemLoc, cond, &matchBlock, + &nextBlock); + builder.setInsertionPointToEnd(&nextBlock); } - // Gather all items' conditions. - defaultConds.push_back(cond); - cond = - builder.create(loc, builder.getI1Type(), cond); - auto ifOp = builder.create(loc, cond); + + // The current block is the fall-through after all conditions have been + // checked and nothing matched. Move the match block up before this point + // to make the IR easier to read. + matchBlock.moveBefore(builder.getInsertionBlock()); + + // Generate the code for this item's statement in the match block. OpBuilder::InsertionGuard guard(builder); - builder.setInsertionPoint(ifOp.thenYield()); + builder.setInsertionPointToEnd(&matchBlock); if (failed(context.convertStatement(*item.stmt))) return failure(); - } - // Handle the 'default case' statement if it exists. - if (caseStmt.defaultCase) { - auto cond = defaultConds.back(); - defaultConds.pop_back(); - while (!defaultConds.empty()) { - cond = builder.create(loc, defaultConds.back(), cond); - defaultConds.pop_back(); + if (!isTerminated()) { + auto loc = context.convertLocation(item.stmt->sourceRange); + builder.create(loc, &exitBlock); } - cond = builder.create(loc, cond); - cond = - builder.create(loc, builder.getI1Type(), cond); - auto ifOp = builder.create(loc, cond); - OpBuilder::InsertionGuard guard(builder); - builder.setInsertionPoint(ifOp.thenYield()); + } + + // Generate the default case if present. + if (caseStmt.defaultCase) if (failed(context.convertStatement(*caseStmt.defaultCase))) return failure(); + if (!isTerminated()) + builder.create(loc, &exitBlock); + + // If control never reaches the exit block, remove it and mark control flow + // as terminated. Otherwise we continue inserting ops in the exit block. + if (exitBlock.hasNoPredecessors()) { + exitBlock.erase(); + setTerminated(); + } else { + builder.setInsertionPointToEnd(&exitBlock); } return success(); } @@ -190,127 +249,179 @@ struct StmtVisitor { if (!context.convertRvalueExpression(*initExpr)) return failure(); - // Create the while op. - auto whileOp = builder.create(loc, TypeRange{}, ValueRange{}); - OpBuilder::InsertionGuard guard(builder); + // Create the blocks for the loop condition, body, step, and exit. + auto &exitBlock = createBlock(); + auto &stepBlock = createBlock(); + auto &bodyBlock = createBlock(); + auto &checkBlock = createBlock(); + builder.create(loc, &checkBlock); + + // Push the blocks onto the loop stack such that we can continue and break. + context.loopStack.push_back({&stepBlock, &exitBlock}); + auto done = llvm::make_scope_exit([&] { context.loopStack.pop_back(); }); - // In the "before" region, check that the condition holds. - builder.createBlock(&whileOp.getBefore()); + // Generate the loop condition check. + builder.setInsertionPointToEnd(&checkBlock); auto cond = context.convertRvalueExpression(*stmt.stopExpr); if (!cond) return failure(); cond = builder.createOrFold(loc, cond); cond = builder.create(loc, builder.getI1Type(), cond); - builder.create(loc, cond, ValueRange{}); + builder.create(loc, cond, &bodyBlock, &exitBlock); - // In the "after" region, generate the loop body and step expressions. - builder.createBlock(&whileOp.getAfter()); + // Generate the loop body. + builder.setInsertionPointToEnd(&bodyBlock); if (failed(context.convertStatement(stmt.body))) return failure(); + if (!isTerminated()) + builder.create(loc, &stepBlock); + + // Generate the step expressions. + builder.setInsertionPointToEnd(&stepBlock); for (auto *stepExpr : stmt.steps) if (!context.convertRvalueExpression(*stepExpr)) return failure(); - builder.create(loc); - + if (!isTerminated()) + builder.create(loc, &checkBlock); + + // If control never reaches the exit block, remove it and mark control flow + // as terminated. Otherwise we continue inserting ops in the exit block. + if (exitBlock.hasNoPredecessors()) { + exitBlock.erase(); + setTerminated(); + } else { + builder.setInsertionPointToEnd(&exitBlock); + } return success(); } // Handle `repeat` loops. LogicalResult visit(const slang::ast::RepeatLoopStatement &stmt) { - // Create the while op and feed in the repeat count as the initial counter - // value. auto count = context.convertRvalueExpression(stmt.count); if (!count) return failure(); - auto type = cast(count.getType()); - auto whileOp = builder.create(loc, type, count); - OpBuilder::InsertionGuard guard(builder); - - // In the "before" region, check that the counter is non-zero. - auto *block = builder.createBlock(&whileOp.getBefore(), {}, type, loc); - auto counterArg = block->getArgument(0); - auto cond = builder.createOrFold(loc, counterArg); + + // Create the blocks for the loop condition, body, step, and exit. + auto &exitBlock = createBlock(); + auto &stepBlock = createBlock(); + auto &bodyBlock = createBlock(); + auto &checkBlock = createBlock(); + auto currentCount = checkBlock.addArgument(count.getType(), count.getLoc()); + builder.create(loc, &checkBlock, count); + + // Push the blocks onto the loop stack such that we can continue and break. + context.loopStack.push_back({&stepBlock, &exitBlock}); + auto done = llvm::make_scope_exit([&] { context.loopStack.pop_back(); }); + + // Generate the loop condition check. + builder.setInsertionPointToEnd(&checkBlock); + auto cond = builder.createOrFold(loc, currentCount); cond = builder.create(loc, builder.getI1Type(), cond); - builder.create(loc, cond, counterArg); + builder.create(loc, cond, &bodyBlock, &exitBlock); - // In the "after" region, generate the loop body and decrement the counter. - block = builder.createBlock(&whileOp.getAfter(), {}, type, loc); + // Generate the loop body. + builder.setInsertionPointToEnd(&bodyBlock); if (failed(context.convertStatement(stmt.body))) return failure(); - counterArg = block->getArgument(0); - auto constOne = builder.create(loc, type, 1); - auto subOp = builder.create(loc, counterArg, constOne); - builder.create(loc, ValueRange{subOp}); - + if (!isTerminated()) + builder.create(loc, &stepBlock); + + // Decrement the current count and branch back to the check block. + builder.setInsertionPointToEnd(&stepBlock); + auto one = builder.create( + count.getLoc(), cast(count.getType()), 1); + Value nextCount = + builder.create(count.getLoc(), currentCount, one); + builder.create(loc, &checkBlock, nextCount); + + // If control never reaches the exit block, remove it and mark control flow + // as terminated. Otherwise we continue inserting ops in the exit block. + if (exitBlock.hasNoPredecessors()) { + exitBlock.erase(); + setTerminated(); + } else { + builder.setInsertionPointToEnd(&exitBlock); + } return success(); } - // Handle `while` loops. - LogicalResult visit(const slang::ast::WhileLoopStatement &stmt) { - // Create the while op. - auto whileOp = builder.create(loc, TypeRange{}, ValueRange{}); - OpBuilder::InsertionGuard guard(builder); - - // In the "before" region, check that the condition holds. - builder.createBlock(&whileOp.getBefore()); - auto cond = context.convertRvalueExpression(stmt.cond); + // Handle `while` and `do-while` loops. + LogicalResult createWhileLoop(const slang::ast::Expression &condExpr, + const slang::ast::Statement &bodyStmt, + bool atLeastOnce) { + // Create the blocks for the loop condition, body, and exit. + auto &exitBlock = createBlock(); + auto &bodyBlock = createBlock(); + auto &checkBlock = createBlock(); + builder.create(loc, atLeastOnce ? &bodyBlock : &checkBlock); + if (atLeastOnce) + bodyBlock.moveBefore(&checkBlock); + + // Push the blocks onto the loop stack such that we can continue and break. + context.loopStack.push_back({&checkBlock, &exitBlock}); + auto done = llvm::make_scope_exit([&] { context.loopStack.pop_back(); }); + + // Generate the loop condition check. + builder.setInsertionPointToEnd(&checkBlock); + auto cond = context.convertRvalueExpression(condExpr); if (!cond) return failure(); cond = builder.createOrFold(loc, cond); cond = builder.create(loc, builder.getI1Type(), cond); - builder.create(loc, cond, ValueRange{}); + builder.create(loc, cond, &bodyBlock, &exitBlock); - // In the "after" region, generate the loop body. - builder.createBlock(&whileOp.getAfter()); - if (failed(context.convertStatement(stmt.body))) + // Generate the loop body. + builder.setInsertionPointToEnd(&bodyBlock); + if (failed(context.convertStatement(bodyStmt))) return failure(); - builder.create(loc); - + if (!isTerminated()) + builder.create(loc, &checkBlock); + + // If control never reaches the exit block, remove it and mark control flow + // as terminated. Otherwise we continue inserting ops in the exit block. + if (exitBlock.hasNoPredecessors()) { + exitBlock.erase(); + setTerminated(); + } else { + builder.setInsertionPointToEnd(&exitBlock); + } return success(); } - // Handle `do ... while` loops. - LogicalResult visit(const slang::ast::DoWhileLoopStatement &stmt) { - // Create the while op. - auto whileOp = builder.create(loc, TypeRange{}, ValueRange{}); - OpBuilder::InsertionGuard guard(builder); - - // In the "before" region, generate the loop body and check that the - // condition holds. - builder.createBlock(&whileOp.getBefore()); - if (failed(context.convertStatement(stmt.body))) - return failure(); - auto cond = context.convertRvalueExpression(stmt.cond); - if (!cond) - return failure(); - cond = builder.createOrFold(loc, cond); - cond = builder.create(loc, builder.getI1Type(), cond); - builder.create(loc, cond, ValueRange{}); - - // Generate an empty "after" region. - builder.createBlock(&whileOp.getAfter()); - builder.create(loc); + LogicalResult visit(const slang::ast::WhileLoopStatement &stmt) { + return createWhileLoop(stmt.cond, stmt.body, false); + } - return success(); + LogicalResult visit(const slang::ast::DoWhileLoopStatement &stmt) { + return createWhileLoop(stmt.cond, stmt.body, true); } // Handle `forever` loops. LogicalResult visit(const slang::ast::ForeverLoopStatement &stmt) { - // Create the while op. - auto whileOp = builder.create(loc, TypeRange{}, ValueRange{}); - OpBuilder::InsertionGuard guard(builder); + // Create the blocks for the loop body and exit. + auto &exitBlock = createBlock(); + auto &bodyBlock = createBlock(); + builder.create(loc, &bodyBlock); - // In the "before" region, return true for the condition. - builder.createBlock(&whileOp.getBefore()); - auto cond = builder.create(loc, builder.getI1Type(), 1); - builder.create(loc, cond, ValueRange{}); + // Push the blocks onto the loop stack such that we can continue and break. + context.loopStack.push_back({&bodyBlock, &exitBlock}); + auto done = llvm::make_scope_exit([&] { context.loopStack.pop_back(); }); - // In the "after" region, generate the loop body. - builder.createBlock(&whileOp.getAfter()); + // Generate the loop body. + builder.setInsertionPointToEnd(&bodyBlock); if (failed(context.convertStatement(stmt.body))) return failure(); - builder.create(loc); - + if (!isTerminated()) + builder.create(loc, &bodyBlock); + + // If control never reaches the exit block, remove it and mark control flow + // as terminated. Otherwise we continue inserting ops in the exit block. + if (exitBlock.hasNoPredecessors()) { + exitBlock.erase(); + setTerminated(); + } else { + builder.setInsertionPointToEnd(&exitBlock); + } return success(); } @@ -325,13 +436,34 @@ struct StmtVisitor { // Handle return statements. LogicalResult visit(const slang::ast::ReturnStatement &stmt) { - Value expr; if (stmt.expr) { - expr = context.convertRvalueExpression(*stmt.expr); + auto expr = context.convertRvalueExpression(*stmt.expr); if (!expr) return failure(); + builder.create(loc, expr); + } else { + builder.create(loc); } - builder.create(loc, expr); + setTerminated(); + return success(); + } + + // Handle continue statements. + LogicalResult visit(const slang::ast::ContinueStatement &stmt) { + if (context.loopStack.empty()) + return mlir::emitError(loc, + "cannot `continue` without a surrounding loop"); + builder.create(loc, context.loopStack.back().continueBlock); + setTerminated(); + return success(); + } + + // Handle break statements. + LogicalResult visit(const slang::ast::BreakStatement &stmt) { + if (context.loopStack.empty()) + return mlir::emitError(loc, "cannot `break` without a surrounding loop"); + builder.create(loc, context.loopStack.back().breakBlock); + setTerminated(); return success(); } @@ -352,6 +484,7 @@ struct StmtVisitor { } // namespace LogicalResult Context::convertStatement(const slang::ast::Statement &stmt) { + assert(builder.getInsertionBlock()); auto loc = convertLocation(stmt.sourceRange); return stmt.visit(StmtVisitor(*this, loc)); } diff --git a/lib/Conversion/ImportVerilog/Structure.cpp b/lib/Conversion/ImportVerilog/Structure.cpp index a72c381ec1d7..e1061e96571d 100644 --- a/lib/Conversion/ImportVerilog/Structure.cpp +++ b/lib/Conversion/ImportVerilog/Structure.cpp @@ -440,11 +440,14 @@ struct ModuleVisitor : public BaseVisitor { LogicalResult visit(const slang::ast::ProceduralBlockSymbol &procNode) { auto procOp = builder.create( loc, convertProcedureKind(procNode.procedureKind)); - procOp.getBodyRegion().emplaceBlock(); OpBuilder::InsertionGuard guard(builder); - builder.setInsertionPointToEnd(procOp.getBody()); + builder.setInsertionPointToEnd(&procOp.getBody().emplaceBlock()); Context::ValueSymbolScope scope(context.valueSymbols); - return context.convertStatement(procNode.getBody()); + if (failed(context.convertStatement(procNode.getBody()))) + return failure(); + if (builder.getBlock()) + builder.create(loc); + return success(); } // Handle parameters. @@ -913,15 +916,15 @@ Context::convertFunction(const slang::ast::SubroutineSymbol &subroutine) { // If there was no explicit return statement provided by the user, insert a // default one. - if (block.empty() || !block.back().hasTrait()) { + if (builder.getBlock()) { if (returnVar && !subroutine.getReturnType().isVoid()) { - returnVar = builder.create(returnVar.getLoc(), returnVar); - builder.create(lowering->op.getLoc(), returnVar); + Value read = builder.create(returnVar.getLoc(), returnVar); + builder.create(lowering->op.getLoc(), read); } else { builder.create(lowering->op.getLoc(), ValueRange{}); } } - if (returnVar.use_empty()) + if (returnVar && returnVar.use_empty()) returnVar.getDefiningOp()->erase(); return success(); } diff --git a/lib/Dialect/Moore/Transforms/SimplifyProcedures.cpp b/lib/Dialect/Moore/Transforms/SimplifyProcedures.cpp index 1054a3f8e899..c4ee317eab38 100644 --- a/lib/Dialect/Moore/Transforms/SimplifyProcedures.cpp +++ b/lib/Dialect/Moore/Transforms/SimplifyProcedures.cpp @@ -44,7 +44,8 @@ void SimplifyProceduresPass::runOnOperation() { // Use to collect blocking assignments that have been replaced by a "shadow" // variable. DenseSet assignOps; - for (auto &nestedOp : procedureOp) { + procedureOp.walk([&](Operation *op) { + auto &nestedOp = *op; // Only create a "shadow" varaible for the global variable used by other // operations in the procedure body. if (isa(nestedOp) && @@ -54,14 +55,14 @@ void SimplifyProceduresPass::runOnOperation() { DenseSet users; for (auto *user : nestedOp.getOperand(0).getUsers()) // Ensuring don't handle the users existing in another procedure body. - if (user->getBlock() == procedureOp.getBody()) + if (procedureOp->isAncestor(user)) users.insert(user); // Because the operand of moore.event_wait is net. if (auto varOp = llvm::dyn_cast_or_null( nestedOp.getOperand(0).getDefiningOp())) { auto resultType = varOp.getResult().getType(); - builder.setInsertionPointToStart(procedureOp.getBody()); + builder.setInsertionPointToStart(&procedureOp.getBody().front()); auto readOp = builder.create( nestedOp.getLoc(), cast(resultType).getNestedType(), varOp.getResult()); @@ -96,6 +97,6 @@ void SimplifyProceduresPass::runOnOperation() { builder.clearInsertionPoint(); assignOps.erase(assignOp); } - } + }); }); } diff --git a/test/Conversion/ImportVerilog/basic.sv b/test/Conversion/ImportVerilog/basic.sv index 66de36168ea8..34ba203ebbfd 100644 --- a/test/Conversion/ImportVerilog/basic.sv +++ b/test/Conversion/ImportVerilog/basic.sv @@ -231,219 +231,323 @@ module Basic; MyStruct s3 = ~s2; endmodule -// CHECK-LABEL: moore.module @Statements -module Statements; - bit x, y, z; - int i; - initial begin - // CHECK: %a = moore.variable : - automatic int a; - // CHECK: [[TMP1:%.+]] = moore.read %a - // CHECK moore.blocking_assign %i, [[TMP1]] : i32 - i = a; +// CHECK-LABEL: func.func private @dummyA( +// CHECK-LABEL: func.func private @dummyB( +// CHECK-LABEL: func.func private @dummyC( +function void dummyA(); endfunction +function void dummyB(); endfunction +function void dummyC(); endfunction + +// CHECK-LABEL: func.func private @ConditionalStatements( +// CHECK-SAME: %arg0: !moore.i1 +// CHECK-SAME: %arg1: !moore.i1 +function void ConditionalStatements(bit x, bit y); + // CHECK: [[COND:%.+]] = moore.conversion %arg0 : !moore.i1 -> i1 + // CHECK: cf.cond_br [[COND]], ^[[BB1:.+]], ^[[BB2:.+]] + // CHECK: ^[[BB1]]: + // CHECK: call @dummyA() + // CHECK: cf.br ^[[BB2]] + // CHECK: ^[[BB2]]: + if (x) dummyA(); + + // CHECK: [[COND1:%.+]] = moore.and %arg0, %arg1 + // CHECK: [[COND2:%.+]] = moore.conversion [[COND1]] : !moore.i1 -> i1 + // CHECK: cf.cond_br [[COND2]], ^[[BB1:.+]], ^[[BB2:.+]] + // CHECK: ^[[BB1]]: + // CHECK: call @dummyA() + // CHECK: cf.br ^[[BB2]] + // CHECK: ^[[BB2]]: + if (x &&& y) dummyA(); + + // CHECK: [[COND:%.+]] = moore.conversion %arg0 : !moore.i1 -> i1 + // CHECK: cf.cond_br [[COND]], ^[[BB1:.+]], ^[[BB2:.+]] + // CHECK: ^[[BB1]]: + // CHECK: call @dummyA() + // CHECK: cf.br ^[[BB3:.+]] + // CHECK: ^[[BB2]]: + // CHECK: call @dummyB() + // CHECK: cf.br ^[[BB3]] + // CHECK: ^[[BB3]]: + if (x) + dummyA(); + else + dummyB(); + + // CHECK: [[COND:%.+]] = moore.conversion %arg0 : !moore.i1 -> i1 + // CHECK: cf.cond_br [[COND]], ^[[BB1:.+]], ^[[BB2:.+]] + // CHECK: ^[[BB1]]: + // CHECK: call @dummyA() + // CHECK: cf.br ^[[BB6:.+]] + // CHECK: ^[[BB2]]: + // CHECK: [[COND:%.+]] = moore.conversion %arg1 : !moore.i1 -> i1 + // CHECK: cf.cond_br [[COND]], ^[[BB3:.+]], ^[[BB4:.+]] + // CHECK: ^[[BB3]]: + // CHECK: call @dummyB() + // CHECK: cf.br ^[[BB5:.+]] + // CHECK: ^[[BB4]]: + // CHECK: call @dummyC() + // CHECK: cf.br ^[[BB5]] + // CHECK: ^[[BB5]]: + // CHECK: cf.br ^[[BB6]] + // CHECK: ^[[BB6]]: + if (x) + dummyA(); + else if (y) + dummyB(); + else + dummyC(); + + // CHECK: [[COND:%.+]] = moore.conversion %arg0 : !moore.i1 -> i1 + // CHECK: cf.cond_br [[COND]], ^[[BB1:.+]], ^[[BB2:.+]] + // CHECK: ^[[BB1]]: + // CHECK: return + // CHECK: ^[[BB2]]: + if (x) return; +endfunction - //===------------------------------------------------------------------===// - // Conditional statements +// CHECK-LABEL: func.func private @CaseStatements( +// CHECK-SAME: %arg0: !moore.i32 +// CHECK-SAME: %arg1: !moore.i32 +// CHECK-SAME: %arg2: !moore.i32 +// CHECK-SAME: %arg3: !moore.i32 +function void CaseStatements(int x, int a, int b, int c); + // CHECK: [[FLAG:%.+]] = moore.add %arg0, %arg0 + case (x + x) + // CHECK: [[COND1:%.+]] = moore.case_eq [[FLAG]], %arg1 + // CHECK: [[COND2:%.+]] = moore.conversion [[COND1]] : !moore.i1 -> i1 + // CHECK: cf.cond_br [[COND2]], ^[[BB1:.+]], ^[[BB2:.+]] + // CHECK: ^[[BB1]]: + // CHECK: call @dummyA() + // CHECK: cf.br ^[[BB3:.+]] + a: dummyA(); + // CHECK: ^[[BB2]]: + // CHECK: call @dummyB() + // CHECK: cf.br ^[[BB3]] + default: dummyB(); + // CHECK: ^[[BB3]]: + endcase + + // CHECK: [[COND1:%.+]] = moore.case_eq %arg0, %arg1 + // CHECK: [[COND2:%.+]] = moore.conversion [[COND1]] : !moore.i1 -> i1 + // CHECK: cf.cond_br [[COND2]], ^[[BB_MATCH:.+]], ^[[BB1:.+]] + // CHECK: ^[[BB1]]: + // CHECK: [[TMP:%.+]] = moore.add %arg2, %arg3 + // CHECK: [[COND1:%.+]] = moore.case_eq %arg0, [[TMP]] + // CHECK: [[COND2:%.+]] = moore.conversion [[COND1]] : !moore.i1 -> i1 + // CHECK: cf.cond_br [[COND2]], ^[[BB_MATCH:.+]], ^[[BB2:.+]] + // CHECK: ^[[BB_MATCH]]: + // CHECK: call @dummyA() + // CHECK: cf.br ^[[BB_EXIT:.+]] + // CHECK: ^[[BB2]]: + // CHECK: call @dummyB() + // CHECK: cf.br ^[[BB_EXIT]] + // CHECK: ^[[BB_EXIT]]: + case (x) + a, (b+c): dummyA(); + default: dummyB(); + endcase + + // CHECK: [[COND1:%.+]] = moore.casez_eq %arg0, %arg1 + // CHECK: [[COND2:%.+]] = moore.conversion [[COND1]] : !moore.i1 -> i1 + // CHECK: cf.cond_br [[COND2]], ^[[BB1:.+]], ^[[BB2:.+]] + // CHECK: ^[[BB1]]: + // CHECK: call @dummyA() + // CHECK: cf.br ^[[BB3:.+]] + // CHECK: ^[[BB2]]: + // CHECK: cf.br ^[[BB3]] + // CHECK: ^[[BB3]]: + casez (x) + a: dummyA(); + endcase + + // CHECK: [[COND1:%.+]] = moore.casexz_eq %arg0, %arg1 + // CHECK: [[COND2:%.+]] = moore.conversion [[COND1]] : !moore.i1 -> i1 + // CHECK: cf.cond_br [[COND2]], ^[[BB1:.+]], ^[[BB2:.+]] + // CHECK: ^[[BB1]]: + // CHECK: call @dummyA() + // CHECK: cf.br ^[[BB3:.+]] + // CHECK: ^[[BB2]]: + // CHECK: cf.br ^[[BB3]] + // CHECK: ^[[BB3]]: + casex (x) + a: dummyA(); + endcase +endfunction - // CHECK: [[TMP1:%.+]] = moore.read %x - // CHECK: [[COND:%.+]] = moore.conversion [[TMP1]] : !moore.i1 -> i1 - // CHECK: scf.if [[COND]] { - // CHECK: [[TMP2:%.+]] = moore.read %y : - // CHECK: moore.blocking_assign %x, [[TMP2]] : i1 - // CHECK: } - if (x) x = y; - - // CHECK: [[TMP1:%.+]] = moore.read %x - // CHECK: [[TMP2:%.+]] = moore.read %y - // CHECK: [[COND0:%.+]] = moore.and [[TMP1]], [[TMP2]] - // CHECK: [[COND1:%.+]] = moore.conversion [[COND0]] : !moore.i1 -> i1 - // CHECK: scf.if [[COND1]] { - // CHECK: [[TMP3:%.+]] = moore.read %y - // CHECK: moore.blocking_assign %x, [[TMP3]] - // CHECK: } - if (x &&& y) x = y; - - // CHECK: [[TMP1:%.+]] = moore.read %x - // CHECK: [[COND:%.+]] = moore.conversion [[TMP1]] : !moore.i1 -> i1 - // CHECK: scf.if [[COND]] { - // CHECK: [[TMP2:%.+]] = moore.read %z - // CHECK: moore.blocking_assign %x, [[TMP2]] - // CHECK: } else { - // CHECK: [[TMP3:%.+]] = moore.read %y - // CHECK: moore.blocking_assign %x, [[TMP3]] - // CHECK: } - if (x) x = z; else x = y; - - // CHECK: [[TMP1:%.+]] = moore.read %x - // CHECK: [[COND:%.+]] = moore.conversion [[TMP1]] : !moore.i1 -> i1 - // CHECK: scf.if [[COND]] { - // CHECK: [[TMP2:%.+]] = moore.read %x - // CHECK: moore.blocking_assign %x, [[TMP2]] - // CHECK: } else { - // CHECK: [[TMP3:%.+]] = moore.read %y - // CHECK: [[COND:%.+]] = moore.conversion [[TMP3]] : !moore.i1 -> i1 - // CHECK: scf.if [[COND]] { - // CHECK: [[TMP4:%.+]] = moore.read %y - // CHECK: moore.blocking_assign %x, [[TMP4]] - // CHECK: } else { - // CHECK: [[TMP5:%.+]] = moore.read %z - // CHECK: moore.blocking_assign %x, [[TMP5]] - // CHECK: } - // CHECK: } +// CHECK-LABEL: func.func private @ForLoopStatements( +// CHECK-SAME: %arg0: !moore.i32 +// CHECK-SAME: %arg1: !moore.i32 +// CHECK-SAME: %arg2: !moore.i1 +function void ForLoopStatements(int a, int b, bit c); + int x; + + // CHECK: moore.blocking_assign %x, %arg0 + // CHECK: cf.br ^[[BB_CHECK:.+]] + // CHECK: ^[[BB_CHECK]]: + // CHECK: [[TMP1:%.+]] = moore.read %x + // CHECK: [[TMP2:%.+]] = moore.slt [[TMP1]], %arg1 + // CHECK: [[TMP3:%.+]] = moore.conversion [[TMP2]] : !moore.i1 -> i1 + // CHECK: cf.cond_br [[TMP3]], ^[[BB_BODY:.+]], ^[[BB_EXIT:.+]] + // CHECK: ^[[BB_BODY]]: + // CHECK: call @dummyA() + // CHECK: cf.br ^[[BB_STEP:.+]] + // CHECK: ^[[BB_STEP]]: + // CHECK: call @dummyB() + // CHECK: cf.br ^[[BB_CHECK]] + // CHECK: ^[[BB_EXIT]]: + for (x = a; x < b; dummyB()) dummyA(); + + // CHECK: %y = moore.variable %arg0 : + // CHECK: cf.br ^[[BB_CHECK:.+]] + // CHECK: ^[[BB_CHECK]]: + // CHECK: [[TMP1:%.+]] = moore.read %y + // CHECK: [[TMP2:%.+]] = moore.slt [[TMP1]], %arg1 + // CHECK: [[TMP3:%.+]] = moore.conversion [[TMP2]] : !moore.i1 -> i1 + // CHECK: cf.cond_br [[TMP3]], ^[[BB_BODY:.+]], ^[[BB_EXIT:.+]] + // CHECK: ^[[BB_BODY]]: + // CHECK: call @dummyA() + // CHECK: cf.br ^[[BB_STEP:.+]] + // CHECK: ^[[BB_STEP]]: + // CHECK: call @dummyB() + // CHECK: cf.br ^[[BB_CHECK]] + // CHECK: ^[[BB_EXIT]]: + for (int y = a; y < b; dummyB()) dummyA(); + + // CHECK: cf.br ^[[BB_CHECK:.+]] + // CHECK: ^[[BB_CHECK]]: + // CHECK: [[TMP:%.+]] = moore.conversion %arg2 : !moore.i1 -> i1 + // CHECK: cf.cond_br [[TMP]], ^[[BB_BODY:.+]], ^[[BB_EXIT:.+]] + // CHECK: ^[[BB_BODY]]: + // CHECK: [[TMP:%.+]] = moore.conversion %arg2 : !moore.i1 -> i1 + // CHECK: cf.cond_br [[TMP]], ^[[BB_TRUE:.+]], ^[[BB_FALSE:.+]] + // CHECK: ^[[BB_TRUE]]: + // CHECK: cf.br ^[[BB_STEP:.+]] + // CHECK: ^[[BB_FALSE]]: + // CHECK: cf.br ^[[BB_EXIT]] + // CHECK: ^[[BB_STEP]]: + // CHECK: call @dummyB() + // CHECK: cf.br ^[[BB_CHECK]] + // CHECK: ^[[BB_EXIT]]: + for (; c; dummyB()) + if (c) + continue; + else + break; +endfunction + +// CHECK-LABEL: func.func private @ForeverLoopStatements( +// CHECK-SAME: %arg0: !moore.i1 +// CHECK-SAME: %arg1: !moore.i1 +function void ForeverLoopStatements(bit x, bit y); + // CHECK: cf.br ^[[BB_BODY:.+]] + // CHECK: ^[[BB_BODY]]: + forever begin if (x) begin - x = x; - end else if (y) begin - x = y; + // CHECK: call @dummyA() + // CHECK: cf.br ^[[BB_EXIT:.+]] + dummyA(); + break; end else begin - x = z; + // CHECK: call @dummyB() + // CHECK: cf.br ^[[BB_BODY]] + dummyB(); + continue; end + end + // CHECK: ^[[BB_EXIT]]: - //===------------------------------------------------------------------===// - // Case statements - - - // CHECK: [[TMP1:%.+]] = moore.read %x - // CHECK: [[TMP2:%.+]] = moore.read %x - // CHECK: [[TMP3:%.+]] = moore.eq [[TMP1]], [[TMP2]] : i1 -> i1 - // CHECK: [[TMP4:%.+]] = moore.conversion [[TMP3]] : !moore.i1 -> i1 - // CHECK: scf.if [[TMP4]] { - // CHECK: [[TMP5:%.+]] = moore.read %x - // CHECK: moore.blocking_assign %x, [[TMP5]] : i1 - // CHECK: } - // CHECK: [[TMP6:%.+]] = moore.read %x - // CHECK: [[TMP7:%.+]] = moore.eq [[TMP1]], [[TMP6]] : i1 -> i1 - // CHECK: [[TMP8:%.+]] = moore.read %y - // CHECK: [[TMP9:%.+]] = moore.eq [[TMP1]], [[TMP8]] : i1 -> i1 - // CHECK: [[TMP10:%.+]] = moore.or [[TMP7]], [[TMP9]] : i1 - // CHECK: [[TMP11:%.+]] = moore.conversion [[TMP10]] : !moore.i1 -> i1 - // CHECK: scf.if [[TMP11]] { - // CHECK: [[TMP12:%.+]] = moore.read %y - // CHECK: moore.blocking_assign %x, [[TMP12]] : i1 - // CHECK: } - case (x) - x: x = x; - x, y: x = y; - endcase + // CHECK: cf.br ^[[BB_BODY:.+]] + // CHECK: ^[[BB_BODY]]: + // CHECK: call @dummyA() + // CHECK: cf.br ^[[BB_BODY]] + forever dummyA(); +endfunction - // CHECK: [[TMP1:%.+]] = moore.read %x - // CHECK: [[TMP2:%.+]] = moore.read %x - // CHECK: [[TMP3:%.+]] = moore.eq [[TMP1]], [[TMP2]] : i1 -> i1 - // CHECK: [[TMP4:%.+]] = moore.conversion [[TMP3]] : !moore.i1 -> i1 - // CHECK: scf.if [[TMP4]] { - // CHECK: [[TMP5:%.+]] = moore.read %x - // CHECK: moore.blocking_assign %x, [[TMP5]] : i1 - // CHECK: } - // CHECK: [[TMP6:%.+]] = moore.read %x - // CHECK: [[TMP7:%.+]] = moore.eq [[TMP1]], [[TMP6]] : i1 -> i1 - // CHECK: [[TMP8:%.+]] = moore.read %y - // CHECK: [[TMP9:%.+]] = moore.eq [[TMP1]], [[TMP8]] : i1 -> i1 - // CHECK: [[TMP10:%.+]] = moore.or [[TMP7]], [[TMP9]] : i1 - // CHECK: [[TMP11:%.+]] = moore.conversion [[TMP10]] : !moore.i1 -> i1 - // CHECK: scf.if [[TMP11]] { - // CHECK: [[TMP12:%.+]] = moore.read %y - // CHECK: moore.blocking_assign %x, [[TMP12]] : i1 - // CHECK: } - // CHECK: [[TMP13:%.+]] = moore.read %z - // CHECK: [[TMP14:%.+]] = moore.eq [[TMP1]], [[TMP13]] : i1 -> i1 - // CHECK: [[TMP15:%.+]] = moore.conversion [[TMP14]] : !moore.i1 -> i1 - // CHECK: scf.if [[TMP15]] { - // CHECK: [[TMP16:%.+]] = moore.read %z - // CHECK: moore.blocking_assign %x, [[TMP16]] : i1 - // CHECK: } - // CHECK: [[TMP17:%.+]] = moore.or [[TMP10]], [[TMP14]] : i1 - // CHECK: [[TMP18:%.+]] = moore.or [[TMP3]], [[TMP17]] : i1 - // CHECK: [[TMP19:%.+]] = moore.not [[TMP18]] : i1 - // CHECK: [[TMP20:%.+]] = moore.conversion [[TMP19]] : !moore.i1 -> i1 - // CHECK: scf.if [[TMP20]] { - // CHECK: [[TMP21:%.+]] = moore.read %x - // CHECK: moore.blocking_assign %x, [[TMP21]] : i1 - // CHECK: } - case (x) - x: x = x; - x, y: x = y; - z: x = z; - default x = x; - endcase +// CHECK-LABEL: func.func private @WhileLoopStatements( +// CHECK-SAME: %arg0: !moore.i1 +// CHECK-SAME: %arg1: !moore.i1 +function void WhileLoopStatements(bit x, bit y); + // CHECK: cf.br ^[[BB_CHECK:.+]] + // CHECK: ^[[BB_CHECK]]: + // CHECK: [[TMP:%.+]] = moore.conversion %arg0 : !moore.i1 -> i1 + // CHECK: cf.cond_br [[TMP]], ^[[BB_BODY:.+]], ^[[BB_EXIT:.+]] + // CHECK: ^[[BB_BODY]]: + // CHECK: call @dummyA() + // CHECK: cf.br ^[[BB_CHECK]] + // CHECK: ^[[BB_EXIT]]: + while (x) dummyA(); + + // CHECK: cf.br ^[[BB_BODY:.+]] + // CHECK: ^[[BB_BODY]]: + // CHECK: call @dummyA() + // CHECK: cf.br ^[[BB_CHECK:.+]] + // CHECK: ^[[BB_CHECK]]: + // CHECK: [[TMP:%.+]] = moore.conversion %arg0 : !moore.i1 -> i1 + // CHECK: cf.cond_br [[TMP]], ^[[BB_BODY]], ^[[BB_EXIT:.+]] + // CHECK: ^[[BB_EXIT]]: + do dummyA(); while (x); + + // CHECK: cf.br ^[[BB_CHECK:.+]] + // CHECK: ^[[BB_CHECK]]: + // CHECK: [[TMP:%.+]] = moore.conversion %arg0 : !moore.i1 -> i1 + // CHECK: cf.cond_br [[TMP]], ^[[BB_BODY:.+]], ^[[BB_EXIT:.+]] + // CHECK: ^[[BB_BODY]]: + while (x) begin + if (y) begin + // CHECK: call @dummyA() + // CHECK: cf.br ^[[BB_EXIT]] + dummyA(); + break; + end else begin + // CHECK: call @dummyB() + // CHECK: cf.br ^[[BB_CHECK]] + dummyB(); + continue; + end + end + // CHECK: ^[[BB_EXIT]]: +endfunction - //===------------------------------------------------------------------===// - // Loop statements +// CHECK-LABEL: func.func private @RepeatLoopStatements( +// CHECK-SAME: %arg0: !moore.i32 +// CHECK-SAME: %arg1: !moore.i1 +function void RepeatLoopStatements(int x, bit y); + // CHECK: cf.br ^[[BB_CHECK:.+]](%arg0 : !moore.i32) + repeat (x) begin + // CHECK: ^[[BB_CHECK]]([[COUNT:%.+]]: !moore.i32): + // CHECK: [[TMP1:%.+]] = moore.bool_cast [[COUNT]] : i32 -> i1 + // CHECK: [[TMP2:%.+]] = moore.conversion [[TMP1]] : !moore.i1 -> i1 + // CHECK: cf.cond_br [[TMP2]], ^[[BB_BODY:.+]], ^[[BB_EXIT:.+]] + // CHECK: ^[[BB_BODY]]: + if (y) begin + // CHECK: call @dummyA() + // CHECK: cf.br ^[[BB_EXIT]] + dummyA(); + break; + end else begin + // CHECK: call @dummyB() + // CHECK: cf.br ^[[BB_STEP:.+]] + dummyB(); + continue; + end + // CHECK: ^[[BB_STEP]]: + // CHECK: [[TMP1:%.+]] = moore.constant 1 : i32 + // CHECK: [[TMP2:%.+]] = moore.sub [[COUNT]], [[TMP1]] : i32 + // CHECK: cf.br ^[[BB_CHECK]]([[TMP2]] : !moore.i32) + end + // CHECK: ^[[BB_EXIT]]: +endfunction - // CHECK: [[TMP1:%.+]] = moore.read %x - // CHECK: moore.blocking_assign %y, [[TMP1]] : i1 - // CHECK: scf.while : () -> () { - // CHECK: [[TMP2:%.+]] = moore.read %x - // CHECK: [[COND:%.+]] = moore.conversion [[TMP2]] : !moore.i1 -> i1 - // CHECK: scf.condition([[COND]]) - // CHECK: } do { - // CHECK: [[TMP3:%.+]] = moore.read %y - // CHECK: moore.blocking_assign %x, [[TMP3]] : i1 - // CHECK: [[TMP4:%.+]] = moore.read %z - // CHECK: moore.blocking_assign %x, [[TMP4]] : i1 - // CHECK: scf.yield - // CHECK: } - for (y = x; x; x = z) x = y; - - // CHECK: [[TMP:%.+]] = moore.constant true : i1 - // CHECK: %iv = moore.variable [[TMP]] : - // CHECK: scf.while : () -> () { - // CHECK: [[TMP:%.+]] = moore.read %iv - // CHECK: [[COND:%.+]] = moore.conversion [[TMP]] : !moore.i1 -> i1 - // CHECK: scf.condition([[COND]]) - // CHECK: } do { - // CHECK: [[TMP:%.+]] = moore.read %iv - // CHECK: moore.blocking_assign %y, [[TMP]] : i1 - // CHECK: [[TMP:%.+]] = moore.read %iv - // CHECK: moore.blocking_assign %x, [[TMP]] : i1 - // CHECK: scf.yield - // CHECK: } - for (bit iv = '1; iv; x = iv) y = iv; - - // CHECK: [[TMP1:%.+]] = moore.read %i - // CHECK: scf.while (%arg0 = [[TMP1]]) : (!moore.i32) -> !moore.i32 { - // CHECK: [[TMP2:%.+]] = moore.bool_cast %arg0 : i32 -> i1 - // CHECK: [[TMP3:%.+]] = moore.conversion [[TMP2]] : !moore.i1 -> i1 - // CHECK: scf.condition([[TMP3]]) %arg0 : !moore.i32 - // CHECK: } do { - // CHECK: ^bb0(%arg0: !moore.i32): - // CHECK: [[TMP4:%.+]] = moore.read %y - // CHECK: moore.blocking_assign %x, [[TMP4]] : i1 - // CHECK: [[TMP5:%.+]] = moore.constant 1 : i32 - // CHECK: [[TMP6:%.+]] = moore.sub %arg0, [[TMP5]] : i32 - // CHECK: scf.yield [[TMP6]] : !moore.i32 - // CHECK: } - repeat (i) x = y; - - // CHECK: scf.while : () -> () { - // CHECK: [[TMP1:%.+]] = moore.read %x - // CHECK: [[COND:%.+]] = moore.conversion [[TMP1]] : !moore.i1 -> i1 - // CHECK: scf.condition([[COND]]) - // CHECK: } do { - // CHECK: [[TMP2:%.+]] = moore.read %y - // CHECK: moore.blocking_assign %x, [[TMP2]] : i1 - // CHECK: scf.yield - // CHECK: } - while (x) x = y; - - // CHECK: scf.while : () -> () { - // CHECK: [[TMP1:%.+]] = moore.read %y - // CHECK: moore.blocking_assign %x, [[TMP1]] : i1 - // CHECK: [[TMP2:%.+]] = moore.read %x - // CHECK: [[COND:%.+]] = moore.conversion [[TMP2]] : !moore.i1 -> i1 - // CHECK: scf.condition([[COND]]) - // CHECK: } do { - // CHECK: scf.yield - // CHECK: } - do x = y; while (x); - - // CHECK: scf.while : () -> () { - // CHECK: %true = hw.constant true - // CHECK: scf.condition(%true) - // CHECK: } do { - // CHECK: [[TMP1:%.+]] = moore.read %y - // CHECK: moore.blocking_assign %x, [[TMP1]] : i1 - // CHECK: scf.yield - // CHECK: } - forever x = y; +// CHECK-LABEL: moore.module @Statements +module Statements; + bit x, y, z; + int i; + initial begin + // CHECK: %a = moore.variable : + automatic int a; + // CHECK: [[TMP1:%.+]] = moore.read %a + // CHECK moore.blocking_assign %i, [[TMP1]] : i32 + i = a; //===------------------------------------------------------------------===// // Assignments diff --git a/test/Dialect/Moore/basic.mlir b/test/Dialect/Moore/basic.mlir index f201138ebf4a..d1a2b3a9db1c 100644 --- a/test/Dialect/Moore/basic.mlir +++ b/test/Dialect/Moore/basic.mlir @@ -77,12 +77,12 @@ moore.module @Module() { // CHECK: moore.procedure always_comb { // CHECK: moore.procedure always_latch { // CHECK: moore.procedure always_ff { - moore.procedure initial {} - moore.procedure final {} - moore.procedure always {} - moore.procedure always_comb {} - moore.procedure always_latch {} - moore.procedure always_ff {} + moore.procedure initial { moore.return } + moore.procedure final { moore.return } + moore.procedure always { moore.return } + moore.procedure always_comb { moore.return } + moore.procedure always_latch { moore.return } + moore.procedure always_ff { moore.return } // CHECK: %[[TMP1:.+]] = moore.read %v2 // CHECK: moore.assign %v1, %[[TMP1]] : i1 @@ -100,6 +100,7 @@ moore.module @Module() { moore.nonblocking_assign %v1, %4 : i1 // CHECK: %a = moore.variable : %a = moore.variable : + moore.return } } diff --git a/test/Dialect/Moore/lower-concatref.mlir b/test/Dialect/Moore/lower-concatref.mlir index 391fad9efc52..2183ccd8a580 100644 --- a/test/Dialect/Moore/lower-concatref.mlir +++ b/test/Dialect/Moore/lower-concatref.mlir @@ -43,6 +43,7 @@ // CHECK: moore.nonblocking_assign %v, %[[TMP2]] : l42 moore.nonblocking_assign %7, %8 : l9002 } + moore.return } moore.output } @@ -73,6 +74,7 @@ moore.module @Nested() { // CHECK: %[[TMP5:.+]] = moore.extract %[[TMP2]] from 0 : i96 -> i32 // CHECK: moore.blocking_assign %y, %[[TMP5]] : i32 moore.blocking_assign %3, %6 : i96 + moore.return } moore.output } diff --git a/test/Dialect/Moore/mem2reg.mlir b/test/Dialect/Moore/mem2reg.mlir index b5246e6c556b..80a59bed7b7a 100644 --- a/test/Dialect/Moore/mem2reg.mlir +++ b/test/Dialect/Moore/mem2reg.mlir @@ -29,5 +29,6 @@ moore.module @LocalVar() { moore.blocking_assign %a, %6 : i32 %7 = moore.read %a : moore.blocking_assign %y, %7 : i32 + moore.return } } diff --git a/test/Dialect/Moore/simplify-procedures.mlir b/test/Dialect/Moore/simplify-procedures.mlir index f829549eb099..451160643e37 100644 --- a/test/Dialect/Moore/simplify-procedures.mlir +++ b/test/Dialect/Moore/simplify-procedures.mlir @@ -33,6 +33,8 @@ moore.module @Foo() { %3 = moore.constant 1 : i32 %4 = moore.add %2, %3 : i32 moore.blocking_assign %a, %4 : i32 + + moore.return } // CHECK: moore.procedure always_comb @@ -44,5 +46,7 @@ moore.module @Foo() { // CHECK: moore.blocking_assign %y, [[TMP]] %0 = moore.read %a : moore.blocking_assign %y, %0 : i32 + + moore.return } } From b35bc81fc0f05f035335c1f48d757d9e012676bf Mon Sep 17 00:00:00 2001 From: Asuna Date: Tue, 6 Aug 2024 01:43:22 +0200 Subject: [PATCH 014/119] [Docs][FIRRTL] Build and include docs for intrinsic ops --- docs/Dialects/FIRRTL/_index.md | 4 ++++ include/circt/Dialect/FIRRTL/CMakeLists.txt | 1 + include/circt/Dialect/FIRRTL/FIRRTLIntrinsics.td | 4 ++++ 3 files changed, 9 insertions(+) diff --git a/docs/Dialects/FIRRTL/_index.md b/docs/Dialects/FIRRTL/_index.md index a617ede06af3..4b2dec99bf29 100644 --- a/docs/Dialects/FIRRTL/_index.md +++ b/docs/Dialects/FIRRTL/_index.md @@ -22,6 +22,10 @@ page](https://github.com/freechipsproject/firrtl). [include "Dialects/FIRRTLExpressionOps.md"] +## Operation Definitions -- Intrinsics + +[include "Dialects/FIRRTLIntrinsicOps.md"] + ## Type Definitions [include "Dialects/FIRRTLTypes.md"] diff --git a/include/circt/Dialect/FIRRTL/CMakeLists.txt b/include/circt/Dialect/FIRRTL/CMakeLists.txt index 6b598d85a012..c1bee8852f5c 100644 --- a/include/circt/Dialect/FIRRTL/CMakeLists.txt +++ b/include/circt/Dialect/FIRRTL/CMakeLists.txt @@ -36,6 +36,7 @@ add_circt_doc(FIRRTLStructure Dialects/FIRRTLStructureOps -gen-op-doc) add_circt_doc(FIRRTLDeclarations Dialects/FIRRTLDeclarationOps -gen-op-doc) add_circt_doc(FIRRTLStatements Dialects/FIRRTLStatementOps -gen-op-doc) add_circt_doc(FIRRTLExpressions Dialects/FIRRTLExpressionOps -gen-op-doc) +add_circt_doc(FIRRTLIntrinsics Dialects/FIRRTLIntrinsicOps -gen-op-doc) add_circt_doc(FIRRTLTypes Dialects/FIRRTLTypes -gen-typedef-doc) add_circt_doc(FIRRTLTypesImpl Dialects/FIRRTLTypesImpl -gen-typedef-doc) add_circt_doc(FIRRTLOpInterfaces Dialects/FIRRTLOpInterfaces -gen-op-interface-docs) diff --git a/include/circt/Dialect/FIRRTL/FIRRTLIntrinsics.td b/include/circt/Dialect/FIRRTL/FIRRTLIntrinsics.td index 4e6082eb37d1..99aa2770d51c 100644 --- a/include/circt/Dialect/FIRRTL/FIRRTLIntrinsics.td +++ b/include/circt/Dialect/FIRRTL/FIRRTLIntrinsics.td @@ -13,7 +13,11 @@ #ifndef CIRCT_DIALECT_FIRRTL_FIRRTLINTRINSICS_TD #define CIRCT_DIALECT_FIRRTL_FIRRTLINTRINSICS_TD +include "FIRRTLAttributes.td" include "FIRRTLDialect.td" +include "FIRRTLTypes.td" +include "circt/Dialect/HW/HWOpInterfaces.td" +include "mlir/Interfaces/SideEffectInterfaces.td" //===----------------------------------------------------------------------===// // Generic intrinsic operation for parsing into before lowering. From 930aabe70fe78965a153f6e936c5853e948873eb Mon Sep 17 00:00:00 2001 From: cepheus Date: Tue, 6 Aug 2024 11:29:30 +0800 Subject: [PATCH 015/119] [CMake] Fix install failure with CMAKE_SLANG_FRONTEND_ENABLED enabled (#7437) Slang compiler relies on the third-party libraries like unordered_dense and fmt library. The fmt library provides two ways to integrated it: 1.Headers-only 2.Seperately compiled The main purpose of this commit is to avoid installation failure of CIRCT project due to finding fmt header file in wrong path which is in circt `include` directory when CMake_slang_Frontend_enabled is turned on. We hope to not install header files coming from fmt library. --- CMakeLists.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 44437c9932f9..059cb2337faa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -591,6 +591,13 @@ if(CIRCT_SLANG_FRONTEND_ENABLED) # harder than it ought to be. set_property( GLOBAL APPEND PROPERTY CIRCT_EXPORTS slang_slang unordered_dense fmt) + + # Disable the installation of headers coming from third-party libraries. We + # won't use those APIs directly. Just make them static libraries for the sake + # of running slang normally. + set_target_properties(fmt PROPERTIES PUBLIC_HEADER "") + set_target_properties(unordered_dense PROPERTIES PUBLIC_HEADER "") + install(TARGETS slang_slang unordered_dense fmt EXPORT CIRCTTargets) else() find_package(slang 3.0 REQUIRED) From fd2b37acfd18d9f79efebd7b4d7905a8a215719c Mon Sep 17 00:00:00 2001 From: Morten Borup Petersen Date: Tue, 6 Aug 2024 14:28:36 +0200 Subject: [PATCH 016/119] [Support] Allow erasing names in Namespace (#7424) * [Support] Allow erasing names in Namespace Allowing erasing names in a namespace seems more sane than micro-managing which names gets added to a namespace. E.g., it's convenient to use `Namespace::add(SymbolCache &` to efficiently prime a namespace, and then surgically removing some known identifier,, instead of having to re-implement how symbols are added to the namespace. * Add lock --------- Co-authored-by: Morten Borup Petersen --- include/circt/Support/Namespace.h | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/include/circt/Support/Namespace.h b/include/circt/Support/Namespace.h index 51dac95129ad..d422991f1eb8 100644 --- a/include/circt/Support/Namespace.h +++ b/include/circt/Support/Namespace.h @@ -35,11 +35,13 @@ class Namespace { nextIndex.insert({"", 0}); } Namespace(const Namespace &other) = default; - Namespace(Namespace &&other) : nextIndex(std::move(other.nextIndex)) {} + Namespace(Namespace &&other) + : nextIndex(std::move(other.nextIndex)), locked(other.locked) {} Namespace &operator=(const Namespace &other) = default; Namespace &operator=(Namespace &&other) { nextIndex = std::move(other.nextIndex); + locked = other.locked; return *this; } @@ -59,8 +61,19 @@ class Namespace { nextIndex.insert({strAttr.getValue(), 0}); } + /// Removes a symbol from the namespace. Returns true if the symbol was + /// removed, false if the symbol was not found. + /// This is only allowed to be called _before_ any call to newName. + bool erase(llvm::StringRef symbol) { + assert(!locked && "Cannot erase names from a locked namespace"); + return nextIndex.erase(symbol); + } + /// Empty the namespace. - void clear() { nextIndex.clear(); } + void clear() { + nextIndex.clear(); + locked = false; + } /// Return a unique name, derived from the input `name`, and add the new name /// to the internal namespace. There are two possible outcomes for the @@ -70,6 +83,7 @@ class Namespace { /// 2. The name is given a `_` suffix where `` is a number starting from /// `0` and incrementing by one each time (`_0`, ...). StringRef newName(const Twine &name) { + locked = true; // Special case the situation where there is no name collision to avoid // messing with the SmallString allocation below. llvm::SmallString<64> tryName; @@ -103,6 +117,7 @@ class Namespace { /// 2. The name is given a suffix `__` where `` is a number /// starting from `0` and incrementing by one each time. StringRef newName(const Twine &name, const Twine &suffix) { + locked = true; // Special case the situation where there is no name collision to avoid // messing with the SmallString allocation below. llvm::SmallString<64> tryName; @@ -142,6 +157,11 @@ class Namespace { // namespace. It follows that all values less than the "next index" value are // already used. llvm::StringMap nextIndex; + + // When true, no names can be erased from the namespace. This is to prevent + // erasing names after they have been used, thus leaving users of the + // namespace in an inconsistent state. + bool locked = false; }; } // namespace circt From de281de5ded11650a13af787f3e18d0faefd1acd Mon Sep 17 00:00:00 2001 From: Will Dietz Date: Tue, 6 Aug 2024 08:00:16 -0500 Subject: [PATCH 017/119] [FIRRTL] Verify main module is a module, and is public. (#7439) Add tests for new errors and existing error (changed). Op errors should remember to make sense when read prefixed with "mydialect.opname op ". --- lib/Dialect/FIRRTL/FIRRTLOps.cpp | 12 +++++++++-- test/Dialect/FIRRTL/annotations-errors.mlir | 12 ++++------- test/Dialect/FIRRTL/errors.mlir | 20 +++++++++++++++++++ test/Dialect/FIRRTL/lower-classes.mlir | 2 +- test/Dialect/FIRRTL/lower-intmodules.mlir | 4 ++-- .../FIRRTL/lower-intrinsics-errors.mlir | 2 +- 6 files changed, 38 insertions(+), 14 deletions(-) diff --git a/lib/Dialect/FIRRTL/FIRRTLOps.cpp b/lib/Dialect/FIRRTL/FIRRTLOps.cpp index 2304c4700512..65d3f5f19b84 100644 --- a/lib/Dialect/FIRRTL/FIRRTLOps.cpp +++ b/lib/Dialect/FIRRTL/FIRRTLOps.cpp @@ -519,8 +519,16 @@ LogicalResult CircuitOp::verifyRegions() { mlir::SymbolTable symtbl(getOperation()); - if (!symtbl.lookup(main)) - return emitOpError().append("Module with same name as circuit not found"); + auto *mainModule = symtbl.lookup(main); + if (!mainModule) + return emitOpError().append( + "does not contain module with same name as circuit"); + if (!isa(mainModule)) + return mainModule->emitError( + "entity with name of circuit must be a module"); + if (symtbl.getSymbolVisibility(mainModule) != + mlir::SymbolTable::Visibility::Public) + return mainModule->emitError("main module must be public"); // Store a mapping of defname to either the first external module // that defines it or, preferentially, the first external module diff --git a/test/Dialect/FIRRTL/annotations-errors.mlir b/test/Dialect/FIRRTL/annotations-errors.mlir index 96208374f957..4b229779a383 100644 --- a/test/Dialect/FIRRTL/annotations-errors.mlir +++ b/test/Dialect/FIRRTL/annotations-errors.mlir @@ -438,23 +438,19 @@ firrtl.circuit "Top" attributes { // ----- // OutputDirAnnotation targeting a non-public module should fail. -// expected-error @below {{Unable to apply annotation: {class = "circt.OutputDirAnnotation", dirname = "foo", target = "~Top|Top"}}} +// expected-error @below {{Unable to apply annotation: {class = "circt.OutputDirAnnotation", dirname = "foo", target = "~Top|NotTop"}}} firrtl.circuit "Top" attributes { rawAnnotations = [ { class = "circt.OutputDirAnnotation", dirname = "foo", - target = "~Top|Top" - }, - { - class = "circt.OutputDirAnnotation", - dirname = "foo", - target = "~Top|Top" + target = "~Top|NotTop" } ] } { + firrtl.module @Top() {} // expected-error @below {{circt.OutputDirAnnotation must target a public module}} - firrtl.module private @Top() {} + firrtl.module private @NotTop() {} } // ----- diff --git a/test/Dialect/FIRRTL/errors.mlir b/test/Dialect/FIRRTL/errors.mlir index 25fb6a262e8a..90cf8b9a9786 100644 --- a/test/Dialect/FIRRTL/errors.mlir +++ b/test/Dialect/FIRRTL/errors.mlir @@ -2554,3 +2554,23 @@ firrtl.module @LHSTypes() { } } +// ----- + +// expected-error @below {{op does not contain module with same name as circuit}} +firrtl.circuit "NoMain" { } + +// ----- + +firrtl.circuit "PrivateMain" { + // expected-error @below {{main module must be public}} + firrtl.module private @PrivateMain() {} +} + +// ----- + +firrtl.circuit "MainNotModule" { + // expected-error @below {{entity with name of circuit must be a module}} + func.func @MainNotModule() { + return + } +} diff --git a/test/Dialect/FIRRTL/lower-classes.mlir b/test/Dialect/FIRRTL/lower-classes.mlir index 3cc0a551e2b6..803ee34cec81 100644 --- a/test/Dialect/FIRRTL/lower-classes.mlir +++ b/test/Dialect/FIRRTL/lower-classes.mlir @@ -335,7 +335,7 @@ firrtl.circuit "AnyCast" { // CHECK-LABEL: firrtl.circuit "ModuleWithPropertySubmodule" firrtl.circuit "ModuleWithPropertySubmodule" { // CHECK: om.class @ModuleWithPropertySubmodule_Class - firrtl.module private @ModuleWithPropertySubmodule() { + firrtl.module @ModuleWithPropertySubmodule() { %c0 = firrtl.integer 0 // CHECK: om.object @SubmoduleWithProperty_Class %inst.prop = firrtl.instance inst @SubmoduleWithProperty(in prop: !firrtl.integer) diff --git a/test/Dialect/FIRRTL/lower-intmodules.mlir b/test/Dialect/FIRRTL/lower-intmodules.mlir index 1c9113a11f96..1add51ad8907 100644 --- a/test/Dialect/FIRRTL/lower-intmodules.mlir +++ b/test/Dialect/FIRRTL/lower-intmodules.mlir @@ -49,8 +49,8 @@ firrtl.circuit "ProbeIntrinsicTest" { // CHECK-NOT: firrtl.intmodule private @FPGAProbeIntrinsic firrtl.intmodule private @FPGAProbeIntrinsic(in data: !firrtl.uint, in clock: !firrtl.clock) attributes {intrinsic = "circt_fpga_probe"} - // CHECK-LABEL: firrtl.module private @ProbeIntrinsicTest - firrtl.module private @ProbeIntrinsicTest(in %clock : !firrtl.clock, in %data : !firrtl.uint<32>) { + // CHECK-LABEL: firrtl.module @ProbeIntrinsicTest + firrtl.module @ProbeIntrinsicTest(in %clock : !firrtl.clock, in %data : !firrtl.uint<32>) { // CHECK: [[DATA:%.+]] = firrtl.wire : !firrtl.uint // CHECK-NEXT: [[CLOCK:%.+]] = firrtl.wire : !firrtl.clock // CHECK-NEXT: firrtl.int.generic "circt_fpga_probe" [[DATA]], [[CLOCK]] : (!firrtl.uint, !firrtl.clock) -> () diff --git a/test/Dialect/FIRRTL/lower-intrinsics-errors.mlir b/test/Dialect/FIRRTL/lower-intrinsics-errors.mlir index a83b2f67a850..ca88fdbeb129 100644 --- a/test/Dialect/FIRRTL/lower-intrinsics-errors.mlir +++ b/test/Dialect/FIRRTL/lower-intrinsics-errors.mlir @@ -1,7 +1,7 @@ // RUN: circt-opt --pass-pipeline='builtin.module(firrtl.circuit(firrtl.module(firrtl-lower-intrinsics)))' -verify-diagnostics --split-input-file %s firrtl.circuit "UnknownIntrinsic" { - firrtl.module private @UnknownIntrinsic(in %data: !firrtl.uint<32>) { + firrtl.module @UnknownIntrinsic(in %data: !firrtl.uint<32>) { %0 = firrtl.wire : !firrtl.uint<32> // expected-error @below {{unknown intrinsic}} // expected-error @below {{failed to legalize}} From 0b93783aae4408235a60a616981e4344848bd638 Mon Sep 17 00:00:00 2001 From: Morten Borup Petersen Date: Tue, 6 Aug 2024 17:34:41 +0200 Subject: [PATCH 018/119] [Ibis] Don't include design name in namespace in IbisContainersToHW (#7425) The ibis.design op will be removed after the IbisContainersToHW pass, and there may be ibis.component's inside the design that have the same name as the design; we want that name to persist, and not be falsely considered a duplicate. Co-authored-by: Morten Borup Petersen --- lib/Dialect/Ibis/Transforms/IbisContainersToHW.cpp | 7 +++++++ test/Dialect/Ibis/Transforms/containers_to_hw.mlir | 13 +++++++++++++ 2 files changed, 20 insertions(+) diff --git a/lib/Dialect/Ibis/Transforms/IbisContainersToHW.cpp b/lib/Dialect/Ibis/Transforms/IbisContainersToHW.cpp index 3b73257f5c94..0e6c8ac6f7bd 100644 --- a/lib/Dialect/Ibis/Transforms/IbisContainersToHW.cpp +++ b/lib/Dialect/Ibis/Transforms/IbisContainersToHW.cpp @@ -369,6 +369,13 @@ void ContainersToHWPass::runOnOperation() { target.addIllegalOp(); target.markUnknownOpDynamicallyLegal([](Operation *) { return true; }); + // Remove the name of the ibis.design's from the namespace - The ibis.design + // op will be removed after this pass, and there may be ibis.component's + // inside the design that have the same name as the design; we want that + // name to persist, and not be falsely considered a duplicate. + for (auto designOp : getOperation().getOps()) + modNamespace.erase(designOp.getSymName()); + // Parts of the conversion patterns will update operations in place, which in // turn requires the updated operations to be legalizeable. These in-place ops // also include ibis ops that eventually will get replaced once all of the diff --git a/test/Dialect/Ibis/Transforms/containers_to_hw.mlir b/test/Dialect/Ibis/Transforms/containers_to_hw.mlir index 417c237e624e..b77656b6023f 100644 --- a/test/Dialect/Ibis/Transforms/containers_to_hw.mlir +++ b/test/Dialect/Ibis/Transforms/containers_to_hw.mlir @@ -163,3 +163,16 @@ ibis.container "Foo" sym @B top_level { hw.module.extern @D_Foo(in %theExternModule : i1) hw.module.extern @Foo(in %theExternModule : i1) + +// ----- + +// Test that containers with names that alias with the design op are not +// de-aliased. + +// CHECK: hw.module @D + +ibis.design @D { + ibis.container "D" sym @D top_level { + %this = ibis.this <@D::@D> + } +} From 4415b9c2f74c24f7e75e8c64b2fb3aaf6f3d6d88 Mon Sep 17 00:00:00 2001 From: Will Dietz Date: Tue, 6 Aug 2024 11:03:10 -0500 Subject: [PATCH 019/119] [FIRRTL] Verify RWProbeOp target has layer requirements. (#7372) RWProbe conservatively means a write to the target, so check that the target is indeed writeable from where the rwprobe is. --- lib/Dialect/FIRRTL/FIRRTLOps.cpp | 26 +++++++++++++++++++++++--- test/Dialect/FIRRTL/errors.mlir | 14 ++++++++++++++ test/Dialect/FIRRTL/layers.mlir | 12 ++++++++++++ 3 files changed, 49 insertions(+), 3 deletions(-) diff --git a/lib/Dialect/FIRRTL/FIRRTLOps.cpp b/lib/Dialect/FIRRTL/FIRRTLOps.cpp index 65d3f5f19b84..2d01a650e334 100644 --- a/lib/Dialect/FIRRTL/FIRRTLOps.cpp +++ b/lib/Dialect/FIRRTL/FIRRTLOps.cpp @@ -6201,10 +6201,30 @@ LogicalResult RWProbeOp::verifyInnerRefs(hw::InnerRefNamespace &ns) { } return success(); }; + + auto checkLayers = [&](Location loc) -> LogicalResult { + auto dstLayers = getAmbientLayersAt(target.getOp()); + auto srcLayers = getLayersFor(getResult()); + SmallVector missingLayers; + if (!isLayerSetCompatibleWith(srcLayers, dstLayers, missingLayers)) { + auto diag = emitOpError("target has insufficient layer requirements"); + auto ¬e = diag.attachNote(loc); + note << "target is missing layer requirements: "; + llvm::interleaveComma(missingLayers, note); + return failure(); + } + return success(); + }; + auto checks = [&](auto type, Location loc) { + if (failed(checkLayers(loc))) + return failure(); + return checkFinalType(type, loc); + }; + if (target.isPort()) { auto mod = cast(target.getOp()); - return checkFinalType(mod.getPortType(target.getPort()), - mod.getPortLocation(target.getPort())); + return checks(mod.getPortType(target.getPort()), + mod.getPortLocation(target.getPort())); } hw::InnerSymbolOpInterface symOp = cast(target.getOp()); @@ -6218,7 +6238,7 @@ LogicalResult RWProbeOp::verifyInnerRefs(hw::InnerRefNamespace &ns) { return emitOpError("is not dominated by target") .attachNote(symOp.getLoc()) .append("target here"); - return checkFinalType(symOp.getTargetResult().getType(), symOp.getLoc()); + return checks(symOp.getTargetResult().getType(), symOp.getLoc()); } //===----------------------------------------------------------------------===// diff --git a/test/Dialect/FIRRTL/errors.mlir b/test/Dialect/FIRRTL/errors.mlir index 90cf8b9a9786..199dcc171d2a 100644 --- a/test/Dialect/FIRRTL/errors.mlir +++ b/test/Dialect/FIRRTL/errors.mlir @@ -2072,6 +2072,20 @@ firrtl.circuit "RWProbeUseDef" { // ----- +firrtl.circuit "RWProbeLayerRequirements" { + firrtl.layer @A bind { } + firrtl.module @RWProbeLayerRequirements(in %cond : !firrtl.uint<1>) { + // expected-note @below {{target is missing layer requirements: @A}} + %w = firrtl.wire sym @x : !firrtl.uint<1> + firrtl.layerblock @A { + // expected-error @below {{target has insufficient layer requirements}} + %rw = firrtl.ref.rwprobe <@RWProbeLayerRequirements::@x> : !firrtl.rwprobe> + } + } +} + +// ----- + firrtl.circuit "MissingClassForObjectPortInModule" { // expected-error @below {{'firrtl.module' op references unknown class @Missing}} firrtl.module @MissingClassForObjectPortInModule(out %o: !firrtl.class<@Missing()>) {} diff --git a/test/Dialect/FIRRTL/layers.mlir b/test/Dialect/FIRRTL/layers.mlir index 472b171a9a43..23567729cda7 100644 --- a/test/Dialect/FIRRTL/layers.mlir +++ b/test/Dialect/FIRRTL/layers.mlir @@ -201,4 +201,16 @@ firrtl.circuit "Test" { firrtl.propassign %foo_in, %str : !firrtl.string } } + + //===--------------------------------------------------------------------===// + // RWProbe under Layer + //===--------------------------------------------------------------------===// + + firrtl.module @RWProbeInLayer() { + firrtl.layerblock @A { + %w = firrtl.wire sym @sym : !firrtl.uint<1> + %rwp = firrtl.ref.rwprobe <@RWProbeInLayer::@sym> : !firrtl.rwprobe> + } + } + } From cac5b3a285871bafafdcf35c2f2e98afc24f40ef Mon Sep 17 00:00:00 2001 From: Martin Erhart Date: Tue, 6 Aug 2024 17:10:12 +0100 Subject: [PATCH 020/119] [SMT] Add convenience builder --- include/circt/Dialect/SMT/SMTIntOps.td | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/include/circt/Dialect/SMT/SMTIntOps.td b/include/circt/Dialect/SMT/SMTIntOps.td index 8410402115dc..86802fe87d4a 100644 --- a/include/circt/Dialect/SMT/SMTIntOps.td +++ b/include/circt/Dialect/SMT/SMTIntOps.td @@ -52,6 +52,12 @@ class VariadicIntOp : SMTIntOp { let arguments = (ins Variadic:$inputs); let results = (outs IntType:$result); let assemblyFormat = "$inputs attr-dict"; + + let builders = [ + OpBuilder<(ins "mlir::ValueRange":$inputs), [{ + build($_builder, $_state, $_builder.getType(), inputs); + }]>, + ]; } class BinaryIntOp : SMTIntOp { From 1645d71c1de5cd482f7fa685dff061cb18c213f9 Mon Sep 17 00:00:00 2001 From: Will Dietz Date: Tue, 6 Aug 2024 11:25:28 -0500 Subject: [PATCH 021/119] [FIRRTL] Tweak printing of layers to avoid extra space. (#7449) Before: firrtl.layer @A inline After: firrtl.layer @A inline --- include/circt/Dialect/FIRRTL/FIRRTLStructure.td | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/circt/Dialect/FIRRTL/FIRRTLStructure.td b/include/circt/Dialect/FIRRTL/FIRRTLStructure.td index 80ad66b5082e..dbd23c955ff3 100644 --- a/include/circt/Dialect/FIRRTL/FIRRTLStructure.td +++ b/include/circt/Dialect/FIRRTL/FIRRTLStructure.td @@ -445,7 +445,7 @@ def LayerOp : FIRRTLOp< let results = (outs); let regions = (region SizedRegion<1>:$body); let assemblyFormat = [{ - $sym_name $convention attr-dict-with-keyword $body + $sym_name `` $convention attr-dict-with-keyword $body }]; } From 1a8f82e7a69a50e3514870cc42a1d14a06cb09a7 Mon Sep 17 00:00:00 2001 From: jpien13 <113210968+jpien13@users.noreply.github.com> Date: Tue, 6 Aug 2024 17:47:47 -0400 Subject: [PATCH 022/119] [FIRRTL] Replaced 'replicate' to correctly named 'replace' flags (#7442) Renamed and replaced shouldReplicateSequentialMemories to shouldReplaceSequentialMemories per issue #7384 --- include/circt/Firtool/Firtool.h | 2 +- lib/Firtool/Firtool.cpp | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/include/circt/Firtool/Firtool.h b/include/circt/Firtool/Firtool.h index 9e6e7fbfb678..64ebefca783a 100644 --- a/include/circt/Firtool/Firtool.h +++ b/include/circt/Firtool/Firtool.h @@ -96,7 +96,7 @@ class FirtoolOptions { return allowAddingPortsOnPublic; } bool shouldConvertProbesToSignals() const { return probesToSignals; } - bool shouldReplicateSequentialMemories() const { return replSeqMem; } + bool shouldReplaceSequentialMemories() const { return replSeqMem; } bool shouldDisableOptimization() const { return disableOptimization; } bool shouldLowerMemories() const { return lowerMemories; } bool shouldDedup() const { return !noDedup; } diff --git a/lib/Firtool/Firtool.cpp b/lib/Firtool/Firtool.cpp index 0bfbfc13fb75..07d93548de3b 100644 --- a/lib/Firtool/Firtool.cpp +++ b/lib/Firtool/Firtool.cpp @@ -78,7 +78,7 @@ LogicalResult firtool::populateCHIRRTLToLowFIRRTL(mlir::PassManager &pm, pm.nest().addPass(firrtl::createInferWidthsPass()); pm.nest().addPass( - firrtl::createMemToRegOfVecPass(opt.shouldReplicateSequentialMemories(), + firrtl::createMemToRegOfVecPass(opt.shouldReplaceSequentialMemories(), opt.shouldIgnoreReadEnableMemories())); pm.nest().addPass(firrtl::createInferResetsPass()); @@ -161,7 +161,7 @@ LogicalResult firtool::populateCHIRRTLToLowFIRRTL(mlir::PassManager &pm, pm.nest().nest().addPass( firrtl::createInferReadWritePass()); - if (opt.shouldReplicateSequentialMemories()) + if (opt.shouldReplaceSequentialMemories()) pm.nest().addPass(firrtl::createLowerMemoryPass()); pm.nest().addPass(firrtl::createPrefixModulesPass()); @@ -176,7 +176,7 @@ LogicalResult firtool::populateCHIRRTLToLowFIRRTL(mlir::PassManager &pm, pm.addNestedPass(firrtl::createAddSeqMemPortsPass()); pm.addPass(firrtl::createCreateSiFiveMetadataPass( - opt.shouldReplicateSequentialMemories(), + opt.shouldReplaceSequentialMemories(), opt.getReplaceSequentialMemoriesFile())); pm.addNestedPass(firrtl::createExtractInstancesPass()); @@ -303,7 +303,7 @@ LogicalResult firtool::populateHWToSV(mlir::PassManager &pm, FirtoolOptions::RandomKind::Mem), /*disableRegRandomization=*/ !opt.isRandomEnabled(FirtoolOptions::RandomKind::Reg), - /*replSeqMem=*/opt.shouldReplicateSequentialMemories(), + /*replSeqMem=*/opt.shouldReplaceSequentialMemories(), /*readEnableMode=*/opt.shouldIgnoreReadEnableMemories() ? seq::ReadEnableMode::Ignore : seq::ReadEnableMode::Undefined, From 98287078174c74541cc88f7badb1b37c8b98e27f Mon Sep 17 00:00:00 2001 From: Hideto Ueno Date: Wed, 7 Aug 2024 13:51:14 +0900 Subject: [PATCH 023/119] [Arc][Sim] Lower Sim DPI func to func.func and support dpi call in Arc (#7386) This PR implements initial support for lowering Sim DPI operations to Arc. * sim::LowerDPIFuncPass implements lowering from `sim.dpi.func` to `func.func` that respects C-level ABI. * arc::LowerStatePass is modified to allocate states and call functions for `sim.dpi.call` op. Currently unclocked call is not supported yet. --- include/circt/Dialect/Sim/SimPasses.td | 5 + integration_test/arcilator/JIT/dpi.mlir | 39 ++++ lib/Conversion/ConvertToArcs/CMakeLists.txt | 1 + .../ConvertToArcs/ConvertToArcs.cpp | 3 +- lib/Dialect/Arc/Transforms/CMakeLists.txt | 1 + lib/Dialect/Arc/Transforms/LowerState.cpp | 93 +++++---- lib/Dialect/Sim/CMakeLists.txt | 1 + lib/Dialect/Sim/SimOps.cpp | 10 +- lib/Dialect/Sim/Transforms/CMakeLists.txt | 3 + lib/Dialect/Sim/Transforms/LowerDPIFunc.cpp | 177 ++++++++++++++++++ test/Dialect/Arc/lower-state.mlir | 13 ++ test/Dialect/Sim/lower-dpi.mlir | 50 +++++ test/Dialect/Sim/round-trip.mlir | 9 +- test/Dialect/Sim/sim-errors.mlir | 9 + tools/arcilator/CMakeLists.txt | 1 + tools/arcilator/arcilator.cpp | 4 + 16 files changed, 378 insertions(+), 41 deletions(-) create mode 100644 integration_test/arcilator/JIT/dpi.mlir create mode 100644 lib/Dialect/Sim/Transforms/LowerDPIFunc.cpp create mode 100644 test/Dialect/Sim/lower-dpi.mlir diff --git a/include/circt/Dialect/Sim/SimPasses.td b/include/circt/Dialect/Sim/SimPasses.td index ae178e8fd42b..b2e6d8e98e23 100644 --- a/include/circt/Dialect/Sim/SimPasses.td +++ b/include/circt/Dialect/Sim/SimPasses.td @@ -23,4 +23,9 @@ def ProceduralizeSim : Pass<"sim-proceduralize", "hw::HWModuleOp"> { let dependentDialects = ["circt::hw::HWDialect, circt::seq::SeqDialect, mlir::scf::SCFDialect"]; } +def LowerDPIFunc : Pass<"sim-lower-dpi-func", "mlir::ModuleOp"> { + let summary = "Lower sim.dpi.func into func.func for the simulation flow"; + let dependentDialects = ["mlir::func::FuncDialect", "mlir::LLVM::LLVMDialect"]; +} + #endif // CIRCT_DIALECT_SIM_SEQPASSES diff --git a/integration_test/arcilator/JIT/dpi.mlir b/integration_test/arcilator/JIT/dpi.mlir new file mode 100644 index 000000000000..8d3d5406ff1b --- /dev/null +++ b/integration_test/arcilator/JIT/dpi.mlir @@ -0,0 +1,39 @@ +// RUN: arcilator %s --run --jit-entry=main | FileCheck %s +// REQUIRES: arcilator-jit + +// CHECK: c = 0 +// CHECK-NEXT: c = 5 +sim.func.dpi @dpi(in %a : i32, in %b : i32, out c : i32) attributes {verilogName = "adder_func"} +func.func @adder_func(%arg0: i32, %arg1: i32, %arg2: !llvm.ptr) { + %0 = arith.addi %arg0, %arg1 : i32 + llvm.store %0, %arg2 : i32, !llvm.ptr + return +} +hw.module @adder(in %clock : i1, in %a : i32, in %b : i32, out c : i32) { + %seq_clk = seq.to_clock %clock + + %0 = sim.func.dpi.call @dpi(%a, %b) clock %seq_clk : (i32, i32) -> i32 + hw.output %0 : i32 +} +func.func @main() { + %c2_i32 = arith.constant 2 : i32 + %c3_i32 = arith.constant 3 : i32 + %one = arith.constant 1 : i1 + %zero = arith.constant 0 : i1 + arc.sim.instantiate @adder as %arg0 { + arc.sim.set_input %arg0, "a" = %c2_i32 : i32, !arc.sim.instance<@adder> + arc.sim.set_input %arg0, "b" = %c3_i32 : i32, !arc.sim.instance<@adder> + arc.sim.set_input %arg0, "clock" = %one : i1, !arc.sim.instance<@adder> + + arc.sim.step %arg0 : !arc.sim.instance<@adder> + arc.sim.set_input %arg0, "clock" = %zero : i1, !arc.sim.instance<@adder> + %0 = arc.sim.get_port %arg0, "c" : i32, !arc.sim.instance<@adder> + arc.sim.emit "c", %0 : i32 + + arc.sim.step %arg0 : !arc.sim.instance<@adder> + arc.sim.set_input %arg0, "clock" = %one : i1, !arc.sim.instance<@adder> + %2 = arc.sim.get_port %arg0, "c" : i32, !arc.sim.instance<@adder> + arc.sim.emit "c", %2 : i32 + } + return +} diff --git a/lib/Conversion/ConvertToArcs/CMakeLists.txt b/lib/Conversion/ConvertToArcs/CMakeLists.txt index 23e318eb6bf2..611b7bfb22ec 100644 --- a/lib/Conversion/ConvertToArcs/CMakeLists.txt +++ b/lib/Conversion/ConvertToArcs/CMakeLists.txt @@ -11,5 +11,6 @@ add_circt_conversion_library(CIRCTConvertToArcs CIRCTArc CIRCTHW CIRCTSeq + CIRCTSim MLIRTransforms ) diff --git a/lib/Conversion/ConvertToArcs/ConvertToArcs.cpp b/lib/Conversion/ConvertToArcs/ConvertToArcs.cpp index b07ebe3285fd..14d01aa843a6 100644 --- a/lib/Conversion/ConvertToArcs/ConvertToArcs.cpp +++ b/lib/Conversion/ConvertToArcs/ConvertToArcs.cpp @@ -10,6 +10,7 @@ #include "circt/Dialect/Arc/ArcOps.h" #include "circt/Dialect/HW/HWOps.h" #include "circt/Dialect/Seq/SeqOps.h" +#include "circt/Dialect/Sim/SimOps.h" #include "circt/Support/Namespace.h" #include "mlir/IR/PatternMatch.h" #include "mlir/Pass/Pass.h" @@ -25,7 +26,7 @@ using llvm::MapVector; static bool isArcBreakingOp(Operation *op) { return op->hasTrait() || isa(op) || + seq::ClockGateOp, sim::DPICallOp>(op) || op->getNumResults() > 1; } diff --git a/lib/Dialect/Arc/Transforms/CMakeLists.txt b/lib/Dialect/Arc/Transforms/CMakeLists.txt index fc8296c9dc64..ea3892cefb7e 100644 --- a/lib/Dialect/Arc/Transforms/CMakeLists.txt +++ b/lib/Dialect/Arc/Transforms/CMakeLists.txt @@ -35,6 +35,7 @@ add_circt_dialect_library(CIRCTArcTransforms CIRCTOM CIRCTSV CIRCTSeq + CIRCTSim CIRCTSupport MLIRFuncDialect MLIRLLVMDialect diff --git a/lib/Dialect/Arc/Transforms/LowerState.cpp b/lib/Dialect/Arc/Transforms/LowerState.cpp index 0e04ab078419..7f5025b8e457 100644 --- a/lib/Dialect/Arc/Transforms/LowerState.cpp +++ b/lib/Dialect/Arc/Transforms/LowerState.cpp @@ -11,6 +11,7 @@ #include "circt/Dialect/Comb/CombOps.h" #include "circt/Dialect/HW/HWOps.h" #include "circt/Dialect/Seq/SeqOps.h" +#include "circt/Dialect/Sim/SimOps.h" #include "circt/Support/BackedgeBuilder.h" #include "mlir/Analysis/TopologicalSortUtils.h" #include "mlir/Dialect/Func/IR/FuncOps.h" @@ -117,7 +118,12 @@ struct ModuleLowering { LogicalResult lowerPrimaryInputs(); LogicalResult lowerPrimaryOutputs(); LogicalResult lowerStates(); + template + LogicalResult lowerStateLike(Operation *op, Value clock, Value enable, + Value reset, ArrayRef inputs, + FlatSymbolRefAttr callee); LogicalResult lowerState(StateOp stateOp); + LogicalResult lowerState(sim::DPICallOp dpiCallOp); LogicalResult lowerState(MemoryOp memOp); LogicalResult lowerState(MemoryWritePortOp memWriteOp); LogicalResult lowerState(TapOp tapOp); @@ -139,7 +145,7 @@ static bool shouldMaterialize(Operation *op) { return !isa(op); + StateOp, sim::DPICallOp>(op); } static bool shouldMaterialize(Value value) { @@ -390,53 +396,48 @@ LogicalResult ModuleLowering::lowerPrimaryOutputs() { LogicalResult ModuleLowering::lowerStates() { SmallVector opsToLower; for (auto &op : *moduleOp.getBodyBlock()) - if (isa(&op)) + if (isa(&op)) opsToLower.push_back(&op); for (auto *op : opsToLower) { LLVM_DEBUG(llvm::dbgs() << "- Lowering " << *op << "\n"); - auto result = TypeSwitch(op) - .Case( - [&](auto op) { return lowerState(op); }) - .Default(success()); + auto result = + TypeSwitch(op) + .Case( + [&](auto op) { return lowerState(op); }) + .Default(success()); if (failed(result)) return failure(); } return success(); } -LogicalResult ModuleLowering::lowerState(StateOp stateOp) { - // We don't support arcs beyond latency 1 yet. These should be easy to add in - // the future though. - if (stateOp.getLatency() > 1) - return stateOp.emitError("state with latency > 1 not supported"); - - // Grab all operands from the state op and make it drop all its references. - // This allows `materializeValue` to move an operation if this state was the - // last user. - auto stateClock = stateOp.getClock(); - auto stateEnable = stateOp.getEnable(); - auto stateReset = stateOp.getReset(); - auto stateInputs = SmallVector(stateOp.getInputs()); +template +LogicalResult ModuleLowering::lowerStateLike( + Operation *stateOp, Value stateClock, Value stateEnable, Value stateReset, + ArrayRef stateInputs, FlatSymbolRefAttr callee) { + // Grab all operands from the state op at the callsite and make it drop all + // its references. This allows `materializeValue` to move an operation if this + // state was the last user. // Get the clock tree and enable condition for this state's clock. If this arc // carries an explicit enable condition, fold that into the enable provided by // the clock gates in the arc's clock tree. auto info = getOrCreateClockLowering(stateClock); info.enable = info.clock.getOrCreateAnd( - info.enable, info.clock.materializeValue(stateEnable), stateOp.getLoc()); + info.enable, info.clock.materializeValue(stateEnable), stateOp->getLoc()); // Allocate the necessary state within the model. SmallVector allocatedStates; - for (unsigned stateIdx = 0; stateIdx < stateOp.getNumResults(); ++stateIdx) { - auto type = stateOp.getResult(stateIdx).getType(); + for (unsigned stateIdx = 0; stateIdx < stateOp->getNumResults(); ++stateIdx) { + auto type = stateOp->getResult(stateIdx).getType(); auto intType = dyn_cast(type); if (!intType) - return stateOp.emitOpError("result ") + return stateOp->emitOpError("result ") << stateIdx << " has non-integer type " << type << "; only integer types are supported"; auto stateType = StateType::get(intType); - auto state = stateBuilder.create(stateOp.getLoc(), stateType, + auto state = stateBuilder.create(stateOp->getLoc(), stateType, storageArg); if (auto names = stateOp->getAttrOfType("names")) state->setAttr("name", names[stateIdx]); @@ -455,18 +456,18 @@ LogicalResult ModuleLowering::lowerState(StateOp stateOp) { OpBuilder nonResetBuilder = info.clock.builder; if (stateReset) { auto materializedReset = info.clock.materializeValue(stateReset); - auto ifOp = info.clock.builder.create(stateOp.getLoc(), + auto ifOp = info.clock.builder.create(stateOp->getLoc(), materializedReset, true); for (auto [alloc, resTy] : - llvm::zip(allocatedStates, stateOp.getResultTypes())) { + llvm::zip(allocatedStates, stateOp->getResultTypes())) { if (!isa(resTy)) stateOp->emitOpError("Non-integer result not supported yet!"); auto thenBuilder = ifOp.getThenBodyBuilder(); Value constZero = - thenBuilder.create(stateOp.getLoc(), resTy, 0); - thenBuilder.create(stateOp.getLoc(), alloc, constZero, + thenBuilder.create(stateOp->getLoc(), resTy, 0); + thenBuilder.create(stateOp->getLoc(), alloc, constZero, Value()); } @@ -475,24 +476,50 @@ LogicalResult ModuleLowering::lowerState(StateOp stateOp) { stateOp->dropAllReferences(); - auto newStateOp = nonResetBuilder.create( - stateOp.getLoc(), stateOp.getResultTypes(), stateOp.getArcAttr(), + auto newStateOp = nonResetBuilder.create( + stateOp->getLoc(), stateOp->getResultTypes(), callee, materializedOperands); // Create the write ops that write the result of the transfer function to the // allocated state storage. for (auto [alloc, result] : llvm::zip(allocatedStates, newStateOp.getResults())) - nonResetBuilder.create(stateOp.getLoc(), alloc, result, + nonResetBuilder.create(stateOp->getLoc(), alloc, result, info.enable); // Replace all uses of the arc with reads from the allocated state. - for (auto [alloc, result] : llvm::zip(allocatedStates, stateOp.getResults())) + for (auto [alloc, result] : llvm::zip(allocatedStates, stateOp->getResults())) replaceValueWithStateRead(result, alloc); - stateOp.erase(); + stateOp->erase(); return success(); } +LogicalResult ModuleLowering::lowerState(StateOp stateOp) { + // We don't support arcs beyond latency 1 yet. These should be easy to add in + // the future though. + if (stateOp.getLatency() > 1) + return stateOp.emitError("state with latency > 1 not supported"); + + auto stateInputs = SmallVector(stateOp.getInputs()); + + return lowerStateLike(stateOp, stateOp.getClock(), + stateOp.getEnable(), stateOp.getReset(), + stateInputs, stateOp.getArcAttr()); +} + +LogicalResult ModuleLowering::lowerState(sim::DPICallOp callOp) { + // Clocked call op can be considered as arc state with single latency. + auto stateClock = callOp.getClock(); + if (!stateClock) + return callOp.emitError("unclocked DPI call not implemented yet"); + + auto stateInputs = SmallVector(callOp.getInputs()); + + return lowerStateLike(callOp, stateClock, callOp.getEnable(), + Value(), stateInputs, + callOp.getCalleeAttr()); +} + LogicalResult ModuleLowering::lowerState(MemoryOp memOp) { auto allocMemOp = stateBuilder.create( memOp.getLoc(), memOp.getType(), storageArg, memOp->getAttrs()); diff --git a/lib/Dialect/Sim/CMakeLists.txt b/lib/Dialect/Sim/CMakeLists.txt index db239ddd2652..395e7216dfc8 100644 --- a/lib/Dialect/Sim/CMakeLists.txt +++ b/lib/Dialect/Sim/CMakeLists.txt @@ -29,6 +29,7 @@ add_circt_dialect_library(CIRCTSim CIRCTHW CIRCTSeq CIRCTSV + MLIRFuncDialect MLIRIR MLIRPass MLIRTransforms diff --git a/lib/Dialect/Sim/SimOps.cpp b/lib/Dialect/Sim/SimOps.cpp index 8d7c3c96c805..d0e304fa85be 100644 --- a/lib/Dialect/Sim/SimOps.cpp +++ b/lib/Dialect/Sim/SimOps.cpp @@ -13,6 +13,7 @@ #include "circt/Dialect/Sim/SimOps.h" #include "circt/Dialect/HW/ModuleImplementation.h" #include "circt/Dialect/SV/SVOps.h" +#include "mlir/Dialect/Func/IR/FuncOps.h" #include "mlir/IR/PatternMatch.h" #include "mlir/Interfaces/FunctionImplementation.h" @@ -69,12 +70,15 @@ ParseResult DPIFuncOp::parse(OpAsmParser &parser, OperationState &result) { LogicalResult sim::DPICallOp::verifySymbolUses(SymbolTableCollection &symbolTable) { - auto referencedOp = dyn_cast_or_null( - symbolTable.lookupNearestSymbolFrom(*this, getCalleeAttr())); + auto referencedOp = + symbolTable.lookupNearestSymbolFrom(*this, getCalleeAttr()); if (!referencedOp) return emitError("cannot find function declaration '") << getCallee() << "'"; - return success(); + if (isa(referencedOp)) + return success(); + return emitError("callee must be 'sim.dpi.func' or 'func.func' but got '") + << referencedOp->getName() << "'"; } void DPIFuncOp::print(OpAsmPrinter &p) { diff --git a/lib/Dialect/Sim/Transforms/CMakeLists.txt b/lib/Dialect/Sim/Transforms/CMakeLists.txt index 6802452cca59..d6caf2ddc26f 100644 --- a/lib/Dialect/Sim/Transforms/CMakeLists.txt +++ b/lib/Dialect/Sim/Transforms/CMakeLists.txt @@ -1,4 +1,5 @@ add_circt_dialect_library(CIRCTSimTransforms + LowerDPIFunc.cpp ProceduralizeSim.cpp @@ -12,8 +13,10 @@ add_circt_dialect_library(CIRCTSimTransforms CIRCTSV CIRCTComb CIRCTSupport + MLIRFuncDialect MLIRIR MLIRPass + MLIRLLVMDialect MLIRSCFDialect MLIRTransformUtils ) diff --git a/lib/Dialect/Sim/Transforms/LowerDPIFunc.cpp b/lib/Dialect/Sim/Transforms/LowerDPIFunc.cpp new file mode 100644 index 000000000000..dd65b2bcaa8a --- /dev/null +++ b/lib/Dialect/Sim/Transforms/LowerDPIFunc.cpp @@ -0,0 +1,177 @@ +//===- LowerDPIFunc.cpp - Lower sim.dpi.func to func.func ----*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------=== +// +// This pass lowers Sim DPI func ops to MLIR func and call. +// +// sim.dpi.func @foo(input %a: i32, output %b: i64) +// hw.module @top (..) { +// %result = sim.dpi.call @foo(%a) clock %clock +// } +// +// -> +// +// func.func @foo(%a: i32, %b: !llvm.ptr) // Output is passed by a reference. +// func.func @foo_wrapper(%a: i32) -> (i64) { +// %0 = llvm.alloca: !llvm.ptr +// %v = func.call @foo (%a, %0) +// func.return %v +// } +// hw.module @mod(..) { +// %result = sim.dpi.call @foo_wrapper(%a) clock %clock +// } +//===----------------------------------------------------------------------===// + +#include "circt/Dialect/Sim/SimOps.h" +#include "circt/Dialect/Sim/SimPasses.h" +#include "circt/Support/Namespace.h" +#include "mlir/Dialect/Func/IR/FuncOps.h" +#include "mlir/Dialect/LLVMIR/LLVMDialect.h" +#include "mlir/Dialect/LLVMIR/LLVMTypes.h" +#include "mlir/Transforms/DialectConversion.h" +#include "llvm/Support/Debug.h" + +#define DEBUG_TYPE "sim-lower-dpi-func" + +namespace circt { +namespace sim { +#define GEN_PASS_DEF_LOWERDPIFUNC +#include "circt/Dialect/Sim/SimPasses.h.inc" +} // namespace sim +} // namespace circt + +using namespace mlir; +using namespace circt; + +//===----------------------------------------------------------------------===// +// Pass Implementation +//===----------------------------------------------------------------------===// + +namespace { + +struct LoweringState { + DenseMap dpiFuncDeclMapping; + circt::Namespace nameSpace; +}; + +struct LowerDPIFuncPass : public sim::impl::LowerDPIFuncBase { + + LogicalResult lowerDPI(); + LogicalResult lowerDPIFuncOp(sim::DPIFuncOp simFunc, + LoweringState &loweringState, + SymbolTable &symbolTable); + void runOnOperation() override; +}; + +} // namespace + +LogicalResult LowerDPIFuncPass::lowerDPIFuncOp(sim::DPIFuncOp simFunc, + LoweringState &loweringState, + SymbolTable &symbolTable) { + ImplicitLocOpBuilder builder(simFunc.getLoc(), simFunc); + auto moduleType = simFunc.getModuleType(); + + llvm::SmallVector dpiFunctionArgumentTypes; + for (auto arg : moduleType.getPorts()) { + // TODO: Support a non-integer type. + if (!arg.type.isInteger()) + return simFunc->emitError() + << "non-integer type argument is unsupported now"; + + if (arg.dir == hw::ModulePort::Input) + dpiFunctionArgumentTypes.push_back(arg.type); + else + // Output must be passed by a reference. + dpiFunctionArgumentTypes.push_back( + LLVM::LLVMPointerType::get(arg.type.getContext())); + } + + auto funcType = builder.getFunctionType(dpiFunctionArgumentTypes, {}); + func::FuncOp func; + + // Look up func.func by verilog name since the function name is equal to the + // symbol name in MLIR + if (auto verilogName = simFunc.getVerilogName()) { + func = symbolTable.lookup(*verilogName); + // TODO: Check if function type matches. + } + + // If a referred function is not in the same module, create an external + // function declaration. + if (!func) { + func = builder.create(simFunc.getVerilogName() + ? *simFunc.getVerilogName() + : simFunc.getSymName(), + funcType); + // External function needs to be private. + func.setPrivate(); + } + + // Create a wrapper module that calls a DPI function. + auto funcOp = builder.create( + loweringState.nameSpace.newName(simFunc.getSymName() + "_wrapper"), + moduleType.getFuncType()); + + // Map old symbol to a new func op. + loweringState.dpiFuncDeclMapping[simFunc.getSymNameAttr()] = funcOp; + + builder.setInsertionPointToStart(funcOp.addEntryBlock()); + SmallVector functionInputs; + SmallVector functionOutputAllocas; + + size_t inputIndex = 0; + for (auto arg : moduleType.getPorts()) { + if (arg.dir == hw::ModulePort::InOut) + return funcOp->emitError() << "inout is currently not supported"; + + if (arg.dir == hw::ModulePort::Input) { + functionInputs.push_back(funcOp.getArgument(inputIndex)); + ++inputIndex; + } else { + // Allocate an output placeholder. + auto one = builder.create(builder.getI64IntegerAttr(1)); + auto alloca = builder.create( + builder.getType(), arg.type, one); + functionInputs.push_back(alloca); + functionOutputAllocas.push_back(alloca); + } + } + + builder.create(func, functionInputs); + + SmallVector results; + for (auto functionOutputAlloca : functionOutputAllocas) + results.push_back(builder.create( + functionOutputAlloca.getElemType(), functionOutputAlloca)); + + builder.create(results); + + simFunc.erase(); + return success(); +} + +LogicalResult LowerDPIFuncPass::lowerDPI() { + LLVM_DEBUG(llvm::dbgs() << "Lowering sim DPI func to func.func\n"); + auto op = getOperation(); + LoweringState state; + state.nameSpace.add(op); + auto &symbolTable = getAnalysis(); + for (auto simFunc : llvm::make_early_inc_range(op.getOps())) + if (failed(lowerDPIFuncOp(simFunc, state, symbolTable))) + return failure(); + + op.walk([&](sim::DPICallOp op) { + auto func = state.dpiFuncDeclMapping.at(op.getCalleeAttr().getAttr()); + op.setCallee(func.getSymNameAttr()); + }); + return success(); +} + +void LowerDPIFuncPass::runOnOperation() { + if (failed(lowerDPI())) + return signalPassFailure(); +} diff --git a/test/Dialect/Arc/lower-state.mlir b/test/Dialect/Arc/lower-state.mlir index 0538d1e4aae4..b312bb115c8d 100644 --- a/test/Dialect/Arc/lower-state.mlir +++ b/test/Dialect/Arc/lower-state.mlir @@ -353,3 +353,16 @@ hw.module @BlackBox(in %clk: !seq.clock) { } // CHECK-NOT: hw.module.extern private @BlackBoxExt hw.module.extern private @BlackBoxExt(in %a: i42, in %b: i42, out c: i42, out d: i42) + + +func.func private @func(%arg0: i32, %arg1: i32) -> i32 +// CHECK-LABEL: arc.model @adder +hw.module @adder(in %clock : i1, in %a : i32, in %b : i32, out c : i32) { + %0 = seq.to_clock %clock + %1 = sim.func.dpi.call @func(%a, %b) clock %0 : (i32, i32) -> i32 + // CHECK: arc.clock_tree + // CHECK-NEXT: %[[A:.+]] = arc.state_read %in_a : + // CHECK-NEXT: %[[B:.+]] = arc.state_read %in_b : + // CHECK-NEXT: %[[RESULT:.+]] = func.call @func(%6, %7) : (i32, i32) -> i32 + hw.output %1 : i32 +} diff --git a/test/Dialect/Sim/lower-dpi.mlir b/test/Dialect/Sim/lower-dpi.mlir new file mode 100644 index 000000000000..dc9211f126dd --- /dev/null +++ b/test/Dialect/Sim/lower-dpi.mlir @@ -0,0 +1,50 @@ +// RUN: circt-opt --sim-lower-dpi-func %s | FileCheck %s + +sim.func.dpi @foo(out arg0: i32, in %arg1: i32, out arg2: i32) +// CHECK-LABEL: func.func private @foo(!llvm.ptr, i32, !llvm.ptr) +// CHECK-LABEL: func.func @foo_wrapper(%arg0: i32) -> (i32, i32) { +// CHECK-NEXT: %0 = llvm.mlir.constant(1 : i64) : i64 +// CHECK-NEXT: %1 = llvm.alloca %0 x i32 : (i64) -> !llvm.ptr +// CHECK-NEXT: %2 = llvm.mlir.constant(1 : i64) : i64 +// CHECK-NEXT: %3 = llvm.alloca %2 x i32 : (i64) -> !llvm.ptr +// CHECK-NEXT: call @foo(%1, %arg0, %3) : (!llvm.ptr, i32, !llvm.ptr) -> () +// CHECK-NEXT: %4 = llvm.load %1 : !llvm.ptr -> i32 +// CHECK-NEXT: %5 = llvm.load %3 : !llvm.ptr -> i32 +// CHECK-NEXT: return %4, %5 : i32, i32 +// CHECK-NEXT: } + +// CHECK-LABEL: func.func @bar_wrapper(%arg0: i32) -> (i32, i32) { +// CHECK-NEXT: %0 = llvm.mlir.constant(1 : i64) : i64 +// CHECK-NEXT: %1 = llvm.alloca %0 x i32 : (i64) -> !llvm.ptr +// CHECK-NEXT: %2 = llvm.mlir.constant(1 : i64) : i64 +// CHECK-NEXT: %3 = llvm.alloca %2 x i32 : (i64) -> !llvm.ptr +// CHECK-NEXT: call @bar_c_name(%1, %arg0, %3) : (!llvm.ptr, i32, !llvm.ptr) -> () +// CHECK-NEXT: %4 = llvm.load %1 : !llvm.ptr -> i32 +// CHECK-NEXT: %5 = llvm.load %3 : !llvm.ptr -> i32 +// CHECK-NEXT: return %4, %5 : i32, i32 +// CHECK-NEXT: } +// CHECK-LABEL: func.func @bar_c_name + +sim.func.dpi @bar(out arg0: i32, in %arg1: i32, out arg2: i32) attributes {verilogName="bar_c_name"} +func.func @bar_c_name(%arg0: !llvm.ptr, %arg1: i32, %arg2: !llvm.ptr) { + func.return +} + +// CHECK-LABEL: func.func private @baz_c_name(!llvm.ptr, i32, !llvm.ptr) +// CHECK-LABEL: func.func @baz_wrapper(%arg0: i32) -> (i32, i32) +// CHECK: call @baz_c_name(%1, %arg0, %3) : (!llvm.ptr, i32, !llvm.ptr) -> () +sim.func.dpi @baz(out arg0: i32, in %arg1: i32, out arg2: i32) attributes {verilogName="baz_c_name"} + +// CHECK-LABEL: hw.module @dpi_call +hw.module @dpi_call(in %clock : !seq.clock, in %enable : i1, in %in: i32, + out o1: i32, out o2: i32, out o3: i32, out o4: i32, out o5: i32, out o6: i32) { + // CHECK-NEXT: %0:2 = sim.func.dpi.call @foo_wrapper(%in) clock %clock : (i32) -> (i32, i32) + // CHECK-NEXT: %1:2 = sim.func.dpi.call @bar_wrapper(%in) : (i32) -> (i32, i32) + // CHECK-NEXT: %2:2 = sim.func.dpi.call @baz_wrapper(%in) : (i32) -> (i32, i32) + // CHECK-NEXT: hw.output %0#0, %0#1, %1#0, %1#1, %2#0, %2#1 : i32, i32, i32, i32, i32, i32 + %0, %1 = sim.func.dpi.call @foo(%in) clock %clock : (i32) -> (i32, i32) + %2, %3 = sim.func.dpi.call @bar(%in) : (i32) -> (i32, i32) + %4, %5 = sim.func.dpi.call @baz(%in) : (i32) -> (i32, i32) + + hw.output %0, %1, %2, %3, %4, %5 : i32, i32, i32, i32, i32, i32 +} diff --git a/test/Dialect/Sim/round-trip.mlir b/test/Dialect/Sim/round-trip.mlir index a98b7242decf..bbbe07d4d931 100644 --- a/test/Dialect/Sim/round-trip.mlir +++ b/test/Dialect/Sim/round-trip.mlir @@ -19,14 +19,15 @@ hw.module @stop_finish(in %clock : !seq.clock, in %cond : i1) { // CHECK-LABEL: sim.func.dpi @dpi(out arg0 : i1, in %arg1 : i1, out arg2 : i1) sim.func.dpi @dpi(out arg0: i1, in %arg1: i1, out arg2: i1) +func.func private @func(%arg1: i1) -> (i1, i1) hw.module @dpi_call(in %clock : !seq.clock, in %enable : i1, in %in: i1) { // CHECK: sim.func.dpi.call @dpi(%in) clock %clock enable %enable : (i1) -> (i1, i1) %0, %1 = sim.func.dpi.call @dpi(%in) clock %clock enable %enable: (i1) -> (i1, i1) // CHECK: sim.func.dpi.call @dpi(%in) clock %clock : (i1) -> (i1, i1) %2, %3 = sim.func.dpi.call @dpi(%in) clock %clock : (i1) -> (i1, i1) - // CHECK: sim.func.dpi.call @dpi(%in) enable %enable : (i1) -> (i1, i1) - %4, %5 = sim.func.dpi.call @dpi(%in) enable %enable : (i1) -> (i1, i1) - // CHECK: sim.func.dpi.call @dpi(%in) : (i1) -> (i1, i1) - %6, %7 = sim.func.dpi.call @dpi(%in) : (i1) -> (i1, i1) + // CHECK: sim.func.dpi.call @func(%in) enable %enable : (i1) -> (i1, i1) + %4, %5 = sim.func.dpi.call @func(%in) enable %enable : (i1) -> (i1, i1) + // CHECK: sim.func.dpi.call @func(%in) : (i1) -> (i1, i1) + %6, %7 = sim.func.dpi.call @func(%in) : (i1) -> (i1, i1) } diff --git a/test/Dialect/Sim/sim-errors.mlir b/test/Dialect/Sim/sim-errors.mlir index 393636f0ce28..a70360b1cdfe 100644 --- a/test/Dialect/Sim/sim-errors.mlir +++ b/test/Dialect/Sim/sim-errors.mlir @@ -42,3 +42,12 @@ hw.module @proc_print_sv() { sim.proc.print %lit } } + +// ----- + +hw.module.extern @non_func(out arg0: i1, in %arg1: i1, out arg2: i1) + +hw.module @dpi_call(in %clock : !seq.clock, in %in: i1) { + // expected-error @below {{callee must be 'sim.dpi.func' or 'func.func' but got 'hw.module.extern'}} + %0, %1 = sim.func.dpi.call @non_func(%in) : (i1) -> (i1, i1) +} diff --git a/tools/arcilator/CMakeLists.txt b/tools/arcilator/CMakeLists.txt index 1257bc173107..7293e698aa9d 100644 --- a/tools/arcilator/CMakeLists.txt +++ b/tools/arcilator/CMakeLists.txt @@ -20,6 +20,7 @@ target_link_libraries(arcilator CIRCTOM CIRCTSeqToSV CIRCTSeqTransforms + CIRCTSimTransforms CIRCTSupport CIRCTTransforms MLIRBuiltinToLLVMIRTranslation diff --git a/tools/arcilator/arcilator.cpp b/tools/arcilator/arcilator.cpp index 1348e5a979cf..0c7f5c384d5b 100644 --- a/tools/arcilator/arcilator.cpp +++ b/tools/arcilator/arcilator.cpp @@ -22,6 +22,8 @@ #include "circt/Dialect/Emit/EmitDialect.h" #include "circt/Dialect/HW/HWPasses.h" #include "circt/Dialect/Seq/SeqPasses.h" +#include "circt/Dialect/Sim/SimDialect.h" +#include "circt/Dialect/Sim/SimPasses.h" #include "circt/InitAllDialects.h" #include "circt/InitAllPasses.h" #include "circt/Support/Passes.h" @@ -249,6 +251,7 @@ static void populateHwModuleToArcPipeline(PassManager &pm) { opts.tapMemories = observeMemories; pm.addPass(arc::createInferMemoriesPass(opts)); } + pm.addPass(sim::createLowerDPIFunc()); pm.addPass(createCSEPass()); pm.addPass(arc::createArcCanonicalizerPass()); @@ -567,6 +570,7 @@ static LogicalResult executeArcilator(MLIRContext &context) { mlir::scf::SCFDialect, om::OMDialect, seq::SeqDialect, + sim::SimDialect, sv::SVDialect >(); // clang-format on From 4c215e8ab8d43dd1269361127b4da8837fb976bf Mon Sep 17 00:00:00 2001 From: Hideto Ueno Date: Wed, 7 Aug 2024 21:16:12 +0900 Subject: [PATCH 024/119] [arcilator] Add an option to load shared lib into JIT engine (#7453) This PR adds an option `--shared-libs` to load shared lib in the JIT engine in a similar way to what circt-lec does for loading z3 shared lib. With this change DPI library could be linked in arcilator. --- integration_test/arcilator/JIT/dpi.mlir | 48 ++++++++++++++++--------- integration_test/lit.cfg.py | 3 ++ tools/arcilator/arcilator.cpp | 8 +++++ 3 files changed, 43 insertions(+), 16 deletions(-) diff --git a/integration_test/arcilator/JIT/dpi.mlir b/integration_test/arcilator/JIT/dpi.mlir index 8d3d5406ff1b..bdc3b32d80dc 100644 --- a/integration_test/arcilator/JIT/dpi.mlir +++ b/integration_test/arcilator/JIT/dpi.mlir @@ -1,39 +1,55 @@ -// RUN: arcilator %s --run --jit-entry=main | FileCheck %s +// RUN: split-file %s %t +// RUN: %host_cc %t/shared_lib.c --shared -o %t/shared_lib.so +// RUN: arcilator %t/dpi.mlir --run --jit-entry=main --shared-libs=%t/shared_lib.so | FileCheck %s // REQUIRES: arcilator-jit + +//--- shared_lib.c +void mul_shared(int a, int b, int *result) { *result = a * b; } + +//--- dpi.mlir // CHECK: c = 0 +// CHECK-NEXT: d = 0 // CHECK-NEXT: c = 5 -sim.func.dpi @dpi(in %a : i32, in %b : i32, out c : i32) attributes {verilogName = "adder_func"} -func.func @adder_func(%arg0: i32, %arg1: i32, %arg2: !llvm.ptr) { +// CHECK-NEXT: d = 6 +sim.func.dpi @mul_shared(in %a : i32, in %b : i32, out c : i32) +sim.func.dpi @add_mlir(in %a : i32, in %b : i32, out c : i32) attributes {verilogName = "add_mlir_impl"} +func.func @add_mlir_impl(%arg0: i32, %arg1: i32, %arg2: !llvm.ptr) { %0 = arith.addi %arg0, %arg1 : i32 llvm.store %0, %arg2 : i32, !llvm.ptr return } -hw.module @adder(in %clock : i1, in %a : i32, in %b : i32, out c : i32) { +hw.module @arith(in %clock : i1, in %a : i32, in %b : i32, out c : i32, out d : i32) { %seq_clk = seq.to_clock %clock - %0 = sim.func.dpi.call @dpi(%a, %b) clock %seq_clk : (i32, i32) -> i32 - hw.output %0 : i32 + %0 = sim.func.dpi.call @add_mlir(%a, %b) clock %seq_clk : (i32, i32) -> i32 + %1 = sim.func.dpi.call @mul_shared(%a, %b) clock %seq_clk : (i32, i32) -> i32 + hw.output %0, %1 : i32, i32 } func.func @main() { %c2_i32 = arith.constant 2 : i32 %c3_i32 = arith.constant 3 : i32 %one = arith.constant 1 : i1 %zero = arith.constant 0 : i1 - arc.sim.instantiate @adder as %arg0 { - arc.sim.set_input %arg0, "a" = %c2_i32 : i32, !arc.sim.instance<@adder> - arc.sim.set_input %arg0, "b" = %c3_i32 : i32, !arc.sim.instance<@adder> - arc.sim.set_input %arg0, "clock" = %one : i1, !arc.sim.instance<@adder> + arc.sim.instantiate @arith as %arg0 { + arc.sim.set_input %arg0, "a" = %c2_i32 : i32, !arc.sim.instance<@arith> + arc.sim.set_input %arg0, "b" = %c3_i32 : i32, !arc.sim.instance<@arith> + arc.sim.set_input %arg0, "clock" = %one : i1, !arc.sim.instance<@arith> + + arc.sim.step %arg0 : !arc.sim.instance<@arith> + arc.sim.set_input %arg0, "clock" = %zero : i1, !arc.sim.instance<@arith> + %0 = arc.sim.get_port %arg0, "c" : i32, !arc.sim.instance<@arith> + %1 = arc.sim.get_port %arg0, "d" : i32, !arc.sim.instance<@arith> - arc.sim.step %arg0 : !arc.sim.instance<@adder> - arc.sim.set_input %arg0, "clock" = %zero : i1, !arc.sim.instance<@adder> - %0 = arc.sim.get_port %arg0, "c" : i32, !arc.sim.instance<@adder> arc.sim.emit "c", %0 : i32 + arc.sim.emit "d", %1 : i32 - arc.sim.step %arg0 : !arc.sim.instance<@adder> - arc.sim.set_input %arg0, "clock" = %one : i1, !arc.sim.instance<@adder> - %2 = arc.sim.get_port %arg0, "c" : i32, !arc.sim.instance<@adder> + arc.sim.step %arg0 : !arc.sim.instance<@arith> + arc.sim.set_input %arg0, "clock" = %one : i1, !arc.sim.instance<@arith> + %2 = arc.sim.get_port %arg0, "c" : i32, !arc.sim.instance<@arith> + %3 = arc.sim.get_port %arg0, "d" : i32, !arc.sim.instance<@arith> arc.sim.emit "c", %2 : i32 + arc.sim.emit "d", %3 : i32 } return } diff --git a/integration_test/lit.cfg.py b/integration_test/lit.cfg.py index 94d0ab4c8108..6788cfa8a9f8 100644 --- a/integration_test/lit.cfg.py +++ b/integration_test/lit.cfg.py @@ -187,6 +187,9 @@ if config.bindings_tcl_enabled: config.available_features.add('bindings_tcl') +# Add host c compiler. +config.substitutions.append(("%host_cc", config.host_cc)) + # Enable clang-tidy if it has been detected. if config.clang_tidy_path != "": tool_dirs.append(config.clang_tidy_path) diff --git a/tools/arcilator/arcilator.cpp b/tools/arcilator/arcilator.cpp index 0c7f5c384d5b..7e398cd27ea4 100644 --- a/tools/arcilator/arcilator.cpp +++ b/tools/arcilator/arcilator.cpp @@ -215,6 +215,10 @@ static llvm::cl::opt "simulation to run when output is set to run"), llvm::cl::init("entry"), llvm::cl::cat(mainCategory)); +static llvm::cl::list sharedLibs{ + "shared-libs", llvm::cl::desc("Libraries to link dynamically"), + llvm::cl::MiscFlags::CommaSeparated, llvm::cl::cat(mainCategory)}; + //===----------------------------------------------------------------------===// // Main Tool Logic //===----------------------------------------------------------------------===// @@ -433,11 +437,15 @@ static LogicalResult processBuffer( return failure(); } + SmallVector sharedLibraries(sharedLibs.begin(), + sharedLibs.end()); + mlir::ExecutionEngineOptions engineOptions; engineOptions.jitCodeGenOptLevel = llvm::CodeGenOptLevel::Aggressive; engineOptions.transformer = mlir::makeOptimizingTransformer( /*optLevel=*/3, /*sizeLevel=*/0, /*targetMachine=*/nullptr); + engineOptions.sharedLibPaths = sharedLibraries; auto executionEngine = mlir::ExecutionEngine::create(module.get(), engineOptions); From 30ed753d539e981e5914801231e7a2d1c3710806 Mon Sep 17 00:00:00 2001 From: fzi-hielscher <47524191+fzi-hielscher@users.noreply.github.com> Date: Wed, 7 Aug 2024 16:06:30 +0200 Subject: [PATCH 025/119] [arcilator] Add JIT runtime environment library and stdio hooks (#7445) --- .../arcilator/JIT/runtime-environment.mlir | 40 ++++++++++++++ tools/arcilator/CMakeLists.txt | 9 ++- tools/arcilator/arcilator-runtime.h | 55 +++++++++++++++++++ tools/arcilator/arcilator.cpp | 15 +++++ tools/arcilator/jit-env/CMakeLists.txt | 32 +++++++++++ tools/arcilator/jit-env/arcilator-jit-env.cpp | 20 +++++++ tools/arcilator/jit-env/arcilator-jit-env.h | 34 ++++++++++++ 7 files changed, 204 insertions(+), 1 deletion(-) create mode 100644 integration_test/arcilator/JIT/runtime-environment.mlir create mode 100644 tools/arcilator/jit-env/CMakeLists.txt create mode 100644 tools/arcilator/jit-env/arcilator-jit-env.cpp create mode 100644 tools/arcilator/jit-env/arcilator-jit-env.h diff --git a/integration_test/arcilator/JIT/runtime-environment.mlir b/integration_test/arcilator/JIT/runtime-environment.mlir new file mode 100644 index 000000000000..04ae3e828f14 --- /dev/null +++ b/integration_test/arcilator/JIT/runtime-environment.mlir @@ -0,0 +1,40 @@ +// RUN: arcilator %s --run --jit-entry=main 2>&1 >/dev/null | FileCheck -strict-whitespace %s +// REQUIRES: arcilator-jit + +// CHECK: Hello World!{{[[:space:]]}} + +module { + + llvm.func @_arc_env_get_print_stream(i32) -> !llvm.ptr + llvm.func @_arc_libc_fprintf(!llvm.ptr, !llvm.ptr, ...) -> i32 + llvm.func @_arc_libc_fputc(i32, !llvm.ptr) -> i32 + llvm.func @_arc_libc_fputs(!llvm.ptr, !llvm.ptr) -> i32 + + llvm.mlir.global internal constant @global_hello("He%c%co \00") {addr_space = 0 : i32} + llvm.mlir.global internal constant @global_world("World\00") {addr_space = 0 : i32} + + arc.model @dut io !hw.modty<> { + ^bb0(%arg0: !arc.storage): + %cst0 = llvm.mlir.constant(0 : i32) : i32 + %ascii_em = llvm.mlir.constant(33 : i32) : i32 + %ascii_lf = llvm.mlir.constant(10 : i32) : i32 + %ascii_l = llvm.mlir.constant(108 : i32) : i32 + %stderr = llvm.call @_arc_env_get_print_stream(%cst0) : (i32) -> !llvm.ptr + + %hello = llvm.mlir.addressof @global_hello : !llvm.ptr + %0 = llvm.call @_arc_libc_fprintf(%stderr, %hello, %ascii_l, %ascii_l) vararg(!llvm.func) : (!llvm.ptr, !llvm.ptr, i32, i32) -> i32 + + %world = llvm.mlir.addressof @global_world : !llvm.ptr + %1 = llvm.call @_arc_libc_fputs(%world, %stderr) : (!llvm.ptr, !llvm.ptr) -> i32 + + %2 = llvm.call @_arc_libc_fputc(%ascii_em, %stderr) : (i32, !llvm.ptr) -> i32 + %3 = llvm.call @_arc_libc_fputc(%ascii_lf, %stderr) : (i32, !llvm.ptr) -> i32 + } + + func.func @main() { + arc.sim.instantiate @dut as %arg0 { + arc.sim.step %arg0 : !arc.sim.instance<@dut> + } + return + } +} diff --git a/tools/arcilator/CMakeLists.txt b/tools/arcilator/CMakeLists.txt index 7293e698aa9d..69a7068e1811 100644 --- a/tools/arcilator/CMakeLists.txt +++ b/tools/arcilator/CMakeLists.txt @@ -1,7 +1,8 @@ if(ARCILATOR_JIT_ENABLED) add_compile_definitions(ARCILATOR_ENABLE_JIT) + add_subdirectory(jit-env) set(ARCILATOR_JIT_LLVM_COMPONENTS native) - set(ARCILATOR_JIT_DEPS MLIRExecutionEngine) + set(ARCILATOR_JIT_DEPS MLIRExecutionEngine arc-jit-env) endif() set(LLVM_LINK_COMPONENTS Support ${ARCILATOR_JIT_LLVM_COMPONENTS}) @@ -46,3 +47,9 @@ configure_file(arcilator-runtime.h ${CIRCT_TOOLS_DIR}/arcilator-runtime.h) add_custom_target(arcilator-runtime-header SOURCES ${CIRCT_TOOLS_DIR}/arcilator-runtime.h) + +if(ARCILATOR_JIT_ENABLED) + target_include_directories(arcilator PRIVATE + $ + ) +endif() diff --git a/tools/arcilator/arcilator-runtime.h b/tools/arcilator/arcilator-runtime.h index 396343b8ee03..ee65bebe4a16 100644 --- a/tools/arcilator/arcilator-runtime.h +++ b/tools/arcilator/arcilator-runtime.h @@ -1,12 +1,67 @@ // NOLINTBEGIN #pragma once #include +#include #include +#include #include #include #include #include +// Sanity checks for binary compatibility +#ifdef __BYTE_ORDER__ +#if (__BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__) +#error Unsupported endianess +#endif +#endif +static_assert(sizeof(int) == 4, "Unsupported ABI"); +static_assert(sizeof(long long) == 8, "Unsupported ABI"); + +// --- Exports to the IR --- + +#ifdef _WIN32 +#define ARC_EXPORT extern "C" __declspec(dllexport) +#else +#define ARC_EXPORT extern "C" __attribute__((visibility("default"))) +#endif + +#ifndef ARC_NO_LIBC_EXPORTS + +// libc Adapters +ARC_EXPORT int _arc_libc_fprintf(FILE *stream, const char *format, ...) { + int result; + va_list args; + va_start(args, format); + result = vfprintf(stream, format, args); + va_end(args); + return result; +} + +ARC_EXPORT int _arc_libc_fputs(const char *str, FILE *stream) { + return fputs(str, stream); +} + +ARC_EXPORT int _arc_libc_fputc(int ch, FILE *stream) { + return fputc(ch, stream); +} + +#endif // ARC_NO_LIBC_EXPORTS + +// Runtime Environment calls + +#define ARC_ENV_DECL_GET_PRINT_STREAM(idarg) \ + ARC_EXPORT FILE *_arc_env_get_print_stream(uint32_t idarg) + +#ifndef ARC_NO_DEFAULT_GET_PRINT_STREAM +ARC_ENV_DECL_GET_PRINT_STREAM(id) { + (void)id; + return stderr; +} +#endif // ARC_NO_DEFAULT_GET_PRINT_STREAM + +// ---------------- + struct Signal { const char *name; unsigned offset; diff --git a/tools/arcilator/arcilator.cpp b/tools/arcilator/arcilator.cpp index 7e398cd27ea4..2ecfcbe8c0e8 100644 --- a/tools/arcilator/arcilator.cpp +++ b/tools/arcilator/arcilator.cpp @@ -54,6 +54,7 @@ #include "mlir/Target/LLVMIR/Export.h" #include "mlir/Transforms/GreedyPatternRewriteDriver.h" #include "mlir/Transforms/Passes.h" +#include "llvm/ADT/ScopeExit.h" #include "llvm/IR/LLVMContext.h" #include "llvm/IR/Module.h" #include "llvm/Support/CommandLine.h" @@ -64,6 +65,10 @@ #include "llvm/Support/TargetSelect.h" #include "llvm/Support/ToolOutputFile.h" +#ifdef ARCILATOR_ENABLE_JIT +#include "arcilator-jit-env.h" +#endif + #include using namespace mlir; @@ -437,6 +442,16 @@ static LogicalResult processBuffer( return failure(); } + auto envInitResult = arc_jit_runtime_env_init(); + if (envInitResult != 0) { + llvm::errs() << "Initialization of arcilator runtime library failed (" + << envInitResult << ").\n"; + return failure(); + } + + auto envDeinit = + llvm::make_scope_exit([] { arc_jit_runtime_env_deinit(); }); + SmallVector sharedLibraries(sharedLibs.begin(), sharedLibs.end()); diff --git a/tools/arcilator/jit-env/CMakeLists.txt b/tools/arcilator/jit-env/CMakeLists.txt new file mode 100644 index 000000000000..0b5dbe13577c --- /dev/null +++ b/tools/arcilator/jit-env/CMakeLists.txt @@ -0,0 +1,32 @@ +add_library(arc-jit-env SHARED arcilator-jit-env.cpp) + +if(WIN32) +# Put the DLL into the binary directory so we don't have +# to worry about it not being found. +set_target_properties(arc-jit-env + PROPERTIES + LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib + RUNTIME_OUTPUT_DIRECTORY ${CIRCT_TOOLS_DIR}$<0:> + CXX_VISIBILITY_PRESET "default" +) +install(TARGETS arc-jit-env + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + COMPONENT arc-jit-env +) + +else() + +set_target_properties(arc-jit-env + PROPERTIES + LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib + RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib + CXX_VISIBILITY_PRESET "default" +) +install(TARGETS arc-jit-env + DESTINATION ${CMAKE_INSTALL_LIBDIR} + COMPONENT arc-jit-env +) + +endif() + diff --git a/tools/arcilator/jit-env/arcilator-jit-env.cpp b/tools/arcilator/jit-env/arcilator-jit-env.cpp new file mode 100644 index 000000000000..fa33e3a8f815 --- /dev/null +++ b/tools/arcilator/jit-env/arcilator-jit-env.cpp @@ -0,0 +1,20 @@ +//===- arcilator-jit-env.cpp - Internal arcilator JIT API -----------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// Implementation of the arcilator JIT runtime environment API facing +// the arcilator.cpp. +// +//===----------------------------------------------------------------------===// + +#include "../arcilator-runtime.h" + +#define ARCJITENV_EXPORTS +#include "arcilator-jit-env.h" + +ARCJITENV_API int arc_jit_runtime_env_init(void) { return 0; } +ARCJITENV_API void arc_jit_runtime_env_deinit(void) { return; } diff --git a/tools/arcilator/jit-env/arcilator-jit-env.h b/tools/arcilator/jit-env/arcilator-jit-env.h new file mode 100644 index 000000000000..f3dab859539d --- /dev/null +++ b/tools/arcilator/jit-env/arcilator-jit-env.h @@ -0,0 +1,34 @@ +//===- arcilator-jit-env.h - Internal arcilator JIT API -------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// Declarations of the JIT runtime environment API facing the arcilator.cpp. +// +//===----------------------------------------------------------------------===// + +#pragma once + +#ifdef _WIN32 + +#ifdef ARCJITENV_EXPORTS +#define ARCJITENV_API extern "C" __declspec(dllexport) +#else +#define ARCJITENV_API extern "C" __declspec(dllimport) +#endif // ARCJITENV_EXPORTS + +#else + +#define ARCJITENV_API extern "C" __attribute__((visibility("default"))) + +#endif // _WIN32 + +// These don't do anything at the moment. It is still +// required to call them to make sure the library +// is linked and loaded before the JIT engine starts. + +ARCJITENV_API int arc_jit_runtime_env_init(void); +ARCJITENV_API void arc_jit_runtime_env_deinit(void); From 07179af7d911d543bb1673d173d54c95e24f47f9 Mon Sep 17 00:00:00 2001 From: Leon Hielscher Date: Wed, 7 Aug 2024 17:03:40 +0200 Subject: [PATCH 026/119] [NFC] Remove trailing whitespace --- include/circt/Dialect/Arc/ArcOps.td | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/circt/Dialect/Arc/ArcOps.td b/include/circt/Dialect/Arc/ArcOps.td index bcaf65e56b85..a4ebdae14dde 100644 --- a/include/circt/Dialect/Arc/ArcOps.td +++ b/include/circt/Dialect/Arc/ArcOps.td @@ -814,7 +814,7 @@ def VectorizeOp : ArcOp<"vectorize", [ ``` or SIMD vectors. ```mlir - %0:2 = arc.vectorize (%in0, %in1), (%in2, %in3) : + %0:2 = arc.vectorize (%in0, %in1), (%in2, %in3) : (i1, i1, i1, i1) -> (i1, i1) { ^bb0(%arg0: vector<2xi1>, %arg1: vector<2xi1>): %1 = arith.and %arg0, %arg1 : vector<2xi1> From cadbfe0e5e1d745fe0c143c1c0d6fadde0a7b0e3 Mon Sep 17 00:00:00 2001 From: Hideto Ueno Date: Thu, 8 Aug 2024 00:21:51 +0900 Subject: [PATCH 027/119] [circt-lec] Accept two MLIR inputs (#7450) Practically it is very useful to verify equivalence between modules in two different MLIR files. This commit changes `inputFilename` to a list and implements a very simple module merge that moves operations in the second module to the first by resolving the symbol. --- test/CMakeLists.txt | 1 + test/Tools/circt-lec/merge-inputs-error.mlir | 14 ++++ test/Tools/circt-lec/merge-inputs.mlir | 42 +++++++++++ test/lit.cfg.py | 4 +- tools/circt-lec/circt-lec.cpp | 76 +++++++++++++++++--- 5 files changed, 125 insertions(+), 12 deletions(-) create mode 100644 test/Tools/circt-lec/merge-inputs-error.mlir create mode 100644 test/Tools/circt-lec/merge-inputs.mlir diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 11738c2363bf..07f262ca563c 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -24,6 +24,7 @@ set(CIRCT_TEST_DEPENDS circt-capi-firtool-test circt-as circt-dis + circt-lec circt-opt circt-translate circt-reduce diff --git a/test/Tools/circt-lec/merge-inputs-error.mlir b/test/Tools/circt-lec/merge-inputs-error.mlir new file mode 100644 index 000000000000..4911f17d427b --- /dev/null +++ b/test/Tools/circt-lec/merge-inputs-error.mlir @@ -0,0 +1,14 @@ +// RUN: split-file %s %t +// RUN: not circt-lec %t/a.mlir %t/b.mlir --c1 top_a --c2 top_not_exist 2>&1 | FileCheck %s + +// CHECK: module "top_not_exist" was not found in the second module + +//--- a.mlir +hw.module @top_a(in %a : i8, out b : i8) { + hw.output %a : i8 +} + +//--- b.mlir +hw.module @top_b(in %a : i8, out b : i8) { + hw.output %a : i8 +} diff --git a/test/Tools/circt-lec/merge-inputs.mlir b/test/Tools/circt-lec/merge-inputs.mlir new file mode 100644 index 000000000000..edba84a12140 --- /dev/null +++ b/test/Tools/circt-lec/merge-inputs.mlir @@ -0,0 +1,42 @@ +// RUN: split-file %s %t +// RUN: circt-lec %t/a.mlir %t/b.mlir --c1 top_a --c2 top_b --emit-mlir | FileCheck %s + +//--- a.mlir +hw.module @foo(in %a : i8, out b : i8) { + %c1_i8 = hw.constant 1 : i8 + %add = comb.add %a, %c1_i8: i8 + hw.output %add : i8 +} +hw.module @top_a(in %a : i8, out b : i8) { + %foo.b = hw.instance "foo" @foo(a: %a: i8) -> (b: i8) + hw.output %foo.b : i8 +} + +//--- b.mlir +hw.module @foo(in %a : i8, out b : i8) { + %c2_i8 = hw.constant 2 : i8 + %add = comb.add %a, %c2_i8: i8 + hw.output %add : i8 +} + +hw.module @top_b(in %a : i8, out b : i8) { + %foo.b = hw.instance "foo" @foo(a: %a: i8) -> (b: i8) + hw.output %foo.b : i8 +} + +// Check constants to make sure a.mlir and b.mlir are properly merged. +// CHECK-LABEL: func.func @foo_0(%arg0: !smt.bv<8>) -> !smt.bv<8> +// CHECK-NEXT: %c2_bv8 = smt.bv.constant #smt.bv<2> +// CHECK-NEXT: %0 = smt.bv.add %arg0, %c2_bv8 +// CHECK-NEXT: return %0 + +// CHECK-LABEL: func.func @foo(%arg0: !smt.bv<8>) -> !smt.bv<8> +// CHECK-NEXT: %c1_bv8 = smt.bv.constant #smt.bv<1> +// CHECK-NEXT: %0 = smt.bv.add %arg0, %c1_bv8 +// CHECK-NEXT: return %0 + +// CHECK-LABEL: func.func @top_a +// CHECK: %[[RESULT1:.+]] = func.call @foo(%[[ARG:.+]]) +// CHECK-NEXT: %[[RESULT2:.+]] = func.call @foo_0(%[[ARG]]) +// CHECK-NEXT: %[[VAL:.+]] = smt.distinct %[[RESULT1]], %[[RESULT2]] +// CHECK-NEXT: smt.assert %[[VAL]] diff --git a/test/lit.cfg.py b/test/lit.cfg.py index 0340cc25f881..26b9a164dead 100644 --- a/test/lit.cfg.py +++ b/test/lit.cfg.py @@ -60,8 +60,8 @@ tools = [ 'arcilator', 'circt-as', 'circt-capi-ir-test', 'circt-capi-om-test', 'circt-capi-firrtl-test', 'circt-capi-firtool-test', 'circt-dis', - 'circt-reduce', 'circt-translate', 'firtool', 'hlstool', 'om-linker', - 'ibistool' + 'circt-lec', 'circt-reduce', 'circt-translate', 'firtool', 'hlstool', + 'om-linker', 'ibistool' ] if "CIRCT_OPT_CHECK_IR_ROUNDTRIP" in os.environ: diff --git a/tools/circt-lec/circt-lec.cpp b/tools/circt-lec/circt-lec.cpp index ec03638e7e2a..3a8e0c0ff63b 100644 --- a/tools/circt-lec/circt-lec.cpp +++ b/tools/circt-lec/circt-lec.cpp @@ -71,9 +71,9 @@ static cl::opt secondModuleName( cl::desc("Specify a named module for the second circuit of the comparison"), cl::value_desc("module name"), cl::cat(mainCategory)); -static cl::opt inputFilename(cl::Positional, cl::Required, - cl::desc(""), - cl::cat(mainCategory)); +static cl::list inputFilenames(cl::Positional, cl::OneOrMore, + cl::desc(""), + cl::cat(mainCategory)); static cl::opt outputFilename("o", cl::desc("Output filename"), cl::value_desc("filename"), @@ -122,6 +122,65 @@ static cl::opt outputFormat( // Tool implementation //===----------------------------------------------------------------------===// +// Move all operations in `src` to `dest`. Rename all symbols in `src` to avoid +// conflict. +static FailureOr mergeModules(ModuleOp dest, ModuleOp src, + StringAttr name) { + + SymbolTable destTable(dest), srcTable(src); + StringAttr newName = {}; + for (auto &op : src.getOps()) { + if (SymbolOpInterface symbol = dyn_cast(op)) { + auto oldSymbol = symbol.getNameAttr(); + auto result = srcTable.renameToUnique(&op, {&destTable}); + if (failed(result)) + return src->emitError() << "failed to rename symbol " << oldSymbol; + + if (oldSymbol == name) { + assert(!newName && "symbol must be unique"); + newName = *result; + } + } + } + + if (!newName) + return src->emitError() + << "module " << name << " was not found in the second module"; + + dest.getBody()->getOperations().splice(dest.getBody()->begin(), + src.getBody()->getOperations()); + return newName; +} + +// Parse one or two MLIR modules and merge it into a single module. +static FailureOr> +parseAndMergeModules(MLIRContext &context, TimingScope &ts) { + auto parserTimer = ts.nest("Parse and merge MLIR input(s)"); + + if (inputFilenames.size() > 2) { + llvm::errs() << "more than 2 files are provided!\n"; + return failure(); + } + + auto module = parseSourceFile(inputFilenames[0], &context); + if (!module) + return failure(); + + if (inputFilenames.size() == 2) { + auto moduleOpt = parseSourceFile(inputFilenames[1], &context); + if (!moduleOpt) + return failure(); + auto result = mergeModules(module.get(), moduleOpt.get(), + StringAttr::get(&context, secondModuleName)); + if (failed(result)) + return failure(); + + secondModuleName.setValue(result->getValue().str()); + } + + return module; +} + /// This functions initializes the various components of the tool and /// orchestrates the work to be done. static LogicalResult executeLEC(MLIRContext &context) { @@ -130,15 +189,12 @@ static LogicalResult executeLEC(MLIRContext &context) { applyDefaultTimingManagerCLOptions(tm); auto ts = tm.getRootScope(); - OwningOpRef module; - { - auto parserTimer = ts.nest("Parse MLIR input"); - // Parse the provided input files. - module = parseSourceFile(inputFilename, &context); - } - if (!module) + auto parsedModule = parseAndMergeModules(context, ts); + if (failed(parsedModule)) return failure(); + OwningOpRef module = std::move(parsedModule.value()); + // Create the output directory or output file depending on our mode. std::optional> outputFile; std::string errorMessage; From c08ac4bd782654562b530b4b164501bf785d6385 Mon Sep 17 00:00:00 2001 From: Schuyler Eldridge Date: Wed, 7 Aug 2024 11:50:05 -0400 Subject: [PATCH 028/119] [FIRRTL] Cache symbol table in LowerLayers (#7436) Precompute a symbol to layer mapping inside LowerLayers and uses this instead of a symbol table. This is both faster and avoids problems of trying to compute a symbol table while modules may be created. Repurpose the functions that were being used to create macro declarations to also compute this symbol to layer mapping. Rename these to indicate that they are now doing generic layer preprocessing. Fixes #7434. h/t @youngar for the fix suggestion. Signed-off-by: Schuyler Eldridge --- lib/Dialect/FIRRTL/Transforms/LowerLayers.cpp | 42 +++++++++++-------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/lib/Dialect/FIRRTL/Transforms/LowerLayers.cpp b/lib/Dialect/FIRRTL/Transforms/LowerLayers.cpp index e75088749322..3f01f788a3c3 100644 --- a/lib/Dialect/FIRRTL/Transforms/LowerLayers.cpp +++ b/lib/Dialect/FIRRTL/Transforms/LowerLayers.cpp @@ -123,7 +123,8 @@ static SmallString<32> fileNameForLayer(StringRef circuitName, } /// For a layerblock `@A::@B::@C`, the verilog macro is `A_B_C`. -static SmallString<32> macroNameForLayer(ArrayRef layerName) { +static SmallString<32> +macroNameForLayer(ArrayRef layerName) { SmallString<32> result; for (auto part : layerName) appendName(part, result); @@ -137,8 +138,7 @@ static SmallString<32> macroNameForLayer(ArrayRef layerName) { class LowerLayersPass : public circt::firrtl::impl::LowerLayersBase { hw::OutputFileAttr getOutputFile(SymbolRefAttr layerName) { - auto layer = - cast(SymbolTable::lookupSymbolIn(getOperation(), layerName)); + auto layer = symbolToLayer.lookup(layerName); if (!layer) return nullptr; return layer->getAttrOfType("output_file"); @@ -181,10 +181,11 @@ class LowerLayersPass /// Lower an inline layerblock to an ifdef block. void lowerInlineLayerBlock(LayerOp layer, LayerBlockOp layerBlock); - /// Create macro declarations for a given layer, and its child layers. - void createMacroDecls(CircuitNamespace &ns, OpBuilder &b, LayerOp layer, - SmallVector &stack); - void createMacroDecls(CircuitNamespace &ns, LayerOp layer); + /// Preprocess layers to build macro declarations and cache information about + /// the layers so that this can be quired later. + void preprocessLayers(CircuitNamespace &ns, OpBuilder &b, LayerOp layer, + SmallVector &stack); + void preprocessLayers(CircuitNamespace &ns, LayerOp layer); /// Entry point for the function. void runOnOperation() override; @@ -197,6 +198,9 @@ class LowerLayersPass /// A map from inline layers to their macro names. DenseMap macroNames; + + /// A mapping of symbol name to layer operation. + DenseMap symbolToLayer; }; /// Multi-process safe function to build a module in the circuit and return it. @@ -352,8 +356,7 @@ void LowerLayersPass::runOnModuleBody(FModuleOp moduleOp, if (!layerBlock) return WalkResult::advance(); - auto layer = cast( - SymbolTable::lookupSymbolIn(getOperation(), layerBlock.getLayerName())); + auto layer = symbolToLayer.lookup(layerBlock.getLayerName()); if (layer.getConvention() == LayerConvention::Inline) { lowerInlineLayerBlock(layer, layerBlock); @@ -658,10 +661,14 @@ void LowerLayersPass::runOnModuleBody(FModuleOp moduleOp, }); } -void LowerLayersPass::createMacroDecls(CircuitNamespace &ns, OpBuilder &b, +void LowerLayersPass::preprocessLayers(CircuitNamespace &ns, OpBuilder &b, LayerOp layer, - SmallVector &stack) { - stack.emplace_back(layer.getSymNameAttr()); + SmallVector &stack) { + stack.emplace_back(FlatSymbolRefAttr::get(layer.getSymNameAttr())); + ArrayRef stackRef(stack); + symbolToLayer.insert( + {SymbolRefAttr::get(stackRef.front().getAttr(), stackRef.drop_front()), + layer}); if (layer.getConvention() == LayerConvention::Inline) { auto *ctx = &getContext(); auto macName = macroNameForLayer(stack); @@ -677,14 +684,14 @@ void LowerLayersPass::createMacroDecls(CircuitNamespace &ns, OpBuilder &b, macroNames[layer] = FlatSymbolRefAttr::get(&getContext(), symNameAttr); } for (auto child : layer.getOps()) - createMacroDecls(ns, b, child, stack); + preprocessLayers(ns, b, child, stack); stack.pop_back(); } -void LowerLayersPass::createMacroDecls(CircuitNamespace &ns, LayerOp layer) { +void LowerLayersPass::preprocessLayers(CircuitNamespace &ns, LayerOp layer) { OpBuilder b(layer); - SmallVector stack; - createMacroDecls(ns, b, layer, stack); + SmallVector stack; + preprocessLayers(ns, b, layer, stack); } /// Process a circuit to remove all layer blocks in each module and top-level @@ -712,8 +719,9 @@ void LowerLayersPass::runOnOperation() { continue; } // Build verilog macro declarations for each inline layer declarations. + // Cache layer symbol refs for lookup later. if (auto layerOp = dyn_cast(op)) { - createMacroDecls(ns, layerOp); + preprocessLayers(ns, layerOp); continue; } } From fcdefe5a1afd54a279f59fb079a3bfe535ff1461 Mon Sep 17 00:00:00 2001 From: Schuyler Eldridge Date: Wed, 7 Aug 2024 20:39:31 -0400 Subject: [PATCH 029/119] [FIRRTL] Whitespace cleanup, NFC Fix a trailing double newline. Signed-off-by: Schuyler Eldridge --- lib/Support/FieldRef.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/Support/FieldRef.cpp b/lib/Support/FieldRef.cpp index 41eaf8aac483..2db716c486a6 100644 --- a/lib/Support/FieldRef.cpp +++ b/lib/Support/FieldRef.cpp @@ -21,4 +21,3 @@ Operation *FieldRef::getDefiningOp() const { return op; return cast(value).getOwner()->getParentOp(); } - From aae791b1e941974094d087a184c3083b86165fb5 Mon Sep 17 00:00:00 2001 From: Fabian Schuiki Date: Wed, 7 Aug 2024 17:22:34 -0700 Subject: [PATCH 030/119] [FVInt] Fix printing when bit width is less than one full digit Printing of `FVInt`s would continuously shift the value right by the log2 of the radix. This triggers an assertion in `APInt` in the case where the bit width is less than the number of bits being shifted. --- lib/Support/FVInt.cpp | 5 +++-- unittests/Support/FVIntTest.cpp | 3 +++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/Support/FVInt.cpp b/lib/Support/FVInt.cpp index e70323cb8936..6b9818dff3a4 100644 --- a/lib/Support/FVInt.cpp +++ b/lib/Support/FVInt.cpp @@ -102,8 +102,9 @@ bool FVInt::tryToString(SmallVectorImpl &str, unsigned radix, while (!value.isZero() || !unknown.isZero()) { unsigned digitValue = value.getRawData()[0] & radixMask; unsigned digitUnknown = unknown.getRawData()[0] & radixMask; - value.lshrInPlace(radixLog2); - unknown.lshrInPlace(radixLog2); + unsigned shiftAmount = std::min(radixLog2, getBitWidth()); + value.lshrInPlace(shiftAmount); + unknown.lshrInPlace(shiftAmount); // Handle unknown bits. Since we only get to print a single X or Z character // to the string, either all bits in the digit have to be X, or all have to diff --git a/unittests/Support/FVIntTest.cpp b/unittests/Support/FVIntTest.cpp index c61311797418..eae7c1b0af86 100644 --- a/unittests/Support/FVIntTest.cpp +++ b/unittests/Support/FVIntTest.cpp @@ -94,6 +94,9 @@ TEST(FVIntTest, StringConversion) { StringRef("12345XZ67890ABCDEF")); ASSERT_EQ(FVInt::fromString("12345xz67890abcdef", 16).toString(16, false), StringRef("12345xz67890abcdef")); + + // Narrow <4 bit integers printed as hex. + ASSERT_EQ(FVInt::fromString("10", 2).toString(16), StringRef("2")); } TEST(FVIntTest, LogicOps) { From 1d417e2d3050157b26ca896287a64bbc21096c71 Mon Sep 17 00:00:00 2001 From: Fabian Schuiki Date: Wed, 7 Aug 2024 17:28:05 -0700 Subject: [PATCH 031/119] [FVInt] Add resizing utilities, allow hashing Add more utilities to help with resizing `FVInt`s, including - counting the active bits for signed and unsigned interpretation - truncation - zero/sign extension Also add a default constructor that produces a zero-bit zero value, allow `FVInt`s to be hashed, and consider bit width for equality comparisons. --- include/circt/Support/FVInt.h | 61 +++++++++++++++++++++++++++++++++-- lib/Support/FVInt.cpp | 4 +++ 2 files changed, 63 insertions(+), 2 deletions(-) diff --git a/include/circt/Support/FVInt.h b/include/circt/Support/FVInt.h index e657f65f10d2..f43c74f56056 100644 --- a/include/circt/Support/FVInt.h +++ b/include/circt/Support/FVInt.h @@ -31,6 +31,9 @@ namespace circt { /// 1/Z), and the other whether the bit is unknown (X or Z). class FVInt { public: + /// Default constructor that creates an zero-bit zero value. + explicit FVInt() : FVInt(0, 0) {} + /// Construct an `FVInt` from a 64-bit value. The result has no X or Z bits. FVInt(unsigned numBits, uint64_t value, bool isSigned = false) : FVInt(APInt(numBits, value, isSigned)) {} @@ -72,6 +75,25 @@ class FVInt { /// Return the number of bits this integer has. unsigned getBitWidth() const { return value.getBitWidth(); } + /// Compute the number of active bits in the value. This is the smallest bit + /// width to which the value can be truncated without losing information in + /// the most significant bits. Or put differently, the value truncated to its + /// active bits and zero-extended back to its original width produces the + /// original value. + unsigned getActiveBits() const { + return std::max(value.getActiveBits(), unknown.getActiveBits()); + } + + /// Compute the minimum bit width necessary to accurately represent this + /// integer's value and sign. This is the smallest bit width to which the + /// value can be truncated without losing information in the most significant + /// bits and without flipping from negative to positive or vice versa. Or put + /// differently, the value truncated to its significant bits and sign-extended + /// back to its original width produces the original value. + unsigned getSignificantBits() const { + return std::max(value.getSignificantBits(), unknown.getSignificantBits()); + } + /// Return the underlying `APInt` used to store whether a bit is 0/X or 1/Z. const APInt &getRawValue() const { return value; } @@ -94,9 +116,19 @@ class FVInt { // Resizing //===--------------------------------------------------------------------===// + /// Truncate the integer to a smaller bit width. This simply discards the + /// high-order bits. If the integer is truncated to a bit width less than its + /// "active bits", information will be lost and the resulting integer will + /// have a different value. + FVInt trunc(unsigned bitWidth) const { + assert(bitWidth <= getBitWidth()); + return FVInt(value.trunc(bitWidth), unknown.trunc(bitWidth)); + } + /// Zero-extend the integer to a new bit width. The additional high-order bits /// are filled in with zero. FVInt zext(unsigned bitWidth) const { + assert(bitWidth >= getBitWidth()); return FVInt(value.zext(bitWidth), unknown.zext(bitWidth)); } @@ -105,9 +137,20 @@ class FVInt { /// also when that sign bit is X or Z. Zero-width integers are extended with /// zeros. FVInt sext(unsigned bitWidth) const { + assert(bitWidth >= getBitWidth()); return FVInt(value.sext(bitWidth), unknown.sext(bitWidth)); } + /// Truncate or zero-extend to a target bit width. + FVInt zextOrTrunc(unsigned bitWidth) const { + return bitWidth > getBitWidth() ? zext(bitWidth) : trunc(bitWidth); + } + + /// Truncate or sign-extend to a target bit width. + FVInt sextOrTrunc(unsigned bitWidth) const { + return bitWidth > getBitWidth() ? sext(bitWidth) : trunc(bitWidth); + } + //===--------------------------------------------------------------------===// // Value Tests //===--------------------------------------------------------------------===// @@ -127,6 +170,14 @@ class FVInt { /// Determine if all bits are Z. This is true for zero-width values. bool isAllZ() const { return value.isAllOnes() && unknown.isAllOnes(); } + /// Determine whether the integer interpreted as a signed number would be + /// negative. Returns true if the sign bit is 1, and false if it is 0, X, or + /// Z. + bool isNegative() const { + auto idx = getBitWidth() - 1; + return value[idx] && !unknown[idx]; + } + //===--------------------------------------------------------------------===// // Bit Manipulation //===--------------------------------------------------------------------===// @@ -485,8 +536,11 @@ class FVInt { //===--------------------------------------------------------------------===// /// Determine whether this integer is equal to another. Note that this - /// corresponds to SystemVerilog's `===` operator. + /// corresponds to SystemVerilog's `===` operator. Returns false if the two + /// integers have different bit width. bool operator==(const FVInt &other) const { + if (getBitWidth() != other.getBitWidth()) + return false; return value == other.value && unknown == other.unknown; } @@ -497,7 +551,8 @@ class FVInt { return value == other && !hasUnknown(); } - /// Determine whether this integer is not equal to another. + /// Determine whether this integer is not equal to another. Returns true if + /// the two integers have different bit width. bool operator!=(const FVInt &other) const { return !((*this) == other); } /// Determine whether this integer is not equal to a two-valued integer. @@ -587,6 +642,8 @@ inline raw_ostream &operator<<(raw_ostream &os, const FVInt &value) { return os; } +llvm::hash_code hash_value(const FVInt &a); + } // namespace circt #endif // CIRCT_SUPPORT_FVINT_H diff --git a/lib/Support/FVInt.cpp b/lib/Support/FVInt.cpp index 6b9818dff3a4..838de02842ad 100644 --- a/lib/Support/FVInt.cpp +++ b/lib/Support/FVInt.cpp @@ -138,3 +138,7 @@ void FVInt::print(raw_ostream &os) const { tryToString(buffer, 2); os << buffer; } + +llvm::hash_code circt::hash_value(const FVInt &a) { + return llvm::hash_combine(a.getRawValue(), a.getRawUnknown()); +} From a9436263bd8b9a7dcacd263922dac8b271f355d4 Mon Sep 17 00:00:00 2001 From: Hideto Ueno Date: Thu, 8 Aug 2024 15:39:05 +0900 Subject: [PATCH 032/119] [ExportVerilog] Add a lowering option to fix up empty modules (#7454) This commit adds a new lowering option to sanitize empty modules by creating a dummy wire in it. --- docs/VerilogGeneration.md | 3 +++ include/circt/Support/LoweringOptions.h | 4 ++++ .../ExportVerilog/PrepareForEmission.cpp | 22 +++++++++++++++++++ lib/Support/LoweringOptions.cpp | 4 ++++ .../ExportVerilog/fixup-empty-modules.mlir | 19 ++++++++++++++++ 5 files changed, 52 insertions(+) create mode 100644 test/Conversion/ExportVerilog/fixup-empty-modules.mlir diff --git a/docs/VerilogGeneration.md b/docs/VerilogGeneration.md index 5efb26373422..0a006d3b0888 100644 --- a/docs/VerilogGeneration.md +++ b/docs/VerilogGeneration.md @@ -136,6 +136,9 @@ The current set of "lint warnings fix" Lowering Options is: collisions with Verilog keywords insensitively. E.g., this will treat a variable called `WIRE` as a collision with the keyword and rename it to `WIRE_0` (or similar). When set to `false`, then `WIRE` will not be renamed. + * `fixUpEmptyModules` (default=`false`). If true, then add a dummy wire to + empty modules since some vendor tools consider empty modules as a blackbox and + raise synthesis errors. ## Recommended `LoweringOptions` by Target diff --git a/include/circt/Support/LoweringOptions.h b/include/circt/Support/LoweringOptions.h index 59ce7aadc966..d72fcdfc260d 100644 --- a/include/circt/Support/LoweringOptions.h +++ b/include/circt/Support/LoweringOptions.h @@ -166,6 +166,10 @@ struct LoweringOptions { /// If true, then update the the mlir to include output verilog locations. bool emitVerilogLocations = false; + + /// If true, add a dummy wire to empty modules to prevent tools from regarding + /// the module as blackbox. + bool fixUpEmptyModules = false; }; } // namespace circt diff --git a/lib/Conversion/ExportVerilog/PrepareForEmission.cpp b/lib/Conversion/ExportVerilog/PrepareForEmission.cpp index 59d1b660f7d4..4789346f0760 100644 --- a/lib/Conversion/ExportVerilog/PrepareForEmission.cpp +++ b/lib/Conversion/ExportVerilog/PrepareForEmission.cpp @@ -1315,6 +1315,24 @@ static LogicalResult legalizeHWModule(Block &block, return success(); } +static void fixUpEmptyModules(hw::HWEmittableModuleLike module) { + auto outputOp = dyn_cast(module.getBodyBlock()->begin()); + if (!outputOp || outputOp->getNumOperands() > 0) + return; // Not empty so no need to fix up. + OpBuilder builder(module->getContext()); + builder.setInsertionPoint(outputOp); + auto constant = builder.create(module.getLoc(), + builder.getBoolAttr(true)); + auto wire = builder.create(module.getLoc(), builder.getI1Type()); + sv::setSVAttributes(wire, + sv::SVAttributeAttr::get( + builder.getContext(), + "This wire is added to avoid emitting empty modules. " + "See `fixUpEmptyModules` lowering option in CIRCT.", + /*emitAsComment=*/true)); + builder.create(module.getLoc(), wire, constant); +} + // NOLINTNEXTLINE(misc-no-recursion) LogicalResult ExportVerilog::prepareHWModule(hw::HWEmittableModuleLike module, const LoweringOptions &options) { @@ -1325,6 +1343,10 @@ LogicalResult ExportVerilog::prepareHWModule(hw::HWEmittableModuleLike module, // Zero-valued logic pruning. pruneZeroValuedLogic(module); + // Fix up empty modules if necessary. + if (options.fixUpEmptyModules) + fixUpEmptyModules(module); + // Legalization. if (failed(legalizeHWModule(*module.getBodyBlock(), options))) return failure(); diff --git a/lib/Support/LoweringOptions.cpp b/lib/Support/LoweringOptions.cpp index d6dfff5ee97f..f424494f18f1 100644 --- a/lib/Support/LoweringOptions.cpp +++ b/lib/Support/LoweringOptions.cpp @@ -121,6 +121,8 @@ void LoweringOptions::parse(StringRef text, ErrorHandlerT errorHandler) { caseInsensitiveKeywords = true; } else if (option == "emitVerilogLocations") { emitVerilogLocations = true; + } else if (option == "fixUpEmptyModules") { + fixUpEmptyModules = true; } else { errorHandler(llvm::Twine("unknown style option \'") + option + "\'"); // We continue parsing options after a failure. @@ -180,6 +182,8 @@ std::string LoweringOptions::toString() const { options += "caseInsensitiveKeywords,"; if (emitVerilogLocations) options += "emitVerilogLocations,"; + if (fixUpEmptyModules) + options += "fixUpEmptyModules,"; // Remove a trailing comma if present. if (!options.empty()) { diff --git a/test/Conversion/ExportVerilog/fixup-empty-modules.mlir b/test/Conversion/ExportVerilog/fixup-empty-modules.mlir new file mode 100644 index 000000000000..e0304327fdfa --- /dev/null +++ b/test/Conversion/ExportVerilog/fixup-empty-modules.mlir @@ -0,0 +1,19 @@ +// RUN: circt-opt --test-apply-lowering-options='options=fixUpEmptyModules' --export-verilog %s | FileCheck %s --check-prefixes=CHECK + +// CHECK-LABEL: module empty1 +hw.module @empty1() { + // CHECK-NEXT: /* This wire is added to avoid emitting empty modules. See `fixUpEmptyModules` lowering option in CIRCT. */ + // CHECK-NEXT: wire _GEN = 1'h1; +} + +// CHECK-LABEL: module empty2 +hw.module @empty2(in %in: i1, in %in2: i32) { + // CHECK: /* This wire is added to avoid emitting empty modules. See `fixUpEmptyModules` lowering option in CIRCT. */ + // CHECK-NEXT: wire _GEN = 1'h1; +} + +// CHECK-LABEL: module not_empty +hw.module @not_empty(in %in: i1, out out: i1) { + // CHECK: assign out = in; + hw.output %in : i1 +} From 12822ad0d01ff58b3ce9aa23ff73a106ee869c02 Mon Sep 17 00:00:00 2001 From: Hideto Ueno Date: Thu, 8 Aug 2024 15:40:35 +0900 Subject: [PATCH 033/119] [SimToSV] Add include guards to DPI import (#7459) This adds include guards `__CIRCT_DPI_IMPORT_*` to DPI import statements generated in SimToSV. Fix https://github.com/llvm/circt/issues/7458. --- lib/Conversion/SimToSV/SimToSV.cpp | 11 ++++++++++- test/Conversion/SimToSV/dpi.mlir | 7 ++++++- test/firtool/dpi.fir | 16 +++++++++++++--- 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/lib/Conversion/SimToSV/SimToSV.cpp b/lib/Conversion/SimToSV/SimToSV.cpp index a1d0af51ab42..bcdef4e024ec 100644 --- a/lib/Conversion/SimToSV/SimToSV.cpp +++ b/lib/Conversion/SimToSV/SimToSV.cpp @@ -268,8 +268,17 @@ void LowerDPIFunc::lower(sim::DPIFuncOp func) { auto name = builder.getStringAttr(nameSpace.newName( func.getSymNameAttr().getValue(), "dpi_import_fragument")); + // Add include guards to avoid duplicate declarations. See Issue 7458. + auto macroDecl = builder.create(nameSpace.newName( + "__CIRCT_DPI_IMPORT", func.getSymNameAttr().getValue().upper())); builder.create(name, [&]() { - builder.create(func.getSymNameAttr(), StringAttr()); + builder.create( + macroDecl.getSymNameAttr(), []() {}, + [&]() { + builder.create(func.getSymNameAttr(), + StringAttr()); + builder.create(macroDecl.getSymNameAttr(), ""); + }); }); symbolToFragment.insert({func.getSymNameAttr(), name}); diff --git a/test/Conversion/SimToSV/dpi.mlir b/test/Conversion/SimToSV/dpi.mlir index c352d5907ade..959e5152e2ef 100644 --- a/test/Conversion/SimToSV/dpi.mlir +++ b/test/Conversion/SimToSV/dpi.mlir @@ -3,8 +3,13 @@ sim.func.dpi @dpi(out arg0: i1, in %arg1: i1, out arg2: i1) // CHECK: sv.func private @dpi(out arg0 : i1, in %arg1 : i1, out arg2 : i1) +// CHECK-NEXT: sv.macro.decl @__CIRCT_DPI_IMPORT_DPI // CHECK-NEXT: emit.fragment @dpi_dpi_import_fragument { -// CHECK-NEXT: sv.func.dpi.import @dpi +// CHECK-NEXT: sv.ifdef @__CIRCT_DPI_IMPORT_DPI { +// CHECK-NEXT: } else { +// CHECK-NEXT: sv.func.dpi.import @dpi +// CHECK-NEXT: sv.macro.def @__CIRCT_DPI_IMPORT_DPI "" +// CHECK-NEXT: } // CHECK-NEXT: } // VERILOG: import "DPI-C" context function void dpi( diff --git a/test/firtool/dpi.fir b/test/firtool/dpi.fir index b7a353b2c77c..ce47feb2529e 100644 --- a/test/firtool/dpi.fir +++ b/test/firtool/dpi.fir @@ -2,23 +2,33 @@ FIRRTL version 4.0.0 circuit DPI: -; CHECK-LABEL: import "DPI-C" context function void clocked_result( +; CHECK-LABEL: `ifndef __CIRCT_DPI_IMPORT_CLOCKED_RESULT +; CHECK-NEXT: import "DPI-C" context function void clocked_result( ; CHECK-NEXT: input byte foo, ; CHECK-NEXT: bar, ; CHECK-NEXT: output byte baz ; CHECK-NEXT: ); +; CHECK: `define __CIRCT_DPI_IMPORT_CLOCKED_RESULT +; CHECK-NEXT: `endif -; CHECK-LABEL: import "DPI-C" context function void clocked_void( +; CHECK-LABEL: `ifndef __CIRCT_DPI_IMPORT_CLOCKED_VOID +; CHECK-NEXT: import "DPI-C" context function void clocked_void( ; CHECK-NEXT: input byte in_0, ; CHECK-NEXT: in_1, ; CHECK-NEXT: in_2[] ; CHECK-NEXT: ); +; CHECK: `define __CIRCT_DPI_IMPORT_CLOCKED_VOID +; CHECK-NEXT: `endif -; CHECK-LABEL: import "DPI-C" context function void unclocked_result( + +; CHECK-LABEL: `ifndef __CIRCT_DPI_IMPORT_UNCLOCKED_RESULT +; CHECK-NEXT: import "DPI-C" context function void unclocked_result( ; CHECK-NEXT: input byte in_0, ; CHECK-NEXT: in_1, ; CHECK-NEXT: output byte out_0 ; CHECK-NEXT: ); +; CHECK: `define __CIRCT_DPI_IMPORT_UNCLOCKED_RESULT +; CHECK-NEXT: `endif ; CHECK-LABEL: module DPI( ; CHECK: logic [7:0] [[TMP:_.+]]; From caab2176425ff180d798ab022b52142faee650b3 Mon Sep 17 00:00:00 2001 From: Morten Borup Petersen Date: Thu, 8 Aug 2024 11:02:19 +0200 Subject: [PATCH 034/119] [ESI] Add option to build runtime as a static library (#7455) * [ESI] Add option to build runtime as a static library * review comments --------- Co-authored-by: Morten Borup Petersen --- lib/Dialect/ESI/runtime/CMakeLists.txt | 40 ++++++++++++++----- .../ESI/runtime/cpp/include/esi/Accelerator.h | 7 ++-- .../ESI/runtime/cpp/lib/Accelerator.cpp | 33 ++++++++++----- 3 files changed, 58 insertions(+), 22 deletions(-) diff --git a/lib/Dialect/ESI/runtime/CMakeLists.txt b/lib/Dialect/ESI/runtime/CMakeLists.txt index 1bf82fa04c98..fdda1847c2da 100644 --- a/lib/Dialect/ESI/runtime/CMakeLists.txt +++ b/lib/Dialect/ESI/runtime/CMakeLists.txt @@ -27,6 +27,11 @@ cmake_minimum_required(VERSION 3.20) project(ESIRuntime LANGUAGES CXX) include(FetchContent) +set(ESI_STATIC_RUNTIME OFF CACHE BOOL "Build the ESI runtime as a static library.") +if(ESI_STATIC_RUNTIME) + message("-- Building ESI runtime as a static library.") +endif() + set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED YES) set(CMAKE_INSTALL_RPATH "$ORIGIN/../lib") @@ -44,6 +49,10 @@ if (NOT TARGET nlohmann_json) FetchContent_MakeAvailable(json) endif() +if(ESI_STATIC_RUNTIME) + set(ZLIB_USE_STATIC_LIBS ON) +endif() + # We need zlib to uncompress the manifest. find_package(ZLIB) if(ZLIB_FOUND) @@ -57,7 +66,11 @@ else() ) FetchContent_MakeAvailable(ZLIB) set(ZLIB_INCLUDE_DIR ${zlib_SOURCE_DIR} ${zlib_BINARY_DIR}) - set(ZLIB_LIBRARY zlib) + if(ESI_STATIC_RUNTIME) + set(ZLIB_LIBRARY zlibstatic) + else() + set(ZLIB_LIBRARY zlib) + endif() endif() # In a Python wheel build, we need to install libraries to different places. @@ -118,10 +131,17 @@ IF(MSVC) set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS 1) ENDIF(MSVC) -# The core API. For now, compile the backends into it directly. -add_library(ESICppRuntime SHARED - ${ESICppRuntimeSources} -) +if(ESI_STATIC_RUNTIME) + add_library(ESICppRuntime STATIC + ${ESICppRuntimeSources} + ) +else() + add_library(ESICppRuntime SHARED + ${ESICppRuntimeSources} + ) +endif() +add_library(esiaccel::ESICppRuntime ALIAS ESICppRuntime) + target_include_directories(ESICppRuntime PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/cpp/include ) @@ -165,10 +185,12 @@ install(TARGETS ESICppRuntime COMPONENT ESIRuntime ) -install(IMPORTED_RUNTIME_ARTIFACTS ESICppRuntime - RUNTIME_DEPENDENCY_SET ESICppRuntime_RUNTIME_DEPS - COMPONENT ESIRuntime -) +if(NOT ESI_STATIC_RUNTIME) + install(IMPORTED_RUNTIME_ARTIFACTS ESICppRuntime + RUNTIME_DEPENDENCY_SET ESICppRuntime_RUNTIME_DEPS + COMPONENT ESIRuntime + ) +endif() install(RUNTIME_DEPENDENCY_SET ESICppRuntime_RUNTIME_DEPS DESTINATION ${LIB_DIR} PRE_EXCLUDE_REGEXES .* diff --git a/lib/Dialect/ESI/runtime/cpp/include/esi/Accelerator.h b/lib/Dialect/ESI/runtime/cpp/include/esi/Accelerator.h index 34567c466f88..c3a1cd9da9e6 100644 --- a/lib/Dialect/ESI/runtime/cpp/include/esi/Accelerator.h +++ b/lib/Dialect/ESI/runtime/cpp/include/esi/Accelerator.h @@ -134,15 +134,16 @@ namespace registry { // Connect to an ESI accelerator given a backend name and connection specifier. // Alternatively, instantiate the backend directly (if you're using C++). -std::unique_ptr -connect(Context &ctxt, std::string backend, std::string connection); +std::unique_ptr connect(Context &ctxt, + const std::string &backend, + const std::string &connection); namespace internal { /// Backends can register themselves to be connected via a connection string. using BackendCreate = std::function( Context &, std::string)>; -void registerBackend(std::string name, BackendCreate create); +void registerBackend(const std::string &name, BackendCreate create); // Helper struct to template diff --git a/lib/Dialect/ESI/runtime/cpp/lib/Accelerator.cpp b/lib/Dialect/ESI/runtime/cpp/lib/Accelerator.cpp index 019d734b0023..0aae47341b6e 100644 --- a/lib/Dialect/ESI/runtime/cpp/lib/Accelerator.cpp +++ b/lib/Dialect/ESI/runtime/cpp/lib/Accelerator.cpp @@ -175,22 +175,35 @@ static void loadBackend(std::string backend) { namespace registry { namespace internal { -static std::map backendRegistry; -void registerBackend(std::string name, BackendCreate create) { - if (backendRegistry.count(name)) +class BackendRegistry { +public: + static std::map &get() { + static BackendRegistry instance; + return instance.backendRegistry; + } + +private: + std::map backendRegistry; +}; + +void registerBackend(const std::string &name, BackendCreate create) { + auto ®istry = BackendRegistry::get(); + if (registry.count(name)) throw std::runtime_error("Backend already exists in registry"); - backendRegistry[name] = create; + registry[name] = create; } } // namespace internal -std::unique_ptr -connect(Context &ctxt, std::string backend, std::string connection) { - auto f = internal::backendRegistry.find(backend); - if (f == internal::backendRegistry.end()) { +std::unique_ptr connect(Context &ctxt, + const std::string &backend, + const std::string &connection) { + auto ®istry = internal::BackendRegistry::get(); + auto f = registry.find(backend); + if (f == registry.end()) { // If it's not already found in the registry, try to load it dynamically. loadBackend(backend); - f = internal::backendRegistry.find(backend); - if (f == internal::backendRegistry.end()) + f = registry.find(backend); + if (f == registry.end()) throw std::runtime_error("Backend '" + backend + "' not found"); } return f->second(ctxt, connection); From ad913784651c64f72306b335eedc4f7feaf71372 Mon Sep 17 00:00:00 2001 From: John Demme Date: Thu, 8 Aug 2024 12:53:42 +0200 Subject: [PATCH 035/119] [ESI][Runtime] Poll method and optional service thread polling (#7460) Add a poll method to ports, a master poll method to the Accelerator, and the ability to poll from the service thread. Also, only spin up the service thread if it's requested. The service thread polling (in particular) required some ownership changes: Accelerator objects now belong to the AcceleratorConnection so that the ports aren't destructed before the service thread gets shutdown (which causes an invalid memory access). This particular binding isn't ideal, is brittle, and will be an issue for anything doing the polling. Resolving #7457 should mitigate this issue. Backends are now _required_ to call `disconnect` in their destructor. --- .../Dialect/ESI/runtime/loopback.mlir.py | 3 ++ .../ESI/runtime/cpp/include/esi/Accelerator.h | 22 ++++++-- .../ESI/runtime/cpp/include/esi/Design.h | 5 ++ .../ESI/runtime/cpp/include/esi/Manifest.h | 7 +-- .../ESI/runtime/cpp/include/esi/Ports.h | 52 ++++++++++++++++--- .../runtime/cpp/include/esi/backends/Trace.h | 1 + .../runtime/cpp/include/esi/backends/Xrt.h | 1 + .../ESI/runtime/cpp/lib/Accelerator.cpp | 48 +++++++++++++++-- lib/Dialect/ESI/runtime/cpp/lib/Design.cpp | 9 ++++ lib/Dialect/ESI/runtime/cpp/lib/Manifest.cpp | 5 +- lib/Dialect/ESI/runtime/cpp/lib/Ports.cpp | 4 +- .../ESI/runtime/cpp/lib/backends/Cosim.cpp | 13 ++--- .../ESI/runtime/cpp/lib/backends/Trace.cpp | 42 +++------------ .../ESI/runtime/cpp/lib/backends/Xrt.cpp | 1 + .../ESI/runtime/cpp/tools/esiquery.cpp | 4 +- .../ESI/runtime/cpp/tools/esitester.cpp | 5 +- .../runtime/python/esiaccel/esiCppAccel.cpp | 10 +++- 17 files changed, 164 insertions(+), 68 deletions(-) diff --git a/integration_test/Dialect/ESI/runtime/loopback.mlir.py b/integration_test/Dialect/ESI/runtime/loopback.mlir.py index 8bf332af4473..a942a45b232b 100644 --- a/integration_test/Dialect/ESI/runtime/loopback.mlir.py +++ b/integration_test/Dialect/ESI/runtime/loopback.mlir.py @@ -94,4 +94,7 @@ print(f"result: {result}") if platform != "trace": assert result == [-21, -22] + +acc = None + print("PASS") diff --git a/lib/Dialect/ESI/runtime/cpp/include/esi/Accelerator.h b/lib/Dialect/ESI/runtime/cpp/include/esi/Accelerator.h index c3a1cd9da9e6..365b14f85404 100644 --- a/lib/Dialect/ESI/runtime/cpp/include/esi/Accelerator.h +++ b/lib/Dialect/ESI/runtime/cpp/include/esi/Accelerator.h @@ -74,11 +74,11 @@ class Accelerator : public HWModule { /// Abstract class representing a connection to an accelerator. Actual /// connections (e.g. to a co-simulation or actual device) are implemented by -/// subclasses. +/// subclasses. No methods in here are thread safe. class AcceleratorConnection { public: AcceleratorConnection(Context &ctxt); - virtual ~AcceleratorConnection() = default; + virtual ~AcceleratorConnection(); Context &getCtxt() const { return ctxt; } /// Disconnect from the accelerator cleanly. @@ -89,7 +89,12 @@ class AcceleratorConnection { virtual std::map requestChannelsFor(AppIDPath, const BundleType *) = 0; - AcceleratorServiceThread *getServiceThread() { return serviceThread.get(); } + /// Return a pointer to the accelerator 'service' thread (or threads). If the + /// thread(s) are not running, they will be started when this method is + /// called. `std::thread` is used. If users don't want the runtime to spin up + /// threads, don't call this method. `AcceleratorServiceThread` is owned by + /// AcceleratorConnection and governed by the lifetime of the this object. + AcceleratorServiceThread *getServiceThread(); using Service = services::Service; /// Get a typed reference to a particular service type. Caller does *not* take @@ -109,6 +114,10 @@ class AcceleratorConnection { ServiceImplDetails details = {}, HWClientDetails clients = {}); + /// Assume ownership of an accelerator object. Ties the lifetime of the + /// accelerator to this connection. Returns a raw pointer to the object. + Accelerator *takeOwnership(std::unique_ptr accel); + protected: /// Called by `getServiceImpl` exclusively. It wraps the pointer returned by /// this in a unique_ptr and caches it. Separate this from the @@ -128,6 +137,10 @@ class AcceleratorConnection { std::map> serviceCache; std::unique_ptr serviceThread; + + /// List of accelerator objects owned by this connection. These are destroyed + /// when the connection dies or is shutdown. + std::vector> ownedAccelerators; }; namespace registry { @@ -173,6 +186,9 @@ class AcceleratorServiceThread { addListener(std::initializer_list listenPorts, std::function callback); + /// Poll this module. + void addPoll(HWModule &module); + /// Instruct the service thread to stop running. void stop(); diff --git a/lib/Dialect/ESI/runtime/cpp/include/esi/Design.h b/lib/Dialect/ESI/runtime/cpp/include/esi/Design.h index 6d1713ce82d4..b95421e0cd01 100644 --- a/lib/Dialect/ESI/runtime/cpp/include/esi/Design.h +++ b/lib/Dialect/ESI/runtime/cpp/include/esi/Design.h @@ -77,6 +77,11 @@ class HWModule { return portIndex; } + /// Master poll method. Calls the `poll` method on all locally owned ports and + /// the master `poll` method on all of the children. Returns true if any of + /// the `poll` calls returns true. + bool poll(); + protected: const std::optional info; const std::vector> children; diff --git a/lib/Dialect/ESI/runtime/cpp/include/esi/Manifest.h b/lib/Dialect/ESI/runtime/cpp/include/esi/Manifest.h index 547ecb9dbe94..fccb6272ba13 100644 --- a/lib/Dialect/ESI/runtime/cpp/include/esi/Manifest.h +++ b/lib/Dialect/ESI/runtime/cpp/include/esi/Manifest.h @@ -48,9 +48,10 @@ class Manifest { // Modules which have designer specified metadata. std::vector getModuleInfos() const; - // Build a dynamic design hierarchy from the manifest. - std::unique_ptr - buildAccelerator(AcceleratorConnection &acc) const; + // Build a dynamic design hierarchy from the manifest. The + // AcceleratorConnection owns the returned pointer so its lifetime is + // determined by the connection. + Accelerator *buildAccelerator(AcceleratorConnection &acc) const; /// The Type Table is an ordered list of types. The offset can be used to /// compactly and uniquely within a design. It does not include all of the diff --git a/lib/Dialect/ESI/runtime/cpp/include/esi/Ports.h b/lib/Dialect/ESI/runtime/cpp/include/esi/Ports.h index 2db68e76e5f9..c025cbfa085c 100644 --- a/lib/Dialect/ESI/runtime/cpp/include/esi/Ports.h +++ b/lib/Dialect/ESI/runtime/cpp/include/esi/Ports.h @@ -33,21 +33,38 @@ namespace esi { class ChannelPort { public: ChannelPort(const Type *type) : type(type) {} - virtual ~ChannelPort() { disconnect(); } + virtual ~ChannelPort() {} /// Set up a connection to the accelerator. The buffer size is optional and /// should be considered merely a hint. Individual implementations use it /// however they like. The unit is number of messages of the port type. - virtual void connect(std::optional bufferSize = std::nullopt) { - connectImpl(bufferSize); + virtual void connect(std::optional bufferSize = std::nullopt) = 0; + virtual void disconnect() = 0; + virtual bool isConnected() const = 0; + + /// Poll for incoming data. Returns true if data was read or written into a + /// buffer as a result of the poll. Calling the call back could (will) also + /// happen in that case. Some backends need this to be called periodically. In + /// the usual case, this will be called by a background thread, but the ESI + /// runtime does not want to assume that the host processes use standard + /// threads. If the user wants to provide their own threads, they need to call + /// this on each port occasionally. This is also called from the 'master' poll + /// method in the Accelerator class. + bool poll() { + if (isConnected()) + return pollImpl(); + return false; } - virtual void disconnect() {} const Type *getType() const { return type; } -private: +protected: const Type *type; + /// Method called by poll() to actually poll the channel if the channel is + /// connected. + virtual bool pollImpl() { return false; } + /// Called by all connect methods to let backends initiate the underlying /// connections. virtual void connectImpl(std::optional bufferSize) {} @@ -58,8 +75,19 @@ class WriteChannelPort : public ChannelPort { public: using ChannelPort::ChannelPort; + virtual void + connect(std::optional bufferSize = std::nullopt) override { + connectImpl(bufferSize); + connected = true; + } + virtual void disconnect() override { connected = false; } + virtual bool isConnected() const override { return connected; } + /// A very basic write API. Will likely change for performance reasons. virtual void write(const MessageData &) = 0; + +private: + volatile bool connected = false; }; /// A ChannelPort which reads data from the accelerator. It has two modes: @@ -72,6 +100,9 @@ class ReadChannelPort : public ChannelPort { ReadChannelPort(const Type *type) : ChannelPort(type), mode(Mode::Disconnected) {} virtual void disconnect() override { mode = Mode::Disconnected; } + virtual bool isConnected() const override { + return mode != Mode::Disconnected; + } //===--------------------------------------------------------------------===// // Callback mode: To use a callback, connect with a callback function which @@ -121,7 +152,7 @@ class ReadChannelPort : public ChannelPort { protected: /// Indicates the current mode of the channel. enum Mode { Disconnected, Callback, Polling }; - Mode mode; + volatile Mode mode; /// Backends call this callback when new data is available. std::function callback; @@ -178,6 +209,15 @@ class BundlePort { return const_cast(dynamic_cast(this)); } + /// Calls `poll` on all channels in the bundle and returns true if any of them + /// returned true. + bool poll() { + bool result = false; + for (auto &channel : channels) + result |= channel.second.poll(); + return result; + } + private: AppID id; std::map channels; diff --git a/lib/Dialect/ESI/runtime/cpp/include/esi/backends/Trace.h b/lib/Dialect/ESI/runtime/cpp/include/esi/backends/Trace.h index 6be52ffe6c85..ff5416bb4398 100644 --- a/lib/Dialect/ESI/runtime/cpp/include/esi/backends/Trace.h +++ b/lib/Dialect/ESI/runtime/cpp/include/esi/backends/Trace.h @@ -52,6 +52,7 @@ class TraceAccelerator : public esi::AcceleratorConnection { /// is opened for writing. For 'Read' mode, this file is opened for reading. TraceAccelerator(Context &, Mode mode, std::filesystem::path manifestJson, std::filesystem::path traceFile); + ~TraceAccelerator() override; /// Parse the connection string and instantiate the accelerator. Format is: /// ":[:]". diff --git a/lib/Dialect/ESI/runtime/cpp/include/esi/backends/Xrt.h b/lib/Dialect/ESI/runtime/cpp/include/esi/backends/Xrt.h index 04454cb3c8a4..def6b4ebf8bd 100644 --- a/lib/Dialect/ESI/runtime/cpp/include/esi/backends/Xrt.h +++ b/lib/Dialect/ESI/runtime/cpp/include/esi/backends/Xrt.h @@ -33,6 +33,7 @@ class XrtAccelerator : public esi::AcceleratorConnection { struct Impl; XrtAccelerator(Context &, std::string xclbin, std::string kernelName); + ~XrtAccelerator(); static std::unique_ptr connect(Context &, std::string connectionString); diff --git a/lib/Dialect/ESI/runtime/cpp/lib/Accelerator.cpp b/lib/Dialect/ESI/runtime/cpp/lib/Accelerator.cpp index 0aae47341b6e..9f94d784489d 100644 --- a/lib/Dialect/ESI/runtime/cpp/lib/Accelerator.cpp +++ b/lib/Dialect/ESI/runtime/cpp/lib/Accelerator.cpp @@ -34,7 +34,14 @@ using namespace esi::services; namespace esi { AcceleratorConnection::AcceleratorConnection(Context &ctxt) - : ctxt(ctxt), serviceThread(std::make_unique()) {} + : ctxt(ctxt), serviceThread(nullptr) {} +AcceleratorConnection::~AcceleratorConnection() { disconnect(); } + +AcceleratorServiceThread *AcceleratorConnection::getServiceThread() { + if (!serviceThread) + serviceThread = std::make_unique(); + return serviceThread.get(); +} services::Service *AcceleratorConnection::getService(Service::Type svcType, AppIDPath id, @@ -54,6 +61,13 @@ services::Service *AcceleratorConnection::getService(Service::Type svcType, return cacheEntry.get(); } +Accelerator * +AcceleratorConnection::takeOwnership(std::unique_ptr acc) { + Accelerator *ret = acc.get(); + ownedAccelerators.push_back(std::move(acc)); + return ret; +} + /// Get the path to the currently running executable. static std::filesystem::path getExePath() { #ifdef __linux__ @@ -224,18 +238,27 @@ struct AcceleratorServiceThread::Impl { addListener(std::initializer_list listenPorts, std::function callback); + void addTask(std::function task) { + std::lock_guard g(m); + taskList.push_back(task); + } + private: void loop(); volatile bool shutdown = false; std::thread me; - // Protect the listeners std::map. - std::mutex listenerMutex; + // Protect the shared data structures. + std::mutex m; + // Map of read ports to callbacks. std::map, std::future>> listeners; + + /// Tasks which should be called on every loop iteration. + std::vector> taskList; }; void AcceleratorServiceThread::Impl::loop() { @@ -245,6 +268,7 @@ void AcceleratorServiceThread::Impl::loop() { std::function, MessageData>> portUnlockWorkList; + std::vector> taskListCopy; MessageData data; while (!shutdown) { @@ -256,7 +280,7 @@ void AcceleratorServiceThread::Impl::loop() { // Check and gather data from all the read ports we are monitoring. Put the // callbacks to be called later so we can release the lock. { - std::lock_guard g(listenerMutex); + std::lock_guard g(m); for (auto &[channel, cbfPair] : listeners) { assert(channel && "Null channel in listener list"); std::future &f = cbfPair.second; @@ -273,13 +297,22 @@ void AcceleratorServiceThread::Impl::loop() { // Clear the worklist for the next iteration. portUnlockWorkList.clear(); + + // Call any tasks that have been added. Copy it first so we can release the + // lock ASAP. + { + std::lock_guard g(m); + taskListCopy = taskList; + } + for (auto &task : taskListCopy) + task(); } } void AcceleratorServiceThread::Impl::addListener( std::initializer_list listenPorts, std::function callback) { - std::lock_guard g(listenerMutex); + std::lock_guard g(m); for (auto port : listenPorts) { if (listeners.count(port)) throw std::runtime_error("Port already has a listener"); @@ -312,6 +345,11 @@ void AcceleratorServiceThread::addListener( impl->addListener(listenPorts, callback); } +void AcceleratorServiceThread::addPoll(HWModule &module) { + assert(impl && "Service thread not running"); + impl->addTask([&module]() { module.poll(); }); +} + void AcceleratorConnection::disconnect() { if (serviceThread) { serviceThread->stop(); diff --git a/lib/Dialect/ESI/runtime/cpp/lib/Design.cpp b/lib/Dialect/ESI/runtime/cpp/lib/Design.cpp index 8e5bd5e45f0d..9e54e694e449 100644 --- a/lib/Dialect/ESI/runtime/cpp/lib/Design.cpp +++ b/lib/Dialect/ESI/runtime/cpp/lib/Design.cpp @@ -47,4 +47,13 @@ HWModule::HWModule(std::optional info, childIndex(buildIndex(this->children)), services(services), ports(std::move(ports)), portIndex(buildIndex(this->ports)) {} +bool HWModule::poll() { + bool result = false; + for (auto &port : ports) + result |= port->poll(); + for (auto &child : children) + result |= child->poll(); + return result; +} + } // namespace esi diff --git a/lib/Dialect/ESI/runtime/cpp/lib/Manifest.cpp b/lib/Dialect/ESI/runtime/cpp/lib/Manifest.cpp index 7e4fd84c5034..62d16feec419 100644 --- a/lib/Dialect/ESI/runtime/cpp/lib/Manifest.cpp +++ b/lib/Dialect/ESI/runtime/cpp/lib/Manifest.cpp @@ -534,9 +534,8 @@ std::vector Manifest::getModuleInfos() const { return ret; } -std::unique_ptr -Manifest::buildAccelerator(AcceleratorConnection &acc) const { - return impl->buildAccelerator(acc); +Accelerator *Manifest::buildAccelerator(AcceleratorConnection &acc) const { + return acc.takeOwnership(impl->buildAccelerator(acc)); } const std::vector &Manifest::getTypeTable() const { diff --git a/lib/Dialect/ESI/runtime/cpp/lib/Ports.cpp b/lib/Dialect/ESI/runtime/cpp/lib/Ports.cpp index 29f858defa16..cd5fd6596811 100644 --- a/lib/Dialect/ESI/runtime/cpp/lib/Ports.cpp +++ b/lib/Dialect/ESI/runtime/cpp/lib/Ports.cpp @@ -47,7 +47,7 @@ void ReadChannelPort::connect(std::function callback, throw std::runtime_error("Channel already connected"); mode = Mode::Callback; this->callback = callback; - ChannelPort::connect(bufferSize); + connectImpl(bufferSize); } void ReadChannelPort::connect(std::optional bufferSize) { @@ -71,7 +71,7 @@ void ReadChannelPort::connect(std::optional bufferSize) { } return true; }; - ChannelPort::connect(bufferSize); + connectImpl(bufferSize); } std::future ReadChannelPort::readAsync() { diff --git a/lib/Dialect/ESI/runtime/cpp/lib/backends/Cosim.cpp b/lib/Dialect/ESI/runtime/cpp/lib/backends/Cosim.cpp index 86a4685fd58b..42cc8bc25150 100644 --- a/lib/Dialect/ESI/runtime/cpp/lib/backends/Cosim.cpp +++ b/lib/Dialect/ESI/runtime/cpp/lib/backends/Cosim.cpp @@ -126,6 +126,7 @@ CosimAccelerator::CosimAccelerator(Context &ctxt, std::string hostname, rpcClient = new StubContainer(ChannelServer::NewStub(channel)); } CosimAccelerator::~CosimAccelerator() { + disconnect(); if (rpcClient) delete rpcClient; channels.clear(); @@ -418,23 +419,23 @@ class CosimHostMem : public HostMem { this->size = size; } virtual ~CosimHostMemRegion() { free(ptr); } - virtual void *getPtr() const { return ptr; } - virtual std::size_t getSize() const { return size; } + virtual void *getPtr() const override { return ptr; } + virtual std::size_t getSize() const override { return size; } private: void *ptr; std::size_t size; }; - virtual std::unique_ptr allocate(std::size_t size, - HostMem::Options opts) const { + virtual std::unique_ptr + allocate(std::size_t size, HostMem::Options opts) const override { return std::unique_ptr(new CosimHostMemRegion(size)); } virtual bool mapMemory(void *ptr, std::size_t size, - HostMem::Options opts) const { + HostMem::Options opts) const override { return true; } - virtual void unmapMemory(void *ptr) const {} + virtual void unmapMemory(void *ptr) const override {} }; } // namespace diff --git a/lib/Dialect/ESI/runtime/cpp/lib/backends/Trace.cpp b/lib/Dialect/ESI/runtime/cpp/lib/backends/Trace.cpp index b93df860028d..a69f657b75df 100644 --- a/lib/Dialect/ESI/runtime/cpp/lib/backends/Trace.cpp +++ b/lib/Dialect/ESI/runtime/cpp/lib/backends/Trace.cpp @@ -135,6 +135,7 @@ TraceAccelerator::TraceAccelerator(Context &ctxt, Mode mode, : AcceleratorConnection(ctxt) { impl = std::make_unique(mode, manifestJson, traceFile); } +TraceAccelerator::~TraceAccelerator() { disconnect(); } Service *TraceAccelerator::createService(Service::Type svcType, AppIDPath idPath, std::string implName, @@ -197,22 +198,7 @@ class ReadTraceChannelPort : public ReadChannelPort { : ReadChannelPort(type) {} ~ReadTraceChannelPort() { disconnect(); } - void disconnect() override { - ReadChannelPort::disconnect(); - if (!dataPushThread.joinable()) - return; - shutdown = true; - shutdownCV.notify_all(); - dataPushThread.join(); - } - private: - void connectImpl(std::optional bufferSize) override { - assert(!dataPushThread.joinable() && "already connected"); - shutdown = false; - dataPushThread = std::thread(&ReadTraceChannelPort::dataPushLoop, this); - } - MessageData genMessage() { std::ptrdiff_t numBits = getType()->getBitWidth(); if (numBits < 0) @@ -227,19 +213,7 @@ class ReadTraceChannelPort : public ReadChannelPort { return MessageData(bytes); } - void dataPushLoop() { - std::mutex m; - std::unique_lock lock(m); - while (!shutdown) { - shutdownCV.wait_for(lock, std::chrono::milliseconds(100)); - while (this->callback(genMessage())) - shutdownCV.wait_for(lock, std::chrono::milliseconds(10)); - } - } - - std::thread dataPushThread; - std::condition_variable shutdownCV; - std::atomic shutdown; + bool pollImpl() override { return callback(genMessage()); } }; } // namespace @@ -289,8 +263,8 @@ class TraceHostMem : public HostMem { impl.write("HostMem") << "free " << ptr << std::endl; free(ptr); } - virtual void *getPtr() const { return ptr; } - virtual std::size_t getSize() const { return size; } + virtual void *getPtr() const override { return ptr; } + virtual std::size_t getSize() const override { return size; } private: void *ptr; @@ -298,8 +272,8 @@ class TraceHostMem : public HostMem { TraceAccelerator::Impl &impl; }; - virtual std::unique_ptr allocate(std::size_t size, - HostMem::Options opts) const { + virtual std::unique_ptr + allocate(std::size_t size, HostMem::Options opts) const override { auto ret = std::unique_ptr(new TraceHostMemRegion(size, impl)); impl.write("HostMem 0x") @@ -309,14 +283,14 @@ class TraceHostMem : public HostMem { return ret; } virtual bool mapMemory(void *ptr, std::size_t size, - HostMem::Options opts) const { + HostMem::Options opts) const override { impl.write("HostMem") << "map 0x" << ptr << " size " << size << " bytes. Writeable: " << opts.writeable << ", useLargePages: " << opts.useLargePages << std::endl; return true; } - virtual void unmapMemory(void *ptr) const { + virtual void unmapMemory(void *ptr) const override { impl.write("HostMem") << "unmap 0x" << ptr << std::endl; } diff --git a/lib/Dialect/ESI/runtime/cpp/lib/backends/Xrt.cpp b/lib/Dialect/ESI/runtime/cpp/lib/backends/Xrt.cpp index 694f069852c5..26b7ada78384 100644 --- a/lib/Dialect/ESI/runtime/cpp/lib/backends/Xrt.cpp +++ b/lib/Dialect/ESI/runtime/cpp/lib/backends/Xrt.cpp @@ -78,6 +78,7 @@ XrtAccelerator::XrtAccelerator(Context &ctxt, std::string xclbin, : AcceleratorConnection(ctxt) { impl = make_unique(xclbin, device_id); } +XrtAccelerator::~XrtAccelerator() { disconnect(); } namespace { class XrtMMIO : public MMIO { diff --git a/lib/Dialect/ESI/runtime/cpp/tools/esiquery.cpp b/lib/Dialect/ESI/runtime/cpp/tools/esiquery.cpp index a093988cf1bd..450cabece943 100644 --- a/lib/Dialect/ESI/runtime/cpp/tools/esiquery.cpp +++ b/lib/Dialect/ESI/runtime/cpp/tools/esiquery.cpp @@ -121,10 +121,10 @@ void printInstance(std::ostream &os, const HWModule *d, void printHier(std::ostream &os, AcceleratorConnection &acc) { Manifest manifest(acc.getCtxt(), acc.getService()->getJsonManifest()); - std::unique_ptr design = manifest.buildAccelerator(acc); + Accelerator *design = manifest.buildAccelerator(acc); os << "********************************" << std::endl; os << "* Design hierarchy" << std::endl; os << "********************************" << std::endl; os << std::endl; - printInstance(os, design.get()); + printInstance(os, design); } diff --git a/lib/Dialect/ESI/runtime/cpp/tools/esitester.cpp b/lib/Dialect/ESI/runtime/cpp/tools/esitester.cpp index fa34d4cad4be..d2563616d645 100644 --- a/lib/Dialect/ESI/runtime/cpp/tools/esitester.cpp +++ b/lib/Dialect/ESI/runtime/cpp/tools/esitester.cpp @@ -50,9 +50,10 @@ int main(int argc, const char *argv[]) { std::unique_ptr acc = ctxt.connect(backend, conn); const auto &info = *acc->getService(); Manifest manifest(ctxt, info.getJsonManifest()); - std::unique_ptr accel = manifest.buildAccelerator(*acc); + Accelerator *accel = manifest.buildAccelerator(*acc); + acc->getServiceThread()->addPoll(*accel); - registerCallbacks(accel.get()); + registerCallbacks(accel); if (cmd == "loop") { while (true) { diff --git a/lib/Dialect/ESI/runtime/python/esiaccel/esiCppAccel.cpp b/lib/Dialect/ESI/runtime/python/esiaccel/esiCppAccel.cpp index c73df6872ca5..08061b77e84a 100644 --- a/lib/Dialect/ESI/runtime/python/esiaccel/esiCppAccel.cpp +++ b/lib/Dialect/ESI/runtime/python/esiaccel/esiCppAccel.cpp @@ -270,8 +270,14 @@ PYBIND11_MODULE(esiCppAccel, m) { py::class_(m, "Manifest") .def(py::init()) .def_property_readonly("api_version", &Manifest::getApiVersion) - .def("build_accelerator", &Manifest::buildAccelerator, - py::return_value_policy::take_ownership) + .def( + "build_accelerator", + [&](Manifest &m, AcceleratorConnection &conn) { + auto acc = m.buildAccelerator(conn); + conn.getServiceThread()->addPoll(*acc); + return acc; + }, + py::return_value_policy::reference) .def_property_readonly("type_table", &Manifest::getTypeTable) .def_property_readonly("module_infos", &Manifest::getModuleInfos); } From 5bc663c4460ad3c28f0a9b32f7b7a455efbe3902 Mon Sep 17 00:00:00 2001 From: John Demme Date: Thu, 8 Aug 2024 12:21:02 +0000 Subject: [PATCH 036/119] [ESI][Runtime] Minor cleanups - Got an integration test which wasn't running working. - Fixed a pybind11_stubgen error. - Fixed an AppID constructor warning. - Mitigated a poor hash function. --- .../Dialect/ESI/runtime/callback.mlir | 7 +++--- .../ESI/runtime/cpp/include/esi/Common.h | 1 - .../ESI/runtime/cpp/include/esi/Utils.h | 5 +++++ lib/Dialect/ESI/runtime/cpp/lib/Services.cpp | 22 +++++++++++++++---- .../runtime/python/esiaccel/esiCppAccel.cpp | 14 ++++++++---- 5 files changed, 37 insertions(+), 12 deletions(-) diff --git a/integration_test/Dialect/ESI/runtime/callback.mlir b/integration_test/Dialect/ESI/runtime/callback.mlir index 541fdb5f57e8..e383d88b9af5 100644 --- a/integration_test/Dialect/ESI/runtime/callback.mlir +++ b/integration_test/Dialect/ESI/runtime/callback.mlir @@ -1,12 +1,13 @@ -// REQUIRES: esi-cosim, esi-runtime, rtl-sim, esitester +// REQUIRES: esi-cosim, esi-runtime, rtl-sim // RUN: rm -rf %t6 && mkdir %t6 && cd %t6 // RUN: circt-opt %s --esi-connect-services --esi-appid-hier=top=top --esi-build-manifest=top=top --esi-clean-metadata > %t4.mlir // RUN: circt-opt %t4.mlir --lower-esi-to-physical --lower-esi-bundles --lower-esi-ports --lower-esi-to-hw=platform=cosim --lower-seq-to-sv --lower-hwarith-to-hw --canonicalize --export-split-verilog -o %t3.mlir // RUN: cd .. -// RUN: esi-cosim.py --source %t6 --top top -- esitester cosim env | FileCheck %s +// RUN: esi-cosim.py --source %t6 --top top -- esitester cosim env wait | FileCheck %s -hw.module @EsiTesterTop(in %clk : !seq.clock, in %rst : i1) { +hw.module @top(in %clk : !seq.clock, in %rst : i1) { hw.instance "PrintfExample" sym @PrintfExample @PrintfExample(clk: %clk: !seq.clock, rst: %rst: i1) -> () + esi.service.instance #esi.appid<"cosim_default"> impl as "cosim" (%clk, %rst) : (!seq.clock, i1) -> () } // CHECK: PrintfExample: 7 diff --git a/lib/Dialect/ESI/runtime/cpp/include/esi/Common.h b/lib/Dialect/ESI/runtime/cpp/include/esi/Common.h index 7d4e16868336..997fb53158e8 100644 --- a/lib/Dialect/ESI/runtime/cpp/include/esi/Common.h +++ b/lib/Dialect/ESI/runtime/cpp/include/esi/Common.h @@ -34,7 +34,6 @@ struct AppID { std::string name; std::optional idx; - AppID(const AppID &) = default; AppID(const std::string &name, std::optional idx = std::nullopt) : name(name), idx(idx) {} diff --git a/lib/Dialect/ESI/runtime/cpp/include/esi/Utils.h b/lib/Dialect/ESI/runtime/cpp/include/esi/Utils.h index 0101095c84f5..0d11be95473c 100644 --- a/lib/Dialect/ESI/runtime/cpp/include/esi/Utils.h +++ b/lib/Dialect/ESI/runtime/cpp/include/esi/Utils.h @@ -28,6 +28,11 @@ namespace utils { // Very basic base64 encoding. void encodeBase64(const void *data, size_t size, std::string &out); +/// C++'s stdlib doesn't have a hash_combine function. This is a simple one. +inline size_t hash_combine(size_t h1, size_t h2) { + return h1 + 0x9e3779b9 + (h2 << 6) + (h2 >> 2); +} + /// Thread safe queue. Just wraps std::queue protected with a lock. Long term, /// we need to avoid copying data. It has a lot of data copies currently. template diff --git a/lib/Dialect/ESI/runtime/cpp/lib/Services.cpp b/lib/Dialect/ESI/runtime/cpp/lib/Services.cpp index 854fcba4c7d7..fadd3ace8037 100644 --- a/lib/Dialect/ESI/runtime/cpp/lib/Services.cpp +++ b/lib/Dialect/ESI/runtime/cpp/lib/Services.cpp @@ -145,13 +145,27 @@ CallService::getPort(AppIDPath id, const BundleType *type, return new Callback(acc, id.back(), channels); } +ReadChannelPort &getRead(const std::map &channels, + const std::string &name) { + auto f = channels.find(name); + if (f == channels.end()) + throw std::runtime_error("CallService must have an '" + name + "' channel"); + return dynamic_cast(f->second); +} + +WriteChannelPort &getWrite(const std::map &channels, + const std::string &name) { + auto f = channels.find(name); + if (f == channels.end()) + throw std::runtime_error("CallService must have an '" + name + "' channel"); + return dynamic_cast(f->second); +} + CallService::Callback::Callback( AcceleratorConnection &acc, AppID id, const std::map &channels) - : ServicePort(id, channels), - arg(dynamic_cast(channels.at("arg"))), - result(dynamic_cast(channels.at("result"))), - acc(acc) { + : ServicePort(id, channels), arg(getRead(channels, "arg")), + result(getWrite(channels, "result")), acc(acc) { if (channels.size() != 2) throw std::runtime_error("CallService must have exactly two channels"); } diff --git a/lib/Dialect/ESI/runtime/python/esiaccel/esiCppAccel.cpp b/lib/Dialect/ESI/runtime/python/esiaccel/esiCppAccel.cpp index 08061b77e84a..ab8b94e81d67 100644 --- a/lib/Dialect/ESI/runtime/python/esiaccel/esiCppAccel.cpp +++ b/lib/Dialect/ESI/runtime/python/esiaccel/esiCppAccel.cpp @@ -161,13 +161,19 @@ PYBIND11_MODULE(esiCppAccel, m) { }) .def("__eq__", [](AppID &a, AppID &b) { return a == b; }) .def("__hash__", [](AppID &id) { - // TODO: This is a bad hash function. Replace it. - return std::hash{}(id.name) ^ - (std::hash{}(id.idx.value_or(-1)) << 1); + return utils::hash_combine(std::hash{}(id.name), + std::hash{}(id.idx.value_or(-1))); }); + // py::class_>(m, "MessageDataFuture"); py::class_>(m, "MessageDataFuture") - .def("valid", &std::future::valid) + .def("valid", + [](std::future &f) { + // For some reason, if we just pass the function pointer, pybind11 + // sees `std::__basic_future` as the type and pybind11_stubgen + // emits an error. + return f.valid(); + }) .def("wait", &std::future::wait) .def("get", [](std::future &f) { MessageData data = f.get(); From 04ba6a3fa3947fd52ed1f686b187eb2e288cce5d Mon Sep 17 00:00:00 2001 From: Fabian Schuiki Date: Thu, 8 Aug 2024 08:57:55 -0700 Subject: [PATCH 037/119] [Moore] Add FVIntegerAttr (#7461) Add the `FVIntegerAttr`, an attribute containing an `FVInt` value. This allows four-valued integer constants to be used as attributes on operations and for constant folding to occur on such values. In contrast to the builtin `IntegerAttr`, `FVIntegerAttr` does not have a type yet. The type can be added later as soon as we have a concrete use case. I expect us to eventually move `FVIntegerAttr` into the HW dialect once we are happy with its design. Other parts of CIRCT will eventually want to reason about four-valued integers as well. The parsing and printing of the attribute tries to make the `FVInt` read like a plain old `APInt` when there are no X or Z bits present. Otherwise it falls back to printing as hexadecimal or binary number. To distinguish the different representations and to allow constants with X or Z to be parsed as keywords, a `h` and `b` prefix is used for the hexadecimal and binary formatting, respectively. Examples of the attribute: #moore.fvint<42 : 32> #moore.fvint<-42 : 32> #moore.fvint<1234567890123456789012345678901234567890 : 131> #moore.fvint #moore.fvint --- include/circt/Dialect/Moore/CMakeLists.txt | 6 +- include/circt/Dialect/Moore/Moore.td | 1 + include/circt/Dialect/Moore/MooreAttributes.h | 25 +++++++ .../circt/Dialect/Moore/MooreAttributes.td | 22 ++++++ include/circt/Dialect/Moore/MooreDialect.td | 3 + include/circt/Support/FVInt.h | 27 ++++++++ lib/Dialect/Moore/CMakeLists.txt | 1 + lib/Dialect/Moore/MooreAttributes.cpp | 68 +++++++++++++++++++ lib/Dialect/Moore/MooreDialect.cpp | 3 +- lib/Support/FVInt.cpp | 50 ++++++++++++++ test/Dialect/Moore/attrs-error.mlir | 4 ++ test/Dialect/Moore/attrs.mlir | 12 ++++ 12 files changed, 217 insertions(+), 5 deletions(-) create mode 100644 include/circt/Dialect/Moore/MooreAttributes.h create mode 100644 include/circt/Dialect/Moore/MooreAttributes.td create mode 100644 lib/Dialect/Moore/MooreAttributes.cpp create mode 100644 test/Dialect/Moore/attrs-error.mlir create mode 100644 test/Dialect/Moore/attrs.mlir diff --git a/include/circt/Dialect/Moore/CMakeLists.txt b/include/circt/Dialect/Moore/CMakeLists.txt index 0fdc97c38bf8..38609ece9bcf 100644 --- a/include/circt/Dialect/Moore/CMakeLists.txt +++ b/include/circt/Dialect/Moore/CMakeLists.txt @@ -9,10 +9,8 @@ mlir_tablegen(MooreEnums.cpp.inc -gen-enum-defs) add_public_tablegen_target(CIRCTMooreEnumsIncGen) add_dependencies(circt-headers CIRCTMooreEnumsIncGen) -mlir_tablegen(MooreAttributes.h.inc -gen-attrdef-decls - -attrdefs-dialect MooreDialect) -mlir_tablegen(MooreAttributes.cpp.inc -gen-attrdef-defs - -attrdefs-dialect MooreDialect) +mlir_tablegen(MooreAttributes.h.inc -gen-attrdef-decls -attrdefs-dialect moore) +mlir_tablegen(MooreAttributes.cpp.inc -gen-attrdef-defs -attrdefs-dialect moore) add_public_tablegen_target(CIRCTMooreAttributesIncGen) add_dependencies(circt-headers CIRCTMooreAttributesIncGen) diff --git a/include/circt/Dialect/Moore/Moore.td b/include/circt/Dialect/Moore/Moore.td index 5c6284db230b..ab5bb41d3e43 100644 --- a/include/circt/Dialect/Moore/Moore.td +++ b/include/circt/Dialect/Moore/Moore.td @@ -15,6 +15,7 @@ include "circt/Dialect/Moore/MooreDialect.td" include "circt/Dialect/Moore/MooreTypes.td" +include "circt/Dialect/Moore/MooreAttributes.td" include "circt/Dialect/Moore/MooreOps.td" #endif // CIRCT_DIALECT_MOORE_MOORE diff --git a/include/circt/Dialect/Moore/MooreAttributes.h b/include/circt/Dialect/Moore/MooreAttributes.h new file mode 100644 index 000000000000..c2369cc97fa5 --- /dev/null +++ b/include/circt/Dialect/Moore/MooreAttributes.h @@ -0,0 +1,25 @@ +//===- MooreAttributes.h - Declare Moore dialect attributes ------*- C++-*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// This file declares the attributes for the Moore dialect. +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_MOORE_MOOREATTRIBUTES_H +#define CIRCT_DIALECT_MOORE_MOOREATTRIBUTES_H + +#include "circt/Support/FVInt.h" +#include "mlir/IR/Attributes.h" +#include "mlir/IR/BuiltinAttributes.h" +#include "mlir/IR/BuiltinTypes.h" + +// Include generated attributes. +#define GET_ATTRDEF_CLASSES +#include "circt/Dialect/Moore/MooreAttributes.h.inc" + +#endif // CIRCT_DIALECT_MOORE_MOOREATTRIBUTES_H diff --git a/include/circt/Dialect/Moore/MooreAttributes.td b/include/circt/Dialect/Moore/MooreAttributes.td new file mode 100644 index 000000000000..d184553bf3c4 --- /dev/null +++ b/include/circt/Dialect/Moore/MooreAttributes.td @@ -0,0 +1,22 @@ +//===- MooreAttributes.td - Moore attribute definitions ----*- tablegen -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_MOORE_MOOREATTRIBUTES +#define CIRCT_DIALECT_MOORE_MOOREATTRIBUTES + +include "circt/Dialect/Moore/MooreDialect.td" +include "mlir/IR/AttrTypeBase.td" + +def FVIntegerAttr : AttrDef { + let mnemonic = "fvint"; + let summary = "An attribute containing a four-valued integer"; + let parameters = (ins "FVInt":$value); + let hasCustomAssemblyFormat = 1; +} + +#endif // CIRCT_DIALECT_MOORE_MOOREATTRIBUTES diff --git a/include/circt/Dialect/Moore/MooreDialect.td b/include/circt/Dialect/Moore/MooreDialect.td index 84f57a5285f4..97c0fd0ac055 100644 --- a/include/circt/Dialect/Moore/MooreDialect.td +++ b/include/circt/Dialect/Moore/MooreDialect.td @@ -29,11 +29,14 @@ def MooreDialect : Dialect { let extraClassDeclaration = [{ /// Register all Moore types. void registerTypes(); + /// Register all Moore attributes. + void registerAttributes(); /// Type parsing and printing. Type parseType(DialectAsmParser &parser) const override; void printType(Type, DialectAsmPrinter &) const override; }]; + let useDefaultAttributePrinterParser = 1; let useDefaultTypePrinterParser = 0; let dependentDialects = ["hw::HWDialect"]; } diff --git a/include/circt/Support/FVInt.h b/include/circt/Support/FVInt.h index f43c74f56056..cb7e0e14a10c 100644 --- a/include/circt/Support/FVInt.h +++ b/include/circt/Support/FVInt.h @@ -644,6 +644,33 @@ inline raw_ostream &operator<<(raw_ostream &os, const FVInt &value) { llvm::hash_code hash_value(const FVInt &a); +/// Print a four-valued integer usign an `AsmPrinter`. This produces the +/// following output formats: +/// +/// - Decimal notation if the integer has no unknown bits. The sign bit is used +/// to determine whether the value is printed as negative number or not. +/// - Hexadecimal notation with a leading `h` if the integer the bits in each +/// hex digit are either all known, all X, or all Z. +/// - Binary notation with a leading `b` in all other cases. +void printFVInt(AsmPrinter &p, const FVInt &value); + +/// Parse a four-valued integer using an `AsmParser`. This accepts the following +/// formats: +/// +/// - `42`/`-42`: positive or negative integer in decimal notation. The sign bit +/// of the result indicates whether the value was negative. Cannot contain +/// unknown X or Z digits. +/// - `h123456789ABCDEF0XZ`: signless integer in hexadecimal notation. Can +/// contain unknown X or Z digits. +/// - `b10XZ`: signless integer in binary notation. Can contain unknown X or Z +/// digits. +/// +/// The result has enough bits to fully represent the parsed integer, and to +/// have the sign bit encode whether the integer was written as a negative +/// number in the input. The result's bit width may be larger than the minimum +/// number of bits required to represent its value. +ParseResult parseFVInt(AsmParser &p, FVInt &result); + } // namespace circt #endif // CIRCT_SUPPORT_FVINT_H diff --git a/lib/Dialect/Moore/CMakeLists.txt b/lib/Dialect/Moore/CMakeLists.txt index bda0ea7713cc..f8559fa4f2ad 100644 --- a/lib/Dialect/Moore/CMakeLists.txt +++ b/lib/Dialect/Moore/CMakeLists.txt @@ -1,4 +1,5 @@ add_circt_dialect_library(CIRCTMoore + MooreAttributes.cpp MooreDialect.cpp MooreOps.cpp MooreTypes.cpp diff --git a/lib/Dialect/Moore/MooreAttributes.cpp b/lib/Dialect/Moore/MooreAttributes.cpp new file mode 100644 index 000000000000..4cb8dd159b01 --- /dev/null +++ b/lib/Dialect/Moore/MooreAttributes.cpp @@ -0,0 +1,68 @@ +//===- MooreAttributes.cpp - Implement the Moore attributes ---------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// This file implements the Moore dialect attributes. +// +//===----------------------------------------------------------------------===// + +#include "circt/Dialect/Moore/MooreAttributes.h" +#include "circt/Dialect/Moore/MooreDialect.h" +#include "circt/Dialect/Moore/MooreTypes.h" +#include "mlir/IR/DialectImplementation.h" +#include "llvm/ADT/TypeSwitch.h" + +using namespace circt; +using namespace circt::moore; +using mlir::AsmParser; +using mlir::AsmPrinter; + +//===----------------------------------------------------------------------===// +// FVIntegerAttr +//===----------------------------------------------------------------------===// + +Attribute FVIntegerAttr::parse(AsmParser &p, Type) { + // Parse the value and width specifier. + FVInt value; + unsigned width; + llvm::SMLoc widthLoc; + if (p.parseLess() || parseFVInt(p, value) || p.parseColon() || + p.getCurrentLocation(&widthLoc) || p.parseInteger(width) || + p.parseGreater()) + return {}; + + // Make sure the integer fits into the requested number of bits. + unsigned neededBits = + value.isNegative() ? value.getSignificantBits() : value.getActiveBits(); + if (width < neededBits) { + p.emitError(widthLoc) << "integer literal requires at least " << neededBits + << " bits, but attribute specifies only " << width; + return {}; + } + + return FVIntegerAttr::get(p.getContext(), value.sextOrTrunc(width)); +} + +void FVIntegerAttr::print(AsmPrinter &p) const { + p << "<"; + printFVInt(p, getValue()); + p << " : " << getValue().getBitWidth() << ">"; +} + +//===----------------------------------------------------------------------===// +// Generated logic +//===----------------------------------------------------------------------===// + +#define GET_ATTRDEF_CLASSES +#include "circt/Dialect/Moore/MooreAttributes.cpp.inc" + +void MooreDialect::registerAttributes() { + addAttributes< +#define GET_ATTRDEF_LIST +#include "circt/Dialect/Moore/MooreAttributes.cpp.inc" + >(); +} diff --git a/lib/Dialect/Moore/MooreDialect.cpp b/lib/Dialect/Moore/MooreDialect.cpp index 902fbb27e730..a9cc2a9c8b4b 100644 --- a/lib/Dialect/Moore/MooreDialect.cpp +++ b/lib/Dialect/Moore/MooreDialect.cpp @@ -21,8 +21,9 @@ using namespace circt::moore; //===----------------------------------------------------------------------===// void MooreDialect::initialize() { - // Register types. + // Register types and attributes. registerTypes(); + registerAttributes(); // Register operations. addOperations< diff --git a/lib/Support/FVInt.cpp b/lib/Support/FVInt.cpp index 838de02842ad..caa2fc61810e 100644 --- a/lib/Support/FVInt.cpp +++ b/lib/Support/FVInt.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include "circt/Support/FVInt.h" +#include "mlir/IR/OpImplementation.h" #include "llvm/ADT/StringExtras.h" #define DEBUG_TYPE "fvint" @@ -142,3 +143,52 @@ void FVInt::print(raw_ostream &os) const { llvm::hash_code circt::hash_value(const FVInt &a) { return llvm::hash_combine(a.getRawValue(), a.getRawUnknown()); } + +void circt::printFVInt(AsmPrinter &p, const FVInt &value) { + SmallString<32> buffer; + if (value.isNegative() && (-value).tryToString(buffer)) { + p << "-" << buffer; + } else if (value.tryToString(buffer)) { + p << buffer; + } else if (value.tryToString(buffer, 16)) { + p << "h" << buffer; + } else { + value.tryToString(buffer, 2); + p << "b" << buffer; + } +} + +ParseResult circt::parseFVInt(AsmParser &p, FVInt &result) { + // Parse the value as either a keyword (`b[01XZ]+` for binary or + // `h[0-9A-FXZ]+` for hexadecimal), or an integer value (for decimal). + FVInt value; + StringRef strValue; + auto valueLoc = p.getCurrentLocation(); + if (succeeded(p.parseOptionalKeyword(&strValue))) { + // Determine the radix based on the `b` or `h` prefix. + unsigned base = 0; + if (strValue.consume_front("b")) { + base = 2; + } else if (strValue.consume_front("h")) { + base = 16; + } else { + return p.emitError(valueLoc) << "expected `b` or `h` prefix"; + } + + // Parse the value. + auto parsedValue = FVInt::tryFromString(strValue, base); + if (!parsedValue) { + return p.emitError(valueLoc) + << "expected base-" << base << " four-valued integer"; + } + + // Add a zero bit at the top to ensure the value reads as positive. + result = parsedValue->zext(parsedValue->getBitWidth() + 1); + } else { + APInt intValue; + if (p.parseInteger(intValue)) + return failure(); + result = std::move(intValue); + } + return success(); +} diff --git a/test/Dialect/Moore/attrs-error.mlir b/test/Dialect/Moore/attrs-error.mlir new file mode 100644 index 000000000000..89736fb5b272 --- /dev/null +++ b/test/Dialect/Moore/attrs-error.mlir @@ -0,0 +1,4 @@ +// RUN: circt-opt --verify-diagnostics --split-input-file %s + +// expected-error @below {{integer literal requires at least 6 bits, but attribute specifies only 3}} +hw.constant false {foo = #moore.fvint<42 : 3>} diff --git a/test/Dialect/Moore/attrs.mlir b/test/Dialect/Moore/attrs.mlir new file mode 100644 index 000000000000..3337023dc342 --- /dev/null +++ b/test/Dialect/Moore/attrs.mlir @@ -0,0 +1,12 @@ +// RUN: circt-opt %s | circt-opt | FileCheck %s + +// CHECK: #moore.fvint<42 : 32> +hw.constant false {foo = #moore.fvint<42 : 32>} +// CHECK: #moore.fvint<-42 : 32> +hw.constant false {foo = #moore.fvint<-42 : 32>} +// CHECK: #moore.fvint<1234567890123456789012345678901234567890 : 131> +hw.constant false {foo = #moore.fvint<1234567890123456789012345678901234567890 : 131>} +// CHECK: #moore.fvint +hw.constant false {foo = #moore.fvint} +// CHECK: #moore.fvint +hw.constant false {foo = #moore.fvint} From 27968fcfe78c4ed9bdf7793e5acb1c33afda690c Mon Sep 17 00:00:00 2001 From: Schuyler Eldridge Date: Thu, 8 Aug 2024 13:12:15 -0400 Subject: [PATCH 038/119] [Support] Sort CMakeLists lines, NFC Sort lines in a CMakeLists.txt file. I'm doing this because I want to add something to this in a later commit and would like to put it in a sane place. h/t @dtzSiFive for the suggestion. Signed-off-by: Schuyler Eldridge --- lib/Support/CMakeLists.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/Support/CMakeLists.txt b/lib/Support/CMakeLists.txt index 1a38545425ab..1a9a9b79bd82 100644 --- a/lib/Support/CMakeLists.txt +++ b/lib/Support/CMakeLists.txt @@ -10,19 +10,19 @@ add_circt_library(CIRCTSupport BackedgeBuilder.cpp CustomDirectiveImpl.cpp Debug.cpp - FieldRef.cpp FVInt.cpp + FieldRef.cpp + InstanceGraph.cpp JSON.cpp LoweringOptions.cpp Naming.cpp + ParsingUtils.cpp Passes.cpp Path.cpp PrettyPrinter.cpp PrettyPrinterHelpers.cpp - ParsingUtils.cpp SymCache.cpp ValueMapper.cpp - InstanceGraph.cpp "${VERSION_CPP}" LINK_LIBS PUBLIC From d8c1f6dbadbcf1fd46693a684d69cad14022f933 Mon Sep 17 00:00:00 2001 From: Fabian Schuiki Date: Thu, 8 Aug 2024 10:38:33 -0700 Subject: [PATCH 039/119] [Moore] Support four-valued integers in ConstantOp (#7463) 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 | 2 + 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 | 40 ++++++++++++------ lib/Support/FVInt.cpp | 3 +- test/Conversion/ImportVerilog/basic.sv | 8 +++- test/Conversion/ImportVerilog/errors.sv | 34 --------------- test/Conversion/MooreToCore/basic.mlir | 2 +- test/Dialect/Moore/attrs-error.mlir | 5 +++ test/Dialect/Moore/attrs.mlir | 2 + test/Dialect/Moore/basic.mlir | 14 +++++++ test/Dialect/Moore/errors.mlir | 15 ++++--- 14 files changed, 105 insertions(+), 74 deletions(-) diff --git a/include/circt/Dialect/Moore/MooreAttributes.td b/include/circt/Dialect/Moore/MooreAttributes.td index d184553bf3c4..1b052bf0bc42 100644 --- a/include/circt/Dialect/Moore/MooreAttributes.td +++ b/include/circt/Dialect/Moore/MooreAttributes.td @@ -17,6 +17,8 @@ def FVIntegerAttr : AttrDef { let summary = "An attribute containing a four-valued integer"; let parameters = (ins "FVInt":$value); let hasCustomAssemblyFormat = 1; + let convertFromStorage = "$_self.getValue()"; + 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..0e903b311bb1 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 FVIntegerAttr:$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(svint.getRawPtr(), numWords); + auto unknown = ArrayRef(svint.getRawPtr() + numWords, numWords); + return FVInt(APInt(svint.getBitWidth(), value), + APInt(svint.getBitWidth(), unknown)); + } + auto value = ArrayRef(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(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( - loc, intType, - APInt(value.getBitWidth(), - ArrayRef(value.getRawPtr(), value.getNumWords()))); + Value result = builder.create(loc, intType, fvint); if (result.getType() != type) result = builder.create(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..668bd69c6e16 100644 --- a/lib/Conversion/MooreToCore/MooreToCore.cpp +++ b/lib/Conversion/MooreToCore/MooreToCore.cpp @@ -183,8 +183,11 @@ struct ConstantOpConv : public OpConversionPattern { LogicalResult matchAndRewrite(ConstantOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override { - - rewriter.replaceOpWithNewOp(op, op.getValueAttr()); + // FIXME: Discard unknown bits and map them to 0 for now. + auto value = op.getValue().toAPInt(false); + auto type = rewriter.getIntegerType(value.getBitWidth()); + rewriter.replaceOpWithNewOp( + op, type, rewriter.getIntegerAttr(type, value)); return success(); } }; diff --git a/lib/Dialect/Moore/MooreOps.cpp b/lib/Dialect/Moore/MooreOps.cpp index eae745de174c..6920c404cd93 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,21 @@ void AssignedVarOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) { void ConstantOp::print(OpAsmPrinter &p) { p << " "; - p.printAttributeWithoutType(getValueAttr()); + printFVInt(p, getValue()); 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 (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 +420,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 +449,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/lib/Support/FVInt.cpp b/lib/Support/FVInt.cpp index caa2fc61810e..e895923d1a1c 100644 --- a/lib/Support/FVInt.cpp +++ b/lib/Support/FVInt.cpp @@ -146,7 +146,8 @@ llvm::hash_code circt::hash_value(const FVInt &a) { void circt::printFVInt(AsmPrinter &p, const FVInt &value) { SmallString<32> buffer; - if (value.isNegative() && (-value).tryToString(buffer)) { + if (value.getBitWidth() > 1 && value.isNegative() && + (-value).tryToString(buffer)) { p << "-" << buffer; } else if (value.tryToString(buffer)) { p << buffer; diff --git a/test/Conversion/ImportVerilog/basic.sv b/test/Conversion/ImportVerilog/basic.sv index 34ba203ebbfd..1e79f3beabd4 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 @@ -651,7 +657,7 @@ module Expressions; {a, b, c} = a; // CHECK: moore.concat_ref %d, %e : (!moore.ref, !moore.ref) -> {d, e} = d; - // CHECK: [[TMP1:%.+]] = moore.constant false : i1 + // CHECK: [[TMP1:%.+]] = moore.constant 0 : i1 // CHECK: [[TMP2:%.+]] = moore.concat [[TMP1]] : (!moore.i1) -> i1 // CHECK: moore.replicate [[TMP2]] : i1 -> i32 a = {32{1'b0}}; 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/Conversion/MooreToCore/basic.mlir b/test/Conversion/MooreToCore/basic.mlir index a516525be4a0..31ad9917335e 100644 --- a/test/Conversion/MooreToCore/basic.mlir +++ b/test/Conversion/MooreToCore/basic.mlir @@ -267,7 +267,7 @@ moore.module @Variable() { %b2 = moore.variable %0 : // CHECK: %true = hw.constant true - %1 = moore.constant true : i1 + %1 = moore.constant 1 : i1 // CHECK: [[CAST:%.+]] = hw.bitcast %true : (i1) -> i1 %2 = moore.conversion %1 : !moore.i1 -> !moore.l1 // CHECK: llhd.sig "l" [[CAST]] : i1 diff --git a/test/Dialect/Moore/attrs-error.mlir b/test/Dialect/Moore/attrs-error.mlir index 89736fb5b272..fdb90e70eaa0 100644 --- a/test/Dialect/Moore/attrs-error.mlir +++ b/test/Dialect/Moore/attrs-error.mlir @@ -2,3 +2,8 @@ // expected-error @below {{integer literal requires at least 6 bits, but attribute specifies only 3}} hw.constant false {foo = #moore.fvint<42 : 3>} + +// ----- + +// expected-error @below {{integer literal requires at least 1 bits, but attribute specifies only 0}} +hw.constant false {foo = #moore.fvint<1 : 0>} diff --git a/test/Dialect/Moore/attrs.mlir b/test/Dialect/Moore/attrs.mlir index 3337023dc342..5978e118ecd4 100644 --- a/test/Dialect/Moore/attrs.mlir +++ b/test/Dialect/Moore/attrs.mlir @@ -1,5 +1,7 @@ // RUN: circt-opt %s | circt-opt | FileCheck %s +// CHECK: #moore.fvint<0 : 0> +hw.constant false {foo = #moore.fvint<0 : 0>} // CHECK: #moore.fvint<42 : 32> hw.constant false {foo = #moore.fvint<42 : 32>} // CHECK: #moore.fvint<-42 : 32> diff --git a/test/Dialect/Moore/basic.mlir b/test/Dialect/Moore/basic.mlir index d1a2b3a9db1c..eb7d9b03b678 100644 --- a/test/Dialect/Moore/basic.mlir +++ b/test/Dialect/Moore/basic.mlir @@ -140,12 +140,26 @@ moore.module @Expressions( // CHECK-SAME: in [[REF_STRUCT1:%.+]] : !moore.ref> in %refStruct1: !moore.ref> ) { + // CHECK: moore.constant 0 : i0 + moore.constant 0 : i0 + // CHECK: moore.constant 0 : i1 + moore.constant 0 : i1 + // CHECK: moore.constant 1 : i1 + moore.constant 1 : i1 // 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..fa8b26bacd4c 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 // ----- @@ -74,7 +79,7 @@ moore.yield %0 : i8 // ----- -%0 = moore.constant true : i1 +%0 = moore.constant 1 : i1 %1 = moore.constant 42 : i8 %2 = moore.constant 42 : i32 @@ -87,7 +92,7 @@ moore.conditional %0 : i1 -> i32 { // ----- -%0 = moore.constant true : i1 +%0 = moore.constant 1 : i1 %1 = moore.constant 42 : i32 %2 = moore.constant 42 : i8 From da2428ac1f9544e36b25303a13821b2413991191 Mon Sep 17 00:00:00 2001 From: Will Dietz Date: Thu, 8 Aug 2024 13:55:30 -0500 Subject: [PATCH 040/119] [FIRRTL][Dedup] Reduce size of integer data hashed. (#7469) Don't always zext to size_t. Use smaller integer sizes for ValueId and add to hash together. --- lib/Dialect/FIRRTL/Transforms/Dedup.cpp | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/lib/Dialect/FIRRTL/Transforms/Dedup.cpp b/lib/Dialect/FIRRTL/Transforms/Dedup.cpp index ca0fecb251a0..1bf6c0e4b7dc 100644 --- a/lib/Dialect/FIRRTL/Transforms/Dedup.cpp +++ b/lib/Dialect/FIRRTL/Transforms/Dedup.cpp @@ -97,8 +97,8 @@ struct ModuleInfo { /// For BlockArgument's, this is the argument number. /// For OpResult's, this is the result number. struct ValueId { - uint64_t index; - uint64_t offset; + unsigned index; + unsigned offset; }; struct SymbolTarget { @@ -154,16 +154,20 @@ struct StructuralHasher { } private: - void update(const void *pointer) { - auto *addr = reinterpret_cast(&pointer); - sha.update(ArrayRef(addr, sizeof pointer)); - } - - void update(size_t value) { + /// Hash value as array of bytes. + template + void updateImpl(T value) { auto *addr = reinterpret_cast(&value); sha.update(ArrayRef(addr, sizeof value)); } + void update(const void *pointer) { updateImpl(pointer); } + void update(bool value) { updateImpl(value); } + void update(uint8_t value) { updateImpl(value); } + void update(uint16_t value) { updateImpl(value); } + void update(uint32_t value) { updateImpl(value); } + void update(uint64_t value) { updateImpl(value); } + void update(TypeID typeID) { update(typeID.getAsOpaquePointer()); } // NOLINTNEXTLINE(misc-no-recursion) @@ -209,8 +213,9 @@ struct StructuralHasher { } void update(ValueId index) { - update(index.index); - update(index.offset); + unsigned vals[2] = {index.index, index.offset}; + sha.update(ArrayRef(reinterpret_cast(vals), + sizeof(vals))); } void update(OpOperand &operand) { update(getId(operand.get())); } From 7dded4e8600c5575c253ccb08df6c9959b9c1fbd Mon Sep 17 00:00:00 2001 From: Will Dietz Date: Thu, 8 Aug 2024 14:04:45 -0500 Subject: [PATCH 041/119] [FIRRTL][LowerLayers] Plumb support for errors. (#7470) --- lib/Dialect/FIRRTL/Transforms/LowerLayers.cpp | 64 ++++++++++++------- 1 file changed, 41 insertions(+), 23 deletions(-) diff --git a/lib/Dialect/FIRRTL/Transforms/LowerLayers.cpp b/lib/Dialect/FIRRTL/Transforms/LowerLayers.cpp index 3f01f788a3c3..8c6c7731c3e5 100644 --- a/lib/Dialect/FIRRTL/Transforms/LowerLayers.cpp +++ b/lib/Dialect/FIRRTL/Transforms/LowerLayers.cpp @@ -162,10 +162,10 @@ class LowerLayersPass SmallVectorImpl &ports); /// Strip layer colors from the module's interface. - InnerRefMap runOnModuleLike(FModuleLike moduleLike); + FailureOr runOnModuleLike(FModuleLike moduleLike); /// Extract layerblocks and strip probe colors from all ops under the module. - void runOnModuleBody(FModuleOp moduleOp, InnerRefMap &innerRefMap); + LogicalResult runOnModuleBody(FModuleOp moduleOp, InnerRefMap &innerRefMap); /// Update the module's port types to remove any explicit layer requirements /// from any probe types. @@ -276,7 +276,8 @@ void LowerLayersPass::removeLayersFromPorts(FModuleLike moduleLike) { } } -InnerRefMap LowerLayersPass::runOnModuleLike(FModuleLike moduleLike) { +FailureOr +LowerLayersPass::runOnModuleLike(FModuleLike moduleLike) { LLVM_DEBUG({ llvm::dbgs() << "Module: " << moduleLike.getModuleName() << "\n"; llvm::dbgs() << " Examining Layer Blocks:\n"; @@ -284,18 +285,24 @@ InnerRefMap LowerLayersPass::runOnModuleLike(FModuleLike moduleLike) { // Strip away layers from the interface of the module-like op. InnerRefMap innerRefMap; - TypeSwitch(moduleLike.getOperation()) - .Case([&](auto op) { - op.setLayers({}); - removeLayersFromPorts(op); - runOnModuleBody(op, innerRefMap); - }) - .Case([&](auto op) { - op.setLayers({}); - removeLayersFromPorts(op); - }) - .Case([](auto) {}) - .Default([](auto) { assert(0 && "unknown module-like op"); }); + auto result = + TypeSwitch(moduleLike.getOperation()) + .Case([&](auto op) { + op.setLayers({}); + removeLayersFromPorts(op); + return runOnModuleBody(op, innerRefMap); + }) + .Case([&](auto op) { + op.setLayers({}); + removeLayersFromPorts(op); + return success(); + }) + .Case([](auto) { return success(); }) + .Default( + [](auto *op) { return op->emitError("unknown module-like op"); }); + + if (failed(result)) + return failure(); return innerRefMap; } @@ -309,8 +316,8 @@ void LowerLayersPass::lowerInlineLayerBlock(LayerOp layer, layerBlock.erase(); } -void LowerLayersPass::runOnModuleBody(FModuleOp moduleOp, - InnerRefMap &innerRefMap) { +LogicalResult LowerLayersPass::runOnModuleBody(FModuleOp moduleOp, + InnerRefMap &innerRefMap) { CircuitOp circuitOp = moduleOp->getParentOfType(); StringRef circuitName = circuitOp.getName(); hw::InnerSymbolNamespace ns(moduleOp); @@ -331,7 +338,7 @@ void LowerLayersPass::runOnModuleBody(FModuleOp moduleOp, // this layer block to the new module. // 4. Instantiate the new module outside the layer block and hook it up. // 5. Erase the layer block. - moduleOp.walk([&](Operation *op) { + auto result = moduleOp.walk([&](Operation *op) { // Strip layer requirements from any op that might represent a probe. if (auto wire = dyn_cast(op)) { removeLayersFromValue(wire.getResult()); @@ -659,6 +666,7 @@ void LowerLayersPass::runOnModuleBody(FModuleOp moduleOp, return WalkResult::advance(); }); + return success(!result.wasInterrupted()); } void LowerLayersPass::preprocessLayers(CircuitNamespace &ns, OpBuilder &b, @@ -727,17 +735,27 @@ void LowerLayersPass::runOnOperation() { } auto mergeMaps = [](auto &&a, auto &&b) { - for (auto bb : b) - a.insert(bb); + if (failed(a)) + return std::forward(a); + if (failed(b)) + return std::forward(b); + + for (auto bb : *b) + a->insert(bb); return std::forward(a); }; // Lower the layer blocks of each module. SmallVector modules( circuitOp.getBodyBlock()->getOps()); - auto innerRefMap = - transformReduce(circuitOp.getContext(), modules, InnerRefMap{}, mergeMaps, - [this](FModuleLike mod) { return runOnModuleLike(mod); }); + auto failureOrInnerRefMap = transformReduce( + circuitOp.getContext(), modules, FailureOr(InnerRefMap{}), + mergeMaps, [this](FModuleLike mod) -> FailureOr { + return runOnModuleLike(mod); + }); + if (failed(failureOrInnerRefMap)) + return signalPassFailure(); + auto &innerRefMap = *failureOrInnerRefMap; // Rewrite any hw::HierPathOps which have namepaths that contain rewritting // inner refs. From 46148b263b0da06b53866ce57ad3093196c551bd Mon Sep 17 00:00:00 2001 From: Will Dietz Date: Thu, 8 Aug 2024 14:33:03 -0500 Subject: [PATCH 042/119] Revert "[FIRRTL][Dedup] Reduce size of integer data hashed. (#7469)" Revisit with unambiguous methods. This reverts commit da2428ac1f9544e36b25303a13821b2413991191. --- lib/Dialect/FIRRTL/Transforms/Dedup.cpp | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/lib/Dialect/FIRRTL/Transforms/Dedup.cpp b/lib/Dialect/FIRRTL/Transforms/Dedup.cpp index 1bf6c0e4b7dc..ca0fecb251a0 100644 --- a/lib/Dialect/FIRRTL/Transforms/Dedup.cpp +++ b/lib/Dialect/FIRRTL/Transforms/Dedup.cpp @@ -97,8 +97,8 @@ struct ModuleInfo { /// For BlockArgument's, this is the argument number. /// For OpResult's, this is the result number. struct ValueId { - unsigned index; - unsigned offset; + uint64_t index; + uint64_t offset; }; struct SymbolTarget { @@ -154,20 +154,16 @@ struct StructuralHasher { } private: - /// Hash value as array of bytes. - template - void updateImpl(T value) { + void update(const void *pointer) { + auto *addr = reinterpret_cast(&pointer); + sha.update(ArrayRef(addr, sizeof pointer)); + } + + void update(size_t value) { auto *addr = reinterpret_cast(&value); sha.update(ArrayRef(addr, sizeof value)); } - void update(const void *pointer) { updateImpl(pointer); } - void update(bool value) { updateImpl(value); } - void update(uint8_t value) { updateImpl(value); } - void update(uint16_t value) { updateImpl(value); } - void update(uint32_t value) { updateImpl(value); } - void update(uint64_t value) { updateImpl(value); } - void update(TypeID typeID) { update(typeID.getAsOpaquePointer()); } // NOLINTNEXTLINE(misc-no-recursion) @@ -213,9 +209,8 @@ struct StructuralHasher { } void update(ValueId index) { - unsigned vals[2] = {index.index, index.offset}; - sha.update(ArrayRef(reinterpret_cast(vals), - sizeof(vals))); + update(index.index); + update(index.offset); } void update(OpOperand &operand) { update(getId(operand.get())); } From 75b8b02e1cdb857b21a1e2b9c6aa9530c3502d19 Mon Sep 17 00:00:00 2001 From: Schuyler Eldridge Date: Wed, 7 Aug 2024 18:35:52 -0400 Subject: [PATCH 043/119] [Support] Add circt::isAncestorOfValueOwner util Add a utility `crict::isAncestorOfValueOwner` that can be used to compute if an Operation is an ancestor of a Value. This frequently comes up when trying to compute if a Value is defined _outside_ an Operation with a region. The implementation of this is based on @dtzSiFive suggestions. Signed-off-by: Schuyler Eldridge --- include/circt/Support/Utils.h | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 include/circt/Support/Utils.h diff --git a/include/circt/Support/Utils.h b/include/circt/Support/Utils.h new file mode 100644 index 000000000000..f6c76b2ebd14 --- /dev/null +++ b/include/circt/Support/Utils.h @@ -0,0 +1,30 @@ +//===- Utils.h - Miscellaneous utilities ----------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// Miscellaneous utilities for CIRCT that do not fit in with other files. +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_SUPPORT_UTILS_H +#define CIRCT_SUPPORT_UTILS_H + +#include "circt/Support/LLVM.h" +#include "mlir/IR/Operation.h" +#include "mlir/IR/Value.h" + +namespace circt { +/// Return true if a Value is created "underneath" an operation. This is +/// frequently useful when negated as that indicates that a Value is defined +/// "outside" the region of an Operation and that the Operation is thereby +/// "capturing" the value. +inline bool isAncestorOfValueOwner(Operation *op, Value value) { + return op->isAncestor(value.getParentBlock()->getParentOp()); +} +} // namespace circt + +#endif // CIRCT_SUPPORT_UTILS_H From d8d7574eabda05cc54ad7724bed686bba8eed888 Mon Sep 17 00:00:00 2001 From: Schuyler Eldridge Date: Wed, 7 Aug 2024 18:45:32 -0400 Subject: [PATCH 044/119] [FIRRTL] isAncestorOfValueOwner in LowerLayers Change the LowerLayers pass to use the new utility, `circt::isAncestorOfValueOwner`. This was created from the existing static function of the same name inside LowerLayers with a streamlined implementation. Signed-off-by: Schuyler Eldridge --- lib/Dialect/FIRRTL/Transforms/LowerLayers.cpp | 22 +++++-------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/lib/Dialect/FIRRTL/Transforms/LowerLayers.cpp b/lib/Dialect/FIRRTL/Transforms/LowerLayers.cpp index 8c6c7731c3e5..321be10f8a1a 100644 --- a/lib/Dialect/FIRRTL/Transforms/LowerLayers.cpp +++ b/lib/Dialect/FIRRTL/Transforms/LowerLayers.cpp @@ -16,6 +16,7 @@ #include "circt/Dialect/FIRRTL/Passes.h" #include "circt/Dialect/HW/InnerSymbolNamespace.h" #include "circt/Dialect/SV/SVOps.h" +#include "circt/Support/Utils.h" #include "mlir/Pass/Pass.h" #include "llvm/Support/Debug.h" #include "llvm/Support/Mutex.h" @@ -33,17 +34,6 @@ namespace firrtl { using namespace circt; using namespace firrtl; -//===----------------------------------------------------------------------===// -// Helpers -//===----------------------------------------------------------------------===// - -static bool isAncestor(Operation *op, Value value) { - if (auto result = dyn_cast(value)) - return op->isAncestor(result.getOwner()); - auto argument = cast(value); - return op->isAncestor(argument.getOwner()->getParentOp()); -} - namespace { /// Indicates the kind of reference that was captured. @@ -509,13 +499,13 @@ LogicalResult LowerLayersPass::runOnModuleBody(FModuleOp moduleOp, if (auto refSend = dyn_cast(op)) { auto src = refSend.getBase(); - if (!isAncestor(layerBlock, src)) + if (!isAncestorOfValueOwner(layerBlock, src)) createInputPort(src, op.getLoc()); continue; } if (auto refCast = dyn_cast(op)) { - if (!isAncestor(layerBlock, refCast)) + if (!isAncestorOfValueOwner(layerBlock, refCast)) createInputPort(refCast.getInput(), op.getLoc()); continue; } @@ -523,8 +513,8 @@ LogicalResult LowerLayersPass::runOnModuleBody(FModuleOp moduleOp, if (auto connect = dyn_cast(op)) { auto src = connect.getSrc(); auto dst = connect.getDest(); - auto srcInLayerBlock = isAncestor(layerBlock, src); - auto dstInLayerBlock = isAncestor(layerBlock, dst); + auto srcInLayerBlock = isAncestorOfValueOwner(layerBlock, src); + auto dstInLayerBlock = isAncestorOfValueOwner(layerBlock, dst); if (!srcInLayerBlock && !dstInLayerBlock) { connect->moveBefore(layerBlock); continue; @@ -570,7 +560,7 @@ LogicalResult LowerLayersPass::runOnModuleBody(FModuleOp moduleOp, // For any other ops, create input ports for any captured operands. for (auto operand : op.getOperands()) { - if (!isAncestor(layerBlock, operand)) + if (!isAncestorOfValueOwner(layerBlock, operand)) createInputPort(operand, op.getLoc()); } } From 1c8abbced307bdbd5362a3b49d7737a7475bfd92 Mon Sep 17 00:00:00 2001 From: Martin Erhart Date: Thu, 8 Aug 2024 23:00:15 +0100 Subject: [PATCH 045/119] [MooreToCore] Fix conversion and dyn_extract operation lowering (#7473) The lowering pattern of these two operations crashed when used on aggregate types. This PR adds type conversions for array and struct types and fixes the two lowering patterns by adding support for these types --- lib/Conversion/MooreToCore/MooreToCore.cpp | 60 +++++++++++++++++----- test/Conversion/MooreToCore/basic.mlir | 34 ++++++++++-- 2 files changed, 79 insertions(+), 15 deletions(-) diff --git a/lib/Conversion/MooreToCore/MooreToCore.cpp b/lib/Conversion/MooreToCore/MooreToCore.cpp index 668bd69c6e16..95c81d674743 100644 --- a/lib/Conversion/MooreToCore/MooreToCore.cpp +++ b/lib/Conversion/MooreToCore/MooreToCore.cpp @@ -264,15 +264,27 @@ struct DynExtractOpConversion : public OpConversionPattern { matchAndRewrite(DynExtractOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override { Type resultType = typeConverter->convertType(op.getResult().getType()); - auto width = typeConverter->convertType(op.getInput().getType()) - .getIntOrFloatBitWidth(); - Value amount = - adjustIntegerWidth(rewriter, adaptor.getLowBit(), width, op->getLoc()); - Value value = - rewriter.create(op->getLoc(), adaptor.getInput(), amount); + Type inputType = adaptor.getInput().getType(); - rewriter.replaceOpWithNewOp(op, resultType, value, 0); - return success(); + if (auto intType = dyn_cast(inputType)) { + Value amount = adjustIntegerWidth(rewriter, adaptor.getLowBit(), + intType.getWidth(), op->getLoc()); + Value value = rewriter.create(op->getLoc(), + adaptor.getInput(), amount); + + rewriter.replaceOpWithNewOp(op, resultType, value, 0); + return success(); + } + + if (auto arrType = dyn_cast(inputType)) { + unsigned idxWidth = llvm::Log2_64_Ceil(arrType.getNumElements()); + Value idx = adjustIntegerWidth(rewriter, adaptor.getLowBit(), idxWidth, + op->getLoc()); + rewriter.replaceOpWithNewOp(op, adaptor.getInput(), idx); + return success(); + } + + return failure(); } }; @@ -396,12 +408,20 @@ struct ConversionOpConversion : public OpConversionPattern { LogicalResult matchAndRewrite(ConversionOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override { + Location loc = op.getLoc(); Type resultType = typeConverter->convertType(op.getResult().getType()); - Value amount = - adjustIntegerWidth(rewriter, adaptor.getInput(), - resultType.getIntOrFloatBitWidth(), op->getLoc()); + int64_t inputBw = hw::getBitWidth(adaptor.getInput().getType()); + int64_t resultBw = hw::getBitWidth(resultType); + if (inputBw == -1 || resultBw == -1) + return failure(); - rewriter.replaceOpWithNewOp(op, resultType, amount); + Value input = rewriter.createOrFold( + loc, rewriter.getIntegerType(inputBw), adaptor.getInput()); + Value amount = adjustIntegerWidth(rewriter, input, resultBw, loc); + + Value result = + rewriter.createOrFold(loc, resultType, amount); + rewriter.replaceOp(op, result); return success(); } }; @@ -666,6 +686,22 @@ static void populateTypeConversion(TypeConverter &typeConverter) { return mlir::IntegerType::get(type.getContext(), type.getWidth()); }); + typeConverter.addConversion([&](ArrayType type) { + return hw::ArrayType::get(typeConverter.convertType(type.getElementType()), + type.getSize()); + }); + + typeConverter.addConversion([&](StructType type) { + SmallVector fields; + for (auto field : type.getMembers()) { + hw::StructType::FieldInfo info; + info.type = typeConverter.convertType(field.type); + info.name = field.name; + fields.push_back(info); + } + return hw::StructType::get(type.getContext(), fields); + }); + typeConverter.addConversion([&](RefType type) -> std::optional { if (isa(type.getNestedType())) return mlir::IntegerType::get(type.getContext(), diff --git a/test/Conversion/MooreToCore/basic.mlir b/test/Conversion/MooreToCore/basic.mlir index 31ad9917335e..94a073d4e5d5 100644 --- a/test/Conversion/MooreToCore/basic.mlir +++ b/test/Conversion/MooreToCore/basic.mlir @@ -62,8 +62,17 @@ func.func @Expressions(%arg0: !moore.i1, %arg1: !moore.l1, %arg2: !moore.i6, %ar moore.constant 12 : !moore.i32 moore.constant 3 : !moore.i6 - // CHECK-NEXT: hw.bitcast %arg0 : (i1) -> i1 moore.conversion %arg0 : !moore.i1 -> !moore.l1 + // CHECK-NEXT: [[V0:%.+]] = hw.constant 0 : i2 + // CHECK-NEXT: comb.concat [[V0]], %arg2 : i2, i6 + moore.conversion %arg2 : !moore.i6 -> !moore.l8 + // CHECK-NEXT: [[V0:%.+]] = comb.extract %arg2 from 4 : (i6) -> i2 + // CHECK-NEXT: [[V1:%.+]] = hw.constant 0 : i2 + // CHECK-NEXT: [[V2:%.+]] = comb.icmp eq [[V0]], [[V1]] : i2 + // CHECK-NEXT: [[V3:%.+]] = comb.extract %arg2 from 0 : (i6) -> i4 + // CHECK-NEXT: [[V4:%.+]] = hw.constant -1 : i4 + // CHECK-NEXT: comb.mux [[V2]], [[V3]], [[V4]] : i4 + moore.conversion %arg2 : !moore.i6 -> !moore.l4 // CHECK-NEXT: [[V0:%.+]] = hw.constant 0 : i5 // CHECK-NEXT: [[V1:%.+]] = comb.concat [[V0]], %arg0 : i5, i1 @@ -191,6 +200,26 @@ func.func @Expressions(%arg0: !moore.i1, %arg1: !moore.l1, %arg2: !moore.i6, %ar return } +// CHECK-LABEL: func @AdvancedConversion +func.func @AdvancedConversion(%arg0: !moore.array<5 x struct<{exp_bits: i32, man_bits: i32}>>) -> (!moore.array<5 x struct<{exp_bits: i32, man_bits: i32}>>, !moore.i320) { + // CHECK: [[V0:%.+]] = hw.constant 3978585893941511189997889893581765703992223160870725712510875979948892565035285336817671 : i320 + %0 = moore.constant 3978585893941511189997889893581765703992223160870725712510875979948892565035285336817671 : i320 + // CHECK: [[V1:%.+]] = hw.bitcast [[V0]] : (i320) -> !hw.array<5xstruct> + %1 = moore.conversion %0 : !moore.i320 -> !moore.array<5 x struct<{exp_bits: i32, man_bits: i32}>> + // CHECK: [[V2:%.+]] = hw.bitcast %arg0 : (!hw.array<5xstruct>) -> i320 + %2 = moore.conversion %arg0 : !moore.array<5 x struct<{exp_bits: i32, man_bits: i32}>> -> !moore.i320 + // CHECK: return [[V1]], [[V2]] + return %1, %2 : !moore.array<5 x struct<{exp_bits: i32, man_bits: i32}>>, !moore.i320 +} + +// CHECK-LABEL: func.func @DynExtractConversion +func.func @DynExtractConversion(%arg0: !moore.array<5 x struct<{exp_bits: i32, man_bits: i32}>>) -> !moore.struct<{exp_bits: i32, man_bits: i32}> { + %0 = moore.constant 3 : !moore.i32 + // CHECK: hw.array_get %arg0[{{.*}}] : !hw.array<5xstruct>, i3 + %1 = moore.dyn_extract %arg0 from %0 : !moore.array<5 x struct<{exp_bits: i32, man_bits: i32}>>, !moore.i32 -> !moore.struct<{exp_bits: i32, man_bits: i32}> + return %1 : !moore.struct<{exp_bits: i32, man_bits: i32}> +} + // CHECK-LABEL: hw.module @InstanceNull() { moore.module @InstanceNull() { @@ -268,9 +297,8 @@ moore.module @Variable() { // CHECK: %true = hw.constant true %1 = moore.constant 1 : i1 - // CHECK: [[CAST:%.+]] = hw.bitcast %true : (i1) -> i1 %2 = moore.conversion %1 : !moore.i1 -> !moore.l1 - // CHECK: llhd.sig "l" [[CAST]] : i1 + // CHECK: llhd.sig "l" %true : i1 %l = moore.variable %2 : // CHECK: [[TMP2:%.+]] = hw.constant 10 : i32 From 2f869728d5f09d9efdf3da5f89fefaeaa0d8d000 Mon Sep 17 00:00:00 2001 From: Martin Erhart Date: Fri, 9 Aug 2024 00:37:45 +0100 Subject: [PATCH 046/119] [MooreToCore] Struct extract lowering support (#7475) --- lib/Conversion/MooreToCore/MooreToCore.cpp | 14 +++++++++++++- test/Conversion/MooreToCore/basic.mlir | 7 +++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/lib/Conversion/MooreToCore/MooreToCore.cpp b/lib/Conversion/MooreToCore/MooreToCore.cpp index 95c81d674743..ac741f9e9d55 100644 --- a/lib/Conversion/MooreToCore/MooreToCore.cpp +++ b/lib/Conversion/MooreToCore/MooreToCore.cpp @@ -288,6 +288,18 @@ struct DynExtractOpConversion : public OpConversionPattern { } }; +struct StructExtractOpConversion : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(StructExtractOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + rewriter.replaceOpWithNewOp( + op, adaptor.getInput(), adaptor.getFieldNameAttr()); + return success(); + } +}; + struct ReduceAndOpConversion : public OpConversionPattern { using OpConversionPattern::OpConversionPattern; LogicalResult @@ -741,7 +753,7 @@ static void populateOpConversion(RewritePatternSet &patterns, // Patterns of miscellaneous operations. ConstantOpConv, ConcatOpConversion, ReplicateOpConversion, ExtractOpConversion, DynExtractOpConversion, ConversionOpConversion, - ReadOpConversion, NamedConstantOpConv, + ReadOpConversion, NamedConstantOpConv, StructExtractOpConversion, // Patterns of unary operations. ReduceAndOpConversion, ReduceOrOpConversion, ReduceXorOpConversion, diff --git a/test/Conversion/MooreToCore/basic.mlir b/test/Conversion/MooreToCore/basic.mlir index 94a073d4e5d5..0722144b2975 100644 --- a/test/Conversion/MooreToCore/basic.mlir +++ b/test/Conversion/MooreToCore/basic.mlir @@ -311,3 +311,10 @@ moore.module @Variable() { // CHECK: hw.output moore.output } + +// CHECK-LABEL: func @Struct +func.func @Struct(%arg0: !moore.struct<{exp_bits: i32, man_bits: i32}>) -> !moore.i32 { + // CHECK: hw.struct_extract %arg0["exp_bits"] : !hw.struct + %0 = moore.struct_extract %arg0, "exp_bits" : !moore.struct<{exp_bits: i32, man_bits: i32}> -> i32 + return %0 : !moore.i32 +} From fa95071921a42ff7dda8b0e387264564d2e2c77e Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Thu, 8 Aug 2024 21:39:25 -0700 Subject: [PATCH 047/119] [FIRRTL][InferResets] Generalize FART to support sync reset (#7476) The FullAsyncResetAnnotation is deprecated and replaced by FullResetAnnotation which includes a resetType argument that must be 'sync' or 'async'. IgnoreFullAsyncResetAnnotation is deprecated and replaced by ExcludeFromFullResetAnnotation. The new annotations are in package circt. The behavior of FullResetAnnotation with resetType == 'async' is identical to that of the old FullAsyncResetAnnotation. The behavior of FullResetAnnotation with resetType == 'sync' is very similar, except the type of the reset wired through will be UInt<1>, and any registers with an existing reset at all (both sync or async) will be left unchanged (resetType == 'async' will add async resets to registers with existing sync resets). --- docs/Dialects/FIRRTL/FIRRTLAnnotations.md | 49 ++++ .../circt/Dialect/FIRRTL/AnnotationDetails.h | 5 + include/circt/Dialect/FIRRTL/Passes.td | 8 +- lib/Dialect/FIRRTL/Transforms/InferResets.cpp | 234 ++++++++++-------- .../FIRRTL/Transforms/LowerAnnotations.cpp | 46 +++- lib/Dialect/FIRRTL/Transforms/SFCCompat.cpp | 34 ++- test/Dialect/FIRRTL/annotations.mlir | 34 ++- test/Dialect/FIRRTL/infer-resets-errors.mlir | 69 ++++-- test/Dialect/FIRRTL/infer-resets.mlir | 112 +++++++-- test/firtool/async-reset-anno.fir | 20 +- test/firtool/async-reset.fir | 70 +++++- 11 files changed, 487 insertions(+), 194 deletions(-) diff --git a/docs/Dialects/FIRRTL/FIRRTLAnnotations.md b/docs/Dialects/FIRRTL/FIRRTLAnnotations.md index 34fb19caffb6..0cbac6f916ad 100644 --- a/docs/Dialects/FIRRTL/FIRRTLAnnotations.md +++ b/docs/Dialects/FIRRTL/FIRRTLAnnotations.md @@ -474,8 +474,55 @@ Example: } ``` +### FullResetAnnotation + +| Property | Type | Description | +| ---------- | ------ | ------------- | +| class | string | `circt.FullAsyncResetAnnotation` | +| target | string | Reference target | +| resetType | string | "async" or "sync" | + + +The target must be a signal that is a reset. The type of the signal must be (or inferred +to be) the same as the reset type specified in the annotation. + +Indicates that all reset-less registers which are children of the module containing +the target will have the reset targeted attached, with a reset value of 0. + +The module containing the target of this annotation is not allowed to reside in multiple +hierarchies. + +Example: +```json +{ + "class": "circt.FullResetAnnotation", + "target": "~Foo|Bar/d:Baz>reset", + "resetType": "async" +} +``` + +### ExcludeFromFullResetAnnotation + +| Property | Type | Description | +| ---------- | ------ | ------------- | +| class | string | `circt.ExcludeFromFullResetAnnotation` | +| target | string | Reference target | + +This annotation indicates that the target moudle should be excluded from the +FullResetAnnotation of a parent module. + +Example: +```json +{ + "class": "circt.IgnoreFullAsyncResetAnnotation", + "target": "~Foo|Bar/d:Baz" +} +``` + ### FullAsyncResetAnnotation +**Deprecated, use FullResetAnnotation** + | Property | Type | Description | | ---------- | ------ | ------------- | | class | string | `sifive.enterprise.firrtl.FullAsyncResetAnnotation` | @@ -500,6 +547,8 @@ Example: ### IgnoreFullAsyncResetAnnotation +**Deprecated, use ExcludeFromFullResetAnnotation** + | Property | Type | Description | | ---------- | ------ | ------------- | | class | string | `sifive.enterprise.firrtl.IgnoreFullAsyncResetAnnotation` | diff --git a/include/circt/Dialect/FIRRTL/AnnotationDetails.h b/include/circt/Dialect/FIRRTL/AnnotationDetails.h index b35215619ad7..90da1f5abff8 100644 --- a/include/circt/Dialect/FIRRTL/AnnotationDetails.h +++ b/include/circt/Dialect/FIRRTL/AnnotationDetails.h @@ -159,6 +159,11 @@ constexpr const char *elaborationArtefactsDirectoryAnnoClass = constexpr const char *testHarnessPathAnnoClass = "sifive.enterprise.firrtl.TestHarnessPathAnnotation"; /// Annotation that marks a reset (port or wire) and domain. +constexpr const char *fullResetAnnoClass = "circt.FullResetAnnotation"; +/// Annotation that marks a module as not belonging to any reset domain. +constexpr const char *excludeFromFullResetAnnoClass = + "circt.ExcludeFromFullResetAnnotation"; +/// Annotation that marks a reset (port or wire) and domain. constexpr const char *fullAsyncResetAnnoClass = "sifive.enterprise.firrtl.FullAsyncResetAnnotation"; /// Annotation that marks a module as not belonging to any reset domain. diff --git a/include/circt/Dialect/FIRRTL/Passes.td b/include/circt/Dialect/FIRRTL/Passes.td index 4af7367449b7..3c3e3c4d841a 100644 --- a/include/circt/Dialect/FIRRTL/Passes.td +++ b/include/circt/Dialect/FIRRTL/Passes.td @@ -397,11 +397,13 @@ def InferResets : Pass<"firrtl-infer-resets", "firrtl::CircuitOp"> { let summary = "Infer reset synchronicity and add implicit resets"; let description = [{ This pass infers whether resets are synchronous or asynchronous, and extends - reset-less registers with an asynchronous reset based on the following + reset-less registers with a reset based on the following annotations: - - `sifive.enterprise.firrtl.FullAsyncResetAnnotation` - - `sifive.enterprise.firrtl.IgnoreFullAsyncResetAnnotation` + - `circt.FullResetAnnotation` + - `circt.ExcludeFromFullResetAnnotation` + - `sifive.enterprise.firrtl.FullAsyncResetAnnotation` (deprecated) + - `sifive.enterprise.firrtl.IgnoreFullAsyncResetAnnotation` (deprecated) }]; let constructor = "circt::firrtl::createInferResetsPass()"; } diff --git a/lib/Dialect/FIRRTL/Transforms/InferResets.cpp b/lib/Dialect/FIRRTL/Transforms/InferResets.cpp index 5357125f04ea..ca52ee6c470a 100644 --- a/lib/Dialect/FIRRTL/Transforms/InferResets.cpp +++ b/lib/Dialect/FIRRTL/Transforms/InferResets.cpp @@ -1,4 +1,4 @@ -//===- InferResets.cpp - Infer resets and add async reset -------*- C++ -*-===// +//===- InferResets.cpp - Infer resets and add full reset --------*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -270,6 +270,15 @@ using ResetNetwork = llvm::iterator_range< /// Whether a reset is sync or async. enum class ResetKind { Async, Sync }; +static StringRef resetKindToStringRef(const ResetKind &kind) { + switch (kind) { + case ResetKind::Async: + return "async"; + case ResetKind::Sync: + return "sync"; + } + llvm_unreachable("unhandled reset kind"); +} } // namespace namespace llvm { @@ -306,12 +315,11 @@ static T &operator<<(T &os, const ResetKind &kind) { //===----------------------------------------------------------------------===// namespace { -/// Infer concrete reset types and insert full async reset. +/// Infer concrete reset types and insert full reset. /// /// This pass replaces `reset` types in the IR with a concrete `asyncreset` or -/// `uint<1>` depending on how the reset is used, and adds async resets to -/// registers in modules marked with the corresponding -/// `FullAsyncResetAnnotation`. +/// `uint<1>` depending on how the reset is used, and adds resets to registers +/// in modules marked with the corresponding `FullResetAnnotation`. /// /// On a high level, the first stage of the pass that deals with reset inference /// operates as follows: @@ -332,28 +340,27 @@ namespace { /// found in step 2. This will replace all `reset` types in the IR with /// a concrete type. /// -/// The second stage that deals with the addition of async resets operates as +/// The second stage that deals with the addition of full resets operates as /// follows: /// /// 4. Visit every module in the design and determine if it has an explicit /// reset annotated. Ports of and wires in the module can have a -/// `FullAsyncResetAnnotation`, which marks that port or wire as the async -/// reset for the module. A module may also carry a -/// `IgnoreFullAsyncResetAnnotation`, which marks it as being explicitly not -/// in a reset domain. These annotations are sparse; it is very much possible -/// that just the top-level module in the design has a full async reset -/// annotation. A module can only ever carry one of these annotations, which -/// puts it into one of three categories from an async reset inference -/// perspective: +/// `FullResetAnnotation`, which marks that port or wire as the reset for +/// the module. A module may also carry a `ExcludeFromFullResetAnnotation`, +/// which marks it as being explicitly not in a reset domain. These +/// annotations are sparse; it is very much possible that just the top-level +/// module in the design has a full reset annotation. A module can only +/// ever carry one of these annotations, which puts it into one of three +/// categories from a full reset inference perspective: /// -/// a. unambiguously marks a port or wire as the module's async reset -/// b. explicitly marks it as not to have any async resets added +/// a. unambiguously marks a port or wire as the module's full reset +/// b. explicitly marks it as not to have any full resets added /// c. inherit reset /// -/// 5. For every module in the design, determine the full async reset domain it +/// 5. For every module in the design, determine the full full reset domain it /// is in. Note that this very narrowly deals with the inference of a -/// "default" async reset, which basically goes through the IR and attaches -/// all non-reset registers to a default async reset signal. If a module +/// "default" full reset, which basically goes through the IR and attaches +/// all non-reset registers to a default full reset signal. If a module /// carries one of the annotations mentioned in (4), the annotated port or /// wire is used as its reset domain. Otherwise, it inherits the reset domain /// from parent modules. This conceptually involves looking at all the places @@ -365,7 +372,7 @@ namespace { /// its local ports or wires, or a port or wire within one of its parent /// modules. /// -/// 6. For every module in the design, determine how async resets shall be +/// 6. For every module in the design, determine how full resets shall be /// implemented. This step handles the following distinct cases: /// /// a. Skip a module because it is marked as having no reset domain. @@ -382,30 +389,30 @@ namespace { /// value to reuse (port or wire), the index of an existing port to reuse, /// and the name of an additional port to insert into its port list. /// -/// 7. For every module in the design, async resets are implemented. This +/// 7. For every module in the design, full resets are implemented. This /// determines the local value to use as the reset signal and updates the /// `reg` and `regreset` operations in the design. If the register already -/// has an async reset, it is left unchanged. If it has a sync reset, the -/// sync reset is moved into a `mux` operation on all `connect`s to the -/// register (which the Scala code base called the `RemoveResets` pass). -/// Finally the register is replaced with a `regreset` operation, with the -/// reset signal determined earlier, and a "zero" value constructed for the -/// register's type. +/// has an async reset, or if the type of the full reset is sync, the +/// register's reset is left unchanged. If it has a sync reset and the full +/// reset is async, the sync reset is moved into a `mux` operation on all +/// `connect`s to the register (which the Scala code base called the +/// `RemoveResets` pass). Finally the register is replaced with a `regreset` +/// operation, with the reset signal determined earlier, and a "zero" value +/// constructed for the register's type. /// /// Determining the local reset value is trivial if step 6 found a module to /// be of case a or b. Case c is the non-trivial one, because it requires /// modifying the port list of the module. This is done by first determining /// the name of the reset signal in the parent module, which is either the /// name of the port or wire declaration. We then look for an existing -/// `asyncreset` port in the port list and reuse that as reset. If no port -/// with that name was found, or the existing port is of the wrong type, a -/// new port is inserted into the port list. +/// port of the same type in the port list and reuse that as reset. If no +/// port with that name was found, or the existing port is of the wrong type, +/// a new port is inserted into the port list. /// /// TODO: This logic is *very* brittle and error-prone. It may make sense to -/// just add an additional port for the inferred async reset in any case, -/// with an optimization to use an existing `asyncreset` port if all of the -/// module's instantiations have that port connected to the desired signal -/// already. +/// just add an additional port for the inferred reset in any case, with an +/// optimization to use an existing port if all of the module's +/// instantiations have that port connected to the desired signal already. /// struct InferResetsPass : public circt::firrtl::impl::InferResetsBase { @@ -432,12 +439,12 @@ struct InferResetsPass bool updateReset(FieldRef field, FIRRTLBaseType resetType); //===--------------------------------------------------------------------===// - // Async reset implementation + // Full reset implementation LogicalResult collectAnnos(CircuitOp circuit); - // Collect async reset annotations in the module and return a reset signal. + // Collect reset annotations in the module and return a reset signal. // Return `failure()` if there was an error in the annotation processing. - // Return `std::nullopt` if there was no async reset annotation. + // Return `std::nullopt` if there was no reset annotation. // Return `nullptr` if there was `ignore` annotation. // Return a non-null Value if the reset was actually provided. FailureOr> collectAnnos(FModuleOp module); @@ -450,9 +457,9 @@ struct InferResetsPass void determineImpl(); void determineImpl(FModuleOp module, ResetDomain &domain); - LogicalResult implementAsyncReset(); - LogicalResult implementAsyncReset(FModuleOp module, ResetDomain &domain); - void implementAsyncReset(Operation *op, FModuleOp module, Value actualReset); + LogicalResult implementFullReset(); + LogicalResult implementFullReset(FModuleOp module, ResetDomain &domain); + void implementFullReset(Operation *op, FModuleOp module, Value actualReset); LogicalResult verifyNoAbstractReset(); @@ -536,8 +543,8 @@ void InferResetsPass::runOnOperationInner() { // Determine how each reset shall be implemented. determineImpl(); - // Implement the async resets. - if (failed(implementAsyncReset())) + // Implement the full resets. + if (failed(implementFullReset())) return signalPassFailure(); // Require that no Abstract Resets exist on ports in the design. @@ -1262,7 +1269,7 @@ bool InferResetsPass::updateReset(FieldRef field, FIRRTLBaseType resetType) { LogicalResult InferResetsPass::collectAnnos(CircuitOp circuit) { LLVM_DEBUG({ llvm::dbgs() << "\n"; - debugHeader("Gather async reset annotations") << "\n\n"; + debugHeader("Gather reset annotations") << "\n\n"; }); SmallVector>> results; for (auto module : circuit.getOps()) @@ -1293,15 +1300,15 @@ InferResetsPass::collectAnnos(FModuleOp module) { // explicitly assigns it no reset domain. bool ignore = false; AnnotationSet::removeAnnotations(module, [&](Annotation anno) { - if (anno.isClass(ignoreFullAsyncResetAnnoClass)) { + if (anno.isClass(excludeFromFullResetAnnoClass)) { ignore = true; conflictingAnnos.insert({anno, module.getLoc()}); return true; } - if (anno.isClass(fullAsyncResetAnnoClass)) { + if (anno.isClass(fullResetAnnoClass)) { anyFailed = true; - module.emitError("'FullAsyncResetAnnotation' cannot target module; " - "must target port or wire/node instead"); + module.emitError("''FullResetAnnotation' cannot target module; must " + "target port or wire/node instead"); return true; } return false; @@ -1311,31 +1318,66 @@ InferResetsPass::collectAnnos(FModuleOp module) { // Consume any reset annotations on module ports. Value reset; - AnnotationSet::removePortAnnotations(module, [&](unsigned argNum, - Annotation anno) { - Value arg = module.getArgument(argNum); - if (anno.isClass(fullAsyncResetAnnoClass)) { - if (!isa(arg.getType())) { - mlir::emitError(arg.getLoc(), "'FullAsyncResetAnnotation' must " - "target async reset, but targets ") + // Helper for checking annotations and determining the reset + auto checkAnnotations = [&](Annotation anno, Value arg) { + if (anno.isClass(fullResetAnnoClass)) { + ResetKind expectedResetKind; + if (auto rt = anno.getMember("resetType")) { + if (rt == "sync") { + expectedResetKind = ResetKind::Sync; + } else if (rt == "async") { + expectedResetKind = ResetKind::Async; + } else { + mlir::emitError(arg.getLoc(), + "'FullResetAnnotation' requires resetType == 'sync' " + "| 'async', but got resetType == ") + << rt; + anyFailed = true; + return true; + } + } else { + mlir::emitError(arg.getLoc(), + "'FullResetAnnotation' requires resetType == " + "'sync' | 'async', but got no resetType"); + anyFailed = true; + return true; + } + // Check that the type is well-formed + bool isAsync = expectedResetKind == ResetKind::Async; + bool validUint = false; + if (auto uintT = dyn_cast(arg.getType())) + validUint = uintT.getWidth() == 1; + if ((isAsync && !isa(arg.getType())) || + (!isAsync && !validUint)) { + auto kind = resetKindToStringRef(expectedResetKind); + mlir::emitError(arg.getLoc(), + "'FullResetAnnotation' with resetType == '") + << kind << "' must target " << kind << " reset, but targets " << arg.getType(); anyFailed = true; return true; } + reset = arg; conflictingAnnos.insert({anno, reset.getLoc()}); return false; } - if (anno.isClass(ignoreFullAsyncResetAnnoClass)) { + if (anno.isClass(excludeFromFullResetAnnoClass)) { anyFailed = true; mlir::emitError(arg.getLoc(), - "'IgnoreFullAsyncResetAnnotation' cannot target port; " - "must target module instead"); + "'ExcludeFromFullResetAnnotation' cannot " + "target port/wire/node; must target module instead"); return true; } return false; - }); + }; + + AnnotationSet::removePortAnnotations(module, + [&](unsigned argNum, Annotation anno) { + Value arg = module.getArgument(argNum); + return checkAnnotations(anno, arg); + }); if (anyFailed) return failure(); @@ -1343,8 +1385,8 @@ InferResetsPass::collectAnnos(FModuleOp module) { module.getBody().walk([&](Operation *op) { // Reset annotations must target wire/node ops. if (!isa(op)) { - if (AnnotationSet::hasAnnotation(op, fullAsyncResetAnnoClass, - ignoreFullAsyncResetAnnoClass)) { + if (AnnotationSet::hasAnnotation(op, fullResetAnnoClass, + excludeFromFullResetAnnoClass)) { anyFailed = true; op->emitError( "reset annotations must target module, port, or wire/node"); @@ -1355,27 +1397,8 @@ InferResetsPass::collectAnnos(FModuleOp module) { // At this point we know that we have a WireOp/NodeOp. Process the reset // annotations. AnnotationSet::removeAnnotations(op, [&](Annotation anno) { - if (anno.isClass(fullAsyncResetAnnoClass)) { - auto resultType = op->getResult(0).getType(); - if (!isa(resultType)) { - mlir::emitError(op->getLoc(), "'FullAsyncResetAnnotation' must " - "target async reset, but targets ") - << resultType; - anyFailed = true; - return true; - } - reset = op->getResult(0); - conflictingAnnos.insert({anno, reset.getLoc()}); - return false; - } - if (anno.isClass(ignoreFullAsyncResetAnnoClass)) { - anyFailed = true; - op->emitError( - "'IgnoreFullAsyncResetAnnotation' cannot target wire/node; must " - "target module instead"); - return true; - } - return false; + auto arg = op->getResult(0); + return checkAnnotations(anno, arg); }); }); if (anyFailed) @@ -1430,7 +1453,7 @@ InferResetsPass::collectAnnos(FModuleOp module) { LogicalResult InferResetsPass::buildDomains(CircuitOp circuit) { LLVM_DEBUG({ llvm::dbgs() << "\n"; - debugHeader("Build async reset domains") << "\n\n"; + debugHeader("Build full reset domains") << "\n\n"; }); // Gather the domains. @@ -1559,7 +1582,7 @@ void InferResetsPass::determineImpl() { /// - If the domain is the place where the reset is defined ("top"), fills in /// the existing port/wire/node as reset. /// - If the module already has a port with the reset's name: -/// - If the type is `asyncreset`, reuses that port. +/// - If the port has the same type as the reset domain, reuses that port. /// - Otherwise appends a `_N` suffix with increasing N to create a /// yet-unused /// port name, and marks that as to be created. @@ -1629,18 +1652,18 @@ void InferResetsPass::determineImpl(FModuleOp module, ResetDomain &domain) { } //===----------------------------------------------------------------------===// -// Async Reset Implementation +// Full Reset Implementation //===----------------------------------------------------------------------===// -/// Implement the async resets gathered in the pass' `domains` map. -LogicalResult InferResetsPass::implementAsyncReset() { +/// Implement the annotated resets gathered in the pass' `domains` map. +LogicalResult InferResetsPass::implementFullReset() { LLVM_DEBUG({ llvm::dbgs() << "\n"; - debugHeader("Implement async resets") << "\n\n"; + debugHeader("Implement full resets") << "\n\n"; }); for (auto &it : domains) - if (failed(implementAsyncReset(cast(it.first), - it.second.back().first))) + if (failed(implementFullReset(cast(it.first), + it.second.back().first))) return failure(); return success(); } @@ -1650,9 +1673,9 @@ LogicalResult InferResetsPass::implementAsyncReset() { /// This will add ports to the module as appropriate, update the register ops /// in the module, and update any instantiated submodules with their /// corresponding reset implementation details. -LogicalResult InferResetsPass::implementAsyncReset(FModuleOp module, - ResetDomain &domain) { - LLVM_DEBUG(llvm::dbgs() << "Implementing async reset for " << module.getName() +LogicalResult InferResetsPass::implementFullReset(FModuleOp module, + ResetDomain &domain) { + LLVM_DEBUG(llvm::dbgs() << "Implementing full reset for " << module.getName() << "\n"); // Nothing to do if the module was marked explicitly with no reset domain. @@ -1666,7 +1689,7 @@ LogicalResult InferResetsPass::implementAsyncReset(FModuleOp module, Value actualReset = domain.existingValue; if (domain.newPortName) { PortInfo portInfo{domain.newPortName, - AsyncResetType::get(&getContext()), + domain.reset.getType(), Direction::In, {}, domain.reset.getLoc()}; @@ -1771,15 +1794,15 @@ LogicalResult InferResetsPass::implementAsyncReset(FModuleOp module, // Update the operations. for (auto *op : opsToUpdate) - implementAsyncReset(op, module, actualReset); + implementFullReset(op, module, actualReset); return success(); } -/// Modify an operation in a module to implement an async reset for that +/// Modify an operation in a module to implement an full reset for that /// module. -void InferResetsPass::implementAsyncReset(Operation *op, FModuleOp module, - Value actualReset) { +void InferResetsPass::implementFullReset(Operation *op, FModuleOp module, + Value actualReset) { ImplicitLocOpBuilder builder(op->getLoc(), op); // Handle instances. @@ -1840,7 +1863,7 @@ void InferResetsPass::implementAsyncReset(Operation *op, FModuleOp module, if (AnnotationSet::removeAnnotations(regOp, excludeMemToRegAnnoClass)) return; - LLVM_DEBUG(llvm::dbgs() << "- Adding async reset to " << regOp << "\n"); + LLVM_DEBUG(llvm::dbgs() << "- Adding full reset to " << regOp << "\n"); auto zero = createZeroValue(builder, regOp.getResult().getType()); auto newRegOp = builder.create( regOp.getResult().getType(), regOp.getClockVal(), actualReset, zero, @@ -1855,10 +1878,11 @@ void InferResetsPass::implementAsyncReset(Operation *op, FModuleOp module, // Handle registers with reset. if (auto regOp = dyn_cast(op)) { - // If the register already has an async reset, leave it untouched. - if (type_isa(regOp.getResetSignal().getType())) { - LLVM_DEBUG(llvm::dbgs() - << "- Skipping (has async reset) " << regOp << "\n"); + // If the register already has an async reset or if the type of the added + // reset is sync, leave it alone. + if (type_isa(regOp.getResetSignal().getType()) || + type_isa(actualReset.getType())) { + LLVM_DEBUG(llvm::dbgs() << "- Skipping (has reset) " << regOp << "\n"); // The following performs the logic of `CheckResets` in the original // Scala source code. if (failed(regOp.verifyInvariants())) @@ -1870,9 +1894,9 @@ void InferResetsPass::implementAsyncReset(Operation *op, FModuleOp module, auto reset = regOp.getResetSignal(); auto value = regOp.getResetValue(); - // If we arrive here, the register has a sync reset. In order to add an - // async reset, we have to move the sync reset into a mux in front of the - // register. + // If we arrive here, the register has a sync reset and the added reset is + // async. In order to add an async reset, we have to move the sync reset + // into a mux in front of the register. insertResetMux(builder, regOp.getResult(), reset, value); builder.setInsertionPointAfterValue(regOp.getResult()); auto mux = builder.create(reset, value, regOp.getResult()); diff --git a/lib/Dialect/FIRRTL/Transforms/LowerAnnotations.cpp b/lib/Dialect/FIRRTL/Transforms/LowerAnnotations.cpp index fe19e2141055..e42d83af73fb 100644 --- a/lib/Dialect/FIRRTL/Transforms/LowerAnnotations.cpp +++ b/lib/Dialect/FIRRTL/Transforms/LowerAnnotations.cpp @@ -435,6 +435,45 @@ static LogicalResult applyOutputDirAnno(const AnnoPathValue &target, return success(); } +/// Convert from FullAsyncResetAnnotation to FullResetAnnotation +static LogicalResult convertToFullResetAnnotation(const AnnoPathValue &target, + DictionaryAttr anno, + ApplyState &state) { + auto *op = target.ref.getOp(); + auto *context = op->getContext(); + + mlir::emitWarning(op->getLoc()) + << "'" << fullAsyncResetAnnoClass << "' is deprecated, use '" + << fullResetAnnoClass << "' instead"; + + NamedAttrList newAnno(anno.getValue()); + newAnno.set("class", StringAttr::get(context, fullResetAnnoClass)); + newAnno.append("resetType", StringAttr::get(context, "async")); + + DictionaryAttr newDictionary = DictionaryAttr::get(op->getContext(), newAnno); + + return applyWithoutTarget(target, newDictionary, state); +} + +/// Convert from IgnoreFullAsyncResetAnnotation to +/// ExcludeFromFullResetAnnotation +static LogicalResult convertToExcludeFromFullResetAnnotation( + const AnnoPathValue &target, DictionaryAttr anno, ApplyState &state) { + auto *op = target.ref.getOp(); + auto *context = op->getContext(); + + mlir::emitWarning(op->getLoc()) + << "'" << ignoreFullAsyncResetAnnoClass << "' is deprecated, use '" + << excludeFromFullResetAnnoClass << "' instead"; + + NamedAttrList newAnno(anno.getValue()); + newAnno.set("class", StringAttr::get(context, excludeFromFullResetAnnoClass)); + + DictionaryAttr newDictionary = DictionaryAttr::get(op->getContext(), newAnno); + + return applyWithoutTarget(target, newDictionary, state); +} + //===----------------------------------------------------------------------===// // Driving table //===----------------------------------------------------------------------===// @@ -543,9 +582,12 @@ static llvm::StringMap annotationRecords{{ {addSeqMemPortsFileAnnoClass, NoTargetAnnotation}, {extractClockGatesAnnoClass, NoTargetAnnotation}, {extractBlackBoxAnnoClass, {stdResolve, applyWithoutTarget}}, - {fullAsyncResetAnnoClass, {stdResolve, applyWithoutTarget}}, - {ignoreFullAsyncResetAnnoClass, + {fullResetAnnoClass, {stdResolve, applyWithoutTarget}}, + {excludeFromFullResetAnnoClass, {stdResolve, applyWithoutTarget}}, + {fullAsyncResetAnnoClass, {stdResolve, convertToFullResetAnnotation}}, + {ignoreFullAsyncResetAnnoClass, + {stdResolve, convertToExcludeFromFullResetAnnotation}}, {decodeTableAnnotation, {noResolve, drop}}, {blackBoxTargetDirAnnoClass, NoTargetAnnotation}, {traceNameAnnoClass, {stdResolve, applyTraceName}}, diff --git a/lib/Dialect/FIRRTL/Transforms/SFCCompat.cpp b/lib/Dialect/FIRRTL/Transforms/SFCCompat.cpp index fb79491eb650..d59055fb3167 100644 --- a/lib/Dialect/FIRRTL/Transforms/SFCCompat.cpp +++ b/lib/Dialect/FIRRTL/Transforms/SFCCompat.cpp @@ -54,21 +54,18 @@ void SFCCompatPass::runOnOperation() { bool madeModifications = false; SmallVector invalidOps; - auto fullAsyncResetAttr = - StringAttr::get(&getContext(), fullAsyncResetAnnoClass); - auto isFullAsyncResetAnno = [fullAsyncResetAttr](Annotation anno) { - return anno.getClassAttr() == fullAsyncResetAttr; + auto fullResetAttr = StringAttr::get(&getContext(), fullResetAnnoClass); + auto isFullResetAnno = [fullResetAttr](Annotation anno) { + auto annoClassAttr = anno.getClassAttr(); + return annoClassAttr == fullResetAttr; }; - bool fullAsyncResetExists = AnnotationSet::removePortAnnotations( - getOperation(), [&](unsigned argNum, Annotation anno) { - return isFullAsyncResetAnno(anno); - }); - getOperation()->walk( - [isFullAsyncResetAnno, &fullAsyncResetExists](Operation *op) { - fullAsyncResetExists |= - AnnotationSet::removeAnnotations(op, isFullAsyncResetAnno); - }); - madeModifications |= fullAsyncResetExists; + bool fullResetExists = AnnotationSet::removePortAnnotations( + getOperation(), + [&](unsigned argNum, Annotation anno) { return isFullResetAnno(anno); }); + getOperation()->walk([isFullResetAnno, &fullResetExists](Operation *op) { + fullResetExists |= AnnotationSet::removeAnnotations(op, isFullResetAnno); + }); + madeModifications |= fullResetExists; auto result = getOperation()->walk([&](Operation *op) { // Populate invalidOps for later handling. @@ -82,11 +79,10 @@ void SFCCompatPass::runOnOperation() { // If the `RegResetOp` has an invalidated initialization and we // are not running FART, then replace it with a `RegOp`. - if (!fullAsyncResetExists && - walkDrivers(reg.getResetValue(), true, true, false, - [](FieldRef dst, FieldRef src) { - return src.isa(); - })) { + if (!fullResetExists && walkDrivers(reg.getResetValue(), true, true, false, + [](FieldRef dst, FieldRef src) { + return src.isa(); + })) { ImplicitLocOpBuilder builder(reg.getLoc(), reg); RegOp newReg = builder.create( reg.getResult().getType(), reg.getClockVal(), reg.getNameAttr(), diff --git a/test/Dialect/FIRRTL/annotations.mlir b/test/Dialect/FIRRTL/annotations.mlir index a43617ce1ee9..bc0da5dd8d22 100644 --- a/test/Dialect/FIRRTL/annotations.mlir +++ b/test/Dialect/FIRRTL/annotations.mlir @@ -1,4 +1,4 @@ -// RUN: circt-opt --pass-pipeline='builtin.module(firrtl.circuit(firrtl-lower-annotations{allow-adding-ports-on-public-modules=true}))' --split-input-file %s | FileCheck %s +// RUN: circt-opt --pass-pipeline='builtin.module(firrtl.circuit(firrtl-lower-annotations{allow-adding-ports-on-public-modules=true}))' --verify-diagnostics --split-input-file %s | FileCheck %s // circt.test copies the annotation to the target // circt.testNT puts the targetless annotation on the circuit @@ -1953,3 +1953,35 @@ firrtl.circuit "Top" attributes { // CHECK-SAME: attributes {output_file = #hw.output_file<"foobarbaz{{/|\\\\}}qux{{/|\\\\}}">} firrtl.module @Top() {} } + +// ----- +// Check that FullAsyncResetAnnotation is lowered to FullResetAnnotation (and prints a warning) +// CHECK-LABEL: firrtl.circuit "Top" +firrtl.circuit "Top" attributes { + rawAnnotations = [ + { + class = "sifive.enterprise.firrtl.FullAsyncResetAnnotation", + target = "~Top|Top>reset" + }] + } { + // CHECK-LABEL: firrtl.module @Top + // CHECK-SAME: (in %reset: !firrtl.asyncreset [{class = "circt.FullResetAnnotation", resetType = "async"}]) + // expected-warning @+1 {{'sifive.enterprise.firrtl.FullAsyncResetAnnotation' is deprecated, use 'circt.FullResetAnnotation' instead}} + firrtl.module @Top(in %reset: !firrtl.asyncreset) {} +} + +// ----- +// Check that IgnoreFullAsyncResetAnnotation is lowered to ExcludeFromFullResetAnnotation (and prints a warning) +// CHECK-LABEL: firrtl.circuit "Top" +firrtl.circuit "Top" attributes { + rawAnnotations = [ + { + class = "sifive.enterprise.firrtl.IgnoreFullAsyncResetAnnotation", + target = "~Top|Top" + }] + } { + // CHECK-LABEL: firrtl.module @Top + // CHECK-SAME: (in %reset: !firrtl.asyncreset) attributes {annotations = [{class = "circt.ExcludeFromFullResetAnnotation"}]} + // expected-warning @+1 {{'sifive.enterprise.firrtl.IgnoreFullAsyncResetAnnotation' is deprecated, use 'circt.ExcludeFromFullResetAnnotation' instead}} + firrtl.module @Top(in %reset: !firrtl.asyncreset) {} +} \ No newline at end of file diff --git a/test/Dialect/FIRRTL/infer-resets-errors.mlir b/test/Dialect/FIRRTL/infer-resets-errors.mlir index c0c6814756b5..15c45ef61f18 100644 --- a/test/Dialect/FIRRTL/infer-resets-errors.mlir +++ b/test/Dialect/FIRRTL/infer-resets-errors.mlir @@ -6,7 +6,6 @@ // - github.com/sifive/$internal: // - test/scala/firrtl/FullAsyncResetTransform.scala - //===----------------------------------------------------------------------===// // Reset Inference //===----------------------------------------------------------------------===// @@ -152,34 +151,43 @@ firrtl.circuit "top" { } //===----------------------------------------------------------------------===// -// Full Async Reset +// Full Reset //===----------------------------------------------------------------------===// // ----- // Reset annotation cannot target module firrtl.circuit "top" { - // expected-error @+1 {{FullAsyncResetAnnotation' cannot target module; must target port or wire/node instead}} - firrtl.module @top() attributes {annotations = [{class = "sifive.enterprise.firrtl.FullAsyncResetAnnotation"}]} { + // expected-error @+1 {{'FullResetAnnotation' cannot target module; must target port or wire/node instead}} + firrtl.module @top() attributes {annotations = [{class = "circt.FullResetAnnotation", resetType = "async"}]} { } } // ----- -// Reset annotation cannot target synchronous reset signals +// Reset annotation resetType must match type of signal firrtl.circuit "top" { firrtl.module @top() { - // expected-error @below {{'FullAsyncResetAnnotation' must target async reset, but targets '!firrtl.uint<1>'}} - %innerReset = firrtl.wire {annotations = [{class = "sifive.enterprise.firrtl.FullAsyncResetAnnotation"}]} : !firrtl.uint<1> + // expected-error @below {{'FullResetAnnotation' with resetType == 'async' must target async reset, but targets '!firrtl.uint<1>'}} + %innerReset = firrtl.wire {annotations = [{class = "circt.FullResetAnnotation", resetType = "async"}]} : !firrtl.uint<1> + // expected-error @below {{'FullResetAnnotation' with resetType == 'sync' must target sync reset, but targets '!firrtl.asyncreset'}} + %innerReset2 = firrtl.wire {annotations = [{class = "circt.FullResetAnnotation", resetType = "sync"}]} : !firrtl.asyncreset + // expected-error @below {{'FullResetAnnotation' with resetType == 'sync' must target sync reset, but targets '!firrtl.uint<2>'}} + %innerReset3 = firrtl.wire {annotations = [{class = "circt.FullResetAnnotation", resetType = "sync"}]} : !firrtl.uint<2> } } // ----- -// Reset annotation cannot target reset signals which are inferred to be synchronous +// Reset annotation cannot target reset signals which are inferred to the wrong type firrtl.circuit "top" { firrtl.module @top() { - // expected-error @below {{'FullAsyncResetAnnotation' must target async reset, but targets '!firrtl.uint<1>'}} - %innerReset = firrtl.wire {annotations = [{class = "sifive.enterprise.firrtl.FullAsyncResetAnnotation"}]} : !firrtl.reset + // expected-error @below {{'FullResetAnnotation' with resetType == 'async' must target async reset, but targets '!firrtl.uint<1>'}} + %innerReset = firrtl.wire {annotations = [{class = "circt.FullResetAnnotation", resetType = "async"}]} : !firrtl.reset %invalid = firrtl.invalidvalue : !firrtl.reset firrtl.matchingconnect %innerReset, %invalid : !firrtl.reset + + // expected-error @below {{'FullResetAnnotation' with resetType == 'sync' must target sync reset, but targets '!firrtl.asyncreset'}} + %innerReset2 = firrtl.wire {annotations = [{class = "circt.FullResetAnnotation", resetType = "sync"}]} : !firrtl.reset + %asyncWire = firrtl.wire : !firrtl.asyncreset + firrtl.connect %innerReset2, %asyncWire : !firrtl.reset, !firrtl.asyncreset } } @@ -187,8 +195,8 @@ firrtl.circuit "top" { // ----- // Ignore reset annotation cannot target port firrtl.circuit "top" { - // expected-error @+1 {{IgnoreFullAsyncResetAnnotation' cannot target port; must target module instead}} - firrtl.module @top(in %reset: !firrtl.asyncreset) attributes {portAnnotations =[[{class = "sifive.enterprise.firrtl.IgnoreFullAsyncResetAnnotation"}]]} { + // expected-error @+1 {{ExcludeFromFullResetAnnotation' cannot target port/wire/node; must target module instead}} + firrtl.module @top(in %reset: !firrtl.asyncreset) attributes {portAnnotations =[[{class = "circt.ExcludeFromFullResetAnnotation"}]]} { } } @@ -196,14 +204,14 @@ firrtl.circuit "top" { // Ignore reset annotation cannot target wire/node firrtl.circuit "top" { firrtl.module @top() { - // expected-error @+1 {{IgnoreFullAsyncResetAnnotation' cannot target wire/node; must target module instead}} - %0 = firrtl.wire {annotations = [{class = "sifive.enterprise.firrtl.IgnoreFullAsyncResetAnnotation"}]} : !firrtl.asyncreset - // expected-error @+1 {{IgnoreFullAsyncResetAnnotation' cannot target wire/node; must target module instead}} - %1 = firrtl.node %0 {annotations = [{class = "sifive.enterprise.firrtl.IgnoreFullAsyncResetAnnotation"}]} : !firrtl.asyncreset + // expected-error @+1 {{ExcludeFromFullResetAnnotation' cannot target port/wire/node; must target module instead}} + %0 = firrtl.wire {annotations = [{class = "circt.ExcludeFromFullResetAnnotation"}]} : !firrtl.asyncreset + // expected-error @+1 {{ExcludeFromFullResetAnnotation' cannot target port/wire/node; must target module instead}} + %1 = firrtl.node %0 {annotations = [{class = "circt.ExcludeFromFullResetAnnotation"}]} : !firrtl.asyncreset // expected-error @+1 {{reset annotations must target module, port, or wire/node}} - %2 = firrtl.asUInt %0 {annotations = [{class = "sifive.enterprise.firrtl.FullAsyncResetAnnotation"}]} : (!firrtl.asyncreset) -> !firrtl.uint<1> + %2 = firrtl.asUInt %0 {annotations = [{class = "circt.FullResetAnnotation", resetType = "async"}]} : (!firrtl.asyncreset) -> !firrtl.uint<1> // expected-error @+1 {{reset annotations must target module, port, or wire/node}} - %3 = firrtl.asUInt %0 {annotations = [{class = "sifive.enterprise.firrtl.IgnoreFullAsyncResetAnnotation"}]} : (!firrtl.asyncreset) -> !firrtl.uint<1> + %3 = firrtl.asUInt %0 {annotations = [{class = "circt.ExcludeFromFullResetAnnotation"}]} : (!firrtl.asyncreset) -> !firrtl.uint<1> } } @@ -211,12 +219,12 @@ firrtl.circuit "top" { // Cannot have multiple reset annotations on a module firrtl.circuit "top" { // expected-error @+2 {{multiple reset annotations on module 'top'}} - // expected-note @+1 {{conflicting "sifive.enterprise.firrtl.FullAsyncResetAnnotation":}} - firrtl.module @top(in %outerReset: !firrtl.asyncreset) attributes {portAnnotations = [[{class = "sifive.enterprise.firrtl.FullAsyncResetAnnotation"}]]} { - // expected-note @+1 {{conflicting "sifive.enterprise.firrtl.FullAsyncResetAnnotation":}} - %innerReset = firrtl.wire {annotations = [{class = "sifive.enterprise.firrtl.FullAsyncResetAnnotation"}]} : !firrtl.asyncreset - // expected-note @+1 {{conflicting "sifive.enterprise.firrtl.FullAsyncResetAnnotation":}} - %anotherReset = firrtl.node %innerReset {annotations = [{class = "sifive.enterprise.firrtl.FullAsyncResetAnnotation"}]} : !firrtl.asyncreset + // expected-note @+1 {{conflicting "circt.FullResetAnnotation":}} + firrtl.module @top(in %outerReset: !firrtl.asyncreset) attributes {portAnnotations = [[{class = "circt.FullResetAnnotation", resetType = "async"}]]} { + // expected-note @+1 {{conflicting "circt.FullResetAnnotation":}} + %innerReset = firrtl.wire {annotations = [{class = "circt.FullResetAnnotation", resetType = "async"}]} : !firrtl.asyncreset + // expected-note @+1 {{conflicting "circt.FullResetAnnotation":}} + %anotherReset = firrtl.node %innerReset {annotations = [{class = "circt.FullResetAnnotation", resetType = "async"}]} : !firrtl.asyncreset } } @@ -228,18 +236,18 @@ firrtl.circuit "Top" { %reg = firrtl.reg %clock : !firrtl.clock, !firrtl.uint<8> } // expected-note @+1 {{reset domain 'otherReset' of module 'Child' declared here:}} - firrtl.module @Child(in %clock: !firrtl.clock, in %otherReset: !firrtl.asyncreset) attributes {portAnnotations = [[],[{class = "sifive.enterprise.firrtl.FullAsyncResetAnnotation"}]]} { + firrtl.module @Child(in %clock: !firrtl.clock, in %otherReset: !firrtl.asyncreset) attributes {portAnnotations = [[],[{class = "circt.FullResetAnnotation", resetType = "async"}]]} { // expected-note @+1 {{instance 'child/inst' is in reset domain rooted at 'otherReset' of module 'Child'}} %inst_clock = firrtl.instance inst @Foo(in clock: !firrtl.clock) firrtl.connect %inst_clock, %clock : !firrtl.clock, !firrtl.clock } - firrtl.module @Other(in %clock: !firrtl.clock) attributes {annotations = [{class = "sifive.enterprise.firrtl.IgnoreFullAsyncResetAnnotation"}]} { + firrtl.module @Other(in %clock: !firrtl.clock) attributes {annotations = [{class = "circt.ExcludeFromFullResetAnnotation"}]} { // expected-note @+1 {{instance 'other/inst' is in no reset domain}} %inst_clock = firrtl.instance inst @Foo(in clock: !firrtl.clock) firrtl.connect %inst_clock, %clock : !firrtl.clock, !firrtl.clock } // expected-note @+1 {{reset domain 'reset' of module 'Top' declared here:}} - firrtl.module @Top(in %clock: !firrtl.clock, in %reset: !firrtl.asyncreset) attributes {portAnnotations = [[],[{class = "sifive.enterprise.firrtl.FullAsyncResetAnnotation"}]]} { + firrtl.module @Top(in %clock: !firrtl.clock, in %reset: !firrtl.asyncreset) attributes {portAnnotations = [[],[{class = "circt.FullResetAnnotation", resetType = "async"}]]} { %child_clock, %child_otherReset = firrtl.instance child @Child(in clock: !firrtl.clock, in otherReset: !firrtl.asyncreset) %other_clock = firrtl.instance other @Other(in clock: !firrtl.clock) // expected-note @+1 {{instance 'foo' is in reset domain rooted at 'reset' of module 'Top'}} @@ -266,3 +274,10 @@ firrtl.circuit "UninferredRefReset" { // expected-note @+1 {{the module with this uninferred reset port was defined here}} firrtl.module private @UninferredRefResetPriv(out %reset: !firrtl.probe) {} } + +// ----- +// Invalid FullResetAnnotation resetType +firrtl.circuit "Top" { + // expected-error @+1 {{'FullResetAnnotation' requires resetType == 'sync' | 'async', but got resetType == "potato"}} + firrtl.module @Top(in %reset: !firrtl.asyncreset) attributes {portAnnotations = [[{class = "circt.FullResetAnnotation", resetType = "potato"}]]} {} +} \ No newline at end of file diff --git a/test/Dialect/FIRRTL/infer-resets.mlir b/test/Dialect/FIRRTL/infer-resets.mlir index 7f13717d7792..1eb7d0e0a77d 100644 --- a/test/Dialect/FIRRTL/infer-resets.mlir +++ b/test/Dialect/FIRRTL/infer-resets.mlir @@ -358,18 +358,18 @@ firrtl.module @ForeignTypes(out %out: !firrtl.reset) { // CHECK-LABEL: firrtl.module @ConsumeIgnoreAnno -// CHECK-NOT: IgnoreFullAsyncResetAnnotation -firrtl.module @ConsumeIgnoreAnno() attributes {annotations = [{class = "sifive.enterprise.firrtl.IgnoreFullAsyncResetAnnotation"}]} { +// CHECK-NOT: ExcludeFromFullResetAnnotation +firrtl.module @ConsumeIgnoreAnno() attributes {annotations = [{class = "circt.ExcludeFromFullResetAnnotation"}]} { } } // firrtl.circuit // ----- -// Reset-less registers should inherit the annotated async reset signal. +// AsyncReset-less registers should inherit the annotated async reset signal. firrtl.circuit "Top" { // CHECK-LABEL: firrtl.module @Top firrtl.module @Top(in %clock: !firrtl.clock, in %reset: !firrtl.asyncreset, in %init: !firrtl.uint<1>, in %in: !firrtl.uint<8>, in %extraReset: !firrtl.asyncreset ) attributes { - portAnnotations = [[],[],[],[],[{class = "firrtl.transforms.DontTouchAnnotation"}, {class = "sifive.enterprise.firrtl.FullAsyncResetAnnotation"}]]} { + portAnnotations = [[],[],[],[],[{class = "firrtl.transforms.DontTouchAnnotation"}, {class = "circt.FullResetAnnotation", resetType = "async"}]]} { %c1_ui8 = firrtl.constant 1 : !firrtl.uint<8> // CHECK: %reg1 = firrtl.regreset sym @reg1 %clock, %extraReset, %c0_ui8 %reg1 = firrtl.reg sym @reg1 %clock : !firrtl.clock, !firrtl.uint<8> @@ -434,13 +434,36 @@ firrtl.circuit "Top" { } } +// ----- +// Reset-less registers should inherit the annotated sync reset signal. +firrtl.circuit "Top" { + // CHECK-LABEL: firrtl.module @Top + firrtl.module @Top(in %clock: !firrtl.clock, in %reset: !firrtl.asyncreset, in %init: !firrtl.uint<1>, in %in: !firrtl.uint<8>, in %extraReset: !firrtl.uint<1> ) attributes { + portAnnotations = [[],[],[],[],[{class = "firrtl.transforms.DontTouchAnnotation"}, {class = "circt.FullResetAnnotation", resetType = "sync"}]]} { + %c1_ui8 = firrtl.constant 1 : !firrtl.uint<8> + // CHECK: %reg1 = firrtl.regreset sym @reg1 %clock, %extraReset, %c0_ui8 + %reg1 = firrtl.reg sym @reg1 %clock : !firrtl.clock, !firrtl.uint<8> + firrtl.matchingconnect %reg1, %in : !firrtl.uint<8> + + // Existing async reset remains untouched. + // CHECK: %reg2 = firrtl.regreset %clock, %reset, %c1_ui8 + %reg2 = firrtl.regreset %clock, %reset, %c1_ui8 : !firrtl.clock, !firrtl.asyncreset, !firrtl.uint<8>, !firrtl.uint<8> + firrtl.matchingconnect %reg2, %in : !firrtl.uint<8> + + // Existing sync reset remains untouched. + // CHECK: %reg3 = firrtl.regreset %clock, %init, %c1_ui8 + %reg3 = firrtl.regreset %clock, %init, %c1_ui8 : !firrtl.clock, !firrtl.uint<1>, !firrtl.uint<8>, !firrtl.uint<8> + firrtl.matchingconnect %reg3, %in : !firrtl.uint<8> + } +} + // ----- // Async reset inference should be able to construct reset values for aggregate // types. firrtl.circuit "Top" { // CHECK-LABEL: firrtl.module @Top firrtl.module @Top(in %clock: !firrtl.clock, in %reset: !firrtl.asyncreset) attributes { - portAnnotations = [[],[{class = "sifive.enterprise.firrtl.FullAsyncResetAnnotation"}]]} { + portAnnotations = [[],[{class = "circt.FullResetAnnotation", resetType = "async"}]]} { // CHECK: %c0_ui = firrtl.constant 0 : !firrtl.const.uint // CHECK: %reg_uint = firrtl.regreset %clock, %reset, %c0_ui %reg_uint = firrtl.reg %clock : !firrtl.clock, !firrtl.uint @@ -476,8 +499,8 @@ firrtl.circuit "Top" { } // ----- -// Reset should reuse ports if name and type matches. -firrtl.circuit "ReusePorts" { +// Reset should reuse ports if name and type matches for async wiring. +firrtl.circuit "ReusePortsAsync" { // CHECK-LABEL: firrtl.module @Child // CHECK-SAME: in %clock: !firrtl.clock // CHECK-SAME: in %reset: !firrtl.asyncreset @@ -502,8 +525,8 @@ firrtl.circuit "ReusePorts" { %reg = firrtl.reg %clock : !firrtl.clock, !firrtl.uint<8> } // CHECK-LABEL: firrtl.module @ReusePorts - firrtl.module @ReusePorts(in %clock: !firrtl.clock, in %reset: !firrtl.asyncreset) attributes { - portAnnotations = [[],[{class = "sifive.enterprise.firrtl.FullAsyncResetAnnotation"}]]} { + firrtl.module @ReusePortsAsync(in %clock: !firrtl.clock, in %reset: !firrtl.asyncreset) attributes { + portAnnotations = [[],[{class = "circt.FullResetAnnotation", resetType = "async"}]]} { // CHECK: %child_clock, %child_reset = firrtl.instance child // CHECK: firrtl.matchingconnect %child_reset, %reset // CHECK: %badName_reset, %badName_clock, %badName_existingReset = firrtl.instance badName @@ -516,6 +539,47 @@ firrtl.circuit "ReusePorts" { } } +// ----- +// Reset should reuse ports if name and type matches for sync wiring. +firrtl.circuit "ReusePortsSync" { + // CHECK-LABEL: firrtl.module @Child + // CHECK-SAME: in %clock: !firrtl.clock + // CHECK-SAME: in %reset: !firrtl.uint<1> + // CHECK: %reg = firrtl.regreset %clock, %reset, %c0_ui8 + firrtl.module @Child(in %clock: !firrtl.clock, in %reset: !firrtl.uint<1>) { + %reg = firrtl.reg %clock : !firrtl.clock, !firrtl.uint<8> + } + // CHECK-LABEL: firrtl.module @BadName + // CHECK-SAME: in %reset: !firrtl.uint<1>, + // CHECK-SAME: in %clock: !firrtl.clock + // CHECK-SAME: in %existingReset: !firrtl.uint<1> + // CHECK: %reg = firrtl.regreset %clock, %reset, %c0_ui8 + firrtl.module @BadName(in %clock: !firrtl.clock, in %existingReset: !firrtl.uint<1>) { + %reg = firrtl.reg %clock : !firrtl.clock, !firrtl.uint<8> + } + // CHECK-LABEL: firrtl.module @BadType + // CHECK-SAME: in %reset_0: !firrtl.uint<1> + // CHECK-SAME: in %clock: !firrtl.clock + // CHECK-SAME: in %reset: !firrtl.asyncreset + // CHECK: %reg = firrtl.regreset %clock, %reset_0, %c0_ui8 + firrtl.module @BadType(in %clock: !firrtl.clock, in %reset: !firrtl.asyncreset) { + %reg = firrtl.reg %clock : !firrtl.clock, !firrtl.uint<8> + } + // CHECK-LABEL: firrtl.module @ReusePorts + firrtl.module @ReusePortsSync(in %clock: !firrtl.clock, in %reset: !firrtl.uint<1>) attributes { + portAnnotations = [[],[{class = "circt.FullResetAnnotation", resetType = "sync"}]]} { + // CHECK: %child_clock, %child_reset = firrtl.instance child + // CHECK: firrtl.matchingconnect %child_reset, %reset + // CHECK: %badName_reset, %badName_clock, %badName_existingReset = firrtl.instance badName + // CHECK: firrtl.matchingconnect %badName_reset, %reset + // CHECK: %badType_reset_0, %badType_clock, %badType_reset = firrtl.instance badType + // CHECK: firrtl.matchingconnect %badType_reset_0, %reset + %child_clock, %child_reset = firrtl.instance child @Child(in clock: !firrtl.clock, in reset: !firrtl.uint<1>) + %badName_clock, %badName_existingReset = firrtl.instance badName @BadName(in clock: !firrtl.clock, in existingReset: !firrtl.uint<1>) + %badType_clock, %badType_reset = firrtl.instance badType @BadType(in clock: !firrtl.clock, in reset: !firrtl.asyncreset) + } +} + // ----- // Infer async reset: nested firrtl.circuit "FullAsyncNested" { @@ -544,7 +608,7 @@ firrtl.circuit "FullAsyncNested" { } // CHECK-LABEL: firrtl.module @FullAsyncNested firrtl.module @FullAsyncNested(in %clock: !firrtl.clock, in %reset: !firrtl.asyncreset, in %io_in: !firrtl.uint<8>, out %io_out: !firrtl.uint<8>) attributes { - portAnnotations=[[],[{class = "firrtl.transforms.DontTouchAnnotation"}, {class = "sifive.enterprise.firrtl.FullAsyncResetAnnotation"}], [], []] } { + portAnnotations=[[],[{class = "firrtl.transforms.DontTouchAnnotation"}, {class = "circt.FullResetAnnotation", resetType = "async"}], [], []] } { %inst_clock, %inst_reset, %inst_io_in, %inst_io_out = firrtl.instance inst @FullAsyncNestedChild(in clock: !firrtl.clock, in reset: !firrtl.asyncreset, in io_in: !firrtl.uint<8>, out io_out: !firrtl.uint<8>) firrtl.matchingconnect %inst_clock, %clock : !firrtl.clock firrtl.matchingconnect %inst_reset, %reset : !firrtl.asyncreset @@ -560,7 +624,7 @@ firrtl.circuit "FullAsyncNested" { firrtl.circuit "FullAsyncExcluded" { // CHECK-LABEL: firrtl.module @FullAsyncExcludedChild // CHECK-SAME: (in %clock: !firrtl.clock, in %reset: !firrtl.asyncreset, in %io_in: !firrtl.uint<8>, out %io_out: !firrtl.uint<8>) - firrtl.module @FullAsyncExcludedChild(in %clock: !firrtl.clock, in %reset: !firrtl.asyncreset, in %io_in: !firrtl.uint<8>, out %io_out: !firrtl.uint<8>) attributes {annotations = [{class = "sifive.enterprise.firrtl.IgnoreFullAsyncResetAnnotation"}]} { + firrtl.module @FullAsyncExcludedChild(in %clock: !firrtl.clock, in %reset: !firrtl.asyncreset, in %io_in: !firrtl.uint<8>, out %io_out: !firrtl.uint<8>) attributes {annotations = [{class = "circt.ExcludeFromFullResetAnnotation"}]} { // CHECK: %io_out_REG = firrtl.reg %clock %io_out_REG = firrtl.reg %clock : !firrtl.clock, !firrtl.uint<8> firrtl.matchingconnect %io_out_REG, %io_in : !firrtl.uint<8> @@ -568,7 +632,7 @@ firrtl.circuit "FullAsyncExcluded" { } // CHECK-LABEL: firrtl.module @FullAsyncExcluded firrtl.module @FullAsyncExcluded(in %clock: !firrtl.clock, in %reset: !firrtl.asyncreset, in %io_in: !firrtl.uint<8>, out %io_out: !firrtl.uint<8>, in %extraReset: !firrtl.asyncreset) attributes { - portAnnotations = [[],[],[],[],[{class = "firrtl.transforms.DontTouchAnnotation"}, {class = "sifive.enterprise.firrtl.FullAsyncResetAnnotation"}]]} { + portAnnotations = [[],[],[],[],[{class = "firrtl.transforms.DontTouchAnnotation"}, {class = "circt.FullResetAnnotation", resetType = "async"}]]} { // CHECK: %inst_clock, %inst_reset, %inst_io_in, %inst_io_out = firrtl.instance inst @FullAsyncExcludedChild %inst_clock, %inst_reset, %inst_io_in, %inst_io_out = firrtl.instance inst @FullAsyncExcludedChild(in clock: !firrtl.clock, in reset: !firrtl.asyncreset, in io_in: !firrtl.uint<8>, out io_out: !firrtl.uint<8>) firrtl.matchingconnect %inst_clock, %clock : !firrtl.clock @@ -586,7 +650,7 @@ firrtl.circuit "WireShouldDominate" { // CHECK-LABEL: firrtl.module @WireShouldDominate firrtl.module @WireShouldDominate(in %clock: !firrtl.clock) { %reg = firrtl.reg %clock : !firrtl.clock, !firrtl.uint<8> // gets wired to localReset - %localReset = firrtl.wire {annotations = [{class = "sifive.enterprise.firrtl.FullAsyncResetAnnotation"}]} : !firrtl.asyncreset + %localReset = firrtl.wire {annotations = [{class = "circt.FullResetAnnotation", resetType = "async"}]} : !firrtl.asyncreset // CHECK-NEXT: %localReset = firrtl.wire // CHECK-NEXT: [[RV:%.+]] = firrtl.constant 0 // CHECK-NEXT: %reg = firrtl.regreset %clock, %localReset, [[RV]] @@ -602,7 +666,7 @@ firrtl.circuit "MovableNodeShouldDominate" { firrtl.module @MovableNodeShouldDominate(in %clock: !firrtl.clock, in %ui1: !firrtl.uint<1>) { %0 = firrtl.asAsyncReset %ui1 : (!firrtl.uint<1>) -> !firrtl.asyncreset // does not block move of node %reg = firrtl.reg %clock : !firrtl.clock, !firrtl.uint<8> // gets wired to localReset - %localReset = firrtl.node sym @theReset %0 {annotations = [{class = "sifive.enterprise.firrtl.FullAsyncResetAnnotation"}]} : !firrtl.asyncreset + %localReset = firrtl.node sym @theReset %0 {annotations = [{class = "circt.FullResetAnnotation", resetType = "async"}]} : !firrtl.asyncreset // CHECK-NEXT: %0 = firrtl.asAsyncReset %ui1 // CHECK-NEXT: %localReset = firrtl.node sym @theReset %0 // CHECK-NEXT: [[RV:%.+]] = firrtl.constant 0 @@ -620,7 +684,7 @@ firrtl.circuit "UnmovableNodeShouldDominate" { firrtl.module @UnmovableNodeShouldDominate(in %clock: !firrtl.clock, in %ui1: !firrtl.uint<1>) { %reg = firrtl.reg %clock : !firrtl.clock, !firrtl.uint<8> // gets wired to localReset %0 = firrtl.asAsyncReset %ui1 : (!firrtl.uint<1>) -> !firrtl.asyncreset // blocks move of node - %localReset = firrtl.node sym @theReset %0 {annotations = [{class = "sifive.enterprise.firrtl.FullAsyncResetAnnotation"}]} : !firrtl.asyncreset + %localReset = firrtl.node sym @theReset %0 {annotations = [{class = "circt.FullResetAnnotation", resetType = "async"}]} : !firrtl.asyncreset // CHECK-NEXT: %localReset = firrtl.wire sym @theReset // CHECK-NEXT: [[RV:%.+]] = firrtl.constant 0 // CHECK-NEXT: %reg = firrtl.regreset %clock, %localReset, [[RV]] @@ -638,7 +702,7 @@ firrtl.circuit "UnmovableForceableNodeShouldDominate" { firrtl.module @UnmovableForceableNodeShouldDominate(in %clock: !firrtl.clock, in %ui1: !firrtl.uint<1>) { %reg = firrtl.reg %clock : !firrtl.clock, !firrtl.uint<8> // gets wired to localReset %0 = firrtl.asAsyncReset %ui1 : (!firrtl.uint<1>) -> !firrtl.asyncreset // blocks move of node - %localReset, %ref = firrtl.node sym @theReset %0 forceable {annotations = [{class = "sifive.enterprise.firrtl.FullAsyncResetAnnotation"}]} : !firrtl.asyncreset + %localReset, %ref = firrtl.node sym @theReset %0 forceable {annotations = [{class = "circt.FullResetAnnotation", resetType = "async"}]} : !firrtl.asyncreset // CHECK-NEXT: %localReset, %{{.+}} = firrtl.wire sym @theReset // CHECK-NEXT: [[RV:%.+]] = firrtl.constant 0 // CHECK-NEXT: %reg = firrtl.regreset %clock, %localReset, [[RV]] @@ -660,7 +724,7 @@ firrtl.circuit "MoveAcrossBlocks1" { } firrtl.when %ui1 : !firrtl.uint<1> { %0 = firrtl.asAsyncReset %ui1 : (!firrtl.uint<1>) -> !firrtl.asyncreset // blocks move of node - %localReset = firrtl.node sym @theReset %0 {annotations = [{class = "sifive.enterprise.firrtl.FullAsyncResetAnnotation"}]} : !firrtl.asyncreset + %localReset = firrtl.node sym @theReset %0 {annotations = [{class = "circt.FullResetAnnotation", resetType = "async"}]} : !firrtl.asyncreset } // CHECK-NEXT: %localReset = firrtl.wire // CHECK-NEXT: firrtl.when %ui1 : !firrtl.uint<1> { @@ -683,7 +747,7 @@ firrtl.circuit "MoveAcrossBlocks2" { // <-- should move reset here firrtl.when %ui1 : !firrtl.uint<1> { %0 = firrtl.asAsyncReset %ui1 : (!firrtl.uint<1>) -> !firrtl.asyncreset // blocks move of node - %localReset = firrtl.node sym @theReset %0 {annotations = [{class = "sifive.enterprise.firrtl.FullAsyncResetAnnotation"}]} : !firrtl.asyncreset + %localReset = firrtl.node sym @theReset %0 {annotations = [{class = "circt.FullResetAnnotation", resetType = "async"}]} : !firrtl.asyncreset } firrtl.when %ui1 : !firrtl.uint<1> { %reg = firrtl.reg %clock : !firrtl.clock, !firrtl.uint<8> // gets wired to localReset @@ -710,7 +774,7 @@ firrtl.circuit "MoveAcrossBlocks3" { %reg = firrtl.reg %clock : !firrtl.clock, !firrtl.uint<8> // gets wired to localReset firrtl.when %ui1 : !firrtl.uint<1> { %0 = firrtl.asAsyncReset %ui1 : (!firrtl.uint<1>) -> !firrtl.asyncreset // blocks move of node - %localReset = firrtl.node sym @theReset %0 {annotations = [{class = "sifive.enterprise.firrtl.FullAsyncResetAnnotation"}]} : !firrtl.asyncreset + %localReset = firrtl.node sym @theReset %0 {annotations = [{class = "circt.FullResetAnnotation", resetType = "async"}]} : !firrtl.asyncreset } // CHECK-NEXT: %localReset = firrtl.wire // CHECK-NEXT: [[RV:%.+]] = firrtl.constant 0 @@ -733,7 +797,7 @@ firrtl.circuit "MoveAcrossBlocks4" { %reg = firrtl.reg %clock : !firrtl.clock, !firrtl.uint<8> // gets wired to localReset } %0 = firrtl.asAsyncReset %ui1 : (!firrtl.uint<1>) -> !firrtl.asyncreset // blocks move of node - %localReset = firrtl.node sym @theReset %0 {annotations = [{class = "sifive.enterprise.firrtl.FullAsyncResetAnnotation"}]} : !firrtl.asyncreset + %localReset = firrtl.node sym @theReset %0 {annotations = [{class = "circt.FullResetAnnotation", resetType = "async"}]} : !firrtl.asyncreset // CHECK-NEXT: %localReset = firrtl.wire // CHECK-NEXT: firrtl.when %ui1 : !firrtl.uint<1> { // CHECK-NEXT: [[RV:%.+]] = firrtl.constant 0 @@ -750,7 +814,7 @@ firrtl.circuit "MoveAcrossBlocks4" { firrtl.circuit "SubAccess" { firrtl.module @SubAccess(in %clock: !firrtl.clock, in %reset: !firrtl.asyncreset, in %init: !firrtl.uint<1>, in %in: !firrtl.uint<8>, in %extraReset: !firrtl.asyncreset ) attributes { // CHECK-LABEL: firrtl.module @SubAccess - portAnnotations = [[],[],[],[],[{class = "firrtl.transforms.DontTouchAnnotation"}, {class = "sifive.enterprise.firrtl.FullAsyncResetAnnotation"}]]} { + portAnnotations = [[],[],[],[],[{class = "firrtl.transforms.DontTouchAnnotation"}, {class = "circt.FullResetAnnotation", resetType = "async"}]]} { %c1_ui8 = firrtl.constant 1 : !firrtl.uint<2> %arr = firrtl.wire : !firrtl.vector, 1> %reg6 = firrtl.regreset %clock, %init, %c1_ui8 : !firrtl.clock, !firrtl.uint<1>, !firrtl.uint<2>, !firrtl.uint<2> @@ -772,7 +836,7 @@ firrtl.circuit "SubAccess" { // CHECK-LABEL: firrtl.module @ZeroWidthRegister firrtl.circuit "ZeroWidthRegister" { firrtl.module @ZeroWidthRegister(in %clock: !firrtl.clock, in %reset: !firrtl.asyncreset) attributes { - portAnnotations = [[],[{class = "sifive.enterprise.firrtl.FullAsyncResetAnnotation"}]]} { + portAnnotations = [[],[{class = "circt.FullResetAnnotation", resetType = "async"}]]} { %reg = firrtl.reg %clock : !firrtl.clock, !firrtl.uint<0> // CHECK-NEXT: [[TMP:%.+]] = firrtl.constant 0 : !firrtl.const.uint<0> // CHECK-NEXT: %reg = firrtl.regreset %clock, %reset, [[TMP]] @@ -1143,8 +1207,8 @@ firrtl.circuit "MovableNodeShouldDominateInstance" { firrtl.connect %child_clock, %clock : !firrtl.clock %ui1 = firrtl.constant 1 : !firrtl.uint<1> %0 = firrtl.asAsyncReset %ui1 : (!firrtl.uint<1>) -> !firrtl.asyncreset - %localReset = firrtl.node %0 {annotations = [{class = "sifive.enterprise.firrtl.FullAsyncResetAnnotation"}]} : !firrtl.asyncreset - // CHECK: %localReset = firrtl.wire {annotations = [{class = "sifive.enterprise.firrtl.FullAsyncResetAnnotation"}]} : !firrtl.asyncreset + %localReset = firrtl.node %0 {annotations = [{class = "circt.FullResetAnnotation", resetType = "async"}]} : !firrtl.asyncreset + // CHECK: %localReset = firrtl.wire {annotations = [{class = "circt.FullResetAnnotation", resetType = "async"}]} : !firrtl.asyncreset // CHECK: %child_localReset, %child_clock = firrtl.instance child @Child(in localReset: !firrtl.asyncreset, in clock: !firrtl.clock } firrtl.module @Child(in %clock: !firrtl.clock) { diff --git a/test/firtool/async-reset-anno.fir b/test/firtool/async-reset-anno.fir index e6a14ddde84f..bb94d8fdde23 100644 --- a/test/firtool/async-reset-anno.fir +++ b/test/firtool/async-reset-anno.fir @@ -1,22 +1,24 @@ ; RUN: firtool %s -parse-only | circt-opt -pass-pipeline='builtin.module(firrtl.circuit(firrtl-infer-resets))' | FileCheck %s --check-prefixes COMMON,POST-INFER-RESETS ; RUN: firtool %s -parse-only | circt-opt -pass-pipeline='builtin.module(firrtl.circuit(firrtl-infer-resets,firrtl.module(firrtl-sfc-compat)))' | FileCheck %s --check-prefixes COMMON,POST-SFC-COMPAT -; Check that FullAsyncResetAnnotation exists after infer-resets pass +; Check that FullResetAnnotation exists after infer-resets pass ; but is deleted after sfc-compat FIRRTL version 3.3.0 circuit test :%[[ -{ "class":"sifive.enterprise.firrtl.FullAsyncResetAnnotation", - "target":"~test|test>reset" }, -{ "class":"sifive.enterprise.firrtl.FullAsyncResetAnnotation", - "target":"~test|foo>r" } +{ "class":"circt.FullResetAnnotation", + "target":"~test|test>reset", + "resetType":"async" }, +{ "class":"circt.FullResetAnnotation", + "target":"~test|foo>r", + "resetType":"async" } ]] ; COMMON-LABEL: module @test module test : input clock : Clock input reset : AsyncReset - ; POST-INFER-RESETS: [{class = "sifive.enterprise.firrtl.FullAsyncResetAnnotation"}] - ; POST-SFC-COMPAT-NOT: [{class = "sifive.enterprise.firrtl.FullAsyncResetAnnotation"}] + ; POST-INFER-RESETS: [{class = "circt.FullResetAnnotation", resetType = "async"}] + ; POST-SFC-COMPAT-NOT: [{class = "circt.FullResetAnnotation", resetType = "async"}] input in : { foo : UInt<8>, bar : UInt<8>} output out : { foo : UInt<8>, bar : UInt<8>} connect out, in @@ -28,7 +30,7 @@ circuit test :%[[ input in : { foo : UInt<8>, bar : UInt<8>} output out : { foo : UInt<8>, bar : UInt<8>} - ; POST-INFER-RESETS: [{class = "sifive.enterprise.firrtl.FullAsyncResetAnnotation"}] - ; POST-SFC-COMPAT-NOT: [{class = "sifive.enterprise.firrtl.FullAsyncResetAnnotation"}] + ; POST-INFER-RESETS: [{class = "circt.FullResetAnnotation", resetType = "async"}] + ; POST-SFC-COMPAT-NOT: [{class = "circt.FullResetAnnotation", resetType = "async"}] wire r : AsyncReset connect out, in diff --git a/test/firtool/async-reset.fir b/test/firtool/async-reset.fir index 9588be9ccc0a..66e0ed48f123 100644 --- a/test/firtool/async-reset.fir +++ b/test/firtool/async-reset.fir @@ -3,8 +3,9 @@ FIRRTL version 3.3.0 ; CHECK-LABEL: module test( circuit test :%[[{ - "class":"sifive.enterprise.firrtl.FullAsyncResetAnnotation", - "target":"~test|test>reset" + "class":"circt.FullResetAnnotation", + "target":"~test|test>reset", + "resetType":"async" }]] module test : input clock : Clock @@ -31,8 +32,9 @@ circuit test :%[[{ ; CHECK-LABEL: module test_wire( FIRRTL version 3.3.0 circuit test_wire :%[[{ - "class":"sifive.enterprise.firrtl.FullAsyncResetAnnotation", - "target":"~test_wire|test_wire>reset" + "class":"circt.FullResetAnnotation", + "target":"~test_wire|test_wire>reset", + "resetType":"async" }]] module test_wire : input clock : Clock @@ -56,3 +58,63 @@ circuit test_wire :%[[{ connect reg1, in connect reg2, reg1 connect out, reg2 + +;// ----- +; CHECK-LABEL: module test_sync( +FIRRTL version 3.3.0 +circuit test_sync :%[[{ + "class":"circt.FullResetAnnotation", + "target":"~test_sync|test_sync>reset", + "resetType":"sync" + }]] + module test_sync : + input clock : Clock + input reset : UInt<1> + input in : { foo : UInt<8>, bar : UInt<8>} + output out : { foo : UInt<8>, bar : UInt<8>} + + wire reg1_w : { foo : UInt<8>, bar : UInt<8>} + invalidate reg1_w.bar + invalidate reg1_w.foo + connect reg1_w.foo, UInt<8>(0hc) + invalidate reg1_w.bar + ; CHECK: always @(posedge clock) begin + ; CHECK-NEXT: if (reset) begin + ; CHECK-NEXT: reg1_foo <= 8'hC; + ; CHECK-NEXT: reg1_bar <= 8'h0; + regreset reg1 : { foo : UInt<8>, bar : UInt<8>}, clock, reset, reg1_w + wire reg2 : { foo : UInt<8>, bar : UInt<8>} + connect reg1, in + connect reg2, reg1 + connect out, reg2 + +;// ----- +; CHECK-LABEL: module test_wire_sync( +FIRRTL version 3.3.0 +circuit test_wire_sync :%[[{ + "class":"circt.FullResetAnnotation", + "target":"~test_wire_sync|test_wire_sync>reset", + "resetType":"sync" + }]] + module test_wire_sync : + input clock : Clock + input rst : UInt<1> + input in : { foo : UInt<8>, bar : UInt<8>} + output out : { foo : UInt<8>, bar : UInt<8>} + + node reset = rst + + wire reg1_w : { foo : UInt<8>, bar : UInt<8>} + invalidate reg1_w.bar + invalidate reg1_w.foo + connect reg1_w.foo, UInt<8>(0hc) + invalidate reg1_w.bar + ; CHECK: always @(posedge clock) begin + ; CHECK-NEXT: if (rst) begin + ; CHECK-NEXT: reg1_foo <= 8'hC; + ; CHECK-NEXT: reg1_bar <= 8'h0; + regreset reg1 : { foo : UInt<8>, bar : UInt<8>}, clock, reset, reg1_w + wire reg2 : { foo : UInt<8>, bar : UInt<8>} + connect reg1, in + connect reg2, reg1 + connect out, reg2 From 84d73b94aac932f2b1e545446a4c0b8720c04545 Mon Sep 17 00:00:00 2001 From: Martin Erhart Date: Fri, 9 Aug 2024 08:46:31 +0100 Subject: [PATCH 048/119] [MooreToCore] Fix variable op lowering of aggregate types (#7481) --- lib/Conversion/MooreToCore/MooreToCore.cpp | 22 ++++++++++++++-------- test/Conversion/MooreToCore/basic.mlir | 17 ++++++++++++++--- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/lib/Conversion/MooreToCore/MooreToCore.cpp b/lib/Conversion/MooreToCore/MooreToCore.cpp index ac741f9e9d55..f6b53d77d720 100644 --- a/lib/Conversion/MooreToCore/MooreToCore.cpp +++ b/lib/Conversion/MooreToCore/MooreToCore.cpp @@ -158,6 +158,7 @@ struct VariableOpConversion : public OpConversionPattern { LogicalResult matchAndRewrite(VariableOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override { + Location loc = op.getLoc(); Type resultType = typeConverter->convertType(op.getResult().getType()); Value init = adaptor.getInitial(); // TODO: Unsupport x/z, so the initial value is 0. @@ -165,10 +166,18 @@ struct VariableOpConversion : public OpConversionPattern { Domain::FourValued) return failure(); - if (!init) - init = rewriter.create(op->getLoc(), resultType, 0); - rewriter.replaceOpWithNewOp(op, hw::InOutType::get(resultType), - op.getNameAttr(), init); + if (!init) { + Type elementType = cast(resultType).getElementType(); + int64_t width = hw::getBitWidth(elementType); + if (width == -1) + return failure(); + + Value constZero = rewriter.create(loc, APInt(width, 0)); + init = rewriter.createOrFold(loc, elementType, constZero); + } + + rewriter.replaceOpWithNewOp(op, resultType, op.getNameAttr(), + init); return success(); } }; @@ -715,10 +724,7 @@ static void populateTypeConversion(TypeConverter &typeConverter) { }); typeConverter.addConversion([&](RefType type) -> std::optional { - if (isa(type.getNestedType())) - return mlir::IntegerType::get(type.getContext(), - type.getBitSize().value()); - return std::nullopt; + return hw::InOutType::get(typeConverter.convertType(type.getNestedType())); }); // Valid target types. diff --git a/test/Conversion/MooreToCore/basic.mlir b/test/Conversion/MooreToCore/basic.mlir index 0722144b2975..1df63b4ea905 100644 --- a/test/Conversion/MooreToCore/basic.mlir +++ b/test/Conversion/MooreToCore/basic.mlir @@ -312,9 +312,20 @@ moore.module @Variable() { moore.output } -// CHECK-LABEL: func @Struct -func.func @Struct(%arg0: !moore.struct<{exp_bits: i32, man_bits: i32}>) -> !moore.i32 { +// CHECK-LABEL: hw.module @Struct +moore.module @Struct(in %arg0 : !moore.struct<{exp_bits: i32, man_bits: i32}>, out a : !moore.i32, out b : !moore.struct<{exp_bits: i32, man_bits: i32}>, out c : !moore.struct<{exp_bits: i32, man_bits: i32}>) { // CHECK: hw.struct_extract %arg0["exp_bits"] : !hw.struct %0 = moore.struct_extract %arg0, "exp_bits" : !moore.struct<{exp_bits: i32, man_bits: i32}> -> i32 - return %0 : !moore.i32 + + // CHECK: [[C0:%.+]] = hw.constant 0 : i64 + // CHECK: [[INIT:%.+]] = hw.bitcast [[C0]] : (i64) -> !hw.struct + // CHECK: llhd.sig "" [[INIT]] : !hw.struct + // CHECK: llhd.sig "" %arg0 : !hw.struct + %1 = moore.variable : > + %2 = moore.variable %arg0 : > + + %3 = moore.read %1 : > + %4 = moore.read %2 : > + + moore.output %0, %3, %4 : !moore.i32, !moore.struct<{exp_bits: i32, man_bits: i32}>, !moore.struct<{exp_bits: i32, man_bits: i32}> } From 273439ec61037fbd963eadb3094322adeda0e1c5 Mon Sep 17 00:00:00 2001 From: Martin Erhart Date: Fri, 9 Aug 2024 09:58:03 +0100 Subject: [PATCH 049/119] [MooreToCore] Fix variable op lowering of aggregate types (#7481) (#7362) --- include/circt/Conversion/Passes.td | 2 +- lib/Conversion/MooreToCore/CMakeLists.txt | 1 + lib/Conversion/MooreToCore/MooreToCore.cpp | 164 +++++++++++++++++++-- test/Conversion/MooreToCore/basic.mlir | 130 +++++++++++++++- 4 files changed, 286 insertions(+), 11 deletions(-) diff --git a/include/circt/Conversion/Passes.td b/include/circt/Conversion/Passes.td index 684e6616cb01..7cf405fefe29 100644 --- a/include/circt/Conversion/Passes.td +++ b/include/circt/Conversion/Passes.td @@ -516,7 +516,7 @@ def ConvertMooreToCore : Pass<"convert-moore-to-core", "mlir::ModuleOp"> { }]; let constructor = "circt::createConvertMooreToCorePass()"; let dependentDialects = ["comb::CombDialect", "hw::HWDialect", - "llhd::LLHDDialect"]; + "llhd::LLHDDialect", "mlir::cf::ControlFlowDialect"]; } //===----------------------------------------------------------------------===// diff --git a/lib/Conversion/MooreToCore/CMakeLists.txt b/lib/Conversion/MooreToCore/CMakeLists.txt index d97747ce8404..89e706ea9d22 100644 --- a/lib/Conversion/MooreToCore/CMakeLists.txt +++ b/lib/Conversion/MooreToCore/CMakeLists.txt @@ -15,4 +15,5 @@ add_circt_conversion_library(CIRCTMooreToCore MLIRControlFlowDialect MLIRFuncDialect MLIRTransforms + MLIRSideEffectInterfaces ) diff --git a/lib/Conversion/MooreToCore/MooreToCore.cpp b/lib/Conversion/MooreToCore/MooreToCore.cpp index f6b53d77d720..af58a7e1ed01 100644 --- a/lib/Conversion/MooreToCore/MooreToCore.cpp +++ b/lib/Conversion/MooreToCore/MooreToCore.cpp @@ -18,6 +18,7 @@ #include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h" #include "mlir/Dialect/Func/IR/FuncOps.h" #include "mlir/IR/BuiltinDialect.h" +#include "mlir/Interfaces/SideEffectInterfaces.h" #include "mlir/Pass/Pass.h" #include "mlir/Transforms/DialectConversion.h" #include "llvm/ADT/TypeSwitch.h" @@ -148,6 +149,152 @@ struct InstanceOpConversion : public OpConversionPattern { } }; +struct ProcedureOpConversion : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(ProcedureOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + Location loc = op.getLoc(); + auto procOp = rewriter.create(loc); + + // TODO: properly handle the procedure kind attribute + if (op.getKind() != ProcedureKind::Always) + return rewriter.notifyMatchFailure(loc, "not yet supported"); + + // Collect all event ops in the procedure. + SmallVector events(op.getOps()); + + auto *entry = rewriter.createBlock(&procOp.getBody()); + auto *wait = rewriter.createBlock(&procOp.getBody()); + auto *check = rewriter.createBlock(&procOp.getBody()); + + // We need to add an empty entry block because it is not allowed in MLIR to + // branch back to the entry block. Instead we put the logic in the second + // block and branch to that. + rewriter.setInsertionPointToStart(entry); + rewriter.create(loc, wait); + + // The block in which we can sample the past and where the wait terminator + // resides. + rewriter.setInsertionPointToStart(wait); + + auto getSignal = [&](Value input) -> Value { + // If the read op input is defined outside and before the procedure + // operation, we can get the remapped value directly. + Value signal = rewriter.getRemappedValue(input); + + // Otherwise, it hasn't been converted yet, so we take the old one and + // insert a cast. + if (!signal) { + Type convertedType = typeConverter->convertType(input.getType()); + assert(convertedType && + "if the input has not been converted yet, it should have a " + "moore type and a valid type conversion"); + signal = + rewriter + .create(loc, convertedType, input) + ->getResult(0); + } + + return signal; + }; + + // All signals to observe in the `llhd.wait` operation. + SmallVector toObserve; + DenseSet alreadyObserved; + // If there are no event operations in the procedure, it's a combinational + // one. Thus we need to collect all signals used. + if (events.empty()) { + op->walk([&](Operation *operation) { + for (auto &operand : operation->getOpOperands()) { + Value value = getSignal(operand.get()); + auto memOp = dyn_cast(operation); + if (!memOp) + return; + + // The process is only sensitive to values that are read. + if (isa(operand.get().getType()) && + memOp.getEffectOnValue(operand.get()) + .has_value()) { + if (!alreadyObserved.contains(value)) + toObserve.push_back(value); + + alreadyObserved.insert(value); + } + } + }); + } + + // Forall edge triggered events, probe the old value + SmallVector oldValues(events.size(), Value()); + for (auto [i, event] : llvm::enumerate(events)) { + auto readOp = event.getInput().getDefiningOp(); + if (!readOp) + return failure(); + + Value signal = getSignal(readOp.getInput()); + toObserve.push_back(signal); + + // Non-edge triggered events only need the value in the present + if (event.getEdge() != Edge::None) + oldValues[i] = rewriter.create(loc, signal); + } + + rewriter.create(loc, toObserve, Value(), ValueRange{}, check); + rewriter.setInsertionPointToStart(check); + + if (events.empty()) { + rewriter.create(loc, &op.getBody().front()); + } else { + SmallVector disjuncts; + for (auto [i, signal, event] : llvm::enumerate(toObserve, events)) { + if (event.getEdge() == Edge::None) + disjuncts.push_back(rewriter.create(loc, signal)); + + if (event.getEdge() == Edge::PosEdge || + event.getEdge() == Edge::BothEdges) { + Value currVal = rewriter.create(loc, signal); + Value trueVal = rewriter.create(loc, APInt(1, 1)); + Value notOldVal = + rewriter.create(loc, oldValues[i], trueVal); + Value posedge = rewriter.create(loc, notOldVal, currVal); + disjuncts.push_back(posedge); + } + if (event.getEdge() == Edge::NegEdge || + event.getEdge() == Edge::BothEdges) { + Value currVal = rewriter.create(loc, signal); + Value trueVal = rewriter.create(loc, APInt(1, 1)); + Value notCurrVal = + rewriter.create(loc, currVal, trueVal); + Value posedge = + rewriter.create(loc, oldValues[i], notCurrVal); + disjuncts.push_back(posedge); + } + } + + Value isValid = rewriter.create(loc, disjuncts, false); + rewriter.create(loc, isValid, &op.getBody().front(), + wait); + } + + for (auto event : events) + rewriter.eraseOp(event); + + rewriter.inlineRegionBefore(op.getBody(), procOp.getBody(), + procOp.getBody().end()); + + for (auto returnOp : procOp.getOps()) { + rewriter.setInsertionPoint(returnOp); + rewriter.create(loc, wait); + rewriter.eraseOp(returnOp); + } + + rewriter.eraseOp(op); + return success(); + } +}; + //===----------------------------------------------------------------------===// // Declaration Conversion //===----------------------------------------------------------------------===// @@ -617,14 +764,12 @@ struct ReadOpConversion : public OpConversionPattern { LogicalResult matchAndRewrite(ReadOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override { - Type resultType = typeConverter->convertType(op.getResult().getType()); - rewriter.replaceOpWithNewOp(op, resultType, - adaptor.getInput()); + rewriter.replaceOpWithNewOp(op, adaptor.getInput()); return success(); } }; -template +template struct AssignOpConversion : public OpConversionPattern { using OpConversionPattern::OpConversionPattern; using OpAdaptor = typename OpTy::Adaptor; @@ -634,9 +779,8 @@ struct AssignOpConversion : public OpConversionPattern { ConversionPatternRewriter &rewriter) const override { // TODO: When we support delay control in Moore dialect, we need to update // this conversion. - auto timeAttr = - llhd::TimeAttr::get(op->getContext(), unsigned(0), - llvm::StringRef("ns"), unsigned(0), unsigned(0)); + auto timeAttr = llhd::TimeAttr::get( + op->getContext(), 0U, llvm::StringRef("ns"), DeltaTime, EpsilonTime); auto time = rewriter.create(op->getLoc(), timeAttr); rewriter.replaceOpWithNewOp(op, adaptor.getDst(), adaptor.getSrc(), time, Value{}); @@ -794,13 +938,15 @@ static void populateOpConversion(RewritePatternSet &patterns, ICmpOpConversion, // Patterns of structural operations. - SVModuleOpConversion, InstanceOpConversion, + SVModuleOpConversion, InstanceOpConversion, ProcedureOpConversion, // Patterns of shifting operations. ShrOpConversion, ShlOpConversion, AShrOpConversion, // Patterns of assignment operations. - AssignOpConversion, + AssignOpConversion, + AssignOpConversion, + AssignOpConversion, // Patterns of branch operations. CondBranchOpConversion, BranchOpConversion, diff --git a/test/Conversion/MooreToCore/basic.mlir b/test/Conversion/MooreToCore/basic.mlir index 1df63b4ea905..64c105f1d231 100644 --- a/test/Conversion/MooreToCore/basic.mlir +++ b/test/Conversion/MooreToCore/basic.mlir @@ -304,7 +304,7 @@ moore.module @Variable() { // CHECK: [[TMP2:%.+]] = hw.constant 10 : i32 %3 = moore.constant 10 : i32 - // CHECK: [[TIME:%.+]] = llhd.constant_time <0ns, 0d, 0e> + // CHECK: [[TIME:%.+]] = llhd.constant_time <0ns, 0d, 1e> // CHECK: llhd.drv [[A]], [[TMP2]] after [[TIME]] : !hw.inout moore.assign %a, %3 : i32 @@ -329,3 +329,131 @@ moore.module @Struct(in %arg0 : !moore.struct<{exp_bits: i32, man_bits: i32}>, o moore.output %0, %3, %4 : !moore.i32, !moore.struct<{exp_bits: i32, man_bits: i32}>, !moore.struct<{exp_bits: i32, man_bits: i32}> } + +// CHECK-LABEL: hw.module @Process +moore.module @Process(in %cond : i1) { + // CHECK: [[B:%.+]] = llhd.sig "b" + // CHECK: [[C:%.+]] = llhd.sig "c" + // CHECK: [[D:%.+]] = llhd.sig "d" + // CHECK: [[E:%.+]] = llhd.sig "e" + %b = moore.variable : + %c = moore.variable : + %d = moore.variable : + %e = moore.variable : + + // CHECK: llhd.process + moore.procedure always { + // CHECK: cf.br ^[[BB1:.+]] + // CHECK: ^[[BB1]]: + // CHECK: [[C_OLD:%.+]] = llhd.prb [[C]] + // CHECK: llhd.wait ([[B]], [[C]] : !hw.inout, !hw.inout), ^[[BB2:.+]] + // CHECK: ^[[BB2]]: + // CHECK: [[B_CURR:%.+]] = llhd.prb [[B]] + // CHECK: [[C_CURR:%.+]] = llhd.prb [[C]] + // CHECK: [[NOT_C_OLD:%.+]] = comb.xor [[C_OLD]], %true + // CHECK: [[POSEDGE:%.+]] = comb.and [[NOT_C_OLD]], [[C_CURR]] : i1 + // CHECK: [[PROCEED:%.+]] = comb.or [[B_CURR]], [[POSEDGE]] : i1 + // CHECK: cf.cond_br [[PROCEED]], ^[[BB3:.+]], ^[[BB1]] + // CHECK: ^[[BB3]]: + // CHECK: [[B_PRB:%.+]] = llhd.prb [[B]] + // CHECK: [[C_PRB:%.+]] = llhd.prb [[C]] + // CHECK: [[RES:%.+]] = comb.add [[B_PRB]], [[C_PRB]] : i1 + // CHECK: [[T0:%.+]] = llhd.constant_time <0ns, 1d, 0e> + // CHECK: llhd.drv [[D]], [[RES]] after [[T0]] + // CHECK: [[T1:%.+]] = llhd.constant_time <0ns, 0d, 1e> + // CHECK: llhd.drv [[E]], [[RES]] after [[T1]] + // CHECK: cf.br ^[[BB1]] + %br = moore.read %b : + moore.wait_event none %br : i1 + %cr = moore.read %c : + moore.wait_event posedge %cr : i1 + + %0 = moore.add %br, %cr : i1 + + moore.nonblocking_assign %d, %0 : i1 + moore.blocking_assign %e, %0 : i1 + moore.return + } + + // CHECK: llhd.process + moore.procedure always { + // CHECK: cf.br ^[[BB1:.+]] + // CHECK: ^[[BB1]]: + // CHECK: llhd.wait ([[B]], [[C]] : !hw.inout, !hw.inout), ^[[BB2:.+]] + // CHECK: ^[[BB2]]: + // CHECK: cf.br ^[[BB3:.+]] + // CHECK: ^[[BB3]]: + // CHECK: [[B_PRB:%.+]] = llhd.prb [[B]] + // CHECK: [[C_PRB:%.+]] = llhd.prb [[C]] + // CHECK: [[RES:%.+]] = comb.add [[B_PRB]], [[C_PRB]] : i1 + // CHECK: [[T0:%.+]] = llhd.constant_time <0ns, 1d, 0e> + // CHECK: llhd.drv [[D]], [[RES]] after [[T0]] + // CHECK: cf.br ^[[BB1]] + %br = moore.read %b : + %cr = moore.read %c : + %0 = moore.add %br, %cr : i1 + moore.nonblocking_assign %d, %0 : i1 + moore.return + } + + // CHECK: llhd.process + moore.procedure always { + // CHECK: cf.br ^[[BB1:.+]] + // CHECK: ^[[BB1]]: + // CHECK: [[C_OLD:%.+]] = llhd.prb [[C]] + // CHECK: llhd.wait ([[C]] : !hw.inout), ^[[BB2:.+]] + // CHECK: ^[[BB2]]: + // CHECK: [[C_CURR:%.+]] = llhd.prb [[C]] + // CHECK: [[NOT_C_OLD:%.+]] = comb.xor [[C_OLD]], %true + // CHECK: [[POSEDGE:%.+]] = comb.and [[NOT_C_OLD]], [[C_CURR]] : i1 + // CHECK: [[C_CURR1:%.+]] = llhd.prb [[C]] + // CHECK: [[NOT_C_CURR:%.+]] = comb.xor [[C_CURR1]], %true + // CHECK: [[NEGEDGE:%.+]] = comb.and [[C_OLD]], [[NOT_C_CURR]] : i1 + // CHECK: [[PROCEED:%.+]] = comb.or [[POSEDGE]], [[NEGEDGE]] : i1 + // CHECK: cf.cond_br [[PROCEED]], ^[[BB3:.+]], ^[[BB1]] + // CHECK: ^[[BB3]]: + // CHECK: [[RES:%.+]] = comb.add [[B_PRB:%.+]], [[C_PRB:%.+]] : i1 + // CHECK: [[T0:%.+]] = llhd.constant_time <0ns, 1d, 0e> + // CHECK: llhd.drv [[D]], [[RES]] after [[T0]] + // CHECK: cf.br ^[[BB1]] + moore.wait_event edge %cr : i1 + %0 = moore.add %br, %cr : i1 + moore.nonblocking_assign %d, %0 : i1 + moore.return + } + + // CHECK: [[B_PRB]] = llhd.prb [[B]] + // CHECK: [[C_PRB]] = llhd.prb [[C]] + %br = moore.read %b : + %cr = moore.read %c : + // CHECK: llhd.process + moore.procedure always { + // CHECK: cf.br ^[[BB1:.+]] + // CHECK: ^[[BB1]]: + // CHECK: [[C_OLD:%.+]] = llhd.prb [[C]] + // CHECK: llhd.wait ([[C]] : !hw.inout), ^[[BB2:.+]] + // CHECK: ^[[BB2]]: + // CHECK: [[C_CURR:%.+]] = llhd.prb [[C]] + // CHECK: [[NOT_C_CURR:%.+]] = comb.xor [[C_CURR]], %true + // CHECK: [[NEGEDGE:%.+]] = comb.and [[C_OLD]], [[NOT_C_CURR]] : i1 + // CHECK: [[PROCEED:%.+]] = comb.or [[NEGEDGE]] : i1 + // CHECK: cf.cond_br [[PROCEED]], ^[[BB3:.+]], ^[[BB1]] + // CHECK: ^[[BB3]]: + // CHECK: [[RES:%.+]] = comb.add [[B_PRB]], [[C_PRB]] : i1 + // CHECK: cf.cond_br %cond, ^[[BB4:.+]], ^[[BB5:.+]] + // CHECK: ^[[BB4]]: + // CHECK: [[T0:%.+]] = llhd.constant_time <0ns, 1d, 0e> + // CHECK: llhd.drv [[D]], [[RES]] after [[T0]] + // CHECK: cf.br ^[[BB1]] + // CHECK: ^[[BB5]]: + // CHECK: cf.br ^[[BB1]] + moore.wait_event negedge %cr : i1 + %0 = moore.add %br, %cr : i1 + cf.cond_br %cond, ^bb1, ^bb2 + ^bb1: + moore.nonblocking_assign %d, %0 : i1 + moore.return + ^bb2: + moore.return + } +} From a2c44c1e4f491f92b841b9e38f0e3dc321470368 Mon Sep 17 00:00:00 2001 From: Hideto Ueno Date: Fri, 9 Aug 2024 20:51:11 +0900 Subject: [PATCH 050/119] [FIRRTL] Canonicalize multibit_mux with narrow index (#7373) This adds a canonicalization to optimize multibit_mux whose index has narrow width. Close https://github.com/llvm/circt/issues/7361 --- lib/Dialect/FIRRTL/FIRRTLFolds.cpp | 11 +++++++++++ test/Dialect/FIRRTL/canonicalization.mlir | 15 +++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/lib/Dialect/FIRRTL/FIRRTLFolds.cpp b/lib/Dialect/FIRRTL/FIRRTLFolds.cpp index 17d013ba4f9f..1ef0ad8857d1 100644 --- a/lib/Dialect/FIRRTL/FIRRTLFolds.cpp +++ b/lib/Dialect/FIRRTL/FIRRTLFolds.cpp @@ -1656,6 +1656,17 @@ LogicalResult MultibitMuxOp::canonicalize(MultibitMuxOp op, return success(); } + // If the index width is narrower than the size of inputs, drop front + // elements. + auto indexWidth = op.getIndex().getType().getBitWidthOrSentinel(); + uint64_t inputSize = op.getInputs().size(); + if (indexWidth >= 0 && indexWidth < 64 && 1ull << indexWidth < inputSize) { + rewriter.modifyOpInPlace(op, [&]() { + op.getInputsMutable().erase(0, inputSize - (1ull << indexWidth)); + }); + return success(); + } + // If the op is a vector indexing (e.g. `multbit_mux idx, a[n-1], a[n-2], ..., // a[0]`), we can fold the op into subaccess op `a[idx]`. if (auto lastSubindex = op.getInputs().back().getDefiningOp()) { diff --git a/test/Dialect/FIRRTL/canonicalization.mlir b/test/Dialect/FIRRTL/canonicalization.mlir index cc7754073a6a..022922b59422 100644 --- a/test/Dialect/FIRRTL/canonicalization.mlir +++ b/test/Dialect/FIRRTL/canonicalization.mlir @@ -3619,5 +3619,20 @@ firrtl.module @sizeof(in %clock: !firrtl.clock, %n_bundle = firrtl.node interesting_name %s_bundle : !firrtl.uint<32> } +// CHECK-LABEL: @multibit_mux_drop_front +firrtl.module @multibit_mux_drop_front(in %vec_0: !firrtl.uint<8>, in %vec_1: !firrtl.uint<8>, + in %vec_2: !firrtl.uint<8>, in %vec_3: !firrtl.uint<8>, + in %vec_4: !firrtl.uint<8>, in %vec_5: !firrtl.uint<8>, + in %vec_6: !firrtl.uint<8>, in %vec_7: !firrtl.uint<8>, + in %index: !firrtl.uint<2>, in %index_unknown_width: !firrtl.uint, + out %b: !firrtl.uint<8>, out %c: !firrtl.uint<8>) { + // CHECK-NEXT: %0 = firrtl.multibit_mux %index, %vec_3, %vec_2, %vec_1, %vec_0 + %0 = firrtl.multibit_mux %index, %vec_7, %vec_6, %vec_5, %vec_4, %vec_3, %vec_2, %vec_1, %vec_0 : !firrtl.uint<2>, !firrtl.uint<8> + firrtl.matchingconnect %b, %0 : !firrtl.uint<8> + + // CHECK: %1 = firrtl.multibit_mux %index_unknown_width, %vec_7 + %1 = firrtl.multibit_mux %index_unknown_width, %vec_7, %vec_6, %vec_5, %vec_4, %vec_3, %vec_2, %vec_1, %vec_0 : !firrtl.uint, !firrtl.uint<8> + firrtl.matchingconnect %c, %1 : !firrtl.uint<8> +} } From 46e129756eb6b2eb4e9c949fbf3742180663909d Mon Sep 17 00:00:00 2001 From: John Demme Date: Fri, 9 Aug 2024 12:50:19 +0000 Subject: [PATCH 051/119] [ExportVerilog] Fix broken test: prepare-for-emission Mitigates #7414, but the true fix is likely #7484. --- test/Conversion/ExportVerilog/prepare-for-emission.mlir | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Conversion/ExportVerilog/prepare-for-emission.mlir b/test/Conversion/ExportVerilog/prepare-for-emission.mlir index 1992b2d8a33e..7bfa026fa110 100644 --- a/test/Conversion/ExportVerilog/prepare-for-emission.mlir +++ b/test/Conversion/ExportVerilog/prepare-for-emission.mlir @@ -1,5 +1,5 @@ // RUN: circt-opt %s --pass-pipeline='builtin.module(any(prepare-for-emission))' --split-input-file -verify-diagnostics | FileCheck %s -// RUN: circt-opt %s -export-verilog -split-input-file +// RUN: circt-opt %s --lower-verif-to-sv -export-verilog -split-input-file // CHECK: @namehint_variadic hw.module @namehint_variadic(in %a: i3, out b: i3) { From a6028eea60f6220cf06aaa545cf2ebcfb64bacc5 Mon Sep 17 00:00:00 2001 From: Schuyler Eldridge Date: Wed, 7 Aug 2024 20:33:20 -0400 Subject: [PATCH 052/119] [FIRRTL] Fix sub-* op in layer block verifier Fix issues with verification of subfield, subindex, and subaccess operations which appear in a layer block. These operations are allowed to occur in a layer block even if they capture non-passive operands. This requires reworking layer block verification to no longer check for operations using non-passive operands. The spec requires that no operation in a layer block _drives_ a value declared outside the layer block. However, this is exceedingly difficult to verify due to the fact that non-passive destinations in ConnectLike operations can be source-to-destination, destination-to-source, or bi-directional. If the verifier sees this, just allow it. The FIRRTL pass pipeline will later canonicalize away flips (i.e., make all types passive) which will then allow the verifier to check these. This should be revisited in the future. Fixes #7451. Signed-off-by: Schuyler Eldridge Don't check non-passive connects. --- lib/Dialect/FIRRTL/FIRRTLOps.cpp | 40 ++++++++++++++++++------------- test/Dialect/FIRRTL/errors.mlir | 17 ------------- test/Dialect/FIRRTL/layers.mlir | 41 ++++++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 33 deletions(-) diff --git a/lib/Dialect/FIRRTL/FIRRTLOps.cpp b/lib/Dialect/FIRRTL/FIRRTLOps.cpp index 2d01a650e334..4e5c94de3146 100644 --- a/lib/Dialect/FIRRTL/FIRRTLOps.cpp +++ b/lib/Dialect/FIRRTL/FIRRTLOps.cpp @@ -18,9 +18,11 @@ #include "circt/Dialect/FIRRTL/FIRRTLTypes.h" #include "circt/Dialect/FIRRTL/FIRRTLUtils.h" #include "circt/Dialect/FIRRTL/FIRRTLVisitors.h" +#include "circt/Dialect/FIRRTL/FieldRefCache.h" #include "circt/Dialect/HW/HWAttributes.h" #include "circt/Dialect/HW/HWTypes.h" #include "circt/Support/CustomDirectiveImpl.h" +#include "circt/Support/Utils.h" #include "mlir/IR/BuiltinTypes.h" #include "mlir/IR/Diagnostics.h" #include "mlir/IR/DialectImplementation.h" @@ -6284,6 +6286,7 @@ LogicalResult LayerBlockOp::verify() { } // Verify the body of the region. + FieldRefCache fieldRefCache; auto result = getBody(0)->walk( [&](Operation *op) -> WalkResult { // Skip nested layer blocks. Those will be verified separately. @@ -6307,17 +6310,6 @@ LogicalResult LayerBlockOp::verify() { diag.attachNote(op->getLoc()) << "operand is used here"; return WalkResult::interrupt(); } - - // Capturing a non-passive type is illegal. - if (auto baseType = type_dyn_cast(type)) { - if (!baseType.isPassive()) { - auto diag = emitOpError() - << "captures an operand which is not a passive type"; - diag.attachNote(operand.getLoc()) << "operand is defined here"; - diag.attachNote(op->getLoc()) << "operand is used here"; - return WalkResult::interrupt(); - } - } } // Ensure that the layer block does not drive any sinks outside. @@ -6326,11 +6318,27 @@ LogicalResult LayerBlockOp::verify() { if (isa(connect)) return WalkResult::advance(); - // We can drive any destination inside the current layerblock. - auto dest = getFieldRefFromValue(connect.getDest()).getValue(); - if (auto *destOp = dest.getDefiningOp()) - if (getOperation()->isAncestor(destOp)) - return WalkResult::advance(); + // Verify that connects only drive values declared in the layer block. + // If we see a non-passive connect destination, then verify that the + // source is in the same layer block so that the source is not driven. + auto dest = + fieldRefCache.getFieldRefFromValue(connect.getDest()).getValue(); + bool passive = true; + if (auto type = + type_dyn_cast(connect.getDest().getType())) + passive = type.isPassive(); + // TODO: Improve this verifier. This is intentionally _not_ verifying + // a non-passive ConnectLike because it is hugely annoying to do + // so---it requires a full understanding of if the connect is driving + // destination-to-source, source-to-destination, or bi-directionally + // which requires deep inspection of the type. Eventually, the FIRRTL + // pass pipeline will remove all flips (e.g., canonicalize connect to + // matchingconnect) and this hole won't exist. + if (!passive) + return WalkResult::advance(); + + if (isAncestorOfValueOwner(getOperation(), dest)) + return WalkResult::advance(); auto diag = connect.emitOpError() diff --git a/test/Dialect/FIRRTL/errors.mlir b/test/Dialect/FIRRTL/errors.mlir index 199dcc171d2a..ebb1c5dbaf74 100644 --- a/test/Dialect/FIRRTL/errors.mlir +++ b/test/Dialect/FIRRTL/errors.mlir @@ -1894,23 +1894,6 @@ firrtl.circuit "WrongLayerBlockNesting" { // ----- -// A layer block captures a non-passive type. -firrtl.circuit "NonPassiveCapture" { - firrtl.layer @A bind {} - firrtl.module @NonPassiveCapture() { - // expected-note @below {{operand is defined here}} - %a = firrtl.wire : !firrtl.bundle> - // expected-error @below {{'firrtl.layerblock' op captures an operand which is not a passive type}} - firrtl.layerblock @A { - %b = firrtl.wire : !firrtl.bundle> - // expected-note @below {{operand is used here}} - firrtl.connect %b, %a : !firrtl.bundle>, !firrtl.bundle> - } - } -} - -// ----- - // A layer block may not drive sinks outside the layer block. firrtl.circuit "LayerBlockDrivesSinksOutside" { firrtl.layer @A bind {} diff --git a/test/Dialect/FIRRTL/layers.mlir b/test/Dialect/FIRRTL/layers.mlir index 23567729cda7..2155d0cc7910 100644 --- a/test/Dialect/FIRRTL/layers.mlir +++ b/test/Dialect/FIRRTL/layers.mlir @@ -25,6 +25,47 @@ firrtl.circuit "Test" { firrtl.ref.define %o, %s : !firrtl.probe, @B> } + //===--------------------------------------------------------------------===// + // Capture Tests + //===--------------------------------------------------------------------===// + + firrtl.module @CaptureTests() { + %a = firrtl.wire : !firrtl.vector, b flip: uint<1>>, 2> + %b = firrtl.wire : !firrtl.bundle, b flip: uint<2>> + firrtl.layerblock @A { + %0 = firrtl.subindex %a[0] : !firrtl.vector, b flip: uint<1>>, 2> + %1 = firrtl.constant 0 : !firrtl.uint<1> + %2 = firrtl.subaccess %a[%1] : !firrtl.vector, b flip: uint<1>>, 2>, !firrtl.uint<1> + %3 = firrtl.subfield %b[a] : !firrtl.bundle, b flip: uint<2>> + %4 = firrtl.ref.send %b : !firrtl.bundle, b flip: uint<2>> + } + } + + //===--------------------------------------------------------------------===// + // Connect Tests + //===--------------------------------------------------------------------===// + + firrtl.module @ConnectTests() { + %a = firrtl.wire : !firrtl.uint<1> + %b = firrtl.wire : !firrtl.bundle> + %c = firrtl.wire : !firrtl.bundle> + %d = firrtl.wire : !firrtl.bundle>> + %e = firrtl.wire : !firrtl.bundle>> + firrtl.layerblock @A { + %_a = firrtl.wire : !firrtl.uint<1> + %_b = firrtl.wire : !firrtl.bundle> + %_c = firrtl.wire : !firrtl.bundle> + %_d = firrtl.wire : !firrtl.bundle>> + %_e = firrtl.wire : !firrtl.bundle>> + + firrtl.connect %_a, %a : !firrtl.uint<1> + firrtl.connect %_b, %b : !firrtl.bundle> + firrtl.connect %c, %_c : !firrtl.bundle> + firrtl.connect %d, %_d : !firrtl.bundle>> + firrtl.connect %_e, %e : !firrtl.bundle>> + } + } + //===--------------------------------------------------------------------===// // Basic Casting Tests //===--------------------------------------------------------------------===// From 61d2719269cef338c1169fa13edcdddaa3c95f58 Mon Sep 17 00:00:00 2001 From: Mike Urbach Date: Fri, 9 Aug 2024 11:56:40 -0600 Subject: [PATCH 053/119] [OM] Add AnyType C API and Python bindings. (#7488) We want to expose this type through the Python bindings for isinstance queries, etc., so add the necessary boilerplate. --- include/circt-c/Dialect/OM.h | 6 ++++++ integration_test/Bindings/Python/dialects/om.py | 6 +++++- lib/Bindings/Python/OMModule.cpp | 3 +++ lib/Bindings/Python/dialects/om.py | 2 +- lib/CAPI/Dialect/OM.cpp | 6 ++++++ 5 files changed, 21 insertions(+), 2 deletions(-) diff --git a/include/circt-c/Dialect/OM.h b/include/circt-c/Dialect/OM.h index 2248113d98c1..2085edfb9edb 100644 --- a/include/circt-c/Dialect/OM.h +++ b/include/circt-c/Dialect/OM.h @@ -25,6 +25,12 @@ MLIR_DECLARE_CAPI_DIALECT_REGISTRATION(OM, om); // Type API. //===----------------------------------------------------------------------===// +/// Is the Type an AnyType. +MLIR_CAPI_EXPORTED bool omTypeIsAAnyType(MlirType type); + +/// Get the TypeID for an AnyType. +MLIR_CAPI_EXPORTED MlirTypeID omAnyTypeGetTypeID(void); + /// Is the Type a ClassType. MLIR_CAPI_EXPORTED bool omTypeIsAClassType(MlirType type); diff --git a/integration_test/Bindings/Python/dialects/om.py b/integration_test/Bindings/Python/dialects/om.py index e7afceb1ddc3..bb863a691417 100644 --- a/integration_test/Bindings/Python/dialects/om.py +++ b/integration_test/Bindings/Python/dialects/om.py @@ -3,7 +3,7 @@ import circt from circt.dialects import om -from circt.ir import Context, InsertionPoint, Location, Module, IntegerAttr, IntegerType +from circt.ir import Context, InsertionPoint, Location, Module, IntegerAttr, IntegerType, Type from circt.support import var_to_attribute from dataclasses import dataclass @@ -307,3 +307,7 @@ IntegerAttr.get(IntegerType.get_unsigned(64), -42)) # CHECK: 18446744073709551574 print(str(int_attr6)) + + # Test AnyType + any_type = Type.parse("!om.any") + assert isinstance(any_type, om.AnyType) diff --git a/lib/Bindings/Python/OMModule.cpp b/lib/Bindings/Python/OMModule.cpp index 16fa03873ea1..eea989b73201 100644 --- a/lib/Bindings/Python/OMModule.cpp +++ b/lib/Bindings/Python/OMModule.cpp @@ -612,6 +612,9 @@ void circt::python::populateDialectOMSubmodule(py::module &m) { .def("__len__", &omMapAttrGetNumElements); PyMapAttrIterator::bind(m); + // Add the AnyType class definition. + mlir_type_subclass(m, "AnyType", omTypeIsAAnyType, omAnyTypeGetTypeID); + // Add the ClassType class definition. mlir_type_subclass(m, "ClassType", omTypeIsAClassType, omClassTypeGetTypeID) .def_property_readonly("name", [](MlirType type) { diff --git a/lib/Bindings/Python/dialects/om.py b/lib/Bindings/Python/dialects/om.py index bdf51eed84e1..123c6d104df8 100644 --- a/lib/Bindings/Python/dialects/om.py +++ b/lib/Bindings/Python/dialects/om.py @@ -5,7 +5,7 @@ from __future__ import annotations from ._om_ops_gen import * -from .._mlir_libs._circt._om import Evaluator as BaseEvaluator, Object as BaseObject, List as BaseList, Tuple as BaseTuple, Map as BaseMap, BasePath as BaseBasePath, BasePathType, Path, PathType, ClassType, ReferenceAttr, ListAttr, MapAttr, OMIntegerAttr +from .._mlir_libs._circt._om import AnyType, Evaluator as BaseEvaluator, Object as BaseObject, List as BaseList, Tuple as BaseTuple, Map as BaseMap, BasePath as BaseBasePath, BasePathType, Path, PathType, ClassType, ReferenceAttr, ListAttr, MapAttr, OMIntegerAttr from ..ir import Attribute, Diagnostic, DiagnosticSeverity, Module, StringAttr, IntegerAttr, IntegerType from ..support import attribute_to_var, var_to_attribute diff --git a/lib/CAPI/Dialect/OM.cpp b/lib/CAPI/Dialect/OM.cpp index 1263974d5ab8..fd40fb5acac0 100644 --- a/lib/CAPI/Dialect/OM.cpp +++ b/lib/CAPI/Dialect/OM.cpp @@ -29,6 +29,12 @@ MLIR_DEFINE_CAPI_DIALECT_REGISTRATION(OM, om, OMDialect) // Type API. //===----------------------------------------------------------------------===// +/// Is the Type an AnyType. +bool omTypeIsAAnyType(MlirType type) { return isa(unwrap(type)); } + +/// Get the TypeID for an AnyType. +MlirTypeID omAnyTypeGetTypeID(void) { return wrap(AnyType::getTypeID()); } + /// Is the Type a ClassType. bool omTypeIsAClassType(MlirType type) { return isa(unwrap(type)); } From 36a3a424fc73ad368c959cb5282d376eacc29a6e Mon Sep 17 00:00:00 2001 From: Mike Urbach Date: Fri, 9 Aug 2024 13:53:25 -0600 Subject: [PATCH 054/119] [OM] Add ListType C API and Python bindings. (#7490) We want to expose this type through the Python bindings for isinstance queries, etc., so add the necessary boilerplate. --- include/circt-c/Dialect/OM.h | 9 +++++++++ integration_test/Bindings/Python/dialects/om.py | 5 +++++ lib/Bindings/Python/OMModule.cpp | 4 ++++ lib/Bindings/Python/dialects/om.py | 2 +- lib/CAPI/Dialect/OM.cpp | 11 +++++++++++ 5 files changed, 30 insertions(+), 1 deletion(-) diff --git a/include/circt-c/Dialect/OM.h b/include/circt-c/Dialect/OM.h index 2085edfb9edb..d9089bb0d38c 100644 --- a/include/circt-c/Dialect/OM.h +++ b/include/circt-c/Dialect/OM.h @@ -52,6 +52,15 @@ MLIR_CAPI_EXPORTED bool omTypeIsAFrozenPathType(MlirType type); /// Get the TypeID for a FrozenPathType. MLIR_CAPI_EXPORTED MlirTypeID omFrozenPathTypeGetTypeID(void); +/// Is the Type a ListType. +MLIR_CAPI_EXPORTED bool omTypeIsAListType(MlirType type); + +/// Get the TypeID for a ListType. +MLIR_CAPI_EXPORTED MlirTypeID omListTypeGetTypeID(void); + +// Return a element type of a ListType. +MLIR_CAPI_EXPORTED MlirType omListTypeGetElementType(MlirType type); + /// Is the Type a MapType. MLIR_CAPI_EXPORTED bool omTypeIsAMapType(MlirType type); diff --git a/integration_test/Bindings/Python/dialects/om.py b/integration_test/Bindings/Python/dialects/om.py index bb863a691417..2e2bce8a4815 100644 --- a/integration_test/Bindings/Python/dialects/om.py +++ b/integration_test/Bindings/Python/dialects/om.py @@ -311,3 +311,8 @@ # Test AnyType any_type = Type.parse("!om.any") assert isinstance(any_type, om.AnyType) + + # Test ListType + list_type = Type.parse("!om.list") + assert isinstance(list_type, om.ListType) + assert isinstance(list_type.element_type, om.AnyType) diff --git a/lib/Bindings/Python/OMModule.cpp b/lib/Bindings/Python/OMModule.cpp index eea989b73201..7908867d116a 100644 --- a/lib/Bindings/Python/OMModule.cpp +++ b/lib/Bindings/Python/OMModule.cpp @@ -626,6 +626,10 @@ void circt::python::populateDialectOMSubmodule(py::module &m) { mlir_type_subclass(m, "BasePathType", omTypeIsAFrozenBasePathType, omFrozenBasePathTypeGetTypeID); + // Add the ListType class definition. + mlir_type_subclass(m, "ListType", omTypeIsAListType, omListTypeGetTypeID) + .def_property_readonly("element_type", omListTypeGetElementType); + // Add the PathType class definition. mlir_type_subclass(m, "PathType", omTypeIsAFrozenPathType, omFrozenPathTypeGetTypeID); diff --git a/lib/Bindings/Python/dialects/om.py b/lib/Bindings/Python/dialects/om.py index 123c6d104df8..ef1d9fe43871 100644 --- a/lib/Bindings/Python/dialects/om.py +++ b/lib/Bindings/Python/dialects/om.py @@ -5,7 +5,7 @@ from __future__ import annotations from ._om_ops_gen import * -from .._mlir_libs._circt._om import AnyType, Evaluator as BaseEvaluator, Object as BaseObject, List as BaseList, Tuple as BaseTuple, Map as BaseMap, BasePath as BaseBasePath, BasePathType, Path, PathType, ClassType, ReferenceAttr, ListAttr, MapAttr, OMIntegerAttr +from .._mlir_libs._circt._om import AnyType, Evaluator as BaseEvaluator, Object as BaseObject, List as BaseList, Tuple as BaseTuple, Map as BaseMap, BasePath as BaseBasePath, BasePathType, Path, PathType, ClassType, ReferenceAttr, ListAttr, ListType, MapAttr, OMIntegerAttr from ..ir import Attribute, Diagnostic, DiagnosticSeverity, Module, StringAttr, IntegerAttr, IntegerType from ..support import attribute_to_var, var_to_attribute diff --git a/lib/CAPI/Dialect/OM.cpp b/lib/CAPI/Dialect/OM.cpp index fd40fb5acac0..5b7f828737b9 100644 --- a/lib/CAPI/Dialect/OM.cpp +++ b/lib/CAPI/Dialect/OM.cpp @@ -66,6 +66,17 @@ MlirTypeID omFrozenPathTypeGetTypeID(void) { return wrap(FrozenPathType::getTypeID()); } +/// Is the Type a ListType. +bool omTypeIsAListType(MlirType type) { return isa(unwrap(type)); } + +/// Get the TypeID for a ListType. +MlirTypeID omListTypeGetTypeID(void) { return wrap(ListType::getTypeID()); } + +// Return a element type of a ListType. +MlirType omListTypeGetElementType(MlirType type) { + return wrap(cast(unwrap(type)).getElementType()); +} + /// Is the Type a StringType. bool omTypeIsAStringType(MlirType type) { return isa(unwrap(type)); From 562036cde8d08b68883484a0d44522819e52ba0c Mon Sep 17 00:00:00 2001 From: John Demme Date: Fri, 9 Aug 2024 22:24:32 +0200 Subject: [PATCH 055/119] [HWArith] Make `hwarith.icmp` result an `i1` (#7413) As discussed in #7406. Resolves #7406. --- docs/Dialects/HWArith/RationaleHWArith.md | 15 +++++++-------- include/circt/Dialect/HWArith/HWArithOps.td | 14 +++++++------- test/Conversion/HWArithToHW/test_basic.mlir | 14 ++------------ test/Dialect/HWArith/basic.mlir | 2 -- 4 files changed, 16 insertions(+), 29 deletions(-) diff --git a/docs/Dialects/HWArith/RationaleHWArith.md b/docs/Dialects/HWArith/RationaleHWArith.md index 86442c2deb9d..dfbea368cbe3 100644 --- a/docs/Dialects/HWArith/RationaleHWArith.md +++ b/docs/Dialects/HWArith/RationaleHWArith.md @@ -483,19 +483,18 @@ can be simplified to: %4 = hwarith.icmp lt %0, %1 : si3, ui5 ``` -Note that the result of the comparison is *always* of type `ui1`, regardless of -the operands. So if the `i1` type is needed, the result must be cast -accordingly. +Note that the result of the comparison is *always* of type `i1` since the +logical result is a boolean, which doesn't have signedness semantics. #### Overview | | LHS type | RHS type | Comparison type | Result type | | - | :------- | :------- | :--------------------------------------- | :---------- | -|(U)| `ui` | `ui` | `ui`, *r* = max(*a*, *b*) | `ui1` | -|(S)| `si` | `si` | `si`, *r* = max(*a*, *b*) | `ui1` | -|(M)| `ui` | `si` | `si`, *r* = *a* + 1 **if** *a* โ‰ฅ *b* | `ui1` | -| | | | `si`, *r* = *b* **if** *a* < *b* | `ui1` | -|(M)| `si` | `ui` | Same as `ui si` | `ui1` | +|(U)| `ui` | `ui` | `ui`, *r* = max(*a*, *b*) | `i1` | +|(S)| `si` | `si` | `si`, *r* = max(*a*, *b*) | `i1` | +|(M)| `ui` | `si` | `si`, *r* = *a* + 1 **if** *a* โ‰ฅ *b* | `i1` | +| | | | `si`, *r* = *b* **if** *a* < *b* | `i1` | +|(M)| `si` | `ui` | Same as `ui si` | `i1` | #### Examples ```mlir diff --git a/include/circt/Dialect/HWArith/HWArithOps.td b/include/circt/Dialect/HWArith/HWArithOps.td index 779d4ef35a7f..3425350d2941 100644 --- a/include/circt/Dialect/HWArith/HWArithOps.td +++ b/include/circt/Dialect/HWArith/HWArithOps.td @@ -227,16 +227,16 @@ def ICmpOp : HWArithOp<"icmp", [Pure]> { let description = [{ The `icmp` operation compares two integers using a predicate. If the predicate is true, returns 1, otherwise returns 0. This operation always - returns a one bit wide result of type `ui1`. Both operand types may be + returns a one bit wide result of type `i1`. Both operand types may be signed or unsigned scalar integer types of arbitrary bitwidth. | LHS type | RHS type | Comparison type | Result type | | :------- | :------- | :--------------------------------------- | :---------- | - | `ui` | `ui` | `ui`, *r* = max(*a*, *b*) | `ui1` | - | `si` | `si` | `si`, *r* = max(*a*, *b*) | `ui1` | - | `ui` | `si` | `si`, *r* = *a* + 1 **if** *a* โ‰ฅ *b* | `ui1` | - | | | `si`, *r* = *b* **if** *a* < *b* | `ui1` | - | `si` | `ui` | Same as `ui si` | `ui1` | + | `ui` | `ui` | `ui`, *r* = max(*a*, *b*) | `i1` | + | `si` | `si` | `si`, *r* = max(*a*, *b*) | `i1` | + | `ui` | `si` | `si`, *r* = *a* + 1 **if** *a* โ‰ฅ *b* | `i1` | + | | | `si`, *r* = *b* **if** *a* < *b* | `i1` | + | `si` | `ui` | Same as `ui si` | `i1` | Examples: ```mlir @@ -248,7 +248,7 @@ def ICmpOp : HWArithOp<"icmp", [Pure]> { let arguments = (ins ICmpPredicate:$predicate, HWArithIntegerType:$lhs, HWArithIntegerType:$rhs); - let results = (outs UI1:$result); + let results = (outs I1:$result); let assemblyFormat = "$predicate $lhs `,` $rhs attr-dict `:` type($lhs) `,` type($rhs)"; } diff --git a/test/Conversion/HWArithToHW/test_basic.mlir b/test/Conversion/HWArithToHW/test_basic.mlir index b44fa44c387c..11d4c5282ac6 100644 --- a/test/Conversion/HWArithToHW/test_basic.mlir +++ b/test/Conversion/HWArithToHW/test_basic.mlir @@ -243,13 +243,8 @@ hw.module @icmp(in %op0: i32, in %op1: i32, out sisi: i1, out siui: i1, out uisi // CHECK: %[[UIUI_OUT:.*]] = comb.icmp ult %op0, %op1 : i32 %uiui = hwarith.icmp lt %op0Unsigned, %op1Unsigned : ui32, ui32 - %sisiOut = hwarith.cast %sisi : (ui1) -> i1 - %siuiOut = hwarith.cast %siui : (ui1) -> i1 - %uisiOut = hwarith.cast %uisi : (ui1) -> i1 - %uiuiOut = hwarith.cast %uiui : (ui1) -> i1 - // CHECK: hw.output %[[SISI_OUT]], %[[SIUI_OUT]], %[[UISI_OUT]], %[[UIUI_OUT]] : i1, i1, i1, i1 - hw.output %sisiOut, %siuiOut, %uisiOut, %uiuiOut : i1, i1, i1, i1 + hw.output %sisi, %siui, %uisi, %uiui: i1, i1, i1, i1 } // ----- @@ -285,13 +280,8 @@ hw.module @icmp_mixed_width(in %op0: i5, in %op1: i7, out sisi: i1, out siui: i1 // CHECK: %[[UIUI_OUT:.*]] = comb.icmp ult %[[OP0_PADDED]], %op1 : i7 %uiui = hwarith.icmp lt %op0Unsigned, %op1Unsigned : ui5, ui7 - %sisiOut = hwarith.cast %sisi : (ui1) -> i1 - %siuiOut = hwarith.cast %siui : (ui1) -> i1 - %uisiOut = hwarith.cast %uisi : (ui1) -> i1 - %uiuiOut = hwarith.cast %uiui : (ui1) -> i1 - // CHECK: hw.output %[[SISI_OUT]], %[[SIUI_OUT]], %[[UISI_OUT]], %[[UIUI_OUT]] : i1, i1, i1, i1 - hw.output %sisiOut, %siuiOut, %uisiOut, %uiuiOut : i1, i1, i1, i1 + hw.output %sisi, %siui, %uisi, %uiui: i1, i1, i1, i1 } // ----- diff --git a/test/Dialect/HWArith/basic.mlir b/test/Dialect/HWArith/basic.mlir index 3a8b89eb61dd..9285c61de1aa 100644 --- a/test/Dialect/HWArith/basic.mlir +++ b/test/Dialect/HWArith/basic.mlir @@ -33,6 +33,4 @@ hw.module @test1() { %12 = hwarith.cast %11 : (si10) -> i9 // CHECK: %13 = hwarith.icmp eq %5, %10 : ui2, si9 %13 = hwarith.icmp eq %5, %10 : ui2, si9 - // CHECK: %14 = hwarith.cast %13 : (ui1) -> i1 - %14 = hwarith.cast %13 : (ui1) -> i1 } From 450c968235d3f94b6e420335979c5fa166c877cd Mon Sep 17 00:00:00 2001 From: Fabian Schuiki Date: Fri, 9 Aug 2024 14:18:23 -0700 Subject: [PATCH 056/119] [Moore] Add more AssignedVariableOp canonicalizations (#7477) Change the `AssignedVariableOp` to directly return type `T` instead of the `ref`. This removes the implied allocation and assignment, and makes this op behave essentially like `hw.wire`. The canonicalizers for `VariableOp` and `NetOp` can now replace all reads from the old variable or net with the value of `AssignedVariableOp` directly, since there is no more `ref` type involved. This also allows us to fully eliminate unnamed variables and nets that have a unique continuous assignment. Also add canonicalizers that remove `AssignedVariableOp` if they shadow an input or output port of the same name, or if there are multiple such variables with the same name in a chain. At a later stage we may want to replace `AssignedVariableOp` entirely with `dbg.variable`. --- include/circt/Dialect/Moore/MooreOps.td | 15 +- lib/Dialect/Moore/MooreOps.cpp | 159 ++++++++++++++++++--- test/Dialect/Moore/canonicalizers.mlir | 180 +++++++++++++++++++++--- 3 files changed, 309 insertions(+), 45 deletions(-) diff --git a/include/circt/Dialect/Moore/MooreOps.td b/include/circt/Dialect/Moore/MooreOps.td index 0e903b311bb1..14751b58794b 100644 --- a/include/circt/Dialect/Moore/MooreOps.td +++ b/include/circt/Dialect/Moore/MooreOps.td @@ -266,19 +266,20 @@ def NetOp : MooreOp<"net", [ `` custom($name) $kind ($assignment^)? attr-dict `:` type($result) }]; + let hasCanonicalizeMethod = true; } -def AssignedVarOp : MooreOp<"assigned_variable", [ +def AssignedVariableOp : MooreOp<"assigned_variable", [ DeclareOpInterfaceMethods, - TypesMatchWith<"initial value and variable types match", - "result", "initial", "cast($_self).getNestedType()"> + SameOperandsAndResultType ]> { - let summary = "The copy of variable must have the initial value"; - let arguments = (ins OptionalAttr:$name, UnpackedType:$initial); - let results = (outs Res:$result); + let summary = "A variable with a unique continuously assigned value"; + let arguments = (ins OptionalAttr:$name, UnpackedType:$input); + let results = (outs UnpackedType:$result); let assemblyFormat = [{ - `` custom($name) $initial attr-dict `:` type($result) + `` custom($name) $input attr-dict `:` type($input) }]; + let hasCanonicalizeMethod = true; } def ReadOp : MooreOp<"read", [ diff --git a/lib/Dialect/Moore/MooreOps.cpp b/lib/Dialect/Moore/MooreOps.cpp index 6920c404cd93..20553e17d720 100644 --- a/lib/Dialect/Moore/MooreOps.cpp +++ b/lib/Dialect/Moore/MooreOps.cpp @@ -289,25 +289,49 @@ VariableOp::handlePromotionComplete(const MemorySlot &slot, Value defaultValue, } LogicalResult VariableOp::canonicalize(VariableOp op, - ::mlir::PatternRewriter &rewriter) { - Value initial; - for (auto *user : op->getUsers()) - if (isa(user) && - (user->getOperand(0) == op.getResult())) { - // Don't canonicalize the multiple continuous assignment to the same - // variable. - if (initial) + PatternRewriter &rewriter) { + // Check if the variable has one unique continuous assignment to it, all other + // uses are reads, and that all uses are in the same block as the variable + // itself. + auto *block = op->getBlock(); + ContinuousAssignOp uniqueAssignOp; + for (auto *user : op->getUsers()) { + // Ensure that all users of the variable are in the same block. + if (user->getBlock() != block) + return failure(); + + // Ensure there is at most one unique continuous assignment to the variable. + if (auto assignOp = dyn_cast(user)) { + if (uniqueAssignOp) return failure(); - initial = user->getOperand(1); + uniqueAssignOp = assignOp; + continue; } - if (initial) { - rewriter.replaceOpWithNewOp(op, op.getType(), - op.getNameAttr(), initial); - return success(); + // Ensure all other users are reads. + if (!isa(user)) + return failure(); } + if (!uniqueAssignOp) + return failure(); - return failure(); + // If the original variable had a name, create an `AssignedVariableOp` as a + // replacement. Otherwise substitute the assigned value directly. + Value assignedValue = uniqueAssignOp.getSrc(); + if (auto name = op.getNameAttr(); name && !name.empty()) + assignedValue = rewriter.create( + op.getLoc(), name, uniqueAssignOp.getSrc()); + + // Remove the assign op and replace all reads with the new assigned var op. + rewriter.eraseOp(uniqueAssignOp); + for (auto *user : llvm::make_early_inc_range(op->getUsers())) { + auto readOp = cast(user); + rewriter.replaceOp(readOp, assignedValue); + } + + // Remove the original variable. + rewriter.eraseOp(op); + return success(); } SmallVector VariableOp::getDestructurableSlots() { @@ -371,15 +395,118 @@ void NetOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) { setNameFn(getResult(), *getName()); } +LogicalResult NetOp::canonicalize(NetOp op, PatternRewriter &rewriter) { + bool modified = false; + + // Check if the net has one unique continuous assignment to it, and + // additionally if all other users are reads. + auto *block = op->getBlock(); + ContinuousAssignOp uniqueAssignOp; + bool allUsesAreReads = true; + for (auto *user : op->getUsers()) { + // Ensure that all users of the net are in the same block. + if (user->getBlock() != block) + return failure(); + + // Ensure there is at most one unique continuous assignment to the net. + if (auto assignOp = dyn_cast(user)) { + if (uniqueAssignOp) + return failure(); + uniqueAssignOp = assignOp; + continue; + } + + // Ensure all other users are reads. + if (!isa(user)) + allUsesAreReads = false; + } + + // If there was one unique assignment, and the `NetOp` does not yet have an + // assigned value set, fold the assignment into the net. + if (uniqueAssignOp && !op.getAssignment()) { + rewriter.modifyOpInPlace( + op, [&] { op.getAssignmentMutable().assign(uniqueAssignOp.getSrc()); }); + rewriter.eraseOp(uniqueAssignOp); + modified = true; + uniqueAssignOp = {}; + } + + // If all users of the net op are reads, and any potential unique assignment + // has been folded into the net op itself, directly replace the reads with the + // net's assigned value. + if (!uniqueAssignOp && allUsesAreReads && op.getAssignment()) { + // If the original net had a name, create an `AssignedVariableOp` as a + // replacement. Otherwise substitute the assigned value directly. + auto assignedValue = op.getAssignment(); + if (auto name = op.getNameAttr(); name && !name.empty()) + assignedValue = + rewriter.create(op.getLoc(), name, assignedValue); + + // Replace all reads with the new assigned var op and remove the original + // net op. + for (auto *user : llvm::make_early_inc_range(op->getUsers())) { + auto readOp = cast(user); + rewriter.replaceOp(readOp, assignedValue); + } + rewriter.eraseOp(op); + modified = true; + } + + return success(modified); +} + //===----------------------------------------------------------------------===// -// AssignedVarOp +// AssignedVariableOp //===----------------------------------------------------------------------===// -void AssignedVarOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) { +void AssignedVariableOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) { if (getName() && !getName()->empty()) setNameFn(getResult(), *getName()); } +LogicalResult AssignedVariableOp::canonicalize(AssignedVariableOp op, + PatternRewriter &rewriter) { + // Eliminate chained variables with the same name. + // var(name, var(name, x)) -> var(name, x) + if (auto otherOp = op.getInput().getDefiningOp()) { + if (otherOp.getNameAttr() == op.getNameAttr()) { + rewriter.replaceOp(op, otherOp); + return success(); + } + } + + // Eliminate variables that alias an input port of the same name. + if (auto blockArg = dyn_cast(op.getInput())) { + if (auto moduleOp = + dyn_cast(blockArg.getOwner()->getParentOp())) { + auto moduleType = moduleOp.getModuleType(); + auto portName = moduleType.getInputNameAttr(blockArg.getArgNumber()); + if (portName == op.getNameAttr()) { + rewriter.replaceOp(op, blockArg); + return success(); + } + } + } + + // Eliminate variables that feed an output port of the same name. + for (auto &use : op->getUses()) { + auto outputOp = dyn_cast(use.getOwner()); + if (!outputOp) + continue; + auto moduleOp = dyn_cast(outputOp.getParentOp()); + if (!moduleOp) + break; + auto moduleType = moduleOp.getModuleType(); + auto portName = moduleType.getOutputNameAttr(use.getOperandNumber()); + if (portName == op.getNameAttr()) { + rewriter.replaceOp(op, op.getInput()); + return success(); + } + } + + return failure(); +} + //===----------------------------------------------------------------------===// // ConstantOp //===----------------------------------------------------------------------===// diff --git a/test/Dialect/Moore/canonicalizers.mlir b/test/Dialect/Moore/canonicalizers.mlir index 872fa7766d00..0104799c0946 100644 --- a/test/Dialect/Moore/canonicalizers.mlir +++ b/test/Dialect/Moore/canonicalizers.mlir @@ -10,32 +10,168 @@ func.func @Casts(%arg0: !moore.i1) -> (!moore.i1, !moore.i1) { return %0, %1 : !moore.i1, !moore.i1 } -// CHECK-LABEL: moore.module @SingleAssign -moore.module @SingleAssign() { +// CHECK-LABEL: moore.module @OptimizeUniquelyAssignedVars +moore.module @OptimizeUniquelyAssignedVars(in %u: !moore.i42, in %v: !moore.i42, in %w: !moore.i42) { + // Unique continuous assignments to variables should remove the `ref` + // indirection and instead directly propagate the assigned value to readers. + // CHECK-NOT: moore.assign // CHECK-NOT: moore.variable - // CHECK: %a = moore.assigned_variable %0 : - %a = moore.variable : - // CHECK: %0 = moore.constant 32 : i32 - %0 = moore.constant 32 : i32 - // CHECK: moore.assign %a, %0 : i32 - moore.assign %a, %0 : i32 - moore.output + // CHECK: %a = moore.assigned_variable %u : i42 + // CHECK: dbg.variable "a", %a : !moore.i42 + moore.assign %a, %u : i42 + %a = moore.variable : + %3 = moore.read %a : + dbg.variable "a", %3 : !moore.i42 + + // Continuous assignments to variables should override the initial value. + // CHECK-NOT: moore.assign + // CHECK-NOT: moore.constant 9001 + // CHECK-NOT: moore.variable + // CHECK: %b = moore.assigned_variable %v : i42 + // CHECK: dbg.variable "b", %b : !moore.i42 + moore.assign %b, %v : i42 + %0 = moore.constant 9001 : i42 + %b = moore.variable %0 : + %4 = moore.read %b : + dbg.variable "b", %4 : !moore.i42 + + // Unique continuous assignments to nets should remove the `ref` + // indirection and instead directly propagate the assigned value to readers. + // CHECK-NOT: moore.assign + // CHECK-NOT: moore.net wire + // CHECK: %c = moore.assigned_variable %w : i42 + // CHECK: dbg.variable "c", %c : !moore.i42 + moore.assign %c, %w : i42 + %c = moore.net wire : + %5 = moore.read %c : + dbg.variable "c", %5 : !moore.i42 + + // Variables without names should not create an `assigned_variable`. + // CHECK-NOT: moore.assign + // CHECK-NOT: moore.variable + // CHECK-NOT: moore.assigned_variable + // CHECK: dbg.variable "d", %u : !moore.i42 + moore.assign %1, %u : i42 + %1 = moore.variable : + %6 = moore.read %1 : + dbg.variable "d", %6 : !moore.i42 + + // Nets without names should not create an `assigned_variable`. + // CHECK-NOT: moore.assign + // CHECK-NOT: moore.net wire + // CHECK-NOT: moore.assigned_variable + // CHECK: dbg.variable "e", %v : !moore.i42 + moore.assign %2, %v : i42 + %2 = moore.net wire : + %7 = moore.read %2 : + dbg.variable "e", %7 : !moore.i42 +} + +// CHECK-LABEL: moore.module @DontOptimizeVarsWithMultipleAssigns +moore.module @DontOptimizeVarsWithMultipleAssigns() { + %0 = moore.constant 1337 : i42 + %1 = moore.constant 9001 : i42 + + // CHECK: %a = moore.variable + // CHECK: moore.assign %a + // CHECK: moore.assign %a + // CHECK: [[TMP:%.+]] = moore.read %a + // CHECK: dbg.variable "a", [[TMP]] + %a = moore.variable : + moore.assign %a, %0 : i42 + moore.assign %a, %1 : i42 + %2 = moore.read %a : + dbg.variable "a", %2 : !moore.i42 + + // CHECK: %b = moore.net + // CHECK: moore.assign %b + // CHECK: moore.assign %b + // CHECK: [[TMP:%.+]] = moore.read %b + // CHECK: dbg.variable "b", [[TMP]] + %b = moore.net wire : + moore.assign %b, %0 : i42 + moore.assign %b, %1 : i42 + %3 = moore.read %b : + dbg.variable "b", %3 : !moore.i42 + + // CHECK: %c = moore.net + // CHECK: moore.assign %c + // CHECK: [[TMP:%.+]] = moore.read %c + // CHECK: dbg.variable "c", [[TMP]] + %c = moore.net wire %0 : + moore.assign %c, %1 : i42 + %4 = moore.read %c : + dbg.variable "c", %4 : !moore.i42 +} + +// CHECK-LABEL: moore.module @DontOptimizeVarsWithNonReadUses +moore.module @DontOptimizeVarsWithNonReadUses(in %u: !moore.i42, in %v: !moore.i42) { + // CHECK: %a = moore.variable + // CHECK: moore.assign %a, %u + // CHECK: func.call @useRef(%a) + // CHECK: [[TMP:%.+]] = moore.read %a + // CHECK: dbg.variable "a", [[TMP]] + %a = moore.variable : + moore.assign %a, %u : i42 + func.call @useRef(%a) : (!moore.ref) -> () + %2 = moore.read %a : + dbg.variable "a", %2 : !moore.i42 + + // CHECK: %b = moore.net wire %u + // CHECK: moore.assign %b, %v + // CHECK: func.call @useRef(%b) + // CHECK: [[TMP:%.+]] = moore.read %b + // CHECK: dbg.variable "b", [[TMP]] + %b = moore.net wire %u : + moore.assign %b, %v : i42 + func.call @useRef(%b) : (!moore.ref) -> () + %3 = moore.read %b : + dbg.variable "b", %3 : !moore.i42 + + // Unique continuous assigns should be folded into net definitions even if the + // net has non-read uses. + // CHECK: %c = moore.net wire %u + // CHECK-NOT: moore.assign %c + // CHECK: func.call @useRef(%c) + %c = moore.net wire : + moore.assign %c, %u : i42 + func.call @useRef(%c) : (!moore.ref) -> () } -// CHECK-LABEL: moore.module @MultiAssign -moore.module @MultiAssign() { +func.func private @useRef(%arg0: !moore.ref) + +// CHECK-LABEL: moore.module @DropRedundantVars +moore.module @DropRedundantVars(in %a : !moore.i42, out b : !moore.i42, out c : !moore.i42) { + // Remove variables that shadow an input port of the same name. // CHECK-NOT: moore.assigned_variable - // CHECK: %a = moore.variable : - %a = moore.variable : - // CHECK: %0 = moore.constant 32 : i32 - %0 = moore.constant 32 : i32 - // CHECK: moore.assign %a, %0 : i32 - moore.assign %a, %0 : i32 - // CHECK: %1 = moore.constant 64 : i32 - %1 = moore.constant 64 : i32 - // CHECK: moore.assign %a, %1 : i32 - moore.assign %a, %1 : i32 - moore.output + // CHECK: dbg.variable "a", %a + %0 = moore.assigned_variable name "a" %a : i42 + dbg.variable "a", %0 : !moore.i42 + + // Variables that shadow an input port of a different name should remain. + // CHECK: %a2 = moore.assigned_variable + // CHECK: dbg.variable "a2", %a + %a2 = moore.assigned_variable %a : i42 + dbg.variable "a2", %a2 : !moore.i42 + + // Chained variables with the same name should be reduced to just one. + // CHECK: %v = moore.assigned_variable %a + // CHECK-NOT: moore.assigned_variable + // CHECK: dbg.variable "v", %v + %1 = moore.assigned_variable name "v" %a : i42 + %2 = moore.assigned_variable name "v" %1 : i42 + dbg.variable "v", %2 : !moore.i42 + + // Remove variables that shadow an output port of the same name. Variables + // that shadow an output port of a different name should remain. + // CHECK: [[TMP:%.+]] = moore.constant 9001 : i42 + // CHECK-NOT: %b = moore.assigned_variable + // CHECK: %w = moore.assigned_variable [[TMP]] + // CHECK: moore.output [[TMP]], %w + %3 = moore.constant 9001 : i42 + %b = moore.assigned_variable %3 : i42 + %w = moore.assigned_variable %3 : i42 + moore.output %b, %w : !moore.i42, !moore.i42 } // CHECK-LABEL: func.func @StructExtractFold1 From 7c32ed42e21b7295f512b7a4db0824d496e66873 Mon Sep 17 00:00:00 2001 From: Fabian Schuiki Date: Fri, 9 Aug 2024 14:23:44 -0700 Subject: [PATCH 057/119] [Moore] Add constant materialization, fold constant conversions (#7478) Implement the `materializeConstant` function for the Moore dialect and mark the `ConstantOp` as a `ConstantLike` operation. This now allows us to write constant folders for various operations. As a first example, add a constant folder for `ConversionOp` that directly applies domain conversions (e.g., `i42` to `l42` or vice versa) to constants. --- include/circt/Dialect/Moore/MooreDialect.td | 1 + include/circt/Dialect/Moore/MooreOps.td | 2 +- lib/Dialect/Moore/MooreDialect.cpp | 9 ++++++ lib/Dialect/Moore/MooreOps.cpp | 17 +++++++++++ test/Conversion/MooreToCore/basic.mlir | 5 ++-- test/Dialect/Moore/canonicalizers.mlir | 33 +++++++++++++++++---- 6 files changed, 57 insertions(+), 10 deletions(-) diff --git a/include/circt/Dialect/Moore/MooreDialect.td b/include/circt/Dialect/Moore/MooreDialect.td index 97c0fd0ac055..590e59da6068 100644 --- a/include/circt/Dialect/Moore/MooreDialect.td +++ b/include/circt/Dialect/Moore/MooreDialect.td @@ -38,6 +38,7 @@ def MooreDialect : Dialect { }]; let useDefaultAttributePrinterParser = 1; let useDefaultTypePrinterParser = 0; + let hasConstantMaterializer = 1; let dependentDialects = ["hw::HWDialect"]; } diff --git a/include/circt/Dialect/Moore/MooreOps.td b/include/circt/Dialect/Moore/MooreOps.td index 14751b58794b..b9f77bc0d1c2 100644 --- a/include/circt/Dialect/Moore/MooreOps.td +++ b/include/circt/Dialect/Moore/MooreOps.td @@ -417,7 +417,7 @@ def EventOp : MooreOp<"wait_event", [ // Expressions //===----------------------------------------------------------------------===// -def ConstantOp : MooreOp<"constant", [Pure]> { +def ConstantOp : MooreOp<"constant", [Pure, ConstantLike]> { let summary = "A constant integer value"; let arguments = (ins FVIntegerAttr:$value); let results = (outs IntType:$result); diff --git a/lib/Dialect/Moore/MooreDialect.cpp b/lib/Dialect/Moore/MooreDialect.cpp index a9cc2a9c8b4b..64341d36e39a 100644 --- a/lib/Dialect/Moore/MooreDialect.cpp +++ b/lib/Dialect/Moore/MooreDialect.cpp @@ -32,4 +32,13 @@ void MooreDialect::initialize() { >(); } +Operation *MooreDialect::materializeConstant(OpBuilder &builder, + Attribute value, Type type, + Location loc) { + if (auto intType = dyn_cast(type)) + if (auto intValue = dyn_cast(value)) + return builder.create(loc, intType, intValue); + return nullptr; +} + #include "circt/Dialect/Moore/MooreDialect.cpp.inc" diff --git a/lib/Dialect/Moore/MooreOps.cpp b/lib/Dialect/Moore/MooreOps.cpp index 20553e17d720..2572a804a61d 100644 --- a/lib/Dialect/Moore/MooreOps.cpp +++ b/lib/Dialect/Moore/MooreOps.cpp @@ -998,6 +998,23 @@ OpFoldResult ConversionOp::fold(FoldAdaptor adaptor) { // Fold away no-op casts. if (getInput().getType() == getResult().getType()) return getInput(); + + // Convert domains of constant integer inputs. + auto intInput = dyn_cast_or_null(adaptor.getInput()); + auto fromIntType = dyn_cast(getInput().getType()); + auto toIntType = dyn_cast(getResult().getType()); + if (intInput && fromIntType && toIntType && + fromIntType.getWidth() == toIntType.getWidth()) { + // If we are going *to* a four-valued type, simply pass through the + // constant. + if (toIntType.getDomain() == Domain::FourValued) + return intInput; + + // Otherwise map all unknown bits to zero (the default in SystemVerilog) and + // return a new constant. + return FVIntegerAttr::get(getContext(), intInput.getValue().toAPInt(false)); + } + return {}; } diff --git a/test/Conversion/MooreToCore/basic.mlir b/test/Conversion/MooreToCore/basic.mlir index 64c105f1d231..4ec8c84f9515 100644 --- a/test/Conversion/MooreToCore/basic.mlir +++ b/test/Conversion/MooreToCore/basic.mlir @@ -296,10 +296,9 @@ moore.module @Variable() { %b2 = moore.variable %0 : // CHECK: %true = hw.constant true - %1 = moore.constant 1 : i1 - %2 = moore.conversion %1 : !moore.i1 -> !moore.l1 + %1 = moore.constant 1 : l1 // CHECK: llhd.sig "l" %true : i1 - %l = moore.variable %2 : + %l = moore.variable %1 : // CHECK: [[TMP2:%.+]] = hw.constant 10 : i32 %3 = moore.constant 10 : i32 diff --git a/test/Dialect/Moore/canonicalizers.mlir b/test/Dialect/Moore/canonicalizers.mlir index 0104799c0946..7128f0039b72 100644 --- a/test/Dialect/Moore/canonicalizers.mlir +++ b/test/Dialect/Moore/canonicalizers.mlir @@ -142,6 +142,9 @@ func.func private @useRef(%arg0: !moore.ref) // CHECK-LABEL: moore.module @DropRedundantVars moore.module @DropRedundantVars(in %a : !moore.i42, out b : !moore.i42, out c : !moore.i42) { + // CHECK: [[C9001:%.+]] = moore.constant 9001 : i42 + %c9001_i42 = moore.constant 9001 : i42 + // Remove variables that shadow an input port of the same name. // CHECK-NOT: moore.assigned_variable // CHECK: dbg.variable "a", %a @@ -164,13 +167,11 @@ moore.module @DropRedundantVars(in %a : !moore.i42, out b : !moore.i42, out c : // Remove variables that shadow an output port of the same name. Variables // that shadow an output port of a different name should remain. - // CHECK: [[TMP:%.+]] = moore.constant 9001 : i42 // CHECK-NOT: %b = moore.assigned_variable - // CHECK: %w = moore.assigned_variable [[TMP]] - // CHECK: moore.output [[TMP]], %w - %3 = moore.constant 9001 : i42 - %b = moore.assigned_variable %3 : i42 - %w = moore.assigned_variable %3 : i42 + // CHECK: %w = moore.assigned_variable [[C9001]] + // CHECK: moore.output [[C9001]], %w + %b = moore.assigned_variable %c9001_i42 : i42 + %w = moore.assigned_variable %c9001_i42 : i42 moore.output %b, %w : !moore.i42, !moore.i42 } @@ -229,3 +230,23 @@ func.func @StructInjectFold3(%arg0: !moore.struct<{a: i32, b: i32}>) -> (!moore. %3 = moore.struct_inject %2, "a", %1 : struct<{a: i32, b: i32}>, i32 return %3 : !moore.struct<{a: i32, b: i32}> } + +// CHECK-LABEL: func.func @ConvertConstantTwoToFourValued +func.func @ConvertConstantTwoToFourValued() -> (!moore.l42) { + // CHECK: [[TMP:%.+]] = moore.constant 9001 : l42 + // CHECK-NOT: moore.conversion + // CHECK: return [[TMP]] : + %0 = moore.constant 9001 : i42 + %1 = moore.conversion %0 : !moore.i42 -> !moore.l42 + return %1 : !moore.l42 +} + +// CHECK-LABEL: func.func @ConvertConstantFourToTwoValued +func.func @ConvertConstantFourToTwoValued() -> (!moore.i42) { + // CHECK: [[TMP:%.+]] = moore.constant 8 : i42 + // CHECK-NOT: moore.conversion + // CHECK: return [[TMP]] : + %0 = moore.constant b1XZ0 : l42 + %1 = moore.conversion %0 : !moore.l42 -> !moore.i42 + return %1 : !moore.i42 +} From 9f1d9f98eea33ad20191900edaa55d2d41406e43 Mon Sep 17 00:00:00 2001 From: Megan Wachs Date: Fri, 9 Aug 2024 15:10:30 -0700 Subject: [PATCH 058/119] bump llvm to tip of main (#7440) Co-authored-by: Fabian Schuiki Co-authored-by: Martin Erhart Co-authored-by: Will Dietz --- lib/Conversion/MooreToCore/MooreToCore.cpp | 10 +++++++--- lib/Conversion/SeqToSV/FirMemLowering.cpp | 1 + lib/Conversion/SeqToSV/FirMemLowering.h | 1 + lib/Dialect/Arc/Transforms/Dedup.cpp | 1 + lib/Dialect/HW/Transforms/FlattenIO.cpp | 5 +---- lib/Dialect/Sim/SimOps.cpp | 1 + llvm | 2 +- test/Conversion/MooreToCore/basic.mlir | 6 +++--- test/Conversion/VerifToSMT/verif-to-smt.mlir | 16 ++++++++-------- test/Dialect/HW/flatten-io.mlir | 6 +++--- tools/circt-verilog/circt-verilog.cpp | 1 + tools/om-linker/om-linker.cpp | 1 + 12 files changed, 29 insertions(+), 22 deletions(-) diff --git a/lib/Conversion/MooreToCore/MooreToCore.cpp b/lib/Conversion/MooreToCore/MooreToCore.cpp index af58a7e1ed01..962e558f1a31 100644 --- a/lib/Conversion/MooreToCore/MooreToCore.cpp +++ b/lib/Conversion/MooreToCore/MooreToCore.cpp @@ -848,7 +848,7 @@ static void populateLegality(ConversionTarget &target) { static void populateTypeConversion(TypeConverter &typeConverter) { typeConverter.addConversion([&](IntType type) { - return mlir::IntegerType::get(type.getContext(), type.getWidth()); + return IntegerType::get(type.getContext(), type.getWidth()); }); typeConverter.addConversion([&](ArrayType type) { @@ -868,11 +868,15 @@ static void populateTypeConversion(TypeConverter &typeConverter) { }); typeConverter.addConversion([&](RefType type) -> std::optional { - return hw::InOutType::get(typeConverter.convertType(type.getNestedType())); + auto innerType = typeConverter.convertType(type.getNestedType()); + if (innerType) + return hw::InOutType::get(innerType); + return {}; }); // Valid target types. - typeConverter.addConversion([](mlir::IntegerType type) { return type; }); + typeConverter.addConversion([](IntegerType type) { return type; }); + typeConverter.addTargetMaterialization( [&](mlir::OpBuilder &builder, mlir::Type resultType, mlir::ValueRange inputs, diff --git a/lib/Conversion/SeqToSV/FirMemLowering.cpp b/lib/Conversion/SeqToSV/FirMemLowering.cpp index 8ae7c0a9649e..e8c745fb6f26 100644 --- a/lib/Conversion/SeqToSV/FirMemLowering.cpp +++ b/lib/Conversion/SeqToSV/FirMemLowering.cpp @@ -8,6 +8,7 @@ #include "FirMemLowering.h" #include "mlir/IR/Threading.h" +#include "llvm/ADT/MapVector.h" #include "llvm/Support/Debug.h" using namespace circt; diff --git a/lib/Conversion/SeqToSV/FirMemLowering.h b/lib/Conversion/SeqToSV/FirMemLowering.h index 079535f613c4..edb9a80611ca 100644 --- a/lib/Conversion/SeqToSV/FirMemLowering.h +++ b/lib/Conversion/SeqToSV/FirMemLowering.h @@ -15,6 +15,7 @@ #include "circt/Support/LLVM.h" #include "circt/Support/Namespace.h" #include "circt/Support/SymCache.h" +#include "llvm/ADT/MapVector.h" namespace circt { diff --git a/lib/Dialect/Arc/Transforms/Dedup.cpp b/lib/Dialect/Arc/Transforms/Dedup.cpp index d75601e6ff54..53e904d38c4c 100644 --- a/lib/Dialect/Arc/Transforms/Dedup.cpp +++ b/lib/Dialect/Arc/Transforms/Dedup.cpp @@ -13,6 +13,7 @@ #include "llvm/ADT/SetVector.h" #include "llvm/Support/Debug.h" #include "llvm/Support/SHA256.h" +#include #define DEBUG_TYPE "arc-dedup" diff --git a/lib/Dialect/HW/Transforms/FlattenIO.cpp b/lib/Dialect/HW/Transforms/FlattenIO.cpp index d23ef099d195..63e827c46cad 100644 --- a/lib/Dialect/HW/Transforms/FlattenIO.cpp +++ b/lib/Dialect/HW/Transforms/FlattenIO.cpp @@ -171,7 +171,6 @@ class FlattenIOTypeConverter : public TypeConverter { results.push_back(type); else { for (auto field : structType.getElements()) - results.push_back(field.type); } return success(); @@ -185,9 +184,7 @@ class FlattenIOTypeConverter : public TypeConverter { addTargetMaterialization([](OpBuilder &builder, hw::TypeAliasType type, ValueRange inputs, Location loc) { - auto structType = getStructType(type); - assert(structType && "expected struct type"); - auto result = builder.create(loc, structType, inputs); + auto result = builder.create(loc, type, inputs); return result.getResult(); }); } diff --git a/lib/Dialect/Sim/SimOps.cpp b/lib/Dialect/Sim/SimOps.cpp index d0e304fa85be..7ae834a12931 100644 --- a/lib/Dialect/Sim/SimOps.cpp +++ b/lib/Dialect/Sim/SimOps.cpp @@ -16,6 +16,7 @@ #include "mlir/Dialect/Func/IR/FuncOps.h" #include "mlir/IR/PatternMatch.h" #include "mlir/Interfaces/FunctionImplementation.h" +#include "llvm/ADT/MapVector.h" using namespace mlir; using namespace circt; diff --git a/llvm b/llvm index 0870afaaaccd..5689cccead7b 160000 --- a/llvm +++ b/llvm @@ -1 +1 @@ -Subproject commit 0870afaaaccde5b4bae37abfc982207ffafb8332 +Subproject commit 5689cccead7b70d8eeae4c641e8078e6d3c50b9a diff --git a/test/Conversion/MooreToCore/basic.mlir b/test/Conversion/MooreToCore/basic.mlir index 4ec8c84f9515..9ef58523606c 100644 --- a/test/Conversion/MooreToCore/basic.mlir +++ b/test/Conversion/MooreToCore/basic.mlir @@ -241,13 +241,13 @@ moore.module private @Null() { // CHECK-SAME: %[[V0:.*]] : i1, in // CHECK-SAME: %[[V1:.*]] : i1, out out0 : i1) { moore.module @Top(in %arg0 : !moore.l1, in %arg1 : !moore.l1, out out0 : !moore.l1) { -// CHECK-NEXT: %[[V2:.*]] = hw.instance "inst_0" @SubModule_0(a: %[[V0]]: i1, b: %[[V1]]: i1) -> (c: i1) + // CHECK-NEXT: %[[V2:.*]] = hw.instance "inst_0" @SubModule_0(a: %[[V0]]: i1, b: %[[V1]]: i1) -> (c: i1) %inst_0.c = moore.instance "inst_0" @SubModule_0(a: %arg0 : !moore.l1, b: %arg1 : !moore.l1) -> (c: !moore.l1) -// CHECK-NEXT: %[[V3:.*]] = hw.instance "inst_1" @SubModule_0(a: %[[V2]]: i1, b: %[[V1]]: i1) -> (c: i1) + // CHECK-NEXT: %[[V3:.*]] = hw.instance "inst_1" @SubModule_0(a: %[[V2]]: i1, b: %[[V1]]: i1) -> (c: i1) %inst_1.c = moore.instance "inst_1" @SubModule_0(a: %inst_0.c : !moore.l1, b: %arg1 : !moore.l1) -> (c: !moore.l1) -// CHECK-NEXT: hw.output %[[V3]] : i1 + // CHECK-NEXT: hw.output %[[V3]] : i1 moore.output %inst_1.c : !moore.l1 } diff --git a/test/Conversion/VerifToSMT/verif-to-smt.mlir b/test/Conversion/VerifToSMT/verif-to-smt.mlir index b73dd35520f2..92b828b23c9e 100644 --- a/test/Conversion/VerifToSMT/verif-to-smt.mlir +++ b/test/Conversion/VerifToSMT/verif-to-smt.mlir @@ -11,15 +11,15 @@ func.func @test(%arg0: !smt.bv<1>) -> (i1, i1, i1) { // CHECK: [[EQ:%.+]] = smt.solver() : () -> i1 // CHECK: [[IN0:%.+]] = smt.declare_fun : !smt.bv<32> - // CHECK: [[V0:%.+]] = builtin.unrealized_conversion_cast [[IN0]] : !smt.bv<32> to i32 // CHECK: [[IN1:%.+]] = smt.declare_fun : !smt.bv<32> - // CHECK: [[V1:%.+]] = builtin.unrealized_conversion_cast [[IN1]] : !smt.bv<32> to i32 - // CHECK: [[V2:%.+]]:2 = "some_op"([[V0]], [[V1]]) : (i32, i32) -> (i32, i32) - // CHECK: [[V3:%.+]] = builtin.unrealized_conversion_cast [[V2]]#0 : i32 to !smt.bv<32> - // CHECK: [[V4:%.+]] = smt.distinct [[IN0]], [[V3]] : !smt.bv<32> - // CHECK: [[V5:%.+]] = builtin.unrealized_conversion_cast [[V2]]#1 : i32 to !smt.bv<32> - // CHECK: [[V6:%.+]] = smt.distinct [[IN1]], [[V5]] : !smt.bv<32> - // CHECK: [[V7:%.+]] = smt.or [[V4]], [[V6]] + // CHECK-DAG: [[V0:%.+]] = builtin.unrealized_conversion_cast [[IN0]] : !smt.bv<32> to i32 + // CHECK-DAG: [[V1:%.+]] = builtin.unrealized_conversion_cast [[IN1]] : !smt.bv<32> to i32 + // CHECK-DAG: [[V2:%.+]]:2 = "some_op"([[V0]], [[V1]]) : (i32, i32) -> (i32, i32) + // CHECK-DAG: [[V3:%.+]] = builtin.unrealized_conversion_cast [[V2]]#0 : i32 to !smt.bv<32> + // CHECK-DAG: [[V4:%.+]] = smt.distinct [[IN0]], [[V3]] : !smt.bv<32> + // CHECK-DAG: [[V5:%.+]] = builtin.unrealized_conversion_cast [[V2]]#1 : i32 to !smt.bv<32> + // CHECK-DAG: [[V6:%.+]] = smt.distinct [[IN1]], [[V5]] : !smt.bv<32> + // CHECK-DAG: [[V7:%.+]] = smt.or [[V4]], [[V6]] // CHECK: smt.assert [[V7]] // CHECK: [[FALSE:%.+]] = arith.constant false // CHECK: [[TRUE:%.+]] = arith.constant true diff --git a/test/Dialect/HW/flatten-io.mlir b/test/Dialect/HW/flatten-io.mlir index 7273c4d6356e..b76e79fb5f6d 100644 --- a/test/Dialect/HW/flatten-io.mlir +++ b/test/Dialect/HW/flatten-io.mlir @@ -37,11 +37,11 @@ hw.module @level2(in %in : !Struct2, out out: !Struct2) { hw.type_scope @foo { hw.typedecl @bar : !Struct1 } -!ScopedStruct = !hw.typealias<@foo::@bar,!Struct1> +!ScopedStruct = !hw.typealias<@foo::@bar, !Struct1> // BASIC-LABEL: hw.module @scoped(in %arg0 : i32, in %in.a : i1, in %in.b : i2, in %arg1 : i32, out out0 : i32, out out.a : i1, out out.b : i2, out out1 : i32) { -// BASIC-NEXT: %0 = hw.struct_create (%in.a, %in.b) : !hw.struct -// BASIC-NEXT: %a, %b = hw.struct_explode %0 : !hw.struct +// BASIC-NEXT: %0 = hw.struct_create (%in.a, %in.b) : !hw.typealias<@foo::@bar, !hw.struct> +// BASIC-NEXT: %a, %b = hw.struct_explode %0 : !hw.typealias<@foo::@bar, !hw.struct> // BASIC-NEXT: hw.output %arg0, %a, %b, %arg1 : i32, i1, i2, i32 // BASIC-NEXT: } hw.module @scoped(in %arg0 : i32, in %in : !ScopedStruct, in %arg1: i32, out out0 : i32, out out: !ScopedStruct, out out1: i32) { diff --git a/tools/circt-verilog/circt-verilog.cpp b/tools/circt-verilog/circt-verilog.cpp index 33d7353a91a6..46e04a6fdaae 100644 --- a/tools/circt-verilog/circt-verilog.cpp +++ b/tools/circt-verilog/circt-verilog.cpp @@ -17,6 +17,7 @@ #include "circt/Dialect/Moore/MoorePasses.h" #include "circt/Support/Passes.h" #include "circt/Support/Version.h" +#include "mlir/IR/AsmState.h" #include "mlir/IR/BuiltinOps.h" #include "mlir/Pass/PassManager.h" #include "mlir/Support/FileUtilities.h" diff --git a/tools/om-linker/om-linker.cpp b/tools/om-linker/om-linker.cpp index e5a4da154198..bd1e8fea3577 100644 --- a/tools/om-linker/om-linker.cpp +++ b/tools/om-linker/om-linker.cpp @@ -20,6 +20,7 @@ #include "circt/Dialect/SV/SVDialect.h" #include "circt/Dialect/Verif/VerifDialect.h" #include "circt/Support/Version.h" +#include "mlir/Bytecode/BytecodeWriter.h" #include "mlir/IR/BuiltinOps.h" #include "mlir/IR/Threading.h" #include "mlir/Parser/Parser.h" From 73c053de32941656332d500e957cf6bef89df91f Mon Sep 17 00:00:00 2001 From: Schuyler Eldridge Date: Wed, 7 Aug 2024 15:24:43 -0400 Subject: [PATCH 059/119] [FIRRTL] Don't capture non-passives in LowerLayers Fix a bug where the LowerLayers pass could create output ports on a module lowered from a bind convention layer. Avoid this _almost_ entirely by moving all subfield, subindex, and subaccess operations (sub-* ops) out of layerblocks before modules are created (when this is possible). LowerLayers works by converting "captured" values into ports. However, the sub-* ops may capture non-passive types allowably, but then never drive them. E.g., consider the following: %0 = firrtl.wire : !firrtl.bundle, b flip: uint<1>> firrtl.layerblock @A { %1 = firrtl.subfield %0[a] : !firrtl.bundle, b flip: uint<1>> %2 = firrtl.node %1 : !firrtl.uint<1> } Naively, this "captures" the non-passive %0. However, this is really only capturing the _passive portion_ of %0 through a subfield. Without this commit, LowerLayers will try to create a port with the same type as %0 when it should be creating a port with the same type of %1. In order to determine what the port is, LowerLayers needs to know what is actually captured and not blindly assume that anything captured needs to be a port. Because this analysis may be tricky, instead solve this by moving the sub-* ops outside the layerblock before computing captures. The captures can then _continue_ to be naively computed by seeing if a value is defined outside the layerblock. This approach always works for subfield and subindex. However, this approach does not work if the subaccess index is defined inside the layerblock. If this happens, error. This can be revisited later with a complete solution. Signed-off-by: Schuyler Eldridge --- lib/Dialect/FIRRTL/Transforms/LowerLayers.cpp | 31 +++++++++++++++++++ test/Dialect/FIRRTL/lower-layers-errors.mlir | 16 ++++++++++ test/Dialect/FIRRTL/lower-layers.mlir | 23 ++++++++++++++ 3 files changed, 70 insertions(+) create mode 100644 test/Dialect/FIRRTL/lower-layers-errors.mlir diff --git a/lib/Dialect/FIRRTL/Transforms/LowerLayers.cpp b/lib/Dialect/FIRRTL/Transforms/LowerLayers.cpp index 321be10f8a1a..20422b4001b6 100644 --- a/lib/Dialect/FIRRTL/Transforms/LowerLayers.cpp +++ b/lib/Dialect/FIRRTL/Transforms/LowerLayers.cpp @@ -497,6 +497,37 @@ LogicalResult LowerLayersPass::runOnModuleBody(FModuleOp moduleOp, continue; } + // If this operation has non-passive operands defined outside the + // layerblock, then it may result in a lowering that creates output ports + // on the module. This is illegal in the FIRRTL spec since no layerblock + // may write to values outside it. Try to fix this up if possible. + if (llvm::any_of(op.getOperands(), [&](Value a) { + auto type = type_dyn_cast(a.getType()); + return type && !type.isPassive(); + })) { + // If this is a subfield, subindex, or subaccess op, then try to fix the + // problem by moving the value outside the layerblock. This always + // works for subfield and subindex, but may fail for subaccess if the + // index is defined inside the layerblock. + if (isa(op)) { + if (llvm::none_of(op.getOperands(), [&](Value a) { + return isAncestorOfValueOwner(layerBlock, a); + })) { + op.moveBefore(layerBlock); + continue; + } + auto diag = + op.emitOpError() + << "has a non-passive operand and captures a value defined " + "outside its enclosing bind-convention layerblock. The " + "'LowerLayers' pass cannot lower this as it would create an " + "output port on the resulting module."; + diag.attachNote(layerBlock.getLoc()) + << "the layerblock is defined here"; + return WalkResult::interrupt(); + } + } + if (auto refSend = dyn_cast(op)) { auto src = refSend.getBase(); if (!isAncestorOfValueOwner(layerBlock, src)) diff --git a/test/Dialect/FIRRTL/lower-layers-errors.mlir b/test/Dialect/FIRRTL/lower-layers-errors.mlir new file mode 100644 index 000000000000..5d8e45986a26 --- /dev/null +++ b/test/Dialect/FIRRTL/lower-layers-errors.mlir @@ -0,0 +1,16 @@ +// RUN: circt-opt -firrtl-lower-layers -split-input-file -verify-diagnostics %s + +firrtl.circuit "NonPassiveSubaccess" { + firrtl.layer @A bind {} + firrtl.module @NonPassiveSubaccess( + in %a: !firrtl.vector, b flip: uint<1>>, 2>, + in %b: !firrtl.uint<1> + ) { + // expected-note @below {{the layerblock is defined here}} + firrtl.layerblock @A { + %n = firrtl.node %b : !firrtl.uint<1> + // expected-error @below {{'firrtl.subaccess' op has a non-passive operand and captures a value defined outside its enclosing bind-convention layerblock}} + %0 = firrtl.subaccess %a[%b] : !firrtl.vector, b flip: uint<1>>, 2>, !firrtl.uint<1> + } + } +} diff --git a/test/Dialect/FIRRTL/lower-layers.mlir b/test/Dialect/FIRRTL/lower-layers.mlir index 2104b976d5d7..8ff13ed3a649 100644 --- a/test/Dialect/FIRRTL/lower-layers.mlir +++ b/test/Dialect/FIRRTL/lower-layers.mlir @@ -201,6 +201,29 @@ firrtl.circuit "Test" { } } + // Test that subfield, subindex, and subaccess are moved out of layerblocks to + // avoid capturing non-passive types. + // + // CHECK: firrtl.module private @[[SubOpsInLayerBlock_A:[A-Za-z0-9_]+]] + // CHECK-SAME: in %[[port:[A-Za-z0-9_]+]]: !firrtl.uint<1> + // CHECK-NEXT: firrtl.node %[[port]] + // CHECK-NEXT: } + // CHECK: firrtl.module @SubOpsInLayerBlock + // CHECK-NEXT: firrtl.subaccess + // CHECK-NEXT: firrtl.subindex + // CHECK-NEXT: firrtl.subfield + firrtl.module @SubOpsInLayerBlock( + in %a: !firrtl.vector, b flip: uint<2>>, 2>, 2>, + in %b: !firrtl.uint<1> + ) { + firrtl.layerblock @A { + %0 = firrtl.subaccess %a[%b] : !firrtl.vector, b flip: uint<2>>, 2>, 2>, !firrtl.uint<1> + %1 = firrtl.subindex %0[0] : !firrtl.vector, b flip: uint<2>>, 2> + %2 = firrtl.subfield %1[a] : !firrtl.bundle, b flip: uint<2>> + %3 = firrtl.node %2 : !firrtl.uint<1> + } + } + //===--------------------------------------------------------------------===// // Connecting/Defining Refs //===--------------------------------------------------------------------===// From d4e785764935516572f94ac4625316d1fdbe49a8 Mon Sep 17 00:00:00 2001 From: Schuyler Eldridge Date: Fri, 9 Aug 2024 18:35:14 -0400 Subject: [PATCH 060/119] fixup! [FIRRTL] Don't capture non-passives in LowerLayers --- lib/Dialect/FIRRTL/Transforms/LowerLayers.cpp | 65 +++++++++++-------- 1 file changed, 37 insertions(+), 28 deletions(-) diff --git a/lib/Dialect/FIRRTL/Transforms/LowerLayers.cpp b/lib/Dialect/FIRRTL/Transforms/LowerLayers.cpp index 20422b4001b6..ffa8d5dec734 100644 --- a/lib/Dialect/FIRRTL/Transforms/LowerLayers.cpp +++ b/lib/Dialect/FIRRTL/Transforms/LowerLayers.cpp @@ -497,37 +497,46 @@ LogicalResult LowerLayersPass::runOnModuleBody(FModuleOp moduleOp, continue; } - // If this operation has non-passive operands defined outside the - // layerblock, then it may result in a lowering that creates output ports - // on the module. This is illegal in the FIRRTL spec since no layerblock - // may write to values outside it. Try to fix this up if possible. - if (llvm::any_of(op.getOperands(), [&](Value a) { - auto type = type_dyn_cast(a.getType()); - return type && !type.isPassive(); - })) { - // If this is a subfield, subindex, or subaccess op, then try to fix the - // problem by moving the value outside the layerblock. This always - // works for subfield and subindex, but may fail for subaccess if the - // index is defined inside the layerblock. - if (isa(op)) { - if (llvm::none_of(op.getOperands(), [&](Value a) { - return isAncestorOfValueOwner(layerBlock, a); - })) { - op.moveBefore(layerBlock); - continue; - } - auto diag = - op.emitOpError() - << "has a non-passive operand and captures a value defined " - "outside its enclosing bind-convention layerblock. The " - "'LowerLayers' pass cannot lower this as it would create an " - "output port on the resulting module."; - diag.attachNote(layerBlock.getLoc()) - << "the layerblock is defined here"; - return WalkResult::interrupt(); + // Handle subfields, subindexes, and subaccesses which are indexing into + // non-passive values. If these are kept in the module, then the module + // must create bi-directional ports. This doesn't make sense as the + // FIRRTL spec states that no layerblock may write to values outside it. + // Fix this for subfield and subindex by moving these ops outside the + // layerblock. Try to fix this for subaccess and error if the move can't + // be made because the index is defined inside the layerblock. (This case + // is exceedingly rare given that subaccesses are almost always unexepcted + // when this pass runs.) + if (isa(op)) { + auto input = op.getOperand(0); + if (!firrtl::type_cast(input.getType()).isPassive() && + !isAncestorOfValueOwner(layerBlock, input)) + op.moveBefore(layerBlock); + continue; + } + + if (auto subOp = dyn_cast(op)) { + auto input = subOp.getInput(); + if (firrtl::type_cast(input.getType()).isPassive()) + continue; + + if (!isAncestorOfValueOwner(layerBlock, input) && + !isAncestorOfValueOwner(layerBlock, subOp.getIndex())) { + subOp->moveBefore(layerBlock); + continue; } + auto diag = op.emitOpError() + << "has a non-passive operand and captures a value defined " + "outside its enclosing bind-convention layerblock. The " + "'LowerLayers' pass cannot lower this as it would " + "create an output port on the resulting module."; + diag.attachNote(layerBlock.getLoc()) + << "the layerblock is defined here"; + return WalkResult::interrupt(); } + // Beyond this point, we are handling operations which capture values + // defined outside the layerblock. Whenever we see this, we need to + // create ports for the module that this layerblock will become. if (auto refSend = dyn_cast(op)) { auto src = refSend.getBase(); if (!isAncestorOfValueOwner(layerBlock, src)) From 09fc72563131e4f9ddecb0d16c1621517e427c7d Mon Sep 17 00:00:00 2001 From: Martin Erhart Date: Sat, 10 Aug 2024 00:05:26 +0100 Subject: [PATCH 061/119] [Moore] Power operator folders and canonicalizers (#7494) --- include/circt/Dialect/Moore/MooreOps.td | 3 ++ lib/Dialect/Moore/MooreOps.cpp | 68 +++++++++++++++++++++++++ test/Dialect/Moore/canonicalizers.mlir | 30 +++++++++++ 3 files changed, 101 insertions(+) diff --git a/include/circt/Dialect/Moore/MooreOps.td b/include/circt/Dialect/Moore/MooreOps.td index b9f77bc0d1c2..a80747cd1a62 100644 --- a/include/circt/Dialect/Moore/MooreOps.td +++ b/include/circt/Dialect/Moore/MooreOps.td @@ -686,6 +686,9 @@ class PowOpBase : BinaryOpBase { See IEEE 1800-2017 ยง 11.4.3 "Arithmetic operators". }]; + + let hasCanonicalizeMethod = 1; + let hasFolder = 1; } def PowUOp : PowOpBase<"powu">; diff --git a/lib/Dialect/Moore/MooreOps.cpp b/lib/Dialect/Moore/MooreOps.cpp index 2572a804a61d..76674ef3bd79 100644 --- a/lib/Dialect/Moore/MooreOps.cpp +++ b/lib/Dialect/Moore/MooreOps.cpp @@ -1101,6 +1101,74 @@ ReadOp::removeBlockingUses(const MemorySlot &slot, return DeletionKind::Delete; } +//===----------------------------------------------------------------------===// +// PowSOp +//===----------------------------------------------------------------------===// + +static OpFoldResult powCommonFolding(MLIRContext *ctxt, Attribute lhs, + Attribute rhs) { + auto lhsValue = dyn_cast_or_null(lhs); + if (lhsValue && lhsValue.getValue() == 1) + return lhs; + + auto rhsValue = dyn_cast_or_null(rhs); + if (rhsValue && rhsValue.getValue().isZero()) + return FVIntegerAttr::get(ctxt, + FVInt(rhsValue.getValue().getBitWidth(), 1)); + + return {}; +} + +OpFoldResult PowSOp::fold(FoldAdaptor adaptor) { + return powCommonFolding(getContext(), adaptor.getLhs(), adaptor.getRhs()); +} + +LogicalResult PowSOp::canonicalize(PowSOp op, PatternRewriter &rewriter) { + Location loc = op.getLoc(); + auto intType = cast(op.getRhs().getType()); + if (auto baseOp = op.getLhs().getDefiningOp()) { + if (baseOp.getValue() == 2) { + Value constOne = rewriter.create(loc, intType, 1); + Value constZero = rewriter.create(loc, intType, 0); + Value shift = rewriter.create(loc, constOne, op.getRhs()); + Value isNegative = rewriter.create(loc, op.getRhs(), constZero); + auto condOp = rewriter.replaceOpWithNewOp( + op, op.getLhs().getType(), isNegative); + Block *thenBlock = rewriter.createBlock(&condOp.getTrueRegion()); + rewriter.setInsertionPointToStart(thenBlock); + rewriter.create(loc, constZero); + Block *elseBlock = rewriter.createBlock(&condOp.getFalseRegion()); + rewriter.setInsertionPointToStart(elseBlock); + rewriter.create(loc, shift); + return success(); + } + } + + return failure(); +} + +//===----------------------------------------------------------------------===// +// PowUOp +//===----------------------------------------------------------------------===// + +OpFoldResult PowUOp::fold(FoldAdaptor adaptor) { + return powCommonFolding(getContext(), adaptor.getLhs(), adaptor.getRhs()); +} + +LogicalResult PowUOp::canonicalize(PowUOp op, PatternRewriter &rewriter) { + Location loc = op.getLoc(); + auto intType = cast(op.getRhs().getType()); + if (auto baseOp = op.getLhs().getDefiningOp()) { + if (baseOp.getValue() == 2) { + Value constOne = rewriter.create(loc, intType, 1); + rewriter.replaceOpWithNewOp(op, constOne, op.getRhs()); + return success(); + } + } + + return failure(); +} + //===----------------------------------------------------------------------===// // TableGen generated logic. //===----------------------------------------------------------------------===// diff --git a/test/Dialect/Moore/canonicalizers.mlir b/test/Dialect/Moore/canonicalizers.mlir index 7128f0039b72..c31831318613 100644 --- a/test/Dialect/Moore/canonicalizers.mlir +++ b/test/Dialect/Moore/canonicalizers.mlir @@ -250,3 +250,33 @@ func.func @ConvertConstantFourToTwoValued() -> (!moore.i42) { %1 = moore.conversion %0 : !moore.l42 -> !moore.i42 return %1 : !moore.i42 } + +// CHECK-LABEL: func @Pow +func.func @Pow(%arg0 : !moore.l32) -> (!moore.l32, !moore.l32, !moore.l32, !moore.l32, !moore.l32, !moore.l32) { + // CHECK-NEXT: [[V0:%.+]] = moore.constant 1 : l32 + // CHECK-NEXT: [[V1:%.+]] = moore.constant 0 : l32 + %0 = moore.constant 0 : l32 + %1 = moore.constant 1 : l32 + %2 = moore.constant 2 : l32 + + %3 = moore.pows %1, %arg0 : l32 + %4 = moore.pows %arg0, %0 : l32 + + %5 = moore.powu %1, %arg0 : l32 + %6 = moore.powu %arg0, %0 : l32 + + // CHECK-NEXT: [[V2:%.+]] = moore.shl [[V0]], %arg0 + // CHECK-NEXT: [[V3:%.+]] = moore.slt %arg0, [[V1]] + // CHECK-NEXT: [[V4:%.+]] = moore.conditional [[V3]] + // CHECK-NEXT: moore.yield [[V1]] + // CHECK-NEXT: } { + // CHECK-NEXT: moore.yield [[V2]] + // CHECK-NEXT: } + %7 = moore.pows %2, %arg0 : l32 + + // CHECK-NEXT: [[V5:%.+]] = moore.shl [[V0]], %arg0 + %8 = moore.powu %2, %arg0 : l32 + + // CHECK-NEXT: return [[V0]], [[V0]], [[V0]], [[V0]], [[V4]], [[V5]] : + return %3, %4, %5, %6, %7, %8 : !moore.l32, !moore.l32, !moore.l32, !moore.l32, !moore.l32, !moore.l32 +} From bfed5353fd21bc320529fc70854639ec78a23a40 Mon Sep 17 00:00:00 2001 From: Martin Erhart Date: Sat, 10 Aug 2024 00:07:48 +0100 Subject: [PATCH 062/119] [MooreToCore] Support StructExtractRefOp (#7497) --- lib/Conversion/MooreToCore/MooreToCore.cpp | 33 ++++++++++++++++++++-- test/Conversion/MooreToCore/basic.mlir | 6 +++- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/lib/Conversion/MooreToCore/MooreToCore.cpp b/lib/Conversion/MooreToCore/MooreToCore.cpp index 962e558f1a31..4277688d7222 100644 --- a/lib/Conversion/MooreToCore/MooreToCore.cpp +++ b/lib/Conversion/MooreToCore/MooreToCore.cpp @@ -74,10 +74,19 @@ static hw::ModulePortInfo getModulePortInfo(const TypeConverter &typeConverter, inputs.reserve(moduleTy.getNumInputs()); outputs.reserve(moduleTy.getNumOutputs()); - for (auto port : moduleTy.getPorts()) + for (auto port : moduleTy.getPorts()) { + Type portTy = typeConverter.convertType(port.type); + if (auto ioTy = dyn_cast_or_null(portTy)) { + inputs.push_back(hw::PortInfo( + {{port.name, ioTy.getElementType(), hw::ModulePort::InOut}, + inputNum++, + {}})); + continue; + } + if (port.dir == hw::ModulePort::Direction::Output) { outputs.push_back( - hw::PortInfo({{port.name, port.type, port.dir}, resultNum++, {}})); + hw::PortInfo({{port.name, portTy, port.dir}, resultNum++, {}})); } else { // FIXME: Once we support net<...>, ref<...> type to represent type of // special port like inout or ref port which is not a input or output @@ -85,8 +94,9 @@ static hw::ModulePortInfo getModulePortInfo(const TypeConverter &typeConverter, // port or do specified operation to it. Now inout and ref port is treated // as input port. inputs.push_back( - hw::PortInfo({{port.name, port.type, port.dir}, inputNum++, {}})); + hw::PortInfo({{port.name, portTy, port.dir}, inputNum++, {}})); } + } return hw::ModulePortInfo(inputs, outputs); } @@ -113,6 +123,9 @@ struct SVModuleOpConversion : public OpConversionPattern { SymbolTable::setSymbolVisibility(hwModuleOp, SymbolTable::getSymbolVisibility(op)); rewriter.eraseBlock(hwModuleOp.getBodyBlock()); + if (failed( + rewriter.convertRegionTypes(&op.getBodyRegion(), *typeConverter))) + return failure(); rewriter.inlineRegionBefore(op.getBodyRegion(), hwModuleOp.getBodyRegion(), hwModuleOp.getBodyRegion().end()); @@ -456,6 +469,19 @@ struct StructExtractOpConversion : public OpConversionPattern { } }; +struct StructExtractRefOpConversion + : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(StructExtractRefOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + rewriter.replaceOpWithNewOp( + op, adaptor.getInput(), adaptor.getFieldNameAttr()); + return success(); + } +}; + struct ReduceAndOpConversion : public OpConversionPattern { using OpConversionPattern::OpConversionPattern; LogicalResult @@ -908,6 +934,7 @@ static void populateOpConversion(RewritePatternSet &patterns, ConstantOpConv, ConcatOpConversion, ReplicateOpConversion, ExtractOpConversion, DynExtractOpConversion, ConversionOpConversion, ReadOpConversion, NamedConstantOpConv, StructExtractOpConversion, + StructExtractRefOpConversion, // Patterns of unary operations. ReduceAndOpConversion, ReduceOrOpConversion, ReduceXorOpConversion, diff --git a/test/Conversion/MooreToCore/basic.mlir b/test/Conversion/MooreToCore/basic.mlir index 9ef58523606c..38edef2e6df9 100644 --- a/test/Conversion/MooreToCore/basic.mlir +++ b/test/Conversion/MooreToCore/basic.mlir @@ -312,9 +312,13 @@ moore.module @Variable() { } // CHECK-LABEL: hw.module @Struct -moore.module @Struct(in %arg0 : !moore.struct<{exp_bits: i32, man_bits: i32}>, out a : !moore.i32, out b : !moore.struct<{exp_bits: i32, man_bits: i32}>, out c : !moore.struct<{exp_bits: i32, man_bits: i32}>) { +moore.module @Struct(in %arg0 : !moore.struct<{exp_bits: i32, man_bits: i32}>, in %arg1 : !moore.ref>, out a : !moore.i32, out b : !moore.struct<{exp_bits: i32, man_bits: i32}>, out c : !moore.struct<{exp_bits: i32, man_bits: i32}>) { // CHECK: hw.struct_extract %arg0["exp_bits"] : !hw.struct %0 = moore.struct_extract %arg0, "exp_bits" : !moore.struct<{exp_bits: i32, man_bits: i32}> -> i32 + + // CHECK: llhd.sig.struct_extract %arg1["exp_bits"] : !hw.inout> + %ref = moore.struct_extract_ref %arg1, "exp_bits" : > -> + moore.assign %ref, %0 : !moore.i32 // CHECK: [[C0:%.+]] = hw.constant 0 : i64 // CHECK: [[INIT:%.+]] = hw.bitcast [[C0]] : (i64) -> !hw.struct From b3a54e355706470ccc4f2e6e4c0cab21343aad9b Mon Sep 17 00:00:00 2001 From: Will Dietz Date: Fri, 9 Aug 2024 18:56:27 -0500 Subject: [PATCH 063/119] LLVM bump to include upstream verifier performance fix. (#7496) Bump LLVM [1] to include an upstream verifier performance fix [2]. This required two minor fixes to CIRCT: * [HW] Qualify types for safer use in other dialects. * [ImportVerilog] Fix ternary with diff types, both become Value. [1]: https://github.com/llvm/llvm-project/compare/5689cccead7b70d8eeae4c641e8078e6d3c50b9a...c69b8c445a6b7efd29e67b665adaf04575f3ed92 [2]: https://github.com/llvm/llvm-project/commit/7a98071da2d3e126bacd6c004d2f5d8ecaae7819 --- include/circt/Dialect/HW/HWTypes.td | 4 ++-- lib/Conversion/ImportVerilog/Expressions.cpp | 4 +++- llvm | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/include/circt/Dialect/HW/HWTypes.td b/include/circt/Dialect/HW/HWTypes.td index f2f8c1c8bceb..431ed6c34857 100644 --- a/include/circt/Dialect/HW/HWTypes.td +++ b/include/circt/Dialect/HW/HWTypes.td @@ -39,7 +39,7 @@ def HWNonInOutType : DialectType($_self)">, - "InOutType", "InOutType">; + "InOutType", "::circt::hw::InOutType">; class InOutTypeOf allowedTypes> : ContainerType, CPred<"::circt::hw::type_isa<::circt::hw::InOutType>($_self)">, @@ -62,7 +62,7 @@ def StructType : DialectType($_self)">, - "a UnionType", "::circt::hw::TypeAliasOr">; + "a UnionType", "::circt::hw::TypeAliasOr<::circt::hw::UnionType>">; // A handle to refer to circt::hw::EnumType in ODS. def EnumType : DialectType(loc, preValue, one).getResult() : builder.create(loc, preValue, one).getResult(); builder.create(loc, arg, postValue); - return isPost ? preValue : postValue; + if (isPost) + return preValue; + return postValue; } // Handle unary operators. diff --git a/llvm b/llvm index 5689cccead7b..c69b8c445a6b 160000 --- a/llvm +++ b/llvm @@ -1 +1 @@ -Subproject commit 5689cccead7b70d8eeae4c641e8078e6d3c50b9a +Subproject commit c69b8c445a6b7efd29e67b665adaf04575f3ed92 From 49c82be57a3cd3e360a05dadc1e75fd3399fb14b Mon Sep 17 00:00:00 2001 From: Fabian Schuiki Date: Fri, 9 Aug 2024 20:15:45 -0700 Subject: [PATCH 064/119] [Moore] Fix mem2reg implementation (#7498) Fix a few issues in the mem2reg interface implementations of VariableOp, ReadOp, and BlockingAssignOp. Add tests reduced from the Snitch core that used to fail before this fix. --- lib/Dialect/Moore/MooreOps.cpp | 87 ++++++++++++------- .../Moore/Transforms/SimplifyProcedures.cpp | 29 +++---- test/Dialect/Moore/canonicalizers.mlir | 11 +++ test/Dialect/Moore/mem2reg.mlir | 78 ++++++++++------- test/Dialect/Moore/simplify-procedures.mlir | 6 +- tools/circt-reduce/circt-reduce.cpp | 4 +- 6 files changed, 129 insertions(+), 86 deletions(-) diff --git a/lib/Dialect/Moore/MooreOps.cpp b/lib/Dialect/Moore/MooreOps.cpp index 76674ef3bd79..788751f7f381 100644 --- a/lib/Dialect/Moore/MooreOps.cpp +++ b/lib/Dialect/Moore/MooreOps.cpp @@ -259,37 +259,19 @@ void VariableOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) { setNameFn(getResult(), *getName()); } -llvm::SmallVector VariableOp::getPromotableSlots() { - if (isa(getOperation()->getParentOp())) - return {}; - if (getInitial()) - return {}; - return {MemorySlot{getResult(), getType()}}; -} - -Value VariableOp::getDefaultValue(const MemorySlot &slot, OpBuilder &builder) { - if (auto value = getInitial()) - return value; - return builder.create( - getLoc(), - cast(cast(slot.elemType).getNestedType()), 0); -} - -void VariableOp::handleBlockArgument(const MemorySlot &slot, - BlockArgument argument, - OpBuilder &builder) {} - -::std::optional<::mlir::PromotableAllocationOpInterface> -VariableOp::handlePromotionComplete(const MemorySlot &slot, Value defaultValue, - OpBuilder &builder) { - if (defaultValue.use_empty()) - defaultValue.getDefiningOp()->erase(); - this->erase(); - return std::nullopt; -} - LogicalResult VariableOp::canonicalize(VariableOp op, PatternRewriter &rewriter) { + // If the variable is embedded in an SSACFG region, move the initial value + // into an assignment immediately after the variable op. This allows the + // mem2reg pass which cannot handle variables with initial values. + auto initial = op.getInitial(); + if (initial && mlir::mayHaveSSADominance(*op->getParentRegion())) { + rewriter.modifyOpInPlace(op, [&] { op.getInitialMutable().clear(); }); + rewriter.setInsertionPointAfter(op); + rewriter.create(initial.getLoc(), op, initial); + return success(); + } + // Check if the variable has one unique continuous assignment to it, all other // uses are reads, and that all uses are in the same block as the variable // itself. @@ -334,6 +316,46 @@ LogicalResult VariableOp::canonicalize(VariableOp op, return success(); } +SmallVector VariableOp::getPromotableSlots() { + // We cannot promote variables with an initial value, since that value may not + // dominate the location where the default value needs to be constructed. + if (mlir::mayBeGraphRegion(*getOperation()->getParentRegion()) || + getInitial()) + return {}; + return {MemorySlot{getResult(), getType().getNestedType()}}; +} + +Value VariableOp::getDefaultValue(const MemorySlot &slot, OpBuilder &builder) { + auto packedType = dyn_cast(slot.elemType); + if (!packedType) + return {}; + auto bitWidth = packedType.getBitSize(); + if (!bitWidth) + return {}; + auto fvint = packedType.getDomain() == Domain::FourValued + ? FVInt::getAllX(*bitWidth) + : FVInt::getZero(*bitWidth); + Value value = builder.create( + getLoc(), IntType::get(getContext(), *bitWidth, packedType.getDomain()), + fvint); + if (value.getType() != packedType) + builder.create(getLoc(), packedType, value); + return value; +} + +void VariableOp::handleBlockArgument(const MemorySlot &slot, + BlockArgument argument, + OpBuilder &builder) {} + +std::optional +VariableOp::handlePromotionComplete(const MemorySlot &slot, Value defaultValue, + OpBuilder &builder) { + if (defaultValue && defaultValue.use_empty()) + defaultValue.getDefiningOp()->erase(); + this->erase(); + return {}; +} + SmallVector VariableOp::getDestructurableSlots() { if (isa(getOperation()->getParentOp())) return {}; @@ -1054,8 +1076,7 @@ bool BlockingAssignOp::canUsesBeRemoved( return false; Value blockingUse = (*blockingUses.begin())->get(); return blockingUse == slot.ptr && getDst() == slot.ptr && - getSrc() != slot.ptr && - getSrc().getType() == cast(slot.elemType).getNestedType(); + getSrc() != slot.ptr && getSrc().getType() == slot.elemType; } DeletionKind BlockingAssignOp::removeBlockingUses( @@ -1070,7 +1091,7 @@ DeletionKind BlockingAssignOp::removeBlockingUses( //===----------------------------------------------------------------------===// bool ReadOp::loadsFrom(const MemorySlot &slot) { - return getOperand() == slot.ptr; + return getInput() == slot.ptr; } bool ReadOp::storesTo(const MemorySlot &slot) { return false; } @@ -1089,7 +1110,7 @@ bool ReadOp::canUsesBeRemoved(const MemorySlot &slot, return false; Value blockingUse = (*blockingUses.begin())->get(); return blockingUse == slot.ptr && getOperand() == slot.ptr && - getResult().getType() == cast(slot.elemType).getNestedType(); + getResult().getType() == slot.elemType; } DeletionKind diff --git a/lib/Dialect/Moore/Transforms/SimplifyProcedures.cpp b/lib/Dialect/Moore/Transforms/SimplifyProcedures.cpp index c4ee317eab38..c80f3e731a39 100644 --- a/lib/Dialect/Moore/Transforms/SimplifyProcedures.cpp +++ b/lib/Dialect/Moore/Transforms/SimplifyProcedures.cpp @@ -43,8 +43,8 @@ void SimplifyProceduresPass::runOnOperation() { // Use to collect blocking assignments that have been replaced by a "shadow" // variable. - DenseSet assignOps; procedureOp.walk([&](Operation *op) { + SmallVector> assignOps; auto &nestedOp = *op; // Only create a "shadow" varaible for the global variable used by other // operations in the procedure body. @@ -67,15 +67,16 @@ void SimplifyProceduresPass::runOnOperation() { nestedOp.getLoc(), cast(resultType).getNestedType(), varOp.getResult()); auto newVarOp = builder.create( - nestedOp.getLoc(), resultType, StringAttr{}, readOp); + nestedOp.getLoc(), resultType, StringAttr{}, Value{}); + builder.create(nestedOp.getLoc(), newVarOp, readOp); builder.clearInsertionPoint(); // Replace the users of the global variable with a corresponding // "shadow" variable. for (auto *user : users) { user->replaceUsesOfWith(user->getOperand(0), newVarOp); - if (isa(user)) - assignOps.insert(user); + if (auto assignOp = dyn_cast(user)) + assignOps.push_back({assignOp, newVarOp, varOp}); } } } @@ -83,20 +84,12 @@ void SimplifyProceduresPass::runOnOperation() { // Ensure the global variable has the correct value. So needing to create // a blocking assign for the global variable when the "shadow" variable // has a new value. - for (auto *assignOp : assignOps) - if (auto localVarOp = llvm::dyn_cast_or_null( - assignOp->getOperand(0).getDefiningOp())) { - auto resultType = localVarOp.getResult().getType(); - builder.setInsertionPointAfter(assignOp); - auto readOp = builder.create( - localVarOp.getLoc(), cast(resultType).getNestedType(), - localVarOp.getResult()); - builder.create( - nestedOp.getLoc(), - localVarOp.getInitial().getDefiningOp()->getOperand(0), readOp); - builder.clearInsertionPoint(); - assignOps.erase(assignOp); - } + for (auto [assignOp, localVar, var] : assignOps) { + builder.setInsertionPointAfter(assignOp); + auto readOp = builder.create(assignOp.getLoc(), localVar); + builder.create(assignOp.getLoc(), var, readOp); + builder.clearInsertionPoint(); + } }); }); } diff --git a/test/Dialect/Moore/canonicalizers.mlir b/test/Dialect/Moore/canonicalizers.mlir index c31831318613..2754fcef860c 100644 --- a/test/Dialect/Moore/canonicalizers.mlir +++ b/test/Dialect/Moore/canonicalizers.mlir @@ -280,3 +280,14 @@ func.func @Pow(%arg0 : !moore.l32) -> (!moore.l32, !moore.l32, !moore.l32, !moor // CHECK-NEXT: return [[V0]], [[V0]], [[V0]], [[V0]], [[V4]], [[V5]] : return %3, %4, %5, %6, %7, %8 : !moore.l32, !moore.l32, !moore.l32, !moore.l32, !moore.l32, !moore.l32 } + +// CHECK-LABEL: func.func @MoveInitialOutOfSSAVariable +func.func @MoveInitialOutOfSSAVariable() { + // CHECK: [[TMP:%.+]] = moore.constant 9001 + %0 = moore.constant 9001 : i42 + // CHECK: [[VAR:%.+]] = moore.variable : + // CHECK-NEXT: moore.blocking_assign [[VAR]], [[TMP]] + %1 = moore.variable %0 : + func.call @useRef(%1) : (!moore.ref) -> () + return +} diff --git a/test/Dialect/Moore/mem2reg.mlir b/test/Dialect/Moore/mem2reg.mlir index 80a59bed7b7a..4c48d971d269 100644 --- a/test/Dialect/Moore/mem2reg.mlir +++ b/test/Dialect/Moore/mem2reg.mlir @@ -1,34 +1,48 @@ -// RUN: circt-opt --mem2reg %s | FileCheck %s +// RUN: circt-opt --mem2reg --verify-diagnostics %s | FileCheck %s -// CHECK-LABEL: moore.module @LocalVar() { -moore.module @LocalVar() { - // CHECK: %x = moore.variable : - // CHECK: %y = moore.variable : - // CHECK: %z = moore.variable : - %x = moore.variable : - %y = moore.variable : - %z = moore.variable : - moore.procedure always_comb { - // CHECK: %0 = moore.read %x - // CHECK: %1 = moore.constant 1 : i32 - // CHECK: %2 = moore.add %0, %1 : i32 - // CHECK: moore.blocking_assign %z, %2 : i32 - // CHECK: %3 = moore.constant 1 : i32 - // CHECK: %4 = moore.add %2, %3 : i32 - // CHECK: moore.blocking_assign %y, %4 : i32 - %a = moore.variable : - %0 = moore.read %x : - %1 = moore.constant 1 : i32 - %2 = moore.add %0, %1 : i32 - moore.blocking_assign %a, %2 : i32 - %3 = moore.read %a : - moore.blocking_assign %z, %3 : i32 - %4 = moore.read %a : - %5 = moore.constant 1 : i32 - %6 = moore.add %4, %5 : i32 - moore.blocking_assign %a, %6 : i32 - %7 = moore.read %a : - moore.blocking_assign %y, %7 : i32 - moore.return - } +// CHECK-LABEL: func.func @Basic( +func.func @Basic() -> !moore.i42 { + // CHECK: [[TMP:%.*]] = moore.constant 9001 : i42 + // CHECK-NOT: = moore.variable + // CHECK-NOT: = moore.blocking_assign + // CHECK-NOT: = moore.read + %0 = moore.constant 9001 : i42 + %1 = moore.variable : + moore.blocking_assign %1, %0 : i42 + %2 = moore.read %1 : + // CHECK: return [[TMP]] : !moore.i42 + return %2 : !moore.i42 +} + +// CHECK-LABEL: func.func @ControlFlow( +func.func @ControlFlow(%arg0: i1, %arg1: !moore.l8) -> !moore.l8 { + // CHECK-NOT: moore.variable + // CHECK: [[DEFAULT:%.+]] = moore.constant hXX : l8 + %0 = moore.variable : + // CHECK: cf.cond_br %arg0, ^[[BB1:.+]], ^[[BB2:.+]]([[DEFAULT]] : !moore.l8) + cf.cond_br %arg0, ^bb1, ^bb2 +^bb1: + // CHECK-NOT: moore.blocking_assign + // CHECK: cf.br ^[[BB2]](%arg1 : !moore.l8) + moore.blocking_assign %0, %arg1 : l8 + cf.br ^bb2 +^bb2: + // CHECK: ^[[BB2]]([[TMP:%.+]]: !moore.l8): + // CHECK-NOT: moore.read + // CHECK: return [[TMP]] + %1 = moore.read %0 : + return %1 : !moore.l8 +} + +// CHECK-LABEL: func.func @InitialValueDoesNotDominateDefault( +func.func @InitialValueDoesNotDominateDefault() { + cf.br ^bb1 +^bb1: + %0 = moore.constant 0 : i32 + %1 = moore.variable %0 : + cf.br ^bb2 +^bb2: + %2 = moore.read %1 : + moore.blocking_assign %1, %2 : i32 + cf.br ^bb1 } diff --git a/test/Dialect/Moore/simplify-procedures.mlir b/test/Dialect/Moore/simplify-procedures.mlir index 451160643e37..1e8f4c7102ed 100644 --- a/test/Dialect/Moore/simplify-procedures.mlir +++ b/test/Dialect/Moore/simplify-procedures.mlir @@ -9,7 +9,8 @@ moore.module @Foo() { // CHECK: moore.procedure always_comb moore.procedure always_comb { // CHECK: [[TMP:%.+]] = moore.read %a - // CHECK: [[LOCAL_A:%.+]] = moore.variable [[TMP]] + // CHECK: [[LOCAL_A:%.+]] = moore.variable + // CHECK: moore.blocking_assign [[LOCAL_A]], [[TMP]] // CHECK: [[C1:%.+]] = moore.constant 1 // CHECK: moore.blocking_assign [[LOCAL_A]], [[C1]] @@ -40,7 +41,8 @@ moore.module @Foo() { // CHECK: moore.procedure always_comb moore.procedure always_comb { // CHECK: [[TMP:%.+]] = moore.read %a - // CHECK: [[LOCAL_A:%.+]] = moore.variable %0 + // CHECK: [[LOCAL_A:%.+]] = moore.variable + // CHECK: moore.blocking_assign [[LOCAL_A]], [[TMP]] // CHECK: [[TMP:%.+]] = moore.read [[LOCAL_A]] // CHECK: moore.blocking_assign %y, [[TMP]] diff --git a/tools/circt-reduce/circt-reduce.cpp b/tools/circt-reduce/circt-reduce.cpp index 0b9d289b18a9..399be4d96666 100644 --- a/tools/circt-reduce/circt-reduce.cpp +++ b/tools/circt-reduce/circt-reduce.cpp @@ -19,6 +19,7 @@ #include "circt/Reduce/GenericReductions.h" #include "circt/Reduce/Tester.h" #include "circt/Support/Version.h" +#include "mlir/Dialect/ControlFlow/IR/ControlFlow.h" #include "mlir/Dialect/Func/IR/FuncOps.h" #include "mlir/Dialect/LLVMIR/LLVMDialect.h" #include "mlir/Dialect/SCF/IR/SCF.h" @@ -424,7 +425,8 @@ int main(int argc, char **argv) { // Register all the dialects and create a context to work wtih. mlir::DialectRegistry registry; registerAllDialects(registry); - registry.insert(); + registry.insert(); arc::registerReducePatternDialectInterface(registry); firrtl::registerReducePatternDialectInterface(registry); hw::registerReducePatternDialectInterface(registry); From 62cd9aca7f0e9b1b47cb6d9f26dbe6507a777532 Mon Sep 17 00:00:00 2001 From: Martin Erhart Date: Sat, 10 Aug 2024 14:22:53 +0100 Subject: [PATCH 065/119] [MooreToCore] More extract op lowerings (#7499) --- include/circt/Dialect/LLHD/IR/ExtractOps.td | 4 +- lib/Conversion/MooreToCore/MooreToCore.cpp | 146 +++++++++++++++++++- test/Conversion/MooreToCore/basic.mlir | 74 ++++++++-- 3 files changed, 206 insertions(+), 18 deletions(-) diff --git a/include/circt/Dialect/LLHD/IR/ExtractOps.td b/include/circt/Dialect/LLHD/IR/ExtractOps.td index 121fc80e390b..b977058f7932 100644 --- a/include/circt/Dialect/LLHD/IR/ExtractOps.td +++ b/include/circt/Dialect/LLHD/IR/ExtractOps.td @@ -18,8 +18,8 @@ class SigPtrIndexBitWidthConstraint : TypesMatchWith<"Index width should be exactly clog2 (size of array)", input, index, [{ - IntegerType::get($_self.getContext(), std::max( - llvm::Log2_64_Ceil(llhd::getLLHDTypeWidth($_self)), 1)) + IntegerType::get($_self.getContext(), + llvm::Log2_64_Ceil(llhd::getLLHDTypeWidth($_self))) }]>; class SigArrayElementTypeConstraint diff --git a/lib/Conversion/MooreToCore/MooreToCore.cpp b/lib/Conversion/MooreToCore/MooreToCore.cpp index 4277688d7222..312688577046 100644 --- a/lib/Conversion/MooreToCore/MooreToCore.cpp +++ b/lib/Conversion/MooreToCore/MooreToCore.cpp @@ -419,10 +419,78 @@ struct ExtractOpConversion : public OpConversionPattern { LogicalResult matchAndRewrite(ExtractOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override { + // TODO: properly handle out-of-bounds accesses Type resultType = typeConverter->convertType(op.getResult().getType()); - rewriter.replaceOpWithNewOp( - op, resultType, adaptor.getInput(), adaptor.getLowBit()); - return success(); + Type inputType = adaptor.getInput().getType(); + + if (isa(inputType)) { + rewriter.replaceOpWithNewOp( + op, resultType, adaptor.getInput(), adaptor.getLowBit()); + return success(); + } + + if (auto arrTy = dyn_cast(inputType)) { + int64_t width = llvm::Log2_64_Ceil(arrTy.getNumElements()); + Value idx = rewriter.create( + op.getLoc(), rewriter.getIntegerType(width), adaptor.getLowBit()); + if (isa(resultType)) { + rewriter.replaceOpWithNewOp(op, resultType, + adaptor.getInput(), idx); + return success(); + } + + // Otherwise, it has to be the array's element type + rewriter.replaceOpWithNewOp(op, adaptor.getInput(), idx); + return success(); + } + + return failure(); + } +}; + +struct ExtractRefOpConversion : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(ExtractRefOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + // TODO: properly handle out-of-bounds accesses + Type resultType = typeConverter->convertType(op.getResult().getType()); + Type inputType = + cast(adaptor.getInput().getType()).getElementType(); + + if (auto intType = dyn_cast(inputType)) { + int64_t width = hw::getBitWidth(inputType); + if (width == -1) + return failure(); + + Value lowBit = rewriter.create( + op.getLoc(), rewriter.getIntegerType(llvm::Log2_64_Ceil(width)), + adaptor.getLowBit()); + rewriter.replaceOpWithNewOp( + op, resultType, adaptor.getInput(), lowBit); + return success(); + } + + if (auto arrType = dyn_cast(inputType)) { + Value lowBit = rewriter.create( + op.getLoc(), + rewriter.getIntegerType(llvm::Log2_64_Ceil(arrType.getNumElements())), + adaptor.getLowBit()); + + if (isa( + cast(resultType).getElementType())) { + rewriter.replaceOpWithNewOp( + op, resultType, adaptor.getInput(), lowBit); + return success(); + } + + rewriter.replaceOpWithNewOp(op, adaptor.getInput(), + lowBit); + return success(); + } + + return failure(); } }; @@ -449,6 +517,13 @@ struct DynExtractOpConversion : public OpConversionPattern { unsigned idxWidth = llvm::Log2_64_Ceil(arrType.getNumElements()); Value idx = adjustIntegerWidth(rewriter, adaptor.getLowBit(), idxWidth, op->getLoc()); + + if (isa(resultType)) { + rewriter.replaceOpWithNewOp(op, resultType, + adaptor.getInput(), idx); + return success(); + } + rewriter.replaceOpWithNewOp(op, adaptor.getInput(), idx); return success(); } @@ -457,6 +532,64 @@ struct DynExtractOpConversion : public OpConversionPattern { } }; +struct DynExtractRefOpConversion : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(DynExtractRefOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + // TODO: properly handle out-of-bounds accesses + Type resultType = typeConverter->convertType(op.getResult().getType()); + Type inputType = + cast(adaptor.getInput().getType()).getElementType(); + + if (auto intType = dyn_cast(inputType)) { + int64_t width = hw::getBitWidth(inputType); + if (width == -1) + return failure(); + + Value amount = + adjustIntegerWidth(rewriter, adaptor.getLowBit(), + llvm::Log2_64_Ceil(width), op->getLoc()); + rewriter.replaceOpWithNewOp( + op, resultType, adaptor.getInput(), amount); + return success(); + } + + if (auto arrType = dyn_cast(inputType)) { + Value idx = adjustIntegerWidth( + rewriter, adaptor.getLowBit(), + llvm::Log2_64_Ceil(arrType.getNumElements()), op->getLoc()); + + if (isa( + cast(resultType).getElementType())) { + rewriter.replaceOpWithNewOp( + op, resultType, adaptor.getInput(), idx); + return success(); + } + + rewriter.replaceOpWithNewOp(op, adaptor.getInput(), + idx); + return success(); + } + + return failure(); + } +}; + +struct StructCreateOpConversion : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(StructCreateOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + Type resultType = typeConverter->convertType(op.getResult().getType()); + rewriter.replaceOpWithNewOp(op, resultType, + adaptor.getFields()); + return success(); + } +}; + struct StructExtractOpConversion : public OpConversionPattern { using OpConversionPattern::OpConversionPattern; @@ -932,9 +1065,10 @@ static void populateOpConversion(RewritePatternSet &patterns, // Patterns of miscellaneous operations. ConstantOpConv, ConcatOpConversion, ReplicateOpConversion, - ExtractOpConversion, DynExtractOpConversion, ConversionOpConversion, - ReadOpConversion, NamedConstantOpConv, StructExtractOpConversion, - StructExtractRefOpConversion, + ExtractOpConversion, DynExtractOpConversion, DynExtractRefOpConversion, + ConversionOpConversion, ReadOpConversion, NamedConstantOpConv, + StructExtractOpConversion, StructExtractRefOpConversion, + ExtractRefOpConversion, StructCreateOpConversion, // Patterns of unary operations. ReduceAndOpConversion, ReduceOrOpConversion, ReduceXorOpConversion, diff --git a/test/Conversion/MooreToCore/basic.mlir b/test/Conversion/MooreToCore/basic.mlir index 38edef2e6df9..1ac9bfd3e1bf 100644 --- a/test/Conversion/MooreToCore/basic.mlir +++ b/test/Conversion/MooreToCore/basic.mlir @@ -46,7 +46,7 @@ func.func @UnrealizedConversionCast(%arg0: !moore.i8) -> !moore.i16 { } // CHECK-LABEL: func @Expressions -func.func @Expressions(%arg0: !moore.i1, %arg1: !moore.l1, %arg2: !moore.i6, %arg3: !moore.i5, %arg4: !moore.i1) { +func.func @Expressions(%arg0: !moore.i1, %arg1: !moore.l1, %arg2: !moore.i6, %arg3: !moore.i5, %arg4: !moore.i1, %arg5: !moore.array<5 x i32>, %arg6: !moore.ref, %arg7: !moore.ref>) { // CHECK-NEXT: %0 = comb.concat %arg0, %arg0 : i1, i1 // CHECK-NEXT: %1 = comb.concat %arg1, %arg1 : i1, i1 moore.concat %arg0, %arg0 : (!moore.i1, !moore.i1) -> !moore.i2 @@ -107,9 +107,27 @@ func.func @Expressions(%arg0: !moore.i1, %arg1: !moore.l1, %arg2: !moore.i6, %ar // CHECK-NEXT: %c2_i32 = hw.constant 2 : i32 %2 = moore.constant 2 : !moore.i32 + // CHECK-NEXT: %c0_i32 = hw.constant 0 : i32 + %c0 = moore.constant 0 : !moore.i32 // CHECK-NEXT: comb.extract %arg2 from 2 : (i6) -> i2 moore.extract %arg2 from 2 : !moore.i6 -> !moore.i2 + // CHECK-NEXT: [[V0:%.+]] = hw.constant 2 : i3 + // CHECK-NEXT: hw.array_slice %arg5[[[V0]]] : (!hw.array<5xi32>) -> !hw.array<2xi32> + moore.extract %arg5 from 2 : !moore.array<5 x i32> -> !moore.array<2 x i32> + // CHECK-NEXT: [[V0:%.+]] = hw.constant 2 : i3 + // CHECK-NEXT: hw.array_get %arg5[[[V0]]] : !hw.array<5xi32> + moore.extract %arg5 from 2 : !moore.array<5 x i32> -> i32 + + // CHECK-NEXT: [[V0:%.+]] = hw.constant 0 : i0 + // CHECK-NEXT: llhd.sig.extract %arg6 from [[V0]] : (!hw.inout) -> !hw.inout + moore.extract_ref %arg6 from 0 : !moore.ref -> !moore.ref + // CHECK-NEXT: [[V0:%.+]] = hw.constant 2 : i3 + // CHECK-NEXT: llhd.sig.array_slice %arg7 at [[V0]] : (!hw.inout>) -> !hw.inout> + moore.extract_ref %arg7 from 2 : !moore.ref> -> !moore.ref> + // CHECK-NEXT: [[V0:%.+]] = hw.constant 2 : i3 + // CHECK-NEXT: llhd.sig.array_get %arg7[[[V0]]] : !hw.inout> + moore.extract_ref %arg7 from 2 : !moore.ref> -> !moore.ref // CHECK-NEXT: [[V21:%.+]] = comb.extract %c2_i32 from 6 : (i32) -> i26 // CHECK-NEXT: [[CONST_0:%.+]] = hw.constant 0 : i26 @@ -120,6 +138,47 @@ func.func @Expressions(%arg0: !moore.i1, %arg1: !moore.l1, %arg2: !moore.i6, %ar // CHECK-NEXT: [[V25:%.+]] = comb.shru %arg2, [[V24]] : i6 // CHECK-NEXT: comb.extract [[V25]] from 0 : (i6) -> i1 moore.dyn_extract %arg2 from %2 : !moore.i6, !moore.i32 -> !moore.i1 + // CHECK-NEXT: [[V21:%.+]] = comb.extract %c2_i32 from 3 : (i32) -> i29 + // CHECK-NEXT: [[CONST_0:%.+]] = hw.constant 0 : i29 + // CHECK-NEXT: [[V22:%.+]] = comb.icmp eq [[V21]], [[CONST_0]] : i29 + // CHECK-NEXT: [[V23:%.+]] = comb.extract %c2_i32 from 0 : (i32) -> i3 + // CHECK-NEXT: [[MAX:%.+]] = hw.constant -1 : i3 + // CHECK-NEXT: [[V24:%.+]] = comb.mux [[V22]], [[V23]], [[MAX]] : i3 + // CHECK-NEXT: hw.array_slice %arg5[[[V24]]] : (!hw.array<5xi32>) -> !hw.array<2xi32> + moore.dyn_extract %arg5 from %2 : !moore.array<5 x i32>, !moore.i32 -> !moore.array<2 x i32> + // CHECK-NEXT: [[V21:%.+]] = comb.extract %c2_i32 from 3 : (i32) -> i29 + // CHECK-NEXT: [[CONST_0:%.+]] = hw.constant 0 : i29 + // CHECK-NEXT: [[V22:%.+]] = comb.icmp eq [[V21]], [[CONST_0]] : i29 + // CHECK-NEXT: [[V23:%.+]] = comb.extract %c2_i32 from 0 : (i32) -> i3 + // CHECK-NEXT: [[MAX:%.+]] = hw.constant -1 : i3 + // CHECK-NEXT: [[V24:%.+]] = comb.mux [[V22]], [[V23]], [[MAX]] : i3 + // CHECK-NEXT: hw.array_get %arg5[[[V24]]] : !hw.array<5xi32> + moore.dyn_extract %arg5 from %2 : !moore.array<5 x i32>, !moore.i32 -> !moore.i32 + + // CHECK-NEXT: [[V21:%.+]] = comb.extract %c0_i32 from 0 : (i32) -> i32 + // CHECK-NEXT: [[CONST_0:%.+]] = hw.constant 0 : i32 + // CHECK-NEXT: [[V22:%.+]] = comb.icmp eq [[V21]], [[CONST_0]] : i32 + // CHECK-NEXT: [[V23:%.+]] = comb.extract %c0_i32 from 0 : (i32) -> i0 + // CHECK-NEXT: [[MAX:%.+]] = hw.constant 0 : i0 + // CHECK-NEXT: [[V24:%.+]] = comb.mux [[V22]], [[V23]], [[MAX]] : i0 + // CHECK-NEXT: llhd.sig.extract %arg6 from [[V24]] : (!hw.inout) -> !hw.inout + moore.dyn_extract_ref %arg6 from %c0 : !moore.ref, !moore.i32 -> !moore.ref + // CHECK-NEXT: [[V21:%.+]] = comb.extract %c2_i32 from 3 : (i32) -> i29 + // CHECK-NEXT: [[CONST_0:%.+]] = hw.constant 0 : i29 + // CHECK-NEXT: [[V22:%.+]] = comb.icmp eq [[V21]], [[CONST_0]] : i29 + // CHECK-NEXT: [[V23:%.+]] = comb.extract %c2_i32 from 0 : (i32) -> i3 + // CHECK-NEXT: [[MAX:%.+]] = hw.constant -1 : i3 + // CHECK-NEXT: [[V24:%.+]] = comb.mux [[V22]], [[V23]], [[MAX]] : i3 + // CHECK-NEXT: llhd.sig.array_slice %arg7 at [[V24]] : (!hw.inout>) -> !hw.inout> + moore.dyn_extract_ref %arg7 from %2 : !moore.ref>, !moore.i32 -> !moore.ref> + // CHECK-NEXT: [[V21:%.+]] = comb.extract %c2_i32 from 3 : (i32) -> i29 + // CHECK-NEXT: [[CONST_0:%.+]] = hw.constant 0 : i29 + // CHECK-NEXT: [[V22:%.+]] = comb.icmp eq [[V21]], [[CONST_0]] : i29 + // CHECK-NEXT: [[V23:%.+]] = comb.extract %c2_i32 from 0 : (i32) -> i3 + // CHECK-NEXT: [[MAX:%.+]] = hw.constant -1 : i3 + // CHECK-NEXT: [[V24:%.+]] = comb.mux [[V22]], [[V23]], [[MAX]] : i3 + // CHECK-NEXT: llhd.sig.array_get %arg7[[[V24]]] : !hw.inout> + moore.dyn_extract_ref %arg7 from %2 : !moore.ref>, !moore.i32 -> !moore.ref // CHECK-NEXT: [[V26:%.+]] = hw.constant -1 : i6 // CHECK-NEXT: comb.icmp eq %arg2, [[V26]] : i6 @@ -212,14 +271,6 @@ func.func @AdvancedConversion(%arg0: !moore.array<5 x struct<{exp_bits: i32, man return %1, %2 : !moore.array<5 x struct<{exp_bits: i32, man_bits: i32}>>, !moore.i320 } -// CHECK-LABEL: func.func @DynExtractConversion -func.func @DynExtractConversion(%arg0: !moore.array<5 x struct<{exp_bits: i32, man_bits: i32}>>) -> !moore.struct<{exp_bits: i32, man_bits: i32}> { - %0 = moore.constant 3 : !moore.i32 - // CHECK: hw.array_get %arg0[{{.*}}] : !hw.array<5xstruct>, i3 - %1 = moore.dyn_extract %arg0 from %0 : !moore.array<5 x struct<{exp_bits: i32, man_bits: i32}>>, !moore.i32 -> !moore.struct<{exp_bits: i32, man_bits: i32}> - return %1 : !moore.struct<{exp_bits: i32, man_bits: i32}> -} - // CHECK-LABEL: hw.module @InstanceNull() { moore.module @InstanceNull() { @@ -312,7 +363,7 @@ moore.module @Variable() { } // CHECK-LABEL: hw.module @Struct -moore.module @Struct(in %arg0 : !moore.struct<{exp_bits: i32, man_bits: i32}>, in %arg1 : !moore.ref>, out a : !moore.i32, out b : !moore.struct<{exp_bits: i32, man_bits: i32}>, out c : !moore.struct<{exp_bits: i32, man_bits: i32}>) { +moore.module @Struct(in %a : !moore.i32, in %b : !moore.i32, in %arg0 : !moore.struct<{exp_bits: i32, man_bits: i32}>, in %arg1 : !moore.ref>, out a : !moore.i32, out b : !moore.struct<{exp_bits: i32, man_bits: i32}>, out c : !moore.struct<{exp_bits: i32, man_bits: i32}>) { // CHECK: hw.struct_extract %arg0["exp_bits"] : !hw.struct %0 = moore.struct_extract %arg0, "exp_bits" : !moore.struct<{exp_bits: i32, man_bits: i32}> -> i32 @@ -330,6 +381,9 @@ moore.module @Struct(in %arg0 : !moore.struct<{exp_bits: i32, man_bits: i32}>, i %3 = moore.read %1 : > %4 = moore.read %2 : > + // CHECK; hw.struct_create %a, %b : !hw.struct + moore.struct_create %a, %b : !moore.i32, !moore.i32 -> struct<{a: i32, b: i32}> + moore.output %0, %3, %4 : !moore.i32, !moore.struct<{exp_bits: i32, man_bits: i32}>, !moore.struct<{exp_bits: i32, man_bits: i32}> } From 286e73e86c574d5f95e45ecf87bebd9f2c24a427 Mon Sep 17 00:00:00 2001 From: lonely eagle <2020382038@qq.com> Date: Sat, 10 Aug 2024 22:02:08 +0800 Subject: [PATCH 066/119] [calyx] fix calyx canonicalization. (#7456) * add calyx canonicalization. * Added more checks to calyx.component, fix some details. * update CmakeLists.txt. * Update lib/Dialect/Calyx/CalyxOps.cpp Optimized code. Co-authored-by: Chris Gyurgyik * Update lib/Dialect/Calyx/CalyxOps.cpp Optimize error reporting. Co-authored-by: Chris Gyurgyik * use clang-format and add calyx.par test. * Optimised code. --------- Co-authored-by: Chris Gyurgyik --- lib/Dialect/Calyx/CMakeLists.txt | 1 + lib/Dialect/Calyx/CalyxOps.cpp | 27 +++++++--- .../CalyxToFSM/materialize-errors.mlir | 6 +-- test/Dialect/Calyx/clk-insertion.mlir | 4 +- test/Dialect/Calyx/errors.mlir | 52 ++++++++++++++++++- 5 files changed, 75 insertions(+), 15 deletions(-) diff --git a/lib/Dialect/Calyx/CMakeLists.txt b/lib/Dialect/Calyx/CMakeLists.txt index bf327785be47..d0fc81a14898 100644 --- a/lib/Dialect/Calyx/CMakeLists.txt +++ b/lib/Dialect/Calyx/CMakeLists.txt @@ -21,6 +21,7 @@ add_circt_dialect_library(CIRCTCalyx CIRCTComb CIRCTSV CIRCTHW + CIRCTFSM MLIRArithDialect MLIRIR MLIRMemRefDialect diff --git a/lib/Dialect/Calyx/CalyxOps.cpp b/lib/Dialect/Calyx/CalyxOps.cpp index ead67b05b50a..e5b592ab373e 100644 --- a/lib/Dialect/Calyx/CalyxOps.cpp +++ b/lib/Dialect/Calyx/CalyxOps.cpp @@ -12,6 +12,7 @@ #include "circt/Dialect/Calyx/CalyxOps.h" #include "circt/Dialect/Comb/CombOps.h" +#include "circt/Dialect/FSM/FSMOps.h" #include "circt/Dialect/HW/HWAttributes.h" #include "circt/Dialect/HW/HWOps.h" #include "circt/Dialect/HW/HWTypes.h" @@ -745,16 +746,25 @@ LogicalResult ComponentOp::verify() { // Verify the component actually does something: has a non-empty Control // region, or continuous assignments. - bool hasNoControlConstructs = - getControlOp().getBodyBlock()->getOperations().empty(); + bool hasNoControlConstructs = true; + getControlOp().walk([&](Operation *op) { + if (isa(op)) { + hasNoControlConstructs = false; + return WalkResult::interrupt(); + } + return WalkResult::advance(); + }); bool hasNoAssignments = getWiresOp().getBodyBlock()->getOps().empty(); if (hasNoControlConstructs && hasNoAssignments) return emitOpError( - "The component currently does nothing. It needs to either have " - "continuous assignments in the Wires region or control constructs in " - "the Control region."); - + "The component currently does nothing. It needs to either have " + "continuous assignments in the Wires region or control " + "constructs in the Control region. The Control region " + "should contain at least one of ") + << "'" << EnableOp::getOperationName() << "' , " + << "'" << InvokeOp::getOperationName() << "' or " + << "'" << fsm::MachineOp::getOperationName() << "'."; return success(); } @@ -843,8 +853,7 @@ LogicalResult CombComponentOp::verify() { if (hasNoAssignments) return emitOpError( "The component currently does nothing. It needs to either have " - "continuous assignments in the Wires region or control constructs in " - "the Control region."); + "continuous assignments in the Wires region."); // Check that all cells are combinational auto cells = getOps(); @@ -2236,6 +2245,8 @@ template static std::optional getLastEnableOp(OpTy parent) { static_assert(IsAny(), "Should be a StaticSeqOp or SeqOp."); + if (parent.getBodyBlock()->empty()) + return std::nullopt; auto &lastOp = parent.getBodyBlock()->back(); if (auto enableOp = dyn_cast(lastOp)) return enableOp; diff --git a/test/Conversion/CalyxToFSM/materialize-errors.mlir b/test/Conversion/CalyxToFSM/materialize-errors.mlir index 84aa9cd99940..87976620d925 100644 --- a/test/Conversion/CalyxToFSM/materialize-errors.mlir +++ b/test/Conversion/CalyxToFSM/materialize-errors.mlir @@ -1,7 +1,8 @@ // RUN: circt-opt -pass-pipeline='builtin.module(calyx.component(materialize-calyx-to-fsm))' -split-input-file -verify-diagnostics %s - calyx.component @main(%go: i1 {go}, %reset: i1 {reset}, %clk: i1 {clk}) -> (%done: i1 {done}) { + %r.in, %r.write_en, %r.clk, %r.reset, %r.out, %r.done = calyx.register @r : i32, i1, i1, i1, i32, i1 calyx.wires { + calyx.assign %r.clk = %clk : i1 } // expected-error @+1 {{'calyx.control' op expected an 'fsm.machine' operation as the top-level operation within the control region of this component.}} calyx.control { @@ -9,10 +10,7 @@ calyx.component @main(%go: i1 {go}, %reset: i1 {reset}, %clk: i1 {clk}) -> (%don } } - - // ----- - calyx.component @main(%go: i1 {go}, %reset: i1 {reset}, %clk: i1 {clk}) -> (%done: i1 {done}) { calyx.wires { } diff --git a/test/Dialect/Calyx/clk-insertion.mlir b/test/Dialect/Calyx/clk-insertion.mlir index 4b32b7498ac1..8f7f924d1d41 100644 --- a/test/Dialect/Calyx/clk-insertion.mlir +++ b/test/Dialect/Calyx/clk-insertion.mlir @@ -6,7 +6,7 @@ module attributes {calyx.entrypoint = "main"} { calyx.wires { calyx.assign %done = %c1_1 : i1 } calyx.control {} } - calyx.component @main(%go: i1 {go}, %clk: i1 {clk}, %reset: i1 {reset}) -> (%done: i1 {done}) { + calyx.component @main(%in: i8, %go: i1 {go}, %clk: i1 {clk}, %reset: i1 {reset}) -> (%out: i8, %done: i1 {done}) { %c0.in, %c0.go, %c0.clk, %c0.reset, %c0.out, %c0.done = calyx.instance @c0 of @A : i8, i1, i1, i1, i8, i1 %r.in, %r.write_en, %r.clk, %r.reset, %r.out, %r.done = calyx.register @r : i1, i1, i1, i1, i1, i1 // CHECK: calyx.wires { @@ -16,6 +16,8 @@ module attributes {calyx.entrypoint = "main"} { // CHECK: calyx.assign %r.clk = %clk : i1 // CHECK: } calyx.wires { + calyx.assign %c0.in = %in : i8 + calyx.assign %out = %c0.out : i8 } calyx.control { calyx.seq { } diff --git a/test/Dialect/Calyx/errors.mlir b/test/Dialect/Calyx/errors.mlir index 78129580ab18..68a765023090 100644 --- a/test/Dialect/Calyx/errors.mlir +++ b/test/Dialect/Calyx/errors.mlir @@ -886,7 +886,7 @@ module attributes {calyx.entrypoint = "main"} { // ----- module attributes {calyx.entrypoint = "main"} { - // expected-error @+1 {{'calyx.component' op The component currently does nothing. It needs to either have continuous assignments in the Wires region or control constructs in the Control region.}} + // expected-error @+1 {{'calyx.component' op The component currently does nothing. It needs to either have continuous assignments in the Wires region or control constructs in the Control region. The Control region should contain at least one of 'calyx.enable' , 'calyx.invoke' or 'fsm.machine'.}} calyx.component @main(%go: i1 {go}, %clk: i1 {clk}, %reset: i1 {reset}) -> (%done: i1 {done}) { %std_lt_0.left, %std_lt_0.right, %std_lt_0.out = calyx.std_lt @std_lt_0 : i32, i32, i1 %c64_i32 = hw.constant 64 : i32 @@ -899,6 +899,54 @@ module attributes {calyx.entrypoint = "main"} { } } +// ----- + +module attributes {calyx.entrypoint = "main"} { + // expected-error @+1 {{'calyx.component' op The component currently does nothing. It needs to either have continuous assignments in the Wires region or control constructs in the Control region. The Control region should contain at least one of 'calyx.enable' , 'calyx.invoke' or 'fsm.machine'.}} + calyx.component @main(%go: i1 {go}, %clk: i1 {clk}, %reset: i1 {reset}) -> (%done: i1 {done}) { + %r.in, %r.write_en, %r.clk, %r.reset, %r.out, %r.done = calyx.register @r : i1, i1, i1, i1, i1, i1 + %eq.left, %eq.right, %eq.out = calyx.std_eq @eq : i1, i1, i1 + %c1_1 = hw.constant 1 : i1 + calyx.wires { + } + calyx.control { + calyx.seq { + calyx.if %eq.out { + calyx.seq { + } + } else { + calyx.seq { + } + } + } + } + } +} + +// ----- + +module attributes {calyx.entrypoint = "main"} { + // expected-error @+1 {{'calyx.component' op The component currently does nothing. It needs to either have continuous assignments in the Wires region or control constructs in the Control region. The Control region should contain at least one of 'calyx.enable' , 'calyx.invoke' or 'fsm.machine'.}} + calyx.component @main(%go: i1 {go}, %clk: i1 {clk}, %reset: i1 {reset}) -> (%done: i1 {done}) { + %r.in, %r.write_en, %r.clk, %r.reset, %r.out, %r.done = calyx.register @r : i1, i1, i1, i1, i1, i1 + %eq.left, %eq.right, %eq.out = calyx.std_eq @eq : i1, i1, i1 + %c1_1 = hw.constant 1 : i1 + calyx.wires { + } + calyx.control { + calyx.par { + calyx.if %eq.out { + calyx.par { + } + } else { + calyx.par { + } + } + } + } + } +} + // ----- module attributes {calyx.entrypoint = "A"} { hw.module.extern @params(in %in: !hw.int<#hw.param.decl.ref<"WIDTH">>, in %clk: i1 {calyx.clk}, in %go: i1 {calyx.go = 1}, out out: !hw.int<#hw.param.decl.ref<"WIDTH">>, out done: i1 {calyx.done}) attributes {filename = "test.v"} @@ -988,7 +1036,7 @@ module attributes {calyx.entrypoint = "main"} { // ----- module attributes {calyx.entrypoint = "main"} { - // expected-error @+1 {{'calyx.comb_component' op The component currently does nothing. It needs to either have continuous assignments in the Wires region or control constructs in the Control region.}} + // expected-error @+1 {{'calyx.comb_component' op The component currently does nothing. It needs to either have continuous assignments in the Wires region.}} calyx.comb_component @main() -> () { %std_lt_0.left, %std_lt_0.right, %std_lt_0.out = calyx.std_lt @std_lt_0 : i32, i32, i1 %c64_i32 = hw.constant 64 : i32 From 9d207a3899dbeb35d32c48aedeb6b6a950d9d326 Mon Sep 17 00:00:00 2001 From: Martin Erhart Date: Sat, 10 Aug 2024 16:10:36 +0100 Subject: [PATCH 067/119] [MooreToCore] Support AssignedVariableOp (#7500) --- lib/Conversion/MooreToCore/MooreToCore.cpp | 14 ++++++++++++++ test/Conversion/MooreToCore/basic.mlir | 3 +++ 2 files changed, 17 insertions(+) diff --git a/lib/Conversion/MooreToCore/MooreToCore.cpp b/lib/Conversion/MooreToCore/MooreToCore.cpp index 312688577046..12858f01d097 100644 --- a/lib/Conversion/MooreToCore/MooreToCore.cpp +++ b/lib/Conversion/MooreToCore/MooreToCore.cpp @@ -928,6 +928,19 @@ struct ReadOpConversion : public OpConversionPattern { } }; +struct AssignedVariableOpConversion + : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(AssignedVariableOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + rewriter.replaceOpWithNewOp(op, adaptor.getInput(), + adaptor.getNameAttr()); + return success(); + } +}; + template struct AssignOpConversion : public OpConversionPattern { using OpConversionPattern::OpConversionPattern; @@ -1112,6 +1125,7 @@ static void populateOpConversion(RewritePatternSet &patterns, AssignOpConversion, AssignOpConversion, AssignOpConversion, + AssignedVariableOpConversion, // Patterns of branch operations. CondBranchOpConversion, BranchOpConversion, diff --git a/test/Conversion/MooreToCore/basic.mlir b/test/Conversion/MooreToCore/basic.mlir index 1ac9bfd3e1bf..af4b6b96628c 100644 --- a/test/Conversion/MooreToCore/basic.mlir +++ b/test/Conversion/MooreToCore/basic.mlir @@ -57,6 +57,9 @@ func.func @Expressions(%arg0: !moore.i1, %arg1: !moore.l1, %arg2: !moore.i6, %ar moore.replicate %arg0 : i1 -> i2 moore.replicate %arg1 : l1 -> l2 + // CHECK-NEXT: %name = hw.wire %arg0 : i1 + %name = moore.assigned_variable %arg0 : !moore.i1 + // CHECK-NEXT: %c12_i32 = hw.constant 12 : i32 // CHECK-NEXT: %c3_i6 = hw.constant 3 : i6 moore.constant 12 : !moore.i32 From 43608bea24707335af5905699b51c400fc37876f Mon Sep 17 00:00:00 2001 From: Fabian Schuiki Date: Sat, 10 Aug 2024 10:29:56 -0700 Subject: [PATCH 068/119] [MooreToCore] Support four-valued VariableOp without init (#7502) Initialize `VariableOp`s of a four-valued type with a zero. This is in line with the rest of `MooreToCore` which maps all X/Z to zero at the moment, either implicitly by mapping to `comb.*` ops, or explicitly by conjuring up zero constants. --- lib/Conversion/MooreToCore/MooreToCore.cpp | 10 +++++----- test/Conversion/MooreToCore/basic.mlir | 3 +++ 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/Conversion/MooreToCore/MooreToCore.cpp b/lib/Conversion/MooreToCore/MooreToCore.cpp index 12858f01d097..9b2c8e7b6317 100644 --- a/lib/Conversion/MooreToCore/MooreToCore.cpp +++ b/lib/Conversion/MooreToCore/MooreToCore.cpp @@ -320,18 +320,18 @@ struct VariableOpConversion : public OpConversionPattern { ConversionPatternRewriter &rewriter) const override { Location loc = op.getLoc(); Type resultType = typeConverter->convertType(op.getResult().getType()); - Value init = adaptor.getInitial(); - // TODO: Unsupport x/z, so the initial value is 0. - if (!init && cast(op.getResult().getType()).getDomain() == - Domain::FourValued) - return failure(); + // Determine the initial value of the signal. + Value init = adaptor.getInitial(); if (!init) { Type elementType = cast(resultType).getElementType(); int64_t width = hw::getBitWidth(elementType); if (width == -1) return failure(); + // TODO: Once the core dialects support four-valued integers, this code + // will additionally need to generate an all-X value for four-valued + // variables. Value constZero = rewriter.create(loc, APInt(width, 0)); init = rewriter.createOrFold(loc, elementType, constZero); } diff --git a/test/Conversion/MooreToCore/basic.mlir b/test/Conversion/MooreToCore/basic.mlir index af4b6b96628c..71c4c45039b4 100644 --- a/test/Conversion/MooreToCore/basic.mlir +++ b/test/Conversion/MooreToCore/basic.mlir @@ -353,6 +353,9 @@ moore.module @Variable() { %1 = moore.constant 1 : l1 // CHECK: llhd.sig "l" %true : i1 %l = moore.variable %1 : + // CHECK: [[TMP:%.+]] = hw.constant 0 : i19 + // CHECK: llhd.sig "m" [[TMP]] : i19 + %m = moore.variable : // CHECK: [[TMP2:%.+]] = hw.constant 10 : i32 %3 = moore.constant 10 : i32 From 42401036d8ee08a277ce55aa67209ab67645971b Mon Sep 17 00:00:00 2001 From: Fabian Schuiki Date: Sat, 10 Aug 2024 11:46:18 -0700 Subject: [PATCH 069/119] [MooreToCore] Support ConditionalOp (#7501) Add a lowering pattern from `moore.conditional` to `scf.if`. This currently relies on the condition being a two-valued integer after lowering. Once we support four-valued integers at the core dialect level, the lowering of `moore.conditional` will become a lot more complicated. --- include/circt/Conversion/Passes.td | 3 +- include/circt/Dialect/Moore/MooreOps.td | 29 +++++++-------- lib/Conversion/ImportVerilog/Expressions.cpp | 8 ++-- lib/Conversion/MooreToCore/CMakeLists.txt | 9 +++-- lib/Conversion/MooreToCore/MooreToCore.cpp | 39 +++++++++++++++++++- test/Conversion/MooreToCore/basic.mlir | 32 ++++++++++++++++ 6 files changed, 94 insertions(+), 26 deletions(-) diff --git a/include/circt/Conversion/Passes.td b/include/circt/Conversion/Passes.td index 7cf405fefe29..dddc507bd80f 100644 --- a/include/circt/Conversion/Passes.td +++ b/include/circt/Conversion/Passes.td @@ -516,7 +516,8 @@ def ConvertMooreToCore : Pass<"convert-moore-to-core", "mlir::ModuleOp"> { }]; let constructor = "circt::createConvertMooreToCorePass()"; let dependentDialects = ["comb::CombDialect", "hw::HWDialect", - "llhd::LLHDDialect", "mlir::cf::ControlFlowDialect"]; + "llhd::LLHDDialect", "mlir::cf::ControlFlowDialect", + "mlir::scf::SCFDialect"]; } //===----------------------------------------------------------------------===// diff --git a/include/circt/Dialect/Moore/MooreOps.td b/include/circt/Dialect/Moore/MooreOps.td index a80747cd1a62..ba7f5dc6d475 100644 --- a/include/circt/Dialect/Moore/MooreOps.td +++ b/include/circt/Dialect/Moore/MooreOps.td @@ -1224,24 +1224,21 @@ def UnionExtractRefOp : MooreOp<"union_extract_ref"> { def ConditionalOp : MooreOp<"conditional",[ RecursiveMemoryEffects, NoRegionArguments, - SingleBlockImplicitTerminator<"moore::YieldOp"> ]> { let summary = "Conditional operation"; let description = [{ - If cond_predicate is true, the operator returns the value of the first - expression without evaluating the second expression; if false, it returns - the value of the second expression without evaluating the first expression. - If cond_predicate evaluates to an ambiguous value (x or z), then both the - first expression and the second expression shall be evaluated, and compared - for logical equivalence. If that comparison is true (1), the operator shall - return either the first or second expression. Otherwise the operator returns - a result based on the data types of the expressions. - - When both the first and second expressions are of integral types, if the - cond_predicate evaluates to an ambiguous value and the expressions are not - logically equivalent, their results shall be combined bit by bit using the - table below to calculate the final result. The first and second expressions - are extended to the same width. + If the condition is true, this op evaluates the first region and returns its + result without evaluating the second region. If the the condition is false, + this op evaluates the second region and returns its result without + evaluating the first region. + + If the condition is unknown (X or Z), _both_ regions are evaluated. If both + results are equal as per `case_eq`, one of the results is returned. If the + results are not equal, this op returns a value based on the data types of + the results. + + In case the results of the first and second region are of an integral type, + they are merged by applying the following bit-wise truth table: |?: | 0 | 1 | X | Z | |---|---|---|---|---| @@ -1250,6 +1247,7 @@ def ConditionalOp : MooreOp<"conditional",[ | X | X | X | X | X | | Z | X | X | X | X | + Non-integral data types define other rules which are not yet implemented. See IEEE 1800-2017 ยง 11.4.11 "Conditional operator". }]; let arguments = (ins AnySingleBitType:$condition); @@ -1280,7 +1278,6 @@ def YieldOp : MooreOp<"yield", [ yielded. }]; let arguments = (ins UnpackedType:$result); - let builders = [OpBuilder<(ins), [{ /* nothing to do */ }]>]; let assemblyFormat = [{ attr-dict $result `:` type($result) }]; diff --git a/lib/Conversion/ImportVerilog/Expressions.cpp b/lib/Conversion/ImportVerilog/Expressions.cpp index dc8b370129c7..ded3bd8bd8e3 100644 --- a/lib/Conversion/ImportVerilog/Expressions.cpp +++ b/lib/Conversion/ImportVerilog/Expressions.cpp @@ -677,13 +677,13 @@ struct RvalueExprVisitor { auto conditionalOp = builder.create(loc, type, value); // Create blocks for true region and false region. - conditionalOp.getTrueRegion().emplaceBlock(); - conditionalOp.getFalseRegion().emplaceBlock(); + auto &trueBlock = conditionalOp.getTrueRegion().emplaceBlock(); + auto &falseBlock = conditionalOp.getFalseRegion().emplaceBlock(); OpBuilder::InsertionGuard g(builder); // Handle left expression. - builder.setInsertionPointToStart(conditionalOp.getBody(0)); + builder.setInsertionPointToStart(&trueBlock); auto trueValue = context.convertRvalueExpression(expr.left()); if (!trueValue) return {}; @@ -692,7 +692,7 @@ struct RvalueExprVisitor { builder.create(loc, trueValue); // Handle right expression. - builder.setInsertionPointToStart(conditionalOp.getBody(1)); + builder.setInsertionPointToStart(&falseBlock); auto falseValue = context.convertRvalueExpression(expr.right()); if (!falseValue) return {}; diff --git a/lib/Conversion/MooreToCore/CMakeLists.txt b/lib/Conversion/MooreToCore/CMakeLists.txt index 89e706ea9d22..04efccd99682 100644 --- a/lib/Conversion/MooreToCore/CMakeLists.txt +++ b/lib/Conversion/MooreToCore/CMakeLists.txt @@ -8,12 +8,13 @@ add_circt_conversion_library(CIRCTMooreToCore Core LINK_LIBS PUBLIC - CIRCTMoore - CIRCTLLHD - CIRCTHW CIRCTComb + CIRCTHW + CIRCTLLHD + CIRCTMoore MLIRControlFlowDialect MLIRFuncDialect - MLIRTransforms + MLIRSCFDialect MLIRSideEffectInterfaces + MLIRTransforms ) diff --git a/lib/Conversion/MooreToCore/MooreToCore.cpp b/lib/Conversion/MooreToCore/MooreToCore.cpp index 9b2c8e7b6317..b2fe1a00aa7c 100644 --- a/lib/Conversion/MooreToCore/MooreToCore.cpp +++ b/lib/Conversion/MooreToCore/MooreToCore.cpp @@ -17,6 +17,7 @@ #include "circt/Dialect/Moore/MooreOps.h" #include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h" #include "mlir/Dialect/Func/IR/FuncOps.h" +#include "mlir/Dialect/SCF/IR/SCF.h" #include "mlir/IR/BuiltinDialect.h" #include "mlir/Interfaces/SideEffectInterfaces.h" #include "mlir/Pass/Pass.h" @@ -960,6 +961,39 @@ struct AssignOpConversion : public OpConversionPattern { } }; +struct ConditionalOpConversion : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(ConditionalOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + // TODO: This lowering is only correct if the condition is two-valued. If + // the condition is X or Z, both branches of the conditional must be + // evaluated and merged with the appropriate lookup table. See documentation + // for `ConditionalOp`. + auto type = typeConverter->convertType(op.getType()); + auto ifOp = + rewriter.create(op.getLoc(), type, adaptor.getCondition()); + rewriter.inlineRegionBefore(op.getTrueRegion(), ifOp.getThenRegion(), + ifOp.getThenRegion().end()); + rewriter.inlineRegionBefore(op.getFalseRegion(), ifOp.getElseRegion(), + ifOp.getElseRegion().end()); + rewriter.replaceOp(op, ifOp); + return success(); + } +}; + +struct YieldOpConversion : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(YieldOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + rewriter.replaceOpWithNewOp(op, adaptor.getResult()); + return success(); + } +}; + } // namespace //===----------------------------------------------------------------------===// @@ -992,6 +1026,8 @@ static void populateLegality(ConversionTarget &target) { addGenericLegality(target); addGenericLegality(target); + addGenericLegality(target); + addGenericLegality(target); addGenericLegality(target); addGenericLegality(target); addGenericLegality(target); @@ -1081,7 +1117,8 @@ static void populateOpConversion(RewritePatternSet &patterns, ExtractOpConversion, DynExtractOpConversion, DynExtractRefOpConversion, ConversionOpConversion, ReadOpConversion, NamedConstantOpConv, StructExtractOpConversion, StructExtractRefOpConversion, - ExtractRefOpConversion, StructCreateOpConversion, + ExtractRefOpConversion, StructCreateOpConversion, ConditionalOpConversion, + YieldOpConversion, // Patterns of unary operations. ReduceAndOpConversion, ReduceOrOpConversion, ReduceXorOpConversion, diff --git a/test/Conversion/MooreToCore/basic.mlir b/test/Conversion/MooreToCore/basic.mlir index 71c4c45039b4..9a6bf5968dbd 100644 --- a/test/Conversion/MooreToCore/basic.mlir +++ b/test/Conversion/MooreToCore/basic.mlir @@ -258,6 +258,38 @@ func.func @Expressions(%arg0: !moore.i1, %arg1: !moore.l1, %arg2: !moore.i6, %ar moore.wildcard_eq %arg0, %arg0 : !moore.i1 -> !moore.i1 moore.wildcard_ne %arg0, %arg0 : !moore.i1 -> !moore.i1 + // CHECK-NEXT: [[RES:%.+]] = scf.if %arg0 -> (i6) { + // CHECK-NEXT: scf.yield %arg2 : i6 + // CHECK-NEXT: } else { + // CHECK-NEXT: [[TMP:%.+]] = hw.constant 19 : i6 + // CHECK-NEXT: scf.yield [[TMP]] : i6 + // CHECK-NEXT: } + // CHECK-NEXT: comb.parity [[RES]] : i6 + %k0 = moore.conditional %arg0 : i1 -> i6 { + moore.yield %arg2 : i6 + } { + %0 = moore.constant 19 : i6 + moore.yield %0 : i6 + } + moore.reduce_xor %k0 : i6 -> i1 + + // CHECK-NEXT: [[RES:%.+]] = scf.if %arg1 -> (i6) { + // CHECK-NEXT: [[TMP:%.+]] = hw.constant 0 : i6 + // CHECK-NEXT: scf.yield [[TMP]] : i6 + // CHECK-NEXT: } else { + // CHECK-NEXT: [[TMP:%.+]] = hw.constant 19 : i6 + // CHECK-NEXT: scf.yield [[TMP]] : i6 + // CHECK-NEXT: } + // CHECK-NEXT: comb.parity [[RES]] : i6 + %k1 = moore.conditional %arg1 : l1 -> l6 { + %0 = moore.constant bXXXXXX : l6 + moore.yield %0 : l6 + } { + %0 = moore.constant 19 : l6 + moore.yield %0 : l6 + } + moore.reduce_xor %k1 : l6 -> l1 + // CHECK-NEXT: return return } From e871e0b4f14998dab1eea8afc08929914563de03 Mon Sep 17 00:00:00 2001 From: Fabian Schuiki Date: Sat, 10 Aug 2024 12:05:42 -0700 Subject: [PATCH 070/119] [MooreToCore] Support CaseZEq and CaseXZEq ops (#7503) Add a conversion for `moore.casez_eq` and `moore.casexz_eq` operations. These only really make sense if the operands are four-valued integers, since the X and Z bits indicate which bits to ignore during the equality check. We don't have support for four-valued integers in the core dialects yet. However, the vast majority of uses of this op are comparing an SSA value against a `moore.constant`. This case we can handle properly by looking at the constant, identifying the unknown bits, and then masking them before performing a regular two-valued comparison between the two operands. --- include/circt/Support/FVInt.h | 3 ++ lib/Conversion/MooreToCore/MooreToCore.cpp | 50 +++++++++++++++++++++- test/Conversion/MooreToCore/basic.mlir | 44 +++++++++++++++++++ 3 files changed, 95 insertions(+), 2 deletions(-) diff --git a/include/circt/Support/FVInt.h b/include/circt/Support/FVInt.h index cb7e0e14a10c..accc4b263b8b 100644 --- a/include/circt/Support/FVInt.h +++ b/include/circt/Support/FVInt.h @@ -208,6 +208,9 @@ class FVInt { /// Compute a mask of all the Z bits in this integer. APInt getZBits() const { return value & unknown; } + /// Compute a mask of all the X and Z bits in this integer. + APInt getUnknownBits() const { return unknown; } + /// Set the value of all bits in the mask to 0. template void setZeroBits(const T &mask) { diff --git a/lib/Conversion/MooreToCore/MooreToCore.cpp b/lib/Conversion/MooreToCore/MooreToCore.cpp index b2fe1a00aa7c..e55d85332753 100644 --- a/lib/Conversion/MooreToCore/MooreToCore.cpp +++ b/lib/Conversion/MooreToCore/MooreToCore.cpp @@ -719,13 +719,57 @@ struct ICmpOpConversion : public OpConversionPattern { using OpAdaptor = typename SourceOp::Adaptor; LogicalResult - matchAndRewrite(SourceOp op, OpAdaptor adapter, + matchAndRewrite(SourceOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override { Type resultType = ConversionPattern::typeConverter->convertType(op.getResult().getType()); rewriter.replaceOpWithNewOp( - op, resultType, pred, adapter.getLhs(), adapter.getRhs()); + op, resultType, pred, adaptor.getLhs(), adaptor.getRhs()); + return success(); + } +}; + +template +struct CaseXZEqOpConversion : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + using OpAdaptor = typename SourceOp::Adaptor; + + LogicalResult + matchAndRewrite(SourceOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + // Check each operand if it is a known constant and extract the X and/or Z + // bits to be ignored. + // TODO: Once the core dialects support four-valued integers, we will have + // to create ops that extract X and Z bits from the operands, since we also + // have to do the right casez/casex comparison on non-constant inputs. + unsigned bitWidth = op.getLhs().getType().getWidth(); + auto ignoredBits = APInt::getZero(bitWidth); + auto detectIgnoredBits = [&](Value value) { + auto constOp = value.getDefiningOp(); + if (!constOp) + return; + auto constValue = constOp.getValue(); + if (withoutX) + ignoredBits |= constValue.getZBits(); + else + ignoredBits |= constValue.getUnknownBits(); + }; + detectIgnoredBits(op.getLhs()); + detectIgnoredBits(op.getRhs()); + + // If we have detected any bits to be ignored, mask them in the operands for + // the comparison. + Value lhs = adaptor.getLhs(); + Value rhs = adaptor.getRhs(); + if (!ignoredBits.isZero()) { + ignoredBits.flipAllBits(); + auto maskOp = rewriter.create(op.getLoc(), ignoredBits); + lhs = rewriter.createOrFold(op.getLoc(), lhs, maskOp); + rhs = rewriter.createOrFold(op.getLoc(), rhs, maskOp); + } + + rewriter.replaceOpWithNewOp(op, ICmpPredicate::ceq, lhs, rhs); return success(); } }; @@ -1151,6 +1195,8 @@ static void populateOpConversion(RewritePatternSet &patterns, ICmpOpConversion, ICmpOpConversion, ICmpOpConversion, + CaseXZEqOpConversion, + CaseXZEqOpConversion, // Patterns of structural operations. SVModuleOpConversion, InstanceOpConversion, ProcedureOpConversion, diff --git a/test/Conversion/MooreToCore/basic.mlir b/test/Conversion/MooreToCore/basic.mlir index 9a6bf5968dbd..a0b8f3cd39a7 100644 --- a/test/Conversion/MooreToCore/basic.mlir +++ b/test/Conversion/MooreToCore/basic.mlir @@ -552,3 +552,47 @@ moore.module @Process(in %cond : i1) { moore.return } } + +// CHECK-LABEL: func.func @CaseXZ( +func.func @CaseXZ(%arg0: !moore.l8, %arg1: !moore.l8) { + // CHECK: hw.constant -124 : i8 + // CHECK: hw.constant -120 : i8 + %0 = moore.constant b10XX01ZZ : l8 + %1 = moore.constant b1XX01ZZ0 : l8 + + // CHECK: comb.icmp ceq %arg0, %arg1 : i8 + moore.casez_eq %arg0, %arg1 : l8 + // CHECK: [[MASK:%.+]] = hw.constant -7 : i8 + // CHECK: [[TMP1:%.+]] = comb.and %arg0, [[MASK]] + // CHECK: [[TMP2:%.+]] = hw.constant -120 : i8 + // CHECK: comb.icmp ceq [[TMP1]], [[TMP2]] : i8 + moore.casez_eq %arg0, %1 : l8 + // CHECK: [[MASK:%.+]] = hw.constant -4 : i8 + // CHECK: [[TMP1:%.+]] = comb.and %arg1, [[MASK]] + // CHECK: [[TMP2:%.+]] = hw.constant -124 : i8 + // CHECK: comb.icmp ceq [[TMP1]], [[TMP2]] : i8 + moore.casez_eq %0, %arg1 : l8 + // CHECK: [[TMP1:%.+]] = hw.constant -128 : i8 + // CHECK: [[TMP2:%.+]] = hw.constant -120 : i8 + // CHECK: comb.icmp ceq [[TMP1]], [[TMP2]] : i8 + moore.casez_eq %0, %1 : l8 + + // CHECK: comb.icmp ceq %arg0, %arg1 : i8 + moore.casexz_eq %arg0, %arg1 : l8 + // CHECK: [[MASK:%.+]] = hw.constant -103 : i8 + // CHECK: [[TMP1:%.+]] = comb.and %arg0, [[MASK]] + // CHECK: [[TMP2:%.+]] = hw.constant -120 : i8 + // CHECK: comb.icmp ceq [[TMP1]], [[TMP2]] : i8 + moore.casexz_eq %arg0, %1 : l8 + // CHECK: [[MASK:%.+]] = hw.constant -52 : i8 + // CHECK: [[TMP1:%.+]] = comb.and %arg1, [[MASK]] + // CHECK: [[TMP2:%.+]] = hw.constant -124 : i8 + // CHECK: comb.icmp ceq [[TMP1]], [[TMP2]] : i8 + moore.casexz_eq %0, %arg1 : l8 + // CHECK: [[TMP1:%.+]] = hw.constant -128 : i8 + // CHECK: [[TMP2:%.+]] = hw.constant -120 : i8 + // CHECK: comb.icmp ceq [[TMP1]], [[TMP2]] : i8 + moore.casexz_eq %0, %1 : l8 + + return +} From a250818d2792cef3ae74875f4f9e1608b1ddf9c2 Mon Sep 17 00:00:00 2001 From: Martin Erhart Date: Sat, 10 Aug 2024 22:24:32 +0100 Subject: [PATCH 071/119] [LLHD] Add `llhd.delay` operation (#7505) Add an operation that delays the propagation of value changes from its input to its output. This allows mem2reg of LLHD signals with a unique driver with a delay. For example ``` %sig = llhd.sig llhd.drv %sig, %val after <1ns, 0d, 0e> %res = llhd.prb %sig ``` can become ``` %res = llhd.delay %val by <1ns, 0d, 0e> ``` --- include/circt/Dialect/LLHD/IR/SignalOps.td | 16 ++++++++++++++++ test/Dialect/LLHD/IR/basic.mlir | 9 +++++++++ test/Dialect/LLHD/IR/errors.mlir | 7 +++++++ 3 files changed, 32 insertions(+) create mode 100644 test/Dialect/LLHD/IR/basic.mlir create mode 100644 test/Dialect/LLHD/IR/errors.mlir diff --git a/include/circt/Dialect/LLHD/IR/SignalOps.td b/include/circt/Dialect/LLHD/IR/SignalOps.td index 4aa94591770d..4fd4a5323b28 100644 --- a/include/circt/Dialect/LLHD/IR/SignalOps.td +++ b/include/circt/Dialect/LLHD/IR/SignalOps.td @@ -307,3 +307,19 @@ def LLHD_RegOp : LLHD_Op<"reg", [ let hasVerifier = 1; } + +def DelayOp : LLHD_Op<"delay", [Pure, SameOperandsAndResultType]> { + let summary = "specifies value propagation delay"; + let description = [{ + This operation propagates all value changes of the input to the output after + the specified time delay. + Reference values are not supported (e.g., pointers, inout, etc.) + since the store-like operation used for those types should encode a delayed + store. + }]; + + let arguments = (ins HWNonInOutType:$input, LLHD_TimeAttr:$delay); + let results = (outs HWNonInOutType:$result); + + let assemblyFormat = "$input `by` $delay attr-dict `:` type($result)"; +} diff --git a/test/Dialect/LLHD/IR/basic.mlir b/test/Dialect/LLHD/IR/basic.mlir new file mode 100644 index 000000000000..ca9afd94bb6f --- /dev/null +++ b/test/Dialect/LLHD/IR/basic.mlir @@ -0,0 +1,9 @@ +// RUN: circt-opt %s | circt-opt | FileCheck %s + +// CHECK-LABEL: @basic +// CHECK-SAME: (in [[IN0:%.+]] : i32, out out0 : i32) +hw.module @basic(in %in0 : i32, out out0 : i32) { + // CHECK: %{{.*}} = llhd.delay [[IN0]] by <0ns, 1d, 0e> : i32 + %0 = llhd.delay %in0 by <0ns, 1d, 0e> : i32 + hw.output %0 : i32 +} diff --git a/test/Dialect/LLHD/IR/errors.mlir b/test/Dialect/LLHD/IR/errors.mlir new file mode 100644 index 000000000000..e131465385c5 --- /dev/null +++ b/test/Dialect/LLHD/IR/errors.mlir @@ -0,0 +1,7 @@ +// RUN: circt-opt %s --split-input-file --verify-diagnostics + +hw.module @errors(in %in0: i32, out out0: i8) { + // expected-error @below {{requires the same type for all operands and results}} + %0 = "llhd.delay"(%in0) {delay = #llhd.time<0ns, 1d, 0e>} : (i32) -> i8 + hw.output %0 : i8 +} From ac8c3a1ae4544038780397f3990a74b17c8c4e3f Mon Sep 17 00:00:00 2001 From: Martin Erhart Date: Sun, 11 Aug 2024 16:38:51 +0100 Subject: [PATCH 072/119] [LLHD] Clean up regression tests a bit (#7507) --- test/Dialect/LLHD/IR/basic.mlir | 212 ++++++++++++++++++ test/Dialect/LLHD/IR/connect-errors.mlir | 16 -- test/Dialect/LLHD/IR/connect.mlir | 8 - test/Dialect/LLHD/IR/errors.mlir | 117 ++++++++++ test/Dialect/LLHD/IR/extract-errors.mlir | 53 ----- test/Dialect/LLHD/IR/extract.mlir | 29 --- test/Dialect/LLHD/IR/memory-errors.mlir | 9 - test/Dialect/LLHD/IR/memory.mlir | 49 ---- test/Dialect/LLHD/IR/reg.mlir | 23 -- test/Dialect/LLHD/IR/signal-errors.mlir | 30 --- test/Dialect/LLHD/IR/signal.mlir | 67 ------ .../LLHD/IR/type-and-attribute-parsing.mlir | 14 -- test/Dialect/LLHD/IR/wait.mlir | 59 ----- 13 files changed, 329 insertions(+), 357 deletions(-) delete mode 100644 test/Dialect/LLHD/IR/connect-errors.mlir delete mode 100644 test/Dialect/LLHD/IR/connect.mlir delete mode 100644 test/Dialect/LLHD/IR/extract-errors.mlir delete mode 100644 test/Dialect/LLHD/IR/extract.mlir delete mode 100644 test/Dialect/LLHD/IR/memory-errors.mlir delete mode 100644 test/Dialect/LLHD/IR/memory.mlir delete mode 100644 test/Dialect/LLHD/IR/reg.mlir delete mode 100644 test/Dialect/LLHD/IR/signal-errors.mlir delete mode 100644 test/Dialect/LLHD/IR/signal.mlir delete mode 100644 test/Dialect/LLHD/IR/type-and-attribute-parsing.mlir delete mode 100644 test/Dialect/LLHD/IR/wait.mlir diff --git a/test/Dialect/LLHD/IR/basic.mlir b/test/Dialect/LLHD/IR/basic.mlir index ca9afd94bb6f..87bd29618d31 100644 --- a/test/Dialect/LLHD/IR/basic.mlir +++ b/test/Dialect/LLHD/IR/basic.mlir @@ -7,3 +7,215 @@ hw.module @basic(in %in0 : i32, out out0 : i32) { %0 = llhd.delay %in0 by <0ns, 1d, 0e> : i32 hw.output %0 : i32 } + +// CHECK-LABEL: @connect_ports +// CHECK-SAME: (inout %[[IN:.+]] : [[TYPE:.+]], inout %[[OUT:.+]] : [[TYPE]]) +// CHECK-NEXT: llhd.con %[[IN]], %[[OUT]] : !hw.inout<[[TYPE]]> +hw.module @connect_ports(inout %in: i32, inout %out: i32) { + llhd.con %in, %out : !hw.inout +} + +// CHECK-LABEL: @sigExtract +hw.module @sigExtract(inout %arg0 : i32, in %arg1 : i5) { + // CHECK-NEXT: %{{.*}} = llhd.sig.extract %arg0 from %arg1 : (!hw.inout) -> !hw.inout + %1 = llhd.sig.extract %arg0 from %arg1 : (!hw.inout) -> !hw.inout +} + +// CHECK-LABEL: @sigArray +hw.module @sigArray(inout %arg0 : !hw.array<5xi1>, in %arg1 : i3) { + // CHECK-NEXT: %{{.*}} = llhd.sig.array_slice %arg0 at %arg1 : (!hw.inout>) -> !hw.inout> + %0 = llhd.sig.array_slice %arg0 at %arg1 : (!hw.inout>) -> !hw.inout> + // CHECK-NEXT: %{{.*}} = llhd.sig.array_get %arg0[%arg1] : !hw.inout> + %1 = llhd.sig.array_get %arg0[%arg1] : !hw.inout> +} + +// CHECK-LABEL: @sigStructExtract +hw.module @sigStructExtract(inout %arg0 : !hw.struct) { + // CHECK-NEXT: %{{.*}} = llhd.sig.struct_extract %arg0["foo"] : !hw.inout> + %0 = llhd.sig.struct_extract %arg0["foo"] : !hw.inout> + // CHECK-NEXT: %{{.*}} = llhd.sig.struct_extract %arg0["baz"] : !hw.inout> + %1 = llhd.sig.struct_extract %arg0["baz"] : !hw.inout> +} + +// CHECK-LABEL: @check_var +// CHECK-SAME: %[[INT:.*]]: i32 +// CHECK-SAME: %[[ARRAY:.*]]: !hw.array<3xi1> +// CHECK-SAME: %[[TUP:.*]]: !hw.struct +func.func @check_var(%int : i32, %array : !hw.array<3xi1>, %tup : !hw.struct) { + // CHECK-NEXT: %{{.*}} = llhd.var %[[INT]] : i32 + %0 = llhd.var %int : i32 + // CHECK-NEXT: %{{.*}} = llhd.var %[[ARRAY]] : !hw.array<3xi1> + %1 = llhd.var %array : !hw.array<3xi1> + // CHECK-NEXT: %{{.*}} = llhd.var %[[TUP]] : !hw.struct + %2 = llhd.var %tup : !hw.struct + + return +} + +// CHECK-LABEL: @check_load +// CHECK-SAME: %[[INT:.*]]: !llhd.ptr +// CHECK-SAME: %[[ARRAY:.*]]: !llhd.ptr> +// CHECK-SAME: %[[TUP:.*]]: !llhd.ptr> +func.func @check_load(%int : !llhd.ptr, %array : !llhd.ptr>, %tup : !llhd.ptr>) { + // CHECK-NEXT: %{{.*}} = llhd.load %[[INT]] : !llhd.ptr + %0 = llhd.load %int : !llhd.ptr + // CHECK-NEXT: %{{.*}} = llhd.load %[[ARRAY]] : !llhd.ptr> + %1 = llhd.load %array : !llhd.ptr> + // CHECK-NEXT: %{{.*}} = llhd.load %[[TUP]] : !llhd.ptr> + %2 = llhd.load %tup : !llhd.ptr> + + return + +} + +// CHECK-LABEL: @check_store +// CHECK-SAME: %[[INT:.*]]: !llhd.ptr +// CHECK-SAME: %[[INTC:.*]]: i32 +// CHECK-SAME: %[[ARRAY:.*]]: !llhd.ptr> +// CHECK-SAME: %[[ARRAYC:.*]]: !hw.array<3xi1> +// CHECK-SAME: %[[TUP:.*]]: !llhd.ptr> +// CHECK-SAME: %[[TUPC:.*]]: !hw.struct +func.func @check_store(%int : !llhd.ptr, %intC : i32 , %array : !llhd.ptr>, %arrayC : !hw.array<3xi1>, %tup : !llhd.ptr>, %tupC : !hw.struct) { + // CHECK-NEXT: llhd.store %[[INT]], %[[INTC]] : !llhd.ptr + llhd.store %int, %intC : !llhd.ptr + // CHECK-NEXT: llhd.store %[[ARRAY]], %[[ARRAYC]] : !llhd.ptr> + llhd.store %array, %arrayC : !llhd.ptr> + // CHECK-NEXT: llhd.store %[[TUP]], %[[TUPC]] : !llhd.ptr> + llhd.store %tup, %tupC : !llhd.ptr> + + return +} + +// CHECK-LABEL: @checkSigInst +hw.module @checkSigInst() { + // CHECK: %[[CI1:.*]] = hw.constant + %cI1 = hw.constant 0 : i1 + // CHECK-NEXT: %{{.*}} = llhd.sig "sigI1" %[[CI1]] : i1 + %sigI1 = llhd.sig "sigI1" %cI1 : i1 + // CHECK-NEXT: %[[CI64:.*]] = hw.constant + %cI64 = hw.constant 0 : i64 + // CHECK-NEXT: %{{.*}} = llhd.sig "sigI64" %[[CI64]] : i64 + %sigI64 = llhd.sig "sigI64" %cI64 : i64 + + // CHECK-NEXT: %[[TUP:.*]] = hw.struct_create + %tup = hw.struct_create (%cI1, %cI64) : !hw.struct + // CHECK-NEXT: %{{.*}} = llhd.sig "sigTup" %[[TUP]] : !hw.struct + %sigTup = llhd.sig "sigTup" %tup : !hw.struct + + // CHECK-NEXT: %[[ARRAY:.*]] = hw.array_create + %array = hw.array_create %cI1, %cI1 : i1 + // CHECK-NEXT: %{{.*}} = llhd.sig "sigArray" %[[ARRAY]] : !hw.array<2xi1> + %sigArray = llhd.sig "sigArray" %array : !hw.array<2xi1> +} + +// CHECK-LABEL: @checkPrb +hw.module @checkPrb(inout %arg0 : i1, inout %arg1 : i64, inout %arg2 : !hw.array<3xi8>, inout %arg3 : !hw.struct) { + // CHECK: %{{.*}} = llhd.prb %arg0 : !hw.inout + %0 = llhd.prb %arg0 : !hw.inout + // CHECK-NEXT: %{{.*}} = llhd.prb %arg1 : !hw.inout + %1 = llhd.prb %arg1 : !hw.inout + // CHECK-NEXT: %{{.*}} = llhd.prb %arg2 : !hw.inout> + %2 = llhd.prb %arg2 : !hw.inout> + // CHECK-NEXT: %{{.*}} = llhd.prb %arg3 : !hw.inout> + %3 = llhd.prb %arg3 : !hw.inout> +} + +// CHECK-LABEL: @checkOutput +hw.module @checkOutput(in %arg0 : i32) { + %t = llhd.constant_time <0ns, 1d, 0e> + // CHECK: %{{.+}} = llhd.output %arg0 after %{{.*}} : i32 + %0 = llhd.output %arg0 after %t : i32 + // CHECK-NEXT: %{{.+}} = llhd.output "sigName" %arg0 after %{{.*}} : i32 + %1 = llhd.output "sigName" %arg0 after %t : i32 +} + +// CHECK-LABEL: @checkDrv +hw.module @checkDrv(inout %arg0 : i1, inout %arg1 : i64, in %arg2 : i1, + in %arg3 : i64, inout %arg5 : !hw.array<3xi8>, + inout %arg6 : !hw.struct, + in %arg7 : !hw.array<3xi8>, in %arg8 : !hw.struct) { + + %t = llhd.constant_time <0ns, 1d, 0e> + // CHECK: llhd.drv %arg0, %arg2 after %{{.*}} : !hw.inout + llhd.drv %arg0, %arg2 after %t : !hw.inout + // CHECK-NEXT: llhd.drv %arg1, %arg3 after %{{.*}} : !hw.inout + llhd.drv %arg1, %arg3 after %t : !hw.inout + // CHECK-NEXT: llhd.drv %arg1, %arg3 after %{{.*}} if %arg2 : !hw.inout + llhd.drv %arg1, %arg3 after %t if %arg2 : !hw.inout + // CHECK-NEXT: llhd.drv %arg5, %arg7 after %{{.*}} : !hw.inout> + llhd.drv %arg5, %arg7 after %t : !hw.inout> + // CHECK-NEXT: llhd.drv %arg6, %arg8 after %{{.*}} : !hw.inout> + llhd.drv %arg6, %arg8 after %t : !hw.inout> +} + +// CHECK-LABEL: @check_wait_0 +hw.module @check_wait_0 () { + // CHECK-NEXT: llhd.process + llhd.process { + // CHECK: llhd.wait ^[[BB:.*]] + llhd.wait ^bb1 + // CHECK-NEXT: ^[[BB]] + ^bb1: + llhd.halt + } +} + +// CHECK-LABEL: @check_wait_1 +hw.module @check_wait_1 () { + // CHECK-NEXT: llhd.process + llhd.process { + // CHECK-NEXT: %[[TIME:.*]] = llhd.constant_time + %time = llhd.constant_time #llhd.time<0ns, 0d, 0e> + // CHECK-NEXT: llhd.wait for %[[TIME]], ^[[BB:.*]](%[[TIME]] : !llhd.time) + llhd.wait for %time, ^bb1(%time: !llhd.time) + // CHECK-NEXT: ^[[BB]](%[[T:.*]]: !llhd.time): + ^bb1(%t: !llhd.time): + llhd.halt + } +} + +// CHECK: @check_wait_2(inout %[[ARG0:.*]] : i64, inout %[[ARG1:.*]] : i1) { +hw.module @check_wait_2 (inout %arg0 : i64, inout %arg1 : i1) { + // CHECK-NEXT: llhd.process + llhd.process { + // CHECK-NEXT: llhd.wait (%[[ARG0]], %[[ARG1]] : !hw.inout, !hw.inout), ^[[BB:.*]](%[[ARG1]] : !hw.inout) + llhd.wait (%arg0, %arg1 : !hw.inout, !hw.inout), ^bb1(%arg1 : !hw.inout) + // CHECK: ^[[BB]](%[[A:.*]]: !hw.inout): + ^bb1(%a: !hw.inout): + llhd.halt + } +} + +// CHECK: hw.module @check_wait_3(inout %[[ARG0:.*]] : i64, inout %[[ARG1:.*]] : i1) { +hw.module @check_wait_3 (inout %arg0 : i64, inout %arg1 : i1) { + // CHECK-NEXT: llhd.process + llhd.process { + // CHECK-NEXT: %[[TIME:.*]] = llhd.constant_time + %time = llhd.constant_time #llhd.time<0ns, 0d, 0e> + // CHECK-NEXT: llhd.wait for %[[TIME]], (%[[ARG0]], %[[ARG1]] : !hw.inout, !hw.inout), ^[[BB:.*]](%[[ARG1]], %[[ARG0]] : !hw.inout, !hw.inout) + llhd.wait for %time, (%arg0, %arg1 : !hw.inout, !hw.inout), ^bb1(%arg1, %arg0 : !hw.inout, !hw.inout) + // CHECK: ^[[BB]](%[[A:.*]]: !hw.inout, %[[B:.*]]: !hw.inout): + ^bb1(%a: !hw.inout, %b: !hw.inout): + llhd.halt + } +} + +// CHECK-LABEL: @check_reg +// CHECK-SAME: %[[IN64:.*]] : i64 +hw.module @check_reg(inout %in64 : i64) { + // CHECK: %[[C1:.*]] = hw.constant + %c1 = hw.constant 0 : i1 + // CHECK-NEXT: %[[C64:.*]] = hw.constant + %c64 = hw.constant 0 : i64 + // CHECK-NEXT: %[[TIME:.*]] = llhd.constant_time + %time = llhd.constant_time #llhd.time<1ns, 0d, 0e> + // one trigger with optional gate + // CHECK-NEXT: llhd.reg %[[IN64]], (%[[C64]], "low" %[[C1]] after %[[TIME]] if %[[C1]] : i64) : !hw.inout + "llhd.reg"(%in64, %c64, %c1, %time, %c1) {modes=[0], gateMask=[1], operandSegmentSizes=array} : (!hw.inout, i64, i1, !llhd.time, i1) -> () + // two triggers with optional gates + // CHECK-NEXT: llhd.reg %[[IN64]], (%[[C64]], "low" %[[C1]] after %[[TIME]] if %[[C1]] : i64), (%[[IN64]], "high" %[[C1]] after %[[TIME]] if %[[C1]] : !hw.inout) : !hw.inout + "llhd.reg"(%in64, %c64, %in64, %c1, %c1, %time, %time, %c1, %c1) {modes=[0,1], gateMask=[1,2], operandSegmentSizes=array} : (!hw.inout, i64, !hw.inout, i1, i1, !llhd.time, !llhd.time, i1, i1) -> () + // two triggers with only one optional gate + // CHECK-NEXT: llhd.reg %[[IN64]], (%[[C64]], "low" %[[C1]] after %[[TIME]] : i64), (%[[IN64]], "high" %[[C1]] after %[[TIME]] if %[[C1]] : !hw.inout) : !hw.inout + "llhd.reg"(%in64, %c64, %in64, %c1, %c1, %time, %time, %c1) {modes=[0,1], gateMask=[0,1], operandSegmentSizes=array} : (!hw.inout, i64, !hw.inout, i1, i1, !llhd.time, !llhd.time, i1) -> () +} diff --git a/test/Dialect/LLHD/IR/connect-errors.mlir b/test/Dialect/LLHD/IR/connect-errors.mlir deleted file mode 100644 index fe80231cb4f9..000000000000 --- a/test/Dialect/LLHD/IR/connect-errors.mlir +++ /dev/null @@ -1,16 +0,0 @@ -// RUN: circt-opt %s -split-input-file -verify-diagnostics - -// expected-note @+1 {{prior use here}} -hw.module @connect_different_types(inout %in: i8, inout %out: i32) { - // expected-error @+1 {{use of value '%out' expects different type}} - llhd.con %in, %out : !hw.inout -} - -// ----- - -hw.module @connect_non_signals(inout %in: i32, inout %out: i32) { - %0 = llhd.prb %in : !hw.inout - %1 = llhd.prb %out : !hw.inout - // expected-error @+1 {{'llhd.con' op operand #0 must be InOutType, but got 'i32'}} - llhd.con %0, %1 : i32 -} diff --git a/test/Dialect/LLHD/IR/connect.mlir b/test/Dialect/LLHD/IR/connect.mlir deleted file mode 100644 index 9ffe5a77a833..000000000000 --- a/test/Dialect/LLHD/IR/connect.mlir +++ /dev/null @@ -1,8 +0,0 @@ -// RUN: circt-opt %s -split-input-file | FileCheck %s - -// CHECK-LABEL: @connect_ports -// CHECK-SAME: (inout %[[IN:.+]] : [[TYPE:.+]], inout %[[OUT:.+]] : [[TYPE]]) -// CHECK-NEXT: llhd.con %[[IN]], %[[OUT]] : !hw.inout<[[TYPE]]> -hw.module @connect_ports(inout %in: i32, inout %out: i32) { - llhd.con %in, %out : !hw.inout -} diff --git a/test/Dialect/LLHD/IR/errors.mlir b/test/Dialect/LLHD/IR/errors.mlir index e131465385c5..75a1a431c735 100644 --- a/test/Dialect/LLHD/IR/errors.mlir +++ b/test/Dialect/LLHD/IR/errors.mlir @@ -5,3 +5,120 @@ hw.module @errors(in %in0: i32, out out0: i8) { %0 = "llhd.delay"(%in0) {delay = #llhd.time<0ns, 1d, 0e>} : (i32) -> i8 hw.output %0 : i8 } + +// ----- + +// expected-note @+1 {{prior use here}} +hw.module @connect_different_types(inout %in: i8, inout %out: i32) { + // expected-error @+1 {{use of value '%out' expects different type}} + llhd.con %in, %out : !hw.inout +} + +// ----- + +hw.module @connect_non_signals(inout %in: i32, inout %out: i32) { + %0 = llhd.prb %in : !hw.inout + %1 = llhd.prb %out : !hw.inout + // expected-error @+1 {{'llhd.con' op operand #0 must be InOutType, but got 'i32'}} + llhd.con %0, %1 : i32 +} + +// ----- + +hw.module @illegal_signal_to_array(inout %sig : !hw.array<3xi32>, in %ind : i2) { + // expected-error @+1 {{'llhd.sig.array_slice' op result #0 must be InOutType of an ArrayType values, but got '!hw.array<3xi32>'}} + %0 = llhd.sig.array_slice %sig at %ind : (!hw.inout>) -> !hw.array<3xi32> +} + +// ----- + +hw.module @illegal_array_element_type_mismatch(inout %sig : !hw.array<3xi32>, in %ind : i2) { + // expected-error @+1 {{arrays element type must match}} + %0 = llhd.sig.array_slice %sig at %ind : (!hw.inout>) -> !hw.inout> +} + +// ----- + +hw.module @illegal_result_array_too_big(inout %sig : !hw.array<3xi32>, in %ind : i2) { + // expected-error @+1 {{width of result type has to be smaller than or equal to the input type}} + %0 = llhd.sig.array_slice %sig at %ind : (!hw.inout>) -> !hw.inout> +} + +// ----- + +hw.module @illegal_sig_to_int(inout %s : i32, in %ind : i5) { + // expected-error @+1 {{'llhd.sig.extract' op result #0 must be InOutType of a signless integer bitvector values, but got 'i10'}} + %0 = llhd.sig.extract %s from %ind : (!hw.inout) -> i10 +} + +// ----- + +hw.module @illegal_sig_to_int_to_wide(inout %s : i32, in %ind : i5) { + // expected-error @+1 {{width of result type has to be smaller than or equal to the input type}} + %0 = llhd.sig.extract %s from %ind : (!hw.inout) -> !hw.inout +} + +// ----- + +hw.module @extract_element_tuple_index_out_of_bounds(inout %tup : !hw.struct) { + // expected-error @+1 {{invalid field name specified}} + %0 = llhd.sig.struct_extract %tup["foobar"] : !hw.inout> +} + +// ----- + +// expected-note @+1 {{prior use here}} +func.func @check_illegal_store(%i1Ptr : !llhd.ptr, %i32Const : i32) { + // expected-error @+1 {{use of value '%i32Const' expects different type than prior uses: 'i1' vs 'i32'}} + llhd.store %i1Ptr, %i32Const : !llhd.ptr + + return +} + +// ----- + +// expected-error @+1 {{unknown type `illegaltype` in dialect `llhd`}} +func.func @illegaltype(%arg0: !llhd.illegaltype) { + return +} + +// ----- + +// expected-error @+2 {{unknown attribute `illegalattr` in dialect `llhd`}} +func.func @illegalattr() { + %0 = llhd.constant_time #llhd.illegalattr : i1 + return +} + +// ----- + +// expected-error @+3 {{failed to verify that type of 'init' and underlying type of 'signal' have to match.}} +hw.module @check_illegal_sig() { + %cI1 = hw.constant 0 : i1 + %sig1 = "llhd.sig"(%cI1) {name="foo"} : (i1) -> !hw.inout +} + +// ----- + +// expected-error @+2 {{failed to verify that type of 'result' and underlying type of 'signal' have to match.}} +hw.module @check_illegal_prb(inout %sig : i1) { + %prb = "llhd.prb"(%sig) {} : (!hw.inout) -> i32 +} + +// ----- + +// expected-error @+4 {{failed to verify that type of 'value' and underlying type of 'signal' have to match.}} +hw.module @check_illegal_drv(inout %sig : i1) { + %c = hw.constant 0 : i32 + %time = llhd.constant_time #llhd.time<1ns, 0d, 0e> + "llhd.drv"(%sig, %c, %time) {} : (!hw.inout, i32, !llhd.time) -> () +} + +// ----- + +func.func @illegal_sig_parent(%arg0 : i1) { + // expected-error @+1 {{expects parent op to be one of 'hw.module, llhd.process'}} + %0 = llhd.sig "sig" %arg0 : i1 + + return +} diff --git a/test/Dialect/LLHD/IR/extract-errors.mlir b/test/Dialect/LLHD/IR/extract-errors.mlir deleted file mode 100644 index f4378b6db712..000000000000 --- a/test/Dialect/LLHD/IR/extract-errors.mlir +++ /dev/null @@ -1,53 +0,0 @@ -// RUN: circt-opt %s -mlir-print-op-generic -split-input-file -verify-diagnostics - -func.func @illegal_signal_to_array(%sig : !hw.inout>, %ind: i2) { - // expected-error @+1 {{'llhd.sig.array_slice' op result #0 must be InOutType of an ArrayType values, but got '!hw.array<3xi32>'}} - %0 = llhd.sig.array_slice %sig at %ind : (!hw.inout>) -> !hw.array<3xi32> - - return -} - -// ----- - -func.func @illegal_array_element_type_mismatch(%sig : !hw.inout>, %ind: i2) { - // expected-error @+1 {{arrays element type must match}} - %0 = llhd.sig.array_slice %sig at %ind : (!hw.inout>) -> !hw.inout> - - return -} - -// ----- - -func.func @illegal_result_array_too_big(%sig : !hw.inout>, %ind: i2) { - // expected-error @+1 {{width of result type has to be smaller than or equal to the input type}} - %0 = llhd.sig.array_slice %sig at %ind : (!hw.inout>) -> !hw.inout> - - return -} - -// ----- - -func.func @illegal_sig_to_int(%s : !hw.inout, %ind: i5) { - // expected-error @+1 {{'llhd.sig.extract' op result #0 must be InOutType of a signless integer bitvector values, but got 'i10'}} - %0 = llhd.sig.extract %s from %ind : (!hw.inout) -> i10 - - return -} - -// ----- - -func.func @illegal_sig_to_int_to_wide(%s : !hw.inout, %ind: i5) { - // expected-error @+1 {{width of result type has to be smaller than or equal to the input type}} - %0 = llhd.sig.extract %s from %ind : (!hw.inout) -> !hw.inout - - return -} - -// ----- - -func.func @extract_element_tuple_index_out_of_bounds(%tup : !hw.inout>) { - // expected-error @+1 {{invalid field name specified}} - %0 = llhd.sig.struct_extract %tup["foobar"] : !hw.inout> - - return -} diff --git a/test/Dialect/LLHD/IR/extract.mlir b/test/Dialect/LLHD/IR/extract.mlir deleted file mode 100644 index 3f9eab31023e..000000000000 --- a/test/Dialect/LLHD/IR/extract.mlir +++ /dev/null @@ -1,29 +0,0 @@ -// RUN: circt-opt %s -mlir-print-op-generic -split-input-file -verify-diagnostics | circt-opt | circt-opt | FileCheck %s - -// CHECK-LABEL: @sigExtract -func.func @sigExtract (%arg0 : !hw.inout, %arg1: i5) -> () { - // CHECK-NEXT: %{{.*}} = llhd.sig.extract %arg0 from %arg1 : (!hw.inout) -> !hw.inout - %1 = llhd.sig.extract %arg0 from %arg1 : (!hw.inout) -> !hw.inout - - return -} - -// CHECK-LABEL: @sigArray -func.func @sigArray (%arg0 : !hw.inout>, %arg1: i3) -> () { - // CHECK-NEXT: %{{.*}} = llhd.sig.array_slice %arg0 at %arg1 : (!hw.inout>) -> !hw.inout> - %0 = llhd.sig.array_slice %arg0 at %arg1 : (!hw.inout>) -> !hw.inout> - // CHECK-NEXT: %{{.*}} = llhd.sig.array_get %arg0[%arg1] : !hw.inout> - %1 = llhd.sig.array_get %arg0[%arg1] : !hw.inout> - - return -} - -// CHECK-LABEL: @sigStructExtract -func.func @sigStructExtract(%arg0 : !hw.inout>) { - // CHECK-NEXT: %{{.*}} = llhd.sig.struct_extract %arg0["foo"] : !hw.inout> - %0 = llhd.sig.struct_extract %arg0["foo"] : !hw.inout> - // CHECK-NEXT: %{{.*}} = llhd.sig.struct_extract %arg0["baz"] : !hw.inout> - %1 = llhd.sig.struct_extract %arg0["baz"] : !hw.inout> - - return -} diff --git a/test/Dialect/LLHD/IR/memory-errors.mlir b/test/Dialect/LLHD/IR/memory-errors.mlir deleted file mode 100644 index e836b69d6eee..000000000000 --- a/test/Dialect/LLHD/IR/memory-errors.mlir +++ /dev/null @@ -1,9 +0,0 @@ -// RUN: circt-opt %s -mlir-print-op-generic -split-input-file -verify-diagnostics - -// expected-note @+1 {{prior use here}} -func.func @check_illegal_store(%i1Ptr : !llhd.ptr, %i32Const : i32) { - // expected-error @+1 {{use of value '%i32Const' expects different type than prior uses: 'i1' vs 'i32'}} - llhd.store %i1Ptr, %i32Const : !llhd.ptr - - return -} diff --git a/test/Dialect/LLHD/IR/memory.mlir b/test/Dialect/LLHD/IR/memory.mlir deleted file mode 100644 index d4a6226a9f44..000000000000 --- a/test/Dialect/LLHD/IR/memory.mlir +++ /dev/null @@ -1,49 +0,0 @@ -// RUN: circt-opt %s -mlir-print-op-generic -split-input-file -verify-diagnostics | circt-opt | circt-opt | FileCheck %s - -// CHECK-LABEL: @check_var -// CHECK-SAME: %[[INT:.*]]: i32 -// CHECK-SAME: %[[ARRAY:.*]]: !hw.array<3xi1> -// CHECK-SAME: %[[TUP:.*]]: !hw.struct -func.func @check_var(%int : i32, %array : !hw.array<3xi1>, %tup : !hw.struct) { - // CHECK-NEXT: %{{.*}} = llhd.var %[[INT]] : i32 - %0 = llhd.var %int : i32 - // CHECK-NEXT: %{{.*}} = llhd.var %[[ARRAY]] : !hw.array<3xi1> - %1 = llhd.var %array : !hw.array<3xi1> - // CHECK-NEXT: %{{.*}} = llhd.var %[[TUP]] : !hw.struct - %2 = llhd.var %tup : !hw.struct - - return -} - -// CHECK-LABEL: @check_load -// CHECK-SAME: %[[INT:.*]]: !llhd.ptr -// CHECK-SAME: %[[ARRAY:.*]]: !llhd.ptr> -// CHECK-SAME: %[[TUP:.*]]: !llhd.ptr> -func.func @check_load(%int : !llhd.ptr, %array : !llhd.ptr>, %tup : !llhd.ptr>) { - // CHECK-NEXT: %{{.*}} = llhd.load %[[INT]] : !llhd.ptr - %0 = llhd.load %int : !llhd.ptr - // CHECK-NEXT: %{{.*}} = llhd.load %[[ARRAY]] : !llhd.ptr> - %1 = llhd.load %array : !llhd.ptr> - // CHECK-NEXT: %{{.*}} = llhd.load %[[TUP]] : !llhd.ptr> - %2 = llhd.load %tup : !llhd.ptr> - - return - -} -// CHECK-LABEL: @check_store -// CHECK-SAME: %[[INT:.*]]: !llhd.ptr -// CHECK-SAME: %[[INTC:.*]]: i32 -// CHECK-SAME: %[[ARRAY:.*]]: !llhd.ptr> -// CHECK-SAME: %[[ARRAYC:.*]]: !hw.array<3xi1> -// CHECK-SAME: %[[TUP:.*]]: !llhd.ptr> -// CHECK-SAME: %[[TUPC:.*]]: !hw.struct -func.func @check_store(%int : !llhd.ptr, %intC : i32 , %array : !llhd.ptr>, %arrayC : !hw.array<3xi1>, %tup : !llhd.ptr>, %tupC : !hw.struct) { - // CHECK-NEXT: llhd.store %[[INT]], %[[INTC]] : !llhd.ptr - llhd.store %int, %intC : !llhd.ptr - // CHECK-NEXT: llhd.store %[[ARRAY]], %[[ARRAYC]] : !llhd.ptr> - llhd.store %array, %arrayC : !llhd.ptr> - // CHECK-NEXT: llhd.store %[[TUP]], %[[TUPC]] : !llhd.ptr> - llhd.store %tup, %tupC : !llhd.ptr> - - return -} diff --git a/test/Dialect/LLHD/IR/reg.mlir b/test/Dialect/LLHD/IR/reg.mlir deleted file mode 100644 index 2f43a21dd7de..000000000000 --- a/test/Dialect/LLHD/IR/reg.mlir +++ /dev/null @@ -1,23 +0,0 @@ -// RUN: circt-opt %s -split-input-file -verify-diagnostics | circt-opt | FileCheck %s - -// CHECK-LABEL: @check_reg -// CHECK-SAME: %[[IN64:.*]] : i64 -hw.module @check_reg(inout %in64 : i64) { - // CHECK: %[[C1:.*]] = hw.constant - %c1 = hw.constant 0 : i1 - // CHECK-NEXT: %[[C64:.*]] = hw.constant - %c64 = hw.constant 0 : i64 - // CHECK-NEXT: %[[TIME:.*]] = llhd.constant_time - %time = llhd.constant_time #llhd.time<1ns, 0d, 0e> - // one trigger with optional gate - // CHECK-NEXT: llhd.reg %[[IN64]], (%[[C64]], "low" %[[C1]] after %[[TIME]] if %[[C1]] : i64) : !hw.inout - "llhd.reg"(%in64, %c64, %c1, %time, %c1) {modes=[0], gateMask=[1], operandSegmentSizes=array} : (!hw.inout, i64, i1, !llhd.time, i1) -> () - // two triggers with optional gates - // CHECK-NEXT: llhd.reg %[[IN64]], (%[[C64]], "low" %[[C1]] after %[[TIME]] if %[[C1]] : i64), (%[[IN64]], "high" %[[C1]] after %[[TIME]] if %[[C1]] : !hw.inout) : !hw.inout - "llhd.reg"(%in64, %c64, %in64, %c1, %c1, %time, %time, %c1, %c1) {modes=[0,1], gateMask=[1,2], operandSegmentSizes=array} : (!hw.inout, i64, !hw.inout, i1, i1, !llhd.time, !llhd.time, i1, i1) -> () - // two triggers with only one optional gate - // CHECK-NEXT: llhd.reg %[[IN64]], (%[[C64]], "low" %[[C1]] after %[[TIME]] : i64), (%[[IN64]], "high" %[[C1]] after %[[TIME]] if %[[C1]] : !hw.inout) : !hw.inout - "llhd.reg"(%in64, %c64, %in64, %c1, %c1, %time, %time, %c1) {modes=[0,1], gateMask=[0,1], operandSegmentSizes=array} : (!hw.inout, i64, !hw.inout, i1, i1, !llhd.time, !llhd.time, i1) -> () -} - -// TODO: add verification tests inout reg-errors.mlir (expected-error tests) diff --git a/test/Dialect/LLHD/IR/signal-errors.mlir b/test/Dialect/LLHD/IR/signal-errors.mlir deleted file mode 100644 index b2ded8d52c8f..000000000000 --- a/test/Dialect/LLHD/IR/signal-errors.mlir +++ /dev/null @@ -1,30 +0,0 @@ -// RUN: circt-opt %s -mlir-print-op-generic -split-input-file -verify-diagnostics - -// expected-error @+3 {{failed to verify that type of 'init' and underlying type of 'signal' have to match.}} -hw.module @check_illegal_sig() { - %cI1 = hw.constant 0 : i1 - %sig1 = "llhd.sig"(%cI1) {name="foo"} : (i1) -> !hw.inout -} - -// ----- - -// expected-error @+2 {{failed to verify that type of 'result' and underlying type of 'signal' have to match.}} -hw.module @check_illegal_prb(inout %sig : i1) { - %prb = "llhd.prb"(%sig) {} : (!hw.inout) -> i32 -} - -// ----- - -// expected-error @+4 {{failed to verify that type of 'value' and underlying type of 'signal' have to match.}} -hw.module @check_illegal_drv(inout %sig : i1) { - %c = hw.constant 0 : i32 - %time = llhd.constant_time #llhd.time<1ns, 0d, 0e> - "llhd.drv"(%sig, %c, %time) {} : (!hw.inout, i32, !llhd.time) -> () -} - -// ----- - -func.func @illegal_sig_parent (%arg0: i1) { - // expected-error @+1 {{expects parent op to be one of 'hw.module, llhd.process'}} - %0 = llhd.sig "sig" %arg0 : i1 -} diff --git a/test/Dialect/LLHD/IR/signal.mlir b/test/Dialect/LLHD/IR/signal.mlir deleted file mode 100644 index 8d7590ad76fa..000000000000 --- a/test/Dialect/LLHD/IR/signal.mlir +++ /dev/null @@ -1,67 +0,0 @@ -// RUN: circt-opt %s -mlir-print-op-generic -split-input-file -verify-diagnostics | circt-opt | circt-opt | FileCheck %s - -// CHECK-LABEL: checkSigInst -hw.module @checkSigInst() { - // CHECK: %[[CI1:.*]] = hw.constant - %cI1 = hw.constant 0 : i1 - // CHECK-NEXT: %{{.*}} = llhd.sig "sigI1" %[[CI1]] : i1 - %sigI1 = llhd.sig "sigI1" %cI1 : i1 - // CHECK-NEXT: %[[CI64:.*]] = hw.constant - %cI64 = hw.constant 0 : i64 - // CHECK-NEXT: %{{.*}} = llhd.sig "sigI64" %[[CI64]] : i64 - %sigI64 = llhd.sig "sigI64" %cI64 : i64 - - // CHECK-NEXT: %[[TUP:.*]] = hw.struct_create - %tup = hw.struct_create (%cI1, %cI64) : !hw.struct - // CHECK-NEXT: %{{.*}} = llhd.sig "sigTup" %[[TUP]] : !hw.struct - %sigTup = llhd.sig "sigTup" %tup : !hw.struct - - // CHECK-NEXT: %[[ARRAY:.*]] = hw.array_create - %array = hw.array_create %cI1, %cI1 : i1 - // CHECK-NEXT: %{{.*}} = llhd.sig "sigArray" %[[ARRAY]] : !hw.array<2xi1> - %sigArray = llhd.sig "sigArray" %array : !hw.array<2xi1> -} - -// CHECK-LABEL: checkPrb -func.func @checkPrb(%arg0 : !hw.inout, %arg1 : !hw.inout, %arg2 : !hw.inout>, %arg3 : !hw.inout>) { - // CHECK: %{{.*}} = llhd.prb %arg0 : !hw.inout - %0 = llhd.prb %arg0 : !hw.inout - // CHECK-NEXT: %{{.*}} = llhd.prb %arg1 : !hw.inout - %1 = llhd.prb %arg1 : !hw.inout - // CHECK-NEXT: %{{.*}} = llhd.prb %arg2 : !hw.inout> - %2 = llhd.prb %arg2 : !hw.inout> - // CHECK-NEXT: %{{.*}} = llhd.prb %arg3 : !hw.inout> - %3 = llhd.prb %arg3 : !hw.inout> - - return -} - -// CHECK-LABEL: checkOutput -func.func @checkOutput(%arg0: i32, %arg1: !llhd.time) { - // CHECK-NEXT: %{{.+}} = llhd.output %arg0 after %arg1 : i32 - %0 = llhd.output %arg0 after %arg1 : i32 - // CHECK-NEXT: %{{.+}} = llhd.output "sigName" %arg0 after %arg1 : i32 - %1 = llhd.output "sigName" %arg0 after %arg1 : i32 - - return -} - -// CHECK-LABEL: checkDrv -func.func @checkDrv(%arg0 : !hw.inout, %arg1 : !hw.inout, %arg2 : i1, - %arg3 : i64, %arg4 : !llhd.time, %arg5 : !hw.inout>, - %arg6 : !hw.inout>, - %arg7 : !hw.array<3xi8>, %arg8 : !hw.struct) { - - // CHECK-NEXT: llhd.drv %arg0, %arg2 after %arg4 : !hw.inout - llhd.drv %arg0, %arg2 after %arg4 : !hw.inout - // CHECK-NEXT: llhd.drv %arg1, %arg3 after %arg4 : !hw.inout - llhd.drv %arg1, %arg3 after %arg4 : !hw.inout - // CHECK-NEXT: llhd.drv %arg1, %arg3 after %arg4 if %arg2 : !hw.inout - llhd.drv %arg1, %arg3 after %arg4 if %arg2 : !hw.inout - // CHECK-NEXT: llhd.drv %arg5, %arg7 after %arg4 : !hw.inout> - llhd.drv %arg5, %arg7 after %arg4 : !hw.inout> - // CHECK-NEXT: llhd.drv %arg6, %arg8 after %arg4 : !hw.inout> - llhd.drv %arg6, %arg8 after %arg4 : !hw.inout> - - return -} diff --git a/test/Dialect/LLHD/IR/type-and-attribute-parsing.mlir b/test/Dialect/LLHD/IR/type-and-attribute-parsing.mlir deleted file mode 100644 index 3f795c74a6aa..000000000000 --- a/test/Dialect/LLHD/IR/type-and-attribute-parsing.mlir +++ /dev/null @@ -1,14 +0,0 @@ -// RUN: circt-opt %s --split-input-file --verify-diagnostics - -// expected-error @+1 {{unknown type `illegaltype` in dialect `llhd`}} -func.func @illegaltype(%arg0: !llhd.illegaltype) { - return -} - -// ----- - -// expected-error @+2 {{unknown attribute `illegalattr` in dialect `llhd`}} -func.func @illegalattr() { - %0 = llhd.constant_time #llhd.illegalattr : i1 - return -} diff --git a/test/Dialect/LLHD/IR/wait.mlir b/test/Dialect/LLHD/IR/wait.mlir deleted file mode 100644 index d512e81a0f72..000000000000 --- a/test/Dialect/LLHD/IR/wait.mlir +++ /dev/null @@ -1,59 +0,0 @@ -// RUN: circt-opt %s | circt-opt | FileCheck %s - -// Test Overview: -// * 0 observed signals, no time, successor without arguments -// * 0 observed signals, with time, sucessor with arguments -// * 2 observed signals, no time, successor with arguments -// * 2 observed signals, with time, successor with arguments - -// CHECK-LABEL: @check_wait_0 -hw.module @check_wait_0 () { - // CHECK-NEXT: llhd.process - llhd.process { - // CHECK: llhd.wait ^[[BB:.*]] - "llhd.wait"() [^bb1] {operandSegmentSizes=array} : () -> () - // CHECK-NEXT: ^[[BB]] - ^bb1: - llhd.halt - } -} - -// CHECK-LABEL: @check_wait_1 -hw.module @check_wait_1 () { - // CHECK-NEXT: llhd.process - llhd.process { - // CHECK-NEXT: %[[TIME:.*]] = llhd.constant_time - %time = llhd.constant_time #llhd.time<0ns, 0d, 0e> - // CHECK-NEXT: llhd.wait for %[[TIME]], ^[[BB:.*]](%[[TIME]] : !llhd.time) - "llhd.wait"(%time, %time) [^bb1] {operandSegmentSizes=array} : (!llhd.time, !llhd.time) -> () - // CHECK-NEXT: ^[[BB]](%[[T:.*]]: !llhd.time): - ^bb1(%t: !llhd.time): - llhd.halt - } -} - -// CHECK: @check_wait_2(inout %[[ARG0:.*]] : i64, inout %[[ARG1:.*]] : i1) { -hw.module @check_wait_2 (inout %arg0 : i64, inout %arg1 : i1) { - // CHECK-NEXT: llhd.process - llhd.process { - // CHECK-NEXT: llhd.wait (%[[ARG0]], %[[ARG1]] : !hw.inout, !hw.inout), ^[[BB:.*]](%[[ARG1]] : !hw.inout) - "llhd.wait"(%arg0, %arg1, %arg1) [^bb1] {operandSegmentSizes=array} : (!hw.inout, !hw.inout, !hw.inout) -> () - // CHECK: ^[[BB]](%[[A:.*]]: !hw.inout): - ^bb1(%a: !hw.inout): - llhd.halt - } -} - -// CHECK: hw.module @check_wait_3(inout %[[ARG0:.*]] : i64, inout %[[ARG1:.*]] : i1) { -hw.module @check_wait_3 (inout %arg0 : i64, inout %arg1 : i1) { - // CHECK-NEXT: llhd.process - llhd.process { - // CHECK-NEXT: %[[TIME:.*]] = llhd.constant_time - %time = llhd.constant_time #llhd.time<0ns, 0d, 0e> - // CHECK-NEXT: llhd.wait for %[[TIME]], (%[[ARG0]], %[[ARG1]] : !hw.inout, !hw.inout), ^[[BB:.*]](%[[ARG1]], %[[ARG0]] : !hw.inout, !hw.inout) - "llhd.wait"(%arg0, %arg1, %time, %arg1, %arg0) [^bb1] {operandSegmentSizes=array} : (!hw.inout, !hw.inout, !llhd.time, !hw.inout, !hw.inout) -> () - // CHECK: ^[[BB]](%[[A:.*]]: !hw.inout, %[[B:.*]]: !hw.inout): - ^bb1(%a: !hw.inout, %b: !hw.inout): - llhd.halt - } -} From 08fd04fc8e46ab30f41ccc27deb593c767a9064e Mon Sep 17 00:00:00 2001 From: Martin Erhart Date: Sun, 11 Aug 2024 16:49:16 +0100 Subject: [PATCH 073/119] [LLHD] Remove RegOp (#7508) LLHD's `reg` operation dates back to when the `seq` dialect was not a thing yet. It has the disadvantage that * it only works with `inout` values * while it can model all kinds of registers and latches you can think of, more than is actually used/necessary in practice, it is very complicated to work with Instead of having this operation, we can * use the seq registers * if we need to work with inout values, we can continuously drive the seq register output to the signal with an additional `llhd.drv` operation * If some state element occurs frequently for which we don't have an operation in the seq dialect, we can add one there specifically for that kind of element (e.g., a latch?) * the `lllhd.reg` operation can specify triggers with different clocks. The most common case are async resets which we also support in the seq dialect. Other, more weird constructs could also just be left in the `llhd.process` based representation. We need to support them in simulation and verilog export anyway. Alternatively, we could also decompose it to the bare logic gates with feedback loops directly, don't know why that would be necessary though. --- include/circt/Dialect/LLHD/IR/SignalOps.td | 133 --------------- lib/Dialect/LLHD/IR/LLHDOps.cpp | 187 --------------------- test/Dialect/LLHD/IR/basic.mlir | 20 --- 3 files changed, 340 deletions(-) diff --git a/include/circt/Dialect/LLHD/IR/SignalOps.td b/include/circt/Dialect/LLHD/IR/SignalOps.td index 4fd4a5323b28..ed24a4e43758 100644 --- a/include/circt/Dialect/LLHD/IR/SignalOps.td +++ b/include/circt/Dialect/LLHD/IR/SignalOps.td @@ -175,139 +175,6 @@ def LLHD_DrvOp : LLHD_Op<"drv", [ let hasCanonicalizeMethod = 1; } -def REG_MODE_LOW : I64EnumAttrCase<"low", 0>; -def REG_MODE_HIGH : I64EnumAttrCase<"high", 1>; -def REG_MODE_RISE : I64EnumAttrCase<"rise", 2>; -def REG_MODE_FALL : I64EnumAttrCase<"fall", 3>; -def REG_MODE_BOTH : I64EnumAttrCase<"both", 4>; - -def LLHD_RegModeAttr : I64EnumAttr<"RegMode", "", [ - REG_MODE_LOW, REG_MODE_HIGH, REG_MODE_RISE, REG_MODE_FALL, REG_MODE_BOTH - ]> { - let cppNamespace = "::circt::llhd"; -} - -def LLHD_RegModeArrayAttr - : TypedArrayAttrBase {} - -def LLHD_RegOp : LLHD_Op<"reg", [ - HasParent<"hw::HWModuleOp">, - AttrSizedOperandSegments - ]> { - let summary = "Represents a storage element"; - let description = [{ - This instruction represents a storage element. It drives its output onto - the 'signal' value. An arbitrary amount of triggers can be added to the - storage element. However, at least one is required. They are quadruples - consisting of the new value to be stored if the trigger applies, the - mode and trigger value which specify when this trigger has to be applied - as well as a delay. Optionally, each triple may also have a gate - condition, in this case the trigger only applies if the gate is one. If - multiple triggers apply the left-most in the list takes precedence. - - There are five modes available: - - | Mode | Meaning | - |--------|-----------------------------------------------------------------| - | "low" | Storage element stores `value` while the `trigger` is low. Models active-low resets and low-transparent latches. - | "high" | Storage element stores `value` while the `trigger` is high. Models active-high resets and high-transparent latches. - | "rise" | Storage element stores `value` upon the rising edge of the `trigger`. Models rising-edge flip-flops. - | "fall" | Storage element stores `value` upon the falling edge of the `trigger`. Models falling-edge flip-flops. - | "both" | Storage element stores `value` upon the a rising or a falling edge of the `trigger`. Models dual-edge flip-flops. - - This instruction may only be used in an LLHD entity. - - Syntax: - - ``` - reg-op ::= `llhd.reg` signal-ssa-value - ( `,` `(` value-ssa-value `,` mode-string trigger-ssa-value `after` - delay-ssa-value ( `if` gate-ssa-value )? `:` value-type )+ - attr-dict `:` signal-type - ``` - - Examples: - - A rising, falling, and dual-edge triggered flip-flop: - - ```mlir - llhd.reg %Q, (%D, "rise" %CLK after %T : !hw.inout) : !hw.inout - llhd.reg %Q, (%D, "fall" %CLK after %T : !hw.inout) : !hw.inout - llhd.reg %Q, (%D, "both" %CLK after %T : !hw.inout) : !hw.inout - ``` - - A rising-edge triggered flip-flop with active-low reset: - - ```mlir - llhd.reg %Q, (%init, "low" %RSTB after %T : !hw.inout), - (%D, "rise" %CLK after %T : !hw.inout) : !hw.inout - ``` - - A rising-edge triggered enable flip-flop with active-low reset: - - ```mlir - llhd.reg %Q, (%init, "low" %RSTB after %T : !hw.inout), - (%D, "rise" %CLK after %T if %EN : !hw.inout) : !hw.inout - ``` - - A transparent-low and transparent-high latch: - - ```mlir - llhd.reg %Q, (%D, "low" %CLK after %T : !hw.inout) : !hw.inout - llhd.reg %Q, (%D, "high" %CLK after %T : !hw.inout) : !hw.inout - ``` - - An SR latch: - - ```mlir - %0 = llhd.const 0 : i1 - %1 = llhd.const 1 : i1 - llhd.reg %Q, (%0, "high" %R after %T : !hw.inout), - (%1, "high" %S after %T : !hw.inout) : !hw.inout - ``` - }]; - - let arguments = (ins - InOutType: $signal, - LLHD_RegModeArrayAttr: $modes, - Variadic>: $values, - Variadic: $triggers, - Variadic: $delays, - Variadic: $gates, - I64ArrayAttr: $gateMask); - - let extraClassDeclaration = [{ - static StringRef getModeAttrName() { return "modes"; } - static RegMode getRegModeByName(StringRef name) { - std::optional optional = symbolizeRegMode(name); - assert(optional && "Invalid RegMode string."); - return *optional; - } - - bool hasGate(unsigned index) { - assert(index < getGateMask().getValue().size() && "Index out of range."); - return llvm::cast( - getGateMask().getValue()[index]).getInt() != 0; - } - - Value getGateAt(unsigned index) { - assert(index < getGateMask().getValue().size() && "Index out of range."); - if (!hasGate(index)) return Value(); - return - getGates()[llvm::cast( - getGateMask().getValue()[index]).getInt()-1]; - } - - RegMode getRegModeAt(unsigned index) { - assert(index < getModes().getValue().size() && "Index out of range."); - return (RegMode) llvm::cast( - getModes().getValue()[index]).getInt(); - } - }]; - - let hasVerifier = 1; -} - def DelayOp : LLHD_Op<"delay", [Pure, SameOperandsAndResultType]> { let summary = "specifies value propagation delay"; let description = [{ diff --git a/lib/Dialect/LLHD/IR/LLHDOps.cpp b/lib/Dialect/LLHD/IR/LLHDOps.cpp index 7e9517a0bf7b..667dc6c6c60f 100644 --- a/lib/Dialect/LLHD/IR/LLHDOps.cpp +++ b/lib/Dialect/LLHD/IR/LLHDOps.cpp @@ -351,193 +351,6 @@ LogicalResult llhd::ConnectOp::canonicalize(llhd::ConnectOp op, return success(); } -//===----------------------------------------------------------------------===// -// RegOp -//===----------------------------------------------------------------------===// - -ParseResult llhd::RegOp::parse(OpAsmParser &parser, OperationState &result) { - OpAsmParser::UnresolvedOperand signal; - Type signalType; - SmallVector valueOperands; - SmallVector triggerOperands; - SmallVector delayOperands; - SmallVector gateOperands; - SmallVector valueTypes; - llvm::SmallVector modesArray; - llvm::SmallVector gateMask; - int64_t gateCount = 0; - - if (parser.parseOperand(signal)) - return failure(); - while (succeeded(parser.parseOptionalComma())) { - OpAsmParser::UnresolvedOperand value; - OpAsmParser::UnresolvedOperand trigger; - OpAsmParser::UnresolvedOperand delay; - OpAsmParser::UnresolvedOperand gate; - Type valueType; - StringAttr modeAttr; - NamedAttrList attrStorage; - - if (parser.parseLParen()) - return failure(); - if (parser.parseOperand(value) || parser.parseComma()) - return failure(); - if (parser.parseAttribute(modeAttr, parser.getBuilder().getNoneType(), - "modes", attrStorage)) - return failure(); - auto attrOptional = llhd::symbolizeRegMode(modeAttr.getValue()); - if (!attrOptional) - return parser.emitError(parser.getCurrentLocation(), - "invalid string attribute"); - modesArray.push_back(static_cast(*attrOptional)); - if (parser.parseOperand(trigger)) - return failure(); - if (parser.parseKeyword("after") || parser.parseOperand(delay)) - return failure(); - if (succeeded(parser.parseOptionalKeyword("if"))) { - gateMask.push_back(++gateCount); - if (parser.parseOperand(gate)) - return failure(); - gateOperands.push_back(gate); - } else { - gateMask.push_back(0); - } - if (parser.parseColon() || parser.parseType(valueType) || - parser.parseRParen()) - return failure(); - valueOperands.push_back(value); - triggerOperands.push_back(trigger); - delayOperands.push_back(delay); - valueTypes.push_back(valueType); - } - if (parser.parseOptionalAttrDict(result.attributes) || parser.parseColon() || - parser.parseType(signalType)) - return failure(); - if (parser.resolveOperand(signal, signalType, result.operands)) - return failure(); - if (parser.resolveOperands(valueOperands, valueTypes, - parser.getCurrentLocation(), result.operands)) - return failure(); - for (auto operand : triggerOperands) - if (parser.resolveOperand(operand, parser.getBuilder().getI1Type(), - result.operands)) - return failure(); - for (auto operand : delayOperands) - if (parser.resolveOperand( - operand, llhd::TimeType::get(parser.getBuilder().getContext()), - result.operands)) - return failure(); - for (auto operand : gateOperands) - if (parser.resolveOperand(operand, parser.getBuilder().getI1Type(), - result.operands)) - return failure(); - result.addAttribute("gateMask", - parser.getBuilder().getI64ArrayAttr(gateMask)); - result.addAttribute("modes", parser.getBuilder().getI64ArrayAttr(modesArray)); - llvm::SmallVector operandSizes; - operandSizes.push_back(1); - operandSizes.push_back(valueOperands.size()); - operandSizes.push_back(triggerOperands.size()); - operandSizes.push_back(delayOperands.size()); - operandSizes.push_back(gateOperands.size()); - result.addAttribute("operandSegmentSizes", - parser.getBuilder().getDenseI32ArrayAttr(operandSizes)); - - return success(); -} - -void llhd::RegOp::print(OpAsmPrinter &printer) { - printer << " " << getSignal(); - for (size_t i = 0, e = getValues().size(); i < e; ++i) { - std::optional mode = llhd::symbolizeRegMode( - cast(getModes().getValue()[i]).getInt()); - if (!mode) { - emitError("invalid RegMode"); - return; - } - printer << ", (" << getValues()[i] << ", \"" - << llhd::stringifyRegMode(*mode) << "\" " << getTriggers()[i] - << " after " << getDelays()[i]; - if (hasGate(i)) - printer << " if " << getGateAt(i); - printer << " : " << getValues()[i].getType() << ")"; - } - printer.printOptionalAttrDict((*this)->getAttrs(), - {"modes", "gateMask", "operandSegmentSizes"}); - printer << " : " << getSignal().getType(); -} - -LogicalResult llhd::RegOp::verify() { - // At least one trigger has to be present - if (getTriggers().size() < 1) - return emitError("At least one trigger quadruple has to be present."); - - // Values variadic operand must have the same size as the triggers variadic - if (getValues().size() != getTriggers().size()) - return emitOpError("Number of 'values' is not equal to the number of " - "'triggers', got ") - << getValues().size() << " modes, but " << getTriggers().size() - << " triggers!"; - - // Delay variadic operand must have the same size as the triggers variadic - if (getDelays().size() != getTriggers().size()) - return emitOpError("Number of 'delays' is not equal to the number of " - "'triggers', got ") - << getDelays().size() << " modes, but " << getTriggers().size() - << " triggers!"; - - // Array Attribute of RegModes must have the same number of elements as the - // variadics - if (getModes().size() != getTriggers().size()) - return emitOpError("Number of 'modes' is not equal to the number of " - "'triggers', got ") - << getModes().size() << " modes, but " << getTriggers().size() - << " triggers!"; - - // Array Attribute 'gateMask' must have the same number of elements as the - // triggers and values variadics - if (getGateMask().size() != getTriggers().size()) - return emitOpError("Size of 'gateMask' is not equal to the size of " - "'triggers', got ") - << getGateMask().size() << " modes, but " << getTriggers().size() - << " triggers!"; - - // Number of non-zero elements in 'gateMask' has to be the same as the size - // of the gates variadic, also each number from 1 to size-1 has to occur - // only once and in increasing order - unsigned counter = 0; - unsigned prevElement = 0; - for (Attribute maskElem : getGateMask().getValue()) { - int64_t val = cast(maskElem).getInt(); - if (val < 0) - return emitError("Element in 'gateMask' must not be negative!"); - if (val == 0) - continue; - if (val != ++prevElement) - return emitError( - "'gateMask' has to contain every number from 1 to the " - "number of gates minus one exactly once in increasing order " - "(may have zeros in-between)."); - counter++; - } - if (getGates().size() != counter) - return emitError("The number of non-zero elements in 'gateMask' and the " - "size of the 'gates' variadic have to match."); - - // Each value must be either the same type as the 'signal' or the underlying - // type of the 'signal' - for (auto val : getValues()) { - if (val.getType() != getSignal().getType() && - val.getType() != - cast(getSignal().getType()).getElementType()) { - return emitOpError( - "type of each 'value' has to be either the same as the " - "type of 'signal' or the underlying type of 'signal'"); - } - } - return success(); -} - #include "circt/Dialect/LLHD/IR/LLHDEnums.cpp.inc" #define GET_OP_CLASSES diff --git a/test/Dialect/LLHD/IR/basic.mlir b/test/Dialect/LLHD/IR/basic.mlir index 87bd29618d31..96d768958628 100644 --- a/test/Dialect/LLHD/IR/basic.mlir +++ b/test/Dialect/LLHD/IR/basic.mlir @@ -199,23 +199,3 @@ hw.module @check_wait_3 (inout %arg0 : i64, inout %arg1 : i1) { llhd.halt } } - -// CHECK-LABEL: @check_reg -// CHECK-SAME: %[[IN64:.*]] : i64 -hw.module @check_reg(inout %in64 : i64) { - // CHECK: %[[C1:.*]] = hw.constant - %c1 = hw.constant 0 : i1 - // CHECK-NEXT: %[[C64:.*]] = hw.constant - %c64 = hw.constant 0 : i64 - // CHECK-NEXT: %[[TIME:.*]] = llhd.constant_time - %time = llhd.constant_time #llhd.time<1ns, 0d, 0e> - // one trigger with optional gate - // CHECK-NEXT: llhd.reg %[[IN64]], (%[[C64]], "low" %[[C1]] after %[[TIME]] if %[[C1]] : i64) : !hw.inout - "llhd.reg"(%in64, %c64, %c1, %time, %c1) {modes=[0], gateMask=[1], operandSegmentSizes=array} : (!hw.inout, i64, i1, !llhd.time, i1) -> () - // two triggers with optional gates - // CHECK-NEXT: llhd.reg %[[IN64]], (%[[C64]], "low" %[[C1]] after %[[TIME]] if %[[C1]] : i64), (%[[IN64]], "high" %[[C1]] after %[[TIME]] if %[[C1]] : !hw.inout) : !hw.inout - "llhd.reg"(%in64, %c64, %in64, %c1, %c1, %time, %time, %c1, %c1) {modes=[0,1], gateMask=[1,2], operandSegmentSizes=array} : (!hw.inout, i64, !hw.inout, i1, i1, !llhd.time, !llhd.time, i1, i1) -> () - // two triggers with only one optional gate - // CHECK-NEXT: llhd.reg %[[IN64]], (%[[C64]], "low" %[[C1]] after %[[TIME]] : i64), (%[[IN64]], "high" %[[C1]] after %[[TIME]] if %[[C1]] : !hw.inout) : !hw.inout - "llhd.reg"(%in64, %c64, %in64, %c1, %c1, %time, %time, %c1) {modes=[0,1], gateMask=[0,1], operandSegmentSizes=array} : (!hw.inout, i64, !hw.inout, i1, i1, !llhd.time, !llhd.time, i1) -> () -} From 22eb6b5cb2fe585c6e60a3671b477884940b80ee Mon Sep 17 00:00:00 2001 From: Martin Erhart Date: Sun, 11 Aug 2024 20:01:31 +0100 Subject: [PATCH 074/119] [LLHD][NFC] Clean up some LLHD files (#7509) --- include/circt/Dialect/LLHD/IR/CMakeLists.txt | 2 + include/circt/Dialect/LLHD/IR/LLHD.td | 99 ++-------------- include/circt/Dialect/LLHD/IR/LLHDDialect.td | 49 ++++++++ .../IR/{ExtractOps.td => LLHDExtractOps.td} | 34 +++--- .../IR/{MemoryOps.td => LLHDMemoryOps.td} | 41 +++---- .../IR/{SignalOps.td => LLHDSignalOps.td} | 86 +++++--------- .../{StructureOps.td => LLHDStructureOps.td} | 27 +---- .../IR/{LLHDTypesImpl.td => LLHDTypes.td} | 29 +++-- .../LLHD/IR/{ValueOps.td => LLHDValueOps.td} | 8 +- .../circt/Dialect/LLHD/Transforms/Passes.td | 14 ++- lib/Dialect/LLHD/IR/LLHDDialect.cpp | 3 - lib/Dialect/LLHD/IR/LLHDOps.cpp | 110 +----------------- 12 files changed, 166 insertions(+), 336 deletions(-) create mode 100644 include/circt/Dialect/LLHD/IR/LLHDDialect.td rename include/circt/Dialect/LLHD/IR/{ExtractOps.td => LLHDExtractOps.td} (91%) rename include/circt/Dialect/LLHD/IR/{MemoryOps.td => LLHDMemoryOps.td} (65%) rename include/circt/Dialect/LLHD/IR/{SignalOps.td => LLHDSignalOps.td} (68%) rename include/circt/Dialect/LLHD/IR/{StructureOps.td => LLHDStructureOps.td} (87%) rename include/circt/Dialect/LLHD/IR/{LLHDTypesImpl.td => LLHDTypes.td} (76%) rename include/circt/Dialect/LLHD/IR/{ValueOps.td => LLHDValueOps.td} (84%) diff --git a/include/circt/Dialect/LLHD/IR/CMakeLists.txt b/include/circt/Dialect/LLHD/IR/CMakeLists.txt index 0f62129db938..93d0696c504d 100644 --- a/include/circt/Dialect/LLHD/IR/CMakeLists.txt +++ b/include/circt/Dialect/LLHD/IR/CMakeLists.txt @@ -5,7 +5,9 @@ set(LLVM_TARGET_DEFINITIONS LLHD.td) mlir_tablegen(LLHDEnums.h.inc -gen-enum-decls) mlir_tablegen(LLHDEnums.cpp.inc -gen-enum-defs) add_public_tablegen_target(CIRCTLLHDEnumsIncGen) +add_dependencies(circt-headers CIRCTLLHDEnumsIncGen) mlir_tablegen(LLHDAttributes.h.inc -gen-attrdef-decls -attrdefs-dialect=llhd) mlir_tablegen(LLHDAttributes.cpp.inc -gen-attrdef-defs -attrdefs-dialect=llhd) add_public_tablegen_target(CIRCTLLHDAttributesIncGen) +add_dependencies(circt-headers CIRCTLLHDAttributesIncGen) diff --git a/include/circt/Dialect/LLHD/IR/LLHD.td b/include/circt/Dialect/LLHD/IR/LLHD.td index 8c3b1031b907..0c6a6bc2734e 100644 --- a/include/circt/Dialect/LLHD/IR/LLHD.td +++ b/include/circt/Dialect/LLHD/IR/LLHD.td @@ -10,8 +10,8 @@ // //===----------------------------------------------------------------------===// -#ifndef CIRCT_DIALECT_LLHD_IR_LLHD -#define CIRCT_DIALECT_LLHD_IR_LLHD +#ifndef CIRCT_DIALECT_LLHD_IR_LLHD_TD +#define CIRCT_DIALECT_LLHD_IR_LLHD_TD include "circt/Dialect/HW/HWTypes.td" include "mlir/IR/AttrTypeBase.td" @@ -24,97 +24,18 @@ include "mlir/Interfaces/InferTypeOpInterface.td" include "mlir/Interfaces/FunctionInterfaces.td" include "mlir/IR/SymbolInterfaces.td" -//===----------------------------------------------------------------------===// -// LLHD dialect definition -//===----------------------------------------------------------------------===// - -def LLHD_Dialect : Dialect { - let name = "llhd"; - let cppNamespace = "::circt::llhd"; - let dependentDialects = ["circt::hw::HWDialect"]; - - let description = [{ - A low-level hardware description dialect in MLIR. - }]; - - let hasConstantMaterializer = 1; - let useDefaultTypePrinterParser = 1; - let useDefaultAttributePrinterParser = 1; - - // Opt-out of properties for now, must migrate by LLVM 19. #5273. - let usePropertiesForAttributes = 0; - - let extraClassDeclaration = [{ - /// Register all LLHD types. - void registerTypes(); - /// Register all LLHD attributes. - void registerAttributes(); - }]; -} - -//===----------------------------------------------------------------------===// -// Import HW Types -//===----------------------------------------------------------------------===// - +include "circt/Dialect/LLHD/IR/LLHDDialect.td" include "circt/Dialect/HW/HWTypes.td" - -//===----------------------------------------------------------------------===// -// LLHD type definitions -//===----------------------------------------------------------------------===// - -include "LLHDTypesImpl.td" - -// LLHD Time Type -def LLHD_TimeType : DialectType($_self)">, "LLHD time type", "TimeType">, - BuildableType<"TimeType::get($_builder.getContext())">; - -// Legal underlying types for signals and pointers. -def LLHD_AnyElementType : - AnyTypeOf<[HWIntegerType, ArrayType, StructType]>; - -// LLHD ptr type -class LLHD_PtrType allowedTypes> - : ContainerType, CPred<"llvm::isa($_self)">, - "llvm::cast($_self).getElementType()", "LLHD pointer type">; - -def LLHD_AnyPtrType : LLHD_PtrType<[LLHD_AnyElementType]>; - -def LLHD_AnySigOrPtrType : AnyTypeOf<[LLHD_AnyPtrType, InOutType]>; - -//===----------------------------------------------------------------------===// -// LLHD op definition -//===----------------------------------------------------------------------===// - -// Base class for all LLHD ops. -class LLHD_Op traits = []> - : Op { - - // For each LLHD op, the following static functions need to be defined in - // LLHDOps.cpp: - // - // * static ParseResult parse(OpAsmParser &parser, - // OperationState &state); - // * static void print(OpAsmPrinter &p, op) - let hasCustomAssemblyFormat = 1; -} - -//===----------------------------------------------------------------------===// -// LLHD trait definitions -//===----------------------------------------------------------------------===// - -class SameTypeArbitraryWidth - : PredOpTrait>; +include "circt/Dialect/LLHD/IR/LLHDTypes.td" //===----------------------------------------------------------------------===// // LLHD Operations //===----------------------------------------------------------------------===// -include "ValueOps.td" -include "SignalOps.td" -include "ExtractOps.td" -include "StructureOps.td" -include "MemoryOps.td" +include "circt/Dialect/LLHD/IR/LLHDValueOps.td" +include "circt/Dialect/LLHD/IR/LLHDSignalOps.td" +include "circt/Dialect/LLHD/IR/LLHDExtractOps.td" +include "circt/Dialect/LLHD/IR/LLHDStructureOps.td" +include "circt/Dialect/LLHD/IR/LLHDMemoryOps.td" -#endif // CIRCT_DIALECT_LLHD_IR_LLHD +#endif // CIRCT_DIALECT_LLHD_IR_LLHD_TD diff --git a/include/circt/Dialect/LLHD/IR/LLHDDialect.td b/include/circt/Dialect/LLHD/IR/LLHDDialect.td new file mode 100644 index 000000000000..108e509ea427 --- /dev/null +++ b/include/circt/Dialect/LLHD/IR/LLHDDialect.td @@ -0,0 +1,49 @@ +//===- LLHDDialect.td - LLHD dialect definition ------------*- tablegen -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// This is the top level file for the LLHD dialect. +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_LLHD_IR_LLHDDIALECT_TD +#define CIRCT_DIALECT_LLHD_IR_LLHDDIALECT_TD + +include "mlir/IR/DialectBase.td" + +//===----------------------------------------------------------------------===// +// LLHD dialect definition +//===----------------------------------------------------------------------===// + +def LLHDDialect : Dialect { + let name = "llhd"; + let cppNamespace = "::circt::llhd"; + let dependentDialects = ["circt::hw::HWDialect"]; + + let description = [{ + A low-level hardware description dialect in MLIR. + }]; + + let hasConstantMaterializer = 1; + let useDefaultTypePrinterParser = 1; + let useDefaultAttributePrinterParser = 1; + + // Opt-out of properties for now, must migrate by LLVM 19. #5273. + let usePropertiesForAttributes = 0; + + let extraClassDeclaration = [{ + /// Register all LLHD types. + void registerTypes(); + /// Register all LLHD attributes. + void registerAttributes(); + }]; +} + +class LLHDOp traits = []> + : Op; + +#endif // CIRCT_DIALECT_LLHD_IR_LLHDDIALECT_TD diff --git a/include/circt/Dialect/LLHD/IR/ExtractOps.td b/include/circt/Dialect/LLHD/IR/LLHDExtractOps.td similarity index 91% rename from include/circt/Dialect/LLHD/IR/ExtractOps.td rename to include/circt/Dialect/LLHD/IR/LLHDExtractOps.td index b977058f7932..955e63e0aaf2 100644 --- a/include/circt/Dialect/LLHD/IR/ExtractOps.td +++ b/include/circt/Dialect/LLHD/IR/LLHDExtractOps.td @@ -1,4 +1,4 @@ -//===- ExtractOps.td - LLHD extract operations -------------*- tablegen -*-===// +//===- LLHDExtractOps.td - LLHD extract operations ---------*- tablegen -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -47,7 +47,7 @@ class SmallerOrEqualResultTypeWidthConstraint // Integer Operations //===----------------------------------------------------------------------===// -def LLHD_SigExtractOp : LLHD_Op<"sig.extract", +def SigExtractOp : LLHDOp<"sig.extract", [Pure, SmallerOrEqualResultTypeWidthConstraint<"result", "input">, SigPtrIndexBitWidthConstraint<"lowBit", "input">]> { @@ -74,7 +74,7 @@ def LLHD_SigExtractOp : LLHD_Op<"sig.extract", let hasFolder = true; } -def LLHD_PtrExtractOp : LLHD_Op<"ptr.extract", +def PtrExtractOp : LLHDOp<"ptr.extract", [Pure, SmallerOrEqualResultTypeWidthConstraint<"result", "input">, SigPtrIndexBitWidthConstraint<"lowBit", "input">]> { @@ -86,9 +86,9 @@ def LLHD_PtrExtractOp : LLHD_Op<"ptr.extract", operand. The result length is defined by the result type. }]; - let arguments = (ins LLHD_PtrType<[HWIntegerType]>:$input, + let arguments = (ins LLHDPtrTypeOf<[HWIntegerType]>:$input, HWIntegerType:$lowBit); - let results = (outs LLHD_PtrType<[HWIntegerType]>: $result); + let results = (outs LLHDPtrTypeOf<[HWIntegerType]>: $result); let assemblyFormat = [{ $input `from` $lowBit attr-dict `:` functional-type($input, $result) @@ -105,7 +105,7 @@ def LLHD_PtrExtractOp : LLHD_Op<"ptr.extract", // Array Operations //===----------------------------------------------------------------------===// -def LLHD_SigArraySliceOp : LLHD_Op<"sig.array_slice", +def SigArraySliceOp : LLHDOp<"sig.array_slice", [Pure, SmallerOrEqualResultTypeWidthConstraint<"result", "input">, SameSigPtrArrayElementTypeConstraint<"result", "input">, @@ -156,7 +156,7 @@ def LLHD_SigArraySliceOp : LLHD_Op<"sig.array_slice", let hasCanonicalizeMethod = true; } -def LLHD_PtrArraySliceOp : LLHD_Op<"ptr.array_slice", +def PtrArraySliceOp : LLHDOp<"ptr.array_slice", [Pure, SmallerOrEqualResultTypeWidthConstraint<"result", "input">, SameSigPtrArrayElementTypeConstraint<"result", "input">, @@ -183,9 +183,9 @@ def LLHD_PtrArraySliceOp : LLHD_Op<"ptr.array_slice", ``` }]; - let arguments = (ins LLHD_PtrType<[ArrayType]>: $input, + let arguments = (ins LLHDPtrTypeOf<[ArrayType]>: $input, HWIntegerType: $lowIndex); - let results = (outs LLHD_PtrType<[ArrayType]>: $result); + let results = (outs LLHDPtrTypeOf<[ArrayType]>: $result); let assemblyFormat = [{ $input `at` $lowIndex attr-dict `:` functional-type($input, $result) @@ -207,7 +207,7 @@ def LLHD_PtrArraySliceOp : LLHD_Op<"ptr.array_slice", let hasCanonicalizeMethod = true; } -def LLHD_SigArrayGetOp : LLHD_Op<"sig.array_get", +def SigArrayGetOp : LLHDOp<"sig.array_get", [Pure, SigPtrIndexBitWidthConstraint<"index", "input">, SigArrayElementTypeConstraint<"result", "input">]> { @@ -239,7 +239,7 @@ def LLHD_SigArrayGetOp : LLHD_Op<"sig.array_get", }]; } -def LLHD_PtrArrayGetOp : LLHD_Op<"ptr.array_get", +def PtrArrayGetOp : LLHDOp<"ptr.array_get", [Pure, SigPtrIndexBitWidthConstraint<"index", "input">, PtrArrayElementTypeConstraint<"result", "input">]> { @@ -258,8 +258,8 @@ def LLHD_PtrArrayGetOp : LLHD_Op<"ptr.array_get", ``` }]; - let arguments = (ins LLHD_PtrType<[ArrayType]>:$input, HWIntegerType:$index); - let results = (outs LLHD_PtrType<[HWNonInOutType]>: $result); + let arguments = (ins LLHDPtrTypeOf<[ArrayType]>:$input, HWIntegerType:$index); + let results = (outs LLHDPtrTypeOf<[HWNonInOutType]>: $result); let assemblyFormat = "$input `[` $index `]` attr-dict `:` qualified(type($input))"; @@ -275,7 +275,7 @@ def LLHD_PtrArrayGetOp : LLHD_Op<"ptr.array_get", // Structure Operations //===----------------------------------------------------------------------===// -def LLHD_SigStructExtractOp : LLHD_Op<"sig.struct_extract", [Pure, +def SigStructExtractOp : LLHDOp<"sig.struct_extract", [Pure, DeclareOpInterfaceMethods, InferTypeOpInterface]> { let summary = "Extract a field from a signal of a struct."; let description = [{ @@ -305,7 +305,7 @@ def LLHD_SigStructExtractOp : LLHD_Op<"sig.struct_extract", [Pure, }]; } -def LLHD_PtrStructExtractOp : LLHD_Op<"ptr.struct_extract", [Pure, +def PtrStructExtractOp : LLHDOp<"ptr.struct_extract", [Pure, DeclareOpInterfaceMethods, InferTypeOpInterface]> { let summary = "Extract a field from a pointer to a struct."; let description = [{ @@ -322,8 +322,8 @@ def LLHD_PtrStructExtractOp : LLHD_Op<"ptr.struct_extract", [Pure, ``` }]; - let arguments = (ins LLHD_PtrType<[StructType]>:$input, StrAttr:$field); - let results = (outs LLHD_PtrType<[HWNonInOutType]>:$result); + let arguments = (ins LLHDPtrTypeOf<[StructType]>:$input, StrAttr:$field); + let results = (outs LLHDPtrTypeOf<[HWNonInOutType]>:$result); let assemblyFormat = "$input `[` $field `]` attr-dict `:` qualified(type($input))"; diff --git a/include/circt/Dialect/LLHD/IR/MemoryOps.td b/include/circt/Dialect/LLHD/IR/LLHDMemoryOps.td similarity index 65% rename from include/circt/Dialect/LLHD/IR/MemoryOps.td rename to include/circt/Dialect/LLHD/IR/LLHDMemoryOps.td index 95ff53306758..12c50def8dce 100644 --- a/include/circt/Dialect/LLHD/IR/MemoryOps.td +++ b/include/circt/Dialect/LLHD/IR/LLHDMemoryOps.td @@ -1,4 +1,4 @@ -//===- MemoryOps.td - LLHD memory operations ---------------*- tablegen -*-===// +//===- LLHDMemoryOps.td - LLHD memory operations -----------*- tablegen -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -10,7 +10,7 @@ // //===----------------------------------------------------------------------===// -def LLHD_VarOp : LLHD_Op<"var", [ +def VarOp : LLHDOp<"var", [ TypesMatchWith< "type of 'init' and underlying type of 'result' have to match.", "init", "result", "PtrType::get($_self)"> @@ -21,25 +21,22 @@ def LLHD_VarOp : LLHD_Op<"var", [ initial value given by `init`, and returns a pointer to the allocated region. - **Examples:** + Example: ``` - %int = llhd.const 0 : i32 - %arr = llhd.array_uniform %int : !llhd.array<3xi32> - + %int = hw.constant 0 : i32 %iPtr = llhd.var %int : i32 - %arrPtr = llhd.var %arr : !llhd.array<3xi32> ``` }]; - let arguments = (ins LLHD_AnyElementType: $init); - let results = (outs Res: $result); let assemblyFormat = "$init attr-dict `:` qualified(type($init))"; } -def LLHD_LoadOp : LLHD_Op<"load", [ +def LoadOp : LLHDOp<"load", [ TypesMatchWith< "type of 'result' and underlying type of 'pointer' have to match.", "pointer", "result", "llvm::cast($_self).getElementType()"> @@ -49,27 +46,24 @@ def LLHD_LoadOp : LLHD_Op<"load", [ The `llhd.load` operation loads a value from a memory region given by `pointer`. - **Examples:** + Example: ``` - %int = llhd.const 0 : i32 - %arr = llhd.array_uniform %int : !llhd.array<3xi32> + %int = hw.constant 0 : i32 %iPtr = llhd.var %int : i32 - %arrPtr = llhd.var %arr : !llhd.array<3xi32> %iLd = llhd.load %iPtr : !llhd.ptr - %arrLd = llhd.load %arrPtr : !llhd.ptr> ``` }]; - let arguments = (ins Arg: $pointer); - let results = (outs LLHD_AnyElementType: $result); + let results = (outs HWValueType: $result); let assemblyFormat = "$pointer attr-dict `:` qualified(type($pointer))"; } -def LLHD_StoreOp : LLHD_Op<"store", [ +def StoreOp : LLHDOp<"store", [ TypesMatchWith< "type of 'value' and underlying type of 'pointer' have to match.", "pointer", "value", "llvm::cast($_self).getElementType()"> @@ -79,22 +73,19 @@ def LLHD_StoreOp : LLHD_Op<"store", [ The `llhd.store` operation stores the value `value` to the memory region given by `pointer`. - **Examples:** + Example: ``` - %int = llhd.const 0 : i32 - %arr = llhd.array_uniform %int : !llhd.array<3xi32> + %int = hw.constant 0 : i32 %iPtr = llhd.var %int : i32 - %arrPtr = llhd.var %arr : !llhd.array<3xi32> llhd.store %iPtr, %int : !llhd.ptr - llhd.store %arrPtr, %arr : !llhd.ptr> ``` }]; - let arguments = (ins Arg: $pointer, - LLHD_AnyElementType: $value); + HWValueType: $value); let assemblyFormat = "$pointer `,` $value attr-dict `:` qualified(type($pointer))"; } diff --git a/include/circt/Dialect/LLHD/IR/SignalOps.td b/include/circt/Dialect/LLHD/IR/LLHDSignalOps.td similarity index 68% rename from include/circt/Dialect/LLHD/IR/SignalOps.td rename to include/circt/Dialect/LLHD/IR/LLHDSignalOps.td index ed24a4e43758..d03b2397a4e2 100644 --- a/include/circt/Dialect/LLHD/IR/SignalOps.td +++ b/include/circt/Dialect/LLHD/IR/LLHDSignalOps.td @@ -1,4 +1,4 @@ -//===- SignalOps.td - LLHD signal operations ---------------*- tablegen -*-===// +//===- LLHDSignalOps.td - LLHD signal operations -----------*- tablegen -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -12,7 +12,7 @@ include "mlir/IR/EnumAttr.td" -def LLHD_SigOp : LLHD_Op<"sig", [ +def SigOp : LLHDOp<"sig", [ ParentOneOf<["hw::HWModuleOp", "llhd::ProcessOp"]>, TypesMatchWith< "type of 'init' and underlying type of 'signal' have to match.", @@ -25,67 +25,51 @@ def LLHD_SigOp : LLHD_Op<"sig", [ result type will always be a signal carrying the type of the init operand. A signal defines a unique name within the entity it resides in. - Syntax: - - ``` - sig-op ::= ssa-id `=` `llhd.sig` sig-name ssa-init attr-dict `:` init-type - ``` - Example: ```mlir - %init_i64 = llhd.const 123 : i64 - %sig_i64 = llhd.sig "foo" %init_64 : i64 - - %init_i1 = llhd.const 1 : i1 - %sig_i1 = llhd.sig "bar" %init_i1 : i1 + %c123_i64 = hw.constant 123 : i64 + %sig_i64 = llhd.sig "foo" %c123_i64 : i64 ``` - The first `llhd.sig` instruction creates a new signal named "foo", carrying - an `i64` type with initial value of 123, while the second one creates a new - signal named "bar", carrying an `i1` type with initial value of 1. + This example creates a new signal named "foo", carrying an `i64` type with + initial value of 123. }]; - let arguments = (ins StrAttr: $name, LLHD_AnyElementType: $init); + let arguments = (ins StrAttr: $name, HWValueType: $init); let results = (outs InOutType: $result); let assemblyFormat = "$name $init attr-dict `:` qualified(type($init))"; } -def LLHD_PrbOp : LLHD_Op<"prb", [ +def PrbOp : LLHDOp<"prb", [ TypesMatchWith< "type of 'result' and underlying type of 'signal' have to match.", "signal", "result", "llvm::cast($_self).getElementType()"> ]> { let summary = "Probe a signal."; let description = [{ - The `llhd.prb` instruction probes a signal and returns the value it + This operation probes a signal and returns the value it currently carries as a new SSA operand. The result type is always the type carried by the signal. - Syntax: - - ``` - prb-op ::= ssa-id `=` `llhd.prb` ssa-sig attr-dict `:` !hw.inout - ``` - Example: ```mlir - %const_i1 = llhd.const 1 : i1 - %sig_i1 = llhd.sig %const_i1 : i1 + %true = hw.constant true + %sig_i1 = llhd.sig %true : i1 %prbd = llhd.prb %sig_i1 : !hw.inout ``` }]; let arguments = (ins Arg: $signal); - let results = (outs LLHD_AnyElementType: $result); + let results = (outs HWValueType: $result); let assemblyFormat = "$signal attr-dict `:` qualified(type($signal))"; } -def LLHD_OutputOp : LLHD_Op<"output", [ +def OutputOp : LLHDOp<"output", [ TypesMatchWith< "type of 'value' and underlying type of 'result' have to match.", "value", "result", "hw::InOutType::get($_self)"> @@ -101,22 +85,22 @@ def LLHD_OutputOp : LLHD_Op<"output", [ Example: ```mlir - %value = llhd.const 1 : i1 - %time = llhd.const #llhd.time<1ns, 0d, 0e> : !llhd.time + %value = hw.constant true + %time = llhd.constant_time <1ns, 0d, 0e> %sig = llhd.output "sigName" %value after %time : i1 // is equivalent to - %value = llhd.const 1 : i1 - %time = llhd.const #llhd.time<1ns, 0d, 0e> : !llhd.time + %value = hw.constant true + %time = llhd.constant_time <1ns, 0d, 0e> %sig = llhd.sig "sigName" %value : i1 llhd.drv %sig, %value after %time : !hw.inout ``` }]; let arguments = (ins OptionalAttr: $name, - LLHD_AnyElementType: $value, - LLHD_TimeType: $time); + HWValueType: $value, + LLHDTimeType: $time); let results = (outs InOutType: $result); @@ -125,7 +109,7 @@ def LLHD_OutputOp : LLHD_Op<"output", [ }]; } -def LLHD_DrvOp : LLHD_Op<"drv", [ +def DrvOp : LLHDOp<"drv", [ TypesMatchWith< "type of 'value' and underlying type of 'signal' have to match.", "signal", "value", "llvm::cast($_self).getElementType()"> @@ -139,31 +123,23 @@ def LLHD_DrvOp : LLHD_Op<"drv", [ value is 1. In case no enable signal is passed the drive will always be performed. This operation does not define any new SSA operands. - Syntax: - - ``` - drv-op ::= `llhd.drv` ssa-signal `,` ssa-const `after` ssa-time - (`if` ssa-enable)? `:` !hw.inout - ``` - Example: ```mlir - %init = llhd.const 1 : i1 - %en = llhd.const 0 : i1 - %time = llhd.const #llhd.time<1ns, 0d, 0e> : !llhd.time - %sig = llhd.sig %init : i1 - %new = llhd.not %init : i1 - - llhd.drv %sig, %new after %time : !hw.inout - llhd.drv %sig, %new after %time if %en : !hw.inout + %true = hw.constant true + %false = hw.constant false + %time = llhd.constant_time <1ns, 0d, 0e> + %sig = llhd.sig %true : i1 + + llhd.drv %sig, %false after %time : !hw.inout + llhd.drv %sig, %false after %time if %true : !hw.inout ``` }]; let arguments = (ins Arg: $signal, - LLHD_AnyElementType: $value, - LLHD_TimeType: $time, + HWValueType: $value, + LLHDTimeType: $time, Optional: $enable); let assemblyFormat = [{ @@ -175,7 +151,7 @@ def LLHD_DrvOp : LLHD_Op<"drv", [ let hasCanonicalizeMethod = 1; } -def DelayOp : LLHD_Op<"delay", [Pure, SameOperandsAndResultType]> { +def DelayOp : LLHDOp<"delay", [Pure, SameOperandsAndResultType]> { let summary = "specifies value propagation delay"; let description = [{ This operation propagates all value changes of the input to the output after @@ -185,7 +161,7 @@ def DelayOp : LLHD_Op<"delay", [Pure, SameOperandsAndResultType]> { store. }]; - let arguments = (ins HWNonInOutType:$input, LLHD_TimeAttr:$delay); + let arguments = (ins HWNonInOutType:$input, LLHDTimeAttr:$delay); let results = (outs HWNonInOutType:$result); let assemblyFormat = "$input `by` $delay attr-dict `:` type($result)"; diff --git a/include/circt/Dialect/LLHD/IR/StructureOps.td b/include/circt/Dialect/LLHD/IR/LLHDStructureOps.td similarity index 87% rename from include/circt/Dialect/LLHD/IR/StructureOps.td rename to include/circt/Dialect/LLHD/IR/LLHDStructureOps.td index f56e11ce641a..3f007c8c7689 100644 --- a/include/circt/Dialect/LLHD/IR/StructureOps.td +++ b/include/circt/Dialect/LLHD/IR/LLHDStructureOps.td @@ -1,4 +1,4 @@ -//===- StructureOps.td - Process and Entity definitions ----*- tablegen -*-===// +//===- LLHDStructureOps.td - Process and Entity defs -------*- tablegen -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -10,7 +10,7 @@ // //===----------------------------------------------------------------------===// -def LLHD_ProcessOp : LLHD_Op<"process", [ +def ProcessOp : LLHDOp<"process", [ NoRegionArguments, HasParent<"hw::HWModuleOp"> ]> { @@ -51,7 +51,7 @@ def LLHD_ProcessOp : LLHD_Op<"process", [ let assemblyFormat = "attr-dict-with-keyword $body"; } -def LLHD_ConnectOp : LLHD_Op<"con", [ +def ConnectOp : LLHDOp<"con", [ SameTypeOperands, HasParent<"hw::HWModuleOp"> ]> { @@ -71,7 +71,7 @@ def LLHD_ConnectOp : LLHD_Op<"con", [ let hasCanonicalizeMethod = 1; } -def LLHD_WaitOp : LLHD_Op<"wait", [ +def WaitOp : LLHDOp<"wait", [ Terminator, AttrSizedOperandSegments, HasParent<"ProcessOp">, @@ -97,7 +97,7 @@ def LLHD_WaitOp : LLHD_Op<"wait", [ }]; let arguments = (ins Variadic:$obs, - Optional:$time, + Optional:$time, Variadic:$destOps); let successors = (successor AnySuccessor:$dest); @@ -108,26 +108,11 @@ def LLHD_WaitOp : LLHD_Op<"wait", [ }]; } -def LLHD_HaltOp : LLHD_Op<"halt", [Terminator, HasParent<"ProcessOp">]> { +def HaltOp : LLHDOp<"halt", [Terminator, HasParent<"ProcessOp">]> { let summary = "Terminates execution of a process."; let description = [{ The `halt` instruction terminates execution of a process. All processes must halt eventually or consist of an infinite loop. - - * This is a terminator instruction - * This instruction is only allowed in processes (`llhd.process`). - - Syntax: - - ``` - halt-op ::= `llhd.halt` - ``` - - Example: - - ```mlir - llhd.halt - ``` }]; let assemblyFormat = "attr-dict"; diff --git a/include/circt/Dialect/LLHD/IR/LLHDTypesImpl.td b/include/circt/Dialect/LLHD/IR/LLHDTypes.td similarity index 76% rename from include/circt/Dialect/LLHD/IR/LLHDTypesImpl.td rename to include/circt/Dialect/LLHD/IR/LLHDTypes.td index a87126f018da..fc2d2d62753d 100644 --- a/include/circt/Dialect/LLHD/IR/LLHDTypesImpl.td +++ b/include/circt/Dialect/LLHD/IR/LLHDTypes.td @@ -10,15 +10,20 @@ // //===----------------------------------------------------------------------===// +#ifndef CIRCT_DIALECT_LLHD_IR_LLHDTYPES_TD +#define CIRCT_DIALECT_LLHD_IR_LLHDTYPES_TD + +include "circt/Dialect/LLHD/IR/LLHDDialect.td" +include "mlir/IR/AttrTypeBase.td" + // Base class for other typedefs. Provides dialact-specific defaults. -class LLHDType : TypeDef { } +class LLHDType : TypeDef { } //===----------------------------------------------------------------------===// // Type declarations //===----------------------------------------------------------------------===// -// Declares the llhd::PtrType in C++. -def PtrTypeImpl : LLHDType<"Ptr"> { +def LLHDPtrType : LLHDType<"Ptr"> { let summary = "pointer type"; let description = [{ Represents a pointer to a memory location holding a value of its element @@ -36,8 +41,7 @@ def PtrTypeImpl : LLHDType<"Ptr"> { ]; } -// Declares the llhd::TimeType in C++. -def TimeTypeImpl : LLHDType<"Time"> { +def LLHDTimeType : LLHDType<"Time"> { let summary = "time type"; let description = [{ Represents a simulation time value as a combination of a real time value in @@ -49,12 +53,21 @@ def TimeTypeImpl : LLHDType<"Time"> { let mnemonic = "time"; } +//===----------------------------------------------------------------------===// +// Type Constraints +//===----------------------------------------------------------------------===// + +class LLHDPtrTypeOf allowedTypes> + : ContainerType, CPred<"llvm::isa($_self)">, + "llvm::cast($_self).getElementType()", "LLHD pointer type">; + +def LLHDAnySigOrPtrType : AnyTypeOf<[LLHDPtrType, InOutType]>; + //===----------------------------------------------------------------------===// // Attribute declarations //===----------------------------------------------------------------------===// -// Declares the llhd::TimeAttr in C++. -def LLHD_TimeAttr : AttrDef { +def LLHDTimeAttr : AttrDef { let summary = "time attribute"; let description = [{ Represents a value of the LLHD time type. @@ -83,3 +96,5 @@ def LLHD_TimeAttr : AttrDef { time, timeUnit, delta, epsilon); }]>]; } + +#endif // CIRCT_DIALECT_LLHD_IR_LLHDTYPES_TD diff --git a/include/circt/Dialect/LLHD/IR/ValueOps.td b/include/circt/Dialect/LLHD/IR/LLHDValueOps.td similarity index 84% rename from include/circt/Dialect/LLHD/IR/ValueOps.td rename to include/circt/Dialect/LLHD/IR/LLHDValueOps.td index 1620095f3b9a..176b0199ee97 100644 --- a/include/circt/Dialect/LLHD/IR/ValueOps.td +++ b/include/circt/Dialect/LLHD/IR/LLHDValueOps.td @@ -1,4 +1,4 @@ -//===- ValueOps.td - LLHD value operations -----------------*- tablegen -*-===// +//===- LLHDValueOps.td - LLHD value operations -------------*- tablegen -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -10,7 +10,7 @@ // //===----------------------------------------------------------------------===// -def LLHD_ConstantTimeOp : LLHD_Op<"constant_time", +def ConstantTimeOp : LLHDOp<"constant_time", [ConstantLike, Pure]> { let summary = "Introduce a new time constant."; let description = [{ @@ -35,8 +35,8 @@ def LLHD_ConstantTimeOp : LLHD_Op<"constant_time", "unsigned":$epsilon)> ]; - let arguments = (ins LLHD_TimeAttr: $value); - let results = (outs LLHD_TimeType: $result); + let arguments = (ins LLHDTimeAttr: $value); + let results = (outs LLHDTimeType: $result); let hasFolder = 1; } diff --git a/include/circt/Dialect/LLHD/Transforms/Passes.td b/include/circt/Dialect/LLHD/Transforms/Passes.td index b4db78a4020b..145f921e1d96 100644 --- a/include/circt/Dialect/LLHD/Transforms/Passes.td +++ b/include/circt/Dialect/LLHD/Transforms/Passes.td @@ -40,17 +40,18 @@ def MemoryToBlockArgument : Pass<"llhd-memory-to-block-argument", ```mlir llhd.process { - %c5 = llhd.const 5 : i32 + %c5 = hw.constant 5 : i32 %cond = llhd.prb %condsig : !hw.inout %ptr = llhd.var %c5 : i32 cond_br %cond, ^bb1, ^bb2 ^bb1: - %c6 = llhd.const 6 : i32 + %c6 = hw.constant 6 : i32 llhd.store %ptr, %c6 : !llhd.ptr br ^bb2 ^bb2: %ld = llhd.load %ptr : !llhd.ptr - %res = llhd.not %ld : i32 + %c-1_i32 = hw.constant -1 : i32 + %res = comb.xor %ld, %c-1_i32 : i32 llhd.halt } ``` @@ -59,14 +60,15 @@ def MemoryToBlockArgument : Pass<"llhd-memory-to-block-argument", ```mlir llhd.process { - %c5 = llhd.const 5 : i32 + %c5 = hw.constant 5 : i32 %cond = llhd.prb %condsig : !hw.inout cond_br %cond, ^bb1, ^bb2(%c5 : i32) ^bb1: - %c6 = llhd.const 6 : i32 + %c6 = hw.constant 6 : i32 br ^bb2(%c6 : i32) ^bb2(%arg : i32): - %res = llhd.not %arg : i32 + %c-1_i32 = hw.constant -1 : i32 + %res = comb.xor %arg, %c-1_i32 : i32 llhd.halt } ``` diff --git a/lib/Dialect/LLHD/IR/LLHDDialect.cpp b/lib/Dialect/LLHD/IR/LLHDDialect.cpp index 1c60d080e23c..a284de799b5d 100644 --- a/lib/Dialect/LLHD/IR/LLHDDialect.cpp +++ b/lib/Dialect/LLHD/IR/LLHDDialect.cpp @@ -15,11 +15,8 @@ #include "circt/Dialect/LLHD/IR/LLHDOps.h" #include "circt/Dialect/LLHD/IR/LLHDTypes.h" #include "circt/Support/LLVM.h" -#include "mlir/IR/Builders.h" #include "mlir/IR/DialectImplementation.h" #include "mlir/Transforms/InliningUtils.h" -#include "llvm/ADT/ArrayRef.h" -#include "llvm/ADT/StringRef.h" using namespace circt; using namespace circt::llhd; diff --git a/lib/Dialect/LLHD/IR/LLHDOps.cpp b/lib/Dialect/LLHD/IR/LLHDOps.cpp index 667dc6c6c60f..6e85ea56163b 100644 --- a/lib/Dialect/LLHD/IR/LLHDOps.cpp +++ b/lib/Dialect/LLHD/IR/LLHDOps.cpp @@ -6,129 +6,25 @@ // //===----------------------------------------------------------------------===// // -// This file implement the LLHD ops. +// This file implements the LLHD ops. // //===----------------------------------------------------------------------===// #include "circt/Dialect/LLHD/IR/LLHDOps.h" #include "circt/Dialect/HW/HWOps.h" -#include "circt/Dialect/LLHD/IR/LLHDDialect.h" -#include "mlir/Dialect/CommonFolders.h" #include "mlir/IR/Attributes.h" -#include "mlir/IR/BuiltinOps.h" #include "mlir/IR/BuiltinTypes.h" #include "mlir/IR/Matchers.h" -#include "mlir/IR/OpImplementation.h" #include "mlir/IR/PatternMatch.h" #include "mlir/IR/Region.h" #include "mlir/IR/Types.h" #include "mlir/IR/Value.h" -#include "mlir/Support/LogicalResult.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/SmallVector.h" -#include "llvm/ADT/StringSet.h" -#include "llvm/ADT/TypeSwitch.h" using namespace circt; using namespace mlir; -template > -static Attribute constFoldUnaryOp(ArrayRef operands, - const CalculationT &calculate) { - assert(operands.size() == 1 && "unary op takes one operand"); - if (!operands[0]) - return {}; - - if (auto val = dyn_cast(operands[0])) { - return AttrElementT::get(val.getType(), calculate(val.getValue())); - } else if (auto val = dyn_cast(operands[0])) { - // Operand is a splat so we can avoid expanding the value out and - // just fold based on the splat value. - auto elementResult = calculate(val.getSplatValue()); - return DenseElementsAttr::get(val.getType(), elementResult); - } - if (auto val = dyn_cast(operands[0])) { - // Operand is ElementsAttr-derived; perform an element-wise fold by - // expanding the values. - auto valIt = val.getValues().begin(); - SmallVector elementResults; - elementResults.reserve(val.getNumElements()); - for (size_t i = 0, e = val.getNumElements(); i < e; ++i, ++valIt) - elementResults.push_back(calculate(*valIt)); - return DenseElementsAttr::get(val.getType(), elementResults); - } - return {}; -} - -template > -static Attribute constFoldTernaryOp(ArrayRef operands, - const CalculationT &calculate) { - assert(operands.size() == 3 && "ternary op takes three operands"); - if (!operands[0] || !operands[1] || !operands[2]) - return {}; - - if (isa(operands[0]) && isa(operands[1]) && - isa(operands[2])) { - auto fst = cast(operands[0]); - auto snd = cast(operands[1]); - auto trd = cast(operands[2]); - - return AttrElementT::get( - fst.getType(), - calculate(fst.getValue(), snd.getValue(), trd.getValue())); - } - if (isa(operands[0]) && - isa(operands[1]) && - isa(operands[2])) { - // Operands are splats so we can avoid expanding the values out and - // just fold based on the splat value. - auto fst = cast(operands[0]); - auto snd = cast(operands[1]); - auto trd = cast(operands[2]); - - auto elementResult = calculate(fst.getSplatValue(), - snd.getSplatValue(), - trd.getSplatValue()); - return DenseElementsAttr::get(fst.getType(), elementResult); - } - if (isa(operands[0]) && isa(operands[1]) && - isa(operands[2])) { - // Operands are ElementsAttr-derived; perform an element-wise fold by - // expanding the values. - auto fst = cast(operands[0]); - auto snd = cast(operands[1]); - auto trd = cast(operands[2]); - - auto fstIt = fst.getValues().begin(); - auto sndIt = snd.getValues().begin(); - auto trdIt = trd.getValues().begin(); - SmallVector elementResults; - elementResults.reserve(fst.getNumElements()); - for (size_t i = 0, e = fst.getNumElements(); i < e; - ++i, ++fstIt, ++sndIt, ++trdIt) - elementResults.push_back(calculate(*fstIt, *sndIt, *trdIt)); - return DenseElementsAttr::get(fst.getType(), elementResults); - } - return {}; -} - -namespace { - -struct constant_int_all_ones_matcher { - bool match(Operation *op) { - APInt value; - return mlir::detail::constant_int_value_binder(&value).match(op) && - value.isAllOnes(); - } -}; - -} // anonymous namespace - unsigned circt::llhd::getLLHDTypeWidth(Type type) { if (auto sig = dyn_cast(type)) type = sig.getElementType(); @@ -151,10 +47,6 @@ Type circt::llhd::getLLHDElementType(Type type) { return type; } -//===---------------------------------------------------------------------===// -// LLHD Operations -//===---------------------------------------------------------------------===// - //===----------------------------------------------------------------------===// // ConstantTimeOp //===----------------------------------------------------------------------===// From 3af0079f5859d20fdeb10aba7f61538915b9d070 Mon Sep 17 00:00:00 2001 From: John Demme Date: Mon, 12 Aug 2024 14:06:24 +0200 Subject: [PATCH 075/119] [ESI][Manifest] Embed constants in manifest (#7489) Users want to know what constants were used in a design. Plumb them through using the manifest. No runtime support, no pycde support. Cleanups to the manifest as well. Update the runtime to support the schema changes to the manifest. --- include/circt/Dialect/ESI/ESIInterfaces.td | 8 + include/circt/Dialect/ESI/ESIManifest.td | 17 ++ lib/Dialect/ESI/ESIOps.cpp | 6 + lib/Dialect/ESI/Passes/ESIBuildManifest.cpp | 192 +++++++++++------- .../ESI/runtime/cpp/include/esi/Common.h | 12 +- lib/Dialect/ESI/runtime/cpp/lib/Manifest.cpp | 90 +++++--- test/Dialect/ESI/manifest.mlir | 167 +++++++-------- 7 files changed, 299 insertions(+), 193 deletions(-) diff --git a/include/circt/Dialect/ESI/ESIInterfaces.td b/include/circt/Dialect/ESI/ESIInterfaces.td index f37ccea5bccb..764a718ce6db 100644 --- a/include/circt/Dialect/ESI/ESIInterfaces.td +++ b/include/circt/Dialect/ESI/ESIInterfaces.td @@ -68,6 +68,14 @@ def IsManifestData : OpInterface<"IsManifestData"> { "Get the class name for this op.", "StringRef", "getManifestClass", (ins) >, + InterfaceMethod< + "Get the symbol to which this manifest data is referring, if any.", + "FlatSymbolRefAttr", "getSymbolRefAttr", (ins), + /*methodBody=*/"", + /*defaultImplementation=*/[{ + return FlatSymbolRefAttr(); + }] + >, InterfaceMethod< "Populate results with the manifest data.", "void", "getDetails", (ins "SmallVectorImpl&":$results), diff --git a/include/circt/Dialect/ESI/ESIManifest.td b/include/circt/Dialect/ESI/ESIManifest.td index 1fd3ef9089dc..1913016bd4d5 100644 --- a/include/circt/Dialect/ESI/ESIManifest.td +++ b/include/circt/Dialect/ESI/ESIManifest.td @@ -169,6 +169,23 @@ def AppIDHierNodeOp : ESI_Op<"manifest.hier_node", [ }]; } +def SymbolConstantsOp : ESI_Op<"manifest.constants", [ + DeclareOpInterfaceMethods]> { + let summary = "Constant values associated with a symbol"; + + let arguments = (ins FlatSymbolRefAttr:$symbolRef, + DictionaryAttr:$constants); + let assemblyFormat = [{ + $symbolRef $constants attr-dict + }]; + + let extraClassDeclaration = [{ + // Get information which needs to appear in the manifest for the host to + // connect to this service. + void getDetails(SmallVectorImpl &results); + }]; +} + def SymbolMetadataOp : ESI_Op<"manifest.sym", [ DeclareOpInterfaceMethods]> { let summary = "Metadata about a symbol"; diff --git a/lib/Dialect/ESI/ESIOps.cpp b/lib/Dialect/ESI/ESIOps.cpp index 893f375f60fc..acf794dc679b 100644 --- a/lib/Dialect/ESI/ESIOps.cpp +++ b/lib/Dialect/ESI/ESIOps.cpp @@ -696,6 +696,12 @@ void ServiceRequestRecordOp::getDetails( StringRef SymbolMetadataOp::getManifestClass() { return "sym_info"; } +StringRef SymbolConstantsOp::getManifestClass() { return "sym_consts"; } +void SymbolConstantsOp::getDetails(SmallVectorImpl &results) { + for (auto &attr : getConstantsAttr()) + results.push_back(attr); +} + #define GET_OP_CLASSES #include "circt/Dialect/ESI/ESI.cpp.inc" diff --git a/lib/Dialect/ESI/Passes/ESIBuildManifest.cpp b/lib/Dialect/ESI/Passes/ESIBuildManifest.cpp index a50afdf48261..a4022cf75d07 100644 --- a/lib/Dialect/ESI/Passes/ESIBuildManifest.cpp +++ b/lib/Dialect/ESI/Passes/ESIBuildManifest.cpp @@ -39,10 +39,13 @@ struct ESIBuildManifestPass void gatherFilters(Operation *); void gatherFilters(Attribute); - /// Get a JSON representation of a type. - llvm::json::Value json(Operation *errorOp, Type); - /// Get a JSON representation of a type. - llvm::json::Value json(Operation *errorOp, Attribute); + /// Get a JSON representation of a type. 'useTable' indicates whether to use + /// the type table to determine if the type should be emitted as a reference + /// if it already exists in the type table. + llvm::json::Value json(Operation *errorOp, Type, bool useTable = true); + /// Get a JSON representation of a type. 'elideType' indicates to not print + /// the type if it would have been printed. + llvm::json::Value json(Operation *errorOp, Attribute, bool elideType = false); // Output a node in the appid hierarchy. void emitNode(llvm::json::OStream &, AppIDHierNodeOp nodeOp); @@ -88,6 +91,12 @@ void ESIBuildManifestPass::runOnOperation() { // scraping unnecessary types. appidRoot->walk([&](Operation *op) { gatherFilters(op); }); + // Also gather types from the manifest data. + for (Region ®ion : mod->getRegions()) + for (Block &block : region) + for (auto manifestInfo : block.getOps()) + gatherFilters(manifestInfo); + // JSONify the manifest. std::string jsonManifest = json(); @@ -165,15 +174,34 @@ std::string ESIBuildManifestPass::json() { j.attribute("api_version", esiApiVersion); j.attributeArray("symbols", [&]() { - for (auto symInfo : mod.getBody()->getOps()) { - if (!symbols.contains(symInfo.getSymbolRefAttr())) + // First, gather all of the manifest data for each symbol. + DenseMap> symbolInfoLookup; + for (auto symInfo : mod.getBody()->getOps()) { + FlatSymbolRefAttr sym = symInfo.getSymbolRefAttr(); + if (!sym || !symbols.contains(sym)) continue; + symbolInfoLookup[sym].push_back(symInfo); + } + + // Now, emit a JSON object for each symbol. + for (const auto &symNameInfo : symbolInfoLookup) { j.object([&] { - SmallVector attrs; - symInfo.getDetails(attrs); - for (auto attr : attrs) - j.attribute(attr.getName().getValue(), - json(symInfo, attr.getValue())); + j.attribute("symbol", json(symNameInfo.second.front(), + symNameInfo.first, /*elideType=*/true)); + for (auto symInfo : symNameInfo.second) { + j.attributeBegin(symInfo.getManifestClass()); + j.object([&] { + SmallVector attrs; + symInfo.getDetails(attrs); + for (auto attr : attrs) { + if (attr.getName().getValue() == "symbolRef") + continue; + j.attribute(attr.getName().getValue(), + json(symInfo, attr.getValue())); + } + }); + j.attributeEnd(); + } }); } }); @@ -215,7 +243,7 @@ std::string ESIBuildManifestPass::json() { j.attributeArray("types", [&]() { for (auto type : types) { - j.value(json(mod, type)); + j.value(json(mod, type, /*useTable=*/false)); } }); j.objectEnd(); @@ -245,6 +273,7 @@ void ESIBuildManifestPass::gatherFilters(Attribute attr) { // This is far from complete. Build out as necessary. TypeSwitch(attr) .Case([&](TypeAttr a) { addType(a.getValue()); }) + .Case([&](IntegerAttr a) { addType(a.getType()); }) .Case([&](FlatSymbolRefAttr a) { symbols.insert(a); }) .Case([&](hw::InnerRefAttr a) { symbols.insert(a.getModuleRef()); }) .Case([&](ArrayAttr a) { @@ -259,18 +288,28 @@ void ESIBuildManifestPass::gatherFilters(Attribute attr) { /// Get a JSON representation of a type. // NOLINTNEXTLINE(misc-no-recursion) -llvm::json::Value ESIBuildManifestPass::json(Operation *errorOp, Type type) { +llvm::json::Value ESIBuildManifestPass::json(Operation *errorOp, Type type, + bool useTable) { using llvm::json::Array; using llvm::json::Object; using llvm::json::Value; + if (useTable && typeLookup.contains(type)) { + // If the type is in the type table, it'll be present in the types + // section. Just give the circt type name, which is guaranteed to + // uniquely identify the type. + std::string typeName; + llvm::raw_string_ostream(typeName) << type; + return typeName; + } + std::string m; Object o = // This is not complete. Build out as necessary. TypeSwitch(type) .Case([&](ChannelType t) { m = "channel"; - return Object({{"inner", json(errorOp, t.getInner())}}); + return Object({{"inner", json(errorOp, t.getInner(), useTable)}}); }) .Case([&](ChannelBundleType t) { m = "bundle"; @@ -279,7 +318,7 @@ llvm::json::Value ESIBuildManifestPass::json(Operation *errorOp, Type type) { channels.push_back(Object( {{"name", field.name.getValue()}, {"direction", stringifyChannelDirection(field.direction)}, - {"type", json(errorOp, field.type)}})); + {"type", json(errorOp, field.type, useTable)}})); return Object({{"channels", Value(std::move(channels))}}); }) .Case([&](AnyType t) { @@ -288,25 +327,29 @@ llvm::json::Value ESIBuildManifestPass::json(Operation *errorOp, Type type) { }) .Case([&](ListType t) { m = "list"; - return Object({{"element", json(errorOp, t.getElementType())}}); + return Object( + {{"element", json(errorOp, t.getElementType(), useTable)}}); }) .Case([&](hw::ArrayType t) { m = "array"; - return Object({{"size", t.getNumElements()}, - {"element", json(errorOp, t.getElementType())}}); + return Object( + {{"size", t.getNumElements()}, + {"element", json(errorOp, t.getElementType(), useTable)}}); }) .Case([&](hw::StructType t) { m = "struct"; Array fields; for (auto field : t.getElements()) - fields.push_back(Object({{"name", field.name.getValue()}, - {"type", json(errorOp, field.type)}})); + fields.push_back( + Object({{"name", field.name.getValue()}, + {"type", json(errorOp, field.type, useTable)}})); return Object({{"fields", Value(std::move(fields))}}); }) .Case([&](hw::TypeAliasType t) { m = "alias"; - return Object({{"name", t.getTypeDecl(symCache).getPreferredName()}, - {"inner", json(errorOp, t.getInnerType())}}); + return Object( + {{"name", t.getTypeDecl(symCache).getPreferredName()}, + {"inner", json(errorOp, t.getInnerType(), useTable)}}); }) .Case([&](IntegerType t) { m = "int"; @@ -322,9 +365,9 @@ llvm::json::Value ESIBuildManifestPass::json(Operation *errorOp, Type type) { }); // Common metadata. - std::string circtName; - llvm::raw_string_ostream(circtName) << type; - o["circt_name"] = circtName; + std::string typeID; + llvm::raw_string_ostream(typeID) << type; + o["id"] = typeID; int64_t width = hw::getBitWidth(type); if (auto chanType = dyn_cast(type)) @@ -340,59 +383,58 @@ llvm::json::Value ESIBuildManifestPass::json(Operation *errorOp, Type type) { // Serialize an attribute to a JSON value. // NOLINTNEXTLINE(misc-no-recursion) -llvm::json::Value ESIBuildManifestPass::json(Operation *errorOp, - Attribute attr) { +llvm::json::Value ESIBuildManifestPass::json(Operation *errorOp, Attribute attr, + bool elideType) { + // This is far from complete. Build out as necessary. + using llvm::json::Object; using llvm::json::Value; - return TypeSwitch(attr) - .Case([&](StringAttr a) { return a.getValue(); }) - .Case([&](IntegerAttr a) { return a.getValue().getLimitedValue(); }) - .Case([&](TypeAttr a) { - Type t = a.getValue(); - - llvm::json::Object typeMD; - if (typeLookup.contains(t)) { - // If the type is in the type table, it'll be present in the types - // section. Just give the circt type name, which is guaranteed to - // uniquely identify the type. - std::string buff; - llvm::raw_string_ostream(buff) << a; - typeMD["circt_name"] = buff; - return typeMD; - } + Value value = + TypeSwitch(attr) + .Case([&](StringAttr a) { return a.getValue(); }) + .Case([&](IntegerAttr a) { return a.getValue().getLimitedValue(); }) + .Case([&](TypeAttr a) { return json(errorOp, a.getValue()); }) + .Case([&](ArrayAttr a) { + return llvm::json::Array(llvm::map_range( + a, [&](Attribute a) { return json(errorOp, a); })); + }) + .Case([&](DictionaryAttr a) { + llvm::json::Object dict; + for (const auto &entry : a.getValue()) + dict[entry.getName().getValue()] = + json(errorOp, entry.getValue()); + return dict; + }) + .Case([&](hw::InnerRefAttr ref) { + llvm::json::Object dict; + dict["outer_sym"] = ref.getModule().getValue(); + dict["inner"] = ref.getName().getValue(); + return dict; + }) + .Case([&](AppIDAttr appid) { + llvm::json::Object dict; + dict["name"] = appid.getName().getValue(); + auto idx = appid.getIndex(); + if (idx) + dict["index"] = *idx; + return dict; + }) + .Default([&](Attribute a) { + std::string value; + llvm::raw_string_ostream(value) << a; + return value; + }); - typeMD["type"] = json(errorOp, t); - return typeMD; - }) - .Case([&](ArrayAttr a) { - return llvm::json::Array( - llvm::map_range(a, [&](Attribute a) { return json(errorOp, a); })); - }) - .Case([&](DictionaryAttr a) { - llvm::json::Object dict; - for (const auto &entry : a.getValue()) - dict[entry.getName().getValue()] = json(errorOp, entry.getValue()); - return dict; - }) - .Case([&](hw::InnerRefAttr ref) { - llvm::json::Object dict; - dict["outer_sym"] = ref.getModule().getValue(); - dict["inner"] = ref.getName().getValue(); - return dict; - }) - .Case([&](AppIDAttr appid) { - llvm::json::Object dict; - dict["name"] = appid.getName().getValue(); - auto idx = appid.getIndex(); - if (idx) - dict["index"] = *idx; - return dict; - }) - .Default([&](Attribute a) { - std::string buff; - llvm::raw_string_ostream(buff) << a; - return buff; - }); + // Don't print the type if it's None or we're eliding it. + auto typedAttr = llvm::dyn_cast(attr); + if (elideType || !typedAttr || isa(typedAttr.getType())) + return value; + + // Otherwise, return an object with the value and type. + Object dict; + dict["value"] = value; + dict["type"] = json(errorOp, typedAttr.getType()); + return dict; } std::unique_ptr> diff --git a/lib/Dialect/ESI/runtime/cpp/include/esi/Common.h b/lib/Dialect/ESI/runtime/cpp/include/esi/Common.h index 997fb53158e8..ee502c72f774 100644 --- a/lib/Dialect/ESI/runtime/cpp/include/esi/Common.h +++ b/lib/Dialect/ESI/runtime/cpp/include/esi/Common.h @@ -54,12 +54,12 @@ class AppIDPath : public std::vector { bool operator<(const AppIDPath &a, const AppIDPath &b); struct ModuleInfo { - const std::optional name; - const std::optional summary; - const std::optional version; - const std::optional repo; - const std::optional commitHash; - const std::map extra; + std::optional name; + std::optional summary; + std::optional version; + std::optional repo; + std::optional commitHash; + std::map extra; }; /// A description of a service port. Used pretty exclusively in setting up the diff --git a/lib/Dialect/ESI/runtime/cpp/lib/Manifest.cpp b/lib/Dialect/ESI/runtime/cpp/lib/Manifest.cpp index 62d16feec419..0640538eb91f 100644 --- a/lib/Dialect/ESI/runtime/cpp/lib/Manifest.cpp +++ b/lib/Dialect/ESI/runtime/cpp/lib/Manifest.cpp @@ -19,7 +19,7 @@ #include #include -using namespace esi; +using namespace ::esi; // While building the design, keep around a std::map of active services indexed // by the service name. When a new service is encountered during descent, add it @@ -95,6 +95,10 @@ class Manifest::Impl { const Type *parseType(const nlohmann::json &typeJson); + const std::map &getSymbolInfo() const { + return symbolInfoCache; + } + private: Context &ctxt; std::vector _typeTable; @@ -106,7 +110,7 @@ class Manifest::Impl { // The parsed json. nlohmann::json manifestJson; // Cache the module info for each symbol. - std::map symbolInfoCache; + std::map symbolInfoCache; }; //===----------------------------------------------------------------------===// @@ -169,14 +173,12 @@ static std::any getAny(const nlohmann::json &value) { throw std::runtime_error("Unknown type in manifest: " + value.dump(2)); } -static ModuleInfo parseModuleInfo(const nlohmann::json &mod) { - - std::map extras; +static void parseModuleInfo(ModuleInfo &info, const nlohmann::json &mod) { for (auto &extra : mod.items()) if (extra.key() != "name" && extra.key() != "summary" && extra.key() != "version" && extra.key() != "repo" && - extra.key() != "commitHash" && extra.key() != "symbolRef") - extras[extra.key()] = getAny(extra.value()); + extra.key() != "commitHash") + info.extra[extra.key()] = getAny(extra.value()); auto value = [&](const std::string &key) -> std::optional { auto f = mod.find(key); @@ -184,8 +186,11 @@ static ModuleInfo parseModuleInfo(const nlohmann::json &mod) { return std::nullopt; return f.value(); }; - return ModuleInfo{value("name"), value("summary"), value("version"), - value("repo"), value("commitHash"), extras}; + info.name = value("name"); + info.summary = value("summary"); + info.version = value("version"); + info.repo = value("repo"); + info.commitHash = value("commitHash"); } //===----------------------------------------------------------------------===// @@ -196,10 +201,23 @@ Manifest::Impl::Impl(Context &ctxt, const std::string &manifestStr) : ctxt(ctxt) { manifestJson = nlohmann::ordered_json::parse(manifestStr); - for (auto &mod : manifestJson.at("symbols")) - symbolInfoCache.insert( - make_pair(mod.at("symbolRef"), parseModuleInfo(mod))); - populateTypes(manifestJson.at("types")); + try { + // Populate the types table first since anything else might need it. + populateTypes(manifestJson.at("types")); + + // Populate the symbol info cache. + for (auto &mod : manifestJson.at("symbols")) { + ModuleInfo info; + if (mod.contains("sym_info")) + parseModuleInfo(info, mod); + symbolInfoCache.insert(make_pair(mod.at("symbol"), info)); + } + } catch (const std::exception &e) { + std::string msg = "malformed manifest: " + std::string(e.what()); + if (manifestJson.at("api_version") == 0) + msg += " (schema version 0 is not considered stable)"; + throw std::runtime_error(msg); + } } std::unique_ptr @@ -377,7 +395,7 @@ Manifest::Impl::getBundlePorts(AcceleratorConnection &acc, AppIDPath idPath, } services::Service *svc = svcIter->second; - std::string typeName = content.at("bundleType").at("circt_name"); + std::string typeName = content.at("bundleType"); auto type = getType(typeName); if (!type) throw std::runtime_error( @@ -425,12 +443,12 @@ BundleType *parseBundleType(const nlohmann::json &typeJson, Context &cache) { channels.emplace_back(chanJson.at("name"), dir, parseType(chanJson["type"], cache)); } - return new BundleType(typeJson.at("circt_name"), channels); + return new BundleType(typeJson.at("id"), channels); } ChannelType *parseChannelType(const nlohmann::json &typeJson, Context &cache) { assert(typeJson.at("mnemonic") == "channel"); - return new ChannelType(typeJson.at("circt_name"), + return new ChannelType(typeJson.at("id"), parseType(typeJson.at("inner"), cache)); } @@ -438,7 +456,7 @@ Type *parseInt(const nlohmann::json &typeJson, Context &cache) { assert(typeJson.at("mnemonic") == "int"); std::string sign = typeJson.at("signedness"); uint64_t width = typeJson.at("hw_bitwidth"); - Type::ID id = typeJson.at("circt_name"); + Type::ID id = typeJson.at("id"); if (sign == "signed") return new SIntType(id, width); @@ -459,13 +477,13 @@ StructType *parseStruct(const nlohmann::json &typeJson, Context &cache) { for (auto &fieldJson : typeJson["fields"]) fields.emplace_back(fieldJson.at("name"), parseType(fieldJson["type"], cache)); - return new StructType(typeJson.at("circt_name"), fields); + return new StructType(typeJson.at("id"), fields); } ArrayType *parseArray(const nlohmann::json &typeJson, Context &cache) { assert(typeJson.at("mnemonic") == "array"); uint64_t size = typeJson.at("size"); - return new ArrayType(typeJson.at("circt_name"), + return new ArrayType(typeJson.at("id"), parseType(typeJson.at("element"), cache), size); } @@ -473,10 +491,8 @@ using TypeParser = std::function; const std::map typeParsers = { {"bundle", parseBundleType}, {"channel", parseChannelType}, - {"std::any", - [](const nlohmann::json &typeJson, Context &cache) { - return new AnyType(typeJson.at("circt_name")); - }}, + {"std::any", [](const nlohmann::json &typeJson, + Context &cache) { return new AnyType(typeJson.at("id")); }}, {"int", parseInt}, {"struct", parseStruct}, {"array", parseArray}, @@ -485,19 +501,24 @@ const std::map typeParsers = { // Parse a type if it doesn't already exist in the cache. const Type *parseType(const nlohmann::json &typeJson, Context &cache) { - // We use the circt type string as a unique ID. - std::string circt_name = typeJson.at("circt_name"); - if (std::optional t = cache.getType(circt_name)) + std::string id; + if (typeJson.is_string()) + id = typeJson.get(); + else + id = typeJson.at("id"); + if (std::optional t = cache.getType(id)) return *t; + if (typeJson.is_string()) + throw std::runtime_error("malformed manifest: unknown type '" + id + "'"); - std::string mnemonic = typeJson.at("mnemonic"); Type *t; + std::string mnemonic = typeJson.at("mnemonic"); auto f = typeParsers.find(mnemonic); if (f != typeParsers.end()) t = f->second(typeJson, cache); else // Types we don't know about are opaque. - t = new Type(circt_name); + t = new Type(id); // Insert into the cache. cache.registerType(t); @@ -529,13 +550,20 @@ uint32_t Manifest::getApiVersion() const { std::vector Manifest::getModuleInfos() const { std::vector ret; - for (auto &mod : impl->at("symbols")) - ret.push_back(parseModuleInfo(mod)); + for (auto &[symbol, info] : impl->getSymbolInfo()) + ret.push_back(info); return ret; } Accelerator *Manifest::buildAccelerator(AcceleratorConnection &acc) const { - return acc.takeOwnership(impl->buildAccelerator(acc)); + try { + return acc.takeOwnership(impl->buildAccelerator(acc)); + } catch (const std::exception &e) { + std::string msg = "malformed manifest: " + std::string(e.what()); + if (getApiVersion() == 0) + msg += " (schema version 0 is not considered stable)"; + throw std::runtime_error(msg); + } } const std::vector &Manifest::getTypeTable() const { diff --git a/test/Dialect/ESI/manifest.mlir b/test/Dialect/ESI/manifest.mlir index b32af1474641..4f4b193ed9a7 100644 --- a/test/Dialect/ESI/manifest.mlir +++ b/test/Dialect/ESI/manifest.mlir @@ -33,6 +33,7 @@ hw.module @Loopback (in %clk: !seq.clock) { } esi.manifest.sym @Loopback name "LoopbackIP" version "v0.0" summary "IP which simply echos bytes" {foo=1} +esi.manifest.constants @Loopback {depth=5:ui32} esi.service.std.func @funcs @@ -95,13 +96,25 @@ hw.module @top(in %clk: !seq.clock, in %rst: i1) { // CHECK: { // CHECK-LABEL: "api_version": 0, + // CHECK-LABEL: "symbols": [ // CHECK-NEXT: { -// CHECK-NEXT: "foo": 1, -// CHECK-NEXT: "name": "LoopbackIP", -// CHECK-NEXT: "summary": "IP which simply echos bytes", -// CHECK-NEXT: "symbolRef": "@Loopback", -// CHECK-NEXT: "version": "v0.0" +// CHECK-NEXT: "symbol": "@Loopback", +// CHECK-NEXT: "sym_info": { +// CHECK-NEXT: "foo": { +// CHECK-NEXT: "type": "i64", +// CHECK-NEXT: "value": 1 +// CHECK-NEXT: }, +// CHECK-NEXT: "name": "LoopbackIP", +// CHECK-NEXT: "summary": "IP which simply echos bytes", +// CHECK-NEXT: "version": "v0.0" +// CHECK-NEXT: }, +// CHECK-NEXT: "sym_consts": { +// CHECK-NEXT: "depth": { +// CHECK-NEXT: "type": "ui32", +// CHECK-NEXT: "value": 5 +// CHECK-NEXT: } +// CHECK-NEXT: } // CHECK-NEXT: } // CHECK-NEXT: ], @@ -231,9 +244,7 @@ hw.module @top(in %clk: !seq.clock, in %rst: i1) { // CHECK-NEXT: "appID": { // CHECK-NEXT: "name": "func1" // CHECK-NEXT: }, -// CHECK-NEXT: "bundleType": { -// CHECK-NEXT: "circt_name": "!esi.bundle<[!esi.channel to \"arg\", !esi.channel from \"result\"]>" -// CHECK-NEXT: }, +// CHECK-NEXT: "bundleType": "!esi.bundle<[!esi.channel to \"arg\", !esi.channel from \"result\"]>" // CHECK-NEXT: "servicePort": { // CHECK-NEXT: "inner": "call", // CHECK-NEXT: "outer_sym": "funcs" @@ -254,9 +265,7 @@ hw.module @top(in %clk: !seq.clock, in %rst: i1) { // CHECK-NEXT: "appID": { // CHECK-NEXT: "name": "loopback_tohw" // CHECK-NEXT: }, -// CHECK-NEXT: "bundleType": { -// CHECK-NEXT: "circt_name": "!esi.bundle<[!esi.channel to \"recv\"]>" -// CHECK-NEXT: }, +// CHECK-NEXT: "bundleType": "!esi.bundle<[!esi.channel to \"recv\"]>" // CHECK-NEXT: "servicePort": { // CHECK-NEXT: "inner": "Recv", // CHECK-NEXT: "outer_sym": "HostComms" @@ -267,9 +276,7 @@ hw.module @top(in %clk: !seq.clock, in %rst: i1) { // CHECK-NEXT: "appID": { // CHECK-NEXT: "name": "loopback_fromhw" // CHECK-NEXT: }, -// CHECK-NEXT: "bundleType": { -// CHECK-NEXT: "circt_name": "!esi.bundle<[!esi.channel from \"send\"]>" -// CHECK-NEXT: }, +// CHECK-NEXT: "bundleType": "!esi.bundle<[!esi.channel from \"send\"]>" // CHECK-NEXT: "servicePort": { // CHECK-NEXT: "inner": "Send", // CHECK-NEXT: "outer_sym": "HostComms" @@ -280,9 +287,7 @@ hw.module @top(in %clk: !seq.clock, in %rst: i1) { // CHECK-NEXT: "appID": { // CHECK-NEXT: "name": "loopback_fromhw_i0" // CHECK-NEXT: }, -// CHECK-NEXT: "bundleType": { -// CHECK-NEXT: "circt_name": "!esi.bundle<[!esi.channel from \"send\"]>" -// CHECK-NEXT: }, +// CHECK-NEXT: "bundleType": "!esi.bundle<[!esi.channel from \"send\"]>" // CHECK-NEXT: "servicePort": { // CHECK-NEXT: "inner": "SendI0", // CHECK-NEXT: "outer_sym": "HostComms" @@ -303,9 +308,7 @@ hw.module @top(in %clk: !seq.clock, in %rst: i1) { // CHECK-NEXT: "appID": { // CHECK-NEXT: "name": "loopback_tohw" // CHECK-NEXT: }, -// CHECK-NEXT: "bundleType": { -// CHECK-NEXT: "circt_name": "!esi.bundle<[!esi.channel to \"recv\"]>" -// CHECK-NEXT: }, +// CHECK-NEXT: "bundleType": "!esi.bundle<[!esi.channel to \"recv\"]>" // CHECK-NEXT: "servicePort": { // CHECK-NEXT: "inner": "Recv", // CHECK-NEXT: "outer_sym": "HostComms" @@ -316,9 +319,7 @@ hw.module @top(in %clk: !seq.clock, in %rst: i1) { // CHECK-NEXT: "appID": { // CHECK-NEXT: "name": "loopback_fromhw" // CHECK-NEXT: }, -// CHECK-NEXT: "bundleType": { -// CHECK-NEXT: "circt_name": "!esi.bundle<[!esi.channel from \"send\"]>" -// CHECK-NEXT: }, +// CHECK-NEXT: "bundleType": "!esi.bundle<[!esi.channel from \"send\"]>" // CHECK-NEXT: "servicePort": { // CHECK-NEXT: "inner": "Send", // CHECK-NEXT: "outer_sym": "HostComms" @@ -329,9 +330,7 @@ hw.module @top(in %clk: !seq.clock, in %rst: i1) { // CHECK-NEXT: "appID": { // CHECK-NEXT: "name": "loopback_fromhw_i0" // CHECK-NEXT: }, -// CHECK-NEXT: "bundleType": { -// CHECK-NEXT: "circt_name": "!esi.bundle<[!esi.channel from \"send\"]>" -// CHECK-NEXT: }, +// CHECK-NEXT: "bundleType": "!esi.bundle<[!esi.channel from \"send\"]>" // CHECK-NEXT: "servicePort": { // CHECK-NEXT: "inner": "SendI0", // CHECK-NEXT: "outer_sym": "HostComms" @@ -349,21 +348,15 @@ hw.module @top(in %clk: !seq.clock, in %rst: i1) { // CHECK-NEXT: "ports": [ // CHECK-NEXT: { // CHECK-NEXT: "name": "Send", -// CHECK-NEXT: "type": { -// CHECK-NEXT: "circt_name": "!esi.bundle<[!esi.channel from \"send\"]>" -// CHECK-NEXT: } +// CHECK-NEXT: "type": "!esi.bundle<[!esi.channel from \"send\"]>" // CHECK-NEXT: }, // CHECK-NEXT: { // CHECK-NEXT: "name": "Recv", -// CHECK-NEXT: "type": { -// CHECK-NEXT: "circt_name": "!esi.bundle<[!esi.channel to \"recv\"]>" -// CHECK-NEXT: } +// CHECK-NEXT: "type": "!esi.bundle<[!esi.channel to \"recv\"]>" // CHECK-NEXT: }, // CHECK-NEXT: { // CHECK-NEXT: "name": "SendI0", -// CHECK-NEXT: "type": { -// CHECK-NEXT: "circt_name": "!esi.bundle<[!esi.channel from \"send\"]>" -// CHECK-NEXT: } +// CHECK-NEXT: "type": "!esi.bundle<[!esi.channel from \"send\"]>" // CHECK-NEXT: } // CHECK-NEXT: ] // CHECK-NEXT: }, @@ -374,41 +367,39 @@ hw.module @top(in %clk: !seq.clock, in %rst: i1) { // CHECK-NEXT: { // CHECK-NEXT: "name": "call", // CHECK-NEXT: "type": { -// CHECK-NEXT: "type": { -// CHECK-NEXT: "channels": [ -// CHECK-NEXT: { -// CHECK-NEXT: "direction": "to", -// CHECK-NEXT: "name": "arg", -// CHECK-NEXT: "type": { -// CHECK-NEXT: "circt_name": "!esi.channel", +// CHECK-NEXT: "channels": [ +// CHECK-NEXT: { +// CHECK-NEXT: "direction": "to", +// CHECK-NEXT: "name": "arg", +// CHECK-NEXT: "type": { +// CHECK-NEXT: "dialect": "esi", +// CHECK-NEXT: "id": "!esi.channel", +// CHECK-NEXT: "inner": { // CHECK-NEXT: "dialect": "esi", -// CHECK-NEXT: "inner": { -// CHECK-NEXT: "circt_name": "!esi.any", -// CHECK-NEXT: "dialect": "esi", -// CHECK-NEXT: "mnemonic": "any" -// CHECK-NEXT: }, -// CHECK-NEXT: "mnemonic": "channel" -// CHECK-NEXT: } -// CHECK-NEXT: }, -// CHECK-NEXT: { -// CHECK-NEXT: "direction": "from", -// CHECK-NEXT: "name": "result", -// CHECK-NEXT: "type": { -// CHECK-NEXT: "circt_name": "!esi.channel", +// CHECK-NEXT: "id": "!esi.any", +// CHECK-NEXT: "mnemonic": "any" +// CHECK-NEXT: }, +// CHECK-NEXT: "mnemonic": "channel" +// CHECK-NEXT: } +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "direction": "from", +// CHECK-NEXT: "name": "result", +// CHECK-NEXT: "type": { +// CHECK-NEXT: "dialect": "esi", +// CHECK-NEXT: "id": "!esi.channel", +// CHECK-NEXT: "inner": { // CHECK-NEXT: "dialect": "esi", -// CHECK-NEXT: "inner": { -// CHECK-NEXT: "circt_name": "!esi.any", -// CHECK-NEXT: "dialect": "esi", -// CHECK-NEXT: "mnemonic": "any" -// CHECK-NEXT: }, -// CHECK-NEXT: "mnemonic": "channel" -// CHECK-NEXT: } +// CHECK-NEXT: "id": "!esi.any", +// CHECK-NEXT: "mnemonic": "any" +// CHECK-NEXT: }, +// CHECK-NEXT: "mnemonic": "channel" // CHECK-NEXT: } -// CHECK-NEXT: ], -// CHECK-NEXT: "circt_name": "!esi.bundle<[!esi.channel to \"arg\", !esi.channel from \"result\"]>", -// CHECK-NEXT: "dialect": "esi", -// CHECK-NEXT: "mnemonic": "bundle" -// CHECK-NEXT: } +// CHECK-NEXT: } +// CHECK-NEXT: ], +// CHECK-NEXT: "dialect": "esi", +// CHECK-NEXT: "id": "!esi.bundle<[!esi.channel to \"arg\", !esi.channel from \"result\"]>", +// CHECK-NEXT: "mnemonic": "bundle" // CHECK-NEXT: } // CHECK-NEXT: } // CHECK-NEXT: ] @@ -422,13 +413,13 @@ hw.module @top(in %clk: !seq.clock, in %rst: i1) { // CHECK-NEXT: "direction": "to", // CHECK-NEXT: "name": "recv", // CHECK-NEXT: "type": { -// CHECK-NEXT: "circt_name": "!esi.channel", // CHECK-NEXT: "dialect": "esi", // CHECK-NEXT: "hw_bitwidth": 8, +// CHECK-NEXT: "id": "!esi.channel", // CHECK-NEXT: "inner": { -// CHECK-NEXT: "circt_name": "i8", // CHECK-NEXT: "dialect": "builtin", // CHECK-NEXT: "hw_bitwidth": 8, +// CHECK-NEXT: "id": "i8", // CHECK-NEXT: "mnemonic": "int", // CHECK-NEXT: "signedness": "signless" // CHECK-NEXT: }, @@ -436,8 +427,8 @@ hw.module @top(in %clk: !seq.clock, in %rst: i1) { // CHECK-NEXT: } // CHECK-NEXT: } // CHECK-NEXT: ], -// CHECK-NEXT: "circt_name": "!esi.bundle<[!esi.channel to \"recv\"]>", // CHECK-NEXT: "dialect": "esi", +// CHECK-NEXT: "id": "!esi.bundle<[!esi.channel to \"recv\"]>", // CHECK-NEXT: "mnemonic": "bundle" // CHECK-NEXT: }, // CHECK-NEXT: { @@ -446,13 +437,13 @@ hw.module @top(in %clk: !seq.clock, in %rst: i1) { // CHECK-NEXT: "direction": "from", // CHECK-NEXT: "name": "send", // CHECK-NEXT: "type": { -// CHECK-NEXT: "circt_name": "!esi.channel", // CHECK-NEXT: "dialect": "esi", // CHECK-NEXT: "hw_bitwidth": 8, +// CHECK-NEXT: "id": "!esi.channel", // CHECK-NEXT: "inner": { -// CHECK-NEXT: "circt_name": "i8", // CHECK-NEXT: "dialect": "builtin", // CHECK-NEXT: "hw_bitwidth": 8, +// CHECK-NEXT: "id": "i8", // CHECK-NEXT: "mnemonic": "int", // CHECK-NEXT: "signedness": "signless" // CHECK-NEXT: }, @@ -460,8 +451,8 @@ hw.module @top(in %clk: !seq.clock, in %rst: i1) { // CHECK-NEXT: } // CHECK-NEXT: } // CHECK-NEXT: ], -// CHECK-NEXT: "circt_name": "!esi.bundle<[!esi.channel from \"send\"]>", // CHECK-NEXT: "dialect": "esi", +// CHECK-NEXT: "id": "!esi.bundle<[!esi.channel from \"send\"]>", // CHECK-NEXT: "mnemonic": "bundle" // CHECK-NEXT: }, // CHECK-NEXT: { @@ -470,13 +461,13 @@ hw.module @top(in %clk: !seq.clock, in %rst: i1) { // CHECK-NEXT: "direction": "from", // CHECK-NEXT: "name": "send", // CHECK-NEXT: "type": { -// CHECK-NEXT: "circt_name": "!esi.channel", // CHECK-NEXT: "dialect": "esi", // CHECK-NEXT: "hw_bitwidth": 0, +// CHECK-NEXT: "id": "!esi.channel", // CHECK-NEXT: "inner": { -// CHECK-NEXT: "circt_name": "i0", // CHECK-NEXT: "dialect": "builtin", // CHECK-NEXT: "hw_bitwidth": 0, +// CHECK-NEXT: "id": "i0", // CHECK-NEXT: "mnemonic": "int", // CHECK-NEXT: "signedness": "signless" // CHECK-NEXT: }, @@ -484,8 +475,8 @@ hw.module @top(in %clk: !seq.clock, in %rst: i1) { // CHECK-NEXT: } // CHECK-NEXT: } // CHECK-NEXT: ], -// CHECK-NEXT: "circt_name": "!esi.bundle<[!esi.channel from \"send\"]>", // CHECK-NEXT: "dialect": "esi", +// CHECK-NEXT: "id": "!esi.bundle<[!esi.channel from \"send\"]>", // CHECK-NEXT: "mnemonic": "bundle" // CHECK-NEXT: }, // CHECK-NEXT: { @@ -494,13 +485,13 @@ hw.module @top(in %clk: !seq.clock, in %rst: i1) { // CHECK-NEXT: "direction": "to", // CHECK-NEXT: "name": "arg", // CHECK-NEXT: "type": { -// CHECK-NEXT: "circt_name": "!esi.channel", // CHECK-NEXT: "dialect": "esi", // CHECK-NEXT: "hw_bitwidth": 16, +// CHECK-NEXT: "id": "!esi.channel", // CHECK-NEXT: "inner": { -// CHECK-NEXT: "circt_name": "i16", // CHECK-NEXT: "dialect": "builtin", // CHECK-NEXT: "hw_bitwidth": 16, +// CHECK-NEXT: "id": "i16", // CHECK-NEXT: "mnemonic": "int", // CHECK-NEXT: "signedness": "signless" // CHECK-NEXT: }, @@ -511,13 +502,13 @@ hw.module @top(in %clk: !seq.clock, in %rst: i1) { // CHECK-NEXT: "direction": "from", // CHECK-NEXT: "name": "result", // CHECK-NEXT: "type": { -// CHECK-NEXT: "circt_name": "!esi.channel", // CHECK-NEXT: "dialect": "esi", // CHECK-NEXT: "hw_bitwidth": 16, +// CHECK-NEXT: "id": "!esi.channel", // CHECK-NEXT: "inner": { -// CHECK-NEXT: "circt_name": "i16", // CHECK-NEXT: "dialect": "builtin", // CHECK-NEXT: "hw_bitwidth": 16, +// CHECK-NEXT: "id": "i16", // CHECK-NEXT: "mnemonic": "int", // CHECK-NEXT: "signedness": "signless" // CHECK-NEXT: }, @@ -525,9 +516,23 @@ hw.module @top(in %clk: !seq.clock, in %rst: i1) { // CHECK-NEXT: } // CHECK-NEXT: } // CHECK-NEXT: ], -// CHECK-NEXT: "circt_name": "!esi.bundle<[!esi.channel to \"arg\", !esi.channel from \"result\"]>", // CHECK-NEXT: "dialect": "esi", +// CHECK-NEXT: "id": "!esi.bundle<[!esi.channel to \"arg\", !esi.channel from \"result\"]>", // CHECK-NEXT: "mnemonic": "bundle" +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "dialect": "builtin", +// CHECK-NEXT: "hw_bitwidth": 64, +// CHECK-NEXT: "id": "i64", +// CHECK-NEXT: "mnemonic": "int", +// CHECK-NEXT: "signedness": "signless" +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "dialect": "builtin", +// CHECK-NEXT: "hw_bitwidth": 32, +// CHECK-NEXT: "id": "ui32", +// CHECK-NEXT: "mnemonic": "int", +// CHECK-NEXT: "signedness": "unsigned" // CHECK-NEXT: } // CHECK-NEXT: ] // CHECK-NEXT: } From 736142c6e8965d531f1ec47454349c530f2632df Mon Sep 17 00:00:00 2001 From: Prithayan Barua Date: Mon, 12 Aug 2024 10:07:24 -0400 Subject: [PATCH 076/119] [CreateSifiveMetadata] Update memory hierarchy paths to be pre-extrction (#7491) The `CreateSifiveMetadata` pass was creating hierarchy paths to the memory module pre-extraction. The `om.path` was being updated as the memory was extracted in the following passes. But recently we realized this was a bug, as the downstream tools consuming the metadata files, were actually expecting the pre-extraction hierarchy paths to the memory. This PR fixes the path, to terminate at the parent of the module that instantiates the memory and encodes the pre-extraction memory instance as a string. The tool that will parse the final `mlir` must now construct the actual pre-extraction path from the two lists, by appending the pre-extraction instance name to the path. --- .../Transforms/CreateSiFiveMetadata.cpp | 122 +++++++++++------- test/Dialect/FIRRTL/SFCTests/directories.fir | 3 +- test/Dialect/FIRRTL/emit-metadata.mlir | 27 +++- test/firtool/prefixMemory.fir | 6 +- 4 files changed, 97 insertions(+), 61 deletions(-) diff --git a/lib/Dialect/FIRRTL/Transforms/CreateSiFiveMetadata.cpp b/lib/Dialect/FIRRTL/Transforms/CreateSiFiveMetadata.cpp index 30aaee22ee90..d84150e9798f 100644 --- a/lib/Dialect/FIRRTL/Transforms/CreateSiFiveMetadata.cpp +++ b/lib/Dialect/FIRRTL/Transforms/CreateSiFiveMetadata.cpp @@ -46,7 +46,7 @@ struct ObjectModelIR { ObjectModelIR( CircuitOp circtOp, FModuleOp dutMod, InstanceGraph &instanceGraph, DenseMap &moduleNamespaces) - : circtOp(circtOp), dutMod(dutMod), + : context(circtOp->getContext()), circtOp(circtOp), dutMod(dutMod), circtNamespace(CircuitNamespace(circtOp)), instancePathCache(InstancePathCache(instanceGraph)), moduleNamespaces(moduleNamespaces) {} @@ -54,20 +54,22 @@ struct ObjectModelIR { // Add the tracker annotation to the op and get a PathOp to the op. PathOp createPathRef(Operation *op, hw::HierPathOp nla, mlir::ImplicitLocOpBuilder &builderOM) { - auto *context = op->getContext(); - NamedAttrList fields; auto id = DistinctAttr::create(UnitAttr::get(context)); - fields.append("id", id); - fields.append("class", StringAttr::get(context, "circt.tracker")); - if (nla) - fields.append("circt.nonlocal", mlir::FlatSymbolRefAttr::get(nla)); - AnnotationSet annos(op); - annos.addAnnotations(DictionaryAttr::get(context, fields)); - annos.applyToOperation(op); TargetKind kind = TargetKind::Reference; - if (isa(op)) - kind = TargetKind::Instance; + // If op is null, then create an empty path. + if (op) { + NamedAttrList fields; + fields.append("id", id); + fields.append("class", StringAttr::get(context, "circt.tracker")); + if (nla) + fields.append("circt.nonlocal", mlir::FlatSymbolRefAttr::get(nla)); + AnnotationSet annos(op); + annos.addAnnotations(DictionaryAttr::get(context, fields)); + annos.applyToOperation(op); + if (isa(op)) + kind = TargetKind::Instance; + } // Create the path operation. return builderOM.create(kind, id); @@ -101,7 +103,6 @@ struct ObjectModelIR { } void createMemorySchema() { - auto *context = circtOp.getContext(); auto unknownLoc = mlir::UnknownLoc::get(context); auto builderOM = mlir::ImplicitLocOpBuilder::atBlockEnd( @@ -121,7 +122,7 @@ struct ObjectModelIR { buildSimpleClassOp(builderOM, unknownLoc, "ExtraPortsMemorySchema", extraPortFields, extraPortsType); - mlir::Type classFieldTypes[12] = { + mlir::Type classFieldTypes[13] = { StringType::get(context), FIntegerType::get(context), FIntegerType::get(context), @@ -133,9 +134,11 @@ struct ObjectModelIR { FIntegerType::get(context), ListType::get(context, cast(PathType::get(context))), BoolType::get(context), - ListType::get(context, - cast(detail::getInstanceTypeForClassLike( - extraPortsClass)))}; + ListType::get( + context, cast( + detail::getInstanceTypeForClassLike(extraPortsClass))), + ListType::get(context, cast(StringType::get(context))), + }; memorySchemaClass = buildSimpleClassOp(builderOM, unknownLoc, "MemorySchema", @@ -149,7 +152,6 @@ struct ObjectModelIR { } void createRetimeModulesSchema() { - auto *context = circtOp.getContext(); auto unknownLoc = mlir::UnknownLoc::get(context); auto builderOM = mlir::ImplicitLocOpBuilder::atBlockEnd( unknownLoc, circtOp.getBodyBlock()); @@ -189,7 +191,6 @@ struct ObjectModelIR { } void addBlackBoxModulesSchema() { - auto *context = circtOp.getContext(); auto unknownLoc = mlir::UnknownLoc::get(context); auto builderOM = mlir::ImplicitLocOpBuilder::atBlockEnd( unknownLoc, circtOp.getBodyBlock()); @@ -232,7 +233,6 @@ struct ObjectModelIR { createMemorySchema(); auto builderOM = mlir::ImplicitLocOpBuilder::atBlockEnd( mem.getLoc(), memoryMetadataClass.getBodyBlock()); - auto *context = builderOM.getContext(); auto createConstField = [&](Attribute constVal) -> Value { if (auto boolConstant = dyn_cast_or_null(constVal)) return builderOM.create(boolConstant); @@ -246,32 +246,52 @@ struct ObjectModelIR { auto memPaths = instancePathCache.getAbsolutePaths(mem); SmallVector memoryHierPaths; - for (auto memPath : memPaths) { - Operation *finalInst = memPath.leaf(); - SmallVector namepath; - bool foundDut = dutMod == nullptr; - for (auto inst : memPath) { - if (!foundDut) - if (inst->getParentOfType() == dutMod) - foundDut = true; - if (!foundDut) - continue; + SmallVector finalInstanceNames; + // Memory hierarchy is relevant only for memories under DUT. + if (inDut) { + for (auto memPath : memPaths) { + { + igraph::InstanceOpInterface finalInst = memPath.leaf(); + finalInstanceNames.emplace_back(builderOM.create( + finalInst.getInstanceNameAttr())); + } + SmallVector namepath; + bool foundDut = dutMod == nullptr; + // The hierpath will be created to the pre-extracted + // instance, thus drop the leaf instance of the path, which can be + // extracted in subsequent passes. + igraph::InstanceOpInterface preExtractedLeafInstance; + for (auto inst : llvm::drop_end(memPath)) { + if (!foundDut) + if (inst->getParentOfType() == dutMod) + foundDut = true; + if (!foundDut) + continue; - namepath.emplace_back(firrtl::getInnerRefTo( - inst, [&](auto mod) -> hw::InnerSymbolNamespace & { - return getModuleNamespace(mod); - })); - } - if (namepath.empty()) - continue; - auto nla = nlaBuilder.create( - mem->getLoc(), - nlaBuilder.getStringAttr(circtNamespace.newName("memNLA")), - nlaBuilder.getArrayAttr(namepath)); + namepath.emplace_back(firrtl::getInnerRefTo( + inst, [&](auto mod) -> hw::InnerSymbolNamespace & { + return getModuleNamespace(mod); + })); + preExtractedLeafInstance = inst; + } + PathOp pathRef; + if (!namepath.empty()) { + auto nla = nlaBuilder.create( + mem->getLoc(), + nlaBuilder.getStringAttr(circtNamespace.newName("memNLA")), + nlaBuilder.getArrayAttr(namepath)); + pathRef = createPathRef(preExtractedLeafInstance, nla, builderOM); + } else { + pathRef = createPathRef({}, {}, builderOM); + } - // Create the path operation. - memoryHierPaths.emplace_back(createPathRef(finalInst, nla, builderOM)); + // Create the path operation. + memoryHierPaths.push_back(pathRef); + } } + auto finalInstNamesList = builderOM.create( + ListType::get(context, cast(StringType::get(context))), + finalInstanceNames); auto hierpaths = builderOM.create( ListType::get(context, cast(PathType::get(context))), memoryHierPaths); @@ -313,10 +333,13 @@ struct ObjectModelIR { .Case("writeLatency", mem.getWriteLatencyAttr()) .Case("hierarchy", {}) .Case("inDut", BoolAttr::get(context, inDut)) - .Case("extraPorts", {})); + .Case("extraPorts", {}) + .Case("preExtInstName", {})); if (!propVal) { if (field.value() == "hierarchy") propVal = hierpaths; + else if (field.value() == "preExtInstName") + propVal = finalInstNamesList; else propVal = extraPorts; } @@ -408,7 +431,6 @@ struct ObjectModelIR { // Create the path ref op and record it. pathOpsToDut.emplace_back(createPathRef(leafInst, nla, builder)); } - auto *context = builder.getContext(); // Create the list of paths op and add it as a field of the class. auto pathList = builder.create( ListType::get(context, cast(PathType::get(context))), @@ -425,6 +447,7 @@ struct ObjectModelIR { hw::InnerSymbolNamespace &getModuleNamespace(FModuleLike module) { return moduleNamespaces.try_emplace(module, module).first->second; } + MLIRContext *context; CircuitOp circtOp; FModuleOp dutMod; CircuitNamespace circtNamespace; @@ -435,10 +458,11 @@ struct ObjectModelIR { ClassOp memoryMetadataClass; ClassOp retimeModulesMetadataClass, retimeModulesSchemaClass; ClassOp blackBoxModulesSchemaClass, blackBoxMetadataClass; - StringRef memoryParamNames[12] = { - "name", "depth", "width", "maskBits", - "readPorts", "writePorts", "readwritePorts", "writeLatency", - "readLatency", "hierarchy", "inDut", "extraPorts"}; + StringRef memoryParamNames[13] = { + "name", "depth", "width", "maskBits", + "readPorts", "writePorts", "readwritePorts", "writeLatency", + "readLatency", "hierarchy", "inDut", "extraPorts", + "preExtInstName"}; StringRef retimeModulesParamNames[1] = {"moduleName"}; StringRef blackBoxModulesParamNames[1] = {"moduleName"}; llvm::SmallDenseSet blackboxModules; diff --git a/test/Dialect/FIRRTL/SFCTests/directories.fir b/test/Dialect/FIRRTL/SFCTests/directories.fir index c1d576c99ec4..1c4c13fcd426 100644 --- a/test/Dialect/FIRRTL/SFCTests/directories.fir +++ b/test/Dialect/FIRRTL/SFCTests/directories.fir @@ -151,7 +151,7 @@ circuit TestHarness: ; MLIR_OUT: om.class.field @[[V3:.+]], %5 : !om.class.type<@SitestBlackBoxModulesSchema> ; MLIR_OUT: } -; MLIR_OUT: om.class @MemorySchema(%basepath: !om.basepath, %name_in: !om.string, %depth_in: !om.integer, %width_in: !om.integer, %maskBits_in: !om.integer, %readPorts_in: !om.integer, %writePorts_in: !om.integer, %readwritePorts_in: !om.integer, %writeLatency_in: !om.integer, %readLatency_in: !om.integer, %hierarchy_in: !om.list, %inDut_in: i1, %extraPorts_in: !om.list>) +; MLIR_OUT: om.class @MemorySchema(%basepath: !om.basepath, %name_in: !om.string, %depth_in: !om.integer, %width_in: !om.integer, %maskBits_in: !om.integer, %readPorts_in: !om.integer, %writePorts_in: !om.integer, %readwritePorts_in: !om.integer, %writeLatency_in: !om.integer, %readLatency_in: !om.integer, %hierarchy_in: !om.list, %inDut_in: i1, %extraPorts_in: !om.list> ; MLIR_OUT: om.class.field @name, %name_in : !om.string ; MLIR_OUT: om.class.field @depth, %depth_in : !om.integer ; MLIR_OUT: om.class.field @width, %width_in : !om.integer @@ -163,7 +163,6 @@ circuit TestHarness: ; MLIR_OUT: om.class.field @readLatency, %readLatency_in : !om.integer ; MLIR_OUT: om.class.field @hierarchy, %hierarchy_in : !om.list ; MLIR_OUT: om.class.field @extraPorts, %extraPorts_in : !om.list> -; MLIR_OUT: } ; MLIR_OUT: om.class @MemoryMetadata(%basepath: !om.basepath) ; MLIR_OUT: om.path_create instance %basepath @memNLA ; MLIR_OUT: om.list_create diff --git a/test/Dialect/FIRRTL/emit-metadata.mlir b/test/Dialect/FIRRTL/emit-metadata.mlir index 5f4dec917531..3759433e4f08 100644 --- a/test/Dialect/FIRRTL/emit-metadata.mlir +++ b/test/Dialect/FIRRTL/emit-metadata.mlir @@ -187,7 +187,7 @@ firrtl.circuit "top" // CHECK-LABEL: firrtl.circuit "OneMemory" firrtl.circuit "OneMemory" { firrtl.module @OneMemory() { - %0:5= firrtl.instance MWrite_ext sym @MWrite_ext_0 @MWrite_ext(in W0_addr: !firrtl.uint<4>, in W0_en: !firrtl.uint<1>, in W0_clk: !firrtl.clock, in W0_data: !firrtl.uint<42>, in user_input: !firrtl.uint<5>) + %0:5= firrtl.instance MWrite_ext_inst sym @MWrite_ext_0 @MWrite_ext(in W0_addr: !firrtl.uint<4>, in W0_en: !firrtl.uint<1>, in W0_clk: !firrtl.clock, in W0_data: !firrtl.uint<42>, in user_input: !firrtl.uint<5>) } firrtl.memmodule @MWrite_ext(in W0_addr: !firrtl.uint<4>, in W0_en: !firrtl.uint<1>, in W0_clk: !firrtl.clock, in W0_data: !firrtl.uint<42>, in user_input: !firrtl.uint<5>) attributes {dataWidth = 42 : ui32, depth = 12 : ui64, extraPorts = [{direction = "input", name = "user_input", width = 5 : ui32}], maskBits = 1 : ui32, numReadPorts = 0 : ui32, numReadWritePorts = 0 : ui32, numWritePorts = 1 : ui32, readLatency = 1 : ui32, writeLatency = 1 : ui32} @@ -203,9 +203,11 @@ firrtl.circuit "OneMemory" { // CHECK: firrtl.propassign %writeLatency, %writeLatency_in : !firrtl.integer // CHECK: firrtl.propassign %readLatency, %readLatency_in : !firrtl.integer // CHECK: firrtl.propassign %hierarchy, %hierarchy_in : !firrtl.list - + // CHECK: firrtl.propassign %preExtInstName, %preExtInstName_in : !firrtl.list // CHECK: firrtl.class @MemoryMetadata - // CHECK: firrtl.path instance distinct[0]<> + // CHECK: %[[V0:.+]] = firrtl.string "MWrite_ext_inst" + // CHECK: %[[V1:.+]] = firrtl.path reference distinct[0]<> + // CHECK: %[[V2:.+]] = firrtl.list.create %[[V0]] : !firrtl.list // CHECK: %MWrite_ext = firrtl.object @MemorySchema // CHECK: firrtl.string "MWrite_ext" // CHECK: firrtl.object.subfield %MWrite_ext[name_in] @@ -226,11 +228,13 @@ firrtl.circuit "OneMemory" { // CHECK: firrtl.integer 1 // CHECK: firrtl.object.subfield %MWrite_ext[readLatency_in] // CHECK: firrtl.object.subfield %MWrite_ext[hierarchy_in] + // CHECK: %[[V33:.+]] = firrtl.object.subfield %MWrite_ext[preExtInstName_in] + // CHECK: firrtl.propassign %[[V33]], %[[V2]] : !firrtl.list // CHECK: firrtl.propassign %MWrite_ext_field, %MWrite_ext // CHECK: } // CHECK: emit.file "metadata{{/|\\\\}}seq_mems.json" { - // CHECK-NEXT{LITERAL}: sv.verbatim "[\0A {\0A \22module_name\22: \22{{0}}\22,\0A \22depth\22: 12,\0A \22width\22: 42,\0A \22masked\22: false,\0A \22read\22: 0,\0A \22write\22: 1,\0A \22readwrite\22: 0,\0A \22extra_ports\22: [\0A {\0A \22name\22: \22user_input\22,\0A \22direction\22: \22input\22,\0A \22width\22: 5\0A }\0A ],\0A \22hierarchy\22: [\0A \22{{1}}.MWrite_ext\22\0A ]\0A }\0A]" + // CHECK-NEXT{LITERAL}: sv.verbatim "[\0A {\0A \22module_name\22: \22{{0}}\22,\0A \22depth\22: 12,\0A \22width\22: 42,\0A \22masked\22: false,\0A \22read\22: 0,\0A \22write\22: 1,\0A \22readwrite\22: 0,\0A \22extra_ports\22: [\0A {\0A \22name\22: \22user_input\22,\0A \22direction\22: \22input\22,\0A \22width\22: 5\0A }\0A ],\0A \22hierarchy\22: [\0A \22{{1}}.MWrite_ext_inst\22\0A ]\0A }\0A]" // CHECK-SAME: {symbols = [@MWrite_ext, @OneMemory]} // CHECK-NEXT: } @@ -272,8 +276,9 @@ firrtl.circuit "ReadOnlyMemory" { // CHECK-LABEL: firrtl.circuit "top" firrtl.circuit "top" { // CHECK: hw.hierpath @[[DUTNLA:.+]] [@top::@sym] + // CHECK-LABEL: firrtl.module @top firrtl.module @top() { - // CHECK: firrtl.instance dut sym @[[DUT_SYM:.+]] {annotations = [{circt.nonlocal = @dutNLA, class = "circt.tracker", id = distinct[0]<>}]} @DUT() + // CHECK: firrtl.instance dut sym @[[DUT_SYM:.+]] {annotations = [{circt.nonlocal = @dutNLA, class = "circt.tracker", id = distinct[0]<>}]} @DUT() firrtl.instance dut @DUT() firrtl.instance mem1 @Mem1() firrtl.instance mem2 @Mem2() @@ -284,9 +289,11 @@ firrtl.circuit "top" { firrtl.module private @Mem2() { %0:4 = firrtl.instance head_0_ext @head_0_ext(in W0_addr: !firrtl.uint<5>, in W0_en: !firrtl.uint<1>, in W0_clk: !firrtl.clock, in W0_data: !firrtl.uint<5>) } + // CHECK-LABEL: firrtl.module private @DUT( firrtl.module private @DUT() attributes {annotations = [ {class = "sifive.enterprise.firrtl.MarkDUTAnnotation"}]} { - // CHECK: firrtl.instance mem1 sym @[[MEM1_SYM:.+]] @Mem( + // CHECK: firrtl.instance mem1 sym @[[MEM1_SYM:.+]] {annotations = [{ + // CHECK-SAME: circt.nonlocal = @memNLA, class = "circt.tracker", id = distinct firrtl.instance mem1 @Mem() } firrtl.module private @Mem() { @@ -298,6 +305,12 @@ firrtl.circuit "top" { firrtl.memmodule private @memory_ext(in R0_addr: !firrtl.uint<4>, in R0_en: !firrtl.uint<1>, in R0_clk: !firrtl.clock, out R0_data: !firrtl.uint<8>, in RW0_addr: !firrtl.uint<4>, in RW0_en: !firrtl.uint<1>, in RW0_clk: !firrtl.clock, in RW0_wmode: !firrtl.uint<1>, in RW0_wdata: !firrtl.uint<8>, out RW0_rdata: !firrtl.uint<8>) attributes {dataWidth = 8 : ui32, depth = 16 : ui64, extraPorts = [], maskBits = 1 : ui32, numReadPorts = 1 : ui32, numReadWritePorts = 1 : ui32, numWritePorts = 0 : ui32, readLatency = 1 : ui32, writeLatency = 1 : ui32} firrtl.memmodule private @dumm_ext(in R0_addr: !firrtl.uint<5>, in R0_en: !firrtl.uint<1>, in R0_clk: !firrtl.clock, out R0_data: !firrtl.uint<5>, in W0_addr: !firrtl.uint<5>, in W0_en: !firrtl.uint<1>, in W0_clk: !firrtl.clock, in W0_data: !firrtl.uint<5>) attributes {dataWidth = 5 : ui32, depth = 20 : ui64, extraPorts = [], maskBits = 1 : ui32, numReadPorts = 1 : ui32, numReadWritePorts = 0 : ui32, numWritePorts = 1 : ui32, readLatency = 1 : ui32, writeLatency = 1 : ui32} + // CHECK-LABEL: firrtl.class @MemoryMetadata + // CHECK: %[[V2:.+]] = firrtl.string "memory_ext" + // CHECK: %[[V3:.+]] = firrtl.path instance distinct[1]<> + // CHECK: firrtl.list.create %[[V2]] : !firrtl.list + // CHECK: firrtl.list.create %[[V3]] : !firrtl.list + // CHECK: emit.file "metadata{{/|\\\\}}seq_mems.json" { // CHECK-NEXT{LITERAL}: sv.verbatim "[\0A {\0A \22module_name\22: \22{{0}}\22,\0A \22depth\22: 16,\0A \22width\22: 8,\0A \22masked\22: false,\0A \22read\22: 1,\0A \22write\22: 0,\0A \22readwrite\22: 1,\0A \22extra_ports\22: [],\0A \22hierarchy\22: [\0A \22{{3}}.{{4}}.memory_ext\22\0A ]\0A },\0A {\0A \22module_name\22: \22{{5}}\22,\0A \22depth\22: 20,\0A \22width\22: 5,\0A \22masked\22: false,\0A \22read\22: 1,\0A \22write\22: 1,\0A \22readwrite\22: 0,\0A \22extra_ports\22: [],\0A \22hierarchy\22: [\0A \22{{3}}.{{4}}.dumm_ext\22\0A ]\0A }\0A]" // CHECK-SAME: {symbols = [@memory_ext, @top, #hw.innerNameRef<@top::@[[DUT_SYM]]>, @DUT, #hw.innerNameRef<@DUT::@[[MEM1_SYM]]>, @dumm_ext]} @@ -308,7 +321,7 @@ firrtl.circuit "top" { // CHECK-SAME: {symbols = [@head_ext, @head_0_ext, @memory_ext, @dumm_ext]} // CHECK-NEXT: } - // CHECK: firrtl.class @SiFive_Metadata + // CHECK-LABEL: firrtl.class @SiFive_Metadata // CHECK: %[[V0:.+]] = firrtl.path instance distinct[0]<> // CHECK-NEXT: %[[V1:.+]] = firrtl.list.create %[[V0]] : !firrtl.list // CHECK-NEXT: firrtl.propassign %dutModulePath_field_1, %[[V1]] : !firrtl.list diff --git a/test/firtool/prefixMemory.fir b/test/firtool/prefixMemory.fir index 253b0c31a964..974cfee939ef 100644 --- a/test/firtool/prefixMemory.fir +++ b/test/firtool/prefixMemory.fir @@ -37,7 +37,7 @@ circuit Foo : %[[ input writeAddr : UInt<3> input writeData : UInt<32> - ; REPL-FIR: firrtl.instance mem sym @{{[^ ]+}} @prefix1_mem + ; REPL-FIR: firrtl.instance mem sym @{{[^ ]+}} {annotations = [{circt.nonlocal = @memNLA, class = "circt.tracker", id = distinct[0]<>}]} @prefix1_mem ; REPL-HW: hw.instance "mem" sym @{{[^ ]+}} @prefix1_mem ; SIM-FIR: firrtl.mem ; SIM-FIR-SAME: name = "mem" @@ -69,7 +69,6 @@ circuit Foo : %[[ mem.MPORT.data <= writeData mem.MPORT.mask <= UInt<1>(0h1) - ; CHECK-FIR-LABEL: firrtl.module private @prefix2_Baz ; CHECK-HW-LABEL: hw.module private @prefix2_Baz module Baz : input clock : Clock @@ -80,7 +79,8 @@ circuit Foo : %[[ input writeAddr : UInt<3> input writeData : UInt<32> - ; REPL-FIR: firrtl.instance mem sym @{{[^ ]+}} @prefix2_mem + ; REPL-FIR: firrtl.module private @prefix2_Baz + ; REPL-FIR-NEXT: firrtl.instance mem sym @sym {annotations = [{circt.nonlocal = @memNLA_0, class = "circt.tracker", id = distinct[1]<>}]} @prefix2_mem_0 ; REPL-HW: hw.instance "mem" sym @{{[^ ]+}} @prefix2_mem ; SIM-FIR: firrtl.mem ; SIM-FIR-SAME: name = "mem" From 128d91e85c47e60f858ca2e1787edcb7243d1f0d Mon Sep 17 00:00:00 2001 From: John Demme Date: Mon, 12 Aug 2024 16:19:55 +0200 Subject: [PATCH 077/119] [ESI][Runtime] Parse and expose manifest constants (#7492) Access per-module constants from the ModuleInfo class. --- .../Dialect/ESI/runtime/loopback.mlir | 12 +++ .../Dialect/ESI/runtime/loopback.mlir.py | 7 ++ .../ESI/runtime/cpp/include/esi/Common.h | 7 ++ lib/Dialect/ESI/runtime/cpp/lib/Manifest.cpp | 85 ++++++++++++++----- .../runtime/python/esiaccel/esiCppAccel.cpp | 45 ++++++++++ 5 files changed, 133 insertions(+), 23 deletions(-) diff --git a/integration_test/Dialect/ESI/runtime/loopback.mlir b/integration_test/Dialect/ESI/runtime/loopback.mlir index f5eec8e5e390..58d039724549 100644 --- a/integration_test/Dialect/ESI/runtime/loopback.mlir +++ b/integration_test/Dialect/ESI/runtime/loopback.mlir @@ -3,6 +3,7 @@ // RUN: circt-opt %s --esi-connect-services --esi-appid-hier=top=top --esi-build-manifest=top=top --esi-clean-metadata > %t4.mlir // RUN: circt-opt %t4.mlir --lower-esi-to-physical --lower-esi-bundles --lower-esi-ports --lower-esi-to-hw=platform=cosim --lower-seq-to-sv --lower-hwarith-to-hw --canonicalize --export-split-verilog -o %t3.mlir // RUN: cd .. +// RUN: esiquery trace w:%t6/esi_system_manifest.json info | FileCheck %s --check-prefix=QUERY-INFO // RUN: esiquery trace w:%t6/esi_system_manifest.json hier | FileCheck %s --check-prefix=QUERY-HIER // RUN: %python %s.py trace w:%t6/esi_system_manifest.json // RUN: esi-cosim.py --source %t6 --top top -- %python %s.py cosim env @@ -95,6 +96,7 @@ hw.module @CallableFunc1() { } esi.manifest.sym @Loopback name "LoopbackIP" version "v0.0" summary "IP which simply echos bytes" {foo=1} +esi.manifest.constants @Loopback {depth=5:ui32} hw.module @top(in %clk: !seq.clock, in %rst: i1) { esi.service.instance #esi.appid<"cosim"> svc @HostComms impl as "cosim" (%clk, %rst) : (!seq.clock, i1) -> () @@ -107,6 +109,16 @@ hw.module @top(in %clk: !seq.clock, in %rst: i1) { hw.instance "loopback_array" @LoopbackArray() -> () } +// QUERY-INFO: API version: 0 +// QUERY-INFO: ******************************** +// QUERY-INFO: * Module information +// QUERY-INFO: ******************************** +// QUERY-INFO: - LoopbackIP v0.0 : IP which simply echos bytes +// QUERY-INFO: Constants: +// QUERY-INFO: depth: 5 +// QUERY-INFO: Extra metadata: +// QUERY-INFO: foo: 1 + // QUERY-HIER: ******************************** // QUERY-HIER: * Design hierarchy // QUERY-HIER: ******************************** diff --git a/integration_test/Dialect/ESI/runtime/loopback.mlir.py b/integration_test/Dialect/ESI/runtime/loopback.mlir.py index a942a45b232b..091b02511599 100644 --- a/integration_test/Dialect/ESI/runtime/loopback.mlir.py +++ b/integration_test/Dialect/ESI/runtime/loopback.mlir.py @@ -21,6 +21,13 @@ for esiType in m.type_table: print(f"{esiType}") +for info in m.module_infos: + print(f"{info.name}") + for const_name, const in info.constants.items(): + print(f" {const_name}: {const.value} {const.type}") + if info.name == "LoopbackIP" and const_name == "depth": + assert const.value == 5 + d = acc.build_accelerator() loopback = d.children[esiaccel.AppID("loopback_inst", 0)] diff --git a/lib/Dialect/ESI/runtime/cpp/include/esi/Common.h b/lib/Dialect/ESI/runtime/cpp/include/esi/Common.h index ee502c72f774..a47e2c0e76dd 100644 --- a/lib/Dialect/ESI/runtime/cpp/include/esi/Common.h +++ b/lib/Dialect/ESI/runtime/cpp/include/esi/Common.h @@ -25,6 +25,7 @@ #include namespace esi { +class Type; //===----------------------------------------------------------------------===// // Common accelerator description types. @@ -53,12 +54,18 @@ class AppIDPath : public std::vector { }; bool operator<(const AppIDPath &a, const AppIDPath &b); +struct Constant { + std::any value; + std::optional type; +}; + struct ModuleInfo { std::optional name; std::optional summary; std::optional version; std::optional repo; std::optional commitHash; + std::map constants; std::map extra; }; diff --git a/lib/Dialect/ESI/runtime/cpp/lib/Manifest.cpp b/lib/Dialect/ESI/runtime/cpp/lib/Manifest.cpp index 0640538eb91f..56e7986179e4 100644 --- a/lib/Dialect/ESI/runtime/cpp/lib/Manifest.cpp +++ b/lib/Dialect/ESI/runtime/cpp/lib/Manifest.cpp @@ -107,6 +107,10 @@ class Manifest::Impl { return ctxt.getType(id); } + std::any getAny(const nlohmann::json &value) const; + void parseModuleMetadata(ModuleInfo &info, const nlohmann::json &mod) const; + void parseModuleConsts(ModuleInfo &info, const nlohmann::json &mod) const; + // The parsed json. nlohmann::json manifestJson; // Cache the module info for each symbol. @@ -138,42 +142,50 @@ static ServicePortDesc parseServicePort(const nlohmann::json &jsonPort) { /// Convert the json value to a 'std::any', which can be exposed outside of this /// file. -static std::any getAny(const nlohmann::json &value) { - auto getObject = [](const nlohmann::json &json) { +std::any Manifest::Impl::getAny(const nlohmann::json &value) const { + auto getObject = [this](const nlohmann::json &json) -> std::any { std::map ret; for (auto &e : json.items()) ret[e.key()] = getAny(e.value()); return ret; }; - auto getArray = [](const nlohmann::json &json) { + auto getArray = [this](const nlohmann::json &json) -> std::any { std::vector ret; for (auto &e : json) ret.push_back(getAny(e)); return ret; }; - if (value.is_string()) - return value.get(); - else if (value.is_number_integer()) - return value.get(); - else if (value.is_number_unsigned()) - return value.get(); - else if (value.is_number_float()) - return value.get(); - else if (value.is_boolean()) - return value.get(); - else if (value.is_null()) - return value.get(); - else if (value.is_object()) - return getObject(value); - else if (value.is_array()) - return getArray(value); - else - throw std::runtime_error("Unknown type in manifest: " + value.dump(2)); + auto getValue = [&](const nlohmann::json &innerValue) -> std::any { + if (innerValue.is_string()) + return innerValue.get(); + else if (innerValue.is_number_integer()) + return innerValue.get(); + else if (innerValue.is_number_unsigned()) + return innerValue.get(); + else if (innerValue.is_number_float()) + return innerValue.get(); + else if (innerValue.is_boolean()) + return innerValue.get(); + else if (innerValue.is_null()) + return innerValue.get(); + else if (innerValue.is_object()) + return getObject(innerValue); + else if (innerValue.is_array()) + return getArray(innerValue); + else + throw std::runtime_error("Unknown type in manifest: " + + innerValue.dump(2)); + }; + + if (!value.is_object() || !value.contains("type") || !value.contains("value")) + return getValue(value); + return Constant{getValue(value.at("value")), getType(value.at("type"))}; } -static void parseModuleInfo(ModuleInfo &info, const nlohmann::json &mod) { +void Manifest::Impl::parseModuleMetadata(ModuleInfo &info, + const nlohmann::json &mod) const { for (auto &extra : mod.items()) if (extra.key() != "name" && extra.key() != "summary" && extra.key() != "version" && extra.key() != "repo" && @@ -193,6 +205,19 @@ static void parseModuleInfo(ModuleInfo &info, const nlohmann::json &mod) { info.commitHash = value("commitHash"); } +void Manifest::Impl::parseModuleConsts(ModuleInfo &info, + const nlohmann::json &mod) const { + for (auto &item : mod.items()) { + std::any value = getAny(item.value()); + auto *c = std::any_cast(&value); + if (c) + info.constants[item.key()] = *c; + else + // If the value isn't a "proper" constant, present it as one with no type. + info.constants[item.key()] = Constant{value, std::nullopt}; + } +} + //===----------------------------------------------------------------------===// // Manifest::Impl class implementation. //===----------------------------------------------------------------------===// @@ -209,7 +234,9 @@ Manifest::Impl::Impl(Context &ctxt, const std::string &manifestStr) for (auto &mod : manifestJson.at("symbols")) { ModuleInfo info; if (mod.contains("sym_info")) - parseModuleInfo(info, mod); + parseModuleMetadata(info, mod.at("sym_info")); + if (mod.contains("sym_consts")) + parseModuleConsts(info, mod.at("sym_consts")); symbolInfoCache.insert(make_pair(mod.at("symbol"), info)); } } catch (const std::exception &e) { @@ -577,6 +604,9 @@ const std::vector &Manifest::getTypeTable() const { // Print a module info, including the extra metadata. std::ostream &operator<<(std::ostream &os, const ModuleInfo &m) { auto printAny = [&os](std::any a) { + if (auto *c = std::any_cast(&a)) + a = std::any_cast(a).value; + const std::type_info &t = a.type(); if (t == typeid(std::string)) os << std::any_cast(a); @@ -610,6 +640,15 @@ std::ostream &operator<<(std::ostream &os, const ModuleInfo &m) { os << ": " << *m.summary; os << "\n"; + if (!m.constants.empty()) { + os << " Constants:\n"; + for (auto &e : m.constants) { + os << " " << e.first << ": "; + printAny(e.second); + os << "\n"; + } + } + if (!m.extra.empty()) { os << " Extra metadata:\n"; for (auto &e : m.extra) { diff --git a/lib/Dialect/ESI/runtime/python/esiaccel/esiCppAccel.cpp b/lib/Dialect/ESI/runtime/python/esiaccel/esiCppAccel.cpp index ab8b94e81d67..051106ecc5e2 100644 --- a/lib/Dialect/ESI/runtime/python/esiaccel/esiCppAccel.cpp +++ b/lib/Dialect/ESI/runtime/python/esiaccel/esiCppAccel.cpp @@ -42,8 +42,45 @@ struct polymorphic_type_hook { return port; } }; + +namespace detail { +/// Pybind11 doesn't have a built-in type caster for std::any +/// (https://github.com/pybind/pybind11/issues/1590). We must provide one which +/// knows about all of the potential types which the any might be. +template <> +struct type_caster { +public: + PYBIND11_TYPE_CASTER(std::any, const_name("object")); + + static handle cast(std::any src, return_value_policy /* policy */, + handle /* parent */) { + const std::type_info &t = src.type(); + if (t == typeid(std::string)) + return py::str(std::any_cast(src)); + else if (t == typeid(int64_t)) + return py::int_(std::any_cast(src)); + else if (t == typeid(uint64_t)) + return py::int_(std::any_cast(src)); + else if (t == typeid(double)) + return py::float_(std::any_cast(src)); + else if (t == typeid(bool)) + return py::bool_(std::any_cast(src)); + else if (t == typeid(std::nullptr_t)) + return py::none(); + return py::none(); + } +}; +} // namespace detail } // namespace pybind11 +/// Resolve a Type to the Python wrapper object. +py::object getPyType(std::optional t) { + py::object typesModule = py::module_::import("esiaccel.types"); + if (!t) + return py::none(); + return typesModule.attr("_get_esi_type")(*t); +} + // NOLINTNEXTLINE(readability-identifier-naming) PYBIND11_MODULE(esiCppAccel, m) { py::class_(m, "Type") @@ -75,6 +112,12 @@ PYBIND11_MODULE(esiCppAccel, m) { py::return_value_policy::reference) .def_property_readonly("size", &ArrayType::getSize); + py::class_(m, "Constant") + .def_property_readonly("value", [](Constant &c) { return c.value; }) + .def_property_readonly( + "type", [](Constant &c) { return getPyType(*c.type); }, + py::return_value_policy::reference); + py::class_(m, "ModuleInfo") .def_property_readonly("name", [](ModuleInfo &info) { return info.name; }) .def_property_readonly("summary", @@ -84,6 +127,8 @@ PYBIND11_MODULE(esiCppAccel, m) { .def_property_readonly("repo", [](ModuleInfo &info) { return info.repo; }) .def_property_readonly("commit_hash", [](ModuleInfo &info) { return info.commitHash; }) + .def_property_readonly("constants", + [](ModuleInfo &info) { return info.constants; }) // TODO: "extra" field. .def("__repr__", [](ModuleInfo &info) { std::string ret; From e672d0e43358ad16441d640b8d5a565123b0685f Mon Sep 17 00:00:00 2001 From: John Demme Date: Mon, 12 Aug 2024 14:21:36 +0000 Subject: [PATCH 078/119] [PyCDE] Fixing integration tests - Don't check the type compatibility until `connect()` time. - Account for the extra symbols. --- .../PyCDE/integration_test/test_software/esi_ram.py | 9 +++++++-- lib/Dialect/ESI/runtime/python/esiaccel/types.py | 3 ++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/frontends/PyCDE/integration_test/test_software/esi_ram.py b/frontends/PyCDE/integration_test/test_software/esi_ram.py index cf18fc16dafe..c9f61b54b511 100644 --- a/frontends/PyCDE/integration_test/test_software/esi_ram.py +++ b/frontends/PyCDE/integration_test/test_software/esi_ram.py @@ -1,3 +1,4 @@ +from multiprocessing import dummy import time from typing import cast import esiaccel as esi @@ -26,8 +27,12 @@ # assert len(m.type_table) == len(m_alt.type_table) info = m.module_infos -assert len(info) == 3 -assert info[1].name == "Dummy" +dummy_info = None +for i in info: + if i.name == "Dummy": + dummy_info = i + break +assert dummy_info is not None def read(addr: int) -> bytearray: diff --git a/lib/Dialect/ESI/runtime/python/esiaccel/types.py b/lib/Dialect/ESI/runtime/python/esiaccel/types.py index d6da5d2a0368..eccff627508e 100644 --- a/lib/Dialect/ESI/runtime/python/esiaccel/types.py +++ b/lib/Dialect/ESI/runtime/python/esiaccel/types.py @@ -296,11 +296,12 @@ def __init__(self, owner: BundlePort, cpp_port: cpp.ChannelPort): self.owner = owner self.cpp_port = cpp_port self.type = _get_esi_type(cpp_port.type) + + def connect(self, buffer_size: Optional[int] = None): (supports_host, reason) = self.type.supports_host if not supports_host: raise TypeError(f"unsupported type: {reason}") - def connect(self, buffer_size: Optional[int] = None): self.cpp_port.connect(buffer_size) return self From 689c0a46c41d3401a161aaf8237ba6f7f770d6ae Mon Sep 17 00:00:00 2001 From: Mike Urbach Date: Mon, 12 Aug 2024 10:48:41 -0600 Subject: [PATCH 079/119] [OM] Add list concatenation operation. (#7487) This operation produces a list by concatenating lists of the same type. The operation definition and a simple round trip test have been added. This is used to compose lists, which is a key feature to enable hierarchical composition of OM values. --- include/circt/Dialect/OM/OMOps.td | 17 +++++++++++++++++ test/Dialect/OM/round-trip.mlir | 16 ++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/include/circt/Dialect/OM/OMOps.td b/include/circt/Dialect/OM/OMOps.td index 769da2623e88..6338218c2ce6 100644 --- a/include/circt/Dialect/OM/OMOps.td +++ b/include/circt/Dialect/OM/OMOps.td @@ -207,6 +207,23 @@ def ListCreateOp : OMOp<"list_create", [Pure, SameTypeOperands]> { let hasCustomAssemblyFormat = 1; } +def ListConcatOp : OMOp<"list_concat", [Pure, SameOperandsAndResultType]> { + let summary = "Concatenate multiple lists to produce a new list"; + let description = [{ + Produces a value of list type by concatenating the provided lists. + + Example: + ``` + %3 = om.list_concat %0, %1, %2 : !om.list + ``` + }]; + + let arguments = (ins Variadic:$subLists); + let results = (outs ListType:$result); + + let assemblyFormat = "$subLists attr-dict `:` type($result)"; +} + def TupleCreateOp : OMOp<"tuple_create", [Pure, InferTypeOpInterface]> { let summary = "Create a tuple of values"; let description = [{ diff --git a/test/Dialect/OM/round-trip.mlir b/test/Dialect/OM/round-trip.mlir index f00cc9c9f732..738e895e434c 100644 --- a/test/Dialect/OM/round-trip.mlir +++ b/test/Dialect/OM/round-trip.mlir @@ -158,6 +158,22 @@ om.class @ListCreate() { om.class.field @list_field, %list : !om.list> } +// CHECK-LABEL: @ListConcat +om.class @ListConcat() { + %0 = om.constant #om.integer<0 : i8> : !om.integer + %1 = om.constant #om.integer<1 : i8> : !om.integer + %2 = om.constant #om.integer<2 : i8> : !om.integer + + // CHECK: [[L0:%.+]] = om.list_create %0, %1 + %l0 = om.list_create %0, %1 : !om.integer + + // CHECK: [[L1:%.+]] = om.list_create %2 + %l1 = om.list_create %2 : !om.integer + + // CHECK: om.list_concat [[L0]], [[L1]] + %concat = om.list_concat %l0, %l1 : !om.list +} + // CHECK-LABEL: @Integer om.class @IntegerConstant() { // CHECK: %[[const1:.+]] = om.constant #om.integer<36755551979133953793 : i67> : !om.integer From 7f366b0d85b23cf76ff1cd3e5ad3bc334c2d3b7e Mon Sep 17 00:00:00 2001 From: Mike Urbach Date: Mon, 12 Aug 2024 11:08:05 -0600 Subject: [PATCH 080/119] [FIRRTL] Add list concatenation operation. (#7486) This operation produces a list by concatenating lists of the same type. The operation definition and a simple round trip test have been added. This is used to compose lists, which is a key feature to enable hierarchical composition of Properties. --- .../circt/Dialect/FIRRTL/FIRRTLExpressions.td | 17 +++++++++++++++++ include/circt/Dialect/FIRRTL/FIRRTLVisitors.h | 5 +++-- test/Dialect/FIRRTL/round-trip.mlir | 16 ++++++++++++++++ 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/include/circt/Dialect/FIRRTL/FIRRTLExpressions.td b/include/circt/Dialect/FIRRTL/FIRRTLExpressions.td index f435e86ef7ce..359e64d86571 100644 --- a/include/circt/Dialect/FIRRTL/FIRRTLExpressions.td +++ b/include/circt/Dialect/FIRRTL/FIRRTLExpressions.td @@ -1154,6 +1154,23 @@ def ListCreateOp : FIRRTLOp<"list.create", [Pure, SameTypeOperands]> { let hasVerifier = 1; } +def ListConcatOp : FIRRTLOp<"list.concat", [Pure, SameOperandsAndResultType]> { + let summary = "Concatenate multiple lists to produce a new list"; + let description = [{ + Produces a value of list type by concatenating the provided lists. + + Example: + ```mlir + %3 = firrtl.list.concat %0, %1, %2 : !firrtl.list + ``` + }]; + + let arguments = (ins Variadic:$subLists); + let results = (outs ListType:$result); + + let assemblyFormat = "$subLists attr-dict `:` type($result)"; +} + def BoolConstantOp : FIRRTLOp<"bool", [Pure, ConstantLike]> { let summary = "Produce a constant boolean value"; let description = [{ diff --git a/include/circt/Dialect/FIRRTL/FIRRTLVisitors.h b/include/circt/Dialect/FIRRTL/FIRRTLVisitors.h index 03a2466d2b52..5f9aa0ddac9e 100644 --- a/include/circt/Dialect/FIRRTL/FIRRTLVisitors.h +++ b/include/circt/Dialect/FIRRTL/FIRRTLVisitors.h @@ -65,8 +65,8 @@ class ExprVisitor { UninferredResetCastOp, ConstCastOp, RefCastOp, // Property expressions. StringConstantOp, FIntegerConstantOp, BoolConstantOp, - DoubleConstantOp, ListCreateOp, UnresolvedPathOp, PathOp, - IntegerAddOp, IntegerMulOp, IntegerShrOp>( + DoubleConstantOp, ListCreateOp, ListConcatOp, UnresolvedPathOp, + PathOp, IntegerAddOp, IntegerMulOp, IntegerShrOp>( [&](auto expr) -> ResultType { return thisCast->visitExpr(expr, args...); }) @@ -219,6 +219,7 @@ class ExprVisitor { HANDLE(BoolConstantOp, Unhandled); HANDLE(DoubleConstantOp, Unhandled); HANDLE(ListCreateOp, Unhandled); + HANDLE(ListConcatOp, Unhandled); HANDLE(PathOp, Unhandled); HANDLE(UnresolvedPathOp, Unhandled); HANDLE(IntegerAddOp, Unhandled); diff --git a/test/Dialect/FIRRTL/round-trip.mlir b/test/Dialect/FIRRTL/round-trip.mlir index 96384fde936b..1c6ae9434ed2 100644 --- a/test/Dialect/FIRRTL/round-trip.mlir +++ b/test/Dialect/FIRRTL/round-trip.mlir @@ -104,4 +104,20 @@ firrtl.module @PropertyArithmetic() { %4 = firrtl.integer.shr %0, %1 : (!firrtl.integer, !firrtl.integer) -> !firrtl.integer } +// CHECK-LABEL: firrtl.module @PropertyListOps +firrtl.module @PropertyListOps() { + %0 = firrtl.integer 0 + %1 = firrtl.integer 1 + %2 = firrtl.integer 2 + + // CHECK: [[L0:%.+]] = firrtl.list.create %0, %1 + %l0 = firrtl.list.create %0, %1 : !firrtl.list + + // CHECK: [[L1:%.+]] = firrtl.list.create %2 + %l1 = firrtl.list.create %2 : !firrtl.list + + // CHECK: firrtl.list.concat [[L0]], [[L1]] : !firrtl.list + %concat = firrtl.list.concat %l0, %l1 : !firrtl.list +} + } From bb6471f7e4928f654f4606f1e3e89df44652ea3b Mon Sep 17 00:00:00 2001 From: Amelia Dobis Date: Mon, 12 Aug 2024 10:42:06 -0700 Subject: [PATCH 081/119] [Verif] Generalize Formal Contracts (#7495) * Generalized verif contracts * updated contract op description --- include/circt/Dialect/Verif/VerifOps.td | 97 ++++++++++--------------- lib/Dialect/Verif/VerifOps.cpp | 62 +++++----------- test/Dialect/Verif/basic.mlir | 72 ++++++------------ 3 files changed, 78 insertions(+), 153 deletions(-) diff --git a/include/circt/Dialect/Verif/VerifOps.td b/include/circt/Dialect/Verif/VerifOps.td index 299f9c769eac..8333d8fe2157 100644 --- a/include/circt/Dialect/Verif/VerifOps.td +++ b/include/circt/Dialect/Verif/VerifOps.td @@ -251,7 +251,7 @@ def YieldOp : VerifOp<"yield", [ Terminator, ParentOneOf<[ "verif::LogicEquivalenceCheckingOp", "verif::BoundedModelCheckingOp", - "verif::InstanceOp" + "verif::ContractOp" ]> ]> { let summary = "yields values from a region"; @@ -313,7 +313,7 @@ def FormalOp : VerifOp<"formal", [ //===----------------------------------------------------------------------===// def SymbolicInputOp : VerifOp<"symbolic_input", [ - ParentOneOf<["verif::FormalOp", "verif::InstanceOp"]> + ParentOneOf<["verif::FormalOp"]> ]>{ let summary = "declare a symbolic input for formal verification"; let description = [{ @@ -354,70 +354,46 @@ def ConcreteInputOp : VerifOp<"concrete_input", [ // Formal Contract Ops //===----------------------------------------------------------------------===// -class ContractLikeOp traits = []> - : VerifOp, RegionKindInterface, IsolatedFromAbove - ]> { - - let arguments = (ins Variadic:$inputs); - - let regions = (region SizedRegion<1>:$body); - - let extraClassDeclaration = [{ - /// Implement RegionKindInterface. - static RegionKind getRegionKind(unsigned index) { - return RegionKind::Graph; - } - - /// Retrieves the region block arguments - BlockArgument getRegionArg(unsigned index) { - return getBody().front().getArguments()[index]; - } - - /// Retrieves the number of block arguments - unsigned getNumRegionArgs() { - return getBody().front().getNumArguments(); - } - }]; - - let hasRegionVerifier = 1; -} - - -def ContractOp : ContractLikeOp<"contract", [NoTerminator]>{ +def ContractOp : VerifOp<"contract", [ + SingleBlockImplicitTerminator<"verif::YieldOp">, + HasParent<"hw::HWModuleOp">, + RegionKindInterface +]>{ let summary = "declare a formal contract"; let description = [{ This operation declares a formal contract that is used to create precondition and postconditions on a parent module. These are used as an abstraction to better modularize formal verification such that each module containing a contract is checked exactly once. The contract contains a single block where the block arguments - represent the inputs and outputs in the same order as in the module signature. + represent the results of the code block that the contract is abstracting over. The + operands represent the SSA values that this contract's results will replace. e.g. ``` hw.module @Bar(in %foo : i8, out "" : i8, out "1" : i8) { - verif.contract (%foo) : (i8) { - ^bb0(%arg1 : i8, %bar.0 : i8, %bar.1 : i8): + %o0, %o1 = verif.contract (%to0, %to1) : (i8, i8) -> (i8, i8) { + ^bb0(%bar.0 : i8, %bar.1 : i8): %c0_8 = hw.constant 0 : i8 - %prec = comb.icmp bin ugt %arg1, %c0_8 : i8 + %prec = comb.icmp bin ugt %foo, %c0_8 : i8 verif.require %prec : i1 - %post = comb.icmp bin ugt %bar.0, %arg1 : i8 - %post1 = comb.icmp bin ult %bar.1, %arg1 : i8 + %post = comb.icmp bin ugt %bar.0, %foo : i8 + %post1 = comb.icmp bin ult %bar.1, %foo : i8 verif.ensure %post : i1 verif.ensure %post1 : i1 - } + verif.yield %bar.0, %bar.1 : i8, i8 + } /* ... Module definition ... */ } ``` This later is used to replace any instance of Bar during verification: ``` - %bar.0, %bar.1 = hw.instance "bar" @Bar("foo" : %foo : i8) -> ("" : i8, "1" : i8) + %bar.0, %bar.1 = hw.instance "bar" @Bar("foo" : %c42_8 : i8) -> ("" : i8, "1" : i8) /* ... After PrepareForFormal Pass becomes ... */ - %bar.0, %bar.1 = verif.instance (%c42_8) : (i8) -> (i8, i8) { + %bar.0, %bar.1 = verif.contract (%c42_8) : (i8) -> (i8, i8) { ^bb0(%arg1: i8): %c0_8 = hw.constant 0 : i8 %prec = comb.icmp bin ugt %arg1, %c0_8 : i8 @@ -435,29 +411,32 @@ def ContractOp : ContractLikeOp<"contract", [NoTerminator]>{ ``` }]; + let arguments = (ins Variadic:$inputs); + let results = (outs Variadic:$results); + let regions = (region SizedRegion<1>:$body); + let assemblyFormat = [{ - `(` $inputs `)` attr-dict `:` `(` type($inputs) `)` $body + `(` $inputs `)` attr-dict `:` `(` type($inputs) `)` `->` `(` type($results) `)` $body }]; -} -def InstanceOp : ContractLikeOp<"instance", [ - SingleBlockImplicitTerminator<"verif::YieldOp"> -]>{ - let summary = "declare an instance of a formal contract (replace an hw.instance)"; - let description = [{ - This operation declares an instance of an `hw.module` that contains a - `verif.contract` definition. The body of this op will be a modified version - of the body of the referenced module's contract body, with `verif.require` - ops being reoplaced with `verif.assert` and `verif.ensure` being replaced - with `verif.assume`. The `verif.result` ops are converted into `verif.symbolic_input` - and are yielded as a result of the region to be used in the rest of the module. - }]; + let extraClassDeclaration = [{ + /// Implement RegionKindInterface. + static RegionKind getRegionKind(unsigned index) { + return RegionKind::Graph; + } - let results = (outs Variadic:$results); + /// Retrieves the region block arguments + BlockArgument getRegionArg(unsigned index) { + return getBody().front().getArguments()[index]; + } - let assemblyFormat = [{ - `(` $inputs `)` attr-dict `:` functional-type($inputs, $results) $body + /// Retrieves the number of block arguments + unsigned getNumRegionArgs() { + return getBody().front().getNumArguments(); + } }]; + + let hasRegionVerifier = 1; } class RequireLikeOp traits = [ diff --git a/lib/Dialect/Verif/VerifOps.cpp b/lib/Dialect/Verif/VerifOps.cpp index 21d4e1d1af47..62a56f80de61 100644 --- a/lib/Dialect/Verif/VerifOps.cpp +++ b/lib/Dialect/Verif/VerifOps.cpp @@ -104,50 +104,26 @@ LogicalResult ContractOp::verifyRegions() { if (!parent) return emitOpError() << "parent of contract must be an hw.module!"; - auto nInputsInModule = parent.getNumInputPorts(); - auto nOutputsInModule = parent.getNumOutputPorts(); - auto nOps = (*this)->getNumOperands(); - - // Check that the region block arguments match the op's inputs - if (nInputsInModule != nOps) - return emitOpError() << "contract must have the same number of arguments " - << "as the number of inputs in the parent module!"; - - // Check that the region block arguments match the op's inputs - if (getNumRegionArgs() != (nOps + nOutputsInModule)) - return emitOpError() << "region must have the same number of arguments " - << "as the number of arguments in the parent module!"; - - // Check that the region block arguments share the same types as the inputs - if (getBody().front().getArgumentTypes() != parent.getPortTypes()) + auto nRes = (*this)->getNumResults(); + auto resTypes = (*this)->getResultTypes(); + auto *yield = getBody().front().getTerminator(); + + // Check that the region terminator yields the same number of ops as the + // number of results + if (yield->getNumOperands() != nRes) + return emitOpError() << "region terminator must yield the same number of " + << "operands as there are results!"; + + // Check that the region terminator yields the same types of ops as the + // types of results + if (yield->getOperandTypes() != resTypes) + return emitOpError() << "region terminator must yield the same types of " + << "operands as the result types!"; + + // Check that the region block arguments share the same types as the results + if (getBody().front().getArgumentTypes() != resTypes) return emitOpError() << "region must have the same type of arguments " - << "as the type of inputs!"; - - return success(); -} - -LogicalResult InstanceOp::verifyRegions() { - // Check that the region block arguments match the op's inputs - if (getNumRegionArgs() != (*this)->getNumOperands()) - return emitOpError() << "region must have the same number of arguments " - << "as the number of inputs!"; - - // Check that the region block arguments share the same types as the inputs - if (getBody().front().getArgumentTypes() != (*this)->getOperandTypes()) - return emitOpError() << "region must have the same type of arguments " - << "as the type of inputs!"; - - // Check that verif.yield yielded the expected number of operations - if ((*this)->getNumResults() != - getBody().front().getTerminator()->getNumOperands()) - return emitOpError() << "region terminator must yield the same number" - << "of operations as there are results!"; - - // Check that the yielded types match the result types - if ((*this)->getResultTypes() != - getBody().front().getTerminator()->getOperandTypes()) - return emitOpError() << "region terminator must yield the same types" - << "as the result types!"; + << "as the type of results!"; return success(); } diff --git a/test/Dialect/Verif/basic.mlir b/test/Dialect/Verif/basic.mlir index 1f53b83bd706..4a87a9ab56c8 100644 --- a/test/Dialect/Verif/basic.mlir +++ b/test/Dialect/Verif/basic.mlir @@ -58,70 +58,40 @@ verif.formal @formal1(k = 20) { // CHECK-LABEL: hw.module @Bar hw.module @Bar(in %foo : i8, out "" : i8, out "1" : i8) { - // CHECK: verif.contract(%foo) : (i8) { - verif.contract (%foo) : (i8) { - // CHECK: ^bb0(%[[ARG:.+]]: i8, %[[OUT:.+]]: i8, %[[OUT1:.+]]: i8): - ^bb0(%arg1 : i8, %bar.0 : i8, %bar.1 : i8): + // CHECK: %[[C1:.+]] = hw.constant + %c1_8 = hw.constant 1 : i8 + // CHECK: %[[O1:.+]] = comb.add + %to0 = comb.add bin %foo, %c1_8 : i8 + // CHECK: %[[O2:.+]] = comb.sub + %to1 = comb.sub bin %foo, %c1_8 : i8 + + // CHECK: %[[OUT:.+]]:2 = verif.contract(%[[O1]], %[[O2]]) : (i8, i8) -> (i8, i8) { + %o0, %o1 = verif.contract (%to0, %to1) : (i8, i8) -> (i8, i8) { + // CHECK: ^bb0(%[[BAR0:.+]]: i8, %[[BAR1:.+]]: i8): + ^bb0(%bar.0 : i8, %bar.1 : i8): // CHECK: %[[C0:.+]] = hw.constant 0 : i8 %c0_8 = hw.constant 0 : i8 - // CHECK: %[[PREC:.+]] = comb.icmp bin ugt %[[ARG]], %[[C0]] : i8 - %prec = comb.icmp bin ugt %arg1, %c0_8 : i8 + // CHECK: %[[PREC:.+]] = comb.icmp bin ugt %foo, %[[C0]] : i8 + %prec = comb.icmp bin ugt %foo, %c0_8 : i8 // CHECK: verif.require %[[PREC]] : i1 verif.require %prec : i1 - // CHECK: %[[P0:.+]] = comb.icmp bin ugt %[[OUT]], %[[ARG]] : i8 - %post = comb.icmp bin ugt %bar.0, %arg1 : i8 - // CHECK: %[[P1:.+]] = comb.icmp bin ult %[[OUT1]], %[[ARG]] : i8 - %post1 = comb.icmp bin ult %bar.1, %arg1 : i8 + // CHECK: %[[P0:.+]] = comb.icmp bin ugt %[[BAR0]], %foo : i8 + %post = comb.icmp bin ugt %bar.0, %foo : i8 + // CHECK: %[[P1:.+]] = comb.icmp bin ult %[[BAR1]], %foo : i8 + %post1 = comb.icmp bin ult %bar.1, %foo : i8 // CHECK: verif.ensure %[[P0]] : i1 verif.ensure %post : i1 // CHECK: verif.ensure %[[P1]] : i1 verif.ensure %post1 : i1 - } - // CHECK: %[[C1:.+]] = hw.constant - %c1_8 = hw.constant 1 : i8 - // CHECK: %[[O1:.+]] = comb.add - %o0 = comb.add bin %foo, %c1_8 : i8 - // CHECK: %[[O2:.+]] = comb.sub - %o1 = comb.sub bin %foo, %c1_8 : i8 + // CHECK: verif.yield %[[BAR0]], %[[BAR1]] : i8, i8 + verif.yield %bar.0, %bar.1 : i8, i8 + } + // CHECK-LABEL: hw.output hw.output %o0, %o1 : i8, i8 } -// CHECK-LABEL: hw.module @Foo1 -hw.module @Foo1(in %0 "0": i1, in %1 "1": i1, out "" : i8, out "1" : i8) { - // CHECK: %[[C42:.+]] = hw.constant 42 : i8 - %c42_8 = hw.constant 42 : i8 - // CHECK: %[[OUTS:.+]]:2 = verif.instance(%[[C42]]) : (i8) -> (i8, i8) { - %bar.0, %bar.1 = verif.instance (%c42_8) : (i8) -> (i8, i8) { - // CHECK: ^bb0(%[[ARG:.+]]: i8): - ^bb0(%arg1: i8): - // CHECK: %[[C0:.+]] = hw.constant 0 : i8 - %c0_8 = hw.constant 0 : i8 - // CHECK: %[[PREC:.+]] = comb.icmp bin ugt %[[ARG]], %[[C0]] : i8 - %prec = comb.icmp bin ugt %arg1, %c0_8 : i8 - // CHECK: verif.assert %[[PREC]] : i1 - verif.assert %prec : i1 - - // CHECK: %[[OUT0:.+]] = verif.symbolic_input : i8 - %bar.0 = verif.symbolic_input : i8 - // CHECK: %[[OUT1:.+]] = verif.symbolic_input : i8 - %bar.1 = verif.symbolic_input : i8 - // CHECK: %[[P0:.+]] = comb.icmp bin ugt %[[OUT0]], %[[ARG]] : i8 - %post = comb.icmp bin ugt %bar.0, %arg1 : i8 - // CHECK: %[[P1:.+]] = comb.icmp bin ult %[[OUT1]], %[[ARG]] : i8 - %post1 = comb.icmp bin ult %bar.1, %arg1 : i8 - // CHECK: verif.assume %[[P0]] : i1 - verif.assume %post : i1 - // CHECK: verif.assume %[[P1]] : i1 - verif.assume %post1 : i1 - // CHECK: verif.yield %[[OUT0]], %[[OUT1]] : i8, i8 - verif.yield %bar.0, %bar.1 : i8, i8 - } - // CHECK: hw.output %[[OUTS]]#0, %[[OUTS]]#1 : i8, i8 - hw.output %bar.0, %bar.1 : i8, i8 - } - //===----------------------------------------------------------------------===// // Print-related // Must be inside hw.module to ensure that the dialect is loaded. From 23371f822643a54f2808c9dad1be4e96455a6190 Mon Sep 17 00:00:00 2001 From: Mike Urbach Date: Mon, 12 Aug 2024 12:08:24 -0600 Subject: [PATCH 082/119] [FIRRTL] Add list concatenation parser support. (#7512) This parses `list_concat(exp+)` into the ListConcatOp. The type of the list is inferred during parsing, and all expressions must be the same. It is a parser error to not concatenate at least one list. --- lib/Dialect/FIRRTL/Import/FIRParser.cpp | 49 +++++++++++++++++++++ lib/Dialect/FIRRTL/Import/FIRTokenKinds.def | 1 + test/Dialect/FIRRTL/parse-basic.fir | 14 ++++++ test/Dialect/FIRRTL/parse-errors.fir | 36 +++++++++++++++ 4 files changed, 100 insertions(+) diff --git a/lib/Dialect/FIRRTL/Import/FIRParser.cpp b/lib/Dialect/FIRRTL/Import/FIRParser.cpp index 1a38326136a1..40f6ddf2246d 100644 --- a/lib/Dialect/FIRRTL/Import/FIRParser.cpp +++ b/lib/Dialect/FIRRTL/Import/FIRParser.cpp @@ -1720,6 +1720,7 @@ struct FIRStmtParser : public FIRParser { ParseResult parsePrimExp(Value &result); ParseResult parseIntegerLiteralExp(Value &result); ParseResult parseListExp(Value &result); + ParseResult parseListConcatExp(Value &result); std::optional parseExpWithLeadingKeyword(FIRToken keyword); @@ -1978,6 +1979,16 @@ ParseResult FIRStmtParser::parseExpImpl(Value &result, const Twine &message, return failure(); break; } + + case FIRToken::lp_list_concat: { + if (isLeadingStmt) + return emitError("unexpected list_create() as start of statement"); + if (requireFeature(nextFIRVersion, "List concat") || + parseListConcatExp(result)) + return failure(); + break; + } + case FIRToken::lp_path: if (isLeadingStmt) return emitError("unexpected path() as start of statement"); @@ -2455,6 +2466,44 @@ ParseResult FIRStmtParser::parseListExp(Value &result) { return success(); } +/// list-concat-exp ::= 'list_concat' '(' exp* ')' +ParseResult FIRStmtParser::parseListConcatExp(Value &result) { + consumeToken(FIRToken::lp_list_concat); + + auto loc = getToken().getLoc(); + ListType type; + SmallVector operands; + if (parseListUntil(FIRToken::r_paren, [&]() -> ParseResult { + Value operand; + locationProcessor.setLoc(loc); + if (parseExp(operand, "expected expression in List concat expression")) + return failure(); + + if (!type_isa(operand.getType())) + return emitError(loc, "unexpected expression of type ") + << operand.getType() << " in List concat expression"; + + if (!type) + type = type_cast(operand.getType()); + + if (operand.getType() != type) + return emitError(loc, "unexpected expression of type ") + << operand.getType() << " in List concat expression of type " + << type; + + operands.push_back(operand); + return success(); + })) + return failure(); + + if (operands.empty()) + return emitError(loc, "need at least one List to concatenate"); + + locationProcessor.setLoc(loc); + result = builder.create(type, operands); + return success(); +} + /// The .fir grammar has the annoying property where: /// 1) some statements start with keywords /// 2) some start with an expression diff --git a/lib/Dialect/FIRRTL/Import/FIRTokenKinds.def b/lib/Dialect/FIRRTL/Import/FIRTokenKinds.def index 895779127065..b3983a86068c 100644 --- a/lib/Dialect/FIRRTL/Import/FIRTokenKinds.def +++ b/lib/Dialect/FIRRTL/Import/FIRTokenKinds.def @@ -169,6 +169,7 @@ TOK_LPKEYWORD(assume) TOK_LPKEYWORD(cover) TOK_LPKEYWORD(path) +TOK_LPKEYWORD(list_concat) TOK_LPKEYWORD(force) TOK_LPKEYWORD(force_initial) diff --git a/test/Dialect/FIRRTL/parse-basic.fir b/test/Dialect/FIRRTL/parse-basic.fir index 961cfba9e712..6bfd861f650f 100644 --- a/test/Dialect/FIRRTL/parse-basic.fir +++ b/test/Dialect/FIRRTL/parse-basic.fir @@ -1670,6 +1670,20 @@ circuit IntegerArithmetic : ; CHECK: firrtl.propassign %e, [[E]] propassign e, integer_shr(a, b) +;// ----- +FIRRTL version 4.0.0 + +; CHECK-LABEL: firrtl.circuit "PropertyListOps" +circuit PropertyListOps : + public module PropertyListOps : + input a : List + input b : List + output c : List + + ; CHECK: [[C:%.+]] = firrtl.list.concat %a, %b + ; CHECK: firrtl.propassign %c, [[C]] + propassign c, list_concat(a, b) + ;// ----- FIRRTL version 3.1.0 diff --git a/test/Dialect/FIRRTL/parse-errors.fir b/test/Dialect/FIRRTL/parse-errors.fir index 0ce93e169073..87cc7c60153b 100644 --- a/test/Dialect/FIRRTL/parse-errors.fir +++ b/test/Dialect/FIRRTL/parse-errors.fir @@ -1026,6 +1026,42 @@ circuit Top: ; expected-error @below {{Integer arithmetic expressions are a FIRRTL 4.0.0+ feature, but the specified FIRRTL version was 3.1.0}} propassign c, integer_shr(a, b) +;// ----- +FIRRTL version 4.0.0 + +; CHECK-LABEL: firrtl.circuit "Top" +circuit Top : + public module Top : + input a : Integer + output b : List + + ; expected-error @below {{unexpected expression of type '!firrtl.integer' in List concat expression}} + propassign b, list_concat(a) + +;// ----- +FIRRTL version 4.0.0 + +; CHECK-LABEL: firrtl.circuit "Top" +circuit Top : + public module Top : + input a : List + input b : List + output c : List + + ; expected-error @below {{unexpected expression of type '!firrtl.list' in List concat expression of type '!firrtl.list'}} + propassign c, list_concat(a, b) + +;// ----- +FIRRTL version 4.0.0 + +; CHECK-LABEL: firrtl.circuit "Top" +circuit Top : + public module Top : + output c : List + + ; expected-error @below {{need at least one List to concatenate}} + propassign c, list_concat() + ;// ----- FIRRTL version 3.3.0 From 00f140496c840905d0951cecebadcd794214dc98 Mon Sep 17 00:00:00 2001 From: Mike Urbach Date: Mon, 12 Aug 2024 12:08:49 -0600 Subject: [PATCH 083/119] [FIRRTL] Add list concatenation conversion to LowerClasses. (#7513) This is a straightforward 1:1 conversion from firrtl::ListConcatOp to om::ListConcatOp. --- lib/Dialect/FIRRTL/Transforms/LowerClasses.cpp | 17 +++++++++++++++++ test/Dialect/FIRRTL/lower-classes.mlir | 7 +++++++ 2 files changed, 24 insertions(+) diff --git a/lib/Dialect/FIRRTL/Transforms/LowerClasses.cpp b/lib/Dialect/FIRRTL/Transforms/LowerClasses.cpp index ea1d49c63234..056ae35af0f0 100644 --- a/lib/Dialect/FIRRTL/Transforms/LowerClasses.cpp +++ b/lib/Dialect/FIRRTL/Transforms/LowerClasses.cpp @@ -1385,6 +1385,22 @@ struct ListCreateOpConversion } }; +struct ListConcatOpConversion + : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(firrtl::ListConcatOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + auto listType = getTypeConverter()->convertType(op.getType()); + if (!listType) + return failure(); + rewriter.replaceOpWithNewOp(op, listType, + adaptor.getSubLists()); + return success(); + } +}; + struct IntegerAddOpConversion : public OpConversionPattern { using OpConversionPattern::OpConversionPattern; @@ -1864,6 +1880,7 @@ static void populateRewritePatterns( patterns.add(converter, patterns.getContext()); patterns.add(converter, patterns.getContext()); patterns.add(converter, patterns.getContext()); + patterns.add(converter, patterns.getContext()); patterns.add(converter, patterns.getContext()); patterns.add(converter, patterns.getContext()); patterns.add(converter, patterns.getContext()); diff --git a/test/Dialect/FIRRTL/lower-classes.mlir b/test/Dialect/FIRRTL/lower-classes.mlir index 803ee34cec81..0a1e81a03878 100644 --- a/test/Dialect/FIRRTL/lower-classes.mlir +++ b/test/Dialect/FIRRTL/lower-classes.mlir @@ -224,6 +224,13 @@ firrtl.circuit "PathModule" { // CHECK: %[[c1:.+]] = om.list_create %propIn, %[[c0]] : !om.integer // CHECK: om.class.field @propOut, %[[c1]] : !om.list } + + firrtl.module @ListConcat(in %propIn0: !firrtl.list, in %propIn1: !firrtl.list, out %propOut: !firrtl.list) { + // CHECK: [[CONCAT:%.+]] = om.list_concat %propIn0, %propIn1 + %1 = firrtl.list.concat %propIn0, %propIn1 : !firrtl.list + // CHECK: om.class.field @propOut, [[CONCAT]] + firrtl.propassign %propOut, %1 : !firrtl.list + } } // CHECK-LABEL: firrtl.circuit "WireProp" From b89f54f2bf0cb41e8a5d689d6a3ec9ba0d3e8298 Mon Sep 17 00:00:00 2001 From: Fabian Schuiki Date: Mon, 12 Aug 2024 11:53:39 -0700 Subject: [PATCH 084/119] [Moore] Fix mem2reg for variables with non-packed types Only attempt to promote variables with packed types during mem2reg. Also run the `basic.sv` test of ImportVerilog through `circt-verilog` as a sanity check of the transformations done by the tool. --- include/circt/Dialect/Moore/MooreOps.td | 2 +- lib/Dialect/Moore/MooreOps.cpp | 6 ++++++ test/Conversion/ImportVerilog/basic.sv | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/include/circt/Dialect/Moore/MooreOps.td b/include/circt/Dialect/Moore/MooreOps.td index ba7f5dc6d475..27413163d204 100644 --- a/include/circt/Dialect/Moore/MooreOps.td +++ b/include/circt/Dialect/Moore/MooreOps.td @@ -462,7 +462,7 @@ def NamedConstantOp : MooreOp<"named_constant", [ }]; } -def StringConstantOp : MooreOp<"string_constant", [Pure, ConstantLike]> { +def StringConstantOp : MooreOp<"string_constant", [Pure]> { let summary = "Produce a constant string value"; let description = [{ Produces a constant value of string type. diff --git a/lib/Dialect/Moore/MooreOps.cpp b/lib/Dialect/Moore/MooreOps.cpp index 788751f7f381..0f32fcb220ed 100644 --- a/lib/Dialect/Moore/MooreOps.cpp +++ b/lib/Dialect/Moore/MooreOps.cpp @@ -322,6 +322,12 @@ SmallVector VariableOp::getPromotableSlots() { if (mlir::mayBeGraphRegion(*getOperation()->getParentRegion()) || getInitial()) return {}; + + // Ensure that `getDefaultValue` can conjure up a default value for the + // variable's type. + if (!isa(getType().getNestedType())) + return {}; + return {MemorySlot{getResult(), getType().getNestedType()}}; } diff --git a/test/Conversion/ImportVerilog/basic.sv b/test/Conversion/ImportVerilog/basic.sv index 1e79f3beabd4..d26b65f9772a 100644 --- a/test/Conversion/ImportVerilog/basic.sv +++ b/test/Conversion/ImportVerilog/basic.sv @@ -1,4 +1,5 @@ // RUN: circt-translate --import-verilog %s | FileCheck %s +// RUN: circt-verilog --ir-moore %s // REQUIRES: slang // Internal issue in Slang v3 about jump depending on uninitialised value. From b9c202e6016e7d8cf609b098858d161d1e0c9945 Mon Sep 17 00:00:00 2001 From: Parker L <166864283+parker-research@users.noreply.github.com> Date: Mon, 12 Aug 2024 13:14:03 -0600 Subject: [PATCH 085/119] Explain IR acronym in README (#7506) * Explain IR acronym in README * Pluralize "Representations" --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0e2b7f2893cd..83b3ad4a7667 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ # โšก๏ธ "CIRCT" / Circuit IR Compilers and Tools -"CIRCT" stands for "Circuit IR Compilers and Tools". One might also interpret +"CIRCT" stands for "Circuit Intermediate Representations (IR) Compilers and Tools". One might also interpret it as the recursively as "CIRCT IR Compiler and Tools". The T can be selectively expanded as Tool, Translator, Team, Technology, Target, Tree, Type, ... we're ok with the ambiguity. From 6a0ba529b8771d332d28f33501a5e6cf75ce7906 Mon Sep 17 00:00:00 2001 From: Asuna Date: Sat, 10 Aug 2024 20:45:48 +0200 Subject: [PATCH 086/119] [HW][CAPI] Add a function to get an empty `InnerSymAttr` --- include/circt-c/Dialect/HW.h | 1 + lib/CAPI/Dialect/HW.cpp | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/include/circt-c/Dialect/HW.h b/include/circt-c/Dialect/HW.h index 4b2d7b3abb59..4310190d03a9 100644 --- a/include/circt-c/Dialect/HW.h +++ b/include/circt-c/Dialect/HW.h @@ -161,6 +161,7 @@ MLIR_CAPI_EXPORTED MlirStringRef hwTypeAliasTypeGetScope(MlirType typeAlias); MLIR_CAPI_EXPORTED bool hwAttrIsAInnerSymAttr(MlirAttribute); MLIR_CAPI_EXPORTED MlirAttribute hwInnerSymAttrGet(MlirAttribute symName); +MLIR_CAPI_EXPORTED MlirAttribute hwInnerSymAttrGetEmpty(MlirContext ctx); MLIR_CAPI_EXPORTED MlirAttribute hwInnerSymAttrGetSymName(MlirAttribute); MLIR_CAPI_EXPORTED bool hwAttrIsAInnerRefAttr(MlirAttribute); diff --git a/lib/CAPI/Dialect/HW.cpp b/lib/CAPI/Dialect/HW.cpp index 2ffb1f8e3649..732c322101a7 100644 --- a/lib/CAPI/Dialect/HW.cpp +++ b/lib/CAPI/Dialect/HW.cpp @@ -217,6 +217,10 @@ MlirAttribute hwInnerSymAttrGet(MlirAttribute symName) { return wrap(InnerSymAttr::get(cast(unwrap(symName)))); } +MlirAttribute hwInnerSymAttrGetEmpty(MlirContext ctx) { + return wrap(InnerSymAttr::get(unwrap(ctx))); +} + MlirAttribute hwInnerSymAttrGetSymName(MlirAttribute innerSymAttr) { return wrap((Attribute)cast(unwrap(innerSymAttr)).getSymName()); } From ff40aabb49203a12998575e7549cd2e803875445 Mon Sep 17 00:00:00 2001 From: John Demme Date: Tue, 13 Aug 2024 06:17:00 -0700 Subject: [PATCH 087/119] [PyCDE] Support for adding module constants to manifests (#7510) --- frontends/PyCDE/integration_test/esi_test.py | 11 ++++++---- .../test_software/esi_test.py | 16 ++++++++++---- frontends/PyCDE/src/pycde/common.py | 17 +++++++++++++++ frontends/PyCDE/src/pycde/module.py | 21 ++++++++++++++++--- frontends/PyCDE/src/pycde/support.py | 13 ++++++++++++ frontends/PyCDE/test/test_esi.py | 5 ++++- 6 files changed, 71 insertions(+), 12 deletions(-) diff --git a/frontends/PyCDE/integration_test/esi_test.py b/frontends/PyCDE/integration_test/esi_test.py index 2c78c15a41c1..ddc1cd4ced6a 100644 --- a/frontends/PyCDE/integration_test/esi_test.py +++ b/frontends/PyCDE/integration_test/esi_test.py @@ -7,6 +7,7 @@ import pycde from pycde import (AppID, Clock, Module, Reset, modparams, generator) from pycde.bsp import cosim +from pycde.common import Constant from pycde.constructs import Reg, Wire from pycde.esi import FuncService, MMIO, MMIOReadWriteCmdType from pycde.types import (Bits, Channel, UInt) @@ -15,21 +16,23 @@ import sys -class LoopbackInOutAdd7(Module): +class LoopbackInOutAdd(Module): """Loopback the request from the host, adding 7 to the first 15 bits.""" clk = Clock() rst = Reset() + add_amt = Constant(UInt(16), 11) + @generator def construct(ports): loopback = Wire(Channel(UInt(16))) - args = FuncService.get_call_chans(AppID("loopback_add7"), + args = FuncService.get_call_chans(AppID("add"), arg_type=UInt(24), result=loopback) ready = Wire(Bits(1)) data, valid = args.unwrap(ready) - plus7 = data + 7 + plus7 = data + LoopbackInOutAdd.add_amt.value data_chan, data_ready = loopback.type.wrap(plus7.as_uint(16), valid) data_chan_buffered = data_chan.buffer(ports.clk, ports.rst, 5) ready.assign(data_ready) @@ -96,7 +99,7 @@ class Top(Module): @generator def construct(ports): - LoopbackInOutAdd7(clk=ports.clk, rst=ports.rst) + LoopbackInOutAdd(clk=ports.clk, rst=ports.rst, appid=AppID("loopback")) for i in range(4, 18, 5): MMIOClient(i)() MMIOReadWriteClient(clk=ports.clk, rst=ports.rst) diff --git a/frontends/PyCDE/integration_test/test_software/esi_test.py b/frontends/PyCDE/integration_test/test_software/esi_test.py index f264cf923e06..4eeeabbe8cb1 100644 --- a/frontends/PyCDE/integration_test/test_software/esi_test.py +++ b/frontends/PyCDE/integration_test/test_software/esi_test.py @@ -67,13 +67,21 @@ def read_offset_check(i: int, add_amt: int): print(m.type_table) d = acc.build_accelerator() - -recv = d.ports[esi.AppID("loopback_add7")].read_port("result") +loopback = d.children[esi.AppID("loopback")] +recv = loopback.ports[esi.AppID("add")].read_port("result") recv.connect() -send = d.ports[esi.AppID("loopback_add7")].write_port("arg") +send = loopback.ports[esi.AppID("add")].write_port("arg") send.connect() +loopback_info = None +for mod_info in m.module_infos: + if mod_info.name == "LoopbackInOutAdd": + loopback_info = mod_info + break +assert loopback_info is not None +add_amt = mod_info.constants["add_amt"].value + ################################################################################ # Loopback add 7 tests ################################################################################ @@ -85,6 +93,6 @@ def read_offset_check(i: int, add_amt: int): print(f"data: {data}") print(f"resp: {resp}") -assert resp == data + 7 +assert resp == data + add_amt print("PASS") diff --git a/frontends/PyCDE/src/pycde/common.py b/frontends/PyCDE/src/pycde/common.py index b2e993164e8a..2fce095417e0 100644 --- a/frontends/PyCDE/src/pycde/common.py +++ b/frontends/PyCDE/src/pycde/common.py @@ -135,6 +135,23 @@ def __repr__(self) -> str: return f"{self.name}[{self.index}]" +class Constant: + """A constant value associated with a module. Gets added to the ESI system + manifest so it is accessible at runtime. + + Example usage: + + ``` + def ExampleModule(Module): + const_name = Constant(UInt(16), 42) + ``` + """ + + def __init__(self, type: Type, value: object): + self.type = type + self.value = value + + class _PyProxy: """Parent class for a Python object which has a corresponding IR op (i.e. a proxy class).""" diff --git a/frontends/PyCDE/src/pycde/module.py b/frontends/PyCDE/src/pycde/module.py index 46904adbbeb9..5f76cc9f8da1 100644 --- a/frontends/PyCDE/src/pycde/module.py +++ b/frontends/PyCDE/src/pycde/module.py @@ -6,9 +6,9 @@ from dataclasses import dataclass from typing import Any, List, Optional, Set, Tuple, Dict -from .common import (AppID, Clock, Input, ModuleDecl, Output, PortError, - _PyProxy, Reset) -from .support import (get_user_loc, _obj_to_attribute, create_type_string, +from .common import (AppID, Clock, Constant, Input, ModuleDecl, Output, + PortError, _PyProxy, Reset) +from .support import (get_user_loc, _obj_to_attribute, obj_to_typed_attribute, create_const_zero) from .signals import ClockSignal, Signal, _FromCirctValue from .types import ClockType, Type, _FromCirctType @@ -237,6 +237,7 @@ def scan_cls(self): clock_ports = set() reset_ports = set() generators = {} + constants = {} num_inputs = 0 num_outputs = 0 for attr_name, attr in self.cls_dct.items(): @@ -273,11 +274,14 @@ def scan_cls(self): ports.append(attr) elif isinstance(attr, Generator): generators[attr_name] = attr + elif isinstance(attr, Constant): + constants[attr_name] = attr self.ports = ports self.clocks = clock_ports self.resets = reset_ports self.generators = generators + self.constants = constants def create_port_proxy(self) -> PortProxyBase: """Create a proxy class for generators to use in order to access module @@ -475,6 +479,17 @@ def create_op(self, sys, symbol): else: self.add_metadata(sys, symbol, None) + # If there are associated constants, add them to the manifest. + if len(self.constants) > 0: + constants_dict: Dict[str, ir.Attribute] = {} + for name, constant in self.constants.items(): + constant_attr = obj_to_typed_attribute(constant.value, constant.type) + constants_dict[name] = constant_attr + with ir.InsertionPoint(sys.mod.body): + from .dialects.esi import esi + esi.SymbolConstantsOp(symbolRef=ir.FlatSymbolRefAttr.get(symbol), + constants=ir.DictAttr.get(constants_dict)) + if len(self.generators) > 0: if hasattr(self, "parameters") and self.parameters is not None: self.attributes["pycde.parameters"] = self.parameters diff --git a/frontends/PyCDE/src/pycde/support.py b/frontends/PyCDE/src/pycde/support.py index a276f444d680..b29db4634a2a 100644 --- a/frontends/PyCDE/src/pycde/support.py +++ b/frontends/PyCDE/src/pycde/support.py @@ -1,6 +1,12 @@ +from __future__ import annotations + from .circt import support from .circt import ir +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from .types import Type + import os @@ -43,6 +49,13 @@ def _obj_to_attribute(obj) -> ir.Attribute: "This is required for parameters.") +def obj_to_typed_attribute(obj: object, type: Type) -> ir.Attribute: + from .types import BitVectorType + if isinstance(type, BitVectorType): + return ir.IntegerAttr.get(type._type, obj) + raise ValueError(f"Type '{type}' conversion to attribute not supported yet.") + + __dir__ = os.path.dirname(__file__) _local_files = set([os.path.join(__dir__, x) for x in os.listdir(__dir__)]) _hidden_filenames = set(["functools.py"]) diff --git a/frontends/PyCDE/test/test_esi.py b/frontends/PyCDE/test/test_esi.py index cf4766897304..84f7878cc267 100644 --- a/frontends/PyCDE/test/test_esi.py +++ b/frontends/PyCDE/test/test_esi.py @@ -4,7 +4,7 @@ from pycde import (Clock, Input, InputChannel, Output, OutputChannel, Module, Reset, generator, types) from pycde import esi -from pycde.common import AppID, RecvBundle, SendBundle +from pycde.common import AppID, Constant, RecvBundle, SendBundle from pycde.constructs import Wire from pycde.esi import MMIO from pycde.module import Metadata @@ -36,6 +36,7 @@ class HostComms: # CHECK: esi.manifest.sym @LoopbackInOutTop name "LoopbackInOut" {{.*}}version "0.1" {bar = "baz", foo = 1 : i64} +# CHECK: esi.manifest.constants @LoopbackInOutTop {c1 = 54 : ui8} # CHECK-LABEL: hw.module @LoopbackInOutTop(in %clk : !seq.clock, in %rst : i1) @@ -59,6 +60,8 @@ class LoopbackInOutTop(Module): }, ) + c1 = Constant(UInt(8), 54) + @generator def construct(self): # Use Cosim to implement the 'HostComms' service. From ebb2429224aa67858d4b9ad51dedd53e8577452c Mon Sep 17 00:00:00 2001 From: Will Dietz Date: Tue, 13 Aug 2024 09:15:17 -0500 Subject: [PATCH 088/119] [Comb][Fold] Fix idemp n^2 and bugfix + more optimization. (#7514) * Fix n^2 behavior with canonicalizeIdempotentInputs. Add arbitrary "depth" check to bound the search. * Don't allow flattening or idempotent to search into operations defined in other blocks. * Support removing duplicates even when operands come from other blocks. (or(x, y, x) -> or(x, x) regardless of their origin). * Don't walk into operations with different two-state-ness in the idempotent operand canonicalizer. * When creating new operation in idempotent canonicalizer, create it with matching two-state-ness. Add tests for functional changes above. --- lib/Dialect/Comb/CombFolds.cpp | 70 ++++++++++++++++--------- test/Dialect/Comb/canonicalization.mlir | 53 +++++++++++++++++++ 2 files changed, 97 insertions(+), 26 deletions(-) diff --git a/lib/Dialect/Comb/CombFolds.cpp b/lib/Dialect/Comb/CombFolds.cpp index 913be97a76ff..96cb562b408c 100644 --- a/lib/Dialect/Comb/CombFolds.cpp +++ b/lib/Dialect/Comb/CombFolds.cpp @@ -122,7 +122,7 @@ static inline ComplementMatcher m_Complement(const SubType &subExpr) { } /// Return true if the op will be flattened afterwards. Op will be flattend if -/// it has a single user which has a same op type. +/// it has a single user which has a same op type. User must be in same block. static bool shouldBeFlattened(Operation *op) { assert((isa(op) && "must be commutative operations")); @@ -130,7 +130,8 @@ static bool shouldBeFlattened(Operation *op) { auto *user = *op->getUsers().begin(); return user->getName() == op->getName() && op->getAttrOfType("twoState") == - user->getAttrOfType("twoState"); + user->getAttrOfType("twoState") && + op->getBlock() == user->getBlock(); } return false; } @@ -169,8 +170,11 @@ static bool tryFlatteningOperands(Operation *op, PatternRewriter &rewriter) { Value value = *element.current++; auto *flattenOp = value.getDefiningOp(); + // If not defined by a compatible operation of the same kind and + // from the same block, keep this as-is. if (!flattenOp || flattenOp->getName() != op->getName() || - flattenOp == op || binFlag != op->hasAttrOfType("twoState")) { + flattenOp == op || binFlag != op->hasAttrOfType("twoState") || + flattenOp->getBlock() != op->getBlock()) { newOperands.push_back(value); continue; } @@ -933,34 +937,48 @@ static Value getCommonOperand(Op op) { /// Example: `and(x, y, x, z)` -> `and(x, y, z)` template static bool canonicalizeIdempotentInputs(Op op, PatternRewriter &rewriter) { + // Depth limit to search, in operations. Chosen arbitrarily, keep small. + constexpr unsigned limit = 3; auto inputs = op.getInputs(); llvm::SmallSetVector uniqueInputs(inputs.begin(), inputs.end()); - llvm::SmallDenseSet checked; + llvm::SmallDenseSet checked; checked.insert(op); - llvm::SmallVector worklist; - for (auto input : inputs) { - if (input != op) - worklist.push_back(input); - } + struct OpWithDepth { + Op op; + unsigned depth; + }; + llvm::SmallVector worklist; + + auto enqueue = [&worklist, &checked, &op](Value input, unsigned depth) { + // Add to worklist if within depth limit, is defined in the same block by + // the same kind of operation, has same two-state-ness, and not enqueued + // previously. + if (depth < limit && input.getParentBlock() == op->getBlock()) { + auto inputOp = input.template getDefiningOp(); + if (inputOp && inputOp.getTwoState() == op.getTwoState() && + checked.insert(inputOp).second) + worklist.push_back({inputOp, depth + 1}); + } + }; - while (!worklist.empty()) { - auto element = worklist.pop_back_val(); + for (auto input : uniqueInputs) + enqueue(input, 0); - if (auto idempotentOp = element.getDefiningOp()) { - for (auto input : idempotentOp.getInputs()) { - uniqueInputs.remove(input); + while (!worklist.empty()) { + auto item = worklist.pop_back_val(); - if (checked.insert(input).second) - worklist.push_back(input); - } + for (auto input : item.op.getInputs()) { + uniqueInputs.remove(input); + enqueue(input, item.depth); } } if (uniqueInputs.size() < inputs.size()) { replaceOpWithNewOpAndCopyName(rewriter, op, op.getType(), - uniqueInputs.getArrayRef()); + uniqueInputs.getArrayRef(), + op.getTwoState()); return true; } @@ -968,12 +986,8 @@ static bool canonicalizeIdempotentInputs(Op op, PatternRewriter &rewriter) { } LogicalResult AndOp::canonicalize(AndOp op, PatternRewriter &rewriter) { - if (hasOperandsOutsideOfBlock(&*op)) - return failure(); - auto inputs = op.getInputs(); auto size = inputs.size(); - assert(size > 1 && "expected 2 or more operands, `fold` should handle this"); // and(x, and(...)) -> and(x, ...) -- flatten if (tryFlatteningOperands(op, rewriter)) @@ -985,6 +999,10 @@ LogicalResult AndOp::canonicalize(AndOp op, PatternRewriter &rewriter) { if (size > 1 && canonicalizeIdempotentInputs(op, rewriter)) return success(); + if (hasOperandsOutsideOfBlock(&*op)) + return failure(); + assert(size > 1 && "expected 2 or more operands, `fold` should handle this"); + // Patterns for and with a constant on RHS. APInt value; if (matchPattern(inputs.back(), m_ConstantInt(&value))) { @@ -1255,12 +1273,8 @@ static bool canonicalizeOrOfConcatsWithCstOperands(OrOp op, size_t concatIdx1, } LogicalResult OrOp::canonicalize(OrOp op, PatternRewriter &rewriter) { - if (hasOperandsOutsideOfBlock(&*op)) - return failure(); - auto inputs = op.getInputs(); auto size = inputs.size(); - assert(size > 1 && "expected 2 or more operands"); // or(x, or(...)) -> or(x, ...) -- flatten if (tryFlatteningOperands(op, rewriter)) @@ -1272,6 +1286,10 @@ LogicalResult OrOp::canonicalize(OrOp op, PatternRewriter &rewriter) { if (size > 1 && canonicalizeIdempotentInputs(op, rewriter)) return success(); + if (hasOperandsOutsideOfBlock(&*op)) + return failure(); + assert(size > 1 && "expected 2 or more operands"); + // Patterns for and with a constant on RHS. APInt value; if (matchPattern(inputs.back(), m_ConstantInt(&value))) { diff --git a/test/Dialect/Comb/canonicalization.mlir b/test/Dialect/Comb/canonicalization.mlir index ea4f6af92f9b..faa7e4033452 100644 --- a/test/Dialect/Comb/canonicalization.mlir +++ b/test/Dialect/Comb/canonicalization.mlir @@ -1553,6 +1553,59 @@ hw.module @OrMuxSameTrueValueAndZero(in %tag_0: i1, in %tag_1: i1, in %tag_2: i1 "terminator"(%add2) : (i32) -> () }) : () -> () +// CHECK-LABEL: "test.acrossBlockCanonicalizationBarrierFlattenAndIdem" +// CHECK: ^bb1: +// CHECK-NEXT: %[[OUT:.+]] = comb.or %0, %1, %2 : i32 +// CHECK-NEXT: "terminator"(%[[OUT]]) : (i32) -> () +"test.acrossBlockCanonicalizationBarrierFlattenAndIdem"() ({ +^bb0(%arg0: i32, %arg1: i32, %arg2: i32): + %0 = comb.or %arg0, %arg1 : i32 + %1 = comb.or %arg1, %arg2 : i32 + %2 = comb.or %arg0, %arg2 : i32 + "terminator"() : () -> () +^bb1: // no predecessors + // Flatten and unique, but not across blocks. + %3 = comb.or %0, %1 : i32 + %4 = comb.or %1, %2 : i32 + %5 = comb.or %3, %4, %0, %1, %1, %2 : i32 + + "terminator"(%5) : (i32) -> () +}) : () -> () + +// CHECK-LABEL: "test.acrossBlockCanonicalizationBarrierIdem" +// CHECK: ^bb1: +// CHECK-NEXT: %[[OUT1:.+]] = comb.or %0, %1 : i32 +// CHECK-NEXT: %[[OUT2:.+]] = comb.or %[[OUT1]], %arg0 : i32 +// CHECK-NEXT: "terminator"(%[[OUT1]], %[[OUT2]]) : (i32, i32) -> () +"test.acrossBlockCanonicalizationBarrierIdem"() ({ +^bb0(%arg0: i32, %arg1: i32, %arg2: i32): + %0 = comb.or %arg0, %arg1 : i32 + %1 = comb.or %arg1, %arg2 : i32 + "terminator"() : () -> () +^bb1: // no predecessors + %2 = comb.or %0, %1, %1 : i32 + %3 = comb.or %2, %0, %1, %arg0 : i32 + + "terminator"(%2, %3) : (i32, i32) -> () +}) : () -> () + +// Check multi-operation idempotent operand deduplication. +// CHECK-LABEL: @IdemTwoState +// CHECK-NEXT: %[[ZERO:.+]] = comb.or bin %cond, %val1 +// CHECK-NEXT: %[[ONE:.+]] = comb.or bin %val1, %val2 +// Don't allow dropping these (%0/%1) due to two-state differences. +// CHECK-NEXT: %[[TWO:.+]] = comb.or %[[ZERO]], %[[ONE]] +// New operation should preserve two-state-ness. +// CHECK-NEXT: %[[THREE:.+]] = comb.or bin %[[TWO]], %[[ZERO]], %[[ONE]] +// CHECK-NEXT: hw.output %[[ZERO]], %[[ONE]], %[[TWO]], %[[THREE]] +hw.module @IdemTwoState(in %cond: i32, in %val1: i32, in %val2: i32, out o1: i32, out o2: i32, out o3: i32, out o4: i32) { + %0 = comb.or bin %cond, %val1 : i32 + %1 = comb.or bin %val1, %val2: i32 + %2 = comb.or %0, %1 : i32 + %3 = comb.or bin %cond, %val1, %val2, %2, %0, %1 : i32 + hw.output %0, %1, %2, %3: i32, i32, i32, i32 +} + // CHECK-LABEL: hw.module @combineOppositeBinCmpIntoConstant // CHECK: %[[TRUE:.+]] = hw.constant true // CHECK: %[[FALSE:.+]] = hw.constant false From 4cfcbdad04a3e0ce91c709eb2205b30cb85c566f Mon Sep 17 00:00:00 2001 From: Mike Urbach Date: Tue, 13 Aug 2024 09:25:46 -0600 Subject: [PATCH 089/119] [OM] Support list concatenation in the Evaluator. (#7511) List concatenation is implemented by first ensuring all the sub-lists are evaluated, and then appending the elements of each sub-list into the final ListValue that represents the concatenation of the lists. --- .../circt/Dialect/OM/Evaluator/Evaluator.h | 3 + lib/Dialect/OM/Evaluator/Evaluator.cpp | 50 +++++++- .../Dialect/OM/Evaluator/EvaluatorTests.cpp | 111 ++++++++++++++++++ 3 files changed, 160 insertions(+), 4 deletions(-) diff --git a/include/circt/Dialect/OM/Evaluator/Evaluator.h b/include/circt/Dialect/OM/Evaluator/Evaluator.h index 3834e626c108..2592fbdc7e2b 100644 --- a/include/circt/Dialect/OM/Evaluator/Evaluator.h +++ b/include/circt/Dialect/OM/Evaluator/Evaluator.h @@ -481,6 +481,9 @@ struct Evaluator { FailureOr evaluateListCreate(ListCreateOp op, ActualParameters actualParams, Location loc); + FailureOr evaluateListConcat(ListConcatOp op, + ActualParameters actualParams, + Location loc); FailureOr evaluateTupleCreate(TupleCreateOp op, ActualParameters actualParams, Location loc); diff --git a/lib/Dialect/OM/Evaluator/Evaluator.cpp b/lib/Dialect/OM/Evaluator/Evaluator.cpp index d029cfc9df55..e100a0e8d086 100644 --- a/lib/Dialect/OM/Evaluator/Evaluator.cpp +++ b/lib/Dialect/OM/Evaluator/Evaluator.cpp @@ -167,10 +167,10 @@ FailureOr circt::om::Evaluator::getOrCreateValue( evaluator::PathValue::getEmptyPath(loc)); return success(result); }) - .Case( - [&](auto op) { - return getPartiallyEvaluatedValue(op.getType(), loc); - }) + .Case([&](auto op) { + return getPartiallyEvaluatedValue(op.getType(), loc); + }) .Case([&](auto op) { return getPartiallyEvaluatedValue(op.getType(), op.getLoc()); }) @@ -360,6 +360,9 @@ circt::om::Evaluator::evaluateValue(Value value, ActualParameters actualParams, .Case([&](ListCreateOp op) { return evaluateListCreate(op, actualParams, loc); }) + .Case([&](ListConcatOp op) { + return evaluateListConcat(op, actualParams, loc); + }) .Case([&](TupleCreateOp op) { return evaluateTupleCreate(op, actualParams, loc); }) @@ -583,6 +586,45 @@ circt::om::Evaluator::evaluateListCreate(ListCreateOp op, return list; } +/// Evaluator dispatch function for List concatenation. +FailureOr +circt::om::Evaluator::evaluateListConcat(ListConcatOp op, + ActualParameters actualParams, + Location loc) { + // Evaluate the List concat op itself, in case it hasn't been evaluated yet. + SmallVector values; + auto list = getOrCreateValue(op, actualParams, loc); + + // Extract the ListValue, either directly or through an object reference. + auto extractList = [](evaluator::EvaluatorValue *value) { + return std::move( + llvm::TypeSwitch( + value) + .Case([](evaluator::ListValue *val) { return val; }) + .Case([](evaluator::ReferenceValue *val) { + return cast(val->getStrippedValue()->get()); + })); + }; + + for (auto operand : op.getOperands()) { + auto result = evaluateValue(operand, actualParams, loc); + if (failed(result)) + return result; + if (!result.value()->isFullyEvaluated()) + return list; + + // Append each EvaluatorValue from the sublist. + evaluator::ListValue *subList = extractList(result.value().get()); + for (auto subValue : subList->getElements()) + values.push_back(subValue); + } + + // Return the concatenated list. + llvm::cast(list.value().get()) + ->setElements(std::move(values)); + return list; +} + /// Evaluator dispatch function for Tuple creation. FailureOr circt::om::Evaluator::evaluateTupleCreate(TupleCreateOp op, diff --git a/unittests/Dialect/OM/Evaluator/EvaluatorTests.cpp b/unittests/Dialect/OM/Evaluator/EvaluatorTests.cpp index 8b9e24b0a86b..abd9547c4632 100644 --- a/unittests/Dialect/OM/Evaluator/EvaluatorTests.cpp +++ b/unittests/Dialect/OM/Evaluator/EvaluatorTests.cpp @@ -919,4 +919,115 @@ TEST(EvaluatorTests, IntegerBinaryArithmeticWidthMismatch) { .getValue()); } +TEST(EvaluatorTests, ListConcat) { + StringRef mod = "om.class @ListConcat() {" + " %0 = om.constant #om.integer<0 : i8> : !om.integer" + " %1 = om.constant #om.integer<1 : i8> : !om.integer" + " %2 = om.constant #om.integer<2 : i8> : !om.integer" + " %l0 = om.list_create %0, %1 : !om.integer" + " %l1 = om.list_create %2 : !om.integer" + " %concat = om.list_concat %l0, %l1 : !om.list" + " om.class.field @result, %concat : !om.list" + "}"; + + DialectRegistry registry; + registry.insert(); + + MLIRContext context(registry); + context.getOrLoadDialect(); + + OwningOpRef owning = + parseSourceString(mod, ParserConfig(&context)); + + Evaluator evaluator(owning.release()); + + auto result = + evaluator.instantiate(StringAttr::get(&context, "ListConcat"), {}); + + ASSERT_TRUE(succeeded(result)); + + auto fieldValue = llvm::cast(result.value().get()) + ->getField("result") + .value(); + + auto finalList = + llvm::cast(fieldValue.get())->getElements(); + + ASSERT_EQ(3, finalList.size()); + + ASSERT_EQ(0, llvm::cast(finalList[0].get()) + ->getAs() + .getValue() + .getValue()); + + ASSERT_EQ(1, llvm::cast(finalList[1].get()) + ->getAs() + .getValue() + .getValue()); + + ASSERT_EQ(2, llvm::cast(finalList[2].get()) + ->getAs() + .getValue() + .getValue()); +} + +TEST(EvaluatorTests, ListConcatField) { + StringRef mod = + "om.class @ListField() {" + " %0 = om.constant #om.integer<2 : i8> : !om.integer" + " %1 = om.list_create %0 : !om.integer" + " om.class.field @value, %1 : !om.list" + "}" + "om.class @ListConcatField() {" + " %listField = om.object @ListField() : () -> !om.class.type<@ListField>" + " %0 = om.constant #om.integer<0 : i8> : !om.integer" + " %1 = om.constant #om.integer<1 : i8> : !om.integer" + " %l0 = om.list_create %0, %1 : !om.integer" + " %l1 = om.object.field %listField, [@value] : " + "(!om.class.type<@ListField>) -> !om.list" + " %concat = om.list_concat %l0, %l1 : !om.list" + " om.class.field @result, %concat : !om.list" + "}"; + + DialectRegistry registry; + registry.insert(); + + MLIRContext context(registry); + context.getOrLoadDialect(); + + OwningOpRef owning = + parseSourceString(mod, ParserConfig(&context)); + + Evaluator evaluator(owning.release()); + + auto result = + evaluator.instantiate(StringAttr::get(&context, "ListConcatField"), {}); + + ASSERT_TRUE(succeeded(result)); + + auto fieldValue = llvm::cast(result.value().get()) + ->getField("result") + .value(); + + auto finalList = + llvm::cast(fieldValue.get())->getElements(); + + ASSERT_EQ(3, finalList.size()); + + ASSERT_EQ(0, llvm::cast(finalList[0].get()) + ->getAs() + .getValue() + .getValue()); + + ASSERT_EQ(1, llvm::cast(finalList[1].get()) + ->getAs() + .getValue() + .getValue()); + + ASSERT_EQ(2, llvm::cast(finalList[2].get()) + ->getAs() + .getValue() + .getValue()); +} + } // namespace From bf32a889fbde9b890762971fd4899fdf26d1671b Mon Sep 17 00:00:00 2001 From: Martin Erhart Date: Tue, 13 Aug 2024 17:48:03 +0100 Subject: [PATCH 090/119] [LLHD] Relax sig parent constraint (#7515) This is necessary because the moore dialect does not enforce such constraints for its variables and thus they can occur in functions --- include/circt/Dialect/LLHD/IR/LLHDSignalOps.td | 1 - test/Dialect/LLHD/IR/errors.mlir | 9 --------- 2 files changed, 10 deletions(-) diff --git a/include/circt/Dialect/LLHD/IR/LLHDSignalOps.td b/include/circt/Dialect/LLHD/IR/LLHDSignalOps.td index d03b2397a4e2..165218abcb3b 100644 --- a/include/circt/Dialect/LLHD/IR/LLHDSignalOps.td +++ b/include/circt/Dialect/LLHD/IR/LLHDSignalOps.td @@ -13,7 +13,6 @@ include "mlir/IR/EnumAttr.td" def SigOp : LLHDOp<"sig", [ - ParentOneOf<["hw::HWModuleOp", "llhd::ProcessOp"]>, TypesMatchWith< "type of 'init' and underlying type of 'signal' have to match.", "init", "result", "hw::InOutType::get($_self)"> diff --git a/test/Dialect/LLHD/IR/errors.mlir b/test/Dialect/LLHD/IR/errors.mlir index 75a1a431c735..48d0d2232a74 100644 --- a/test/Dialect/LLHD/IR/errors.mlir +++ b/test/Dialect/LLHD/IR/errors.mlir @@ -113,12 +113,3 @@ hw.module @check_illegal_drv(inout %sig : i1) { %time = llhd.constant_time #llhd.time<1ns, 0d, 0e> "llhd.drv"(%sig, %c, %time) {} : (!hw.inout, i32, !llhd.time) -> () } - -// ----- - -func.func @illegal_sig_parent(%arg0 : i1) { - // expected-error @+1 {{expects parent op to be one of 'hw.module, llhd.process'}} - %0 = llhd.sig "sig" %arg0 : i1 - - return -} From 917c940b81578976c8481fc33dee86d3b448a366 Mon Sep 17 00:00:00 2001 From: elhewaty Date: Tue, 13 Aug 2024 20:29:04 +0300 Subject: [PATCH 091/119] [Arc] Add Initial Cost Model (#7360) * [Arc] Add Initial Cost Model * [Arc] Add the DummyAnalysisTester pass * [Arc] Fix clang-format failure * [Arc] Address @fabianschuiki comments * [Arc] Remove DummyAnalysisTester.cpp --- include/circt/Dialect/Arc/ArcCostModel.h | 62 ++++++++ include/circt/Dialect/Arc/ArcPasses.h | 1 + include/circt/Dialect/Arc/ArcPasses.td | 18 +++ lib/Dialect/Arc/ArcCostModel.cpp | 132 ++++++++++++++++++ lib/Dialect/Arc/CMakeLists.txt | 3 + lib/Dialect/Arc/Transforms/CMakeLists.txt | 1 + lib/Dialect/Arc/Transforms/PrintCostModel.cpp | 56 ++++++++ 7 files changed, 273 insertions(+) create mode 100644 include/circt/Dialect/Arc/ArcCostModel.h create mode 100644 lib/Dialect/Arc/ArcCostModel.cpp create mode 100644 lib/Dialect/Arc/Transforms/PrintCostModel.cpp diff --git a/include/circt/Dialect/Arc/ArcCostModel.h b/include/circt/Dialect/Arc/ArcCostModel.h new file mode 100644 index 000000000000..769a3ceb5e2b --- /dev/null +++ b/include/circt/Dialect/Arc/ArcCostModel.h @@ -0,0 +1,62 @@ +//===- ArcCostModel.h -----------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_ARC_ARCCOSTMODEL_H +#define CIRCT_DIALECT_ARC_ARCCOSTMODEL_H + +#include "circt/Dialect/Arc/ArcOps.h" +#include "mlir/IR/Operation.h" +#include "mlir/Pass/AnalysisManager.h" + +using namespace mlir; + +namespace circt { +namespace arc { + +struct OperationCosts { + size_t normalCost{0}; + size_t packingCost{0}; + size_t shufflingCost{0}; + size_t vectorizeOpsBodyCost{0}; + size_t totalCost() const { + return normalCost + packingCost + shufflingCost + vectorizeOpsBodyCost; + } + OperationCosts &operator+=(const OperationCosts &other) { + this->normalCost += other.normalCost; + this->packingCost += other.packingCost; + this->shufflingCost += other.shufflingCost; + this->vectorizeOpsBodyCost += other.vectorizeOpsBodyCost; + return *this; + } +}; + +class ArcCostModel { +public: + OperationCosts getCost(Operation *op); + +private: + OperationCosts computeOperationCost(Operation *op); + + // gets the cost to pack the vectors we have some cases we need to consider: + // 1: the input is scalar so we can give it a cost of 1 + // 2: the input is a result of another vector but with no shuffling so the + // is 0 + // 3: the input is a result of another vector but with some shuffling so + // the cost is the (number of out of order elements) * 2 + // 4: the input is a mix of some vectors: + // a) same order we multiply by 2 + // b) shuffling we multiply by 3 + OperationCosts getInputVectorsCost(VectorizeOp vecOp); + size_t getShufflingCost(const ValueRange &inputVec, bool isSame = false); + DenseMap opCostCache; +}; + +} // namespace arc +} // namespace circt + +#endif // CIRCT_DIALECT_ARC_ARCCOSTMODEL_H diff --git a/include/circt/Dialect/Arc/ArcPasses.h b/include/circt/Dialect/Arc/ArcPasses.h index d8cbb6f0c19a..bd185fa147ef 100644 --- a/include/circt/Dialect/Arc/ArcPasses.h +++ b/include/circt/Dialect/Arc/ArcPasses.h @@ -46,6 +46,7 @@ std::unique_ptr createLowerVectorizationsPass( LowerVectorizationsModeEnum mode = LowerVectorizationsModeEnum::Full); std::unique_ptr createMakeTablesPass(); std::unique_ptr createMuxToControlFlowPass(); +std::unique_ptr createPrintCostModelPass(); std::unique_ptr createSimplifyVariadicOpsPass(); std::unique_ptr createSplitLoopsPass(); std::unique_ptr createStripSVPass(); diff --git a/include/circt/Dialect/Arc/ArcPasses.td b/include/circt/Dialect/Arc/ArcPasses.td index 3a44ce80b354..6a89f69c7b02 100644 --- a/include/circt/Dialect/Arc/ArcPasses.td +++ b/include/circt/Dialect/Arc/ArcPasses.td @@ -58,6 +58,24 @@ def Dedup : Pass<"arc-dedup", "mlir::ModuleOp"> { ]; } +def PrintCostModel : Pass<"arc-print-cost-model", "mlir::ModuleOp"> { + let summary = "A dymmy pass to test analysis passes"; + let constructor = "circt::arc::createPrintCostModelPass()"; + let dependentDialects = ["arc::ArcDialect"]; + let statistics = [ + Statistic<"moduleCost", "Operation(s)", + "Number of operations in the module">, + Statistic<"packingCost", "Pack operations(s)", + "Number of scalar to vector packking in the module">, + Statistic<"shufflingCost", "Shuffle operation(s)", + "Number of shuffles done to set up the VectorizeOps">, + Statistic<"vectoroizeOpsBodyCost", "VectorizeOps Body Cost", + "Number of operations inside the body of the VectorizeOps">, + Statistic<"allVectorizeOpsCost", "All VectorizeOps Cost", + "Total Cost of all VectorizeOps in the module"> + ]; +} + def FindInitialVectors : Pass<"arc-find-initial-vectors", "mlir::ModuleOp"> { let summary = "Find initial groups of vectorizable ops"; let constructor = "circt::arc::createFindInitialVectorsPass()"; diff --git a/lib/Dialect/Arc/ArcCostModel.cpp b/lib/Dialect/Arc/ArcCostModel.cpp new file mode 100644 index 000000000000..32393506df17 --- /dev/null +++ b/lib/Dialect/Arc/ArcCostModel.cpp @@ -0,0 +1,132 @@ +//===- ArcCostModel.cpp ---------------------------------------------------===// +// +// 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/Arc/ArcCostModel.h" +#include "circt/Dialect/Comb/CombOps.h" +#include "mlir/Dialect/Func/IR/FuncOps.h" +#include + +using namespace llvm; +using namespace circt; +using namespace arc; +using namespace std; + +// FIXME: May be refined and we have more accurate operation costs +enum class OperationCost : size_t { + NOCOST, + NORMALCOST, + PACKCOST = 2, + EXTRACTCOST = 3, + CONCATCOST = 3, + SAMEVECTORNOSHUFFLE = 0, + SAMEVECTORSHUFFLECOST = 2, + DIFFERENTVECTORNOSHUFFLE = 2, + DIFFERENTVECTORSHUFFLECOST = 3 +}; + +OperationCosts ArcCostModel::getCost(Operation *op) { + return computeOperationCost(op); +} + +OperationCosts ArcCostModel::computeOperationCost(Operation *op) { + if (auto it = opCostCache.find(op); it != opCostCache.end()) + return it->second; + + OperationCosts costs; + + if (isa(op)) + costs.normalCost = size_t(OperationCost::CONCATCOST); + else if (isa(op)) + costs.normalCost = size_t(OperationCost::EXTRACTCOST); + else if (auto vecOp = dyn_cast(op)) { + // VectorizeOpCost = packingCost + shufflingCost + bodyCost + OperationCosts inputVecCosts = getInputVectorsCost(vecOp); + costs.packingCost += inputVecCosts.packingCost; + costs.shufflingCost += inputVecCosts.shufflingCost; + + for (auto ®ion : op->getRegions()) { + for (auto &block : region) { + for (auto &innerOp : block) { + OperationCosts innerCosts = computeOperationCost(&innerOp); + costs.vectorizeOpsBodyCost += innerCosts.totalCost(); + } + } + } + } else if (auto callableOp = dyn_cast(op)) { + // Callable Op? then resolve! + if (auto *calledOp = callableOp.resolveCallable()) + return opCostCache[callableOp] = computeOperationCost(calledOp); + } else if (isa(op)) { + // Get the body cost + for (auto ®ion : op->getRegions()) + for (auto &block : region) + for (auto &innerOp : block) + costs += computeOperationCost(&innerOp); + } else + costs.normalCost = size_t(OperationCost::NORMALCOST); + + return opCostCache[op] = costs; +} + +OperationCosts ArcCostModel::getInputVectorsCost(VectorizeOp vecOp) { + OperationCosts costs; + for (auto inputVec : vecOp.getInputs()) { + if (auto otherVecOp = inputVec[0].getDefiningOp(); + all_of(inputVec.begin(), inputVec.end(), [&](auto element) { + return element.template getDefiningOp() == otherVecOp; + })) { + // This means that they came from the same vector or + // VectorizeOp == null so they are all scalars + + // Check if they all scalars we multiply by the PACKCOST (SHL/R + OR) + if (!otherVecOp) + costs.packingCost += inputVec.size() * size_t(OperationCost::PACKCOST); + else + costs.shufflingCost += inputVec == otherVecOp.getResults() + ? size_t(OperationCost::SAMEVECTORNOSHUFFLE) + : getShufflingCost(inputVec, true); + } else + // inputVector consists of elements from different vectotrize ops and + // may have scalars as well. + costs.shufflingCost += getShufflingCost(inputVec); + } + + return costs; +} + +size_t ArcCostModel::getShufflingCost(const ValueRange &inputVec, bool isSame) { + size_t totalCost = 0; + if (isSame) { + auto vecOp = inputVec[0].getDefiningOp(); + for (auto [elem, orig] : llvm::zip(inputVec, vecOp.getResults())) + if (elem != orig) + ++totalCost; + + return totalCost * size_t(OperationCost::SAMEVECTORSHUFFLECOST); + } + + for (size_t i = 0; i < inputVec.size(); ++i) { + auto otherVecOp = inputVec[i].getDefiningOp(); + // If the element is not a result of a vector operation then it's a result + // of a scalar operation, then it just needs to be packed into the vector. + if (!otherVecOp) + totalCost += size_t(OperationCost::PACKCOST); + else { + // If it's a result of a vector operation, then we have two cases: + // (1) Its order in `inputVec` is the same as its order in the result of + // the defining op. + // (2) the order is different. + size_t idx = find(otherVecOp.getResults().begin(), + otherVecOp.getResults().end(), inputVec[i]) - + otherVecOp.getResults().begin(); + totalCost += i == idx ? size_t(OperationCost::DIFFERENTVECTORNOSHUFFLE) + : size_t(OperationCost::DIFFERENTVECTORSHUFFLECOST); + } + } + return totalCost; +} diff --git a/lib/Dialect/Arc/CMakeLists.txt b/lib/Dialect/Arc/CMakeLists.txt index 00a58eb30d41..e6ed7c9c4533 100644 --- a/lib/Dialect/Arc/CMakeLists.txt +++ b/lib/Dialect/Arc/CMakeLists.txt @@ -3,6 +3,7 @@ set(CIRCT_Arc_Sources ArcFolds.cpp ArcOps.cpp ArcTypes.cpp + ArcCostModel.cpp ModelInfo.cpp ) @@ -26,11 +27,13 @@ add_circt_dialect_library(CIRCTArc Support LINK_LIBS PUBLIC + CIRCTComb CIRCTHW CIRCTSeq MLIRIR MLIRInferTypeOpInterface MLIRSideEffectInterfaces + MLIRFuncDialect ) add_circt_library(CIRCTArcReductions diff --git a/lib/Dialect/Arc/Transforms/CMakeLists.txt b/lib/Dialect/Arc/Transforms/CMakeLists.txt index ea3892cefb7e..d3690cd408be 100644 --- a/lib/Dialect/Arc/Transforms/CMakeLists.txt +++ b/lib/Dialect/Arc/Transforms/CMakeLists.txt @@ -18,6 +18,7 @@ add_circt_dialect_library(CIRCTArcTransforms LowerVectorizations.cpp MakeTables.cpp MuxToControlFlow.cpp + PrintCostModel.cpp SimplifyVariadicOps.cpp SplitFuncs.cpp SplitLoops.cpp diff --git a/lib/Dialect/Arc/Transforms/PrintCostModel.cpp b/lib/Dialect/Arc/Transforms/PrintCostModel.cpp new file mode 100644 index 000000000000..2f8a03be392e --- /dev/null +++ b/lib/Dialect/Arc/Transforms/PrintCostModel.cpp @@ -0,0 +1,56 @@ +//===- DummyAnalysisTester.cpp --------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// This is a dummy pass to test the cost model results it doesn't do any thing. +// just walks over the ops to compute some statistics. +// +//===----------------------------------------------------------------------===// + +#include "circt/Dialect/Arc/ArcCostModel.h" +#include "circt/Dialect/Arc/ArcPasses.h" +#include "circt/Dialect/HW/HWOps.h" +#include "mlir/IR/MLIRContext.h" +#include "mlir/Pass/Pass.h" + +#define DEBUG_TYPE "arc-print-cost-model" + +namespace circt { +namespace arc { +#define GEN_PASS_DEF_PRINTCOSTMODEL +#include "circt/Dialect/Arc/ArcPasses.h.inc" +} // namespace arc +} // namespace circt + +using namespace circt; +using namespace arc; + +namespace { +struct PrintCostModelPass + : public arc::impl::PrintCostModelBase { + void runOnOperation() override; +}; +} // namespace + +void PrintCostModelPass::runOnOperation() { + OperationCosts statVars; + ArcCostModel arcCostModel; + for (auto moduleOp : getOperation().getOps()) { + moduleOp.walk([&](Operation *op) { statVars += arcCostModel.getCost(op); }); + } + + moduleCost = statVars.totalCost(); + packingCost = statVars.packingCost; + shufflingCost = statVars.shufflingCost; + vectoroizeOpsBodyCost = statVars.vectorizeOpsBodyCost; + allVectorizeOpsCost = statVars.packingCost + statVars.shufflingCost + + statVars.vectorizeOpsBodyCost; +} + +std::unique_ptr arc::createPrintCostModelPass() { + return std::make_unique(); +} From 8b38b9ecb73007eab0b3542ec988fdc8473eade9 Mon Sep 17 00:00:00 2001 From: Robert Young Date: Wed, 14 Aug 2024 13:23:52 -0400 Subject: [PATCH 092/119] LowerLayers: fix how we emit layer bindfile headers (#7516) For each layer, we emit a bindfile. Each bindfile has a header consisting of an include guard, and an `include for each parent layer's bindfile. To emit the bindfile's headers, we walk the layer declarations, and keep track of a stack of parent layers, which we use to emit the `includes. Unfortunately, a bug in the stack management code means that we forget to unwind the stack of parent layers, when the curent layer has no parents. This caused us to emit an include from one layer to the previous layer, when the previous layer had any children. This PR fixes the stack unwind loop to correctly clear the stack, when the current layer has no parents. --- lib/Dialect/FIRRTL/Transforms/LowerLayers.cpp | 2 +- test/Dialect/FIRRTL/lower-layers.mlir | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/lib/Dialect/FIRRTL/Transforms/LowerLayers.cpp b/lib/Dialect/FIRRTL/Transforms/LowerLayers.cpp index ffa8d5dec734..96b91a8ec3d2 100644 --- a/lib/Dialect/FIRRTL/Transforms/LowerLayers.cpp +++ b/lib/Dialect/FIRRTL/Transforms/LowerLayers.cpp @@ -837,7 +837,7 @@ void LowerLayersPass::runOnOperation() { StringRef circuitName = circuitOp.getName(); circuitOp.walk([&](LayerOp layerOp) { auto parentOp = layerOp->getParentOfType(); - while (parentOp && parentOp != layers.back().first) + while (!layers.empty() && parentOp != layers.back().first) layers.pop_back(); if (layerOp.getConvention() == LayerConvention::Inline) { diff --git a/test/Dialect/FIRRTL/lower-layers.mlir b/test/Dialect/FIRRTL/lower-layers.mlir index 8ff13ed3a649..74b6f7af71f7 100644 --- a/test/Dialect/FIRRTL/lower-layers.mlir +++ b/test/Dialect/FIRRTL/lower-layers.mlir @@ -717,3 +717,22 @@ firrtl.circuit "Foo" attributes { // // CHECK: sv.verbatim // CHECK-SAME: #hw.output_file<"testbench{{/|\\\\}}layers_Foo_A.sv", excludeFromFileList> + + +// ----- +// Check that we correctly implement the verilog header and footer for B. + +firrtl.circuit "Foo" { + firrtl.layer @A bind { + firrtl.layer @X bind {} + } + firrtl.layer @B bind {} + firrtl.module @Foo() {} +} + +// CHECK: firrtl.circuit "Foo" { +// CHECK: sv.verbatim "`ifndef layers_Foo_B\0A`define layers_Foo_B" {output_file = #hw.output_file<"layers_Foo_B.sv", excludeFromFileList>} +// CHECK: firrtl.module @Foo() { +// CHECK: } +// CHECK: sv.verbatim "`endif // layers_Foo_B" {output_file = #hw.output_file<"layers_Foo_B.sv", excludeFromFileList>} +// CHECK: } From 351b62fb8ad87d13abae44e5e9e1eecbcff914d1 Mon Sep 17 00:00:00 2001 From: Amelia Dobis Date: Wed, 14 Aug 2024 16:40:52 -0700 Subject: [PATCH 093/119] [FIRRTL] Add Inline Formal Test ops (#7374) The goal of this PR is to add a new set of inline formal test ops to FIRRTL: ```firrtl FIRRTL version 4.0.0 circuit Foo: public module Foo: input data : UInt<32> input c : UInt<1> output out : UInt<32> ;; Foo body module FooTest: ;; example test inst foo of Foo ;; symbolic input -- maps to input in btor2 input s_foo_c : UInt<1> input s_foo_data : UInt<32> ;; feed the symbolic inputs to the instance connect foo.c, s_foo_c connect foo.data, s_foo_data ;; example assertion that uses the symbolic inputs and outputs intrinsic(circt_verif_assert, intrinsic( circt_ltl_implication : UInt<1>, s_foo_c, eq(foo.out, s_foo_data)) ) formal testFormal of FooTest, bound = 20 ``` These new ops will then be lowered to the existing `verif.formal` op in a future PR. --- .../Dialect/FIRRTL/FIRRTLDeclarations.td | 42 ++++++++++++++++++- lib/Dialect/FIRRTL/Export/FIREmitter.cpp | 17 ++++++++ lib/Dialect/FIRRTL/FIRRTLOps.cpp | 23 ++++++++++ lib/Dialect/FIRRTL/Import/FIRParser.cpp | 40 ++++++++++++++++++ lib/Dialect/FIRRTL/Import/FIRTokenKinds.def | 2 + test/Dialect/FIRRTL/parse-basic.fir | 38 +++++++++++++++++ 6 files changed, 161 insertions(+), 1 deletion(-) diff --git a/include/circt/Dialect/FIRRTL/FIRRTLDeclarations.td b/include/circt/Dialect/FIRRTL/FIRRTLDeclarations.td index 1fd8847385ac..9b184a96868d 100644 --- a/include/circt/Dialect/FIRRTL/FIRRTLDeclarations.td +++ b/include/circt/Dialect/FIRRTL/FIRRTLDeclarations.td @@ -36,10 +36,50 @@ class ReferableDeclOp traits = []> : /// located in a hardware-creating context, such as the body of a module. class HardwareDeclOp traits = []> : ReferableDeclOp]> {} +def FormalOp : FIRRTLOp<"formal", [ + HasParent<"firrtl::CircuitOp">, + DeclareOpInterfaceMethods, + DeclareOpInterfaceMethods +]> { + let summary = "A formal test definition."; + let description = [{ + The `firrtl.formal` operation defines a formal verification problem in the same + context as the rest of the design. This problem is solved using bounded model + checking and should be given a bound k, which represents the number of cycles considered + during model checking. This definition marks a test harness defined as an internal module to + be verified using bounded model checking. + + Example: + ```mlir + // DUT + firrtl.module @Foo(in %bar: !firrtl.uint<8>, out %out: !firrtl.uint<8>) { ... } + + // Test harness + firrtl.module @FooTest(in %bar_s: !firrtl.uint<8>) { + %bar, %out = firrtl.instance foo @Foo(in bar: %bar_s: !firrtl.uint<8>, out out: !firrtl.uint<8>) + %c42_8 = firrtl.constant 42 : !firrtl.uint<8> + firrtl.connect %bar, %c42_8: !firrtl.uint<8>, !firrtl.uint<8> + %c69_8 = firrtl.constant 69 : !firrtl.uint<8> + %cond = firrtl.eq %c69_8, %out : (!firrtl.uint<8>, !firrtl.uint<8>) -> !firrtl.uint<1> + firrtl.assert %cond + } + + // Mark test harness as formal test + firrtl.formal @formal1 of @FooTest bound 20 + ``` + }]; + + let arguments = (ins SymbolNameAttr:$sym_name, FlatSymbolRefAttr:$moduleName, UI64Attr:$bound); + let results = (outs); + let assemblyFormat = [{ + $sym_name `of` $moduleName `bound` $bound attr-dict + }]; +} + def InstanceOp : HardwareDeclOp<"instance", [ DeclareOpInterfaceMethods, DeclareOpInterfaceMethods, diff --git a/lib/Dialect/FIRRTL/Export/FIREmitter.cpp b/lib/Dialect/FIRRTL/Export/FIREmitter.cpp index 76f7fc862288..203ed9df5d7d 100644 --- a/lib/Dialect/FIRRTL/Export/FIREmitter.cpp +++ b/lib/Dialect/FIRRTL/Export/FIREmitter.cpp @@ -60,6 +60,7 @@ struct Emitter { void emitModuleParameters(Operation *op, ArrayAttr parameters); void emitDeclaration(LayerOp op); void emitDeclaration(OptionOp op); + void emitDeclaration(FormalOp op); void emitEnabledLayers(ArrayRef layers); void emitParamAssign(ParamDeclAttr param, Operation *op, @@ -406,6 +407,7 @@ void Emitter::emitCircuit(CircuitOp op) { }) .Case([&](auto op) { emitDeclaration(op); }) .Case([&](auto op) { emitDeclaration(op); }) + .Case([&](auto op) { emitDeclaration(op); }) .Default([&](auto op) { emitOpError(op, "not supported for emission inside circuit"); }); @@ -623,6 +625,21 @@ void Emitter::emitDeclaration(OptionOp op) { ps << PP::newline << PP::newline; } +/// Emit a formal test definition. +void Emitter::emitDeclaration(FormalOp op) { + startStatement(); + ps << "formal " << PPExtString(op.getSymName()) << " of " + << PPExtString(op.getModuleName()) << ", bound = "; + ps.addAsString(op.getBound()); + + if (auto outputFile = op->getAttrOfType("output_file")) { + ps << ", "; + ps.writeQuotedEscaped(outputFile.getFilename().getValue()); + } + + emitLocationAndNewLine(op); +} + /// Check if an operation is inlined into the emission of their users. For /// example, subfields are always inlined. static bool isEmittedInline(Operation *op) { diff --git a/lib/Dialect/FIRRTL/FIRRTLOps.cpp b/lib/Dialect/FIRRTL/FIRRTLOps.cpp index 4e5c94de3146..8462bd9b102b 100644 --- a/lib/Dialect/FIRRTL/FIRRTLOps.cpp +++ b/lib/Dialect/FIRRTL/FIRRTLOps.cpp @@ -3362,6 +3362,25 @@ void RegResetOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) { return forceableAsmResultNames(*this, getName(), setNameFn); } +//===----------------------------------------------------------------------===// +// FormalOp +//===----------------------------------------------------------------------===// + +LogicalResult +FormalOp::verifySymbolUses(::mlir::SymbolTableCollection &symbolTable) { + // The referenced symbol is restricted to FModuleOps + auto referencedModule = symbolTable.lookupNearestSymbolFrom( + *this, getModuleNameAttr()); + if (!referencedModule) + return (*this)->emitOpError("invalid symbol reference"); + + return success(); +} + +//===----------------------------------------------------------------------===// +// WireOp +//===----------------------------------------------------------------------===// + void WireOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) { return forceableAsmResultNames(*this, getName(), setNameFn); } @@ -3384,6 +3403,10 @@ LogicalResult WireOp::verifySymbolUses(SymbolTableCollection &symbolTable) { symbolTable, Twine("'") + getOperationName() + "' op is"); } +//===----------------------------------------------------------------------===// +// ObjectOp +//===----------------------------------------------------------------------===// + void ObjectOp::build(OpBuilder &builder, OperationState &state, ClassLike klass, StringRef name) { build(builder, state, klass.getInstanceType(), diff --git a/lib/Dialect/FIRRTL/Import/FIRParser.cpp b/lib/Dialect/FIRRTL/Import/FIRParser.cpp index 40f6ddf2246d..56ec0e2f25ef 100644 --- a/lib/Dialect/FIRRTL/Import/FIRParser.cpp +++ b/lib/Dialect/FIRRTL/Import/FIRParser.cpp @@ -4634,6 +4634,7 @@ struct FIRCircuitParser : public FIRParser { ParseResult parseExtModule(CircuitOp circuit, unsigned indent); ParseResult parseIntModule(CircuitOp circuit, unsigned indent); ParseResult parseModule(CircuitOp circuit, bool isPublic, unsigned indent); + ParseResult parseFormal(CircuitOp circuit, unsigned indent); ParseResult parseLayerName(SymbolRefAttr &result); ParseResult parseOptionalEnabledLayers(ArrayAttr &result); @@ -4939,6 +4940,7 @@ ParseResult FIRCircuitParser::skipToModuleEnd(unsigned indent) { case FIRToken::kw_extclass: case FIRToken::kw_extmodule: case FIRToken::kw_intmodule: + case FIRToken::kw_formal: case FIRToken::kw_module: case FIRToken::kw_public: case FIRToken::kw_layer: @@ -5202,6 +5204,39 @@ ParseResult FIRCircuitParser::parseModule(CircuitOp circuit, bool isPublic, return success(); } +ParseResult FIRCircuitParser::parseFormal(CircuitOp circuit, unsigned indent) { + consumeToken(FIRToken::kw_formal); + StringRef id, moduleName, boundSpelling; + int64_t bound = -1; + LocWithInfo info(getToken().getLoc(), this); + + // Parse the formal operation + if (parseId(id, "expected a formal test name") || + parseToken(FIRToken::kw_of, + "expected keyword 'of' after formal test name") || + parseId(moduleName, "expected the name of a module") || + parseToken(FIRToken::comma, "expected ','") || + parseGetSpelling(boundSpelling) || + parseToken(FIRToken::identifier, + "expected parameter 'bound' after ','") || + parseToken(FIRToken::equal, "expected '=' after 'bound'") || + parseIntLit(bound, "expected integer in bound specification") || + info.parseOptionalInfo()) + return failure(); + + // Check that the parameter is valid + if (boundSpelling != "bound" || bound <= 0) + return emitError("Invalid parameter given to formal test: ") + << boundSpelling << " = " << bound, + failure(); + + // Build out the firrtl mlir op + auto builder = circuit.getBodyBuilder(); + builder.create(info.getLoc(), id, moduleName, bound); + + return success(); +} + ParseResult FIRCircuitParser::parseToplevelDefinition(CircuitOp circuit, unsigned indent) { switch (getToken().getKind()) { @@ -5216,6 +5251,10 @@ ParseResult FIRCircuitParser::parseToplevelDefinition(CircuitOp circuit, return parseExtClass(circuit, indent); case FIRToken::kw_extmodule: return parseExtModule(circuit, indent); + case FIRToken::kw_formal: + if (requireFeature({4, 0, 0}, "inline formal tests")) + return failure(); + return parseFormal(circuit, indent); case FIRToken::kw_intmodule: if (removedFeature({4, 0, 0}, "intrinsic modules")) return failure(); @@ -5563,6 +5602,7 @@ ParseResult FIRCircuitParser::parseCircuit( case FIRToken::kw_extmodule: case FIRToken::kw_intmodule: case FIRToken::kw_layer: + case FIRToken::kw_formal: case FIRToken::kw_module: case FIRToken::kw_option: case FIRToken::kw_public: diff --git a/lib/Dialect/FIRRTL/Import/FIRTokenKinds.def b/lib/Dialect/FIRRTL/Import/FIRTokenKinds.def index b3983a86068c..7d25c9191735 100644 --- a/lib/Dialect/FIRRTL/Import/FIRTokenKinds.def +++ b/lib/Dialect/FIRRTL/Import/FIRTokenKinds.def @@ -117,6 +117,7 @@ TOK_KEYWORD(extclass) TOK_KEYWORD(extmodule) TOK_KEYWORD(false) TOK_KEYWORD(flip) +TOK_KEYWORD(formal) TOK_KEYWORD(group) TOK_KEYWORD(infer) TOK_KEYWORD(input) @@ -151,6 +152,7 @@ TOK_KEYWORD(regreset) TOK_KEYWORD(reset) TOK_KEYWORD(skip) TOK_KEYWORD(smem) +TOK_KEYWORD(symbolic) TOK_KEYWORD(true) TOK_KEYWORD(type) TOK_KEYWORD(undefined) diff --git a/test/Dialect/FIRRTL/parse-basic.fir b/test/Dialect/FIRRTL/parse-basic.fir index 6bfd861f650f..89e60d66f03f 100644 --- a/test/Dialect/FIRRTL/parse-basic.fir +++ b/test/Dialect/FIRRTL/parse-basic.fir @@ -1921,3 +1921,41 @@ circuit GenericIntrinsics: ; CHECK-NEXT: %[[SZ:.+]] = firrtl.int.generic "circt_isX" ; CHECK-NEXT: "circt_verif_assert" %[[SZ]] intrinsic(circt_verif_assert, intrinsic(circt_isX: UInt<1>, data)) + +;// ----- +FIRRTL version 4.0.0 +; CHECK-LABEL: circuit "Foo" +circuit Foo: + + ; CHECK-LABEL: firrtl.module @Foo(in %data: !firrtl.uint<32>, in %c: !firrtl.uint<1>, out %out: !firrtl.uint<32>) + public module Foo: + input data : UInt<32> + input c : UInt<1> + output out : UInt<32> + + when c: + node add1 = add(data, UInt<32>(1)) + out <= add1 + else: + out <= data + + ; CHECK-LABEL: firrtl.module @FooTest(in %s_foo_c: !firrtl.uint<1>, in %s_foo_data: !firrtl.uint<32>) + public module FooTest: + input s_foo_c : UInt<1> + input s_foo_data : UInt<32> + + ; CHECK-NEXT: %foo_data, %foo_c, %foo_out = firrtl.instance foo interesting_name @Foo(in data: !firrtl.uint<32>, in c: !firrtl.uint<1>, out out: !firrtl.uint<32>) + inst foo of Foo + ; CHECK-NEXT: firrtl.matchingconnect %foo_c, %s_foo_c : !firrtl.uint<1> + connect foo.c, s_foo_c + ; CHECK-NEXT: firrtl.matchingconnect %foo_data, %s_foo_data : !firrtl.uint<32> + connect foo.data, s_foo_data + ; CHECK-NEXT: %0 = firrtl.eq %foo_out, %s_foo_data : (!firrtl.uint<32>, !firrtl.uint<32>) -> !firrtl.uint<1> + ; CHECK-NEXT: %1 = firrtl.int.generic "circt_ltl_implication" %s_foo_c, %0 : (!firrtl.uint<1>, !firrtl.uint<1>) -> !firrtl.uint<1> + ; CHECK-NEXT: firrtl.int.generic "circt_verif_assert" %1 : (!firrtl.uint<1>) -> () + intrinsic(circt_verif_assert, intrinsic(circt_ltl_implication : UInt<1>, s_foo_c, eq(foo.out, s_foo_data))) + + ; CHECK: firrtl.formal @testFormal of @FooTest bound 20 + formal testFormal of FooTest, bound = 20 + + From eacf55434052c8967f486d6ba7fa58c17627ae35 Mon Sep 17 00:00:00 2001 From: Morten Borup Petersen Date: Thu, 15 Aug 2024 15:27:46 +0200 Subject: [PATCH 094/119] [ESI] Disable zlib examples zlib examples contains a random `example` ctest, which isn't desirable to include in depending projects. --- lib/Dialect/ESI/runtime/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/Dialect/ESI/runtime/CMakeLists.txt b/lib/Dialect/ESI/runtime/CMakeLists.txt index fdda1847c2da..3f30cd7160e4 100644 --- a/lib/Dialect/ESI/runtime/CMakeLists.txt +++ b/lib/Dialect/ESI/runtime/CMakeLists.txt @@ -59,6 +59,7 @@ if(ZLIB_FOUND) set(ZLIB_LIBRARY ZLIB::ZLIB) else() message("-- zlib not found, pulling down zlib from git") + set(ZLIB_BUILD_EXAMPLES OFF) FetchContent_Declare( ZLIB GIT_REPOSITORY https://github.com/madler/zlib.git From 420103999d0bd104a71a2a920206bb4104f11fa1 Mon Sep 17 00:00:00 2001 From: fzi-hielscher <47524191+fzi-hielscher@users.noreply.github.com> Date: Thu, 15 Aug 2024 17:52:11 +0200 Subject: [PATCH 095/119] [Arc] Add InitialOp and lowering support for FirReg preset values. (#7480) --- include/circt/Conversion/Passes.td | 2 +- include/circt/Dialect/Arc/ArcOps.td | 78 ++++++++++------ include/circt/Dialect/Arc/ModelInfo.h | 6 +- .../arcilator/JIT/initial-shift-reg.mlir | 69 ++++++++++++++ integration_test/arcilator/JIT/initial.mlir | 25 +++++ lib/Conversion/ArcToLLVM/LowerArcToLLVM.cpp | 14 ++- .../ConvertToArcs/ConvertToArcs.cpp | 42 ++++++++- lib/Dialect/Arc/ArcOps.cpp | 25 +++++ lib/Dialect/Arc/CMakeLists.txt | 1 + lib/Dialect/Arc/ModelInfo.cpp | 6 +- .../Arc/Transforms/LegalizeStateUpdate.cpp | 2 + .../Arc/Transforms/LowerClocksToFuncs.cpp | 91 +++++++++++++++---- lib/Dialect/Arc/Transforms/LowerState.cpp | 74 ++++++++++++--- lib/Dialect/Arc/Transforms/StripSV.cpp | 12 ++- .../ConvertToArcs/convert-to-arcs.mlir | 46 ++++++++++ test/Dialect/Arc/basic-errors.mlir | 39 ++++++++ .../Arc/lower-clocks-to-funcs-errors.mlir | 30 +++++- test/Dialect/Arc/lower-clocks-to-funcs.mlir | 14 ++- test/Dialect/Arc/lower-state-errors.mlir | 24 +++++ test/Dialect/Arc/lower-state.mlir | 38 ++++++++ tools/arcilator/arcilator-header-cpp.py | 10 +- 21 files changed, 584 insertions(+), 64 deletions(-) create mode 100644 integration_test/arcilator/JIT/initial-shift-reg.mlir create mode 100644 integration_test/arcilator/JIT/initial.mlir create mode 100644 test/Dialect/Arc/lower-state-errors.mlir diff --git a/include/circt/Conversion/Passes.td b/include/circt/Conversion/Passes.td index dddc507bd80f..6eb67b865093 100644 --- a/include/circt/Conversion/Passes.td +++ b/include/circt/Conversion/Passes.td @@ -654,7 +654,7 @@ def ConvertToArcs : Pass<"convert-to-arcs", "mlir::ModuleOp"> { latency. }]; let constructor = "circt::createConvertToArcsPass()"; - let dependentDialects = ["circt::arc::ArcDialect"]; + let dependentDialects = ["circt::arc::ArcDialect", "circt::hw::HWDialect"]; let options = [ Option<"tapRegisters", "tap-registers", "bool", "true", "Make registers observable">, diff --git a/include/circt/Dialect/Arc/ArcOps.td b/include/circt/Dialect/Arc/ArcOps.td index a4ebdae14dde..02781979d217 100644 --- a/include/circt/Dialect/Arc/ArcOps.td +++ b/include/circt/Dialect/Arc/ArcOps.td @@ -134,6 +134,9 @@ def StateOp : ArcOp<"state", [ DeclareOpInterfaceMethods, AttrSizedOperandSegments, DeclareOpInterfaceMethods, + PredOpTrait<"types of initial arguments match result types", + CPred<[{getInitials().empty() || + llvm::equal(getInitials().getType(), getResults().getType())}]>> ]> { let summary = "State transfer arc"; @@ -143,13 +146,15 @@ def StateOp : ArcOp<"state", [ Optional:$enable, Optional:$reset, I32Attr:$latency, - Variadic:$inputs); + Variadic:$inputs, + Variadic:$initials); let results = (outs Variadic:$outputs); let assemblyFormat = [{ $arc `(` $inputs `)` (`clock` $clock^)? (`enable` $enable^)? - (`reset` $reset^)? `latency` $latency attr-dict - `:` functional-type($inputs, results) + (`reset` $reset^)? + ( `initial` ` ` `(` $initials^ `:` type($initials) `)`)? + `latency` $latency attr-dict `:` functional-type($inputs, results) }]; let hasFolder = 1; @@ -157,21 +162,24 @@ def StateOp : ArcOp<"state", [ let builders = [ OpBuilder<(ins "DefineOp":$arc, "mlir::Value":$clock, "mlir::Value":$enable, - "unsigned":$latency, CArg<"mlir::ValueRange", "{}">:$inputs), [{ + "unsigned":$latency, CArg<"mlir::ValueRange", "{}">:$inputs, + CArg<"mlir::ValueRange", "{}">:$initials), [{ build($_builder, $_state, mlir::SymbolRefAttr::get(arc), arc.getFunctionType().getResults(), clock, enable, latency, - inputs); + inputs, initials); }]>, OpBuilder<(ins "mlir::SymbolRefAttr":$arc, "mlir::TypeRange":$results, "mlir::Value":$clock, "mlir::Value":$enable, "unsigned":$latency, - CArg<"mlir::ValueRange", "{}">:$inputs + CArg<"mlir::ValueRange", "{}">:$inputs, + CArg<"mlir::ValueRange", "{}">:$initials ), [{ build($_builder, $_state, arc, results, clock, enable, Value(), latency, - inputs); + inputs, initials); }]>, OpBuilder<(ins "mlir::SymbolRefAttr":$arc, "mlir::TypeRange":$results, "mlir::Value":$clock, "mlir::Value":$enable, "mlir::Value":$reset, - "unsigned":$latency, CArg<"mlir::ValueRange", "{}">:$inputs + "unsigned":$latency, CArg<"mlir::ValueRange", "{}">:$inputs, + CArg<"mlir::ValueRange", "{}">:$initials ), [{ if (clock) $_state.addOperands(clock); @@ -180,6 +188,7 @@ def StateOp : ArcOp<"state", [ if (reset) $_state.addOperands(reset); $_state.addOperands(inputs); + $_state.addOperands(initials); $_state.addAttribute("arc", arc); $_state.addAttribute("latency", $_builder.getI32IntegerAttr(latency)); $_state.addAttribute(getOperandSegmentSizeAttr(), @@ -187,23 +196,26 @@ def StateOp : ArcOp<"state", [ clock ? 1 : 0, enable ? 1 : 0, reset ? 1 : 0, - static_cast(inputs.size())})); + static_cast(inputs.size()), + static_cast(initials.size())})); $_state.addTypes(results); }]>, OpBuilder<(ins "mlir::StringAttr":$arc, "mlir::TypeRange":$results, "mlir::Value":$clock, "mlir::Value":$enable, "unsigned":$latency, - CArg<"mlir::ValueRange", "{}">:$inputs + CArg<"mlir::ValueRange", "{}">:$inputs, + CArg<"mlir::ValueRange", "{}">:$initials ), [{ build($_builder, $_state, mlir::SymbolRefAttr::get(arc), results, clock, - enable, latency, inputs); + enable, latency, inputs, initials); }]>, OpBuilder<(ins "mlir::StringRef":$arc, "mlir::TypeRange":$results, "mlir::Value":$clock, "mlir::Value":$enable, "unsigned":$latency, - CArg<"mlir::ValueRange", "{}">:$inputs + CArg<"mlir::ValueRange", "{}">:$inputs, + CArg<"mlir::ValueRange", "{}">:$initials ), [{ build($_builder, $_state, mlir::StringAttr::get($_builder.getContext(), arc), - results, clock, enable, latency, inputs); + results, clock, enable, latency, inputs, initials); }]> ]; let skipDefaultBuilders = 1; @@ -429,26 +441,37 @@ def ClockDomainOp : ArcOp<"clock_domain", [ let hasCanonicalizeMethod = 1; } -def ClockTreeOp : ArcOp<"clock_tree", [NoTerminator, NoRegionArguments]> { +//===----------------------------------------------------------------------===// +// (Pseudo) Clock Trees +//===----------------------------------------------------------------------===// + +class ClockTreeLikeOp traits = []>: + ArcOp +])> { + let regions = (region SizedRegion<1>:$body); +} + +def ClockTreeOp : ClockTreeLikeOp<"clock_tree"> { let summary = "A clock tree"; let arguments = (ins I1:$clock); - let regions = (region SizedRegion<1>:$body); let assemblyFormat = [{ $clock attr-dict-with-keyword $body }]; - let extraClassDeclaration = [{ - mlir::Block &getBodyBlock() { return getBody().front(); } - }]; } -def PassThroughOp : ArcOp<"passthrough", [NoTerminator, NoRegionArguments]> { +def PassThroughOp : ClockTreeLikeOp<"passthrough"> { let summary = "Clock-less logic that is on the pass-through path"; - let regions = (region SizedRegion<1>:$body); let assemblyFormat = [{ attr-dict-with-keyword $body }]; - let extraClassDeclaration = [{ - mlir::Block &getBodyBlock() { return getBody().front(); } +} + +def InitialOp : ClockTreeLikeOp<"initial"> { + let summary = "Clock-less logic called at the start of simulation"; + let assemblyFormat = [{ + attr-dict-with-keyword $body }]; } @@ -651,19 +674,22 @@ def TapOp : ArcOp<"tap"> { let assemblyFormat = [{ $value attr-dict `:` type($value) }]; } -def ModelOp : ArcOp<"model", [RegionKindInterface, IsolatedFromAbove, - NoTerminator, Symbol]> { +def ModelOp : ArcOp<"model", [ + RegionKindInterface, IsolatedFromAbove, NoTerminator, Symbol, + DeclareOpInterfaceMethods +]> { let summary = "A model with stratified clocks"; let description = [{ A model with stratified clocks. The `io` optional attribute specifies the I/O of the module associated to this model. }]; let arguments = (ins SymbolNameAttr:$sym_name, - TypeAttrOf:$io); + TypeAttrOf:$io, + OptionalAttr:$initialFn); let regions = (region SizedRegion<1>:$body); let assemblyFormat = [{ - $sym_name `io` $io attr-dict-with-keyword $body + $sym_name `io` $io (`initializer` $initialFn^)? attr-dict-with-keyword $body }]; let extraClassDeclaration = [{ diff --git a/include/circt/Dialect/Arc/ModelInfo.h b/include/circt/Dialect/Arc/ModelInfo.h index 322f38b774c5..ca3918a772f0 100644 --- a/include/circt/Dialect/Arc/ModelInfo.h +++ b/include/circt/Dialect/Arc/ModelInfo.h @@ -36,11 +36,13 @@ struct ModelInfo { std::string name; size_t numStateBytes; llvm::SmallVector states; + mlir::FlatSymbolRefAttr initialFnSym; ModelInfo(std::string name, size_t numStateBytes, - llvm::SmallVector states) + llvm::SmallVector states, + mlir::FlatSymbolRefAttr initialFnSym) : name(std::move(name)), numStateBytes(numStateBytes), - states(std::move(states)) {} + states(std::move(states)), initialFnSym(initialFnSym) {} }; /// Collects information about states within the provided Arc model storage diff --git a/integration_test/arcilator/JIT/initial-shift-reg.mlir b/integration_test/arcilator/JIT/initial-shift-reg.mlir new file mode 100644 index 000000000000..3724962d8a7f --- /dev/null +++ b/integration_test/arcilator/JIT/initial-shift-reg.mlir @@ -0,0 +1,69 @@ +// RUN: arcilator %s --run --jit-entry=main | FileCheck %s +// REQUIRES: arcilator-jit + +// CHECK-LABEL: output = ca +// CHECK-NEXT: output = ca +// CHECK-NEXT: output = 0 +// CHECK-NEXT: output = fe +// CHECK-NEXT: output = ff + +module { + + hw.module @shiftreg(in %clock : i1, in %reset : i1, in %en : i1, in %din : i8, out dout : i8) { + %seq_clk = seq.to_clock %clock + %srA = seq.firreg %0 clock %seq_clk preset 0xFE : i8 + %srB = seq.firreg %1 clock %seq_clk : i8 + %srC = seq.firreg %2 clock %seq_clk preset 0xCA : i8 + %0 = comb.mux bin %en, %din, %srA : i8 + %1 = comb.mux bin %en, %srA, %srB : i8 + %2 = comb.mux bin %en, %srB, %srC : i8 + hw.output %srC : i8 + } + + func.func @main() { + %ff = arith.constant 0xFF : i8 + %false = arith.constant 0 : i1 + %true = arith.constant 1 : i1 + + arc.sim.instantiate @shiftreg as %model { + arc.sim.set_input %model, "en" = %false : i1, !arc.sim.instance<@shiftreg> + arc.sim.set_input %model, "reset" = %false : i1, !arc.sim.instance<@shiftreg> + arc.sim.set_input %model, "din" = %ff : i8, !arc.sim.instance<@shiftreg> + + %res0 = arc.sim.get_port %model, "dout" : i8, !arc.sim.instance<@shiftreg> + arc.sim.emit "output", %res0 : i8 + + arc.sim.set_input %model, "clock" = %true : i1, !arc.sim.instance<@shiftreg> + arc.sim.step %model : !arc.sim.instance<@shiftreg> + arc.sim.set_input %model, "clock" = %false : i1, !arc.sim.instance<@shiftreg> + arc.sim.step %model : !arc.sim.instance<@shiftreg> + + %res1 = arc.sim.get_port %model, "dout" : i8, !arc.sim.instance<@shiftreg> + arc.sim.emit "output", %res1 : i8 + + arc.sim.set_input %model, "en" = %true : i1, !arc.sim.instance<@shiftreg> + + arc.sim.set_input %model, "clock" = %true : i1, !arc.sim.instance<@shiftreg> + arc.sim.step %model : !arc.sim.instance<@shiftreg> + arc.sim.set_input %model, "clock" = %false : i1, !arc.sim.instance<@shiftreg> + arc.sim.step %model : !arc.sim.instance<@shiftreg> + %res2 = arc.sim.get_port %model, "dout" : i8, !arc.sim.instance<@shiftreg> + arc.sim.emit "output", %res2 : i8 + + arc.sim.set_input %model, "clock" = %true : i1, !arc.sim.instance<@shiftreg> + arc.sim.step %model : !arc.sim.instance<@shiftreg> + arc.sim.set_input %model, "clock" = %false : i1, !arc.sim.instance<@shiftreg> + arc.sim.step %model : !arc.sim.instance<@shiftreg> + %res3 = arc.sim.get_port %model, "dout" : i8, !arc.sim.instance<@shiftreg> + arc.sim.emit "output", %res3 : i8 + + arc.sim.set_input %model, "clock" = %true : i1, !arc.sim.instance<@shiftreg> + arc.sim.step %model : !arc.sim.instance<@shiftreg> + arc.sim.set_input %model, "clock" = %false : i1, !arc.sim.instance<@shiftreg> + arc.sim.step %model : !arc.sim.instance<@shiftreg> + %res4 = arc.sim.get_port %model, "dout" : i8, !arc.sim.instance<@shiftreg> + arc.sim.emit "output", %res4 : i8 + } + return + } +} diff --git a/integration_test/arcilator/JIT/initial.mlir b/integration_test/arcilator/JIT/initial.mlir new file mode 100644 index 000000000000..7cde23b21072 --- /dev/null +++ b/integration_test/arcilator/JIT/initial.mlir @@ -0,0 +1,25 @@ +// RUN: arcilator %s --run --jit-entry=main 2>&1 >/dev/null | FileCheck %s +// REQUIRES: arcilator-jit + +// CHECK: - Init - + +module { + llvm.func @_arc_env_get_print_stream(i32) -> !llvm.ptr + llvm.func @_arc_libc_fputs(!llvm.ptr, !llvm.ptr) -> i32 + llvm.mlir.global internal constant @global_init_str(" - Init -\0A\00") {addr_space = 0 : i32} + + arc.model @initmodel io !hw.modty<> { + ^bb0(%arg0: !arc.storage): + arc.initial { + %cst0 = llvm.mlir.constant(0 : i32) : i32 + %stderr = llvm.call @_arc_env_get_print_stream(%cst0) : (i32) -> !llvm.ptr + %str = llvm.mlir.addressof @global_init_str : !llvm.ptr + %0 = llvm.call @_arc_libc_fputs(%str, %stderr) : (!llvm.ptr, !llvm.ptr) -> i32 + } + } + func.func @main() { + arc.sim.instantiate @initmodel as %arg0 { + } + return + } +} diff --git a/lib/Conversion/ArcToLLVM/LowerArcToLLVM.cpp b/lib/Conversion/ArcToLLVM/LowerArcToLLVM.cpp index 086038148c04..d6e4f5bcc130 100644 --- a/lib/Conversion/ArcToLLVM/LowerArcToLLVM.cpp +++ b/lib/Conversion/ArcToLLVM/LowerArcToLLVM.cpp @@ -318,6 +318,7 @@ namespace { struct ModelInfoMap { size_t numStateBytes; llvm::DenseMap states; + mlir::FlatSymbolRefAttr initialFnSymbol; }; template @@ -378,6 +379,16 @@ struct SimInstantiateOpLowering Value zero = rewriter.create(loc, rewriter.getI8Type(), 0); rewriter.create(loc, allocated, zero, numStateBytes, false); + + // Call the model's 'initial' function if present. + if (model.initialFnSymbol) { + auto initialFnType = LLVM::LLVMFunctionType::get( + LLVM::LLVMVoidType::get(op.getContext()), + {LLVM::LLVMPointerType::get(op.getContext())}); + rewriter.create(loc, initialFnType, model.initialFnSymbol, + ValueRange{allocated}); + } + rewriter.inlineBlockBefore(&adaptor.getBody().getBlocks().front(), op, {allocated}); rewriter.create(loc, freeFunc, ValueRange{allocated}); @@ -646,7 +657,8 @@ void LowerArcToLLVMPass::runOnOperation() { for (StateInfo &stateInfo : modelInfo.states) states.insert({stateInfo.name, stateInfo}); modelMap.insert({modelInfo.name, - ModelInfoMap{modelInfo.numStateBytes, std::move(states)}}); + ModelInfoMap{modelInfo.numStateBytes, std::move(states), + modelInfo.initialFnSym}}); } patterns.add(callOp.getOperation()); Value clock = stateOp ? stateOp.getClock() : Value{}; Value reset; + SmallVector initialValues; SmallVector absorbedRegs; SmallVector absorbedNames(callOp->getNumResults(), {}); if (auto names = callOp->getAttrOfType("names")) @@ -307,6 +308,8 @@ LogicalResult Converter::absorbRegs(HWModuleOp module) { } } + initialValues.push_back(regOp.getPowerOnValue()); + absorbedRegs.push_back(regOp); // If we absorb a register into the arc, the arc effectively produces that // register's value. So if the register had a name, ensure that we assign @@ -345,6 +348,28 @@ LogicalResult Converter::absorbRegs(HWModuleOp module) { "had a reset."); arc.getResetMutable().assign(reset); } + + bool onlyDefaultInitializers = + llvm::all_of(initialValues, [](auto val) -> bool { return !val; }); + + if (!onlyDefaultInitializers) { + if (!arc.getInitials().empty()) { + return arc.emitError( + "StateOp tried to infer initial values from CompReg, but already " + "had an initial value."); + } + // Create 0 constants for default initialization + for (unsigned i = 0; i < initialValues.size(); ++i) { + if (!initialValues[i]) { + OpBuilder zeroBuilder(arc); + initialValues[i] = zeroBuilder.createOrFold( + arc.getLoc(), + zeroBuilder.getIntegerAttr(arc.getResult(i).getType(), 0)); + } + } + arc.getInitialsMutable().assign(initialValues); + } + if (tapRegisters && llvm::any_of(absorbedNames, [](auto name) { return !cast(name).getValue().empty(); })) @@ -385,6 +410,7 @@ LogicalResult Converter::absorbRegs(HWModuleOp module) { SmallVector outputs; SmallVector names; SmallVector types; + SmallVector initialValues; SmallDenseMap mapping; SmallVector regToOutputMapping; for (auto regOp : regOps) { @@ -395,6 +421,7 @@ LogicalResult Converter::absorbRegs(HWModuleOp module) { types.push_back(regOp.getType()); outputs.push_back(block->addArgument(regOp.getType(), regOp.getLoc())); names.push_back(regOp->getAttrOfType("name")); + initialValues.push_back(regOp.getPowerOnValue()); } regToOutputMapping.push_back(it->second); } @@ -411,9 +438,22 @@ LogicalResult Converter::absorbRegs(HWModuleOp module) { defOp.getBody().push_back(block.release()); builder.setInsertionPoint(module.getBodyBlock()->getTerminator()); + + bool onlyDefaultInitializers = + llvm::all_of(initialValues, [](auto val) -> bool { return !val; }); + + if (onlyDefaultInitializers) + initialValues.clear(); + else + for (unsigned i = 0; i < initialValues.size(); ++i) { + if (!initialValues[i]) + initialValues[i] = builder.createOrFold( + loc, builder.getIntegerAttr(types[i], 0)); + } + auto arcOp = builder.create(loc, defOp, std::get<0>(clockAndResetAndOp), - /*enable=*/Value{}, 1, inputs); + /*enable=*/Value{}, 1, inputs, initialValues); auto reset = std::get<1>(clockAndResetAndOp); if (reset) arcOp.getResetMutable().assign(reset); diff --git a/lib/Dialect/Arc/ArcOps.cpp b/lib/Dialect/Arc/ArcOps.cpp index 87070ed8c398..76e457b8d77d 100644 --- a/lib/Dialect/Arc/ArcOps.cpp +++ b/lib/Dialect/Arc/ArcOps.cpp @@ -8,6 +8,7 @@ #include "circt/Dialect/Arc/ArcOps.h" #include "circt/Dialect/HW/HWOpInterfaces.h" +#include "mlir/Dialect/Func/IR/FuncOps.h" #include "mlir/IR/Builders.h" #include "mlir/IR/OpImplementation.h" #include "mlir/IR/PatternMatch.h" @@ -307,6 +308,30 @@ LogicalResult ModelOp::verify() { return success(); } +LogicalResult ModelOp::verifySymbolUses(SymbolTableCollection &symbolTable) { + if (!getInitialFn().has_value()) + return success(); + + auto referencedOp = + symbolTable.lookupNearestSymbolFrom(*this, getInitialFnAttr()); + if (!referencedOp) + return emitError("Cannot find declaration of initializer function '") + << *getInitialFn() << "'."; + auto funcOp = dyn_cast(referencedOp); + if (!funcOp) { + auto diag = emitError("Referenced initializer must be a 'func.func' op."); + diag.attachNote(referencedOp->getLoc()) << "Initializer declared here:"; + return diag; + } + if (!llvm::equal(funcOp.getArgumentTypes(), getBody().getArgumentTypes())) { + auto diag = emitError("Arguments of initializer function must match " + "arguments of model body."); + diag.attachNote(referencedOp->getLoc()) << "Initializer declared here:"; + return diag; + } + return success(); +} + //===----------------------------------------------------------------------===// // LutOp //===----------------------------------------------------------------------===// diff --git a/lib/Dialect/Arc/CMakeLists.txt b/lib/Dialect/Arc/CMakeLists.txt index e6ed7c9c4533..c1703987ed40 100644 --- a/lib/Dialect/Arc/CMakeLists.txt +++ b/lib/Dialect/Arc/CMakeLists.txt @@ -32,6 +32,7 @@ add_circt_dialect_library(CIRCTArc CIRCTSeq MLIRIR MLIRInferTypeOpInterface + MLIRFuncDialect MLIRSideEffectInterfaces MLIRFuncDialect ) diff --git a/lib/Dialect/Arc/ModelInfo.cpp b/lib/Dialect/Arc/ModelInfo.cpp index a16dc0b68e24..91e0449df396 100644 --- a/lib/Dialect/Arc/ModelInfo.cpp +++ b/lib/Dialect/Arc/ModelInfo.cpp @@ -105,6 +105,7 @@ LogicalResult circt::arc::collectStates(Value storage, unsigned offset, LogicalResult circt::arc::collectModels(mlir::ModuleOp module, SmallVector &models) { + for (auto modelOp : module.getOps()) { auto storageArg = modelOp.getBody().getArgument(0); auto storageType = cast(storageArg.getType()); @@ -115,7 +116,7 @@ LogicalResult circt::arc::collectModels(mlir::ModuleOp module, llvm::sort(states, [](auto &a, auto &b) { return a.offset < b.offset; }); models.emplace_back(std::string(modelOp.getName()), storageType.getSize(), - std::move(states)); + std::move(states), modelOp.getInitialFnAttr()); } return success(); @@ -130,6 +131,9 @@ void circt::arc::serializeModelInfoToJson(llvm::raw_ostream &outputStream, json.object([&] { json.attribute("name", model.name); json.attribute("numStateBytes", model.numStateBytes); + json.attribute("initialFnSym", !model.initialFnSym + ? "" + : model.initialFnSym.getValue()); json.attributeArray("states", [&] { for (const auto &state : model.states) { json.object([&] { diff --git a/lib/Dialect/Arc/Transforms/LegalizeStateUpdate.cpp b/lib/Dialect/Arc/Transforms/LegalizeStateUpdate.cpp index 486235d0bf5b..b68abef62513 100644 --- a/lib/Dialect/Arc/Transforms/LegalizeStateUpdate.cpp +++ b/lib/Dialect/Arc/Transforms/LegalizeStateUpdate.cpp @@ -30,6 +30,8 @@ using namespace arc; /// Check if an operation partakes in state accesses. static bool isOpInteresting(Operation *op) { + if (isa(op)) + return false; if (isa(op)) return true; if (op->getNumRegions() > 0) diff --git a/lib/Dialect/Arc/Transforms/LowerClocksToFuncs.cpp b/lib/Dialect/Arc/Transforms/LowerClocksToFuncs.cpp index 0ef473e428cf..c76467e80f38 100644 --- a/lib/Dialect/Arc/Transforms/LowerClocksToFuncs.cpp +++ b/lib/Dialect/Arc/Transforms/LowerClocksToFuncs.cpp @@ -11,6 +11,7 @@ #include "mlir/Dialect/Func/IR/FuncOps.h" #include "mlir/Dialect/SCF/IR/SCF.h" #include "mlir/Pass/Pass.h" +#include "llvm/ADT/TypeSwitch.h" #include "llvm/Support/Debug.h" #define DEBUG_TYPE "arc-lower-clocks-to-funcs" @@ -50,6 +51,9 @@ struct LowerClocksToFuncsPass Statistic numOpsCopied{this, "ops-copied", "Ops copied into clock trees"}; Statistic numOpsMoved{this, "ops-moved", "Ops moved into clock trees"}; + +private: + bool hasPassthroughOp; }; } // namespace @@ -65,11 +69,38 @@ LogicalResult LowerClocksToFuncsPass::lowerModel(ModelOp modelOp) { << "`\n"); // Find the clocks to extract. + SmallVector initialOps; + SmallVector passthroughOps; SmallVector clocks; modelOp.walk([&](Operation *op) { - if (isa(op)) - clocks.push_back(op); + TypeSwitch(op) + .Case([&](auto) { clocks.push_back(op); }) + .Case([&](auto initOp) { + initialOps.push_back(initOp); + clocks.push_back(initOp); + }) + .Case([&](auto ptOp) { + passthroughOps.push_back(ptOp); + clocks.push_back(ptOp); + }); }); + hasPassthroughOp = !passthroughOps.empty(); + + // Sanity check + if (passthroughOps.size() > 1) { + auto diag = modelOp.emitOpError() + << "containing multiple PassThroughOps cannot be lowered."; + for (auto ptOp : passthroughOps) + diag.attachNote(ptOp.getLoc()) << "Conflicting PassThroughOp:"; + } + if (initialOps.size() > 1) { + auto diag = modelOp.emitOpError() + << "containing multiple InitialOps is currently unsupported."; + for (auto initOp : initialOps) + diag.attachNote(initOp.getLoc()) << "Conflicting InitialOp:"; + } + if (passthroughOps.size() > 1 || initialOps.size() > 1) + return failure(); // Perform the actual extraction. OpBuilder funcBuilder(modelOp); @@ -84,7 +115,7 @@ LogicalResult LowerClocksToFuncsPass::lowerClock(Operation *clockOp, Value modelStorageArg, OpBuilder &funcBuilder) { LLVM_DEBUG(llvm::dbgs() << "- Lowering clock " << clockOp->getName() << "\n"); - assert((isa(clockOp))); + assert((isa(clockOp))); // Add a `StorageType` block argument to the clock's body block which we are // going to use to pass the storage pointer to the clock once it has been @@ -103,8 +134,16 @@ LogicalResult LowerClocksToFuncsPass::lowerClock(Operation *clockOp, // Pick a name for the clock function. SmallString<32> funcName; - funcName.append(clockOp->getParentOfType().getName()); - funcName.append(isa(clockOp) ? "_passthrough" : "_clock"); + auto modelOp = clockOp->getParentOfType(); + funcName.append(modelOp.getName()); + + if (isa(clockOp)) + funcName.append("_passthrough"); + else if (isa(clockOp)) + funcName.append("_initial"); + else + funcName.append("_clock"); + auto funcOp = funcBuilder.create( clockOp->getLoc(), funcName, builder.getFunctionType({modelStorageArg.getType()}, {})); @@ -114,21 +153,41 @@ LogicalResult LowerClocksToFuncsPass::lowerClock(Operation *clockOp, // Create a call to the function within the model. builder.setInsertionPoint(clockOp); - if (auto treeOp = dyn_cast(clockOp)) { - auto ifOp = - builder.create(clockOp->getLoc(), treeOp.getClock(), false); - auto builder = ifOp.getThenBodyBuilder(); - builder.create(clockOp->getLoc(), funcOp, - ValueRange{modelStorageArg}); - } else { - builder.create(clockOp->getLoc(), funcOp, - ValueRange{modelStorageArg}); - } + TypeSwitch(clockOp) + .Case([&](auto treeOp) { + auto ifOp = builder.create(clockOp->getLoc(), + treeOp.getClock(), false); + auto builder = ifOp.getThenBodyBuilder(); + builder.template create(clockOp->getLoc(), funcOp, + ValueRange{modelStorageArg}); + }) + .Case([&](auto) { + builder.template create(clockOp->getLoc(), funcOp, + ValueRange{modelStorageArg}); + }) + .Case([&](auto) { + if (modelOp.getInitialFn().has_value()) + modelOp.emitWarning() << "Existing model initializer '" + << modelOp.getInitialFnAttr().getValue() + << "' will be overridden."; + modelOp.setInitialFnAttr( + FlatSymbolRefAttr::get(funcOp.getSymNameAttr())); + }); // Move the clock's body block to the function and remove the old clock op. funcOp.getBody().takeBody(clockRegion); - clockOp->erase(); + if (isa(clockOp) && hasPassthroughOp) { + // Call PassThroughOp after init + builder.setInsertionPoint(funcOp.getBlocks().front().getTerminator()); + funcName.clear(); + funcName.append(modelOp.getName()); + funcName.append("_passthrough"); + builder.create(clockOp->getLoc(), funcName, TypeRange{}, + ValueRange{funcOp.getBody().getArgument(0)}); + } + + clockOp->erase(); return success(); } diff --git a/lib/Dialect/Arc/Transforms/LowerState.cpp b/lib/Dialect/Arc/Transforms/LowerState.cpp index 7f5025b8e457..d659e8870397 100644 --- a/lib/Dialect/Arc/Transforms/LowerState.cpp +++ b/lib/Dialect/Arc/Transforms/LowerState.cpp @@ -8,6 +8,7 @@ #include "circt/Dialect/Arc/ArcOps.h" #include "circt/Dialect/Arc/ArcPasses.h" +#include "circt/Dialect/Comb/CombDialect.h" #include "circt/Dialect/Comb/CombOps.h" #include "circt/Dialect/HW/HWOps.h" #include "circt/Dialect/Seq/SeqOps.h" @@ -63,7 +64,7 @@ struct Statistics { struct ClockLowering { /// The root clock this lowering is for. Value clock; - /// A `ClockTreeOp` or `PassThroughOp`. + /// A `ClockTreeOp` or `PassThroughOp` or `InitialOp`. Operation *treeOp; /// Pass statistics. Statistics &stats; @@ -76,15 +77,21 @@ struct ClockLowering { /// A cache of OR gates created for aggregating enable conditions. DenseMap, Value> orCache; + // Prevent accidental construction and copying + ClockLowering() = delete; + ClockLowering(const ClockLowering &other) = delete; + ClockLowering(Value clock, Operation *treeOp, Statistics &stats) : clock(clock), treeOp(treeOp), stats(stats), builder(treeOp) { - assert((isa(treeOp))); + assert((isa(treeOp))); builder.setInsertionPointToStart(&treeOp->getRegion(0).front()); } Value materializeValue(Value value); Value getOrCreateAnd(Value lhs, Value rhs, Location loc); Value getOrCreateOr(Value lhs, Value rhs, Location loc); + + bool isInitialTree() const { return isa(treeOp); } }; struct GatedClockLowering { @@ -102,6 +109,7 @@ struct ModuleLowering { MLIRContext *context; DenseMap> clockLowerings; DenseMap gatedClockLowerings; + std::unique_ptr initialLowering; Value storageArg; OpBuilder clockBuilder; OpBuilder stateBuilder; @@ -112,6 +120,7 @@ struct ModuleLowering { GatedClockLowering getOrCreateClockLowering(Value clock); ClockLowering &getOrCreatePassThrough(); + ClockLowering &getInitial(); Value replaceValueWithStateRead(Value value, Value state); void addStorageArg(); @@ -121,7 +130,8 @@ struct ModuleLowering { template LogicalResult lowerStateLike(Operation *op, Value clock, Value enable, Value reset, ArrayRef inputs, - FlatSymbolRefAttr callee); + FlatSymbolRefAttr callee, + ArrayRef initialValues = {}); LogicalResult lowerState(StateOp stateOp); LogicalResult lowerState(sim::DPICallOp dpiCallOp); LogicalResult lowerState(MemoryOp memOp); @@ -159,6 +169,17 @@ static bool shouldMaterialize(Value value) { return shouldMaterialize(op); } +static bool canBeMaterializedInInitializer(Operation *op) { + if (!op) + return false; + if (op->hasTrait()) + return true; + if (isa(op->getDialect())) + return true; + // TODO: There are some other ops we probably want to allow + return false; +} + /// Materialize a value within this clock tree. This clones or moves all /// operations required to produce this value inside the clock tree. Value ClockLowering::materializeValue(Value value) { @@ -206,6 +227,10 @@ Value ClockLowering::materializeValue(Value value) { while (!worklist.empty()) { auto &workItem = worklist.back(); + if (isInitialTree() && !canBeMaterializedInInitializer(workItem.op)) { + workItem.op->emitError("Value cannot be used in initializer."); + return {}; + } if (!workItem.operands.empty()) { auto operand = workItem.operands.pop_back_val(); if (materializedValues.contains(operand) || !shouldMaterialize(operand)) @@ -317,6 +342,11 @@ ClockLowering &ModuleLowering::getOrCreatePassThrough() { return *slot; } +ClockLowering &ModuleLowering::getInitial() { + assert(!!initialLowering && "Initial tree op should have been constructed"); + return *initialLowering; +} + /// Replace all uses of a value with a `StateReadOp` on a state. Value ModuleLowering::replaceValueWithStateRead(Value value, Value state) { OpBuilder builder(state.getContext()); @@ -415,7 +445,8 @@ LogicalResult ModuleLowering::lowerStates() { template LogicalResult ModuleLowering::lowerStateLike( Operation *stateOp, Value stateClock, Value stateEnable, Value stateReset, - ArrayRef stateInputs, FlatSymbolRefAttr callee) { + ArrayRef stateInputs, FlatSymbolRefAttr callee, + ArrayRef initialValues) { // Grab all operands from the state op at the callsite and make it drop all // its references. This allows `materializeValue` to move an operation if this // state was the last user. @@ -470,10 +501,23 @@ LogicalResult ModuleLowering::lowerStateLike( thenBuilder.create(stateOp->getLoc(), alloc, constZero, Value()); } - nonResetBuilder = ifOp.getElseBodyBuilder(); } + if (!initialValues.empty()) { + assert(initialValues.size() == allocatedStates.size() && + "Unexpected number of initializers"); + auto &initialTree = getInitial(); + for (auto [alloc, init] : llvm::zip(allocatedStates, initialValues)) { + // TODO: Can we get away without materialization? + auto matierializedInit = initialTree.materializeValue(init); + if (!matierializedInit) + return failure(); + initialTree.builder.create(stateOp->getLoc(), alloc, + matierializedInit, Value()); + } + } + stateOp->dropAllReferences(); auto newStateOp = nonResetBuilder.create( @@ -501,10 +545,11 @@ LogicalResult ModuleLowering::lowerState(StateOp stateOp) { return stateOp.emitError("state with latency > 1 not supported"); auto stateInputs = SmallVector(stateOp.getInputs()); + auto stateInitializers = SmallVector(stateOp.getInitials()); - return lowerStateLike(stateOp, stateOp.getClock(), - stateOp.getEnable(), stateOp.getReset(), - stateInputs, stateOp.getArcAttr()); + return lowerStateLike( + stateOp, stateOp.getClock(), stateOp.getEnable(), stateOp.getReset(), + stateInputs, stateOp.getArcAttr(), stateInitializers); } LogicalResult ModuleLowering::lowerState(sim::DPICallOp callOp) { @@ -829,6 +874,13 @@ LogicalResult LowerStatePass::runOnModule(HWModuleOp moduleOp, Operation *clockSentinel = lowering.stateBuilder.create(moduleOp.getLoc()); + // Create the 'initial' pseudo clock tree. + auto initialTreeOp = + lowering.stateBuilder.create(moduleOp.getLoc()); + initialTreeOp.getBody().emplaceBlock(); + lowering.initialLowering = + std::make_unique(Value{}, initialTreeOp, stats); + lowering.stateBuilder.setInsertionPoint(stateSentinel); lowering.clockBuilder.setInsertionPoint(clockSentinel); @@ -856,9 +908,9 @@ LogicalResult LowerStatePass::runOnModule(HWModuleOp moduleOp, moduleOp.getBodyBlock()->eraseArguments( [&](auto arg) { return arg != lowering.storageArg; }); ImplicitLocOpBuilder builder(moduleOp.getLoc(), moduleOp); - auto modelOp = - builder.create(moduleOp.getLoc(), moduleOp.getModuleNameAttr(), - TypeAttr::get(moduleOp.getModuleType())); + auto modelOp = builder.create( + moduleOp.getLoc(), moduleOp.getModuleNameAttr(), + TypeAttr::get(moduleOp.getModuleType()), mlir::FlatSymbolRefAttr()); modelOp.getBody().takeBody(moduleOp.getBody()); moduleOp->erase(); sortTopologically(&modelOp.getBodyBlock()); diff --git a/lib/Dialect/Arc/Transforms/StripSV.cpp b/lib/Dialect/Arc/Transforms/StripSV.cpp index f18602261b40..dbe400e81bfb 100644 --- a/lib/Dialect/Arc/Transforms/StripSV.cpp +++ b/lib/Dialect/Arc/Transforms/StripSV.cpp @@ -151,9 +151,19 @@ void StripSVPass::runOnOperation() { else next = reg.getNext(); + Value presetValue; + // Materialize initial value, assume zero initialization as default. + if (reg.getPreset() && !reg.getPreset()->isZero()) { + assert(hw::type_isa(reg.getType()) && + "cannot lower non integer preset"); + presetValue = builder.createOrFold( + reg.getLoc(), IntegerAttr::get(reg.getType(), *reg.getPreset())); + } + Value compReg = builder.create( reg.getLoc(), next.getType(), next, reg.getClk(), reg.getNameAttr(), - Value{}, Value{}, Value{}, reg.getInnerSymAttr()); + Value{}, Value{}, /*powerOnValue*/ presetValue, + reg.getInnerSymAttr()); reg.replaceAllUsesWith(compReg); opsToDelete.push_back(reg); continue; diff --git a/test/Conversion/ConvertToArcs/convert-to-arcs.mlir b/test/Conversion/ConvertToArcs/convert-to-arcs.mlir index 21fac9ecc0b5..047a82f59eb3 100644 --- a/test/Conversion/ConvertToArcs/convert-to-arcs.mlir +++ b/test/Conversion/ConvertToArcs/convert-to-arcs.mlir @@ -110,6 +110,36 @@ hw.module @Reshuffling(in %clockA: !seq.clock, in %clockB: !seq.clock, out z0: i hw.module.extern private @Reshuffling2(out z0: i4, out z1: i4, out z2: i4, out z3: i4) +// CHECK-LABEL: arc.define @ReshufflingInit_arc(%arg0: i4, %arg1: i4) +// CHECK-NEXT: arc.output %arg0, %arg1 +// CHECK-NEXT: } + +// CHECK-LABEL: arc.define @ReshufflingInit_arc_0(%arg0: i4, %arg1: i4) +// CHECK-NEXT: arc.output %arg0, %arg1 +// CHECK-NEXT: } + +// CHECK-LABEL: hw.module @ReshufflingInit +hw.module @ReshufflingInit(in %clockA: !seq.clock, in %clockB: !seq.clock, out z0: i4, out z1: i4, out z2: i4, out z3: i4) { + // CHECK-NEXT: [[C1:%.+]] = hw.constant 1 : i4 + // CHECK-NEXT: [[C2:%.+]] = hw.constant 2 : i4 + // CHECK-NEXT: [[C3:%.+]] = hw.constant 3 : i4 + // CHECK-NEXT: hw.instance "x" @Reshuffling2() + // CHECK-NEXT: [[C0:%.+]] = hw.constant 0 : i4 + // CHECK-NEXT: arc.state @ReshufflingInit_arc(%x.z0, %x.z1) clock %clockA initial ([[C0]], [[C1]] : i4, i4) latency 1 + // CHECK-NEXT: arc.state @ReshufflingInit_arc_0(%x.z2, %x.z3) clock %clockB initial ([[C2]], [[C3]] : i4, i4) latency 1 + // CHECK-NEXT: hw.output + %cst1 = hw.constant 1 : i4 + %cst2 = hw.constant 2 : i4 + %cst3 = hw.constant 3 : i4 + %x.z0, %x.z1, %x.z2, %x.z3 = hw.instance "x" @Reshuffling2() -> (z0: i4, z1: i4, z2: i4, z3: i4) + %4 = seq.compreg %x.z0, %clockA : i4 + %5 = seq.compreg %x.z1, %clockA powerOn %cst1 : i4 + %6 = seq.compreg %x.z2, %clockB powerOn %cst2 : i4 + %7 = seq.compreg %x.z3, %clockB powerOn %cst3 : i4 + hw.output %4, %5, %6, %7 : i4, i4, i4, i4 +} +// CHECK-NEXT: } + // CHECK-LABEL: arc.define @FactorOutCommonOps_arc( // CHECK-NEXT: comb.xor @@ -196,6 +226,22 @@ hw.module @Trivial(in %clock: !seq.clock, in %i0: i4, in %reset: i1, out out: i4 } // CHECK-NEXT: } +// CHECK: arc.define @[[TRIVIALINIT_ARC:.+]]([[ARG0:%.+]]: i4) +// CHECK-NEXT: arc.output [[ARG0]] +// CHECK-NEXT: } + +// CHECK-LABEL: hw.module @TrivialWithInit( +hw.module @TrivialWithInit(in %clock: !seq.clock, in %i0: i4, in %reset: i1, out out: i4) { + // CHECK: [[CST2:%.+]] = hw.constant 2 : i4 + // CHECK: [[RES0:%.+]] = arc.state @[[TRIVIALINIT_ARC]](%i0) clock %clock reset %reset initial ([[CST2]] : i4) latency 1 {names = ["foo"] + // CHECK-NEXT: hw.output [[RES0:%.+]] + %0 = hw.constant 0 : i4 + %cst2 = hw.constant 2 : i4 + %foo = seq.compreg %i0, %clock reset %reset, %0 powerOn %cst2: i4 + hw.output %foo : i4 +} +// CHECK-NEXT: } + // CHECK-NEXT: arc.define @[[NONTRIVIAL_ARC_0:.+]]([[ARG0_1:%.+]]: i4) // CHECK-NEXT: arc.output [[ARG0_1]] // CHECK-NEXT: } diff --git a/test/Dialect/Arc/basic-errors.mlir b/test/Dialect/Arc/basic-errors.mlir index 6fe538beef05..723a29ebd5ee 100644 --- a/test/Dialect/Arc/basic-errors.mlir +++ b/test/Dialect/Arc/basic-errors.mlir @@ -524,3 +524,42 @@ hw.module @vectorize(in %in0: i4, in %in1: i4, out out0: i4) { // expected-error @below {{state type must have a known bit width}} func.func @InvalidStateType(%arg0: !arc.state) + +// ----- + +// expected-error @below {{Cannot find declaration of initializer function 'MissingInitilaizer_initial'.}} +arc.model @MissingInitilaizer io !hw.modty<> initializer @MissingInitilaizer_initial { + ^bb0(%arg0: !arc.storage<42>): +} + +// ----- + +// expected-note @below {{Initializer declared here:}} +hw.module @NonFuncInitilaizer_initial() { +} + +// expected-error @below {{Referenced initializer must be a 'func.func' op.}} +arc.model @NonFuncInitilaizer io !hw.modty<> initializer @NonFuncInitilaizer_initial { + ^bb0(%arg0: !arc.storage<42>): +} + +// ----- + +// expected-note @below {{Initializer declared here:}} +func.func @IncorrectArg_initial(!arc.storage<24>) { + ^bb0(%arg0: !arc.storage<24>): + return +} + +// expected-error @below {{Arguments of initializer function must match arguments of model body.}} +arc.model @IncorrectArg io !hw.modty<> initializer @IncorrectArg_initial { + ^bb0(%arg0: !arc.storage<42>): +} + +// ----- + +hw.module @InvalidInitType(in %clock: !seq.clock, in %input: i7) { + %cst = hw.constant 0 : i8 + // expected-error @below {{failed to verify that types of initial arguments match result types}} + %res = arc.state @Bar(%input) clock %clock initial (%cst: i8) latency 1 : (i7) -> i7 +} diff --git a/test/Dialect/Arc/lower-clocks-to-funcs-errors.mlir b/test/Dialect/Arc/lower-clocks-to-funcs-errors.mlir index a80487779825..a9100f6b9430 100644 --- a/test/Dialect/Arc/lower-clocks-to-funcs-errors.mlir +++ b/test/Dialect/Arc/lower-clocks-to-funcs-errors.mlir @@ -1,4 +1,4 @@ -// RUN: circt-opt %s --arc-lower-clocks-to-funcs --verify-diagnostics +// RUN: circt-opt %s --arc-lower-clocks-to-funcs --split-input-file --verify-diagnostics arc.model @NonConstExternalValue io !hw.modty<> { ^bb0(%arg0: !arc.storage<42>): @@ -12,3 +12,31 @@ arc.model @NonConstExternalValue io !hw.modty<> { %1 = comb.sub %0, %0 : i9001 } } + +// ----- + +func.func @VictimInit(%arg0: !arc.storage<42>) { + return +} + +// expected-warning @below {{Existing model initializer 'VictimInit' will be overridden.}} +arc.model @ExistingInit io !hw.modty<> initializer @VictimInit { +^bb0(%arg0: !arc.storage<42>): + arc.initial {} +} + +// ----- + +// expected-error @below {{op containing multiple PassThroughOps cannot be lowered.}} +// expected-error @below {{op containing multiple InitialOps is currently unsupported.}} +arc.model @MultiInitAndPassThrough io !hw.modty<> { +^bb0(%arg0: !arc.storage<1>): + // expected-note @below {{Conflicting PassThroughOp:}} + arc.passthrough {} + // expected-note @below {{Conflicting InitialOp:}} + arc.initial {} + // expected-note @below {{Conflicting PassThroughOp:}} + arc.passthrough {} + // expected-note @below {{Conflicting InitialOp:}} + arc.initial {} +} diff --git a/test/Dialect/Arc/lower-clocks-to-funcs.mlir b/test/Dialect/Arc/lower-clocks-to-funcs.mlir index 4226706910ee..9dd7d8899965 100644 --- a/test/Dialect/Arc/lower-clocks-to-funcs.mlir +++ b/test/Dialect/Arc/lower-clocks-to-funcs.mlir @@ -14,7 +14,15 @@ // CHECK-NEXT: return // CHECK-NEXT: } -// CHECK-LABEL: arc.model @Trivial io !hw.modty<> { +// CHECK-LABEL: func.func @Trivial_initial(%arg0: !arc.storage<42>) { +// CHECK-NEXT: %true = hw.constant true +// CHECK-NEXT: %c1_i9002 = hw.constant 1 : i9002 +// CHECK-NEXT: %0 = comb.mux %true, %c1_i9002, %c1_i9002 : i9002 +// CHECK-NEXT: call @Trivial_passthrough(%arg0) : (!arc.storage<42>) -> () +// CHECK-NEXT: return +// CHECK-NEXT: } + +// CHECK-LABEL: arc.model @Trivial io !hw.modty<> initializer @Trivial_initial { // CHECK-NEXT: ^bb0(%arg0: !arc.storage<42>): // CHECK-NEXT: %true = hw.constant true // CHECK-NEXT: %false = hw.constant false @@ -36,6 +44,10 @@ arc.model @Trivial io !hw.modty<> { %c1_i9001 = hw.constant 1 : i9001 %0 = comb.mux %true, %c1_i9001, %c1_i9001 : i9001 } + arc.initial { + %c1_i9002 = hw.constant 1 : i9002 + %0 = comb.mux %true, %c1_i9002, %c1_i9002 : i9002 + } } //===----------------------------------------------------------------------===// diff --git a/test/Dialect/Arc/lower-state-errors.mlir b/test/Dialect/Arc/lower-state-errors.mlir new file mode 100644 index 000000000000..cd6135a3e072 --- /dev/null +++ b/test/Dialect/Arc/lower-state-errors.mlir @@ -0,0 +1,24 @@ +// RUN: circt-opt %s --arc-lower-state --split-input-file --verify-diagnostics + +arc.define @DummyArc(%arg0: i42) -> i42 { + arc.output %arg0 : i42 +} + +// expected-error @+1 {{Value cannot be used in initializer.}} +hw.module @argInit(in %clk: !seq.clock, in %input: i42) { + %0 = arc.state @DummyArc(%0) clock %clk initial (%input : i42) latency 1 : (i42) -> i42 +} + + +// ----- + + +arc.define @DummyArc(%arg0: i42) -> i42 { + arc.output %arg0 : i42 +} + +hw.module @argInit(in %clk: !seq.clock, in %input: i42) { + // expected-error @+1 {{Value cannot be used in initializer.}} + %0 = arc.state @DummyArc(%0) clock %clk latency 1 : (i42) -> i42 + %1 = arc.state @DummyArc(%1) clock %clk initial (%0 : i42) latency 1 : (i42) -> i42 +} diff --git a/test/Dialect/Arc/lower-state.mlir b/test/Dialect/Arc/lower-state.mlir index b312bb115c8d..87f3e19cd6c6 100644 --- a/test/Dialect/Arc/lower-state.mlir +++ b/test/Dialect/Arc/lower-state.mlir @@ -366,3 +366,41 @@ hw.module @adder(in %clock : i1, in %a : i32, in %b : i32, out c : i32) { // CHECK-NEXT: %[[RESULT:.+]] = func.call @func(%6, %7) : (i32, i32) -> i32 hw.output %1 : i32 } + +// CHECK-LABEL: arc.model @InitializedStates +hw.module @InitializedStates(in %clk: !seq.clock, in %reset: i1, in %input: i42) { + +// CHECK: [[ST1:%.+]] = arc.alloc_state %arg0 : (!arc.storage) -> !arc.state +// CHECK-NEXT: [[ST2:%.+]] = arc.alloc_state %arg0 : (!arc.storage) -> !arc.state +// CHECK-NEXT: [[ST3:%.+]] = arc.alloc_state %arg0 : (!arc.storage) -> !arc.state +// CHECK-NEXT: [[ST4:%.+]] = arc.alloc_state %arg0 : (!arc.storage) -> !arc.state +// CHECK-NEXT: [[ST5:%.+]] = arc.alloc_state %arg0 : (!arc.storage) -> !arc.state + +// CHECK: arc.initial { + + %csta = hw.constant 1 : i42 + %cstb = hw.constant 10 : i42 + %cstc = hw.constant 100 : i42 + %cstd = hw.constant 1000 : i42 + %add = comb.add bin %cstb, %cstc, %csta : i42 + %mul = comb.mul bin %add, %csta : i42 + + // CHECK-NEXT: [[CSTD:%.+]] = hw.constant 1000 : i42 + // CHECK-NEXT: arc.state_write [[ST1]] = [[CSTD]] : + %0 = arc.state @DummyArc(%input) clock %clk initial (%cstd : i42) latency 1 : (i42) -> i42 + + // CHECK-DAG: [[CSTA:%.+]] = hw.constant 1 : i42 + // CHECK-DAG: [[CSTB:%.+]] = hw.constant 10 : i42 + // CHECK-DAG: [[CSTC:%.+]] = hw.constant 100 : i42 + // CHECK-DAG: [[ADD:%.+]] = comb.add bin [[CSTB]], [[CSTC]], [[CSTA]] : i42 + // CHECK-DAG: [[MUL:%.+]] = comb.mul bin [[ADD]], [[CSTA]] : i42 + + // CHECK: arc.state_write [[ST2]] = [[MUL]] : + %1 = arc.state @DummyArc(%0) clock %clk initial (%mul : i42) latency 1 : (i42) -> i42 + // CHECK-NEXT: arc.state_write [[ST3]] = [[CSTB]] : + %2 = arc.state @DummyArc(%1) clock %clk reset %reset initial (%cstb : i42) latency 1 : (i42) -> i42 + // CHECK-DAG: arc.state_write [[ST4]] = [[CSTB]] : + // CHECK-DAG: arc.state_write [[ST5]] = [[ADD]] : + %3, %4 = arc.state @DummyArc2(%2) clock %clk initial (%cstb, %add : i42, i42) latency 1 : (i42) -> (i42, i42) +// CHECK: } +} diff --git a/tools/arcilator/arcilator-header-cpp.py b/tools/arcilator/arcilator-header-cpp.py index 6a1545d950fc..113aa3094be2 100755 --- a/tools/arcilator/arcilator-header-cpp.py +++ b/tools/arcilator/arcilator-header-cpp.py @@ -63,12 +63,13 @@ class StateHierarchy: class ModelInfo: name: str numStateBytes: int + initialFnSym: str states: List[StateInfo] io: List[StateInfo] hierarchy: List[StateHierarchy] def decode(d: dict) -> "ModelInfo": - return ModelInfo(d["name"], d["numStateBytes"], + return ModelInfo(d["name"], d["numStateBytes"], d.get("initialFnSym", ""), [StateInfo.decode(d) for d in d["states"]], list(), list()) @@ -240,6 +241,8 @@ def indent(s: str, amount: int = 1): io.name = io.name + "_" print('extern "C" {') + if model.initialFnSym: + print(f"void {model.name}_initial(void* state);") print(f"void {model.name}_eval(void* state);") print('}') @@ -297,8 +300,11 @@ def indent(s: str, amount: int = 1): print(f" {model.name}View view;") print() print( - f" {model.name}() : storage({model.name}Layout::numStateBytes, 0), view(&storage[0]) {{}}" + f" {model.name}() : storage({model.name}Layout::numStateBytes, 0), view(&storage[0]) {{" ) + if model.initialFnSym: + print(f" {model.initialFnSym}(&storage[0]);") + print(" }") print(f" void eval() {{ {model.name}_eval(&storage[0]); }}") print( f" ValueChangeDump<{model.name}Layout> vcd(std::basic_ostream &os) {{" From 4bbc921cd24d2ae09a7b536e06860eb7254c50ed Mon Sep 17 00:00:00 2001 From: Will Dietz Date: Thu, 15 Aug 2024 12:07:58 -0500 Subject: [PATCH 096/119] [LowerIntmodules] Fix EICG_wrapper intrinsic lowering, swap en/test_en. (#7519) --- .../FIRRTL/Transforms/LowerIntmodules.cpp | 14 +++++++++ .../Dialect/FIRRTL/lower-intmodules-eicg.mlir | 30 +++++++++++++++++-- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/lib/Dialect/FIRRTL/Transforms/LowerIntmodules.cpp b/lib/Dialect/FIRRTL/Transforms/LowerIntmodules.cpp index b87786e66252..cf5aff491e1a 100644 --- a/lib/Dialect/FIRRTL/Transforms/LowerIntmodules.cpp +++ b/lib/Dialect/FIRRTL/Transforms/LowerIntmodules.cpp @@ -181,6 +181,20 @@ void LowerIntmodulesPass::runOnOperation() { }; auto inputs = replaceResults(builder, inst.getResults().drop_back()); + // en and test_en are swapped between extmodule and intrinsic. + if (inputs.size() > 2) { + auto port1 = inst.getPortName(1); + auto port2 = inst.getPortName(2); + if (port1 != "test_en") { + mlir::emitError(op.getPortLocation(1), + "expected port named 'test_en'"); + return signalPassFailure(); + } else if (port2 != "en") { + mlir::emitError(op.getPortLocation(2), "expected port named 'en'"); + return signalPassFailure(); + } else + std::swap(inputs[1], inputs[2]); + } auto intop = builder.create( builder.getType(), "circt_clock_gate", inputs, op.getParameters()); diff --git a/test/Dialect/FIRRTL/lower-intmodules-eicg.mlir b/test/Dialect/FIRRTL/lower-intmodules-eicg.mlir index 9e91432796ce..cb7ffd83e63b 100644 --- a/test/Dialect/FIRRTL/lower-intmodules-eicg.mlir +++ b/test/Dialect/FIRRTL/lower-intmodules-eicg.mlir @@ -11,13 +11,37 @@ firrtl.circuit "FixupEICGWrapper" { annotations = [{class = "firrtl.transforms.DedupGroupAnnotation", group = "foo"}]} // CHECK: FixupEICGWrapper - firrtl.module @FixupEICGWrapper(in %clock: !firrtl.clock, in %en: !firrtl.uint<1>) { + firrtl.module @FixupEICGWrapper(in %clock: !firrtl.clock, in %test_en: !firrtl.uint<1>, in %en: !firrtl.uint<1>) { // CHECK-NOEICG: firrtl.instance // CHECK-EICG-NOT: firrtl.instance - // CHECK-EICG: firrtl.int.generic "circt_clock_gate" + // CHECK-EICG-DAG: firrtl.matchingconnect %[[CLK:.+]], %clock : !firrtl.clock + // CHECK-EICG-DAG: firrtl.matchingconnect %[[TEST_EN:.+]], %test_en : !firrtl.uint<1> + // CHECK-EICG-DAG: firrtl.matchingconnect %[[EN:.+]], %en : !firrtl.uint<1> + // CHECK-EICG-DAG: %[[CLK]] = firrtl.wire : !firrtl.clock + // CHECK-EICG-DAG: %[[TEST_EN]] = firrtl.wire : !firrtl.uint<1> + // CHECK-EICG-DAG: %[[EN]] = firrtl.wire : !firrtl.uint<1> + // CHECK-EICG-DAG: %3 = firrtl.int.generic "circt_clock_gate" %[[CLK]], %[[EN]], %[[TEST_EN]] : (!firrtl.clock, !firrtl.uint<1>, !firrtl.uint<1>) -> !firrtl.clock %ckg_in, %ckg_test_en, %ckg_en, %ckg_out = firrtl.instance ckg @LegacyClockGate(in in: !firrtl.clock, in test_en: !firrtl.uint<1>, in en: !firrtl.uint<1>, out out: !firrtl.clock) firrtl.matchingconnect %ckg_in, %clock : !firrtl.clock - firrtl.matchingconnect %ckg_test_en, %en : !firrtl.uint<1> + firrtl.matchingconnect %ckg_test_en, %test_en : !firrtl.uint<1> + firrtl.matchingconnect %ckg_en, %en : !firrtl.uint<1> + } +} + +// ----- + +firrtl.circuit "EICGWrapperPortName" { + firrtl.extmodule @BadClockGate(in in: !firrtl.clock, + // expected-error @below {{expected port named 'test_en'}} + in en: !firrtl.uint<1>, + in test_en: !firrtl.uint<1>, + out out: !firrtl.clock) + attributes { defname = "EICG_wrapper" } + + firrtl.module @EICGWrapperPortName(in %clock: !firrtl.clock, in %test_en: !firrtl.uint<1>, in %en: !firrtl.uint<1>) { + %ckg_in, %ckg_en, %ckg_test_en, %ckg_out = firrtl.instance ckg @BadClockGate(in in: !firrtl.clock, in en: !firrtl.uint<1>, in test_en: !firrtl.uint<1>, out out: !firrtl.clock) + firrtl.matchingconnect %ckg_in, %clock : !firrtl.clock + firrtl.matchingconnect %ckg_test_en, %test_en : !firrtl.uint<1> firrtl.matchingconnect %ckg_en, %en : !firrtl.uint<1> } } From 6aa871500c87c892ba0893b005d60bdd96a043d5 Mon Sep 17 00:00:00 2001 From: Andrew Young Date: Thu, 15 Aug 2024 10:55:22 -0700 Subject: [PATCH 097/119] [firtool] Change layer specialization CLI interface (#7520) This changes how nested layers are specified when using --enable-layers or --disable-layers. Previously a nested layer was specified with `A::B`, and now it follows the syntax in the FIRRTL spec for layers, which is `A.B`. --- lib/Dialect/FIRRTL/Import/FIRParser.cpp | 4 ++-- test/firtool/specialize-layers-2.fir | 11 ---------- test/firtool/specialize-layers-cli.fir | 21 +++++++++++++++++++ ...ize-layers-1.fir => specialize-layers.fir} | 0 4 files changed, 23 insertions(+), 13 deletions(-) delete mode 100644 test/firtool/specialize-layers-2.fir create mode 100644 test/firtool/specialize-layers-cli.fir rename test/firtool/{specialize-layers-1.fir => specialize-layers.fir} (100%) diff --git a/lib/Dialect/FIRRTL/Import/FIRParser.cpp b/lib/Dialect/FIRRTL/Import/FIRParser.cpp index 56ec0e2f25ef..7470717acab3 100644 --- a/lib/Dialect/FIRRTL/Import/FIRParser.cpp +++ b/lib/Dialect/FIRRTL/Import/FIRParser.cpp @@ -5671,11 +5671,11 @@ ParseResult FIRCircuitParser::parseCircuit( // a SymbolRefAttr. auto parseLayerName = [&](StringRef name) { // Parse the layer name into a SymbolRefAttr. - auto [head, rest] = name.split("::"); + auto [head, rest] = name.split("."); SmallVector nestedRefs; while (!rest.empty()) { StringRef next; - std::tie(next, rest) = rest.split("::"); + std::tie(next, rest) = rest.split("."); nestedRefs.push_back(FlatSymbolRefAttr::get(getContext(), next)); } return SymbolRefAttr::get(getContext(), head, nestedRefs); diff --git a/test/firtool/specialize-layers-2.fir b/test/firtool/specialize-layers-2.fir deleted file mode 100644 index c733179b17ec..000000000000 --- a/test/firtool/specialize-layers-2.fir +++ /dev/null @@ -1,11 +0,0 @@ -; RUN: firtool %s -disable-layers=X,Y -; RUN: firtool %s -enable-layers=X,Y - -; https://github.com/llvm/circt/issues/7345 -; Check that we can specify more than one layer in the command line options. - -FIRRTL version 4.0.0 -circuit Foo: - layer X, bind: - layer Y, bind: - public module Foo: diff --git a/test/firtool/specialize-layers-cli.fir b/test/firtool/specialize-layers-cli.fir new file mode 100644 index 000000000000..128b2eeddddf --- /dev/null +++ b/test/firtool/specialize-layers-cli.fir @@ -0,0 +1,21 @@ +; RUN: firtool -parse-only %s | FileCheck %s --check-prefixes=NONE +; RUN: firtool -parse-only -disable-layers=A %s | FileCheck %s --check-prefixes=DISABLEA +; RUN: firtool -parse-only -enable-layers=A %s | FileCheck %s --check-prefixes=ENABLEA +; RUN: firtool -parse-only -enable-layers=A.B %s | FileCheck %s --check-prefixes=ENABLEB +; RUN: firtool -parse-only -disable-layers=A,A.B %s | FileCheck %s --check-prefixes=DISABLEBOTH +; RUN: firtool -parse-only -disable-layers=A -enable-layers=A.B %s | FileCheck %s --check-prefixes=BOTH + +; Check that the command line options are working correctly. +; https://github.com/llvm/circt/issues/7345 + +FIRRTL version 4.0.0 +; NONE: firrtl.circuit "Foo" { +; DISABLEA: firrtl.circuit "Foo" attributes {disable_layers = [@A]} { +; ENABLEA: firrtl.circuit "Foo" attributes {enable_layers = [@A]} { +; ENABLEB: firrtl.circuit "Foo" attributes {enable_layers = [@A::@B]} { +; DISABLEBOTH: firrtl.circuit "Foo" attributes {disable_layers = [@A, @A::@B]} { +; BOTH: firrtl.circuit "Foo" attributes {disable_layers = [@A], enable_layers = [@A::@B]} { +circuit Foo: + layer A, bind: + layer B, bind: + public module Foo: diff --git a/test/firtool/specialize-layers-1.fir b/test/firtool/specialize-layers.fir similarity index 100% rename from test/firtool/specialize-layers-1.fir rename to test/firtool/specialize-layers.fir From f09f4fa768bb092e1770c041ed217bac78e0fcc0 Mon Sep 17 00:00:00 2001 From: Andrew Young Date: Sun, 4 Aug 2024 13:16:56 -0700 Subject: [PATCH 098/119] [FIRRTL] Remove module self-instantiation verifier This removes a verifier that checks that modules do not instantiate themsselves. This will be covered by a new diagnostic pass which can handle indirect recursion as well. This instance verifier goes a bit beyond what we normally check in verifiers, as it must reach upward to find the owning module to compare against its target module, and we normally try to check only local operation properties in verifiers. --- lib/Dialect/FIRRTL/FIRRTLInstanceImplementation.cpp | 10 ---------- test/Dialect/FIRRTL/errors.mlir | 10 ---------- 2 files changed, 20 deletions(-) diff --git a/lib/Dialect/FIRRTL/FIRRTLInstanceImplementation.cpp b/lib/Dialect/FIRRTL/FIRRTLInstanceImplementation.cpp index 7403c72b81b0..2e3c72b394e3 100644 --- a/lib/Dialect/FIRRTL/FIRRTLInstanceImplementation.cpp +++ b/lib/Dialect/FIRRTL/FIRRTLInstanceImplementation.cpp @@ -16,7 +16,6 @@ LogicalResult instance_like_impl::verifyReferencedModule(Operation *instanceOp, SymbolTableCollection &symbolTable, mlir::FlatSymbolRefAttr moduleName) { - auto module = instanceOp->getParentOfType(); auto referencedModule = symbolTable.lookupNearestSymbolFrom(instanceOp, moduleName); if (!referencedModule) { @@ -29,15 +28,6 @@ instance_like_impl::verifyReferencedModule(Operation *instanceOp, .attachNote(referencedModule.getLoc()) << "class declared here"; - // Check that this instance doesn't recursively instantiate its wrapping - // module. - if (referencedModule == module) { - auto diag = instanceOp->emitOpError() - << "is a recursive instantiation of its containing module"; - return diag.attachNote(module.getLoc()) - << "containing module declared here"; - } - // Small helper add a note to the original declaration. auto emitNote = [&](InFlightDiagnostic &&diag) -> InFlightDiagnostic && { diag.attachNote(referencedModule->getLoc()) diff --git a/test/Dialect/FIRRTL/errors.mlir b/test/Dialect/FIRRTL/errors.mlir index ebb1c5dbaf74..02905986d31b 100644 --- a/test/Dialect/FIRRTL/errors.mlir +++ b/test/Dialect/FIRRTL/errors.mlir @@ -285,16 +285,6 @@ firrtl.circuit "Foo" { // ----- -firrtl.circuit "Foo" { - // expected-note @+1 {{containing module declared here}} - firrtl.module @Foo() { - // expected-error @+1 {{'firrtl.instance' op is a recursive instantiation of its containing module}} - firrtl.instance "" @Foo() - } -} - -// ----- - firrtl.circuit "Foo" { // expected-note @+1 {{original module declared here}} firrtl.module @Callee(in %arg0: !firrtl.uint<1>) { } From a5b599c51b4f2bb569ccd821d30e618ffe165632 Mon Sep 17 00:00:00 2001 From: Andrew Young Date: Fri, 2 Aug 2024 13:25:20 -0700 Subject: [PATCH 099/119] [FIRRTL] Add CheckRecursiveInstantiation diagnostic pass This adds a pass to FIRRTL to check for recursive module instantiation, which is illegal because it corresponds to infinitely sized hardware. --- include/circt/Dialect/FIRRTL/Passes.h | 2 + include/circt/Dialect/FIRRTL/Passes.td | 14 +++ lib/Dialect/FIRRTL/Transforms/CMakeLists.txt | 1 + .../CheckRecursiveInstantiation.cpp | 66 ++++++++++++++ .../check-recursive-instantiation-errors.mlir | 89 +++++++++++++++++++ .../FIRRTL/check-recursive-instantiation.mlir | 6 ++ 6 files changed, 178 insertions(+) create mode 100644 lib/Dialect/FIRRTL/Transforms/CheckRecursiveInstantiation.cpp create mode 100644 test/Dialect/FIRRTL/check-recursive-instantiation-errors.mlir create mode 100644 test/Dialect/FIRRTL/check-recursive-instantiation.mlir diff --git a/include/circt/Dialect/FIRRTL/Passes.h b/include/circt/Dialect/FIRRTL/Passes.h index d3dd11735179..1ae1e07e5358 100644 --- a/include/circt/Dialect/FIRRTL/Passes.h +++ b/include/circt/Dialect/FIRRTL/Passes.h @@ -214,6 +214,8 @@ std::unique_ptr createLowerDPIPass(); std::unique_ptr createAssignOutputDirsPass(mlir::StringRef outputDir = ""); +std::unique_ptr createCheckRecursiveInstantiation(); + /// Generate the code for registering passes. #define GEN_PASS_REGISTRATION #include "circt/Dialect/FIRRTL/Passes.h.inc" diff --git a/include/circt/Dialect/FIRRTL/Passes.td b/include/circt/Dialect/FIRRTL/Passes.td index 3c3e3c4d841a..ec7043fb6a97 100644 --- a/include/circt/Dialect/FIRRTL/Passes.td +++ b/include/circt/Dialect/FIRRTL/Passes.td @@ -970,4 +970,18 @@ def ProbesToSignals : Pass<"firrtl-probes-to-signals", "firrtl::CircuitOp"> { let constructor = "circt::firrtl::createProbesToSignalsPass()"; } +def CheckRecursiveInstantiation : Pass<"firrtl-check-recursive-instantiation", + "firrtl::CircuitOp"> { + let summary = "Check for illegal recursive instantiation"; + let description = [{ + This pass checks for illegal recursive module instantion. Recursive + instantiation is when a module instantiates itself, either directly or + indirectly through other modules it instantiates. Recursive module + instantiation is illegal because it would require infinite hardware to + synthesize. Recursive class instantiation is illegal as it would create an + infinite loop. + }]; + let constructor = "circt::firrtl::createCheckRecursiveInstantiation()"; +} + #endif // CIRCT_DIALECT_FIRRTL_PASSES_TD diff --git a/lib/Dialect/FIRRTL/Transforms/CMakeLists.txt b/lib/Dialect/FIRRTL/Transforms/CMakeLists.txt index c885ea49e910..e86980a540a3 100755 --- a/lib/Dialect/FIRRTL/Transforms/CMakeLists.txt +++ b/lib/Dialect/FIRRTL/Transforms/CMakeLists.txt @@ -3,6 +3,7 @@ add_circt_dialect_library(CIRCTFIRRTLTransforms AddSeqMemPorts.cpp BlackBoxReader.cpp CheckCombLoops.cpp + CheckRecursiveInstantiation.cpp CreateCompanionAssume.cpp CreateSiFiveMetadata.cpp Dedup.cpp diff --git a/lib/Dialect/FIRRTL/Transforms/CheckRecursiveInstantiation.cpp b/lib/Dialect/FIRRTL/Transforms/CheckRecursiveInstantiation.cpp new file mode 100644 index 000000000000..52329f8236db --- /dev/null +++ b/lib/Dialect/FIRRTL/Transforms/CheckRecursiveInstantiation.cpp @@ -0,0 +1,66 @@ +//===- CheckRecursiveInstantiation.cpp - Check recurisve instantiation ----===// +// +// 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/FIRRTL/FIRRTLInstanceGraph.h" +#include "circt/Dialect/FIRRTL/Passes.h" +#include "mlir/Pass/Pass.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/SCCIterator.h" + +namespace circt { +namespace firrtl { +#define GEN_PASS_DEF_CHECKRECURSIVEINSTANTIATION +#include "circt/Dialect/FIRRTL/Passes.h.inc" +} // namespace firrtl +} // namespace circt + +using namespace circt; +using namespace firrtl; + +static void printPath(InstanceGraph &instanceGraph, + ArrayRef nodes) { + assert(nodes.size() > 0 && "an scc should have at least one node"); + auto diag = + emitError(nodes.front()->getModule().getLoc(), "recursive instantiation"); + llvm::SmallPtrSet scc(nodes.begin(), nodes.end()); + for (auto *node : nodes) { + for (auto *record : *node) { + auto *target = record->getTarget(); + if (!scc.contains(target)) + continue; + auto ¬e = diag.attachNote(record->getInstance().getLoc()); + note << record->getParent()->getModule().getModuleName(); + note << " instantiates " + << record->getTarget()->getModule().getModuleName() << " here"; + } + } +} + +namespace { +class CheckRecursiveInstantiationPass + : public impl::CheckRecursiveInstantiationBase< + CheckRecursiveInstantiationPass> { +public: + void runOnOperation() override { + auto &instanceGraph = getAnalysis(); + for (auto it = llvm::scc_begin(&instanceGraph), + end = llvm::scc_end(&instanceGraph); + it != end; ++it) { + if (it.hasCycle()) { + printPath(instanceGraph, *it); + signalPassFailure(); + } + } + markAllAnalysesPreserved(); + } +}; +} // namespace + +std::unique_ptr circt::firrtl::createCheckRecursiveInstantiation() { + return std::make_unique(); +} diff --git a/test/Dialect/FIRRTL/check-recursive-instantiation-errors.mlir b/test/Dialect/FIRRTL/check-recursive-instantiation-errors.mlir new file mode 100644 index 000000000000..608b1f2cc8ab --- /dev/null +++ b/test/Dialect/FIRRTL/check-recursive-instantiation-errors.mlir @@ -0,0 +1,89 @@ +// RUN: circt-opt -pass-pipeline='builtin.module(firrtl.circuit(firrtl-check-recursive-instantiation))' %s --verify-diagnostics --split-input-file + +firrtl.circuit "SelfLoop0" { + // expected-error @below {{recursive instantiation}} + firrtl.module @SelfLoop0() { + // expected-note @below {{SelfLoop0 instantiates SelfLoop0 here}} + firrtl.instance inst @SelfLoop0() + } +} + +// ----- + +firrtl.circuit "SelfLoop1" { + // expected-error @below {{recursive instantiation}} + firrtl.module @SelfLoop1() { + // expected-note @below {{SelfLoop1 instantiates SelfLoop1 here}} + firrtl.instance inst @SelfLoop1() + // expected-note @below {{SelfLoop1 instantiates SelfLoop1 here}} + firrtl.instance inst @SelfLoop1() + } +} + +// ----- + +firrtl.circuit "TwoLoops" { + // expected-error @below {{recursive instantiation}} + firrtl.module @TwoLoops() { + // expected-note @below {{TwoLoops instantiates TwoLoops here}} + firrtl.instance inst @TwoLoops() + firrtl.instance inst @OtherModule() + } + // expected-error @below {{recursive instantiation}} + firrtl.module @OtherModule() { + // expected-note @below {{OtherModule instantiates OtherModule here}} + firrtl.instance inst @OtherModule() + } +} + +// ----- + +firrtl.circuit "MutualLoop" { + firrtl.module @MutualLoop() { + firrtl.instance a @A() + } + firrtl.module @A() { + // expected-note @below {{A instantiates B here}} + firrtl.instance b @B() + } + // expected-error @below {{recursive instantiation}} + firrtl.module @B() { + // expected-note @below {{B instantiates A here}} + firrtl.instance a @A() + } +} + +// ----- + +// Should disallow recursive class instantiation. +firrtl.circuit "Classes" { + firrtl.module @Classes() { + firrtl.object @RecursiveClass() + } + + // expected-error @below {{recursive instantiation}} + firrtl.class @RecursiveClass() { + // expected-note @below {{RecursiveClass instantiates RecursiveClass here}} + %0 = firrtl.object @RecursiveClass() + } +} + +// ----- + +firrtl.circuit "A" { + firrtl.module @A() { + // expected-note @below {{A instantiates B here}} + firrtl.instance b @B() + } + firrtl.module @B() { + // expected-note @below {{B instantiates C here}} + firrtl.instance c @C() + // expected-note @below {{B instantiates A here}} + firrtl.instance a @A() + } + // expected-error @below {{recursive instantiation}} + firrtl.module @C() { + // expected-note @below {{C instantiates B here}} + firrtl.instance b @B() + } +} diff --git a/test/Dialect/FIRRTL/check-recursive-instantiation.mlir b/test/Dialect/FIRRTL/check-recursive-instantiation.mlir new file mode 100644 index 000000000000..c9ede2b247bb --- /dev/null +++ b/test/Dialect/FIRRTL/check-recursive-instantiation.mlir @@ -0,0 +1,6 @@ +// RUN: circt-opt -pass-pipeline='builtin.module(firrtl.circuit(firrtl-check-recursive-instantiation))' %s | FileCheck %s + +// CHECK-LABEL: firrtl.circuit "NoLoop" +firrtl.circuit "NoLoop" { + firrtl.module @NoLoop() { } +} From 669d7e78866c6d0eaafd114c4d06516a00270c00 Mon Sep 17 00:00:00 2001 From: Andrew Young Date: Sun, 4 Aug 2024 13:11:55 -0700 Subject: [PATCH 100/119] [firtool] run CheckRecursiveInstantiation pass --- lib/Firtool/Firtool.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/Firtool/Firtool.cpp b/lib/Firtool/Firtool.cpp index 07d93548de3b..fe93765ce7e3 100644 --- a/lib/Firtool/Firtool.cpp +++ b/lib/Firtool/Firtool.cpp @@ -26,6 +26,8 @@ using namespace circt; LogicalResult firtool::populatePreprocessTransforms(mlir::PassManager &pm, const FirtoolOptions &opt) { + pm.nest().addPass( + firrtl::createCheckRecursiveInstantiation()); // Legalize away "open" aggregates to hw-only versions. pm.nest().addPass(firrtl::createLowerOpenAggsPass()); From 8cd116d37074308a86be193d73ae1ce2b3ff5580 Mon Sep 17 00:00:00 2001 From: Will Dietz Date: Thu, 15 Aug 2024 15:09:33 -0500 Subject: [PATCH 101/119] [NFC] Fix missing eof newline. --- include/circt/Dialect/Seq/SeqEnums.h | 2 +- test/Conversion/ExportVerilog/bugs.mlir | 2 +- test/Dialect/DC/materialize-forks-sinks.mlir | 2 +- test/Dialect/FIRRTL/annotations.mlir | 2 +- test/Dialect/FIRRTL/eliminate-wires.mlir | 2 +- test/Dialect/FIRRTL/infer-resets-errors.mlir | 2 +- test/Dialect/FIRRTL/passive-wire.mlir | 2 +- test/Dialect/Handshake/split-merge.mlir | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/include/circt/Dialect/Seq/SeqEnums.h b/include/circt/Dialect/Seq/SeqEnums.h index 85a7e478f8c5..6007eb060dbf 100644 --- a/include/circt/Dialect/Seq/SeqEnums.h +++ b/include/circt/Dialect/Seq/SeqEnums.h @@ -17,4 +17,4 @@ enum class ReadEnableMode { Zero, Ignore, Undefined }; } // namespace seq } // namespace circt -#endif // CIRCT_DIALECT_SEQ_SEQENUMS_H \ No newline at end of file +#endif // CIRCT_DIALECT_SEQ_SEQENUMS_H diff --git a/test/Conversion/ExportVerilog/bugs.mlir b/test/Conversion/ExportVerilog/bugs.mlir index 9d6792b4f705..cb31e847ac5f 100644 --- a/test/Conversion/ExportVerilog/bugs.mlir +++ b/test/Conversion/ExportVerilog/bugs.mlir @@ -10,4 +10,4 @@ module attributes {circt.loweringOptions = "disallowExpressionInliningInPorts"} // CHECK: .a (a), %bar.b = hw.instance "bar" @Bar(a: %a: !hw.inout) -> (b: i1) } -} \ No newline at end of file +} diff --git a/test/Dialect/DC/materialize-forks-sinks.mlir b/test/Dialect/DC/materialize-forks-sinks.mlir index 79590c654549..9dfa3e4df040 100644 --- a/test/Dialect/DC/materialize-forks-sinks.mlir +++ b/test/Dialect/DC/materialize-forks-sinks.mlir @@ -54,4 +54,4 @@ func.func @testUnusedArg(%t: !dc.token, %v : !dc.value) -> () { // CHECK: } func.func @testForkOfValue(%v : !dc.value) -> (!dc.value, !dc.value) { return %v, %v : !dc.value, !dc.value -} \ No newline at end of file +} diff --git a/test/Dialect/FIRRTL/annotations.mlir b/test/Dialect/FIRRTL/annotations.mlir index bc0da5dd8d22..d753d1905bf0 100644 --- a/test/Dialect/FIRRTL/annotations.mlir +++ b/test/Dialect/FIRRTL/annotations.mlir @@ -1984,4 +1984,4 @@ firrtl.circuit "Top" attributes { // CHECK-SAME: (in %reset: !firrtl.asyncreset) attributes {annotations = [{class = "circt.ExcludeFromFullResetAnnotation"}]} // expected-warning @+1 {{'sifive.enterprise.firrtl.IgnoreFullAsyncResetAnnotation' is deprecated, use 'circt.ExcludeFromFullResetAnnotation' instead}} firrtl.module @Top(in %reset: !firrtl.asyncreset) {} -} \ No newline at end of file +} diff --git a/test/Dialect/FIRRTL/eliminate-wires.mlir b/test/Dialect/FIRRTL/eliminate-wires.mlir index ee4f8eddc085..c6a2e55e3ece 100644 --- a/test/Dialect/FIRRTL/eliminate-wires.mlir +++ b/test/Dialect/FIRRTL/eliminate-wires.mlir @@ -26,4 +26,4 @@ firrtl.circuit "TopLevel" { // CHECK-NEXT: %b = firrtl.node %[[inv]] : !firrtl.uint<3> // CHECK-NEXT: %a = firrtl.node %b : !firrtl.uint<3> } -} \ No newline at end of file +} diff --git a/test/Dialect/FIRRTL/infer-resets-errors.mlir b/test/Dialect/FIRRTL/infer-resets-errors.mlir index 15c45ef61f18..b43520ccb901 100644 --- a/test/Dialect/FIRRTL/infer-resets-errors.mlir +++ b/test/Dialect/FIRRTL/infer-resets-errors.mlir @@ -280,4 +280,4 @@ firrtl.circuit "UninferredRefReset" { firrtl.circuit "Top" { // expected-error @+1 {{'FullResetAnnotation' requires resetType == 'sync' | 'async', but got resetType == "potato"}} firrtl.module @Top(in %reset: !firrtl.asyncreset) attributes {portAnnotations = [[{class = "circt.FullResetAnnotation", resetType = "potato"}]]} {} -} \ No newline at end of file +} diff --git a/test/Dialect/FIRRTL/passive-wire.mlir b/test/Dialect/FIRRTL/passive-wire.mlir index 4a7c2e301562..a728295e4359 100644 --- a/test/Dialect/FIRRTL/passive-wire.mlir +++ b/test/Dialect/FIRRTL/passive-wire.mlir @@ -42,4 +42,4 @@ firrtl.circuit "TopLevel" { // CHECK: firrtl.ref.define %b, %0 : !firrtl.probe, 2>>> } -} \ No newline at end of file +} diff --git a/test/Dialect/Handshake/split-merge.mlir b/test/Dialect/Handshake/split-merge.mlir index dd28703ca154..02ea3b69f185 100644 --- a/test/Dialect/Handshake/split-merge.mlir +++ b/test/Dialect/Handshake/split-merge.mlir @@ -59,4 +59,4 @@ handshake.func @m3(%in0 : i32, %in1 : i32, %in2 : i32) -> (i32) { handshake.func @m5(%in0 : i32, %in1 : i32, %in2 : i32, %in3 : i32, %in4 : i32) -> (i32) { %out = handshake.merge %in0, %in1, %in2, %in3, %in4 : i32 return %out : i32 -} \ No newline at end of file +} From 3b9ceeff5b57efd94fa1a1451cbb99a3b7ab3e11 Mon Sep 17 00:00:00 2001 From: Will Dietz Date: Thu, 15 Aug 2024 15:15:47 -0500 Subject: [PATCH 102/119] [NFC] Add circt copy of upstream's linkify. (#7521) --- utils/linkify.sh | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100755 utils/linkify.sh diff --git a/utils/linkify.sh b/utils/linkify.sh new file mode 100755 index 000000000000..1113552eaaea --- /dev/null +++ b/utils/linkify.sh @@ -0,0 +1,26 @@ +#!/bin/sh + +# This script linkifies (i.e. makes clickable in the terminal) text that appears +# to be a pull request or issue reference (e.g. #12345 or PR12345) or a +# 40-character commit hash (e.g. abc123). You can configure git to automatically +# send the output of commands that pipe their output through a pager, such as +# `git log` and `git show`, through this script by running this command from +# within your CIRCT checkout: +# +# git config core.pager 'utils/linkify.sh | pager' +# +# Consider: +# +# git config core.pager 'utils/linkify.sh | ${PAGER:-less}' +# +# The pager command is run from the root of the repository even if the git +# command is run from a subdirectory, so the relative path should always work. +# +# It requires OSC 8 support in the terminal. For a list of compatible terminals, +# see https://github.com/Alhadis/OSC8-Adoption +# +# Copied from upstream LLVM's llvm/utils/git/linkify. + +sed \ + -e 's,\(#\|\bPR\)\([0-9]\+\),\x1b]8;;https://github.com/llvm/circt/issues/\2\x1b\\\0\x1b]8;;\x1b\\,gi' \ + -e 's,[0-9a-f]\{40\},\x1b]8;;https://github.com/llvm/circt/commit/\0\x1b\\\0\x1b]8;;\x1b\\,g' From 3821d74f62ce2d839ab82c4cc5518581f4111e6e Mon Sep 17 00:00:00 2001 From: Mike Urbach Date: Thu, 15 Aug 2024 16:16:42 -0600 Subject: [PATCH 103/119] [FIRRTL] Update LowerClasses to alter what PathInfo stores. (#7522) PathInfo stores a pointer to an Operation, which was problematic because that Operation may be deleted in updateInstanceInModule. The test case added in this patch would lead to a use-after-free. This pointer was only really used for a few things, which can be handled differently to avoid needing to consider Operation lifetimes. One use was the operator bool implementation, to check if a PathInfo is empty. In the one place this was used, an equivalent check is to query the PathInfoTable, and check if the key was not in the table. Another use was adding the Operation's location in error messages and notes. We can safely store a Location directly for these messages. The final use was to do an isa check while determining the target kind. This is where the use in the use-after-free would manifest. For this, we do the isa check early, and store the result in a bool. In summary, we are able to simplify the data in PathInfo in order to avoid hanging on to an Operation pointer and needing to worry about its lifetime. --- .../FIRRTL/Transforms/LowerClasses.cpp | 44 +++++++++++-------- test/Dialect/FIRRTL/lower-classes.mlir | 13 ++++++ 2 files changed, 39 insertions(+), 18 deletions(-) diff --git a/lib/Dialect/FIRRTL/Transforms/LowerClasses.cpp b/lib/Dialect/FIRRTL/Transforms/LowerClasses.cpp index 056ae35af0f0..1cc16de4f08f 100644 --- a/lib/Dialect/FIRRTL/Transforms/LowerClasses.cpp +++ b/lib/Dialect/FIRRTL/Transforms/LowerClasses.cpp @@ -81,17 +81,18 @@ static bool shouldCreateClassImpl(igraph::InstanceGraphNode *node) { /// to the targeted operation. struct PathInfo { PathInfo() = default; - PathInfo(Operation *op, FlatSymbolRefAttr symRef, + PathInfo(Location loc, bool canBeInstanceTarget, FlatSymbolRefAttr symRef, StringAttr altBasePathModule) - : op(op), symRef(symRef), altBasePathModule(altBasePathModule) { - assert(op && "op must not be null"); + : loc(loc), canBeInstanceTarget(canBeInstanceTarget), symRef(symRef), + altBasePathModule(altBasePathModule) { assert(symRef && "symRef must not be null"); } - operator bool() const { return op != nullptr; } + /// The Location of the hardware component targeted by this path. + std::optional loc = std::nullopt; - /// The hardware component targeted by this path. - Operation *op = nullptr; + /// Flag to indicate if the hardware component can be targeted as an instance. + bool canBeInstanceTarget = false; /// A reference to the hierarchical path targeting the op. FlatSymbolRefAttr symRef = nullptr; @@ -584,17 +585,23 @@ LogicalResult PathTracker::updatePathInfoTable(PathInfoTable &pathInfoTable, auto [it, inserted] = pathInfoTable.table.try_emplace(entry.id); auto &pathInfo = it->second; if (!inserted) { - auto diag = - emitError(pathInfo.op->getLoc(), "duplicate identifier found"); + assert(pathInfo.loc.has_value() && "all PathInfo should have a Location"); + auto diag = emitError(pathInfo.loc.value(), "duplicate identifier found"); diag.attachNote(entry.op->getLoc()) << "other identifier here"; return failure(); } - if (entry.pathAttr) - pathInfo = {entry.op, cache.getRefFor(entry.pathAttr), - entry.altBasePathModule}; - else - pathInfo.op = entry.op; + // Check if the op is targetable by an instance target. The op pointer may + // be invalidated later, so this is the last time we want to access it here. + bool canBeInstanceTarget = isa(entry.op); + + if (entry.pathAttr) { + pathInfo = {entry.op->getLoc(), canBeInstanceTarget, + cache.getRefFor(entry.pathAttr), entry.altBasePathModule}; + } else { + pathInfo.loc = entry.op->getLoc(); + pathInfo.canBeInstanceTarget = canBeInstanceTarget; + } } return success(); } @@ -1453,14 +1460,14 @@ struct PathOpConversion : public OpConversionPattern { ConversionPatternRewriter &rewriter) const override { auto *context = op->getContext(); auto pathType = om::PathType::get(context); - auto pathInfo = pathInfoTable.table.lookup(op.getTarget()); + auto pathInfoIt = pathInfoTable.table.find(op.getTarget()); // The 0'th argument is the base path by default. auto basePath = op->getBlock()->getArgument(0); // If the target was optimized away, then replace the path operation with // a deleted path. - if (!pathInfo) { + if (pathInfoIt == pathInfoTable.table.end()) { if (op.getTargetKind() == firrtl::TargetKind::DontTouch) return emitError(op.getLoc(), "DontTouch target was deleted"); if (op.getTargetKind() == firrtl::TargetKind::Instance) @@ -1469,6 +1476,7 @@ struct PathOpConversion : public OpConversionPattern { return success(); } + auto pathInfo = pathInfoIt->second; auto symbol = pathInfo.symRef; // Convert the target kind to an OMIR target. Member references are updated @@ -1482,15 +1490,15 @@ struct PathOpConversion : public OpConversionPattern { targetKind = om::TargetKind::Reference; break; case firrtl::TargetKind::Instance: - if (!isa(pathInfo.op)) + if (!pathInfo.canBeInstanceTarget) return emitError(op.getLoc(), "invalid target for instance path") - .attachNote(pathInfo.op->getLoc()) + .attachNote(pathInfo.loc) << "target not instance or module"; targetKind = om::TargetKind::Instance; break; case firrtl::TargetKind::MemberInstance: case firrtl::TargetKind::MemberReference: - if (isa(pathInfo.op)) + if (pathInfo.canBeInstanceTarget) targetKind = om::TargetKind::MemberInstance; else targetKind = om::TargetKind::MemberReference; diff --git a/test/Dialect/FIRRTL/lower-classes.mlir b/test/Dialect/FIRRTL/lower-classes.mlir index 0a1e81a03878..5ab1befacc50 100644 --- a/test/Dialect/FIRRTL/lower-classes.mlir +++ b/test/Dialect/FIRRTL/lower-classes.mlir @@ -458,3 +458,16 @@ firrtl.circuit "OwningModulePrefix" { firrtl.path reference distinct[0]<> } } + +// CHECK-LABEL: firrtl.circuit "PathTargetReplaced" +firrtl.circuit "PathTargetReplaced" { + // CHECK: hw.hierpath private [[NLA:@.+]] [@PathTargetReplaced::[[SYM:@.+]]] + firrtl.module @PathTargetReplaced() { + // CHECK: firrtl.instance replaced sym [[SYM]] + firrtl.instance replaced {annotations = [{class = "circt.tracker", id = distinct[0]<>}]} @WillBeReplaced(out output: !firrtl.integer) + // CHECK: om.path_create instance %basepath [[NLA]] + %path = firrtl.path instance distinct[0]<> + } + firrtl.module private @WillBeReplaced(out %output: !firrtl.integer) { + } +} From 4492392eab2520f6149e9ddfc188ff8bd07e5de4 Mon Sep 17 00:00:00 2001 From: Fabian Schuiki Date: Thu, 15 Aug 2024 18:15:07 -0700 Subject: [PATCH 104/119] [LLHD] Align signals with other wire/variable ops (#7523) Make the `llhd.sig` op use the same naming pattern as HW wires, Moore variables, Seq registers, and a handful of other operations in CIRCT. These all use the `custom` parser to provide uniform handling of optional names. Make the signal name optional to align with other ops. Rename the class to `SignalOp` for clarity. --- .../circt/Dialect/LLHD/IR/LLHDSignalOps.td | 31 ++-- lib/Conversion/MooreToCore/MooreToCore.cpp | 4 +- lib/Dialect/LLHD/IR/LLHDOps.cpp | 11 ++ .../LLHD/Transforms/EarlyCodeMotionPass.cpp | 2 +- test/Conversion/MooreToCore/basic.mlir | 26 ++-- test/Dialect/LLHD/IR/basic.mlir | 16 +- .../LLHD/Transforms/temporal-code-motion.mlir | 140 +++++++++--------- 7 files changed, 120 insertions(+), 110 deletions(-) diff --git a/include/circt/Dialect/LLHD/IR/LLHDSignalOps.td b/include/circt/Dialect/LLHD/IR/LLHDSignalOps.td index 165218abcb3b..ae9c60bd0cc8 100644 --- a/include/circt/Dialect/LLHD/IR/LLHDSignalOps.td +++ b/include/circt/Dialect/LLHD/IR/LLHDSignalOps.td @@ -11,12 +11,14 @@ //===----------------------------------------------------------------------===// include "mlir/IR/EnumAttr.td" - -def SigOp : LLHDOp<"sig", [ - TypesMatchWith< - "type of 'init' and underlying type of 'signal' have to match.", - "init", "result", "hw::InOutType::get($_self)"> - ]> { +include "mlir/IR/OpAsmInterface.td" + +def SignalOp : LLHDOp<"sig", [ + DeclareOpInterfaceMethods, + TypesMatchWith< + "type of 'init' and underlying type of 'signal' have to match.", + "init", "result", "hw::InOutType::get($_self)"> +]> { let summary = "Create a signal."; let description = [{ The `llhd.sig` instruction introduces a new signal in the IR. The input @@ -28,17 +30,22 @@ def SigOp : LLHDOp<"sig", [ ```mlir %c123_i64 = hw.constant 123 : i64 - %sig_i64 = llhd.sig "foo" %c123_i64 : i64 + %foo = llhd.sig %c123_i64 : i64 + %0 = llhd.sig name "foo" %c123_i64 : i64 ``` This example creates a new signal named "foo", carrying an `i64` type with initial value of 123. }]; - - let arguments = (ins StrAttr: $name, HWValueType: $init); - let results = (outs InOutType: $result); - - let assemblyFormat = "$name $init attr-dict `:` qualified(type($init))"; + let arguments = (ins + OptionalAttr:$name, + HWValueType:$init + ); + let results = (outs Res:$result); + let assemblyFormat = [{ + `` custom($name) $init attr-dict + `:` type($init) + }]; } def PrbOp : LLHDOp<"prb", [ diff --git a/lib/Conversion/MooreToCore/MooreToCore.cpp b/lib/Conversion/MooreToCore/MooreToCore.cpp index e55d85332753..d0cce3c1551c 100644 --- a/lib/Conversion/MooreToCore/MooreToCore.cpp +++ b/lib/Conversion/MooreToCore/MooreToCore.cpp @@ -337,8 +337,8 @@ struct VariableOpConversion : public OpConversionPattern { init = rewriter.createOrFold(loc, elementType, constZero); } - rewriter.replaceOpWithNewOp(op, resultType, op.getNameAttr(), - init); + rewriter.replaceOpWithNewOp(op, resultType, + op.getNameAttr(), init); return success(); } }; diff --git a/lib/Dialect/LLHD/IR/LLHDOps.cpp b/lib/Dialect/LLHD/IR/LLHDOps.cpp index 6e85ea56163b..7abe6541da69 100644 --- a/lib/Dialect/LLHD/IR/LLHDOps.cpp +++ b/lib/Dialect/LLHD/IR/LLHDOps.cpp @@ -12,6 +12,7 @@ #include "circt/Dialect/LLHD/IR/LLHDOps.h" #include "circt/Dialect/HW/HWOps.h" +#include "circt/Support/CustomDirectiveImpl.h" #include "mlir/IR/Attributes.h" #include "mlir/IR/BuiltinTypes.h" #include "mlir/IR/Matchers.h" @@ -24,6 +25,7 @@ using namespace circt; using namespace mlir; +using namespace llhd; unsigned circt::llhd::getLLHDTypeWidth(Type type) { if (auto sig = dyn_cast(type)) @@ -64,6 +66,15 @@ void llhd::ConstantTimeOp::build(OpBuilder &builder, OperationState &result, return build(builder, result, TimeType::get(ctx), attr); } +//===----------------------------------------------------------------------===// +// SignalOp +//===----------------------------------------------------------------------===// + +void SignalOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) { + if (getName() && !getName()->empty()) + setNameFn(getResult(), *getName()); +} + //===----------------------------------------------------------------------===// // SigExtractOp and PtrExtractOp //===----------------------------------------------------------------------===// diff --git a/lib/Dialect/LLHD/Transforms/EarlyCodeMotionPass.cpp b/lib/Dialect/LLHD/Transforms/EarlyCodeMotionPass.cpp index bab5cb513ffd..27b7a6afe3fe 100644 --- a/lib/Dialect/LLHD/Transforms/EarlyCodeMotionPass.cpp +++ b/lib/Dialect/LLHD/Transforms/EarlyCodeMotionPass.cpp @@ -70,7 +70,7 @@ void EarlyCodeMotionPass::runOnProcess(llhd::ProcessOp proc) { for (auto iter = block->getOperations().begin(); iter != block->getOperations().end(); ++iter) { Operation &op = *iter; - if (!isa(op) && !isa(op) && + if (!isa(op) && !isa(op) && (!mlir::isMemoryEffectFree(&op) || op.hasTrait())) continue; diff --git a/test/Conversion/MooreToCore/basic.mlir b/test/Conversion/MooreToCore/basic.mlir index a0b8f3cd39a7..c20655a8ce5c 100644 --- a/test/Conversion/MooreToCore/basic.mlir +++ b/test/Conversion/MooreToCore/basic.mlir @@ -369,31 +369,31 @@ moore.module @ParamTest(){ moore.module @Variable() { // CHECK: [[TMP0:%.+]] = hw.constant 0 : i32 - // CHECK: [[A:%.+]] = llhd.sig "a" [[TMP0]] : i32 + // CHECK: %a = llhd.sig [[TMP0]] : i32 %a = moore.variable : // CHECK: [[TMP1:%.+]] = hw.constant 0 : i8 - // CHECK: [[B:%.+]] = llhd.sig "b1" [[TMP1]] : i8 + // CHECK: %b1 = llhd.sig [[TMP1]] : i8 %b1 = moore.variable : - // CHECK: [[PRB:%.+]] = llhd.prb [[B]] : !hw.inout + // CHECK: [[PRB:%.+]] = llhd.prb %b1 : !hw.inout %0 = moore.read %b1 : - // CHECK: llhd.sig "b2" [[PRB]] : i8 + // CHECK: %b2 = llhd.sig [[PRB]] : i8 %b2 = moore.variable %0 : // CHECK: %true = hw.constant true %1 = moore.constant 1 : l1 - // CHECK: llhd.sig "l" %true : i1 + // CHECK: %l = llhd.sig %true : i1 %l = moore.variable %1 : // CHECK: [[TMP:%.+]] = hw.constant 0 : i19 - // CHECK: llhd.sig "m" [[TMP]] : i19 + // CHECK: %m = llhd.sig [[TMP]] : i19 %m = moore.variable : // CHECK: [[TMP2:%.+]] = hw.constant 10 : i32 %3 = moore.constant 10 : i32 // CHECK: [[TIME:%.+]] = llhd.constant_time <0ns, 0d, 1e> - // CHECK: llhd.drv [[A]], [[TMP2]] after [[TIME]] : !hw.inout + // CHECK: llhd.drv %a, [[TMP2]] after [[TIME]] : !hw.inout moore.assign %a, %3 : i32 // CHECK: hw.output @@ -411,8 +411,8 @@ moore.module @Struct(in %a : !moore.i32, in %b : !moore.i32, in %arg0 : !moore.s // CHECK: [[C0:%.+]] = hw.constant 0 : i64 // CHECK: [[INIT:%.+]] = hw.bitcast [[C0]] : (i64) -> !hw.struct - // CHECK: llhd.sig "" [[INIT]] : !hw.struct - // CHECK: llhd.sig "" %arg0 : !hw.struct + // CHECK: llhd.sig [[INIT]] : !hw.struct + // CHECK: llhd.sig %arg0 : !hw.struct %1 = moore.variable : > %2 = moore.variable %arg0 : > @@ -427,10 +427,10 @@ moore.module @Struct(in %a : !moore.i32, in %b : !moore.i32, in %arg0 : !moore.s // CHECK-LABEL: hw.module @Process moore.module @Process(in %cond : i1) { - // CHECK: [[B:%.+]] = llhd.sig "b" - // CHECK: [[C:%.+]] = llhd.sig "c" - // CHECK: [[D:%.+]] = llhd.sig "d" - // CHECK: [[E:%.+]] = llhd.sig "e" + // CHECK: [[B:%b]] = llhd.sig + // CHECK: [[C:%c]] = llhd.sig + // CHECK: [[D:%d]] = llhd.sig + // CHECK: [[E:%e]] = llhd.sig %b = moore.variable : %c = moore.variable : %d = moore.variable : diff --git a/test/Dialect/LLHD/IR/basic.mlir b/test/Dialect/LLHD/IR/basic.mlir index 96d768958628..e1a0941fa1a6 100644 --- a/test/Dialect/LLHD/IR/basic.mlir +++ b/test/Dialect/LLHD/IR/basic.mlir @@ -90,22 +90,22 @@ func.func @check_store(%int : !llhd.ptr, %intC : i32 , %array : !llhd.ptr - // CHECK-NEXT: %{{.*}} = llhd.sig "sigTup" %[[TUP]] : !hw.struct - %sigTup = llhd.sig "sigTup" %tup : !hw.struct + // CHECK-NEXT: %sigTup = llhd.sig %[[TUP]] : !hw.struct + %sigTup = llhd.sig %tup : !hw.struct // CHECK-NEXT: %[[ARRAY:.*]] = hw.array_create %array = hw.array_create %cI1, %cI1 : i1 - // CHECK-NEXT: %{{.*}} = llhd.sig "sigArray" %[[ARRAY]] : !hw.array<2xi1> - %sigArray = llhd.sig "sigArray" %array : !hw.array<2xi1> + // CHECK-NEXT: %sigArray = llhd.sig %[[ARRAY]] : !hw.array<2xi1> + %sigArray = llhd.sig %array : !hw.array<2xi1> } // CHECK-LABEL: @checkPrb diff --git a/test/Dialect/LLHD/Transforms/temporal-code-motion.mlir b/test/Dialect/LLHD/Transforms/temporal-code-motion.mlir index d6bce05e2164..00889d6d1277 100644 --- a/test/Dialect/LLHD/Transforms/temporal-code-motion.mlir +++ b/test/Dialect/LLHD/Transforms/temporal-code-motion.mlir @@ -9,36 +9,28 @@ hw.module @basic(in %cond: i1) { // CHECK: [[V1:%.+]] = llhd.constant_time <0ns, 0d, 1e> %0 = llhd.constant_time <0ns, 1d, 0e> %1 = llhd.constant_time <0ns, 0d, 1e> - // CHECK: [[V2:%.+]] = llhd.sig "a" - // CHECK: [[V3:%.+]] = llhd.sig "b" - // CHECK: [[V4:%.+]] = llhd.sig "c" - // CHECK: [[V5:%.+]] = llhd.sig "d" - // CHECK: [[V6:%.+]] = llhd.sig "e" - // CHECK: [[V7:%.+]] = llhd.sig "f" - // CHECK: [[V8:%.+]] = llhd.sig "g" - // CHECK: [[V9:%.+]] = llhd.sig "h" - // CHECK: [[V10:%.+]] = llhd.sig "i" - // CHECK: [[V11:%.+]] = llhd.sig "j" - // CHECK: [[V12:%.+]] = llhd.sig "k" - // CHECK: [[V13:%.+]] = llhd.sig "l" - // CHECK: [[V13_1:%.+]] = llhd.sig "m" - // CHECK: [[V13_2:%.+]] = llhd.sig "n" - // CHECK: [[V13_3:%.+]] = llhd.sig "o" - %2 = llhd.sig "a" %false : i1 - %3 = llhd.sig "b" %false : i1 - %4 = llhd.sig "c" %false : i1 - %5 = llhd.sig "d" %false : i1 - %6 = llhd.sig "e" %false : i1 - %7 = llhd.sig "f" %false : i1 - %8 = llhd.sig "g" %c0_i4 : i4 - %9 = llhd.sig "h" %c0_i4 : i4 - %10 = llhd.sig "i" %c0_i4 : i4 - %11 = llhd.sig "j" %false : i1 - %12 = llhd.sig "k" %c0_i5 : i5 - %13 = llhd.sig "l" %c0_i5 : i5 - %14 = llhd.sig "m" %c0_i5 : i5 - %15 = llhd.sig "n" %c0_i5 : i5 - %16 = llhd.sig "o" %c0_i5 : i5 + // CHECK: %c = llhd.sig + // CHECK: %d = llhd.sig + // CHECK: %e = llhd.sig + // CHECK: %f = llhd.sig + // CHECK: %g = llhd.sig + // CHECK: %h = llhd.sig + // CHECK: %k = llhd.sig + // CHECK: %l = llhd.sig + // CHECK: %m = llhd.sig + // CHECK: %n = llhd.sig + // CHECK: %o = llhd.sig + %c = llhd.sig %false : i1 + %d = llhd.sig %false : i1 + %e = llhd.sig %false : i1 + %f = llhd.sig %false : i1 + %g = llhd.sig %c0_i4 : i4 + %h = llhd.sig %c0_i4 : i4 + %k = llhd.sig %c0_i5 : i5 + %l = llhd.sig %c0_i5 : i5 + %m = llhd.sig %c0_i5 : i5 + %n = llhd.sig %c0_i5 : i5 + %o = llhd.sig %c0_i5 : i5 // COM: Check that an auxillary block is created and all drives are moved to // COM: the exit block with the correct enable condition @@ -49,45 +41,45 @@ hw.module @basic(in %cond: i1) { // CHECK: ^[[BB1]]: ^bb1: // CHECK: llhd.wait ({{.*}}), ^[[BB2:.+]] - llhd.wait (%12, %4, %6, %9, %5, %7, %8 : !hw.inout, !hw.inout, !hw.inout, !hw.inout, !hw.inout, !hw.inout, !hw.inout), ^bb2 + llhd.wait (%k, %c, %e, %h, %d, %f, %g : !hw.inout, !hw.inout, !hw.inout, !hw.inout, !hw.inout, !hw.inout, !hw.inout), ^bb2 // CHECK: ^[[BB2]]: ^bb2: - // CHECK: [[V14:%.+]] = llhd.prb [[V12]] - // CHECK: [[V15:%.+]] = llhd.prb [[V4]] - // CHECK: [[V16:%.+]] = llhd.prb [[V6]] - // CHECK: [[V17:%.+]] = llhd.prb [[V9]] + // CHECK: [[V14:%.+]] = llhd.prb %k + // CHECK: [[V15:%.+]] = llhd.prb %c + // CHECK: [[V16:%.+]] = llhd.prb %e + // CHECK: [[V17:%.+]] = llhd.prb %h // CHECK: [[V18:%.+]] = comb.concat %false{{.*}}, [[V17]] : i1, i4 - // CHECK: [[V19:%.+]] = llhd.prb [[V5]] - // CHECK: [[V20:%.+]] = llhd.prb [[V7]] - // CHECK: [[V21:%.+]] = llhd.prb [[V12]] - // CHECK: [[V22:%.+]] = llhd.prb [[V8]] + // CHECK: [[V19:%.+]] = llhd.prb %d + // CHECK: [[V20:%.+]] = llhd.prb %f + // CHECK: [[V21:%.+]] = llhd.prb %k + // CHECK: [[V22:%.+]] = llhd.prb %g // CHECK: [[V23:%.+]] = comb.concat %false{{.*}}, [[V22]] : i1, i4 // CHECK: [[V24:%.+]] = comb.sub [[V21]], [[V23]] : i5 - // CHECK: [[V25:%.+]] = llhd.prb [[V12]] - // CHECK: [[V26:%.+]] = llhd.prb [[V8]] + // CHECK: [[V25:%.+]] = llhd.prb %k + // CHECK: [[V26:%.+]] = llhd.prb %g // CHECK: [[V27:%.+]] = comb.concat %false{{.*}}, [[V26]] : i1, i4 // CHECK: [[V28:%.+]] = comb.add [[V25]], [[V27]] : i5 // CHECK: cf.cond_br [[V15]], ^[[BB3:.+]], ^[[BB4:.+]] - %25 = llhd.prb %12 : !hw.inout - llhd.drv %12, %25 after %1 : !hw.inout - %26 = llhd.prb %4 : !hw.inout - %27 = llhd.prb %6 : !hw.inout - %28 = llhd.prb %9 : !hw.inout + %25 = llhd.prb %k : !hw.inout + llhd.drv %k, %25 after %1 : !hw.inout + %26 = llhd.prb %c : !hw.inout + %27 = llhd.prb %e : !hw.inout + %28 = llhd.prb %h : !hw.inout %29 = comb.concat %false, %28 : i1, i4 - %30 = llhd.prb %5 : !hw.inout - %31 = llhd.prb %7 : !hw.inout - %32 = llhd.prb %12 : !hw.inout - %33 = llhd.prb %8 : !hw.inout + %30 = llhd.prb %d : !hw.inout + %31 = llhd.prb %f : !hw.inout + %32 = llhd.prb %k : !hw.inout + %33 = llhd.prb %g : !hw.inout %34 = comb.concat %false, %33 : i1, i4 %35 = comb.sub %32, %34 : i5 - %36 = llhd.prb %12 : !hw.inout - %37 = llhd.prb %8 : !hw.inout + %36 = llhd.prb %k : !hw.inout + %37 = llhd.prb %g : !hw.inout %38 = comb.concat %false, %37 : i1, i4 %39 = comb.add %36, %38 : i5 cf.cond_br %26, ^bb3, ^bb4 // CHECK: ^[[BB3]]: ^bb3: - llhd.drv %13, %c0_i5 after %1 if %cond : !hw.inout + llhd.drv %l, %c0_i5 after %1 if %cond : !hw.inout // CHECK: cf.br ^[[BB10:.+]] cf.br ^bb1 // CHECK: ^[[BB4]]: @@ -96,7 +88,7 @@ hw.module @basic(in %cond: i1) { cf.cond_br %27, ^bb5, ^bb6 // CHECK: ^[[BB5]]: ^bb5: - llhd.drv %14, %29 after %1 : !hw.inout + llhd.drv %m, %29 after %1 : !hw.inout // CHECK: cf.br ^[[BB10]] cf.br ^bb1 // CHECK: ^[[BB6]]: @@ -109,28 +101,28 @@ hw.module @basic(in %cond: i1) { cf.cond_br %31, ^bb8, ^bb9 // CHECK: ^[[BB8]]: ^bb8: - llhd.drv %15, %35 after %1 : !hw.inout + llhd.drv %n, %35 after %1 : !hw.inout // CHECK: cf.br ^[[BB10]] cf.br ^bb1 // CHECK: ^[[BB9]]: ^bb9: - llhd.drv %16, %39 after %1 : !hw.inout + llhd.drv %o, %39 after %1 : !hw.inout // CHECK: cf.br ^[[BB10]] cf.br ^bb1 // CHECK: ^[[BB10]]: - // CHECK: llhd.drv [[V12]], [[V14]] after [[V1]] if %true{{.*}} : !hw.inout + // CHECK: llhd.drv %k, [[V14]] after [[V1]] if %true{{.*}} : !hw.inout // CHECK: [[V29:%.+]] = comb.and %true{{.*}}, [[V15]] : i1 // CHECK: [[V30:%.+]] = comb.or %false{{.*}}, [[V29]] : i1 // CHECK: [[V31:%.+]] = comb.and %cond, [[V30]] : i1 - // CHECK: llhd.drv [[V13]], %c0_i5 after [[V1]] if [[V31]] : !hw.inout + // CHECK: llhd.drv %l, %c0_i5 after [[V1]] if [[V31]] : !hw.inout // CHECK: [[V33:%.+]] = comb.xor [[V15]], %true{{.*}} : i1 // CHECK: [[V34:%.+]] = comb.and %true{{.*}}, [[V33]] : i1 // CHECK: [[V35:%.+]] = comb.or %false{{.*}}, [[V34]] : i1 // CHECK: [[V36:%.+]] = comb.and [[V35]], [[V16]] : i1 // CHECK: [[V37:%.+]] = comb.or %false{{.*}}, [[V36]] : i1 - // CHECK: llhd.drv [[V13_1]], [[V18]] after [[V1]] if [[V37]] : !hw.inout + // CHECK: llhd.drv %m, [[V18]] after [[V1]] if [[V37]] : !hw.inout // CHECK: [[V40:%.+]] = comb.xor [[V16]], %true{{.*}} : i1 // CHECK: [[V41:%.+]] = comb.and [[V35]], [[V40]] : i1 @@ -139,12 +131,12 @@ hw.module @basic(in %cond: i1) { // CHECK: [[V44:%.+]] = comb.or %false{{.*}}, [[V43]] : i1 // CHECK: [[V45:%.+]] = comb.and [[V44]], [[V20]] : i1 // CHECK: [[V46:%.+]] = comb.or %false{{.*}}, [[V45]] : i1 - // CHECK: llhd.drv [[V13_2]], [[V24]] after [[V1]] if [[V46]] : !hw.inout + // CHECK: llhd.drv %n, [[V24]] after [[V1]] if [[V46]] : !hw.inout // CHECK: [[V49:%.+]] = comb.xor [[V20]], %true{{.*}} : i1 // CHECK: [[V50:%.+]] = comb.and [[V44]], [[V49]] : i1 // CHECK: [[V51:%.+]] = comb.or %false{{.*}}, [[V50]] : i1 - // CHECK: llhd.drv [[V13_3]], [[V28]] after [[V1]] if [[V51]] : !hw.inout + // CHECK: llhd.drv %o, [[V28]] after [[V1]] if [[V51]] : !hw.inout // CHECK: cf.br ^[[BB1]] } @@ -154,27 +146,27 @@ hw.module @basic(in %cond: i1) { ^bb1: llhd.wait ^bb2 ^bb2: - // CHECK: [[V20:%.+]] = llhd.prb [[V13]] - // CHECK-NEXT: [[V21:%.+]] = llhd.prb [[V13_1]] - %20 = llhd.prb %13 : !hw.inout - %21 = llhd.prb %14 : !hw.inout + // CHECK: [[V20:%.+]] = llhd.prb %l + // CHECK-NEXT: [[V21:%.+]] = llhd.prb %m + %20 = llhd.prb %l : !hw.inout + %21 = llhd.prb %m : !hw.inout - // CHECK-NEXT: llhd.drv [[V12]], [[V21]] after [[V1]] : !hw.inout - llhd.drv %12, %20 after %1 : !hw.inout - llhd.drv %12, %21 after %1 : !hw.inout + // CHECK-NEXT: llhd.drv %k, [[V21]] after [[V1]] : !hw.inout + llhd.drv %k, %20 after %1 : !hw.inout + llhd.drv %k, %21 after %1 : !hw.inout // CHECK-NEXT: [[V22:%.+]] = comb.mux %cond, [[V21]], [[V20]] - // CHECK-NEXT: llhd.drv [[V13]], [[V22]] after [[V1]] : !hw.inout - llhd.drv %13, %20 after %1 : !hw.inout - llhd.drv %13, %21 after %1 if %cond : !hw.inout + // CHECK-NEXT: llhd.drv %l, [[V22]] after [[V1]] : !hw.inout + llhd.drv %l, %20 after %1 : !hw.inout + llhd.drv %l, %21 after %1 if %cond : !hw.inout // CHECK-NEXT: [[V23:%.+]] = comb.xor %cond, %true // CHECK-NEXT: [[V24:%.+]] = comb.mux %cond, [[V21]], [[V20]] // CHECK-NEXT: [[V25:%.+]] = comb.or %cond, [[V23]] - // CHECK-NEXT: llhd.drv [[V13_1]], [[V24]] after [[V1]] if [[V25]] : !hw.inout + // CHECK-NEXT: llhd.drv %m, [[V24]] after [[V1]] if [[V25]] : !hw.inout %22 = comb.xor %cond, %true : i1 - llhd.drv %14, %20 after %1 if %22 : !hw.inout - llhd.drv %14, %21 after %1 if %cond : !hw.inout + llhd.drv %m, %20 after %1 if %22 : !hw.inout + llhd.drv %m, %21 after %1 if %cond : !hw.inout // CHECK-NEXT: cf.br cf.br ^bb1 From d71e6e30c3e1a3ace9dee863d58e2ddd148db532 Mon Sep 17 00:00:00 2001 From: Fabian Schuiki Date: Fri, 16 Aug 2024 13:14:12 -0700 Subject: [PATCH 105/119] [Moore] Improve WaitEventOp, lower to LLHD (#7518) Rework the `moore.wait_event` op to be able to accurately model the semantics of SystemVerilog's `@...` event control statements. The op now has a body region which is executed to detect a relevant change in one or more interesting values. A new `moore.detect_event` op serves as the mechanism to encode whether a posedge, negedge, both, or any change at all on a value should be detected as an event. Based on this new `moore.wait_event` op we can properly convert most of the event control statements in `ImportVerilog` to a corresponding MLIR op. Delay control like `#1ns` is not handled yet. In the MooreToCore conversion this new op allows us to properly generate `llhd.wait` operations at the right place that suspend process execution until an interesting event has occurred. This now also allows us to support almost all SystemVerilog processes in the lowering. The only missing ones are `always_comb` and `always_latch` which require an implicit `llhd.wait` to be inserted. @maerhart has a version of that lowering almost ready though. This commit also adds an `llhd.final` op in order to be able to lower `final` procedures. Fixes #7482. Co-authored-by: Martin Erhart --- .../circt/Dialect/LLHD/IR/LLHDStructureOps.td | 40 +- include/circt/Dialect/Moore/MooreOps.td | 119 ++++-- lib/Conversion/ImportVerilog/Expressions.cpp | 81 ++-- .../ImportVerilog/ImportVerilogInternals.h | 19 +- lib/Conversion/ImportVerilog/Statements.cpp | 6 +- .../ImportVerilog/TimingControls.cpp | 196 ++++++++-- lib/Conversion/MooreToCore/MooreToCore.cpp | 361 ++++++++++++------ test/Conversion/ImportVerilog/basic.sv | 267 ++++++++++--- test/Conversion/MooreToCore/basic.mlir | 357 ++++++++++------- test/Dialect/LLHD/IR/basic.mlir | 10 + test/Dialect/Moore/basic.mlir | 19 + tools/circt-verilog/circt-verilog.cpp | 12 +- 12 files changed, 1078 insertions(+), 409 deletions(-) diff --git a/include/circt/Dialect/LLHD/IR/LLHDStructureOps.td b/include/circt/Dialect/LLHD/IR/LLHDStructureOps.td index 3f007c8c7689..58afcb53bd60 100644 --- a/include/circt/Dialect/LLHD/IR/LLHDStructureOps.td +++ b/include/circt/Dialect/LLHD/IR/LLHDStructureOps.td @@ -12,6 +12,7 @@ def ProcessOp : LLHDOp<"process", [ NoRegionArguments, + RecursiveMemoryEffects, HasParent<"hw::HWModuleOp"> ]> { let summary = "create a process"; @@ -51,6 +52,39 @@ def ProcessOp : LLHDOp<"process", [ let assemblyFormat = "attr-dict-with-keyword $body"; } +def FinalOp : LLHDOp<"final", [ + NoRegionArguments, + RecursiveMemoryEffects, + HasParent<"hw::HWModuleOp">, +]> { + let summary = "A process that runs at the end of simulation"; + let description = [{ + An `llhd.final` op encapsulates a region of IR that is to be executed after + the last time step of a simulation has completed. This can be used to + implement various forms of state cleanup and tear-down. Some verifications + ops may also want to check that certain final conditions hold at the end of + a simulation run. + + The `llhd.wait` terminator is not allowed in `llhd.final` processes since + there is no later time slot for the execution to resume. Control flow must + eventually end in an `llhd.halt` terminator. + + Execution order between multiple `llhd.final` ops is undefined. + + Example: + ```mlir + hw.module @Foo() { + llhd.final { + func.call @printSimulationStatistics() : () -> () + llhd.halt + } + } + ``` + }]; + let regions = (region MinSizedRegion<1>: $body); + let assemblyFormat = "attr-dict-with-keyword $body"; +} + def ConnectOp : LLHDOp<"con", [ SameTypeOperands, HasParent<"hw::HWModuleOp"> @@ -108,12 +142,14 @@ def WaitOp : LLHDOp<"wait", [ }]; } -def HaltOp : LLHDOp<"halt", [Terminator, HasParent<"ProcessOp">]> { +def HaltOp : LLHDOp<"halt", [ + Terminator, + ParentOneOf<["ProcessOp", "FinalOp"]> +]> { let summary = "Terminates execution of a process."; let description = [{ The `halt` instruction terminates execution of a process. All processes must halt eventually or consist of an infinite loop. }]; - let assemblyFormat = "attr-dict"; } diff --git a/include/circt/Dialect/Moore/MooreOps.td b/include/circt/Dialect/Moore/MooreOps.td index 27413163d204..b0ca268ec8c1 100644 --- a/include/circt/Dialect/Moore/MooreOps.td +++ b/include/circt/Dialect/Moore/MooreOps.td @@ -358,58 +358,103 @@ def NonBlockingAssignOp : AssignOpBase<"nonblocking_assign"> { // Statements //===----------------------------------------------------------------------===// -def None: I32EnumAttrCase<"None", 0, "none">; -/// Transit from 0 to x/z/1, and from x/z to 1. +// Any change on the input. +def AnyChange: I32EnumAttrCase<"AnyChange", 0, "any">; +// A transition from 0 to X/Z/1, or from X/Z to 1. def PosEdge: I32EnumAttrCase<"PosEdge", 1, "posedge">; -/// Transit from 1 to x/z/0, and from x/z to 0. +// A transition from 1 to X/Z/0, or from X/Z to 0. def NegEdge: I32EnumAttrCase<"NegEdge", 2, "negedge">; -/// Include the negedge and posedge. +// The combination of `PosEdge` and `NegEdge`. def BothEdges: I32EnumAttrCase<"BothEdges", 3, "edge">; -def EdgeAtrr: I32EnumAttr<"Edge", "Edge kind", - [None, PosEdge, NegEdge, BothEdges]>{ +def EdgeAttr: I32EnumAttr<"Edge", "Edge kind", + [AnyChange, PosEdge, NegEdge, BothEdges]> { let cppNamespace = "circt::moore"; } -def EventOp : MooreOp<"wait_event", [ - HasParent<"ProcedureOp"> +def WaitEventOp : MooreOp<"wait_event", [ + RecursiveMemoryEffects, + NoRegionArguments, + SingleBlock, + NoTerminator ]> { - let summary = "Detecting posedge and negedge"; + let summary = "Suspend execution until an event occurs"; let description = [{ - It is introduced by the symbol `@`, and it allows statement execution to - be delayed until the occurrence of some simulation event occurring in a - procedure executing concurrently with this procedure. - - For the implicit event control(`@(*)`), there are two situations that are - not automatically added to event expression: - 1. Identifiers that only appear in wait or event expressions. - ``` - always @(*) begin // equivalent to @(b) - @(n) kid = b; // n is not added to @(*) - end - ``` - 2. Identifiers that only appear as a hierarchical_variable_identifier - in the variable_lvalue of the left-hand side of assignments. - ``` - always @(*) begin - a = b + c; // a is not added to @(*) - end - ``` + The `moore.wait_event` op suspends execution of the current process until + its body signals that an event has been the detected. Conceptually, the body + of this op is executed whenever any potentially relevant signal has changed. + If one of the contained `moore.detect_event` ops detect an event, execution + resumes after the `moore.wait_event` operation. If no event is detected, the + current process remains suspended. + + Example corresponding to the SystemVerilog `@(posedge x, negedge y iff z)`: + ``` + moore.wait_event { + %0 = moore.read %x : + %1 = moore.read %y : + %2 = moore.read %z : + moore.detect_event posedge %0 : i1 + moore.detect_event negedge %1 if %2 : i1 + } + ``` - Example: + The body may also contain any operations necessary to evaluate the event + conditions. For example, the SV `@(posedge ~x iff i == 42)`: ``` - @(a, b, c) // none - @(posedge clk) // positive edge - @(negedge clk) // negative edge - @(edge clk) // both edge - @(*) // implicit event control + moore.wait_event { + %0 = moore.read %x : + %1 = moore.not %0 : i1 + %2 = moore.read %i : + %3 = moore.constant 42 : i19 + %4 = moore.eq %2, %3 : i19 + moore.detect_event posedge %0 if %4 : i1 + } ``` + See IEEE 1800-2017 ยง 9.4.2 "Event control". }]; - let arguments = (ins EdgeAtrr:$edge, UnpackedType:$input); - let results = (outs); + let regions = (region SizedRegion<1>:$body); + let assemblyFormat = [{ attr-dict-with-keyword $body }]; +} + +def DetectEventOp : MooreOp<"detect_event", [ + HasParent<"WaitEventOp"> +]> { + let summary = "Check if an event occured within a `wait_event` op"; + let description = [{ + The `moore.detect_event` op is used inside the body of a `moore.wait_event` + to check if an interesting value change has occurred on its operand. The + `moore.detect_event` op implicitly stores the previous value of its operand + and compares it against the current value to detect an interesting edge: + + - `posedge` checks for a low-to-high transition + - `negedge` checks for a high-to-low transition + - `edge` checks for either a `posedge` or a `negedge` + - `any` checks for any value change (including e.g. X to Z) + + The edges are detected as follows: + + - `0` to `1 X Z`: `posedge` + - `1` to `0 X Z`: `negedge` + - `X Z` to `1`: `posedge` + - `X Z` to `0`: `negedge` + + | From | To 0 | To 1 | To X | To Z | + |-------|---------|---------|---------|---------| + | 0 | - | posedge | posedge | posedge | + | 1 | negedge | - | negedge | negedge | + | X | negedge | posedge | - | - | + | Z | negedge | posedge | - | - | + + See IEEE 1800-2017 ยง 9.4.2 "Event control". + }]; + let arguments = (ins + EdgeAttr:$edge, + UnpackedType:$input, + Optional:$condition + ); let assemblyFormat = [{ - $edge $input attr-dict `:` type($input) + $edge $input (`if` $condition^)? attr-dict `:` type($input) }]; } diff --git a/lib/Conversion/ImportVerilog/Expressions.cpp b/lib/Conversion/ImportVerilog/Expressions.cpp index ded3bd8bd8e3..8ae630cc9432 100644 --- a/lib/Conversion/ImportVerilog/Expressions.cpp +++ b/lib/Conversion/ImportVerilog/Expressions.cpp @@ -62,32 +62,6 @@ struct RvalueExprVisitor { return {}; } - /// Helper function to convert a value to its "truthy" boolean value. - Value convertToBool(Value value) { - if (!value) - return {}; - if (auto type = dyn_cast_or_null(value.getType())) - if (type.getBitSize() == 1) - return value; - if (auto type = dyn_cast_or_null(value.getType())) - return builder.create(loc, value); - mlir::emitError(loc, "expression of type ") - << value.getType() << " cannot be cast to a boolean"; - return {}; - } - - /// Helper function to convert a value to its "truthy" boolean value and - /// convert it to the given domain. - Value convertToBool(Value value, Domain domain) { - value = convertToBool(value); - if (!value) - return {}; - auto type = moore::IntType::get(context.getContext(), 1, domain); - if (value.getType() == type) - return value; - return builder.create(loc, type, value); - } - // Handle references to the left-hand side of a parent assignment. Value visit(const slang::ast::LValueReferenceExpression &expr) { assert(!context.lvalueStack.empty() && "parent assignments push lvalue"); @@ -98,8 +72,12 @@ struct RvalueExprVisitor { // Handle named values, such as references to declared variables. Value visit(const slang::ast::NamedValueExpression &expr) { if (auto value = context.valueSymbols.lookup(&expr.symbol)) { - if (isa(value.getType())) - value = builder.create(loc, value); + if (isa(value.getType())) { + auto readOp = builder.create(loc, value); + if (context.rvalueReadCallback) + context.rvalueReadCallback(readOp); + value = readOp.getResult(); + } return value; } @@ -229,7 +207,7 @@ struct RvalueExprVisitor { return createReduction(arg, true); case UnaryOperator::LogicalNot: - arg = convertToBool(arg); + arg = context.convertToBool(arg); if (!arg) return {}; return builder.create(loc, arg); @@ -358,10 +336,10 @@ struct RvalueExprVisitor { case BinaryOperator::LogicalAnd: { // TODO: This should short-circuit. Put the RHS code into a separate // block. - lhs = convertToBool(lhs, domain); + lhs = context.convertToBool(lhs, domain); if (!lhs) return {}; - rhs = convertToBool(rhs, domain); + rhs = context.convertToBool(rhs, domain); if (!rhs) return {}; return builder.create(loc, lhs, rhs); @@ -369,20 +347,20 @@ struct RvalueExprVisitor { case BinaryOperator::LogicalOr: { // TODO: This should short-circuit. Put the RHS code into a separate // block. - lhs = convertToBool(lhs, domain); + lhs = context.convertToBool(lhs, domain); if (!lhs) return {}; - rhs = convertToBool(rhs, domain); + rhs = context.convertToBool(rhs, domain); if (!rhs) return {}; return builder.create(loc, lhs, rhs); } case BinaryOperator::LogicalImplication: { // `(lhs -> rhs)` equivalent to `(!lhs || rhs)`. - lhs = convertToBool(lhs, domain); + lhs = context.convertToBool(lhs, domain); if (!lhs) return {}; - rhs = convertToBool(rhs, domain); + rhs = context.convertToBool(rhs, domain); if (!rhs) return {}; auto notLHS = builder.create(loc, lhs); @@ -390,10 +368,10 @@ struct RvalueExprVisitor { } case BinaryOperator::LogicalEquivalence: { // `(lhs <-> rhs)` equivalent to `(lhs && rhs) || (!lhs && !rhs)`. - lhs = convertToBool(lhs, domain); + lhs = context.convertToBool(lhs, domain); if (!lhs) return {}; - rhs = convertToBool(rhs, domain); + rhs = context.convertToBool(rhs, domain); if (!rhs) return {}; auto notLHS = builder.create(loc, lhs); @@ -671,7 +649,8 @@ struct RvalueExprVisitor { mlir::emitError(loc) << "unsupported conditional expression with pattern"; return {}; } - auto value = convertToBool(context.convertRvalueExpression(*cond.expr)); + auto value = + context.convertToBool(context.convertRvalueExpression(*cond.expr)); if (!value) return {}; auto conditionalOp = builder.create(loc, type, value); @@ -1044,3 +1023,29 @@ Value Context::convertLvalueExpression(const slang::ast::Expression &expr) { return expr.visit(LvalueExprVisitor(*this, loc)); } // NOLINTEND(misc-no-recursion) + +/// Helper function to convert a value to its "truthy" boolean value. +Value Context::convertToBool(Value value) { + if (!value) + return {}; + if (auto type = dyn_cast_or_null(value.getType())) + if (type.getBitSize() == 1) + return value; + if (auto type = dyn_cast_or_null(value.getType())) + return builder.create(value.getLoc(), value); + mlir::emitError(value.getLoc(), "expression of type ") + << value.getType() << " cannot be cast to a boolean"; + return {}; +} + +/// Helper function to convert a value to its "truthy" boolean value and +/// convert it to the given domain. +Value Context::convertToBool(Value value, Domain domain) { + value = convertToBool(value); + if (!value) + return {}; + auto type = moore::IntType::get(getContext(), 1, domain); + if (value.getType() == type) + return value; + return builder.create(value.getLoc(), type, value); +} diff --git a/lib/Conversion/ImportVerilog/ImportVerilogInternals.h b/lib/Conversion/ImportVerilog/ImportVerilogInternals.h index e490b48b03c0..536b5ea84bb7 100644 --- a/lib/Conversion/ImportVerilog/ImportVerilogInternals.h +++ b/lib/Conversion/ImportVerilog/ImportVerilogInternals.h @@ -26,6 +26,8 @@ namespace circt { namespace ImportVerilog { +using moore::Domain; + /// Port lowering information. struct PortLowering { const slang::ast::PortSymbol * @@ -102,8 +104,15 @@ struct Context { Value convertLvalueExpression(const slang::ast::Expression &expr); // Convert a slang timing control into an MLIR timing control. - LogicalResult - convertTimingControl(const slang::ast::TimingControl &timingControl); + LogicalResult convertTimingControl(const slang::ast::TimingControl &ctrl, + const slang::ast::Statement &stmt); + + /// Helper function to convert a value to its "truthy" boolean value. + Value convertToBool(Value value); + + /// Helper function to convert a value to its "truthy" boolean value and + /// convert it to the given domain. + Value convertToBool(Value value, Domain domain); slang::ast::Compilation &compilation; mlir::ModuleOp intoModuleOp; @@ -152,6 +161,12 @@ struct Context { /// part of the loop body statements will use this information to branch to /// the correct block. SmallVector loopStack; + + /// A listener called for every variable or net being read. This can be used + /// to collect all variables read as part of an expression or statement, for + /// example to populate the list of observed signals in an implicit event + /// control `@*`. + std::function rvalueReadCallback; }; } // namespace ImportVerilog diff --git a/lib/Conversion/ImportVerilog/Statements.cpp b/lib/Conversion/ImportVerilog/Statements.cpp index f3fa376012cf..6e674fdeaa4f 100644 --- a/lib/Conversion/ImportVerilog/Statements.cpp +++ b/lib/Conversion/ImportVerilog/Statements.cpp @@ -427,11 +427,7 @@ struct StmtVisitor { // Handle timing control. LogicalResult visit(const slang::ast::TimedStatement &stmt) { - if (failed(context.convertTimingControl(stmt.timing))) - return failure(); - if (failed(context.convertStatement(stmt.stmt))) - return failure(); - return success(); + return context.convertTimingControl(stmt.timing, stmt.stmt); } // Handle return statements. diff --git a/lib/Conversion/ImportVerilog/TimingControls.cpp b/lib/Conversion/ImportVerilog/TimingControls.cpp index 962f8db5e0ee..cf9535e3b975 100644 --- a/lib/Conversion/ImportVerilog/TimingControls.cpp +++ b/lib/Conversion/ImportVerilog/TimingControls.cpp @@ -8,57 +8,197 @@ #include "ImportVerilogInternals.h" #include "slang/ast/TimingControl.h" +#include "llvm/ADT/ScopeExit.h" + using namespace circt; using namespace ImportVerilog; + +static moore::Edge convertEdgeKind(const slang::ast::EdgeKind edge) { + using slang::ast::EdgeKind; + switch (edge) { + case EdgeKind::None: + return moore::Edge::AnyChange; + case EdgeKind::PosEdge: + return moore::Edge::PosEdge; + case EdgeKind::NegEdge: + return moore::Edge::NegEdge; + case EdgeKind::BothEdges: + return moore::Edge::BothEdges; + } + llvm_unreachable("all edge kinds handled"); +} + +// NOLINTBEGIN(misc-no-recursion) namespace { -struct TimingCtrlVisitor { + +// Handle any of the event control constructs. +struct EventControlVisitor { Context &context; Location loc; OpBuilder &builder; - TimingCtrlVisitor(Context &context, Location loc) - : context(context), loc(loc), builder(context.builder) {} - + // Handle single signal events like `posedge x`, `negedge y iff z`, or `w`. LogicalResult visit(const slang::ast::SignalEventControl &ctrl) { - // TODO: When updating slang to the latest version, we will handle - // "iffCondition". - auto loc = context.convertLocation(ctrl.sourceRange.start()); - auto input = context.convertRvalueExpression(ctrl.expr); - builder.create(loc, static_cast(ctrl.edge), - input); - return success(); - } - - LogicalResult visit(const slang::ast::ImplicitEventControl &ctrl) { + auto edge = convertEdgeKind(ctrl.edge); + auto expr = context.convertRvalueExpression(ctrl.expr); + if (!expr) + return failure(); + Value condition; + if (ctrl.iffCondition) { + condition = context.convertRvalueExpression(*ctrl.iffCondition); + condition = context.convertToBool(condition, Domain::TwoValued); + if (!condition) + return failure(); + } + builder.create(loc, edge, expr, condition); return success(); } + // Handle a list of signal events. LogicalResult visit(const slang::ast::EventListControl &ctrl) { - for (auto *event : ctrl.as().events) { - if (failed(context.convertTimingControl(*event))) + for (const auto *event : ctrl.events) { + auto visitor = *this; + visitor.loc = context.convertLocation(event->sourceRange); + if (failed(event->visit(visitor))) return failure(); } return success(); } - /// Emit an error for all other timing controls. + // Emit an error for all other timing controls. template - LogicalResult visit(T &&node) { - mlir::emitError(loc, "unspported timing control: ") - << slang::ast::toString(node.kind); - return failure(); + LogicalResult visit(T &&ctrl) { + return mlir::emitError(loc) + << "unsupported event control: " << slang::ast::toString(ctrl.kind); } +}; - LogicalResult visitInvalid(const slang::ast::TimingControl &ctrl) { - mlir::emitError(loc, "invalid timing control"); - return failure(); +// Handle any of the delay control constructs. +struct DelayControlVisitor { + Context &context; + Location loc; + OpBuilder &builder; + + // Emit an error for all other timing controls. + template + LogicalResult visit(T &&ctrl) { + return mlir::emitError(loc) + << "unsupported delay control: " << slang::ast::toString(ctrl.kind); } }; + } // namespace +// Entry point to timing control handling. This deals with the layer of repeats +// that a timing control may be wrapped in, and also handles the implicit event +// control which may appear at that point. For any event control a `WaitEventOp` +// will be created and populated by `handleEventControl`. Any delay control will +// be handled by `handleDelayControl`. +static LogicalResult handleRoot(Context &context, + const slang::ast::TimingControl &ctrl, + moore::WaitEventOp &implicitWaitOp) { + auto &builder = context.builder; + auto loc = context.convertLocation(ctrl.sourceRange); + + using slang::ast::TimingControlKind; + switch (ctrl.kind) { + // TODO: Actually implement a lowering for repeated event control. The main + // way to trigger this is through an intra-assignment timing control, which + // is not yet supported: + // + // a = repeat(3) @(posedge b) c; + // + // This will want to recursively call this function at the right insertion + // point to handle the timing control being repeated. + case TimingControlKind::RepeatedEvent: + return mlir::emitError(loc) << "unsupported repeated event control"; + + // Handle implicit events, i.e. `@*` and `@(*)`. This implicitly includes + // all variables read within the statement that follows after the event + // control. Since we haven't converted that statement yet, simply create and + // empty wait op and let `Context::convertTimingControl` populate it once + // the statement has been lowered. + case TimingControlKind::ImplicitEvent: + implicitWaitOp = builder.create(loc); + return success(); + + // Handle event control. + case TimingControlKind::SignalEvent: + case TimingControlKind::EventList: { + auto waitOp = builder.create(loc); + OpBuilder::InsertionGuard guard(builder); + builder.setInsertionPointToStart(&waitOp.getBody().emplaceBlock()); + EventControlVisitor visitor{context, loc, builder}; + return ctrl.visit(visitor); + } + + // Handle delay control. + case TimingControlKind::Delay: + case TimingControlKind::Delay3: + case TimingControlKind::OneStepDelay: + case TimingControlKind::CycleDelay: { + DelayControlVisitor visitor{context, loc, builder}; + return ctrl.visit(visitor); + } + + default: + return mlir::emitError(loc, "unsupported timing control: ") + << slang::ast::toString(ctrl.kind); + } +} + LogicalResult -Context::convertTimingControl(const slang::ast::TimingControl &timingControl) { - auto loc = convertLocation(timingControl.sourceRange.start()); - TimingCtrlVisitor visitor{*this, loc}; - return timingControl.visit(visitor); +Context::convertTimingControl(const slang::ast::TimingControl &ctrl, + const slang::ast::Statement &stmt) { + // Convert the timing control. Implicit event control will create a new empty + // `WaitEventOp` and assign it to `implicitWaitOp`. This op will be populated + // further down. + moore::WaitEventOp implicitWaitOp; + { + auto previousCallback = rvalueReadCallback; + auto done = + llvm::make_scope_exit([&] { rvalueReadCallback = previousCallback; }); + // Reads happening as part of the event control should not be added to a + // surrounding implicit event control's list of implicitly observed + // variables. + rvalueReadCallback = nullptr; + if (failed(handleRoot(*this, ctrl, implicitWaitOp))) + return failure(); + } + + // Convert the statement. In case `implicitWaitOp` is set, we register a + // callback to collect all the variables read by the statement into + // `readValues`, such that we can populate the op with implicitly observed + // variables afterwards. + llvm::SmallSetVector readValues; + { + auto previousCallback = rvalueReadCallback; + auto done = + llvm::make_scope_exit([&] { rvalueReadCallback = previousCallback; }); + if (implicitWaitOp) { + rvalueReadCallback = [&](moore::ReadOp readOp) { + readValues.insert(readOp.getInput()); + if (previousCallback) + previousCallback(readOp); + }; + } + if (failed(convertStatement(stmt))) + return failure(); + } + + // Populate the implicit wait op with reads from the variables read by the + // statement. + if (implicitWaitOp) { + OpBuilder::InsertionGuard guard(builder); + builder.setInsertionPointToStart(&implicitWaitOp.getBody().emplaceBlock()); + for (auto readValue : readValues) { + auto value = + builder.create(implicitWaitOp.getLoc(), readValue); + builder.create( + implicitWaitOp.getLoc(), moore::Edge::AnyChange, value, Value{}); + } + } + + return success(); } +// NOLINTEND(misc-no-recursion) diff --git a/lib/Conversion/MooreToCore/MooreToCore.cpp b/lib/Conversion/MooreToCore/MooreToCore.cpp index d0cce3c1551c..6a3470049dd7 100644 --- a/lib/Conversion/MooreToCore/MooreToCore.cpp +++ b/lib/Conversion/MooreToCore/MooreToCore.cpp @@ -34,6 +34,7 @@ using namespace circt; using namespace moore; using comb::ICmpPredicate; +using llvm::SmallDenseSet; namespace { @@ -102,6 +103,30 @@ static hw::ModulePortInfo getModulePortInfo(const TypeConverter &typeConverter, return hw::ModulePortInfo(inputs, outputs); } +/// Erase all dead blocks in a region. +static void eraseDeadBlocks(PatternRewriter &rewriter, Region ®ion) { + SmallVector worklist; + for (auto &block : llvm::make_early_inc_range(llvm::drop_begin(region, 1))) { + if (!block.use_empty()) + continue; + worklist.push_back(&block); + while (!worklist.empty()) { + auto *block = worklist.pop_back_val(); + if (!block->use_empty()) + continue; + for (auto *successor : block->getSuccessors()) + worklist.push_back(successor); + rewriter.eraseBlock(block); + } + } +} + +/// Erase all dead blocks in an op. +static void eraseDeadBlocks(PatternRewriter &rewriter, Operation *op) { + for (auto ®ion : op->getRegions()) + eraseDeadBlocks(rewriter, region); +} + //===----------------------------------------------------------------------===// // Structural Conversion //===----------------------------------------------------------------------===// @@ -169,142 +194,240 @@ struct ProcedureOpConversion : public OpConversionPattern { LogicalResult matchAndRewrite(ProcedureOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override { - Location loc = op.getLoc(); - auto procOp = rewriter.create(loc); - - // TODO: properly handle the procedure kind attribute - if (op.getKind() != ProcedureKind::Always) - return rewriter.notifyMatchFailure(loc, "not yet supported"); - - // Collect all event ops in the procedure. - SmallVector events(op.getOps()); + auto loc = op.getLoc(); + if (failed(rewriter.convertRegionTypes(&op.getBody(), *typeConverter))) + return failure(); + eraseDeadBlocks(rewriter, op); + + // Handle initial and final procedures. These lower to a corresponding + // `llhd.process` or `llhd.final` op that executes the body and then halts. + if (op.getKind() == ProcedureKind::Initial || + op.getKind() == ProcedureKind::Final) { + Operation *newOp; + if (op.getKind() == ProcedureKind::Initial) + newOp = rewriter.create(loc); + else + newOp = rewriter.create(loc); + auto &body = newOp->getRegion(0); + rewriter.inlineRegionBefore(op.getBody(), body, body.end()); + for (auto returnOp : + llvm::make_early_inc_range(body.getOps())) { + rewriter.setInsertionPoint(returnOp); + rewriter.replaceOpWithNewOp(returnOp); + } + rewriter.eraseOp(op); + return success(); + } - auto *entry = rewriter.createBlock(&procOp.getBody()); - auto *wait = rewriter.createBlock(&procOp.getBody()); - auto *check = rewriter.createBlock(&procOp.getBody()); + // All other procedures lower to a an `llhd.process`. + auto newOp = rewriter.create(loc); // We need to add an empty entry block because it is not allowed in MLIR to // branch back to the entry block. Instead we put the logic in the second // block and branch to that. - rewriter.setInsertionPointToStart(entry); - rewriter.create(loc, wait); - - // The block in which we can sample the past and where the wait terminator - // resides. - rewriter.setInsertionPointToStart(wait); - - auto getSignal = [&](Value input) -> Value { - // If the read op input is defined outside and before the procedure - // operation, we can get the remapped value directly. - Value signal = rewriter.getRemappedValue(input); - - // Otherwise, it hasn't been converted yet, so we take the old one and - // insert a cast. - if (!signal) { - Type convertedType = typeConverter->convertType(input.getType()); - assert(convertedType && - "if the input has not been converted yet, it should have a " - "moore type and a valid type conversion"); - signal = - rewriter - .create(loc, convertedType, input) - ->getResult(0); - } - - return signal; - }; + rewriter.createBlock(&newOp.getBody()); + auto *block = &op.getBody().front(); + rewriter.create(loc, block); + rewriter.inlineRegionBefore(op.getBody(), newOp.getBody(), + newOp.getBody().end()); + + // Add special handling for `always_comb` and `always_latch` procedures. + // These run once at simulation startup and then implicitly wait for any of + // the values they access to change before running again. To implement this, + // we create another basic block that contains the implicit wait, and make + // all `moore.return` ops branch to that wait block instead of immediately + // jumping back up to the body. + if (op.getKind() == ProcedureKind::AlwaysComb || + op.getKind() == ProcedureKind::AlwaysLatch) { + block = rewriter.createBlock(&newOp.getBody()); + // TODO: Collect observed signals and add LLHD wait op. + return failure(); + } - // All signals to observe in the `llhd.wait` operation. - SmallVector toObserve; - DenseSet alreadyObserved; - // If there are no event operations in the procedure, it's a combinational - // one. Thus we need to collect all signals used. - if (events.empty()) { - op->walk([&](Operation *operation) { - for (auto &operand : operation->getOpOperands()) { - Value value = getSignal(operand.get()); - auto memOp = dyn_cast(operation); - if (!memOp) - return; - - // The process is only sensitive to values that are read. - if (isa(operand.get().getType()) && - memOp.getEffectOnValue(operand.get()) - .has_value()) { - if (!alreadyObserved.contains(value)) - toObserve.push_back(value); - - alreadyObserved.insert(value); - } - } - }); + // Make all `moore.return` ops branch back up to the beginning of the + // process, or the wait block created above for `always_comb` and + // `always_latch` procedures. + for (auto returnOp : llvm::make_early_inc_range(newOp.getOps())) { + rewriter.setInsertionPoint(returnOp); + rewriter.create(loc, block); + rewriter.eraseOp(returnOp); } - // Forall edge triggered events, probe the old value - SmallVector oldValues(events.size(), Value()); - for (auto [i, event] : llvm::enumerate(events)) { - auto readOp = event.getInput().getDefiningOp(); - if (!readOp) - return failure(); + rewriter.eraseOp(op); + return success(); + } +}; - Value signal = getSignal(readOp.getInput()); - toObserve.push_back(signal); +struct WaitEventOpConversion : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; - // Non-edge triggered events only need the value in the present - if (event.getEdge() != Edge::None) - oldValues[i] = rewriter.create(loc, signal); + LogicalResult + matchAndRewrite(WaitEventOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + // In order to convert the `wait_event` op we need to create three separate + // blocks at the location of the op: + // + // - A "wait" block that reads the current state of any values used to + // detect events and then waits until any of those values change. When a + // change occurs, control transfers to the "check" block. + // - A "check" block which is executed after any interesting signal has + // changed. This is where any `detect_event` ops read the current state of + // interesting values and compare them against their state before the wait + // in order to detect an event. If any events were detected, control + // transfers to the "resume" block; otherwise control goes back to the + // "wait" block. + // - A "resume" block which holds any ops after the `wait_event` op. This is + // where control is expected to resume after an event has happened. + // + // Block structure before: + // opA + // moore.wait_event { ... } + // opB + // + // Block structure after: + // opA + // cf.br ^wait + // ^wait: + // + // llhd.wait ^check, ... + // ^check: + // + // + // cf.cond_br %event, ^resume, ^wait + // ^resume: + // opB + auto *resumeBlock = + rewriter.splitBlock(op->getBlock(), ++Block::iterator(op)); + auto *waitBlock = rewriter.createBlock(resumeBlock); + auto *checkBlock = rewriter.createBlock(resumeBlock); + + auto loc = op.getLoc(); + rewriter.setInsertionPoint(op); + rewriter.create(loc, waitBlock); + + // We need to inline two copies of the `wait_event`'s body region: one is + // used to determine the values going into `detect_event` ops before the + // `llhd.wait`, and one will do the actual event detection after the + // `llhd.wait`. + // + // Create a copy of the entire `wait_event` op in the wait block, which also + // creates a copy of its region. Take note of all inputs to `detect_event` + // ops and delete the `detect_event` ops in this copy. + SmallVector valuesBefore; + rewriter.setInsertionPointToEnd(waitBlock); + auto clonedOp = cast(rewriter.clone(*op)); + for (auto detectOp : + llvm::make_early_inc_range(clonedOp.getOps())) { + valuesBefore.push_back(detectOp.getInput()); + rewriter.eraseOp(detectOp); } - rewriter.create(loc, toObserve, Value(), ValueRange{}, check); - rewriter.setInsertionPointToStart(check); + // Determine the values used during event detection that are defined outside + // the `wait_event`'s body region. We want to wait for a change on these + // signals before we check if any interesting event happened. + SmallVector observeValues; + SmallDenseSet alreadyObserved; + clonedOp.getBody().walk([&](Operation *operation) { + for (auto value : operation->getOperands()) { + if (clonedOp.getBody().isAncestor(value.getParentRegion())) + continue; + if (!alreadyObserved.insert(value).second) + continue; + if (auto remapped = rewriter.getRemappedValue(value)) { + observeValues.push_back(remapped); + } else { + auto type = typeConverter->convertType(value.getType()); + auto converted = typeConverter->materializeTargetConversion( + rewriter, loc, type, value); + observeValues.push_back(converted); + } + } + }); + + // Create the `llhd.wait` op that suspends the current process and waits for + // a change in the interesting values listed in `observeValues`. When a + // change is detected, execution resumes in the "check" block. + auto waitOp = rewriter.create(loc, observeValues, Value(), + ValueRange{}, checkBlock); + rewriter.inlineBlockBefore(&clonedOp.getBody().front(), waitOp); + rewriter.eraseOp(clonedOp); + + // Collect a list of all detect ops and inline the `wait_event` body into + // the check block. + SmallVector detectOps(op.getBody().getOps()); + rewriter.inlineBlockBefore(&op.getBody().front(), checkBlock, + checkBlock->end()); + rewriter.eraseOp(op); + + // Helper function to detect if a certain change occurred between a value + // before the `llhd.wait` and after. + auto computeTrigger = [&](Value before, Value after, Edge edge) -> Value { + before = typeConverter->materializeTargetConversion( + rewriter, loc, rewriter.getI1Type(), before); + after = typeConverter->materializeTargetConversion( + rewriter, loc, rewriter.getI1Type(), after); + + if (edge == Edge::AnyChange) + return rewriter.create(loc, ICmpPredicate::ne, before, + after, true); - if (events.empty()) { - rewriter.create(loc, &op.getBody().front()); - } else { SmallVector disjuncts; - for (auto [i, signal, event] : llvm::enumerate(toObserve, events)) { - if (event.getEdge() == Edge::None) - disjuncts.push_back(rewriter.create(loc, signal)); - - if (event.getEdge() == Edge::PosEdge || - event.getEdge() == Edge::BothEdges) { - Value currVal = rewriter.create(loc, signal); - Value trueVal = rewriter.create(loc, APInt(1, 1)); - Value notOldVal = - rewriter.create(loc, oldValues[i], trueVal); - Value posedge = rewriter.create(loc, notOldVal, currVal); - disjuncts.push_back(posedge); - } - if (event.getEdge() == Edge::NegEdge || - event.getEdge() == Edge::BothEdges) { - Value currVal = rewriter.create(loc, signal); - Value trueVal = rewriter.create(loc, APInt(1, 1)); - Value notCurrVal = - rewriter.create(loc, currVal, trueVal); - Value posedge = - rewriter.create(loc, oldValues[i], notCurrVal); - disjuncts.push_back(posedge); - } + Value trueVal = rewriter.create(loc, APInt(1, 1)); + + if (edge == Edge::PosEdge || edge == Edge::BothEdges) { + Value notOldVal = + rewriter.create(loc, before, trueVal, true); + Value posedge = + rewriter.create(loc, notOldVal, after, true); + disjuncts.push_back(posedge); } - Value isValid = rewriter.create(loc, disjuncts, false); - rewriter.create(loc, isValid, &op.getBody().front(), - wait); - } + if (edge == Edge::NegEdge || edge == Edge::BothEdges) { + Value notCurrVal = + rewriter.create(loc, after, trueVal, true); + Value posedge = + rewriter.create(loc, before, notCurrVal, true); + disjuncts.push_back(posedge); + } - for (auto event : events) - rewriter.eraseOp(event); + return rewriter.createOrFold(loc, disjuncts, true); + }; - rewriter.inlineRegionBefore(op.getBody(), procOp.getBody(), - procOp.getBody().end()); + // Convert all `detect_event` ops into a check for the corresponding event + // between the value before and after the `llhd.wait`. The "before" value + // has been collected into `valuesBefore` in the "wait" block; the "after" + // value corresponds to the detect op's input. + SmallVector triggers; + for (auto [detectOp, before] : llvm::zip(detectOps, valuesBefore)) { + // TODO: Support multi-bit values. Edge detection occurs per-bit. + if (auto intType = dyn_cast(before.getType()); + !intType || intType.getWidth() != 1) + return detectOp->emitError() << "requires single bit operand"; + + rewriter.setInsertionPoint(detectOp); + auto trigger = + computeTrigger(before, detectOp.getInput(), detectOp.getEdge()); + if (detectOp.getCondition()) { + auto condition = typeConverter->materializeTargetConversion( + rewriter, loc, rewriter.getI1Type(), detectOp.getCondition()); + trigger = rewriter.create(loc, trigger, condition, true); + } + triggers.push_back(trigger); + rewriter.eraseOp(detectOp); + } - for (auto returnOp : procOp.getOps()) { - rewriter.setInsertionPoint(returnOp); - rewriter.create(loc, wait); - rewriter.eraseOp(returnOp); + // If any `detect_event` op detected an event, branch to the "resume" block + // which contains any code after the `wait_event` op. If no events were + // detected, branch back to the "wait" block to wait for the next change on + // the interesting signals. + rewriter.setInsertionPointToEnd(checkBlock); + if (!triggers.empty()) { + auto triggered = rewriter.createOrFold(loc, triggers, true); + rewriter.create(loc, triggered, resumeBlock, waitBlock); + } else { + rewriter.create(loc, waitBlock); } - rewriter.eraseOp(op); return success(); } }; @@ -1133,9 +1256,11 @@ static void populateTypeConversion(TypeConverter &typeConverter) { [&](mlir::OpBuilder &builder, mlir::Type resultType, mlir::ValueRange inputs, mlir::Location loc) -> std::optional { - if (inputs.size() != 1) + if (inputs.size() != 1 || !inputs[0]) return std::nullopt; - return inputs[0]; + return builder + .create(loc, resultType, inputs[0]) + .getResult(0); }); typeConverter.addSourceMaterialization( @@ -1199,7 +1324,7 @@ static void populateOpConversion(RewritePatternSet &patterns, CaseXZEqOpConversion, // Patterns of structural operations. - SVModuleOpConversion, InstanceOpConversion, ProcedureOpConversion, + SVModuleOpConversion, InstanceOpConversion, ProcedureOpConversion, WaitEventOpConversion, // Patterns of shifting operations. ShrOpConversion, ShlOpConversion, AShrOpConversion, diff --git a/test/Conversion/ImportVerilog/basic.sv b/test/Conversion/ImportVerilog/basic.sv index d26b65f9772a..b5b0085ef92d 100644 --- a/test/Conversion/ImportVerilog/basic.sv +++ b/test/Conversion/ImportVerilog/basic.sv @@ -235,9 +235,12 @@ endmodule // CHECK-LABEL: func.func private @dummyA( // CHECK-LABEL: func.func private @dummyB( // CHECK-LABEL: func.func private @dummyC( +// CHECK-LABEL: func.func private @dummyD( function void dummyA(); endfunction function void dummyB(); endfunction function void dummyC(); endfunction +function void dummyD(int a); endfunction +function bit dummyE(bit a); return a; endfunction // CHECK-LABEL: func.func private @ConditionalStatements( // CHECK-SAME: %arg0: !moore.i1 @@ -1523,53 +1526,6 @@ module PortsUnconnected( // CHECK: moore.output [[D_READ]], [[E_READ]] : !moore.l1, !moore.l1 endmodule -// CHECK-LABEL: moore.module @EventControl(in %clk : !moore.l1) -module EventControl(input clk); - // CHECK: %clk_0 = moore.net name "clk" wire : - - int a1, a2, b, c; - - // CHECK: moore.procedure always - // CHECK: [[CLK_READ:%.+]] = moore.read %clk_0 - // CHECK: moore.wait_event posedge [[CLK_READ]] : l1 - always @(posedge clk) begin end; - - // CHECK: moore.procedure always - // CHECK: [[CLK_READ:%.+]] = moore.read %clk_0 - // CHECK: moore.wait_event negedge [[CLK_READ]] : l1 - always @(negedge clk) begin end; - - // CHECK: moore.procedure always - // CHECK: [[CLK_READ:%.+]] = moore.read %clk_0 - // CHECK: moore.wait_event edge [[CLK_READ]] : l1 - always @(edge clk) begin end; - - // CHECK: moore.procedure always { - // CHECK: [[B_READ:%.+]] = moore.read %b - // CHECK: moore.wait_event none [[B_READ]] : i32 - // CHECK: [[C_READ:%.+]] = moore.read %c - // CHECK: moore.wait_event none [[C_READ]] : i32 - always @(b, c) begin - // CHECK: [[B_READ:%.+]] = moore.read %b - // CHECK: [[C_READ:%.+]] = moore.read %c - // CHECK: [[ADD:%.+]] = moore.add [[B_READ]], [[C_READ]] : i32 - // CHECK: moore.blocking_assign %a1, [[ADD]] : i32 - a1 = b + c; - end; - - // CHECK: moore.procedure always - always @(*) begin - // CHECK: [[B_READ:%.+]] = moore.read %b - // CHECK: [[C_READ:%.+]] = moore.read %c - // CHECK: [[ADD:%.+]] = moore.add [[B_READ]], [[C_READ]] : i32 - // CHECK: moore.blocking_assign %a2, [[ADD]] : i32 - a2 = b + c; - end - - // CHECK: moore.assign %clk_0, %clk : l1 - // CHECK: moore.output -endmodule - // CHECK-LABEL: moore.module @GenerateConstructs() module GenerateConstructs; genvar i; @@ -1731,3 +1687,220 @@ function void ConvertConditionalExprsToResultType(bit [15:0] x, struct packed { // CHECK: } r = z ? y : x; endfunction + +// CHECK-LABEL: func.func private @ImplicitEventControl( +// CHECK-SAME: [[X:%[^:]+]]: !moore.ref +// CHECK-SAME: [[Y:%[^:]+]]: !moore.ref +task automatic ImplicitEventControl(ref int x, ref int y); + // CHECK: moore.wait_event { + // CHECK: } + // CHECK: call @dummyA() + @* dummyA(); + + // CHECK: moore.wait_event { + // CHECK: [[TMP:%.+]] = moore.read [[X]] + // CHECK: moore.detect_event any [[TMP]] + // CHECK: } + // CHECK: [[TMP:%.+]] = moore.read [[X]] + // CHECK: call @dummyD([[TMP]]) + @* dummyD(x); + + // CHECK: moore.wait_event { + // CHECK: [[TMP:%.+]] = moore.read [[X]] + // CHECK: moore.detect_event any [[TMP]] + // CHECK: [[TMP:%.+]] = moore.read [[Y]] + // CHECK: moore.detect_event any [[TMP]] + // CHECK: } + // CHECK: [[TMP1:%.+]] = moore.read [[X]] + // CHECK: [[TMP2:%.+]] = moore.read [[Y]] + // CHECK: [[TMP3:%.+]] = moore.add [[TMP1]], [[TMP2]] + // CHECK: call @dummyD([[TMP3]]) + @* dummyD(x + y); +endtask + +// CHECK-LABEL: func.func private @SignalEventControl( +// CHECK-SAME: [[X:%[^:]+]]: !moore.ref +// CHECK-SAME: [[Y:%[^:]+]]: !moore.ref +// CHECK-SAME: [[U:%[^:]+]]: !moore.ref +// CHECK-SAME: [[V:%[^:]+]]: !moore.ref +task automatic SignalEventControl(ref int x, ref int y, ref bit u, ref logic v); + // CHECK: moore.wait_event { + // CHECK: [[TMP:%.+]] = moore.read [[X]] + // CHECK: moore.detect_event any [[TMP]] + // CHECK: } + // CHECK: call @dummyA() + @x dummyA(); + + // CHECK: moore.wait_event { + // CHECK: [[TMP:%.+]] = moore.read [[X]] + // CHECK: moore.detect_event any [[TMP]] + // CHECK: } + // CHECK: call @dummyA() + @(x) dummyA(); + + // CHECK: moore.wait_event { + // CHECK: [[TMP:%.+]] = moore.read [[X]] + // CHECK: moore.detect_event posedge [[TMP]] + // CHECK: } + // CHECK: call @dummyA() + @(posedge x) dummyA(); + + // CHECK: moore.wait_event { + // CHECK: [[TMP:%.+]] = moore.read [[X]] + // CHECK: moore.detect_event negedge [[TMP]] + // CHECK: } + // CHECK: call @dummyA() + @(negedge x) dummyA(); + + // CHECK: moore.wait_event { + // CHECK: [[TMP:%.+]] = moore.read [[X]] + // CHECK: moore.detect_event edge [[TMP]] + // CHECK: } + // CHECK: call @dummyA() + @(edge x) dummyA(); + + // CHECK: moore.wait_event { + // CHECK: [[TMP1:%.+]] = moore.read [[X]] + // CHECK: [[TMP2:%.+]] = moore.read [[U]] + // CHECK: moore.detect_event posedge [[TMP1]] if [[TMP2]] + // CHECK: } + // CHECK: call @dummyA() + @(posedge x iff u) dummyA(); + + // CHECK: moore.wait_event { + // CHECK: [[TMP1:%.+]] = moore.read [[X]] + // CHECK: [[TMP2:%.+]] = moore.read [[V]] : + // CHECK: [[TMP3:%.+]] = moore.conversion [[TMP2]] : !moore.l1 -> !moore.i1 + // CHECK: moore.detect_event posedge [[TMP1]] if [[TMP3]] + // CHECK: } + // CHECK: call @dummyA() + @(posedge x iff v) dummyA(); + + // CHECK: moore.wait_event { + // CHECK: [[TMP1:%.+]] = moore.read [[X]] + // CHECK: [[TMP2:%.+]] = moore.read [[Y]] + // CHECK: [[TMP3:%.+]] = moore.bool_cast [[TMP2]] : i32 -> i1 + // CHECK: moore.detect_event posedge [[TMP1]] if [[TMP3]] + // CHECK: } + // CHECK: call @dummyA() + @(posedge x iff y) dummyA(); + + // CHECK: moore.wait_event { + // CHECK: [[TMP:%.+]] = moore.read [[X]] + // CHECK: moore.detect_event any [[TMP]] + // CHECK: [[TMP:%.+]] = moore.read [[Y]] + // CHECK: moore.detect_event any [[TMP]] + // CHECK: } + // CHECK: call @dummyA() + @(x or y) dummyA(); + + // CHECK: moore.wait_event { + // CHECK: [[TMP:%.+]] = moore.read [[X]] + // CHECK: moore.detect_event any [[TMP]] + // CHECK: [[TMP:%.+]] = moore.read [[Y]] + // CHECK: moore.detect_event any [[TMP]] + // CHECK: } + // CHECK: call @dummyA() + @(x, y) dummyA(); + + // CHECK: moore.wait_event { + // CHECK: [[TMP1:%.+]] = moore.read [[X]] + // CHECK: [[TMP2:%.+]] = moore.read [[U]] + // CHECK: moore.detect_event posedge [[TMP1]] if [[TMP2]] + // CHECK: [[TMP1:%.+]] = moore.read [[Y]] + // CHECK: [[TMP2:%.+]] = moore.read [[V]] + // CHECK: [[TMP3:%.+]] = moore.conversion [[TMP2]] : !moore.l1 -> !moore.i1 + // CHECK: moore.detect_event negedge [[TMP1]] if [[TMP3]] + // CHECK: } + // CHECK: call @dummyA() + @(posedge x iff u, negedge y iff v) dummyA(); +endtask + +// CHECK-LABEL: func.func private @ImplicitEventControlExamples( +task automatic ImplicitEventControlExamples(); + // Taken from IEEE 1800-2017 section 9.4.2.2 "Implicit event_expression list". + bit a, b, c, d, f, y, tmp1, tmp2; + int x; + + // Example 1 + // CHECK: moore.wait_event { + // CHECK: [[TMP:%.+]] = moore.read %a + // CHECK: moore.detect_event any [[TMP]] + // CHECK: [[TMP:%.+]] = moore.read %b + // CHECK: moore.detect_event any [[TMP]] + // CHECK: [[TMP:%.+]] = moore.read %c + // CHECK: moore.detect_event any [[TMP]] + // CHECK: [[TMP:%.+]] = moore.read %d + // CHECK: moore.detect_event any [[TMP]] + // CHECK: [[TMP:%.+]] = moore.read %f + // CHECK: moore.detect_event any [[TMP]] + // CHECK: } + @(*) y = (a & b) | (c & d) | dummyE(f); // equivalent to @(a, b, c, d, f) + + // Example 2 + // CHECK: moore.wait_event { + // CHECK: [[TMP:%.+]] = moore.read %a + // CHECK: moore.detect_event any [[TMP]] + // CHECK: [[TMP:%.+]] = moore.read %b + // CHECK: moore.detect_event any [[TMP]] + // CHECK: [[TMP:%.+]] = moore.read %c + // CHECK: moore.detect_event any [[TMP]] + // CHECK: [[TMP:%.+]] = moore.read %d + // CHECK: moore.detect_event any [[TMP]] + // CHECK: [[TMP:%.+]] = moore.read %tmp1 + // CHECK: moore.detect_event any [[TMP]] + // CHECK: [[TMP:%.+]] = moore.read %tmp2 + // CHECK: moore.detect_event any [[TMP]] + // CHECK: } + @* begin // equivalent to @(a, b, c, d, tmp1, tmp2) + tmp1 = a & b; + tmp2 = c & d; + y = tmp1 | tmp2; + end + + // Example 3 + // CHECK: moore.wait_event { + // CHECK: [[TMP:%.+]] = moore.read %b + // CHECK: moore.detect_event any [[TMP]] + // CHECK: } + // CHECK: moore.wait_event { + // CHECK: [[TMP:%.+]] = moore.read %a + // CHECK: moore.detect_event any [[TMP]] + // CHECK: } + @* begin // equivalent to @(b) + @(a) y = b; // a inside @(a) is not added to outer @* + end + + // Example 4 + // CHECK: moore.wait_event { + // CHECK: [[TMP:%.+]] = moore.read %a + // CHECK: moore.detect_event any [[TMP]] + // CHECK: [[TMP:%.+]] = moore.read %b + // CHECK: moore.detect_event any [[TMP]] + // CHECK: [[TMP:%.+]] = moore.read %c + // CHECK: moore.detect_event any [[TMP]] + // CHECK: [[TMP:%.+]] = moore.read %d + // CHECK: moore.detect_event any [[TMP]] + // CHECK: } + // CHECK: moore.wait_event { + // CHECK: [[TMP:%.+]] = moore.read %c + // CHECK: moore.detect_event any [[TMP]] + // CHECK: [[TMP:%.+]] = moore.read %d + // CHECK: moore.detect_event any [[TMP]] + // CHECK: } + @* begin // equivalent to @(a, b, c, d) + y = a ^ b; + @* y = c ^ d; // equivalent to @(c, d) + end + + // Example 5 + // CHECK: moore.wait_event { + // CHECK: [[TMP:%.+]] = moore.read %a + // CHECK: moore.detect_event any [[TMP]] + // CHECK: [[TMP:%.+]] = moore.read %b + // CHECK: moore.detect_event any [[TMP]] + // CHECK: } + @* begin // equivalent to @(a, b) + x[a] = !b; + end +endtask diff --git a/test/Conversion/MooreToCore/basic.mlir b/test/Conversion/MooreToCore/basic.mlir index c20655a8ce5c..f3d89bf635b5 100644 --- a/test/Conversion/MooreToCore/basic.mlir +++ b/test/Conversion/MooreToCore/basic.mlir @@ -306,6 +306,20 @@ func.func @AdvancedConversion(%arg0: !moore.array<5 x struct<{exp_bits: i32, man return %1, %2 : !moore.array<5 x struct<{exp_bits: i32, man_bits: i32}>>, !moore.i320 } +// CHECK-LABEL: func @Statements +func.func @Statements(%arg0: !moore.i42) { + // CHECK: %x = llhd.sig + %x = moore.variable : + // CHECK: [[TMP:%.+]] = llhd.constant_time <0ns, 0d, 1e> + // CHECK: llhd.drv %x, %arg0 after [[TMP]] : !hw.inout + moore.blocking_assign %x, %arg0 : i42 + // CHECK: [[TMP:%.+]] = llhd.constant_time <0ns, 1d, 0e> + // CHECK: llhd.drv %x, %arg0 after [[TMP]] : !hw.inout + moore.nonblocking_assign %x, %arg0 : i42 + // CHECK-NEXT: return + return +} + // CHECK-LABEL: hw.module @InstanceNull() { moore.module @InstanceNull() { @@ -425,134 +439,6 @@ moore.module @Struct(in %a : !moore.i32, in %b : !moore.i32, in %arg0 : !moore.s moore.output %0, %3, %4 : !moore.i32, !moore.struct<{exp_bits: i32, man_bits: i32}>, !moore.struct<{exp_bits: i32, man_bits: i32}> } -// CHECK-LABEL: hw.module @Process -moore.module @Process(in %cond : i1) { - // CHECK: [[B:%b]] = llhd.sig - // CHECK: [[C:%c]] = llhd.sig - // CHECK: [[D:%d]] = llhd.sig - // CHECK: [[E:%e]] = llhd.sig - %b = moore.variable : - %c = moore.variable : - %d = moore.variable : - %e = moore.variable : - - // CHECK: llhd.process - moore.procedure always { - // CHECK: cf.br ^[[BB1:.+]] - // CHECK: ^[[BB1]]: - // CHECK: [[C_OLD:%.+]] = llhd.prb [[C]] - // CHECK: llhd.wait ([[B]], [[C]] : !hw.inout, !hw.inout), ^[[BB2:.+]] - // CHECK: ^[[BB2]]: - // CHECK: [[B_CURR:%.+]] = llhd.prb [[B]] - // CHECK: [[C_CURR:%.+]] = llhd.prb [[C]] - // CHECK: [[NOT_C_OLD:%.+]] = comb.xor [[C_OLD]], %true - // CHECK: [[POSEDGE:%.+]] = comb.and [[NOT_C_OLD]], [[C_CURR]] : i1 - // CHECK: [[PROCEED:%.+]] = comb.or [[B_CURR]], [[POSEDGE]] : i1 - // CHECK: cf.cond_br [[PROCEED]], ^[[BB3:.+]], ^[[BB1]] - // CHECK: ^[[BB3]]: - // CHECK: [[B_PRB:%.+]] = llhd.prb [[B]] - // CHECK: [[C_PRB:%.+]] = llhd.prb [[C]] - // CHECK: [[RES:%.+]] = comb.add [[B_PRB]], [[C_PRB]] : i1 - // CHECK: [[T0:%.+]] = llhd.constant_time <0ns, 1d, 0e> - // CHECK: llhd.drv [[D]], [[RES]] after [[T0]] - // CHECK: [[T1:%.+]] = llhd.constant_time <0ns, 0d, 1e> - // CHECK: llhd.drv [[E]], [[RES]] after [[T1]] - // CHECK: cf.br ^[[BB1]] - %br = moore.read %b : - moore.wait_event none %br : i1 - %cr = moore.read %c : - moore.wait_event posedge %cr : i1 - - %0 = moore.add %br, %cr : i1 - - moore.nonblocking_assign %d, %0 : i1 - moore.blocking_assign %e, %0 : i1 - moore.return - } - - // CHECK: llhd.process - moore.procedure always { - // CHECK: cf.br ^[[BB1:.+]] - // CHECK: ^[[BB1]]: - // CHECK: llhd.wait ([[B]], [[C]] : !hw.inout, !hw.inout), ^[[BB2:.+]] - // CHECK: ^[[BB2]]: - // CHECK: cf.br ^[[BB3:.+]] - // CHECK: ^[[BB3]]: - // CHECK: [[B_PRB:%.+]] = llhd.prb [[B]] - // CHECK: [[C_PRB:%.+]] = llhd.prb [[C]] - // CHECK: [[RES:%.+]] = comb.add [[B_PRB]], [[C_PRB]] : i1 - // CHECK: [[T0:%.+]] = llhd.constant_time <0ns, 1d, 0e> - // CHECK: llhd.drv [[D]], [[RES]] after [[T0]] - // CHECK: cf.br ^[[BB1]] - %br = moore.read %b : - %cr = moore.read %c : - %0 = moore.add %br, %cr : i1 - moore.nonblocking_assign %d, %0 : i1 - moore.return - } - - // CHECK: llhd.process - moore.procedure always { - // CHECK: cf.br ^[[BB1:.+]] - // CHECK: ^[[BB1]]: - // CHECK: [[C_OLD:%.+]] = llhd.prb [[C]] - // CHECK: llhd.wait ([[C]] : !hw.inout), ^[[BB2:.+]] - // CHECK: ^[[BB2]]: - // CHECK: [[C_CURR:%.+]] = llhd.prb [[C]] - // CHECK: [[NOT_C_OLD:%.+]] = comb.xor [[C_OLD]], %true - // CHECK: [[POSEDGE:%.+]] = comb.and [[NOT_C_OLD]], [[C_CURR]] : i1 - // CHECK: [[C_CURR1:%.+]] = llhd.prb [[C]] - // CHECK: [[NOT_C_CURR:%.+]] = comb.xor [[C_CURR1]], %true - // CHECK: [[NEGEDGE:%.+]] = comb.and [[C_OLD]], [[NOT_C_CURR]] : i1 - // CHECK: [[PROCEED:%.+]] = comb.or [[POSEDGE]], [[NEGEDGE]] : i1 - // CHECK: cf.cond_br [[PROCEED]], ^[[BB3:.+]], ^[[BB1]] - // CHECK: ^[[BB3]]: - // CHECK: [[RES:%.+]] = comb.add [[B_PRB:%.+]], [[C_PRB:%.+]] : i1 - // CHECK: [[T0:%.+]] = llhd.constant_time <0ns, 1d, 0e> - // CHECK: llhd.drv [[D]], [[RES]] after [[T0]] - // CHECK: cf.br ^[[BB1]] - moore.wait_event edge %cr : i1 - %0 = moore.add %br, %cr : i1 - moore.nonblocking_assign %d, %0 : i1 - moore.return - } - - // CHECK: [[B_PRB]] = llhd.prb [[B]] - // CHECK: [[C_PRB]] = llhd.prb [[C]] - %br = moore.read %b : - %cr = moore.read %c : - // CHECK: llhd.process - moore.procedure always { - // CHECK: cf.br ^[[BB1:.+]] - // CHECK: ^[[BB1]]: - // CHECK: [[C_OLD:%.+]] = llhd.prb [[C]] - // CHECK: llhd.wait ([[C]] : !hw.inout), ^[[BB2:.+]] - // CHECK: ^[[BB2]]: - // CHECK: [[C_CURR:%.+]] = llhd.prb [[C]] - // CHECK: [[NOT_C_CURR:%.+]] = comb.xor [[C_CURR]], %true - // CHECK: [[NEGEDGE:%.+]] = comb.and [[C_OLD]], [[NOT_C_CURR]] : i1 - // CHECK: [[PROCEED:%.+]] = comb.or [[NEGEDGE]] : i1 - // CHECK: cf.cond_br [[PROCEED]], ^[[BB3:.+]], ^[[BB1]] - // CHECK: ^[[BB3]]: - // CHECK: [[RES:%.+]] = comb.add [[B_PRB]], [[C_PRB]] : i1 - // CHECK: cf.cond_br %cond, ^[[BB4:.+]], ^[[BB5:.+]] - // CHECK: ^[[BB4]]: - // CHECK: [[T0:%.+]] = llhd.constant_time <0ns, 1d, 0e> - // CHECK: llhd.drv [[D]], [[RES]] after [[T0]] - // CHECK: cf.br ^[[BB1]] - // CHECK: ^[[BB5]]: - // CHECK: cf.br ^[[BB1]] - moore.wait_event negedge %cr : i1 - %0 = moore.add %br, %cr : i1 - cf.cond_br %cond, ^bb1, ^bb2 - ^bb1: - moore.nonblocking_assign %d, %0 : i1 - moore.return - ^bb2: - moore.return - } -} - // CHECK-LABEL: func.func @CaseXZ( func.func @CaseXZ(%arg0: !moore.l8, %arg1: !moore.l8) { // CHECK: hw.constant -124 : i8 @@ -596,3 +482,218 @@ func.func @CaseXZ(%arg0: !moore.l8, %arg1: !moore.l8) { return } + +// CHECK-LABEL: hw.module @Procedures +moore.module @Procedures() { + // CHECK: llhd.process { + // CHECK: func.call @dummyA() + // CHECK: llhd.halt + // CHECK: } + moore.procedure initial { + func.call @dummyA() : () -> () + moore.return + } + + // CHECK: llhd.final { + // CHECK: func.call @dummyA() + // CHECK: llhd.halt + // CHECK: } + moore.procedure final { + func.call @dummyA() : () -> () + moore.return + } + + // CHECK: llhd.process { + // CHECK: cf.br ^[[BB:.+]] + // CHECK: ^[[BB]]: + // CHECK: func.call @dummyA() + // CHECK: cf.br ^[[BB]] + // CHECK: } + moore.procedure always { + func.call @dummyA() : () -> () + moore.return + } + + // CHECK: llhd.process { + // CHECK: cf.br ^[[BB:.+]] + // CHECK: ^[[BB]]: + // CHECK: func.call @dummyA() + // CHECK: cf.br ^[[BB]] + // CHECK: } + moore.procedure always_ff { + func.call @dummyA() : () -> () + moore.return + } + + // TODO: moore.procedure always_comb + // TODO: moore.procedure always_latch +} + +func.func private @dummyA() -> () +func.func private @dummyB() -> () +func.func private @dummyC() -> () + +// CHECK-LABEL: hw.module @WaitEvent +moore.module @WaitEvent() { + // CHECK: %a = llhd.sig + // CHECK: %b = llhd.sig + // CHECK: %c = llhd.sig + %a = moore.variable : + %b = moore.variable : + %c = moore.variable : + + // CHECK: llhd.process { + // CHECK: func.call @dummyA() + // CHECK: cf.br ^[[WAIT:.+]] + // CHECK: ^[[WAIT]]: + // CHECK: func.call @dummyB() + // CHECK: llhd.wait ^[[CHECK:.+]] + // CHECK: ^[[CHECK]]: + // CHECK: func.call @dummyB() + // CHECK: cf.br ^[[WAIT:.+]] + // CHECK: ^[[RESUME:.+]]: + // CHECK: func.call @dummyC() + // CHECK: llhd.prb %a + // CHECK: llhd.halt + // CHECK: } + moore.procedure initial { + func.call @dummyA() : () -> () + moore.wait_event { + func.call @dummyB() : () -> () + } + func.call @dummyC() : () -> () + moore.read %a : + moore.return + } + + // CHECK: llhd.process { + moore.procedure initial { + // CHECK: [[BEFORE:%.+]] = llhd.prb %a + // CHECK: llhd.wait (%a : {{.+}}), ^[[CHECK:.+]] + // CHECK: ^[[CHECK]]: + // CHECK: [[AFTER:%.+]] = llhd.prb %a + // CHECK: [[TMP:%.+]] = comb.icmp bin ne [[BEFORE]], [[AFTER]] + // CHECK: cf.cond_br [[TMP]] + moore.wait_event { + %0 = moore.read %a : + moore.detect_event any %0 : i1 + } + moore.return + } + + // CHECK: llhd.process { + moore.procedure initial { + // CHECK: [[BEFORE_A:%.+]] = llhd.prb %a + // CHECK: [[BEFORE_B:%.+]] = llhd.prb %b + // CHECK: llhd.wait (%a, %b : {{.+}}), ^[[CHECK:.+]] + // CHECK: ^[[CHECK]]: + // CHECK: [[AFTER_A:%.+]] = llhd.prb %a + // CHECK: [[AFTER_B:%.+]] = llhd.prb %b + // CHECK: [[TMP1:%.+]] = comb.icmp bin ne [[BEFORE_A]], [[AFTER_A]] + // CHECK: [[TMP2:%.+]] = comb.and bin [[TMP1]], [[AFTER_B]] + // CHECK: cf.cond_br [[TMP2]] + moore.wait_event { + %0 = moore.read %a : + %1 = moore.read %b : + moore.detect_event any %0 if %1 : i1 + } + moore.return + } + + // CHECK: llhd.process { + moore.procedure initial { + // CHECK: [[BEFORE_A:%.+]] = llhd.prb %a + // CHECK: [[BEFORE_B:%.+]] = llhd.prb %b + // CHECK: [[BEFORE_C:%.+]] = llhd.prb %c + // CHECK: llhd.wait (%a, %b, %c : {{.+}}), ^[[CHECK:.+]] + // CHECK: ^[[CHECK]]: + // CHECK: [[AFTER_A:%.+]] = llhd.prb %a + // CHECK: [[AFTER_B:%.+]] = llhd.prb %b + // CHECK: [[AFTER_C:%.+]] = llhd.prb %c + // CHECK: [[TMP1:%.+]] = comb.icmp bin ne [[BEFORE_A]], [[AFTER_A]] + // CHECK: [[TMP2:%.+]] = comb.icmp bin ne [[BEFORE_B]], [[AFTER_B]] + // CHECK: [[TMP3:%.+]] = comb.icmp bin ne [[BEFORE_C]], [[AFTER_C]] + // CHECK: [[TMP4:%.+]] = comb.or bin [[TMP1]], [[TMP2]], [[TMP3]] + // CHECK: cf.cond_br [[TMP4]] + moore.wait_event { + %0 = moore.read %a : + %1 = moore.read %b : + %2 = moore.read %c : + moore.detect_event any %0 : i1 + moore.detect_event any %1 : i1 + moore.detect_event any %2 : i1 + } + moore.return + } + + // CHECK: llhd.process { + moore.procedure initial { + // CHECK: [[BEFORE:%.+]] = llhd.prb %a + // CHECK: llhd.wait (%a : {{.+}}), ^[[CHECK:.+]] + // CHECK: ^[[CHECK]]: + // CHECK: [[AFTER:%.+]] = llhd.prb %a + // CHECK: [[TRUE:%.+]] = hw.constant true + // CHECK: [[TMP1:%.+]] = comb.xor bin [[BEFORE]], [[TRUE]] + // CHECK: [[TMP2:%.+]] = comb.and bin [[TMP1]], [[AFTER]] + // CHECK: cf.cond_br [[TMP2]] + moore.wait_event { + %0 = moore.read %a : + moore.detect_event posedge %0 : i1 + } + moore.return + } + + // CHECK: llhd.process { + moore.procedure initial { + // CHECK: [[BEFORE:%.+]] = llhd.prb %a + // CHECK: llhd.wait (%a : {{.+}}), ^[[CHECK:.+]] + // CHECK: ^[[CHECK]]: + // CHECK: [[AFTER:%.+]] = llhd.prb %a + // CHECK: [[TRUE:%.+]] = hw.constant true + // CHECK: [[TMP1:%.+]] = comb.xor bin [[AFTER]], [[TRUE]] + // CHECK: [[TMP2:%.+]] = comb.and bin [[BEFORE]], [[TMP1]] + // CHECK: cf.cond_br [[TMP2]] + moore.wait_event { + %0 = moore.read %a : + moore.detect_event negedge %0 : i1 + } + moore.return + } + + // CHECK: llhd.process { + moore.procedure initial { + // CHECK: [[BEFORE:%.+]] = llhd.prb %a + // CHECK: llhd.wait (%a : {{.+}}), ^[[CHECK:.+]] + // CHECK: ^[[CHECK]]: + // CHECK: [[AFTER:%.+]] = llhd.prb %a + // CHECK: [[TRUE:%.+]] = hw.constant true + // CHECK: [[TMP1:%.+]] = comb.xor bin [[BEFORE]], [[TRUE]] + // CHECK: [[TMP2:%.+]] = comb.and bin [[TMP1]], [[AFTER]] + // CHECK: [[TMP3:%.+]] = comb.xor bin [[AFTER]], [[TRUE]] + // CHECK: [[TMP4:%.+]] = comb.and bin [[BEFORE]], [[TMP3]] + // CHECK: [[TMP5:%.+]] = comb.or bin [[TMP2]], [[TMP4]] + // CHECK: cf.cond_br [[TMP5]] + moore.wait_event { + %0 = moore.read %a : + moore.detect_event edge %0 : i1 + } + moore.return + } + + // CHECK: llhd.process { + moore.procedure initial { + // CHECK: llhd.wait (%a, %b : + moore.wait_event { + %0 = moore.constant 0 : i1 + %1 = moore.conditional %0 : i1 -> i1 { + %2 = moore.read %a : + moore.yield %2 : !moore.i1 + } { + %3 = moore.read %b : + moore.yield %3 : !moore.i1 + } + moore.detect_event any %1 : i1 + } + moore.return + } +} diff --git a/test/Dialect/LLHD/IR/basic.mlir b/test/Dialect/LLHD/IR/basic.mlir index e1a0941fa1a6..fb27f3f064bb 100644 --- a/test/Dialect/LLHD/IR/basic.mlir +++ b/test/Dialect/LLHD/IR/basic.mlir @@ -199,3 +199,13 @@ hw.module @check_wait_3 (inout %arg0 : i64, inout %arg1 : i1) { llhd.halt } } + +// CHECK-LABEL: @FinalProcess +hw.module @FinalProcess () { + // CHECK-NEXT: llhd.final { + // CHECK-NEXT: llhd.halt + // CHECK-NEXT: } + llhd.final { + llhd.halt + } +} diff --git a/test/Dialect/Moore/basic.mlir b/test/Dialect/Moore/basic.mlir index eb7d9b03b678..7b589d3582e3 100644 --- a/test/Dialect/Moore/basic.mlir +++ b/test/Dialect/Moore/basic.mlir @@ -310,3 +310,22 @@ moore.module @GraphRegion() { %1 = moore.add %0, %0 : i32 %0 = moore.constant 0 : i32 } + +// CHECK-LABEL: func.func @WaitEvent +func.func @WaitEvent(%arg0: !moore.i1, %arg1: !moore.i1) { + // CHECK: moore.wait_event { + moore.wait_event { + // CHECK: moore.detect_event any %arg0 : i1 + moore.detect_event any %arg0 : i1 + // CHECK: moore.detect_event posedge %arg0 : i1 + moore.detect_event posedge %arg0 : i1 + // CHECK: moore.detect_event negedge %arg0 : i1 + moore.detect_event negedge %arg0 : i1 + // CHECK: moore.detect_event edge %arg0 : i1 + moore.detect_event edge %arg0 : i1 + // CHECK: moore.detect_event any %arg0 if %arg1 : i1 + moore.detect_event any %arg0 if %arg1 : i1 + } + // CHECK: } + return +} diff --git a/tools/circt-verilog/circt-verilog.cpp b/tools/circt-verilog/circt-verilog.cpp index 46e04a6fdaae..f07fc252a34d 100644 --- a/tools/circt-verilog/circt-verilog.cpp +++ b/tools/circt-verilog/circt-verilog.cpp @@ -229,14 +229,18 @@ static void populateMooreTransforms(PassManager &pm) { // modules/functions. auto &anyPM = pm.nestAny(); anyPM.addPass(mlir::createCSEPass()); - anyPM.addPass(createSimpleCanonicalizerPass()); + anyPM.addPass(mlir::createCanonicalizerPass()); } { // Perform module-specific transformations. auto &modulePM = pm.nest(); modulePM.addPass(moore::createLowerConcatRefPass()); - modulePM.addPass(moore::createSimplifyProceduresPass()); + // TODO: Enable the following once it not longer interferes with @(...) + // event control checks. The introduced dummy variables make the event + // control observe a static local variable that never changes, instead of + // observing a module-wide signal. + // modulePM.addPass(moore::createSimplifyProceduresPass()); } { @@ -245,7 +249,7 @@ static void populateMooreTransforms(PassManager &pm) { anyPM.addPass(mlir::createSROA()); anyPM.addPass(mlir::createMem2Reg()); anyPM.addPass(mlir::createCSEPass()); - anyPM.addPass(createSimpleCanonicalizerPass()); + anyPM.addPass(mlir::createCanonicalizerPass()); } } @@ -259,7 +263,7 @@ static void populateMooreToCoreLowering(PassManager &pm) { // opportunities. auto &anyPM = pm.nestAny(); anyPM.addPass(mlir::createCSEPass()); - anyPM.addPass(createSimpleCanonicalizerPass()); + anyPM.addPass(mlir::createCanonicalizerPass()); } } From 27d3965c5926761d07ad446c77269b23603c5467 Mon Sep 17 00:00:00 2001 From: Schuyler Eldridge Date: Fri, 16 Aug 2024 19:25:51 -0400 Subject: [PATCH 106/119] [firrtl] Test whitespace cleanup, NFC Cleanup trailing whitespace in a test. Signed-off-by: Schuyler Eldridge --- test/Dialect/FIRRTL/specialize-layers.mlir | 28 +++++++++++----------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/test/Dialect/FIRRTL/specialize-layers.mlir b/test/Dialect/FIRRTL/specialize-layers.mlir index 102e49856062..00e53a87c18a 100644 --- a/test/Dialect/FIRRTL/specialize-layers.mlir +++ b/test/Dialect/FIRRTL/specialize-layers.mlir @@ -202,7 +202,7 @@ firrtl.circuit "LayerblockDisableB" attributes { } } } - + //===----------------------------------------------------------------------===// // Default Specialization //===----------------------------------------------------------------------===// @@ -254,7 +254,7 @@ firrtl.circuit "LayerDefaultDisable" attributes { //===----------------------------------------------------------------------===// // CHECK-LABEL: firrtl.circuit "EnableLayerB" -firrtl.circuit "EnableLayerB" +firrtl.circuit "EnableLayerB" attributes { enable_layers = [@A::@B] } { @@ -266,13 +266,13 @@ attributes { firrtl.option @Option { firrtl.option_case @Option } - + firrtl.module public @EnableLayerB() { firrtl.layerblock @A { firrtl.layerblock @A::@B { firrtl.layerblock @A::@B::@C { // CHECK: firrtl.instance instance {layers = [@A, @A, @A::@B_C]} @ExtModule() - firrtl.instance instance {layers = [@A, @A::@B, @A::@B::@C]} @ExtModule() + firrtl.instance instance {layers = [@A, @A::@B, @A::@B::@C]} @ExtModule() // CHECK: firrtl.instance_choice instance_choice // CHECK-SAME: {layers = [@A, @A, @A::@B_C]} @ExtModule firrtl.instance_choice instance_choice @@ -343,8 +343,8 @@ firrtl.circuit "ProbeOpsEnableA" attributes { } // CHECK: firrtl.instance instance @ExtModule(out out: !firrtl.probe>) - firrtl.instance instance @ExtModule(out out : !firrtl.probe, @A>) - + firrtl.instance instance @ExtModule(out out : !firrtl.probe, @A>) + // CHECK: firrtl.instance_choice instance_choice @ExtModule // CHECK-SAME: alternatives @Option { @Option -> @ExtModule } // CHECK-SAME: (out out: !firrtl.probe>) @@ -352,7 +352,7 @@ firrtl.circuit "ProbeOpsEnableA" attributes { alternatives @Option { @Option -> @ExtModule } (out out : !firrtl.probe, @A>) } - + firrtl.extmodule @ExtModule(out out : !firrtl.probe, @A>) } @@ -392,7 +392,7 @@ firrtl.circuit "ProbeOpsDisableA" attributes { } // CHECK: firrtl.instance instance @ExtModule() - firrtl.instance instance @ExtModule(out out : !firrtl.probe, @A>) + firrtl.instance instance @ExtModule(out out : !firrtl.probe, @A>) // CHECK: firrtl.instance_choice instance_choice @ExtModule // CHECK-SAME: alternatives @Option { @Option -> @ExtModule } // CHECK-SAME: () @@ -429,13 +429,13 @@ firrtl.circuit "HierPathDelete" attributes { firrtl.module @HierPathDelete() { firrtl.instance middle sym @middle @Middle() } - + firrtl.module @Middle() { firrtl.layerblock @Layer { firrtl.instance leaf sym @leaf @Leaf() } } - + firrtl.extmodule @Leaf() // CHECK-NOT: hw.hierpath private @DeletedPath [@Deleted] @@ -463,13 +463,13 @@ firrtl.circuit "HierPathDelete2" attributes { firrtl.instance middle sym @middle @Middle() } } - + firrtl.module @Middle() { firrtl.layerblock @Layer { firrtl.instance leaf sym @leaf @Leaf() } } - + firrtl.extmodule @Leaf() // CHECK-NOT: hw.hierpath private @DeletedPath [@Deleted] @@ -495,11 +495,11 @@ firrtl.circuit "Annotations" attributes { firrtl.instance leaf sym @leaf @Leaf(in in : !firrtl.uint<1>) } } - + // CHECK: firrtl.module @Leaf(in %in: !firrtl.uint<1>) { firrtl.module @Leaf(in %in : !firrtl.uint<1> [{circt.nonlocal = @Path}]) attributes {annotations = [{circt.nonlocal = @Path}]} { - + // CHECK: %w = firrtl.wire : !firrtl.uint<1> %w = firrtl.wire {annotations = [{circt.nonlocal = @Path}]} : !firrtl.uint<1> From 209d9fb80e8763a53725f95596fe20064ad7b061 Mon Sep 17 00:00:00 2001 From: Schuyler Eldridge Date: Fri, 16 Aug 2024 19:23:40 -0400 Subject: [PATCH 107/119] [firrtl] Fix bug in sibling layer specialization Fix a bug in the `SpecializeLayers` pass where sibling layers would not be enabled/disabled correctly if an earlier sibling layer was also enabled/disabled. Fixes #7525. Signed-off-by: Schuyler Eldridge --- .../FIRRTL/Transforms/SpecializeLayers.cpp | 2 ++ test/Dialect/FIRRTL/specialize-layers.mlir | 15 +++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/lib/Dialect/FIRRTL/Transforms/SpecializeLayers.cpp b/lib/Dialect/FIRRTL/Transforms/SpecializeLayers.cpp index 6b629bf968fe..07e2dc0bf821 100644 --- a/lib/Dialect/FIRRTL/Transforms/SpecializeLayers.cpp +++ b/lib/Dialect/FIRRTL/Transforms/SpecializeLayers.cpp @@ -605,6 +605,7 @@ struct SpecializeLayers { llvm::make_early_inc_range(block->getOps())) { nestedRefs.push_back(SymbolRefAttr::get(nested)); handleLayer(nested, Block::iterator(nested), ""); + nestedRefs.pop_back(); } return; } @@ -617,6 +618,7 @@ struct SpecializeLayers { nestedRefs.push_back(SymbolRefAttr::get(nested)); handleLayer(nested, insertionPoint, prefix + layer.getSymName() + "_"); + nestedRefs.pop_back(); } // Erase the now empty layer. layer->erase(); diff --git a/test/Dialect/FIRRTL/specialize-layers.mlir b/test/Dialect/FIRRTL/specialize-layers.mlir index 00e53a87c18a..c6f0a042a09e 100644 --- a/test/Dialect/FIRRTL/specialize-layers.mlir +++ b/test/Dialect/FIRRTL/specialize-layers.mlir @@ -115,6 +115,21 @@ firrtl.circuit "LayerDisableInARow" attributes { firrtl.extmodule @LayerDisableInARow() } +// CHECK: firrtl.circuit "LayerblockEnableNestedChildren" +// CHECK-NOT: firrtl.layer +firrtl.circuit "LayerblockEnableNestedChildren" attributes { + enable_layers = [@A, @A::@B, @A::@C] +} { + firrtl.layer @A bind { + firrtl.layer @B bind { + } + firrtl.layer @C bind { + } + } + firrtl.module @LayerblockEnableNestedChildren() { + } +} + //===----------------------------------------------------------------------===// // LayerBlock Specialization //===----------------------------------------------------------------------===// From 246636cee17ff821f98bb1db8eb17999e24cf528 Mon Sep 17 00:00:00 2001 From: Hideto Ueno Date: Mon, 19 Aug 2024 18:04:38 +0900 Subject: [PATCH 108/119] [ExportVerilog] Don't inline unpacked array assignments (#4548) This changes emission style for unpacked array declaration. Verilator doesn't support initialization assignments for unpacked arrays, e.g: ```verilog wire w[1:0] = '{0, 0}; ``` This PR checks the value type and prevents inlining. Ideally it is more desirable to improve verilator but for now I want to avoid inlining unpacked arrays to declaration since it's just a tiny readability optimization. Fix https://github.com/llvm/circt/issues/6363. --- .../ExportVerilog/ExportVerilog.cpp | 17 ++++++++++++-- test/Conversion/ExportVerilog/sv-dialect.mlir | 23 +++++++++++++++++-- .../ExportVerilog/verilog-basic.mlir | 3 ++- test/firtool/dpi.fir | 3 ++- 4 files changed, 40 insertions(+), 6 deletions(-) diff --git a/lib/Conversion/ExportVerilog/ExportVerilog.cpp b/lib/Conversion/ExportVerilog/ExportVerilog.cpp index 74e5760ccae1..a67d02581c64 100644 --- a/lib/Conversion/ExportVerilog/ExportVerilog.cpp +++ b/lib/Conversion/ExportVerilog/ExportVerilog.cpp @@ -341,6 +341,13 @@ static Type stripUnpackedTypes(Type type) { .Default([](Type type) { return type; }); } +/// Return true if the type has a leading unpacked type. +static bool hasLeadingUnpackedType(Type type) { + assert(isa(type) && "inout type is expected"); + auto elementType = cast(type).getElementType(); + return stripUnpackedTypes(elementType) != elementType; +} + /// Return true if type has a struct type as a subtype. static bool hasStructType(Type type) { return TypeSwitch(type) @@ -5855,8 +5862,11 @@ LogicalResult StmtEmitter::emitDeclaration(Operation *op) { } // Try inlining an assignment into declarations. + // FIXME: Unpacked array is not inlined since several tools doesn't support + // that syntax. See Issue 6363. if (isa(op) && - !op->getParentOp()->hasTrait()) { + !op->getParentOp()->hasTrait() && + !hasLeadingUnpackedType(op->getResult(0).getType())) { // Get a single assignments if any. if (auto singleAssign = getSingleAssignAndCheckUsers(op)) { auto *source = singleAssign.getSrc().getDefiningOp(); @@ -5877,7 +5887,10 @@ LogicalResult StmtEmitter::emitDeclaration(Operation *op) { } // Try inlining a blocking assignment to logic op declaration. - if (isa(op) && op->getParentOp()->hasTrait()) { + // FIXME: Unpacked array is not inlined since several tools doesn't support + // that syntax. See Issue 6363. + if (isa(op) && op->getParentOp()->hasTrait() && + !hasLeadingUnpackedType(op->getResult(0).getType())) { // Get a single assignment which might be possible to inline. if (auto singleAssign = getSingleAssignAndCheckUsers(op)) { // It is necessary for the assignment to dominate users of the op. diff --git a/test/Conversion/ExportVerilog/sv-dialect.mlir b/test/Conversion/ExportVerilog/sv-dialect.mlir index 3d75fd441e62..8146d9dfc231 100644 --- a/test/Conversion/ExportVerilog/sv-dialect.mlir +++ b/test/Conversion/ExportVerilog/sv-dialect.mlir @@ -521,7 +521,8 @@ hw.module @reg_0(in %in4: i4, in %in8: i8, in %in8_2: i8, out a: i8, out b: i8) %unpacked_array = sv.unpacked_array_create %in8, %in8_2 : (i8, i8) -> !hw.uarray<2xi8> %unpacked_wire = sv.wire : !hw.inout> - // CHECK: wire [7:0] unpacked_wire[0:1] = '{in8_2, in8}; + // CHECK: wire [7:0] unpacked_wire[0:1]; + // CHECK-NEXT: assign unpacked_wire = '{in8_2, in8}; sv.assign %unpacked_wire, %unpacked_array: !hw.uarray<2xi8> // CHECK-NEXT: assign a = myReg; @@ -1111,6 +1112,23 @@ hw.module @DontDuplicateSideEffectingVerbatim() { } } +// Issue 6363 +// CHECK-LABEL: module DontInlineAssignmentForUnpackedArrays( +hw.module @DontInlineAssignmentForUnpackedArrays(in %a: !hw.uarray<2xi1>) { +// CHECK: wire w[0:1]; +// CHECK-NEXT: assign w = a; + %w = sv.wire : !hw.inout> + sv.assign %w, %a : !hw.uarray<2xi1> + // CHECK: logic u[0:1]; + // CHECK-NEXT: u = a; + sv.initial { + %u = sv.logic : !hw.inout> + sv.bpassign %u, %a : !hw.uarray<2xi1> + } + + hw.output +} + hw.generator.schema @verbatim_schema, "Simple", ["ports", "write_latency", "read_latency"] hw.module.extern @verbatim_inout_2 () // CHECK-LABEL: module verbatim_M1( @@ -1789,7 +1807,8 @@ sv.func private @open_array(in %array : !sv.open_uarray) sv.func.dpi.import @open_array // CHECK-LABEL: test_open_array -// CHECK: wire [7:0] _GEN[0:1] = '{in_0, in_1}; +// CHECK: wire [7:0] _GEN[0:1]; +// CHECK-NEXT: assign _GEN = '{in_0, in_1}; // CHECK-NEXT: always @(posedge clock) // CHECK-NEXT: open_array(_GEN); hw.module @test_open_array(in %clock : i1, in %in_0 : i8, in %in_1 : i8) { diff --git a/test/Conversion/ExportVerilog/verilog-basic.mlir b/test/Conversion/ExportVerilog/verilog-basic.mlir index 2f28b0e88c04..55586662ac4f 100644 --- a/test/Conversion/ExportVerilog/verilog-basic.mlir +++ b/test/Conversion/ExportVerilog/verilog-basic.mlir @@ -489,7 +489,8 @@ hw.module @ArrayParamsInst() { uarr: %uarr : !hw.uarray<2 x i8>) -> () } // CHECK: wire [1:0][7:0] [[G0:_.*]] = '{8'h1, 8'h2}; -// CHECK: wire [7:0] [[G1:_.*]][0:1] = '{8'h1, 8'h2}; +// CHECK: wire [7:0] [[G1:_.*]][0:1]; +// CHECK: assign [[G1]] = '{8'h1, 8'h2}; // CHECK: ArrayParams #( // CHECK: .param(2) // CHECK: ) arrays ( diff --git a/test/firtool/dpi.fir b/test/firtool/dpi.fir index ce47feb2529e..f4bf89d6a541 100644 --- a/test/firtool/dpi.fir +++ b/test/firtool/dpi.fir @@ -33,7 +33,8 @@ circuit DPI: ; CHECK-LABEL: module DPI( ; CHECK: logic [7:0] [[TMP:_.+]]; ; CHECK-NEXT: reg [7:0] [[RESULT1:_.+]]; -; CHECK-NEXT: wire [7:0] [[OPEN_ARRAY:_.+]][0:1] = '{in_0, in_1}; +; CHECK-NEXT: wire [7:0] [[OPEN_ARRAY:_.+]][0:1]; +; CHECK-NEXT: assign [[OPEN_ARRAY]] = '{in_0, in_1}; ; CHECK-NEXT: always @(posedge clock) begin ; CHECK-NEXT: if (enable) begin ; CHECK-NEXT: clocked_result(in_0, in_1, [[TMP]]); From 9efc2e757210c6e5828883f4374aa827f3a50442 Mon Sep 17 00:00:00 2001 From: Martin Erhart Date: Mon, 19 Aug 2024 17:36:10 +0100 Subject: [PATCH 109/119] [LLHD] Let WaitOp observe plain values instead of signals (#7528) This is necessary to lower the moore dialect's always_comb and always_latch without introducing helper signals. It also allows for more mem2reg at the LLHD level. --- .../circt/Dialect/LLHD/IR/LLHDStructureOps.td | 4 +- lib/Conversion/MooreToCore/MooreToCore.cpp | 21 ++++++- .../LLHD/Transforms/ProcessLoweringPass.cpp | 55 ++++++------------- test/Conversion/MooreToCore/basic.mlir | 25 ++++++--- .../LLHD/Canonicalization/probeCSE.mlir | 5 +- test/Dialect/LLHD/IR/basic.mlir | 16 ++++-- .../LLHD/Transforms/earlyCodeMotion.mlir | 14 +++-- .../Transforms/memoryToBlockArgument.mlir | 6 +- .../LLHD/Transforms/processLowering.mlir | 36 ++++++++---- .../Transforms/processLoweringErrors.mlir | 13 +++-- .../LLHD/Transforms/temporal-code-motion.mlir | 10 +++- 11 files changed, 125 insertions(+), 80 deletions(-) diff --git a/include/circt/Dialect/LLHD/IR/LLHDStructureOps.td b/include/circt/Dialect/LLHD/IR/LLHDStructureOps.td index 58afcb53bd60..c288c68e559b 100644 --- a/include/circt/Dialect/LLHD/IR/LLHDStructureOps.td +++ b/include/circt/Dialect/LLHD/IR/LLHDStructureOps.td @@ -130,14 +130,14 @@ def WaitOp : LLHDOp<"wait", [ ``` }]; - let arguments = (ins Variadic:$obs, + let arguments = (ins Variadic:$observed, Optional:$time, Variadic:$destOps); let successors = (successor AnySuccessor:$dest); let assemblyFormat = [{ - (`for` $time^ `,`)? (`(`$obs^ `:` qualified(type($obs))`)` `,`)? + (`for` $time^ `,`)? (`(`$observed^ `:` qualified(type($observed))`)` `,`)? $dest (`(` $destOps^ `:` qualified(type($destOps)) `)`)? attr-dict }]; } diff --git a/lib/Conversion/MooreToCore/MooreToCore.cpp b/lib/Conversion/MooreToCore/MooreToCore.cpp index 6a3470049dd7..a5e184051121 100644 --- a/lib/Conversion/MooreToCore/MooreToCore.cpp +++ b/lib/Conversion/MooreToCore/MooreToCore.cpp @@ -322,6 +322,19 @@ struct WaitEventOpConversion : public OpConversionPattern { rewriter.eraseOp(detectOp); } + auto setInsertionPointAfterDef = [&](Value value) { + if (auto *op = value.getDefiningOp()) + rewriter.setInsertionPointAfter(op); + if (auto arg = dyn_cast(value)) + rewriter.setInsertionPointToStart(value.getParentBlock()); + }; + + auto probeIfSignal = [&](Value value) -> Value { + if (!isa(value.getType())) + return value; + return rewriter.create(loc, value); + }; + // Determine the values used during event detection that are defined outside // the `wait_event`'s body region. We want to wait for a change on these // signals before we check if any interesting event happened. @@ -334,12 +347,16 @@ struct WaitEventOpConversion : public OpConversionPattern { if (!alreadyObserved.insert(value).second) continue; if (auto remapped = rewriter.getRemappedValue(value)) { - observeValues.push_back(remapped); + OpBuilder::InsertionGuard g(rewriter); + setInsertionPointAfterDef(remapped); + observeValues.push_back(probeIfSignal(remapped)); } else { + OpBuilder::InsertionGuard g(rewriter); + setInsertionPointAfterDef(value); auto type = typeConverter->convertType(value.getType()); auto converted = typeConverter->materializeTargetConversion( rewriter, loc, type, value); - observeValues.push_back(converted); + observeValues.push_back(probeIfSignal(converted)); } } }); diff --git a/lib/Dialect/LLHD/Transforms/ProcessLoweringPass.cpp b/lib/Dialect/LLHD/Transforms/ProcessLoweringPass.cpp index d20a5f437e88..2ec645d6ae9c 100644 --- a/lib/Dialect/LLHD/Transforms/ProcessLoweringPass.cpp +++ b/lib/Dialect/LLHD/Transforms/ProcessLoweringPass.cpp @@ -34,40 +34,6 @@ struct ProcessLoweringPass void runOnOperation() override; }; -/// Backtrack a signal value and make sure that every part of it is in the -/// observer list at some point. Assumes that there is no operation that adds -/// parts to a signal that it does not take as input (e.g. something like -/// llhd.sig.zext %sig : !hw.inout -> !hw.inout). -static LogicalResult checkSignalsAreObserved(OperandRange obs, Value value) { - // If the value in the observer list, we don't need to backtrack further. - if (llvm::is_contained(obs, value)) - return success(); - - if (Operation *op = value.getDefiningOp()) { - // If no input is a signal, this operation creates one and thus this is the - // last point where it could have been observed. As we've already checked - // that, we can fail here. This includes for example llhd.sig - if (llvm::none_of(op->getOperands(), [](Value arg) { - return isa(arg.getType()); - })) - return failure(); - - // Only recusively backtrack signal values. Other values cannot be changed - // from outside or with a delay. If they come from probes at some point, - // they are covered by that probe. As soon as we find a signal that is not - // observed no matter how far we backtrack, we fail. - return success(llvm::all_of(op->getOperands(), [&](Value arg) { - return !isa(arg.getType()) || - succeeded(checkSignalsAreObserved(obs, arg)); - })); - } - - // If the value is a module argument (no block arguments except for the entry - // block are allowed here) and was not observed, we cannot backtrack further - // and thus fail. - return failure(); -} - static LogicalResult isProcValidToLower(llhd::ProcessOp op) { size_t numBlocks = op.getBody().getBlocks().size(); @@ -98,13 +64,24 @@ static LogicalResult isProcValidToLower(llhd::ProcessOp op) { "during process-lowering: llhd.wait terminators with optional time " "argument cannot be lowered to structural LLHD"); + SmallVector observedSignals; + for (Value obs : wait.getObserved()) + if (auto prb = obs.getDefiningOp()) + if (!op.getBody().isAncestor(prb->getParentRegion())) + observedSignals.push_back(prb.getSignal()); + // Every probed signal has to occur in the observed signals list in // the wait instruction - WalkResult result = op.walk([&wait](llhd::PrbOp prbOp) -> WalkResult { - if (failed(checkSignalsAreObserved(wait.getObs(), prbOp.getSignal()))) - return wait.emitOpError( - "during process-lowering: the wait terminator is required to " - "have all probed signals as arguments"); + WalkResult result = op.walk([&](Operation *operation) -> WalkResult { + // TODO: value does not need to be observed if all values this value is + // a combinatorial result of are observed. + for (Value operand : operation->getOperands()) + if (!op.getBody().isAncestor(operand.getParentRegion()) && + !llvm::is_contained(wait.getObserved(), operand) && + !llvm::is_contained(observedSignals, operand)) + return wait.emitOpError( + "during process-lowering: the wait terminator is required to " + "have values used in the process as arguments"); return WalkResult::advance(); }); diff --git a/test/Conversion/MooreToCore/basic.mlir b/test/Conversion/MooreToCore/basic.mlir index f3d89bf635b5..3a0c87213131 100644 --- a/test/Conversion/MooreToCore/basic.mlir +++ b/test/Conversion/MooreToCore/basic.mlir @@ -536,8 +536,19 @@ func.func private @dummyC() -> () // CHECK-LABEL: hw.module @WaitEvent moore.module @WaitEvent() { // CHECK: %a = llhd.sig + // CHECK: [[PRB_A6:%.+]] = llhd.prb %a + // CHECK: [[PRB_A5:%.+]] = llhd.prb %a + // CHECK: [[PRB_A4:%.+]] = llhd.prb %a + // CHECK: [[PRB_A3:%.+]] = llhd.prb %a + // CHECK: [[PRB_A2:%.+]] = llhd.prb %a + // CHECK: [[PRB_A1:%.+]] = llhd.prb %a + // CHECK: [[PRB_A0:%.+]] = llhd.prb %a // CHECK: %b = llhd.sig + // CHECK: [[PRB_B2:%.+]] = llhd.prb %b + // CHECK: [[PRB_B1:%.+]] = llhd.prb %b + // CHECK: [[PRB_B0:%.+]] = llhd.prb %b // CHECK: %c = llhd.sig + // CHECK: [[PRB_C:%.+]] = llhd.prb %c %a = moore.variable : %b = moore.variable : %c = moore.variable : @@ -569,7 +580,7 @@ moore.module @WaitEvent() { // CHECK: llhd.process { moore.procedure initial { // CHECK: [[BEFORE:%.+]] = llhd.prb %a - // CHECK: llhd.wait (%a : {{.+}}), ^[[CHECK:.+]] + // CHECK: llhd.wait ([[PRB_A0]] : {{.+}}), ^[[CHECK:.+]] // CHECK: ^[[CHECK]]: // CHECK: [[AFTER:%.+]] = llhd.prb %a // CHECK: [[TMP:%.+]] = comb.icmp bin ne [[BEFORE]], [[AFTER]] @@ -585,7 +596,7 @@ moore.module @WaitEvent() { moore.procedure initial { // CHECK: [[BEFORE_A:%.+]] = llhd.prb %a // CHECK: [[BEFORE_B:%.+]] = llhd.prb %b - // CHECK: llhd.wait (%a, %b : {{.+}}), ^[[CHECK:.+]] + // CHECK: llhd.wait ([[PRB_A1]], [[PRB_B0]] : {{.+}}), ^[[CHECK:.+]] // CHECK: ^[[CHECK]]: // CHECK: [[AFTER_A:%.+]] = llhd.prb %a // CHECK: [[AFTER_B:%.+]] = llhd.prb %b @@ -605,7 +616,7 @@ moore.module @WaitEvent() { // CHECK: [[BEFORE_A:%.+]] = llhd.prb %a // CHECK: [[BEFORE_B:%.+]] = llhd.prb %b // CHECK: [[BEFORE_C:%.+]] = llhd.prb %c - // CHECK: llhd.wait (%a, %b, %c : {{.+}}), ^[[CHECK:.+]] + // CHECK: llhd.wait ([[PRB_A2]], [[PRB_B1]], [[PRB_C]] : {{.+}}), ^[[CHECK:.+]] // CHECK: ^[[CHECK]]: // CHECK: [[AFTER_A:%.+]] = llhd.prb %a // CHECK: [[AFTER_B:%.+]] = llhd.prb %b @@ -629,7 +640,7 @@ moore.module @WaitEvent() { // CHECK: llhd.process { moore.procedure initial { // CHECK: [[BEFORE:%.+]] = llhd.prb %a - // CHECK: llhd.wait (%a : {{.+}}), ^[[CHECK:.+]] + // CHECK: llhd.wait ([[PRB_A3]] : {{.+}}), ^[[CHECK:.+]] // CHECK: ^[[CHECK]]: // CHECK: [[AFTER:%.+]] = llhd.prb %a // CHECK: [[TRUE:%.+]] = hw.constant true @@ -646,7 +657,7 @@ moore.module @WaitEvent() { // CHECK: llhd.process { moore.procedure initial { // CHECK: [[BEFORE:%.+]] = llhd.prb %a - // CHECK: llhd.wait (%a : {{.+}}), ^[[CHECK:.+]] + // CHECK: llhd.wait ([[PRB_A4]] : {{.+}}), ^[[CHECK:.+]] // CHECK: ^[[CHECK]]: // CHECK: [[AFTER:%.+]] = llhd.prb %a // CHECK: [[TRUE:%.+]] = hw.constant true @@ -663,7 +674,7 @@ moore.module @WaitEvent() { // CHECK: llhd.process { moore.procedure initial { // CHECK: [[BEFORE:%.+]] = llhd.prb %a - // CHECK: llhd.wait (%a : {{.+}}), ^[[CHECK:.+]] + // CHECK: llhd.wait ([[PRB_A5]] : {{.+}}), ^[[CHECK:.+]] // CHECK: ^[[CHECK]]: // CHECK: [[AFTER:%.+]] = llhd.prb %a // CHECK: [[TRUE:%.+]] = hw.constant true @@ -682,7 +693,7 @@ moore.module @WaitEvent() { // CHECK: llhd.process { moore.procedure initial { - // CHECK: llhd.wait (%a, %b : + // CHECK: llhd.wait ([[PRB_A6]], [[PRB_B2]] : moore.wait_event { %0 = moore.constant 0 : i1 %1 = moore.conditional %0 : i1 -> i1 { diff --git a/test/Dialect/LLHD/Canonicalization/probeCSE.mlir b/test/Dialect/LLHD/Canonicalization/probeCSE.mlir index c1b968830c0b..8568ec6ddf57 100644 --- a/test/Dialect/LLHD/Canonicalization/probeCSE.mlir +++ b/test/Dialect/LLHD/Canonicalization/probeCSE.mlir @@ -18,7 +18,8 @@ hw.module @checkPrbDceAndCseIn(inout %arg0 : i32, inout %arg1 : i32, inout %arg2 // CHECK-LABEL: @checkPrbDceButNotCse hw.module @checkPrbDceButNotCse(inout %arg0 : i32, inout %arg1 : i32, inout %arg2 : i32) { - // CHECK-NEXT: llhd.process + %prb = llhd.prb %arg0 : !hw.inout + // CHECK: llhd.process llhd.process { // CHECK-NEXT: llhd.constant_time %time = llhd.constant_time <0ns, 1d, 0e> @@ -26,7 +27,7 @@ hw.module @checkPrbDceButNotCse(inout %arg0 : i32, inout %arg1 : i32, inout %arg // CHECK-NEXT: [[P1:%.*]] = llhd.prb %1 = llhd.prb %arg0 : !hw.inout // CHECK-NEXT: llhd.wait - llhd.wait (%arg0: !hw.inout), ^bb1 + llhd.wait (%prb: i32), ^bb1 // CHECK-NEXT: ^bb1: ^bb1: // CHECK-NEXT: [[P2:%.*]] = llhd.prb diff --git a/test/Dialect/LLHD/IR/basic.mlir b/test/Dialect/LLHD/IR/basic.mlir index fb27f3f064bb..618d1a05b0db 100644 --- a/test/Dialect/LLHD/IR/basic.mlir +++ b/test/Dialect/LLHD/IR/basic.mlir @@ -176,10 +176,14 @@ hw.module @check_wait_1 () { // CHECK: @check_wait_2(inout %[[ARG0:.*]] : i64, inout %[[ARG1:.*]] : i1) { hw.module @check_wait_2 (inout %arg0 : i64, inout %arg1 : i1) { + // CHECK: [[PRB0:%.+]] = llhd.prb %arg0 + %prb0 = llhd.prb %arg0 : !hw.inout + // CHECK: [[PRB1:%.+]] = llhd.prb %arg1 + %prb1 = llhd.prb %arg1 : !hw.inout // CHECK-NEXT: llhd.process llhd.process { - // CHECK-NEXT: llhd.wait (%[[ARG0]], %[[ARG1]] : !hw.inout, !hw.inout), ^[[BB:.*]](%[[ARG1]] : !hw.inout) - llhd.wait (%arg0, %arg1 : !hw.inout, !hw.inout), ^bb1(%arg1 : !hw.inout) + // CHECK-NEXT: llhd.wait ([[PRB0]], [[PRB1]] : i64, i1), ^[[BB:.*]](%[[ARG1]] : !hw.inout) + llhd.wait (%prb0, %prb1 : i64, i1), ^bb1(%arg1 : !hw.inout) // CHECK: ^[[BB]](%[[A:.*]]: !hw.inout): ^bb1(%a: !hw.inout): llhd.halt @@ -188,12 +192,16 @@ hw.module @check_wait_2 (inout %arg0 : i64, inout %arg1 : i1) { // CHECK: hw.module @check_wait_3(inout %[[ARG0:.*]] : i64, inout %[[ARG1:.*]] : i1) { hw.module @check_wait_3 (inout %arg0 : i64, inout %arg1 : i1) { + // CHECK: [[PRB0:%.+]] = llhd.prb %arg0 + %prb0 = llhd.prb %arg0 : !hw.inout + // CHECK: [[PRB1:%.+]] = llhd.prb %arg1 + %prb1 = llhd.prb %arg1 : !hw.inout // CHECK-NEXT: llhd.process llhd.process { // CHECK-NEXT: %[[TIME:.*]] = llhd.constant_time %time = llhd.constant_time #llhd.time<0ns, 0d, 0e> - // CHECK-NEXT: llhd.wait for %[[TIME]], (%[[ARG0]], %[[ARG1]] : !hw.inout, !hw.inout), ^[[BB:.*]](%[[ARG1]], %[[ARG0]] : !hw.inout, !hw.inout) - llhd.wait for %time, (%arg0, %arg1 : !hw.inout, !hw.inout), ^bb1(%arg1, %arg0 : !hw.inout, !hw.inout) + // CHECK-NEXT: llhd.wait for %[[TIME]], ([[PRB0]], [[PRB1]] : i64, i1), ^[[BB:.*]](%[[ARG1]], %[[ARG0]] : !hw.inout, !hw.inout) + llhd.wait for %time, (%prb0, %prb1 : i64, i1), ^bb1(%arg1, %arg0 : !hw.inout, !hw.inout) // CHECK: ^[[BB]](%[[A:.*]]: !hw.inout, %[[B:.*]]: !hw.inout): ^bb1(%a: !hw.inout, %b: !hw.inout): llhd.halt diff --git a/test/Dialect/LLHD/Transforms/earlyCodeMotion.mlir b/test/Dialect/LLHD/Transforms/earlyCodeMotion.mlir index 9cdf16d1f013..3ab23494b85a 100644 --- a/test/Dialect/LLHD/Transforms/earlyCodeMotion.mlir +++ b/test/Dialect/LLHD/Transforms/earlyCodeMotion.mlir @@ -123,6 +123,7 @@ hw.module @check_blockarg(inout %sig : i32) { // CHECK-LABEL: @loop // CHECK-SAME: (inout %[[VAL_0:.*]] : i2) +// CHECK: [[PRB:%.+]] = llhd.prb %in_i // CHECK: llhd.process // CHECK: %[[VAL_1:.*]] = hw.constant 0 : i32 // CHECK: %[[VAL_2:.*]] = hw.constant 2 : i32 @@ -138,7 +139,7 @@ hw.module @check_blockarg(inout %sig : i32) { // CHECK: %[[VAL_8:.*]] = llhd.prb %[[VAL_0]] : !hw.inout // CHECK: cf.cond_br %[[VAL_7]], ^[[BB4:.+]], ^[[BB3:.+]] // CHECK: ^[[BB3]]: -// CHECK: llhd.wait (%[[VAL_0]] : !hw.inout), ^[[BB1]] +// CHECK: llhd.wait ([[PRB]] : i2), ^[[BB1]] // CHECK: ^[[BB4]]: // CHECK: %[[VAL_9:.*]] = llhd.load %[[VAL_5]] : !llhd.ptr // CHECK: %[[VAL_10:.*]] = comb.add %[[VAL_9]], %[[VAL_4]] : i32 @@ -146,6 +147,7 @@ hw.module @check_blockarg(inout %sig : i32) { // CHECK: cf.br ^[[BB2]] // CHECK: } hw.module @loop(inout %in_i : i2) { + %prb0 = llhd.prb %in_i : !hw.inout llhd.process { // TR: -1 cf.br ^body @@ -162,7 +164,7 @@ hw.module @loop(inout %in_i : i2) { cf.cond_br %2, ^loop_continue, ^check ^check: // TR: 1 - llhd.wait (%in_i : !hw.inout), ^body + llhd.wait (%prb0 : i2), ^body ^loop_continue: // TR: 1 %3 = hw.constant 0 : i2 @@ -177,6 +179,8 @@ hw.module @loop(inout %in_i : i2) { // CHECK-LABEL: @complicated // CHECK-SAME: (inout %[[VAL_0:.*]] : i1, inout %[[VAL_1:.*]] : i1, inout %[[VAL_2:.*]] : i1, inout %[[VAL_3:.*]] : i1, inout %[[VAL_4:.*]] : i1) +// CHECK: [[PRB_CLK:%.+]] = llhd.prb %[[VAL_1]] +// CHECK: [[PRB_RST:%.+]] = llhd.prb %[[VAL_0]] // CHECK: llhd.process // CHECK: %[[ALLSET:.*]] = hw.constant true // CHECK: %[[VAL_5:.*]] = hw.constant false @@ -191,7 +195,7 @@ hw.module @loop(inout %in_i : i2) { // CHECK: %[[VAL_10:.*]] = llhd.prb %[[VAL_0]] : !hw.inout // CHECK: %[[VAL_11:.*]] = comb.icmp eq %[[VAL_9]], %[[VAL_5]] : i1 // CHECK: %[[VAL_12:.*]] = comb.icmp ne %[[VAL_10]], %[[VAL_5]] : i1 -// CHECK: llhd.wait (%[[VAL_1]], %[[VAL_0]] : !hw.inout, !hw.inout), ^[[BB3:.+]] +// CHECK: llhd.wait ([[PRB_CLK]], [[PRB_RST]] : i1, i1), ^[[BB3:.+]] // CHECK: ^[[BB3]]: // CHECK: %[[VAL_13:.*]] = llhd.prb %[[VAL_3]] : !hw.inout // CHECK: llhd.store %[[VAL_8]], %[[VAL_13]] : !llhd.ptr @@ -221,6 +225,8 @@ hw.module @loop(inout %in_i : i2) { // CHECK: cf.br ^[[BB1]] // CHECK: } hw.module @complicated(inout %rst_ni: i1, inout %clk_i: i1, inout %async_ack_i: i1, inout %ack_src_q: i1, inout %ack_q: i1) { + %prb_clk = llhd.prb %clk_i : !hw.inout + %prb_rst = llhd.prb %rst_ni : !hw.inout llhd.process { %allset = hw.constant 1 : i1 // TR: -1 @@ -234,7 +240,7 @@ hw.module @complicated(inout %rst_ni: i1, inout %clk_i: i1, inout %async_ack_i: // TR: 2 %clk_i_prb = llhd.prb %clk_i : !hw.inout %rst_ni_prb = llhd.prb %rst_ni : !hw.inout - llhd.wait (%clk_i, %rst_ni : !hw.inout, !hw.inout), ^check + llhd.wait (%prb_clk, %prb_rst : i1, i1), ^check ^check: // TR: 0 %2 = llhd.prb %ack_src_q : !hw.inout diff --git a/test/Dialect/LLHD/Transforms/memoryToBlockArgument.mlir b/test/Dialect/LLHD/Transforms/memoryToBlockArgument.mlir index 50cc527fce9c..2c259e61e333 100644 --- a/test/Dialect/LLHD/Transforms/memoryToBlockArgument.mlir +++ b/test/Dialect/LLHD/Transforms/memoryToBlockArgument.mlir @@ -262,6 +262,7 @@ hw.module @multiple_store_one_block() { // CHECK-LABEL: @loop // CHECK-SAME: (inout %[[VAL_0:.*]] : i2) +// CHECK: [[PRB:%.+]] = llhd.prb %[[VAL_0]] // CHECK: cf.br ^bb1 // CHECK: ^bb1: // CHECK: %[[VAL_1:.*]] = hw.constant 0 : i32 @@ -271,7 +272,7 @@ hw.module @multiple_store_one_block() { // CHECK: %[[VAL_4:.*]] = comb.icmp ult %[[VAL_2]], %[[VAL_3]] : i32 // CHECK: cf.cond_br %[[VAL_4]], ^bb4, ^bb3 // CHECK: ^bb3: -// CHECK: llhd.wait (%[[VAL_0]] : !hw.inout), ^bb1 +// CHECK: llhd.wait ([[PRB]] : i2), ^bb1 // CHECK: ^bb4: // CHECK: %[[VAL_5:.*]] = hw.constant 0 : i2 // CHECK: %[[VAL_6:.*]] = hw.constant 1 : i32 @@ -279,6 +280,7 @@ hw.module @multiple_store_one_block() { // CHECK: cf.br ^bb2(%[[VAL_7]] : i32) // CHECK: } hw.module @loop(inout %in_i : i2) { + %prb = llhd.prb %in_i : !hw.inout llhd.process { cf.br ^body ^body: @@ -291,7 +293,7 @@ hw.module @loop(inout %in_i : i2) { %2 = comb.icmp ult %i_ld, %1 : i32 cf.cond_br %2, ^loop_continue, ^check ^check: - llhd.wait (%in_i : !hw.inout), ^body + llhd.wait (%prb : i2), ^body ^loop_continue: %3 = hw.constant 0 : i2 %5 = hw.constant 1 : i32 diff --git a/test/Dialect/LLHD/Transforms/processLowering.mlir b/test/Dialect/LLHD/Transforms/processLowering.mlir index c29335419820..8420c3cb80e4 100644 --- a/test/Dialect/LLHD/Transforms/processLowering.mlir +++ b/test/Dialect/LLHD/Transforms/processLowering.mlir @@ -23,31 +23,41 @@ hw.module @simpleWait() { // Check wait with observing probed signals // CHECK-LABEL: hw.module @prbAndWait hw.module @prbAndWait(inout %arg0 : i64) { + // CHECK-NEXT: %{{.*}} = llhd.prb + %1 = llhd.prb %arg0 : !hw.inout llhd.process { // CHECK-NEXT: %{{.*}} = llhd.prb // CHECK-NEXT: hw.output cf.br ^bb1 ^bb1: %0 = llhd.prb %arg0 : !hw.inout - llhd.wait (%arg0 : !hw.inout), ^bb1 + llhd.wait (%1 : i64), ^bb1 } } // Check wait with observing probed signals // CHECK-LABEL: hw.module @prbAndWaitMoreObserved hw.module @prbAndWaitMoreObserved(inout %arg0 : i64, inout %arg1 : i64) { + // CHECK-NEXT: %{{.*}} = llhd.prb + %1 = llhd.prb %arg0 : !hw.inout + // CHECK-NEXT: %{{.*}} = llhd.prb + %2 = llhd.prb %arg1 : !hw.inout llhd.process { // CHECK-NEXT: %{{.*}} = llhd.prb // CHECK-NEXT: hw.output cf.br ^bb1 ^bb1: %0 = llhd.prb %arg0 : !hw.inout - llhd.wait (%arg0, %arg1 : !hw.inout, !hw.inout), ^bb1 + llhd.wait (%1, %2 : i64, i64), ^bb1 } } // CHECK-LABEL: hw.module @muxedSignal hw.module @muxedSignal(inout %arg0 : i64, inout %arg1 : i64) { + // CHECK-NEXT: %{{.*}} = llhd.prb + %1 = llhd.prb %arg0 : !hw.inout + // CHECK-NEXT: %{{.*}} = llhd.prb + %2 = llhd.prb %arg1 : !hw.inout llhd.process { cf.br ^bb1 ^bb1: @@ -58,28 +68,32 @@ hw.module @muxedSignal(inout %arg0 : i64, inout %arg1 : i64) { %cond = hw.constant true %sig = comb.mux %cond, %arg0, %arg1 : !hw.inout %0 = llhd.prb %sig : !hw.inout - llhd.wait (%arg0, %arg1 : !hw.inout, !hw.inout), ^bb1 + llhd.wait (%1, %2 : i64, i64), ^bb1 } } // CHECK-LABEL: hw.module @muxedSignal2 hw.module @muxedSignal2(inout %arg0 : i64, inout %arg1 : i64) { + // CHECK-NEXT: %{{.*}} = hw.constant + // CHECK-NEXT: %{{.*}} = comb.mux + // CHECK-NEXT: %{{.*}} = llhd.prb + %cond = hw.constant true + %sig = comb.mux %cond, %arg0, %arg1 : !hw.inout + %0 = llhd.prb %sig : !hw.inout llhd.process { cf.br ^bb1 ^bb1: - // CHECK-NEXT: %{{.*}} = hw.constant - // CHECK-NEXT: %{{.*}} = comb.mux - // CHECK-NEXT: %{{.*}} = llhd.prb + // CHECK-NEXT: comb.and + %1 = comb.and %0, %0 : i64 // CHECK-NEXT: hw.output - %cond = hw.constant true - %sig = comb.mux %cond, %arg0, %arg1 : !hw.inout - %0 = llhd.prb %sig : !hw.inout - llhd.wait (%sig : !hw.inout), ^bb1 + llhd.wait (%0 : i64), ^bb1 } } // CHECK-LABEL: hw.module @partialSignal hw.module @partialSignal(inout %arg0 : i64) { + // CHECK-NEXT: %{{.*}} = llhd.prb + %1 = llhd.prb %arg0 : !hw.inout llhd.process { cf.br ^bb1 ^bb1: @@ -90,6 +104,6 @@ hw.module @partialSignal(inout %arg0 : i64) { %c = hw.constant 16 : i6 %sig = llhd.sig.extract %arg0 from %c : (!hw.inout) -> !hw.inout %0 = llhd.prb %sig : !hw.inout - llhd.wait (%arg0 : !hw.inout), ^bb1 + llhd.wait (%1 : i64), ^bb1 } } diff --git a/test/Dialect/LLHD/Transforms/processLoweringErrors.mlir b/test/Dialect/LLHD/Transforms/processLoweringErrors.mlir index 7e2a6dfe6d83..0ecf43626355 100644 --- a/test/Dialect/LLHD/Transforms/processLoweringErrors.mlir +++ b/test/Dialect/LLHD/Transforms/processLoweringErrors.mlir @@ -6,7 +6,7 @@ hw.module @prbAndWaitNotObserved(inout %arg0 : i64) { cf.br ^bb1 ^bb1: %0 = llhd.prb %arg0 : !hw.inout - // expected-error @+1 {{during process-lowering: the wait terminator is required to have all probed signals as arguments}} + // expected-error @+1 {{during process-lowering: the wait terminator is required to have values used in the process as arguments}} llhd.wait ^bb1 } } @@ -17,9 +17,10 @@ hw.module @prbAndWaitNotObserved(inout %arg0 : i64) { hw.module @blockArgumentsNotAllowed(inout %arg0 : i64) { // expected-error @+1 {{during process-lowering: the second block (containing the llhd.wait) is not allowed to have arguments}} llhd.process { - cf.br ^bb1(%arg0 : !hw.inout) - ^bb1(%a : !hw.inout): - llhd.wait ^bb1(%a : !hw.inout) + %prb = llhd.prb %arg0 : !hw.inout + cf.br ^bb1(%prb : i64) + ^bb1(%a : i64): + llhd.wait ^bb1(%a: i64) } } @@ -83,7 +84,7 @@ hw.module @muxedSignal(inout %arg0 : i64, inout %arg1 : i64, inout %arg2 : i1) { %cond = llhd.prb %arg2 : !hw.inout %sig = comb.mux %cond, %arg0, %arg1 : !hw.inout %0 = llhd.prb %sig : !hw.inout - // expected-error @+1 {{during process-lowering: the wait terminator is required to have all probed signals as arguments}} - llhd.wait (%arg0, %arg2 : !hw.inout, !hw.inout), ^bb1 + // expected-error @+1 {{during process-lowering: the wait terminator is required to have values used in the process as arguments}} + llhd.wait (%cond : i1), ^bb1 } } diff --git a/test/Dialect/LLHD/Transforms/temporal-code-motion.mlir b/test/Dialect/LLHD/Transforms/temporal-code-motion.mlir index 00889d6d1277..fb5c8d03946c 100644 --- a/test/Dialect/LLHD/Transforms/temporal-code-motion.mlir +++ b/test/Dialect/LLHD/Transforms/temporal-code-motion.mlir @@ -32,6 +32,14 @@ hw.module @basic(in %cond: i1) { %n = llhd.sig %c0_i5 : i5 %o = llhd.sig %c0_i5 : i5 + %prb_k = llhd.prb %k : !hw.inout + %prb_c = llhd.prb %c : !hw.inout + %prb_e = llhd.prb %e : !hw.inout + %prb_h = llhd.prb %h : !hw.inout + %prb_d = llhd.prb %d : !hw.inout + %prb_f = llhd.prb %f : !hw.inout + %prb_g = llhd.prb %g : !hw.inout + // COM: Check that an auxillary block is created and all drives are moved to // COM: the exit block with the correct enable condition // CHECK: llhd.process @@ -41,7 +49,7 @@ hw.module @basic(in %cond: i1) { // CHECK: ^[[BB1]]: ^bb1: // CHECK: llhd.wait ({{.*}}), ^[[BB2:.+]] - llhd.wait (%k, %c, %e, %h, %d, %f, %g : !hw.inout, !hw.inout, !hw.inout, !hw.inout, !hw.inout, !hw.inout, !hw.inout), ^bb2 + llhd.wait (%prb_k, %prb_c, %prb_e, %prb_h, %prb_d, %prb_f, %prb_g : i5, i1, i1, i4, i1, i1, i4), ^bb2 // CHECK: ^[[BB2]]: ^bb2: // CHECK: [[V14:%.+]] = llhd.prb %k From fcbd718c43347b4aeef03e2eec1716905aeb8519 Mon Sep 17 00:00:00 2001 From: Will Dietz Date: Mon, 19 Aug 2024 12:26:53 -0500 Subject: [PATCH 110/119] [FIRRTL][FIRParser] Cache property constants. (#7530) Greatly reduces IR size generated in presence of many repeat constants as commonly occurs in practice due to the data-like nature of classes / properties. Same tricky as done for FIRRTL integer constants, use single cache/code for both. --- lib/Dialect/FIRRTL/Import/FIRParser.cpp | 36 +++++++++++++++---------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/lib/Dialect/FIRRTL/Import/FIRParser.cpp b/lib/Dialect/FIRRTL/Import/FIRParser.cpp index 7470717acab3..69dc43049589 100644 --- a/lib/Dialect/FIRRTL/Import/FIRParser.cpp +++ b/lib/Dialect/FIRRTL/Import/FIRParser.cpp @@ -1301,8 +1301,9 @@ struct FIRModuleContext : public FIRParser { llvm::DenseMap, Value> constantCache; /// Get a cached constant. - Value getCachedConstantInt(ImplicitLocOpBuilder &builder, Attribute attr, - IntType type, APInt &value) { + template + Value getCachedConstant(ImplicitLocOpBuilder &builder, Attribute attr, + Type type, Args &&...args) { auto &result = constantCache[{attr, type}]; if (result) return result; @@ -1312,15 +1313,15 @@ struct FIRModuleContext : public FIRParser { OpBuilder::InsertPoint savedIP; auto *parentOp = builder.getInsertionBlock()->getParentOp(); - if (!isa(parentOp)) { + if (!isa(parentOp)) { savedIP = builder.saveInsertionPoint(); - while (!isa(parentOp)) { + while (!isa(parentOp)) { builder.setInsertionPoint(parentOp); parentOp = builder.getInsertionBlock()->getParentOp(); } } - result = builder.create(type, value); + result = builder.create(type, std::forward(args)...); if (savedIP.isSet()) builder.setInsertionPoint(savedIP.getBlock(), savedIP.getPoint()); @@ -1913,8 +1914,9 @@ ParseResult FIRStmtParser::parseExpImpl(Value &result, const Twine &message, "expected string literal in String expression") || parseToken(FIRToken::r_paren, "expected ')' in String expression")) return failure(); - result = builder.create( - builder.getStringAttr(FIRToken::getStringValue(spelling))); + auto attr = builder.getStringAttr(FIRToken::getStringValue(spelling)); + result = moduleContext.getCachedConstant( + builder, attr, builder.getType(), attr); break; } case FIRToken::kw_Integer: { @@ -1927,8 +1929,10 @@ ParseResult FIRStmtParser::parseExpImpl(Value &result, const Twine &message, parseIntLit(value, "expected integer literal in Integer expression") || parseToken(FIRToken::r_paren, "expected ')' in Integer expression")) return failure(); - result = - builder.create(APSInt(value, /*isUnsigned=*/false)); + APSInt apint(value, /*isUnsigned=*/false); + result = moduleContext.getCachedConstant( + builder, IntegerAttr::get(getContext(), apint), + builder.getType(), apint); break; } case FIRToken::kw_Bool: { @@ -1947,7 +1951,9 @@ ParseResult FIRStmtParser::parseExpImpl(Value &result, const Twine &message, return emitError("expected true or false in Bool expression"); if (parseToken(FIRToken::r_paren, "expected ')' in Bool expression")) return failure(); - result = builder.create(value); + auto attr = builder.getBoolAttr(value); + result = moduleContext.getCachedConstant( + builder, attr, builder.getType(), value); break; } case FIRToken::kw_Double: { @@ -1967,7 +1973,9 @@ ParseResult FIRStmtParser::parseExpImpl(Value &result, const Twine &message, double d; if (!llvm::to_float(spelling, d)) return emitError("invalid double"); - result = builder.create(builder.getF64FloatAttr(d)); + auto attr = builder.getF64FloatAttr(d); + result = moduleContext.getCachedConstant( + builder, attr, builder.getType(), attr); break; } case FIRToken::kw_List: { @@ -2424,7 +2432,7 @@ ParseResult FIRStmtParser::parseIntegerLiteralExp(Value &result) { } locationProcessor.setLoc(loc); - result = moduleContext.getCachedConstantInt(builder, attr, type, value); + result = moduleContext.getCachedConstant(builder, attr, type, attr); return success(); } @@ -3749,7 +3757,7 @@ ParseResult FIRStmtParser::parseRefForceInitial() { value.getBitWidth(), IntegerType::Unsigned), value); - auto pred = moduleContext.getCachedConstantInt(builder, attr, type, value); + auto pred = moduleContext.getCachedConstant(builder, attr, type, attr); builder.create(pred, dest, src); return success(); @@ -3812,7 +3820,7 @@ ParseResult FIRStmtParser::parseRefReleaseInitial() { value.getBitWidth(), IntegerType::Unsigned), value); - auto pred = moduleContext.getCachedConstantInt(builder, attr, type, value); + auto pred = moduleContext.getCachedConstant(builder, attr, type, attr); builder.create(pred, dest); return success(); From 31ed547abb6ecadb4d723831199a76112eee4c2f Mon Sep 17 00:00:00 2001 From: Schuyler Eldridge Date: Mon, 19 Aug 2024 19:18:19 -0400 Subject: [PATCH 111/119] [FIRRTL] Combine LowerLayers firtool tests, NFC Combine two tests of LowerLayers end-to-end behavior into a single `-split-input-file` test. This is done to add more tests to this file in the future. Signed-off-by: Schuyler Eldridge --- test/firtool/lower-layers.fir | 109 ++++++++++++++++++++++++++++++++- test/firtool/lower-layers2.fir | 106 -------------------------------- 2 files changed, 108 insertions(+), 107 deletions(-) delete mode 100644 test/firtool/lower-layers2.fir diff --git a/test/firtool/lower-layers.fir b/test/firtool/lower-layers.fir index 8342bba67d5a..718c8e909e85 100644 --- a/test/firtool/lower-layers.fir +++ b/test/firtool/lower-layers.fir @@ -1,4 +1,4 @@ -; RUN: firtool -verilog %s | FileCheck %s +; RUN: firtool %s -disable-all-randomization -split-input-file | FileCheck %s ; This is an end-to-end example of a test-bench (Foo) enabling verification, ; probing into a device-under-test (Bar), and reading from hardware which is @@ -70,3 +70,110 @@ circuit Foo: %[[ ; CHECK: .a (d) ; CHECK: ); ; CHECK: endmodule + +; This is an end-to-end example of a test-harness enabling verification, probing +; into a device-under-test, and reading from hardware which is only present if +; the verification layer is enabled. + +; // ----- + +FIRRTL version 4.0.0 + +circuit TestHarness: + + layer Verification, bind: + + ; CHECK: module DUT_Verification( + ; CHECK: input clock, + ; CHECK: input [31:0] a + ; CHECK: ); + ; CHECK: reg [31:0] pc_d; + ; CHECK: wire [31:0] pc_d_probe = pc_d; + ; CHECK: always @(posedge clock) + ; CHECK: pc_d <= a; + ; CHECK: endmodule + + ; CHECK: module DUT( + ; CHECK: input clock, + ; CHECK: input [31:0] a, + ; CHECK: output [31:0] b + ; CHECK: ); + ; CHECK: reg [31:0] pc; + ; CHECK: always @(posedge clock) + ; CHECK: pc <= a; + ; CHECK: assign b = pc; + ; CHECK: endmodule + module DUT: + input clock: Clock + input reset: UInt<1> + input a: UInt<32> + output b: UInt<32> + output trace: Probe, Verification> + + reg pc: UInt<32>, clock + connect pc, a + connect b, pc + + wire x : Probe, Verification> + + layerblock Verification: + reg pc_d: UInt<32>, clock + connect pc_d, a + define x = probe(pc_d) + + layerblock Verification: + define trace = x + + ; CHECK: module TestHarness_Verification( + ; CHECK: input [31:0] dut_trace, + ; CHECK: input clock, + ; CHECK: reset + ; CHECK: ); + ; CHECK: `ifndef SYNTHESIS + ; CHECK: always @(posedge clock) begin + ; CHECK: if ((`PRINTF_COND_) & reset) + ; CHECK: $fwrite(32'h80000002, "The last PC was: %x", dut_trace); + ; CHECK: end // always @(posedge) + ; CHECK: `endif // not def SYNTHESIS + ; CHECK: endmodule + + ; CHECK: module TestHarness( + ; CHECK: input clock, + ; CHECK: reset, + ; CHECK: input [31:0] a, + ; CHECK: output [31:0] b + ; CHECK: ); + ; CHECK: DUT dut ( + ; CHECK: .clock (clock), + ; CHECK: .a (a), + ; CHECK: .b (b) + ; CHECK: ); + ; CHECK: endmodule + public module TestHarness: + input clock: Clock + input reset: UInt<1> + input a: UInt<32> + output b: UInt<32> + + inst dut of DUT + connect dut.clock, clock + connect dut.reset, reset + connect dut.a, a + connect b, dut.b + + layerblock Verification: + printf(clock, reset, "The last PC was: %x", read(dut.trace)) + +; CHECK: FILE "layers_TestHarness_Verification.sv" +; CHECK: `ifndef layers_TestHarness_Verification +; CHECK: `define layers_TestHarness_Verification +; CHECK: bind DUT DUT_Verification verification ( +; CHECK: .clock (clock), +; CHECK: .a (a) +; CHECK: ); +; CHECK: bind TestHarness TestHarness_Verification verification ( +; CHECK: .dut_trace (TestHarness.dut.verification.pc_d_probe), +; CHECK: .clock (clock), +; CHECK: .reset (reset) +; CHECK: ); +; CHECK: `endif // layers_TestHarness_Verification diff --git a/test/firtool/lower-layers2.fir b/test/firtool/lower-layers2.fir deleted file mode 100644 index 13c4e566c79f..000000000000 --- a/test/firtool/lower-layers2.fir +++ /dev/null @@ -1,106 +0,0 @@ -; RUN: firtool -verilog -disable-all-randomization %s | FileCheck %s - -; This is an end-to-end example of a test-harness enabling verification, probing -; into a device-under-test, and reading from hardware which is only present if -; the verification layer is enabled. - -FIRRTL version 4.0.0 - -circuit TestHarness: - - layer Verification, bind: - - ; CHECK: module DUT_Verification( - ; CHECK: input clock, - ; CHECK: input [31:0] a - ; CHECK: ); - ; CHECK: reg [31:0] pc_d; - ; CHECK: wire [31:0] pc_d_probe = pc_d; - ; CHECK: always @(posedge clock) - ; CHECK: pc_d <= a; - ; CHECK: endmodule - - ; CHECK: module DUT( - ; CHECK: input clock, - ; CHECK: input [31:0] a, - ; CHECK: output [31:0] b - ; CHECK: ); - ; CHECK: reg [31:0] pc; - ; CHECK: always @(posedge clock) - ; CHECK: pc <= a; - ; CHECK: assign b = pc; - ; CHECK: endmodule - module DUT: - input clock: Clock - input reset: UInt<1> - input a: UInt<32> - output b: UInt<32> - output trace: Probe, Verification> - - reg pc: UInt<32>, clock - connect pc, a - connect b, pc - - wire x : Probe, Verification> - - layerblock Verification: - reg pc_d: UInt<32>, clock - connect pc_d, a - define x = probe(pc_d) - - layerblock Verification: - define trace = x - - ; CHECK: module TestHarness_Verification( - ; CHECK: input [31:0] dut_trace, - ; CHECK: input clock, - ; CHECK: reset - ; CHECK: ); - ; CHECK: `ifndef SYNTHESIS - ; CHECK: always @(posedge clock) begin - ; CHECK: if ((`PRINTF_COND_) & reset) - ; CHECK: $fwrite(32'h80000002, "The last PC was: %x", dut_trace); - ; CHECK: end // always @(posedge) - ; CHECK: `endif // not def SYNTHESIS - ; CHECK: endmodule - - ; CHECK: module TestHarness( - ; CHECK: input clock, - ; CHECK: reset, - ; CHECK: input [31:0] a, - ; CHECK: output [31:0] b - ; CHECK: ); - ; CHECK: DUT dut ( - ; CHECK: .clock (clock), - ; CHECK: .a (a), - ; CHECK: .b (b) - ; CHECK: ); - ; CHECK: endmodule - public module TestHarness: - input clock: Clock - input reset: UInt<1> - input a: UInt<32> - output b: UInt<32> - - inst dut of DUT - connect dut.clock, clock - connect dut.reset, reset - connect dut.a, a - connect b, dut.b - - layerblock Verification: - printf(clock, reset, "The last PC was: %x", read(dut.trace)) - -; CHECK: // ----- 8< ----- FILE "layers_TestHarness_Verification.sv" ----- 8< ----- -; CHECK: `ifndef layers_TestHarness_Verification -; CHECK: `define layers_TestHarness_Verification -; CHECK: bind DUT DUT_Verification verification ( -; CHECK: .clock (clock), -; CHECK: .a (a) -; CHECK: ); -; CHECK: bind TestHarness TestHarness_Verification verification ( -; CHECK: .dut_trace (TestHarness.dut.verification.pc_d_probe), -; CHECK: .clock (clock), -; CHECK: .reset (reset) -; CHECK: ); -; CHECK: `endif // layers_TestHarness_Verification From a2eec9e062e22916b087a0c22e374aab333bed57 Mon Sep 17 00:00:00 2001 From: Schuyler Eldridge Date: Mon, 19 Aug 2024 19:19:14 -0400 Subject: [PATCH 112/119] [FIRRTL] Whitespace test cleanup, NFC Signed-off-by: Schuyler Eldridge --- test/firtool/lower-layers.fir | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/firtool/lower-layers.fir b/test/firtool/lower-layers.fir index 718c8e909e85..effe49842a00 100644 --- a/test/firtool/lower-layers.fir +++ b/test/firtool/lower-layers.fir @@ -67,7 +67,7 @@ circuit Foo: %[[ ; CHECK: module Foo(); ; CHECK: wire d = Foo.bar.verification.c_probe; ; CHECK: Bar bar ( - ; CHECK: .a (d) + ; CHECK: .a (d) ; CHECK: ); ; CHECK: endmodule From fb052d901ad2c4aeef885887a5868e1eeedd49e0 Mon Sep 17 00:00:00 2001 From: Schuyler Eldridge Date: Mon, 19 Aug 2024 19:47:24 -0400 Subject: [PATCH 113/119] [FIRRTL] Fix comment placement in test, NFC Signed-off-by: Schuyler Eldridge --- test/firtool/lower-layers.fir | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/firtool/lower-layers.fir b/test/firtool/lower-layers.fir index effe49842a00..930e72ba1015 100644 --- a/test/firtool/lower-layers.fir +++ b/test/firtool/lower-layers.fir @@ -71,12 +71,12 @@ circuit Foo: %[[ ; CHECK: ); ; CHECK: endmodule +; // ----- + ; This is an end-to-end example of a test-harness enabling verification, probing ; into a device-under-test, and reading from hardware which is only present if ; the verification layer is enabled. -; // ----- - FIRRTL version 4.0.0 circuit TestHarness: From 898eb8b926102f039c6c0152fc67d191e76469a9 Mon Sep 17 00:00:00 2001 From: Martin Erhart Date: Tue, 20 Aug 2024 12:35:24 +0100 Subject: [PATCH 114/119] [MooreToCore] Add always_comb and always_latch lowering support (#7532) --- lib/Conversion/MooreToCore/MooreToCore.cpp | 122 +++++++++++---------- test/Conversion/MooreToCore/basic.mlir | 74 +++++++++++++ 2 files changed, 137 insertions(+), 59 deletions(-) diff --git a/lib/Conversion/MooreToCore/MooreToCore.cpp b/lib/Conversion/MooreToCore/MooreToCore.cpp index a5e184051121..0913f42afc73 100644 --- a/lib/Conversion/MooreToCore/MooreToCore.cpp +++ b/lib/Conversion/MooreToCore/MooreToCore.cpp @@ -19,9 +19,11 @@ #include "mlir/Dialect/Func/IR/FuncOps.h" #include "mlir/Dialect/SCF/IR/SCF.h" #include "mlir/IR/BuiltinDialect.h" +#include "mlir/IR/Iterators.h" #include "mlir/Interfaces/SideEffectInterfaces.h" #include "mlir/Pass/Pass.h" #include "mlir/Transforms/DialectConversion.h" +#include "mlir/Transforms/RegionUtils.h" #include "llvm/ADT/TypeSwitch.h" namespace circt { @@ -103,30 +105,6 @@ static hw::ModulePortInfo getModulePortInfo(const TypeConverter &typeConverter, return hw::ModulePortInfo(inputs, outputs); } -/// Erase all dead blocks in a region. -static void eraseDeadBlocks(PatternRewriter &rewriter, Region ®ion) { - SmallVector worklist; - for (auto &block : llvm::make_early_inc_range(llvm::drop_begin(region, 1))) { - if (!block.use_empty()) - continue; - worklist.push_back(&block); - while (!worklist.empty()) { - auto *block = worklist.pop_back_val(); - if (!block->use_empty()) - continue; - for (auto *successor : block->getSuccessors()) - worklist.push_back(successor); - rewriter.eraseBlock(block); - } - } -} - -/// Erase all dead blocks in an op. -static void eraseDeadBlocks(PatternRewriter &rewriter, Operation *op) { - for (auto ®ion : op->getRegions()) - eraseDeadBlocks(rewriter, region); -} - //===----------------------------------------------------------------------===// // Structural Conversion //===----------------------------------------------------------------------===// @@ -188,16 +166,63 @@ struct InstanceOpConversion : public OpConversionPattern { } }; +static void getValuesToObserve(Region *region, + function_ref setInsertionPoint, + const TypeConverter *typeConverter, + ConversionPatternRewriter &rewriter, + SmallVector &observeValues) { + SmallDenseSet alreadyObserved; + Location loc = region->getLoc(); + + auto probeIfSignal = [&](Value value) -> Value { + if (!isa(value.getType())) + return value; + return rewriter.create(loc, value); + }; + + region->getParentOp()->walk>( + [&](Operation *operation) { + for (auto value : operation->getOperands()) { + if (region->isAncestor(value.getParentRegion())) + continue; + if (!alreadyObserved.insert(value).second) + continue; + + OpBuilder::InsertionGuard g(rewriter); + if (auto remapped = rewriter.getRemappedValue(value)) { + setInsertionPoint(remapped); + observeValues.push_back(probeIfSignal(remapped)); + } else { + setInsertionPoint(value); + auto type = typeConverter->convertType(value.getType()); + auto converted = typeConverter->materializeTargetConversion( + rewriter, loc, type, value); + observeValues.push_back(probeIfSignal(converted)); + } + } + }); +} + struct ProcedureOpConversion : public OpConversionPattern { using OpConversionPattern::OpConversionPattern; LogicalResult matchAndRewrite(ProcedureOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override { + // Collect values to observe before we do any modifications to the region. + SmallVector observedValues; + if (op.getKind() == ProcedureKind::AlwaysComb || + op.getKind() == ProcedureKind::AlwaysLatch) { + auto setInsertionPoint = [&](Value value) { + rewriter.setInsertionPoint(op); + }; + getValuesToObserve(&op.getBody(), setInsertionPoint, typeConverter, + rewriter, observedValues); + } + auto loc = op.getLoc(); if (failed(rewriter.convertRegionTypes(&op.getBody(), *typeConverter))) return failure(); - eraseDeadBlocks(rewriter, op); // Handle initial and final procedures. These lower to a corresponding // `llhd.process` or `llhd.final` op that executes the body and then halts. @@ -239,9 +264,10 @@ struct ProcedureOpConversion : public OpConversionPattern { // jumping back up to the body. if (op.getKind() == ProcedureKind::AlwaysComb || op.getKind() == ProcedureKind::AlwaysLatch) { - block = rewriter.createBlock(&newOp.getBody()); - // TODO: Collect observed signals and add LLHD wait op. - return failure(); + Block *waitBlock = rewriter.createBlock(&newOp.getBody()); + rewriter.create(loc, observedValues, Value(), ValueRange{}, + block); + block = waitBlock; } // Make all `moore.return` ops branch back up to the beginning of the @@ -322,6 +348,10 @@ struct WaitEventOpConversion : public OpConversionPattern { rewriter.eraseOp(detectOp); } + // Determine the values used during event detection that are defined outside + // the `wait_event`'s body region. We want to wait for a change on these + // signals before we check if any interesting event happened. + SmallVector observeValues; auto setInsertionPointAfterDef = [&](Value value) { if (auto *op = value.getDefiningOp()) rewriter.setInsertionPointAfter(op); @@ -329,37 +359,8 @@ struct WaitEventOpConversion : public OpConversionPattern { rewriter.setInsertionPointToStart(value.getParentBlock()); }; - auto probeIfSignal = [&](Value value) -> Value { - if (!isa(value.getType())) - return value; - return rewriter.create(loc, value); - }; - - // Determine the values used during event detection that are defined outside - // the `wait_event`'s body region. We want to wait for a change on these - // signals before we check if any interesting event happened. - SmallVector observeValues; - SmallDenseSet alreadyObserved; - clonedOp.getBody().walk([&](Operation *operation) { - for (auto value : operation->getOperands()) { - if (clonedOp.getBody().isAncestor(value.getParentRegion())) - continue; - if (!alreadyObserved.insert(value).second) - continue; - if (auto remapped = rewriter.getRemappedValue(value)) { - OpBuilder::InsertionGuard g(rewriter); - setInsertionPointAfterDef(remapped); - observeValues.push_back(probeIfSignal(remapped)); - } else { - OpBuilder::InsertionGuard g(rewriter); - setInsertionPointAfterDef(value); - auto type = typeConverter->convertType(value.getType()); - auto converted = typeConverter->materializeTargetConversion( - rewriter, loc, type, value); - observeValues.push_back(probeIfSignal(converted)); - } - } - }); + getValuesToObserve(&clonedOp.getBody(), setInsertionPointAfterDef, + typeConverter, rewriter, observeValues); // Create the `llhd.wait` op that suspends the current process and waits for // a change in the interesting values listed in `observeValues`. When a @@ -1388,6 +1389,9 @@ void MooreToCorePass::runOnOperation() { MLIRContext &context = getContext(); ModuleOp module = getOperation(); + IRRewriter rewriter(module); + (void)mlir::eraseUnreachableBlocks(rewriter, module->getRegions()); + ConversionTarget target(context); TypeConverter typeConverter; RewritePatternSet patterns(&context); diff --git a/test/Conversion/MooreToCore/basic.mlir b/test/Conversion/MooreToCore/basic.mlir index 3a0c87213131..b90dbb6f7958 100644 --- a/test/Conversion/MooreToCore/basic.mlir +++ b/test/Conversion/MooreToCore/basic.mlir @@ -691,6 +691,45 @@ moore.module @WaitEvent() { moore.return } + // CHECK: [[FALSE:%.+]] = hw.constant false + // CHECK: [[PRB_A:%.+]] = llhd.prb %a + // CHECK: [[PRB_B:%.+]] = llhd.prb %b + // CHECK: llhd.process { + %cond = moore.constant 0 : i1 + moore.procedure always_comb { + // CHECK: cf.br ^[[BB1:.+]] + // CHECK: ^[[BB1]]: + // CHECK: llhd.prb %a + // CHECK: llhd.prb %b + // CHECK: cf.br ^[[BB2:.+]] + // CHECK: ^[[BB2]]: + // CHECK: llhd.wait ([[FALSE]], [[PRB_A]], [[PRB_B]] : {{.*}}), ^[[BB1]] + %1 = moore.conditional %cond : i1 -> i1 { + %2 = moore.read %a : + moore.yield %2 : !moore.i1 + } { + %3 = moore.read %b : + moore.yield %3 : !moore.i1 + } + moore.return + } + + // CHECK: [[PRB_D:%.+]] = llhd.prb %d + // CHECK: llhd.process { + // CHECK: cf.br ^[[BB1:.+]] + // CHECK: ^[[BB1]]: + // CHECK: llhd.prb %d + // CHECK: cf.br ^[[BB2:.+]] + // CHECK: ^[[BB2]]: + // CHECK: llhd.wait ([[PRB_D]] : {{.*}}), ^[[BB1]] + moore.procedure always_latch { + %3 = moore.read %d : + moore.return + } + + // CHECK: %d = llhd.sig %false + %d = moore.variable : + // CHECK: llhd.process { moore.procedure initial { // CHECK: llhd.wait ([[PRB_A6]], [[PRB_B2]] : @@ -708,3 +747,38 @@ moore.module @WaitEvent() { moore.return } } + +// Just check that block without predecessors are handled without crashing +// CHECK-LABEL: @NoPredecessorBlockErasure +moore.module @NoPredecessorBlockErasure(in %clk_i : !moore.l1, in %raddr_i : !moore.array<2 x l5>, out rdata_o : !moore.array<2 x l32>, in %waddr_i : !moore.array<1 x l5>, in %wdata_i : !moore.array<1 x l32>, in %we_i : !moore.l1) { + %0 = moore.constant 0 : l32 + %1 = moore.constant 1 : i32 + %2 = moore.constant 0 : i32 + %rdata_o = moore.variable : > + %mem = moore.variable : > + moore.procedure always_ff { + cf.br ^bb1(%2 : !moore.i32) + ^bb1(%4: !moore.i32): // 2 preds: ^bb0, ^bb8 + moore.return + ^bb2: // no predecessors + cf.br ^bb3(%2 : !moore.i32) + ^bb3(%5: !moore.i32): // 2 preds: ^bb2, ^bb6 + cf.br ^bb8 + ^bb4: // no predecessors + cf.br ^bb6 + ^bb5: // no predecessors + cf.br ^bb6 + ^bb6: // 2 preds: ^bb4, ^bb5 + %6 = moore.add %5, %1 : i32 + cf.br ^bb3(%6 : !moore.i32) + ^bb7: // no predecessors + %7 = moore.extract_ref %mem from 0 : > -> + moore.nonblocking_assign %7, %0 : l32 + cf.br ^bb8 + ^bb8: // 2 preds: ^bb3, ^bb7 + %8 = moore.add %4, %1 : i32 + cf.br ^bb1(%8 : !moore.i32) + } + %3 = moore.read %rdata_o : > + moore.output %3 : !moore.array<2 x l32> +} From 29b1c1cc766851cb265854e4540ef8158b6986b9 Mon Sep 17 00:00:00 2001 From: John Demme Date: Tue, 20 Aug 2024 22:52:52 +0200 Subject: [PATCH 115/119] [ESI][Runtime] Generate C++ header files for constants (#7517) Start of static C++ header file generation. Just integer constants for now. Can work off of any manifest and will even connect to a live accelerator and read the manifest from there. Generates a `types.h` file and one per module listed in the manifest. Inside the module header file, generates one class per module and adds the constants to that class. Puts _everything_ in a namespace specified by the user as `system_name`. --- .../Dialect/ESI/runtime/callback.mlir | 10 +- .../Dialect/ESI/runtime/loopback.mlir | 41 +++- .../Dialect/ESI/runtime/loopback.mlir.cpp | 5 + integration_test/lit.cfg.py | 3 +- lib/Dialect/ESI/runtime/CMakeLists.txt | 1 + .../runtime/cpp/include/esi/backends/Trace.h | 3 + .../ESI/runtime/cpp/lib/backends/Trace.cpp | 35 +++- lib/Dialect/ESI/runtime/pyproject.toml | 1 + .../ESI/runtime/python/esiaccel/codegen.py | 197 ++++++++++++++++++ .../runtime/python/esiaccel/esiCppAccel.cpp | 9 +- .../ESI/runtime/python/esiaccel/types.py | 1 + .../ESI/runtime/python/esiaccel/utils.py | 6 + 12 files changed, 290 insertions(+), 22 deletions(-) create mode 100644 integration_test/Dialect/ESI/runtime/loopback.mlir.cpp create mode 100644 lib/Dialect/ESI/runtime/python/esiaccel/codegen.py diff --git a/integration_test/Dialect/ESI/runtime/callback.mlir b/integration_test/Dialect/ESI/runtime/callback.mlir index e383d88b9af5..35dd8418bdb3 100644 --- a/integration_test/Dialect/ESI/runtime/callback.mlir +++ b/integration_test/Dialect/ESI/runtime/callback.mlir @@ -1,9 +1,13 @@ // REQUIRES: esi-cosim, esi-runtime, rtl-sim + +// Generate SV files // RUN: rm -rf %t6 && mkdir %t6 && cd %t6 -// RUN: circt-opt %s --esi-connect-services --esi-appid-hier=top=top --esi-build-manifest=top=top --esi-clean-metadata > %t4.mlir -// RUN: circt-opt %t4.mlir --lower-esi-to-physical --lower-esi-bundles --lower-esi-ports --lower-esi-to-hw=platform=cosim --lower-seq-to-sv --lower-hwarith-to-hw --canonicalize --export-split-verilog -o %t3.mlir +// RUN: mkdir hw && cd hw +// RUN: circt-opt %s --esi-connect-services --esi-appid-hier=top=top --esi-build-manifest=top=top --esi-clean-metadata --lower-esi-to-physical --lower-esi-bundles --lower-esi-ports --lower-esi-to-hw=platform=cosim --lower-seq-to-sv --lower-hwarith-to-hw --canonicalize --export-split-verilog // RUN: cd .. -// RUN: esi-cosim.py --source %t6 --top top -- esitester cosim env wait | FileCheck %s + +// Test cosimulation +// RUN: esi-cosim.py --source %t6/hw --top top -- esitester cosim env wait | FileCheck %s hw.module @top(in %clk : !seq.clock, in %rst : i1) { hw.instance "PrintfExample" sym @PrintfExample @PrintfExample(clk: %clk: !seq.clock, rst: %rst: i1) -> () diff --git a/integration_test/Dialect/ESI/runtime/loopback.mlir b/integration_test/Dialect/ESI/runtime/loopback.mlir index 58d039724549..92dd32b7b934 100644 --- a/integration_test/Dialect/ESI/runtime/loopback.mlir +++ b/integration_test/Dialect/ESI/runtime/loopback.mlir @@ -1,12 +1,28 @@ // REQUIRES: esi-cosim, esi-runtime, rtl-sim // RUN: rm -rf %t6 && mkdir %t6 && cd %t6 -// RUN: circt-opt %s --esi-connect-services --esi-appid-hier=top=top --esi-build-manifest=top=top --esi-clean-metadata > %t4.mlir -// RUN: circt-opt %t4.mlir --lower-esi-to-physical --lower-esi-bundles --lower-esi-ports --lower-esi-to-hw=platform=cosim --lower-seq-to-sv --lower-hwarith-to-hw --canonicalize --export-split-verilog -o %t3.mlir + +// Generate SV files +// RUN: mkdir hw && cd hw +// RUN: circt-opt %s --esi-connect-services --esi-appid-hier=top=top --esi-build-manifest=top=top --esi-clean-metadata --lower-esi-to-physical --lower-esi-bundles --lower-esi-ports --lower-esi-to-hw=platform=cosim --lower-seq-to-sv --lower-hwarith-to-hw --canonicalize --export-split-verilog -o %t3.mlir // RUN: cd .. -// RUN: esiquery trace w:%t6/esi_system_manifest.json info | FileCheck %s --check-prefix=QUERY-INFO -// RUN: esiquery trace w:%t6/esi_system_manifest.json hier | FileCheck %s --check-prefix=QUERY-HIER -// RUN: %python %s.py trace w:%t6/esi_system_manifest.json -// RUN: esi-cosim.py --source %t6 --top top -- %python %s.py cosim env + +// Test ESI utils +// RUN: esiquery trace w:%t6/hw/esi_system_manifest.json info | FileCheck %s --check-prefix=QUERY-INFO +// RUN: esiquery trace w:%t6/hw/esi_system_manifest.json hier | FileCheck %s --check-prefix=QUERY-HIER + +// Test cosimulation +// RUN: esi-cosim.py --source %t6/hw --top top -- %python %s.py cosim env + +// Test C++ header generation against the manifest file +// RUN: %python -m esiaccel.cppgen --file %t6/hw/esi_system_manifest.json --output-dir %t6/include/loopback/ +// RUN: %host_cxx -I %t6/include %s.cpp -o %t6/test +// RUN: %t6/test | FileCheck %s --check-prefix=CPP-TEST +// RUN: FileCheck %s --check-prefix=LOOPBACK-H --input-file %t6/include/loopback/LoopbackIP.h + +// Test C++ header generation against a live accelerator +// RUN: esi-cosim.py --source %t6 --top top -- %python -m esiaccel.cppgen --platform cosim --connection env --output-dir %t6/include/loopback/ +// RUN: %host_cxx -I %t6/include %s.cpp -o %t6/test +// RUN: %t6/test | FileCheck %s --check-prefix=CPP-TEST !sendI8 = !esi.bundle<[!esi.channel from "send"]> !recvI8 = !esi.bundle<[!esi.channel to "recv"]> @@ -109,6 +125,8 @@ hw.module @top(in %clk: !seq.clock, in %rst: i1) { hw.instance "loopback_array" @LoopbackArray() -> () } +// CPP-TEST: depth: 0x5 + // QUERY-INFO: API version: 0 // QUERY-INFO: ******************************** // QUERY-INFO: * Module information @@ -157,3 +175,14 @@ hw.module @top(in %clk: !seq.clock, in %rst: i1) { // QUERY-HIER: recv: !esi.channel // QUERY-HIER: mysvc_send: // QUERY-HIER: send: !esi.channel + + +// LOOPBACK-H: /// Generated header for esi_system module LoopbackIP. +// LOOPBACK-H-NEXT: #pragma once +// LOOPBACK-H-NEXT: #include "types.h" +// LOOPBACK-H-LABEL: namespace esi_system { +// LOOPBACK-H-LABEL: class LoopbackIP { +// LOOPBACK-H-NEXT: public: +// LOOPBACK-H-NEXT: static constexpr uint32_t depth = 0x5; +// LOOPBACK-H-NEXT: }; +// LOOPBACK-H-NEXT: } // namespace esi_system diff --git a/integration_test/Dialect/ESI/runtime/loopback.mlir.cpp b/integration_test/Dialect/ESI/runtime/loopback.mlir.cpp new file mode 100644 index 000000000000..f9950359498a --- /dev/null +++ b/integration_test/Dialect/ESI/runtime/loopback.mlir.cpp @@ -0,0 +1,5 @@ +#include "loopback/LoopbackIP.h" + +#include + +int main() { printf("depth: 0x%x\n", esi_system::LoopbackIP::depth); } diff --git a/integration_test/lit.cfg.py b/integration_test/lit.cfg.py index 6788cfa8a9f8..f3de280b120e 100644 --- a/integration_test/lit.cfg.py +++ b/integration_test/lit.cfg.py @@ -187,8 +187,9 @@ if config.bindings_tcl_enabled: config.available_features.add('bindings_tcl') -# Add host c compiler. +# Add host c/c++ compiler. config.substitutions.append(("%host_cc", config.host_cc)) +config.substitutions.append(("%host_cxx", config.host_cxx)) # Enable clang-tidy if it has been detected. if config.clang_tidy_path != "": diff --git a/lib/Dialect/ESI/runtime/CMakeLists.txt b/lib/Dialect/ESI/runtime/CMakeLists.txt index 3f30cd7160e4..b14555cd9153 100644 --- a/lib/Dialect/ESI/runtime/CMakeLists.txt +++ b/lib/Dialect/ESI/runtime/CMakeLists.txt @@ -123,6 +123,7 @@ set(ESICppRuntimeBackendHeaders set(ESIPythonRuntimeSources python/esiaccel/__init__.py python/esiaccel/accelerator.py + python/esiaccel/codegen.py python/esiaccel/types.py python/esiaccel/utils.py ) diff --git a/lib/Dialect/ESI/runtime/cpp/include/esi/backends/Trace.h b/lib/Dialect/ESI/runtime/cpp/include/esi/backends/Trace.h index ff5416bb4398..5cd0406e0050 100644 --- a/lib/Dialect/ESI/runtime/cpp/include/esi/backends/Trace.h +++ b/lib/Dialect/ESI/runtime/cpp/include/esi/backends/Trace.h @@ -43,6 +43,9 @@ class TraceAccelerator : public esi::AcceleratorConnection { // Data read from the accelerator is read from the trace file. // TODO: Full trace mode not yet supported. // Read + + // Discard all data sent to the accelerator. Disable trace file generation. + Discard, }; /// Create a trace-based accelerator backend. diff --git a/lib/Dialect/ESI/runtime/cpp/lib/backends/Trace.cpp b/lib/Dialect/ESI/runtime/cpp/lib/backends/Trace.cpp index a69f657b75df..f6e89826fee8 100644 --- a/lib/Dialect/ESI/runtime/cpp/lib/backends/Trace.cpp +++ b/lib/Dialect/ESI/runtime/cpp/lib/backends/Trace.cpp @@ -49,6 +49,8 @@ struct esi::backends::trace::TraceAccelerator::Impl { if (!traceWrite->is_open()) throw std::runtime_error("failed to open trace file '" + traceFile.string() + "'"); + } else if (mode == Discard) { + traceWrite = nullptr; } else { assert(false && "not implemented"); } @@ -76,9 +78,11 @@ struct esi::backends::trace::TraceAccelerator::Impl { void write(const AppIDPath &id, const std::string &portName, const void *data, size_t size); std::ostream &write(std::string service) { + assert(traceWrite && "traceWrite is null"); *traceWrite << "[" << service << "] "; return *traceWrite; } + bool isWriteable() { return traceWrite; } private: std::ofstream *traceWrite; @@ -90,6 +94,8 @@ struct esi::backends::trace::TraceAccelerator::Impl { void TraceAccelerator::Impl::write(const AppIDPath &id, const std::string &portName, const void *data, size_t size) { + if (!isWriteable()) + return; std::string b64data; utils::encodeBase64(data, size, b64data); @@ -105,7 +111,7 @@ TraceAccelerator::connect(Context &ctxt, std::string connectionString) { // Parse the connection std::string. // :[:] - std::regex connPattern("(\\w):([^:]+)(:(\\w+))?"); + std::regex connPattern("([\\w-]):([^:]+)(:(\\w+))?"); std::smatch match; if (regex_search(connectionString, match, connPattern)) { modeStr = match[1]; @@ -121,6 +127,8 @@ TraceAccelerator::connect(Context &ctxt, std::string connectionString) { Mode mode; if (modeStr == "w") mode = Write; + else if (modeStr == "-") + mode = Discard; else throw std::runtime_error("unknown mode '" + modeStr + "'"); @@ -260,7 +268,8 @@ class TraceHostMem : public HostMem { this->size = size; } virtual ~TraceHostMemRegion() { - impl.write("HostMem") << "free " << ptr << std::endl; + if (impl.isWriteable()) + impl.write("HostMem") << "free " << ptr << std::endl; free(ptr); } virtual void *getPtr() const override { return ptr; } @@ -276,22 +285,26 @@ class TraceHostMem : public HostMem { allocate(std::size_t size, HostMem::Options opts) const override { auto ret = std::unique_ptr(new TraceHostMemRegion(size, impl)); - impl.write("HostMem 0x") - << ret->getPtr() << " allocate " << size - << " bytes. Writeable: " << opts.writeable - << ", useLargePages: " << opts.useLargePages << std::endl; + if (impl.isWriteable()) + impl.write("HostMem 0x") + << ret->getPtr() << " allocate " << size + << " bytes. Writeable: " << opts.writeable + << ", useLargePages: " << opts.useLargePages << std::endl; return ret; } virtual bool mapMemory(void *ptr, std::size_t size, HostMem::Options opts) const override { - impl.write("HostMem") << "map 0x" << ptr << " size " << size - << " bytes. Writeable: " << opts.writeable - << ", useLargePages: " << opts.useLargePages - << std::endl; + + if (impl.isWriteable()) + impl.write("HostMem") + << "map 0x" << ptr << " size " << size + << " bytes. Writeable: " << opts.writeable + << ", useLargePages: " << opts.useLargePages << std::endl; return true; } virtual void unmapMemory(void *ptr) const override { - impl.write("HostMem") << "unmap 0x" << ptr << std::endl; + if (impl.isWriteable()) + impl.write("HostMem") << "unmap 0x" << ptr << std::endl; } private: diff --git a/lib/Dialect/ESI/runtime/pyproject.toml b/lib/Dialect/ESI/runtime/pyproject.toml index 4099d43a6d60..5ec0f81ae2aa 100644 --- a/lib/Dialect/ESI/runtime/pyproject.toml +++ b/lib/Dialect/ESI/runtime/pyproject.toml @@ -44,3 +44,4 @@ classifiers = [ [project.scripts] esiquery = "esiaccel.utils:run_esiquery" esi-cosim = "esiaccel.utils:run_esi_cosim" +esi-cppgen = "esiaccel.utils:run_cppgen" diff --git a/lib/Dialect/ESI/runtime/python/esiaccel/codegen.py b/lib/Dialect/ESI/runtime/python/esiaccel/codegen.py new file mode 100644 index 000000000000..4fd1eed65c81 --- /dev/null +++ b/lib/Dialect/ESI/runtime/python/esiaccel/codegen.py @@ -0,0 +1,197 @@ +# 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 + +# Code generation from ESI manifests to source code. C++ header support included +# with the runtime, though it is intended to be extensible for other languages. + +from typing import List, TextIO, Type, Optional +from .accelerator import AcceleratorConnection +from .esiCppAccel import ModuleInfo +from . import types + +import argparse +from pathlib import Path +import textwrap +import sys + +_thisdir = Path(__file__).absolute().resolve().parent + + +class Generator: + """Base class for all generators.""" + + language: Optional[str] = None + + def __init__(self, conn: AcceleratorConnection): + self.manifest = conn.manifest() + + def generate(self, output_dir: Path, system_name: str): + raise NotImplementedError("Generator.generate() must be overridden") + + +class CppGenerator(Generator): + """Generate C++ headers from an ESI manifest.""" + + language = "C++" + + # Supported bit widths for lone integer types. + int_width_support = set([8, 16, 32, 64]) + + def get_type_str(self, type: types.ESIType) -> str: + """Get the textual code for the storage class of a type. + + Examples: uint32_t, int64_t, CustomStruct.""" + + if isinstance(type, (types.BitsType, types.IntType)): + if type.bit_width not in self.int_width_support: + raise ValueError(f"Unsupported integer width: {type.bit_width}") + if isinstance(type, (types.BitsType, types.UIntType)): + return f"uint{type.bit_width}_t" + return f"int{type.bit_width}_t" + raise NotImplementedError(f"Type '{type}' not supported for C++ generation") + + def get_consts_str(self, module_info: ModuleInfo) -> str: + """Get the C++ code for a constant in a module.""" + const_strs: List[str] = [ + f"static constexpr {self.get_type_str(const.type)} " + f"{name} = 0x{const.value:x};" + for name, const in module_info.constants.items() + ] + return "\n".join(const_strs) + + def write_modules(self, output_dir: Path, system_name: str): + """Write the C++ header. One for each module in the manifest.""" + + for module_info in self.manifest.module_infos: + s = f""" + /// Generated header for {system_name} module {module_info.name}. + #pragma once + #include "types.h" + + namespace {system_name} {{ + class {module_info.name} {{ + public: + {self.get_consts_str(module_info)} + }}; + }} // namespace {system_name} + """ + + hdr_file = output_dir / f"{module_info.name}.h" + with open(hdr_file, "w") as hdr: + hdr.write(textwrap.dedent(s)) + + def write_type(self, hdr: TextIO, type: types.ESIType): + if isinstance(type, (types.BitsType, types.IntType)): + # Bit vector types use standard C++ types. + return + raise NotImplementedError(f"Type '{type}' not supported for C++ generation") + + def write_types(self, output_dir: Path, system_name: str): + hdr_file = output_dir / "types.h" + with open(hdr_file, "w") as hdr: + hdr.write( + textwrap.dedent(f""" + // Generated header for {system_name} types. + #pragma once + + #include + + namespace {system_name} {{ + """)) + + for type in self.manifest.type_table: + try: + self.write_type(hdr, type) + except NotImplementedError: + sys.stderr.write( + f"Warning: type '{type}' not supported for C++ generation\n") + + hdr.write( + textwrap.dedent(f""" + }} // namespace {system_name} + """)) + + def generate(self, output_dir: Path, system_name: str): + self.write_types(output_dir, system_name) + self.write_modules(output_dir, system_name) + + +def run(generator: Type[Generator] = CppGenerator, + cmdline_args=sys.argv) -> int: + """Create and run a generator reading options from the command line.""" + + argparser = argparse.ArgumentParser( + description=f"Generate {generator.language} headers from an ESI manifest", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=textwrap.dedent(""" + Can read the manifest from either a file OR a running accelerator. + + Usage examples: + # To read the manifest from a file: + esi-cppgen --file /path/to/manifest.json + + # To read the manifest from a running accelerator: + esi-cppgen --platform cosim --connection localhost:1234 + """)) + + argparser.add_argument("--file", + type=str, + default=None, + help="Path to the manifest file.") + argparser.add_argument( + "--platform", + type=str, + help="Name of platform for live accelerator connection.") + argparser.add_argument( + "--connection", + type=str, + help="Connection string for live accelerator connection.") + argparser.add_argument( + "--output-dir", + type=str, + default="esi", + help="Output directory for generated files. Recommend adding either `esi`" + " or the system name to the end of the path so as to avoid header name" + "conflicts. Defaults to `esi`") + argparser.add_argument( + "--system-name", + type=str, + default="esi_system", + help="Name of the ESI system. For C++, this will be the namespace.") + + if (len(cmdline_args) <= 1): + argparser.print_help() + return 1 + args = argparser.parse_args(cmdline_args[1:]) + + if args.file is not None and args.platform is not None: + print("Cannot specify both --file and --platform") + return 1 + + conn: AcceleratorConnection + if args.file is not None: + conn = AcceleratorConnection("trace", f"-:{args.file}") + elif args.platform is not None: + if args.connection is None: + print("Must specify --connection with --platform") + return 1 + conn = AcceleratorConnection(args.platform, args.connection) + else: + print("Must specify either --file or --platform") + return 1 + + output_dir = Path(args.output_dir) + if output_dir.exists() and not output_dir.is_dir(): + print(f"Output directory {output_dir} is not a directory") + return 1 + if not output_dir.exists(): + output_dir.mkdir(parents=True) + + gen = generator(conn) + gen.generate(output_dir, args.system_name) + return 0 + + +if __name__ == '__main__': + sys.exit(run()) diff --git a/lib/Dialect/ESI/runtime/python/esiaccel/esiCppAccel.cpp b/lib/Dialect/ESI/runtime/python/esiaccel/esiCppAccel.cpp index 051106ecc5e2..8b7730ae8e87 100644 --- a/lib/Dialect/ESI/runtime/python/esiaccel/esiCppAccel.cpp +++ b/lib/Dialect/ESI/runtime/python/esiaccel/esiCppAccel.cpp @@ -329,6 +329,13 @@ PYBIND11_MODULE(esiCppAccel, m) { return acc; }, py::return_value_policy::reference) - .def_property_readonly("type_table", &Manifest::getTypeTable) + .def_property_readonly("type_table", + [](Manifest &m) { + std::vector ret; + std::ranges::transform(m.getTypeTable(), + std::back_inserter(ret), + getPyType); + return ret; + }) .def_property_readonly("module_infos", &Manifest::getModuleInfos); } diff --git a/lib/Dialect/ESI/runtime/python/esiaccel/types.py b/lib/Dialect/ESI/runtime/python/esiaccel/types.py index eccff627508e..1b1041247522 100644 --- a/lib/Dialect/ESI/runtime/python/esiaccel/types.py +++ b/lib/Dialect/ESI/runtime/python/esiaccel/types.py @@ -47,6 +47,7 @@ def supports_host(self) -> Tuple[bool, Optional[str]]: """Does this type support host communication via Python? Returns either '(True, None)' if it is, or '(False, reason)' if it is not.""" + print(f"supports_host: {self.cpp_type} {type(self)}") if self.bit_width % 8 != 0: return (False, "runtime only supports types with multiple of 8 bits") return (True, None) diff --git a/lib/Dialect/ESI/runtime/python/esiaccel/utils.py b/lib/Dialect/ESI/runtime/python/esiaccel/utils.py index c0e9d831dd85..29d92b1d79cb 100644 --- a/lib/Dialect/ESI/runtime/python/esiaccel/utils.py +++ b/lib/Dialect/ESI/runtime/python/esiaccel/utils.py @@ -2,6 +2,8 @@ # See https://llvm.org/LICENSE.txt for license information. # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +from . import codegen + from pathlib import Path import subprocess import sys @@ -26,5 +28,9 @@ def run_esi_cosim(): return cosim_import.__main__(sys.argv) +def run_cppgen(): + return codegen.run() + + def get_cmake_dir(): return _thisdir / "cmake" From 5d8cf69cf222ec8776b1680d4d1de7e0e2aa4a86 Mon Sep 17 00:00:00 2001 From: Hideto Ueno Date: Wed, 21 Aug 2024 12:25:13 +0900 Subject: [PATCH 116/119] [CombFolds] Don't canonicalize extract(shl(1, x)) if shift is multiply used (#7527) There is a canonicalization for `exract(c, shl(1, x))` to `x == c` but this canonicalization introduces a bunch of comparision to constants. This harms PPA when bitwidth is large (e.g. 16 bit shift introduce 2^16 icmp op). To prevent such regressions this commit imposes restriction regarding the number of uses for shift. --- lib/Dialect/Comb/CombFolds.cpp | 25 ++++++++++++++----------- test/Dialect/Comb/canonicalization.mlir | 23 ++++++++++++++++------- 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/lib/Dialect/Comb/CombFolds.cpp b/lib/Dialect/Comb/CombFolds.cpp index 96cb562b408c..ececa63a30f7 100644 --- a/lib/Dialect/Comb/CombFolds.cpp +++ b/lib/Dialect/Comb/CombFolds.cpp @@ -693,17 +693,20 @@ LogicalResult ExtractOp::canonicalize(ExtractOp op, PatternRewriter &rewriter) { // `extract(lowBit, shl(1, x))` -> `x == lowBit` when a single bit is // extracted. if (cast(op.getType()).getWidth() == 1 && inputOp) - if (auto shlOp = dyn_cast(inputOp)) - if (auto lhsCst = shlOp.getOperand(0).getDefiningOp()) - if (lhsCst.getValue().isOne()) { - auto newCst = rewriter.create( - shlOp.getLoc(), - APInt(lhsCst.getValue().getBitWidth(), op.getLowBit())); - replaceOpWithNewOpAndCopyName(rewriter, op, ICmpPredicate::eq, - shlOp->getOperand(1), newCst, - false); - return success(); - } + if (auto shlOp = dyn_cast(inputOp)) { + // Don't canonicalize if the shift is multiply used. + if (shlOp->hasOneUse()) + if (auto lhsCst = shlOp.getLhs().getDefiningOp()) + if (lhsCst.getValue().isOne()) { + auto newCst = rewriter.create( + shlOp.getLoc(), + APInt(lhsCst.getValue().getBitWidth(), op.getLowBit())); + replaceOpWithNewOpAndCopyName( + rewriter, op, ICmpPredicate::eq, shlOp->getOperand(1), newCst, + false); + return success(); + } + } return failure(); } diff --git a/test/Dialect/Comb/canonicalization.mlir b/test/Dialect/Comb/canonicalization.mlir index faa7e4033452..77d0eadbc9ab 100644 --- a/test/Dialect/Comb/canonicalization.mlir +++ b/test/Dialect/Comb/canonicalization.mlir @@ -1221,17 +1221,26 @@ hw.module @test1560(in %value: i38, out a: i1) { } // CHECK-LABEL: hw.module @extractShift -hw.module @extractShift(in %arg0 : i4, out o1 : i1, out o2: i1) { +hw.module @extractShift(in %arg0 : i4, out o1 : i1, out o2: i1, out o3: i1, out o4: i1) { %c1 = hw.constant 1: i4 %0 = comb.shl %c1, %arg0 : i4 + %1 = comb.shl %c1, %arg0 : i4 + %2 = comb.shl %c1, %arg0 : i4 - // CHECK: %0 = comb.icmp eq %arg0, %c0_i4 : i4 - %1 = comb.extract %0 from 0 : (i4) -> i1 + // CHECK: %[[O1:.+]] = comb.icmp eq %arg0, %c0_i4 : i4 + %3 = comb.extract %0 from 0 : (i4) -> i1 - // CHECK: %1 = comb.icmp eq %arg0, %c2_i4 : i4 - %2 = comb.extract %0 from 2 : (i4) -> i1 - // CHECK: hw.output %0, %1 - hw.output %1, %2: i1, i1 + // CHECK: %[[O2:.+]] = comb.icmp eq %arg0, %c2_i4 : i4 + %4 = comb.extract %1 from 2 : (i4) -> i1 + + // CHECK: %[[O3:.+]] = comb.extract + %5 = comb.extract %2 from 2 : (i4) -> i1 + + // CHECK: %[[O4:.+]] = comb.extract + %6 = comb.extract %2 from 2 : (i4) -> i1 + + // CHECK: hw.output %[[O1]], %[[O2]], %[[O3]], %[[O4]] + hw.output %3, %4, %5, %6: i1, i1, i1, i1 } // CHECK-LABEL: hw.module @moduloZeroDividend From aae37b7a5c9263623d3b3c78364b7be2be177e95 Mon Sep 17 00:00:00 2001 From: Fabian Schuiki Date: Wed, 21 Aug 2024 23:39:18 +0200 Subject: [PATCH 117/119] [MooreToCore] Ignore ConstantLike values in wait op (#7540) Skip values defined by `ConstantLike` ops when collecting the list of values to observe in `llhd.wait` ops. Constants will never cause an `llhd.wait` to resume execution since they never change value. --- lib/Conversion/MooreToCore/MooreToCore.cpp | 3 +++ test/Conversion/MooreToCore/basic.mlir | 3 +-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/Conversion/MooreToCore/MooreToCore.cpp b/lib/Conversion/MooreToCore/MooreToCore.cpp index 0913f42afc73..ca56ed58d49b 100644 --- a/lib/Conversion/MooreToCore/MooreToCore.cpp +++ b/lib/Conversion/MooreToCore/MooreToCore.cpp @@ -185,6 +185,9 @@ static void getValuesToObserve(Region *region, for (auto value : operation->getOperands()) { if (region->isAncestor(value.getParentRegion())) continue; + if (auto *defOp = value.getDefiningOp(); + defOp && defOp->hasTrait()) + continue; if (!alreadyObserved.insert(value).second) continue; diff --git a/test/Conversion/MooreToCore/basic.mlir b/test/Conversion/MooreToCore/basic.mlir index b90dbb6f7958..fd2a1fd7b112 100644 --- a/test/Conversion/MooreToCore/basic.mlir +++ b/test/Conversion/MooreToCore/basic.mlir @@ -691,7 +691,6 @@ moore.module @WaitEvent() { moore.return } - // CHECK: [[FALSE:%.+]] = hw.constant false // CHECK: [[PRB_A:%.+]] = llhd.prb %a // CHECK: [[PRB_B:%.+]] = llhd.prb %b // CHECK: llhd.process { @@ -703,7 +702,7 @@ moore.module @WaitEvent() { // CHECK: llhd.prb %b // CHECK: cf.br ^[[BB2:.+]] // CHECK: ^[[BB2]]: - // CHECK: llhd.wait ([[FALSE]], [[PRB_A]], [[PRB_B]] : {{.*}}), ^[[BB1]] + // CHECK: llhd.wait ([[PRB_A]], [[PRB_B]] : {{.*}}), ^[[BB1]] %1 = moore.conditional %cond : i1 -> i1 { %2 = moore.read %a : moore.yield %2 : !moore.i1 From 82782363ae94d31b6cbbd75173c54c2fd9c2e901 Mon Sep 17 00:00:00 2001 From: Schuyler Eldridge Date: Wed, 21 Aug 2024 17:27:13 -0400 Subject: [PATCH 118/119] [FIRRTL] Add debug prints to AssignOutputDirs, NFC Add some basic debugging information to the AssignOutputDirs pass. This indicates that the pass is running and what modules are having their output directories updated. Signed-off-by: Schuyler Eldridge --- lib/Dialect/FIRRTL/Transforms/AssignOutputDirs.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/Dialect/FIRRTL/Transforms/AssignOutputDirs.cpp b/lib/Dialect/FIRRTL/Transforms/AssignOutputDirs.cpp index 59ff53164629..72a30fc84bde 100644 --- a/lib/Dialect/FIRRTL/Transforms/AssignOutputDirs.cpp +++ b/lib/Dialect/FIRRTL/Transforms/AssignOutputDirs.cpp @@ -12,6 +12,7 @@ #include "circt/Dialect/FIRRTL/FIRRTLOps.h" #include "circt/Dialect/FIRRTL/Passes.h" #include "circt/Dialect/HW/HWAttributes.h" +#include "circt/Support/Debug.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/PostOrderIterator.h" #include "llvm/ADT/SmallPtrSet.h" @@ -98,6 +99,7 @@ struct AssignOutputDirsPass } // namespace void AssignOutputDirsPass::runOnOperation() { + LLVM_DEBUG(debugPassHeader(this) << "\n"); SmallString<64> outputDir(outputDirOption); if (fs::make_absolute(outputDir)) { emitError(mlir::UnknownLoc::get(&getContext()), @@ -112,6 +114,7 @@ void AssignOutputDirsPass::runOnOperation() { bool changed = false; + LLVM_DEBUG(llvm::dbgs() << "Updating modules:\n"); DenseSet visited; for (auto *root : getAnalysis()) { for (auto *node : llvm::inverse_post_order_ext(root, visited)) { @@ -149,12 +152,17 @@ void AssignOutputDirsPass::runOnOperation() { hw::OutputFileAttr::getAsDirectory(&getContext(), moduleOutputDir); module->setAttr("output_file", f); changed = true; + LLVM_DEBUG({ + llvm::dbgs() << " - name: " << module.getName() << "\n" + << " directory: " << f.getFilename() << "\n"; + }); } } } if (!changed) markAllAnalysesPreserved(); + LLVM_DEBUG(debugFooter() << "\n"); } std::unique_ptr From eef76ca7e302ccef144343926c4e653a0e43a558 Mon Sep 17 00:00:00 2001 From: Schuyler Eldridge Date: Wed, 21 Aug 2024 18:58:54 -0400 Subject: [PATCH 119/119] [FIRRTL] Debug header/footer in BBoxReader, NFC Add the standard debug header/footer to the `BlackBoxReader` pass. This is entirely cosmetic. Signed-off-by: Schuyler Eldridge --- lib/Dialect/FIRRTL/Transforms/BlackBoxReader.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/Dialect/FIRRTL/Transforms/BlackBoxReader.cpp b/lib/Dialect/FIRRTL/Transforms/BlackBoxReader.cpp index a64deec20c8a..3f9419265ced 100644 --- a/lib/Dialect/FIRRTL/Transforms/BlackBoxReader.cpp +++ b/lib/Dialect/FIRRTL/Transforms/BlackBoxReader.cpp @@ -23,6 +23,7 @@ #include "circt/Dialect/HW/HWAttributes.h" #include "circt/Dialect/HW/HWDialect.h" #include "circt/Dialect/SV/SVOps.h" +#include "circt/Support/Debug.h" #include "circt/Support/Path.h" #include "mlir/IR/Attributes.h" #include "mlir/Pass/Pass.h" @@ -156,6 +157,7 @@ struct BlackBoxReaderPass /// Emit the annotated source code for black boxes in a circuit. void BlackBoxReaderPass::runOnOperation() { + LLVM_DEBUG(debugPassHeader(this) << "\n"); CircuitOp circuitOp = getOperation(); CircuitNamespace ns(circuitOp); @@ -326,6 +328,7 @@ void BlackBoxReaderPass::runOnOperation() { // Clean up. emittedFileMap.clear(); fileListFiles.clear(); + LLVM_DEBUG(debugFooter() << "\n"); } /// Run on an operation-annotation pair. The annotation need not be a black box