From 316421a20bae4a9c03cd15a618ceee2de9f136be Mon Sep 17 00:00:00 2001 From: Andrew Lenharth Date: Sat, 10 Dec 2022 10:34:18 -0600 Subject: [PATCH] [FIRRTL] Add support for "intrinsics" (#4429) Add support for intrinsics in FIRRTL. Until intrinsics are supported in the firrtl spec, or we have more complex intrinsics, do direct lowering of intrinsics into their ops. It is expected that eventually something like an "intmodule" will exist and annotated extern modules will not be used for intrinsics or first they will lower to intmodules internally, then intmodules will be handled uniformly. As an example, add a sizeof operator which returns the number of bits in the argument type. This let's you query the result of type inference from inside the circuit. Also adds support for isX as an intrinsic and plusarg sv functions. --- docs/Dialects/FIRRTL/FIRRTLAnnotations.md | 14 + docs/Dialects/FIRRTL/FIRRTLIntrinsics.md | 78 ++++++ docs/Dialects/FIRRTL/RationaleFIRRTL.md | 14 + .../circt/Dialect/FIRRTL/FIRRTLExpressions.td | 26 +- .../circt/Dialect/FIRRTL/FIRRTLStructure.td | 4 +- include/circt/Dialect/FIRRTL/FIRRTLTypes.td | 6 + include/circt/Dialect/FIRRTL/FIRRTLVisitors.h | 10 +- include/circt/Dialect/FIRRTL/Passes.h | 2 + include/circt/Dialect/FIRRTL/Passes.td | 9 + lib/Conversion/FIRRTLToHW/LowerToHW.cpp | 4 +- lib/Dialect/FIRRTL/FIRRTLFolds.cpp | 15 ++ lib/Dialect/FIRRTL/FIRRTLOps.cpp | 31 ++- lib/Dialect/FIRRTL/Import/FIRParser.cpp | 2 +- .../FIRRTL/Import/FIRParserAsserts.cpp | 2 +- lib/Dialect/FIRRTL/Transforms/CMakeLists.txt | 1 + .../FIRRTL/Transforms/LowerAnnotations.cpp | 7 +- .../FIRRTL/Transforms/LowerIntrinsics.cpp | 249 ++++++++++++++++++ test/Conversion/FIRRTLToHW/lower-to-hw.mlir | 2 +- .../Dialect/FIRRTL/SFCTests/constantProp.mlir | 18 -- test/Dialect/FIRRTL/canonicalization.mlir | 6 +- test/Dialect/FIRRTL/lower-intrinsics.mlir | 38 +++ test/Dialect/FIRRTL/parse-basic.fir | 2 +- tools/firtool/firtool.cpp | 2 + 23 files changed, 499 insertions(+), 43 deletions(-) create mode 100644 docs/Dialects/FIRRTL/FIRRTLIntrinsics.md create mode 100644 lib/Dialect/FIRRTL/Transforms/LowerIntrinsics.cpp create mode 100644 test/Dialect/FIRRTL/lower-intrinsics.mlir diff --git a/docs/Dialects/FIRRTL/FIRRTLAnnotations.md b/docs/Dialects/FIRRTL/FIRRTLAnnotations.md index 90b4226d83..b4ca0db889 100644 --- a/docs/Dialects/FIRRTL/FIRRTLAnnotations.md +++ b/docs/Dialects/FIRRTL/FIRRTLAnnotations.md @@ -771,6 +771,19 @@ Example: } ``` +### circt.intrinsic + +| Property | Type | Description | +| ---------- | ------ | ------------- | +| class | string | `circt.intrinsic` | +| target | string | Reference target | +| intrinsic | string | Name of Intrinsic | + +Used to indicate an external module is really an intrinsic module. This exists +to allow a frontend to generate intrinsics without firrtl language support for +intrinsics. It is expect this will be deprecated as soon as the firrtl language +supports intrinsics. This annotation can only be local and applied to a module. + ### SitestBlackBoxAnnotation | Property | Type | Description | @@ -1474,3 +1487,4 @@ modules' bind file. This attribute has type `OutputFileAttr`. Used by SVExtractTestCode. Indicates a module whose instances should be extracted from the circuit in the indicated extraction type. + diff --git a/docs/Dialects/FIRRTL/FIRRTLIntrinsics.md b/docs/Dialects/FIRRTL/FIRRTLIntrinsics.md new file mode 100644 index 0000000000..f5c4068298 --- /dev/null +++ b/docs/Dialects/FIRRTL/FIRRTLIntrinsics.md @@ -0,0 +1,78 @@ +# Intrinsics + +Intrinsics provide an implementation-specific way to extend the firrtl language +with new operations. + +Intrinsics are currently implemented as annotated external modules. We expect +that native firrtl support for intrinsics will be added to the language. + +## Motivation + +Intrinsics provide a way to add functionality to firrtl without having to extend +the firrtl language. This allows a fast path for prototyping new operations to +rapidly repsond to output requirements. Intrinsics maintain strict definitions +and type checking. + +## Supported Intrinsics + +Annotations here are written in their JSON format. A "reference target" +indicates that the annotation could target any object in the hierarchy, +although there may be further restrictions in the annotation. + +### circt.sizeof + +Returns the size of a type. The input port is not read from and may be any +type, including uninfered types. + +| Parameter | Type | Description | +| ---------- | ------ | ------------- | + +| Port | Direction | Type | Description | +| ---------- | --------- | -------- | ----------------------------------- | +| i | input | Any | value whose type is to be returned | +| size | output | UInt<32> | Size of type of i | + +### circt.isX + +Tests if the value is a literal `x`. Firrtl doesn't have a notion of 'x per-se, +but x can come in to the system from external modules and from SV constructs. +Verification constructs need to explicitly test for 'x. + +| Parameter | Type | Description | +| ---------- | ------ | ------------- | + +| Port | Direction | Type | Description | +| ---------- | --------- | -------- | ----------------------------------- | +| i | input | Any | value test | +| found | output | UInt<1> | i is `x` | + +### circt.plusargs.value + +Tests and extracts a value from simulator command line options with system +verilog $value$plusargs. This is described in SystemVerilog 2012 section 21.6. + +We do not currently check that the format string substitution flag matches the +type of the result. + +| Parameter | Type | Description | +| ---------- | ------ | ------------- | +| FORMAT | string | Format string per SV 21.6 | + +| Port | Direction | Type | Description | +| ---------- | --------- | -------- | ----------------------------------- | +| found | output | UInt<1> | found in args | +| result | output | AnyType | found in args | + + +### circt.plusargs.test + +Tests simulator command line options with system verilog $test$plusargs. This +is described in SystemVerilog 2012 section 21.6. + +| Parameter | Type | Description | +| ---------- | ------ | ------------- | +| FORMAT | string | Format string per SV 21.6 | + +| Port | Direction | Type | Description | +| ---------- | --------- | -------- | ----------------------------------- | +| found | output | UInt<1> | found in args | diff --git a/docs/Dialects/FIRRTL/RationaleFIRRTL.md b/docs/Dialects/FIRRTL/RationaleFIRRTL.md index 5ce722a2ac..e9ee528a1f 100644 --- a/docs/Dialects/FIRRTL/RationaleFIRRTL.md +++ b/docs/Dialects/FIRRTL/RationaleFIRRTL.md @@ -1064,3 +1064,17 @@ b <= mux(cond, a, inv) It follows that interpretation (4) will then convert the false leg of the `mux` to a constant zero. + +## Intrinsics + +Intrinsics are implementation-defined constructs. Intrinsics provide a way to +extend the system with funcitonality without changing the langauge. They form +an implementation-specific built-in library. Unlike traditional libraries, +implementations of intrinsics have access to internals of the compiler, allowing +them to implement features not possible in the language. + +In FIRRTL, we support intrinsic modules. The internal op is `firrtl.intmodule` +which has all the properties of an external module. Until the firrtl spec +supports intrinsics, intrinsic modules are expressed in firrtl as external +modules with the `circt.intrinsic` annotation on the module. + diff --git a/include/circt/Dialect/FIRRTL/FIRRTLExpressions.td b/include/circt/Dialect/FIRRTL/FIRRTLExpressions.td index 0916954425..8c51442269 100644 --- a/include/circt/Dialect/FIRRTL/FIRRTLExpressions.td +++ b/include/circt/Dialect/FIRRTL/FIRRTLExpressions.td @@ -473,6 +473,8 @@ class UnaryPrimOp; + def AsSIntPrimOp : UnaryPrimOp<"asSInt", FIRRTLBaseType, SIntType>; def AsUIntPrimOp : UnaryPrimOp<"asUInt", FIRRTLBaseType, UIntType>; def AsAsyncResetPrimOp @@ -613,10 +615,10 @@ def TailPrimOp : PrimOp<"tail"> { // Verif and SV specific //===----------------------------------------------------------------------===// -def IsXVerifOp : FIRRTLExprOp<"verif_isX"> { +def IsXIntrinsicOp : FIRRTLExprOp<"int.isX"> { let summary = "Test for 'x"; let description = [{ - The verif.isX expression checks that the operand is not a verilog literal + The int.isX expression checks that the operand is not a verilog literal 'x. Firrtl doesn't have a notion of 'x per-se, but x can come in to the system from external modules and from SV constructs. Verification constructs need to explicitly test for 'x. @@ -624,9 +626,25 @@ def IsXVerifOp : FIRRTLExprOp<"verif_isX"> { let arguments = (ins FIRRTLBaseType:$arg); let results = (outs UInt1Type:$result); - let assemblyFormat = - "$arg attr-dict `:` qualified(type($arg))"; + let hasFolder = 1; + let assemblyFormat = "$arg attr-dict `:` type($arg)"; +} +def PlusArgsTestIntrinsicOp : FIRRTLExprOp<"int.plusargs.test"> { + let summary = "Verilog $test$plusargs call"; + + let arguments = (ins StrAttr:$formatString); + let results = (outs UInt1Type:$found); + let assemblyFormat = "$formatString attr-dict"; +} + +def PlusArgsValueIntrinsicOp +: FIRRTLOp<"int.plusargs.value", [HasCustomSSAName, Pure]> { + let summary = "Verilog $value$plusargs call"; + + let arguments = (ins StrAttr:$formatString); + let results = (outs UInt1Type:$found, AnyType:$result); + let assemblyFormat = "$formatString attr-dict `:` type($result)"; } //===----------------------------------------------------------------------===// diff --git a/include/circt/Dialect/FIRRTL/FIRRTLStructure.td b/include/circt/Dialect/FIRRTL/FIRRTLStructure.td index 8e042aac33..9fd6336f81 100644 --- a/include/circt/Dialect/FIRRTL/FIRRTLStructure.td +++ b/include/circt/Dialect/FIRRTL/FIRRTLStructure.td @@ -105,7 +105,7 @@ def FExtModuleOp : FIRRTLOp<"extmodule", [IsolatedFromAbove, Symbol, HasParent<"CircuitOp">, OpAsmOpInterface, DeclareOpInterfaceMethods, InnerSymbolTable, DeclareOpInterfaceMethods]> { - let summary = "FIRRTL extmodule"; + let summary = "FIRRTL external module"; let description = [{ The "firrtl.extmodule" operation represents an external reference to a Verilog module, including a given name and a list of ports. @@ -147,7 +147,7 @@ def FMemModuleOp : FIRRTLOp<"memmodule", [IsolatedFromAbove, Symbol, HasParent<"CircuitOp">, OpAsmOpInterface, DeclareOpInterfaceMethods, InnerSymbolTable, DeclareOpInterfaceMethods]> { - let summary = "FIRRTL Generated Module"; + let summary = "FIRRTL Generated Memory Module"; let description = [{ The "firrtl.memmodule" operation represents an external reference to a memory module. See the "firrtl.mem" op for a deeper explantation of the diff --git a/include/circt/Dialect/FIRRTL/FIRRTLTypes.td b/include/circt/Dialect/FIRRTL/FIRRTLTypes.td index 9940d22242..89694bbe15 100644 --- a/include/circt/Dialect/FIRRTL/FIRRTLTypes.td +++ b/include/circt/Dialect/FIRRTL/FIRRTLTypes.td @@ -102,6 +102,12 @@ def UInt1Type : FIRRTLDialectType< "UInt<1> or UInt", "::circt::firrtl::UIntType">, BuildableType<"UIntType::get($_builder.getContext(), 1)">; +def UInt32Type : FIRRTLDialectType< + CPred<"$_self.isa() && " + "$_self.cast().getWidth() == 32">, + "UInt<32>", "::circt::firrtl::UIntType">, + BuildableType<"UIntType::get($_builder.getContext(), 32)">; + def AsyncResetType : FIRRTLDialectType< CPred<"$_self.isa()">, "AsyncReset", "::circt::firrtl::AsyncResetType">, diff --git a/include/circt/Dialect/FIRRTL/FIRRTLVisitors.h b/include/circt/Dialect/FIRRTL/FIRRTLVisitors.h index 03cc234bbe..c8dfc4970e 100644 --- a/include/circt/Dialect/FIRRTL/FIRRTLVisitors.h +++ b/include/circt/Dialect/FIRRTL/FIRRTLVisitors.h @@ -43,8 +43,8 @@ public: // Unary operators. AsSIntPrimOp, AsUIntPrimOp, AsAsyncResetPrimOp, AsClockPrimOp, CvtPrimOp, NegPrimOp, NotPrimOp, AndRPrimOp, OrRPrimOp, XorRPrimOp, - // Verif Expressions. - IsXVerifOp, + // Intrinsic Expressions. + IsXIntrinsicOp, PlusArgsValueIntrinsicOp, PlusArgsTestIntrinsicOp, // Miscellaneous. BitsPrimOp, HeadPrimOp, MuxPrimOp, PadPrimOp, ShlPrimOp, ShrPrimOp, TailPrimOp, VerbatimExprOp, HWStructCastOp, BitCastOp, RefSendOp, @@ -134,8 +134,10 @@ public: HANDLE(OrRPrimOp, Unary); HANDLE(XorRPrimOp, Unary); - // Verif Expr. - HANDLE(IsXVerifOp, Unhandled); + // Intrinsic Expr. + HANDLE(IsXIntrinsicOp, Unhandled); + HANDLE(PlusArgsValueIntrinsicOp, Unhandled); + HANDLE(PlusArgsTestIntrinsicOp, Unhandled); // Miscellaneous. HANDLE(BitsPrimOp, Unhandled); diff --git a/include/circt/Dialect/FIRRTL/Passes.h b/include/circt/Dialect/FIRRTL/Passes.h index 3e237100a0..86e7732b64 100644 --- a/include/circt/Dialect/FIRRTL/Passes.h +++ b/include/circt/Dialect/FIRRTL/Passes.h @@ -54,6 +54,8 @@ std::unique_ptr createLowerBundleVectorTypesPass(); std::unique_ptr createLowerCHIRRTLPass(); +std::unique_ptr createLowerIntrinsicsPass(); + std::unique_ptr createIMConstPropPass(); std::unique_ptr diff --git a/include/circt/Dialect/FIRRTL/Passes.td b/include/circt/Dialect/FIRRTL/Passes.td index 617e340631..664b46afb1 100644 --- a/include/circt/Dialect/FIRRTL/Passes.td +++ b/include/circt/Dialect/FIRRTL/Passes.td @@ -609,6 +609,15 @@ def LowerXMR : Pass<"firrtl-lower-xmr", "firrtl::CircuitOp"> { let constructor = "circt::firrtl::createLowerXMRPass()"; } +def LowerIntrinsics : Pass<"firrtl-lower-intrinsics", "firrtl::CircuitOp"> { + let summary = "Lower intrinsics"; + let description = [{ + This pass lowers intrinsics encoded as extmodule with annotation and + intmodule to their implementation or op. + }]; + let constructor = "circt::firrtl::createLowerIntrinsicsPass()"; +} + def ResolveTraces : Pass<"firrtl-resolve-traces", "firrtl::CircuitOp"> { let summary = "Write out TraceAnnotations to an output annotation file"; let description = [{ diff --git a/lib/Conversion/FIRRTLToHW/LowerToHW.cpp b/lib/Conversion/FIRRTLToHW/LowerToHW.cpp index 9c5a3c32c9..42b4cb58c5 100644 --- a/lib/Conversion/FIRRTLToHW/LowerToHW.cpp +++ b/lib/Conversion/FIRRTLToHW/LowerToHW.cpp @@ -1609,7 +1609,7 @@ struct FIRRTLLowering : public FIRRTLVisitor { } // Verif Operations - LogicalResult visitExpr(IsXVerifOp op); + LogicalResult visitExpr(IsXIntrinsicOp op); // Other Operations LogicalResult visitExpr(BitsPrimOp op); @@ -3370,7 +3370,7 @@ LogicalResult FIRRTLLowering::visitExpr(CatPrimOp op) { // Verif Operations //===----------------------------------------------------------------------===// -LogicalResult FIRRTLLowering::visitExpr(IsXVerifOp op) { +LogicalResult FIRRTLLowering::visitExpr(IsXIntrinsicOp op) { auto input = getLoweredValue(op.getArg()); if (!input) return failure(); diff --git a/lib/Dialect/FIRRTL/FIRRTLFolds.cpp b/lib/Dialect/FIRRTL/FIRRTLFolds.cpp index d1b19782b7..472b720132 100644 --- a/lib/Dialect/FIRRTL/FIRRTLFolds.cpp +++ b/lib/Dialect/FIRRTL/FIRRTLFolds.cpp @@ -888,6 +888,21 @@ LogicalResult NEQPrimOp::canonicalize(NEQPrimOp op, PatternRewriter &rewriter) { // Unary Operators //===----------------------------------------------------------------------===// +OpFoldResult SizeOfIntrinsicOp::fold(llvm::ArrayRef) { + auto base = getInput().getType().cast(); + auto w = base.getBitWidthOrSentinel(); + if (w >= 0) + return getIntAttr(getType(), APInt(32, w)); + return {}; +} + +OpFoldResult IsXIntrinsicOp::fold(llvm::ArrayRef operands) { + // No constant can be 'x' by definition. + if (auto cst = getConstant(operands[0])) + return getIntAttr(getType(), APInt(1, 0)); + return {}; +} + OpFoldResult AsSIntPrimOp::fold(ArrayRef operands) { // No effect. if (getInput().getType() == getType()) diff --git a/lib/Dialect/FIRRTL/FIRRTLOps.cpp b/lib/Dialect/FIRRTL/FIRRTLOps.cpp index accb5ac175..6ae8ef4f90 100644 --- a/lib/Dialect/FIRRTL/FIRRTLOps.cpp +++ b/lib/Dialect/FIRRTL/FIRRTLOps.cpp @@ -2903,6 +2903,11 @@ LogicalResult impl::validateUnaryOpArguments(ValueRange operands, return success(); } +FIRRTLType SizeOfIntrinsicOp::inferUnaryReturnType(FIRRTLType arg, + Optional loc) { + return UIntType::get(arg.getContext(), 32); +} + FIRRTLType AsSIntPrimOp::inferUnaryReturnType(FIRRTLType input, Optional loc) { auto base = input.dyn_cast(); @@ -3316,12 +3321,19 @@ FIRRTLType TailPrimOp::inferReturnType(ValueRange operands, // Verif Expressions //===----------------------------------------------------------------------===// -FIRRTLType IsXVerifOp::inferReturnType(ValueRange operands, - ArrayRef attrs, - Optional loc) { +FIRRTLType IsXIntrinsicOp::inferReturnType(ValueRange operands, + ArrayRef attrs, + Optional loc) { return UIntType::get(operands[0].getContext(), 1); } +FIRRTLType +PlusArgsTestIntrinsicOp::inferReturnType(ValueRange operands, + ArrayRef attrs, + Optional loc) { + return UIntType::get(attrs[0].getName().getContext(), 1); +} + //===----------------------------------------------------------------------===// // VerbatimExprOp //===----------------------------------------------------------------------===// @@ -3975,6 +3987,9 @@ void AndRPrimOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) { genericAsmResultNames(*this, setNameFn); } +void SizeOfIntrinsicOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) { + genericAsmResultNames(*this, setNameFn); +} void AsAsyncResetPrimOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) { genericAsmResultNames(*this, setNameFn); } @@ -4020,13 +4035,19 @@ void GTPrimOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) { void HeadPrimOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) { genericAsmResultNames(*this, setNameFn); } -void IsXVerifOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) { +void IsXIntrinsicOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) { + genericAsmResultNames(*this, setNameFn); +} +void PlusArgsValueIntrinsicOp::getAsmResultNames( + OpAsmSetValueNameFn setNameFn) { + genericAsmResultNames(*this, setNameFn); +} +void PlusArgsTestIntrinsicOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) { genericAsmResultNames(*this, setNameFn); } void LEQPrimOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) { genericAsmResultNames(*this, setNameFn); } - void LTPrimOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) { genericAsmResultNames(*this, setNameFn); } diff --git a/lib/Dialect/FIRRTL/Import/FIRParser.cpp b/lib/Dialect/FIRRTL/Import/FIRParser.cpp index d2e194d341..afb9a4ea14 100644 --- a/lib/Dialect/FIRRTL/Import/FIRParser.cpp +++ b/lib/Dialect/FIRRTL/Import/FIRParser.cpp @@ -3012,7 +3012,7 @@ ParseResult FIRCircuitParser::parseModule(CircuitOp circuit, // Otherwise, handle extmodule specific features like parameters. - // Parse a defname if present. + // Parse a defname if present and is an extmodule. // TODO(firrtl spec): defname isn't documented at all, what is it? StringRef defName; if (consumeIf(FIRToken::kw_defname)) { diff --git a/lib/Dialect/FIRRTL/Import/FIRParserAsserts.cpp b/lib/Dialect/FIRRTL/Import/FIRParserAsserts.cpp index b96b9e71b2..295b2e48b0 100644 --- a/lib/Dialect/FIRRTL/Import/FIRParserAsserts.cpp +++ b/lib/Dialect/FIRRTL/Import/FIRParserAsserts.cpp @@ -404,7 +404,7 @@ ParseResult circt::firrtl::foldWhenEncodedVerifOp(PrintFOp printOp) { // Construct a `!whenCond | (value !== 1'bx)` predicate. Value notCond = predicate; predicate = builder.create(printOp.getSubstitutions()[0]); - predicate = builder.create(predicate); + predicate = builder.create(predicate); predicate = builder.create(predicate); predicate = builder.create(notCond, predicate); } diff --git a/lib/Dialect/FIRRTL/Transforms/CMakeLists.txt b/lib/Dialect/FIRRTL/Transforms/CMakeLists.txt index 6d85e3248e..357d72369c 100755 --- a/lib/Dialect/FIRRTL/Transforms/CMakeLists.txt +++ b/lib/Dialect/FIRRTL/Transforms/CMakeLists.txt @@ -21,6 +21,7 @@ add_circt_dialect_library(CIRCTFIRRTLTransforms InnerSymbolDCE.cpp LowerAnnotations.cpp LowerCHIRRTL.cpp + LowerIntrinsics.cpp LowerMemory.cpp LowerTypes.cpp LowerXMR.cpp diff --git a/lib/Dialect/FIRRTL/Transforms/LowerAnnotations.cpp b/lib/Dialect/FIRRTL/Transforms/LowerAnnotations.cpp index 951d679c41..c1378be302 100644 --- a/lib/Dialect/FIRRTL/Transforms/LowerAnnotations.cpp +++ b/lib/Dialect/FIRRTL/Transforms/LowerAnnotations.cpp @@ -236,7 +236,7 @@ static LogicalResult applyWithoutTargetImpl(const AnnoPathValue &target, } /// An applier which puts the annotation on the target and drops the 'target' -/// field from the annotaiton. Optionally handles non-local annotations. +/// field from the annotation. Optionally handles non-local annotations. /// Ensures the target resolves to an expected type of operation. template @@ -294,9 +294,9 @@ struct AnnoRecord { /// the FIRRTL Circuit, i.e., an Annotation which has no target. Historically, /// NoTargetAnnotations were used to control the Scala FIRRTL Compiler (SFC) or /// its passes, e.g., to set the output directory or to turn on a pass. -/// Examplesof these in the SFC are "firrtl.options.TargetDirAnnotation" to set +/// Examples of these in the SFC are "firrtl.options.TargetDirAnnotation" to set /// the output directory or "firrtl.stage.RunFIRRTLTransformAnnotation" to -/// casuse the SFC to schedule a specified pass. Instead of leaving these +/// cause the SFC to schedule a specified pass. Instead of leaving these /// floating or attaching them to the top-level MLIR module (which is a purer /// interpretation of "no target"), we choose to attach them to the Circuit even /// they do not "apply" to the Circuit. This gives later passes a common place, @@ -313,6 +313,7 @@ static const llvm::StringMap annotationRecords{{ {"circt.testLocalOnly", {stdResolve, applyWithoutTarget<>}}, {"circt.testNT", {noResolve, applyWithoutTarget<>}}, {"circt.missing", {tryResolve, applyWithoutTarget}}, + {"circt.intrinsic", {stdResolve, applyWithoutTarget}}, // Grand Central Views/Interfaces Annotations {extractGrandCentralClass, NoTargetAnnotation}, {grandCentralHierarchyFileAnnoClass, NoTargetAnnotation}, diff --git a/lib/Dialect/FIRRTL/Transforms/LowerIntrinsics.cpp b/lib/Dialect/FIRRTL/Transforms/LowerIntrinsics.cpp new file mode 100644 index 0000000000..a7dad40b88 --- /dev/null +++ b/lib/Dialect/FIRRTL/Transforms/LowerIntrinsics.cpp @@ -0,0 +1,249 @@ +//===- LowerIntrinsics.cpp - Lower Intrinsics -------------------*- 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 defines the LowerIntrinsics pass. This pass processes FIRRTL +// extmodules with intrinsic annotations and rewrites the instances as +// appropriate. +// +//===----------------------------------------------------------------------===// + +#include "PassDetails.h" +#include "circt/Dialect/FIRRTL/FIRRTLInstanceGraph.h" +#include "circt/Dialect/FIRRTL/FIRRTLOps.h" +#include "circt/Dialect/FIRRTL/FIRRTLTypes.h" +#include "circt/Dialect/FIRRTL/FIRRTLVisitors.h" +#include "circt/Dialect/FIRRTL/Namespace.h" +#include "circt/Dialect/FIRRTL/Passes.h" +#include "mlir/IR/Diagnostics.h" +#include "llvm/ADT/APSInt.h" +#include "llvm/ADT/PostOrderIterator.h" +#include "llvm/ADT/StringExtras.h" +#include "llvm/Support/Debug.h" + +using namespace circt; +using namespace firrtl; + +// Pass Infrastructure +//===----------------------------------------------------------------------===// + +namespace { +struct LowerIntrinsicsPass : public LowerIntrinsicsBase { + void runOnOperation() override; +}; +} // end anonymous namespace + +static ParseResult hasNPorts(StringRef name, FExtModuleOp mod, unsigned n) { + if (mod.getPorts().size() != n) { + mod.emitError(name) << " has " << mod.getPorts().size() + << " ports instead of " << n; + return failure(); + } + return success(); +} + +static ParseResult namedPort(StringRef name, FExtModuleOp mod, unsigned n, + StringRef portName) { + auto ports = mod.getPorts(); + if (n >= ports.size()) { + mod.emitError(name) << " missing port " << n; + return failure(); + } + if (!ports[n].getName().equals(portName)) { + mod.emitError(name) << " port " << n << " named '" << ports[n].getName() + << "' instead of '" << portName << "'"; + return failure(); + } + return success(); +} + +template +static ParseResult sizedPort(StringRef name, FExtModuleOp mod, unsigned n, + int32_t size) { + auto ports = mod.getPorts(); + if (n >= ports.size()) { + mod.emitError(name) << " missing port " << n; + return failure(); + } + if (!ports[n].type.isa()) { + mod.emitError(name) << " port " << n << " not a correct type"; + return failure(); + } + if (ports[n].type.cast().getWidth() != size) { + mod.emitError(name) << " port " << n << " not size " << size; + return failure(); + } + return success(); +} + +static ParseResult hasNParam(StringRef name, FExtModuleOp mod, unsigned n) { + unsigned num = 0; + if (mod.getParameters()) + num = mod.getParameters().size(); + if (n != num) { + mod.emitError(name) << " has " << num << " parameters instead of " << n; + return failure(); + } + return success(); +} +static ParseResult namedParam(StringRef name, FExtModuleOp mod, + StringRef paramName) { + for (auto a : mod.getParameters()) { + auto param = a.cast(); + if (param.getName().getValue().equals(paramName)) { + if (param.getValue().isa()) + return success(); + + mod.emitError(name) << " test has parameter '" << param.getName() + << "' which should be a string but is not"; + return failure(); + } + } + mod.emitError(name) << " is missing parameter " << paramName; + return failure(); +} + +static bool lowerCirctSizeof(InstancePathCache &instancePathCache, + FExtModuleOp mod) { + auto ports = mod.getPorts(); + if (hasNPorts("circt.sizeof", mod, 2) || + namedPort("circt.sizeof", mod, 0, "i") || + namedPort("circt.sizeof", mod, 1, "size") || + sizedPort("circt.sizeof", mod, 1, 32) || + hasNParam("circt.sizeof", mod, 0)) + return false; + + for (auto *use : instancePathCache.instanceGraph[mod]->uses()) { + auto inst = cast(use->getInstance().getOperation()); + ImplicitLocOpBuilder builder(inst.getLoc(), inst); + auto inputWire = builder.create(ports[0].type); + inst.getResult(0).replaceAllUsesWith(inputWire); + auto size = builder.create(inputWire); + inst.getResult(1).replaceAllUsesWith(size); + inst.erase(); + } + return true; +} + +static bool lowerCirctIsX(InstancePathCache &instancePathCache, + FExtModuleOp mod) { + auto ports = mod.getPorts(); + if (hasNPorts("circt.isaX", mod, 2) || namedPort("circt.isX", mod, 0, "i") || + namedPort("circt.isX", mod, 1, "found") || + sizedPort("circt.isX", mod, 1, 1) || + hasNParam("circt.isX", mod, 0)) + return false; + + for (auto *use : instancePathCache.instanceGraph[mod]->uses()) { + auto inst = cast(use->getInstance().getOperation()); + ImplicitLocOpBuilder builder(inst.getLoc(), inst); + auto inputWire = builder.create(ports[0].type); + inst.getResult(0).replaceAllUsesWith(inputWire); + auto size = builder.create(inputWire); + inst.getResult(1).replaceAllUsesWith(size); + inst.erase(); + } + return true; +} + +static bool lowerCirctPlusArgTest(InstancePathCache &instancePathCache, + FExtModuleOp mod) { + if (hasNPorts("circt.plusargs.test", mod, 1) || + namedPort("circt.plusargs.test", mod, 0, "found") || + sizedPort("circt.plusargs.test", mod, 0, 1) || + hasNParam("circt.plusargs.test", mod, 1) || + namedParam("circt.plusargs.test", mod, "FORMAT")) + return false; + + auto param = mod.getParameters()[0].cast(); + for (auto *use : instancePathCache.instanceGraph[mod]->uses()) { + auto inst = cast(use->getInstance().getOperation()); + ImplicitLocOpBuilder builder(inst.getLoc(), inst); + auto newop = builder.create( + param.getValue().cast()); + inst.getResult(0).replaceAllUsesWith(newop); + inst.erase(); + } + return true; +} + +static bool lowerCirctPlusArgValue(InstancePathCache &instancePathCache, + FExtModuleOp mod) { + if (hasNPorts("circt.plusargs.value", mod, 2) || + namedPort("circt.plusargs.value", mod, 0, "found") || + namedPort("circt.plusargs.value", mod, 1, "result") || + sizedPort("circt.plusargs.value", mod, 0, 1) || + hasNParam("circt.plusargs.value", mod, 1) || + namedParam("circt.plusargs.value", mod, "FORMAT")) + return false; + + auto param = mod.getParameters()[0].cast(); + + for (auto *use : instancePathCache.instanceGraph[mod]->uses()) { + auto inst = cast(use->getInstance().getOperation()); + ImplicitLocOpBuilder builder(inst.getLoc(), inst); + auto newop = builder.create( + inst.getResultTypes(), param.getValue().cast()); + inst.getResult(0).replaceAllUsesWith(newop.getFound()); + inst.getResult(1).replaceAllUsesWith(newop.getResult()); + inst.erase(); + } + return true; +} + +std::pair> + intrinsics[] = { + {"circt.sizeof", lowerCirctSizeof}, + {"circt.isX", lowerCirctIsX}, + {"circt.plusargs.test", lowerCirctPlusArgTest}, + {"circt.plusargs.value", lowerCirctPlusArgValue}, +}; + +// This is the main entrypoint for the lowering pass. +void LowerIntrinsicsPass::runOnOperation() { + size_t numFailures = 0; + size_t numConverted = 0; + InstancePathCache instancePathCache(getAnalysis()); + for (auto op : + llvm::make_early_inc_range(getOperation().getOps())) { + auto anno = AnnotationSet(op).getAnnotation("circt.intrinsic"); + if (!anno) + continue; + auto intname = anno.getMember("intrinsic"); + if (!intname) { + op.emitError("Intrinsic annotation with no intrinsic name"); + ++numFailures; + continue; + } + bool found = false; + for (const auto &intrinsic : intrinsics) { + if (intname.getValue().equals(intrinsic.first)) { + found = true; + if (intrinsic.second(instancePathCache, op)) { + ++numConverted; + op.erase(); + } else { + ++numFailures; + } + break; + } + } + if (!found) { + op.emitError("Unknown intrinsic '") << intname << "'"; + ++numFailures; + } + } + if (numFailures) + signalPassFailure(); + if (!numConverted) + markAllAnalysesPreserved(); +} + +/// This is the pass constructor. +std::unique_ptr circt::firrtl::createLowerIntrinsicsPass() { + return std::make_unique(); +} diff --git a/test/Conversion/FIRRTLToHW/lower-to-hw.mlir b/test/Conversion/FIRRTLToHW/lower-to-hw.mlir index 04d53c320b..9dd969db4d 100644 --- a/test/Conversion/FIRRTLToHW/lower-to-hw.mlir +++ b/test/Conversion/FIRRTLToHW/lower-to-hw.mlir @@ -208,7 +208,7 @@ firrtl.circuit "Simple" attributes {annotations = [{class = %28 = firrtl.andr %18 : (!firrtl.uint<14>) -> !firrtl.uint<1> // CHECK-NEXT: = comb.icmp bin ceq {{.*}}, %x_i1 - %x28 = firrtl.verif_isX %28 : !firrtl.uint<1> + %x28 = firrtl.int.isX %28 : !firrtl.uint<1> // CHECK-NEXT: [[XOREXT:%.+]] = comb.concat %c0_i11, [[XOR]] // CHECK-NEXT: [[SHIFT:%.+]] = comb.shru bin [[XOREXT]], [[VAL18]] : i14 diff --git a/test/Dialect/FIRRTL/SFCTests/constantProp.mlir b/test/Dialect/FIRRTL/SFCTests/constantProp.mlir index 19245e8042..c02c824f93 100644 --- a/test/Dialect/FIRRTL/SFCTests/constantProp.mlir +++ b/test/Dialect/FIRRTL/SFCTests/constantProp.mlir @@ -169,24 +169,6 @@ firrtl.circuit "padConstReg" { } } -// TODO: This requires SFCCompat -// "pad zero when constant propping a register replaced with zero" -firrtl.circuit "padZeroReg" { - // CHECK-LABEL: firrtl.module @padZeroReg - firrtl.module @padZeroReg(in %clock: !firrtl.clock, out %z: !firrtl.uint<16>) { - %_r = firrtl.reg droppable_name %clock : !firrtl.uint<8> - %c0_ui1 = firrtl.constant 0 : !firrtl.uint<1> - %0 = firrtl.or %_r, %c0_ui1 : (!firrtl.uint<8>, !firrtl.uint<1>) -> !firrtl.uint<8> - firrtl.connect %_r, %0 : !firrtl.uint<8>, !firrtl.uint<8> - %c171_ui8 = firrtl.constant 171 : !firrtl.uint<8> - %_n = firrtl.node droppable_name %c171_ui8 : !firrtl.uint<8> - %1 = firrtl.cat %_n, %_r : (!firrtl.uint<8>, !firrtl.uint<8>) -> !firrtl.uint<16> - firrtl.connect %z, %1 : !firrtl.uint<16>, !firrtl.uint<16> - // _HECK: %[[TMP:.+]] = firrtl.constant 43776 : !firrtl.uint<16> - // _HECK-NEXT: firrtl.strictconnect %z, %[[TMP]] : !firrtl.uint<16> - } -} - // should "pad constant connections to outputs when propagating" firrtl.circuit "padConstOut" { firrtl.module private @padConstOutChild(out %x: !firrtl.uint<8>) { diff --git a/test/Dialect/FIRRTL/canonicalization.mlir b/test/Dialect/FIRRTL/canonicalization.mlir index ae3f512190..73c46e0ab7 100644 --- a/test/Dialect/FIRRTL/canonicalization.mlir +++ b/test/Dialect/FIRRTL/canonicalization.mlir @@ -2488,7 +2488,7 @@ firrtl.module @AnnotationsBlockRemoval( } // CHECK-LABEL: firrtl.module @Verification -firrtl.module @Verification(in %clock: !firrtl.clock, in %p: !firrtl.uint<1>) { +firrtl.module @Verification(in %clock: !firrtl.clock, in %p: !firrtl.uint<1>, out %o : !firrtl.uint<1>) { %c0 = firrtl.constant 0 : !firrtl.uint<1> %c1 = firrtl.constant 1 : !firrtl.uint<1> @@ -2507,6 +2507,10 @@ firrtl.module @Verification(in %clock: !firrtl.clock, in %p: !firrtl.uint<1>) { firrtl.assume %clock, %c1, %p, "assume1" // CHECK-NOT: firrtl.cover firrtl.cover %clock, %c0, %p, "cover0" + + // CHECK-NOT: firrtl.int.isX + %x = firrtl.int.isX %c0 : !firrtl.uint<1> + firrtl.strictconnect %o, %x : !firrtl.uint<1> } // COMMON-LABEL: firrtl.module @MultibitMux diff --git a/test/Dialect/FIRRTL/lower-intrinsics.mlir b/test/Dialect/FIRRTL/lower-intrinsics.mlir new file mode 100644 index 0000000000..16f764eba3 --- /dev/null +++ b/test/Dialect/FIRRTL/lower-intrinsics.mlir @@ -0,0 +1,38 @@ +// RUN: circt-opt --pass-pipeline='builtin.module(firrtl.circuit(firrtl-lower-intrinsics))' %s | FileCheck %s + +// CHECK-LABEL: "Foo" +firrtl.circuit "Foo" { + // CHECK-NOT: NameDoesNotMatter + firrtl.extmodule @NameDoesNotMatter(in i : !firrtl.clock, out size : !firrtl.uint<32>) attributes + {annotations = [{class = "circt.intrinsic", intrinsic = "circt.sizeof"}]} + // CHECK-NOT: NameDoesNotMatter2 + firrtl.extmodule @NameDoesNotMatter2(in i : !firrtl.clock, out found : !firrtl.uint<1>) attributes + {annotations = [{class = "circt.intrinsic", intrinsic = "circt.isX"}]} + // CHECK-NOT: NameDoesNotMatter3 + firrtl.extmodule @NameDoesNotMatter3(out found : !firrtl.uint<1>) attributes + {annotations = [{class = "circt.intrinsic", intrinsic = "circt.plusargs.test"}]} + // CHECK-NOT: NameDoesNotMatter4 + firrtl.extmodule @NameDoesNotMatter4(out found : !firrtl.uint<1>, out result: !firrtl.uint<5>) attributes + {annotations = [{class = "circt.intrinsic", intrinsic = "circt.plusargs.value"}]} + + // CHECK: Foo + firrtl.module @Foo(in %clk : !firrtl.clock) { + %i1, %size = firrtl.instance "" @NameDoesNotMatter(in i : !firrtl.clock, out size : !firrtl.uint<32>) + // CHECK-NOT: NameDoesNotMatter + // CHECK: firrtl.int.sizeof + firrtl.strictconnect %i1, %clk : !firrtl.clock + + %i2, %found2 = firrtl.instance "" @NameDoesNotMatter2(in i : !firrtl.clock, out found : !firrtl.uint<1>) + // CHECK-NOT: NameDoesNotMatter2 + // CHECK: firrtl.int.isX + firrtl.strictconnect %i2, %clk : !firrtl.clock + + %found3 = firrtl.instance "" @NameDoesNotMatter3(out found : !firrtl.uint<1>) + // CHECK-NOT: NameDoesNotMatter3 + // CHECK: firrtl.int.plusargs.test "foo" + + %found4, %result1 = firrtl.instance "" @NameDoesNotMatter4(out found : !firrtl.uint<1>, out result: !firrtl.uint<5>) + // CHECK-NOT: NameDoesNotMatter4 + // CHECK: %5:2 = firrtl.int.plusargs.value "foo" : !firrtl.uint<5> + } +} diff --git a/test/Dialect/FIRRTL/parse-basic.fir b/test/Dialect/FIRRTL/parse-basic.fir index f0fd0bc866..f9d2e000d6 100644 --- a/test/Dialect/FIRRTL/parse-basic.fir +++ b/test/Dialect/FIRRTL/parse-basic.fir @@ -999,7 +999,7 @@ circuit MyModule : ; CHECK: firrtl.circuit "MyModule" { printf(clock, enable, "assertNotX:%d:value must not be X!", value) ; CHECK: [[TMP1:%.+]] = firrtl.not %cond ; CHECK: [[TMP2:%.+]] = firrtl.xorr %value - ; CHECK: [[TMP3:%.+]] = firrtl.verif_isX + ; CHECK: [[TMP3:%.+]] = firrtl.int.isX ; CHECK: [[TMP4:%.+]] = firrtl.not ; CHECK: [[TMP5:%.+]] = firrtl.or [[TMP1]], [[TMP4]] ; CHECK: firrtl.assert %clock, [[TMP5]], %enable, "value must not be X!" diff --git a/tools/firtool/firtool.cpp b/tools/firtool/firtool.cpp index 5fd68f317a..b910aba8bf 100644 --- a/tools/firtool/firtool.cpp +++ b/tools/firtool/firtool.cpp @@ -612,6 +612,8 @@ processBuffer(MLIRContext &context, TimingScope &ts, llvm::SourceMgr &sourceMgr, return success(); } + pm.nest().addPass(firrtl::createLowerIntrinsicsPass()); + // TODO: Move this to the O1 pipeline. pm.nest().nest().addPass( firrtl::createDropNamesPass(preserveMode));