diff --git a/include/circt/Dialect/HW/HWPasses.h b/include/circt/Dialect/HW/HWPasses.h index 93d4ea6a6954..b17c1894a322 100644 --- a/include/circt/Dialect/HW/HWPasses.h +++ b/include/circt/Dialect/HW/HWPasses.h @@ -26,6 +26,8 @@ std::unique_ptr createHWSpecializePass(); std::unique_ptr createPrintHWModuleGraphPass(); std::unique_ptr createFlattenIOPass(); std::unique_ptr createVerifyInnerRefNamespacePass(); +std::unique_ptr createHWLegalizeNamesPass(); +std::unique_ptr createLowerHierPathToOpsPass(); /// Generate the code for registering passes. #define GEN_PASS_REGISTRATION diff --git a/include/circt/Dialect/HW/HWStructure.td b/include/circt/Dialect/HW/HWStructure.td index 9ce9ced92df8..1b9557a5826f 100644 --- a/include/circt/Dialect/HW/HWStructure.td +++ b/include/circt/Dialect/HW/HWStructure.td @@ -485,6 +485,28 @@ def OutputOp : HWOp<"output", [Terminator, HasParent<"HWModuleOp">, let hasVerifier = 1; } +def HierPathToOp : HWOp<"hierpath.to", [Symbol, Pure]> { + let summary = "Path to current location operation"; + let description = [{ + The `hierpath.to` operation represents a path through the instance + hierarchy which starts from the top-level module and ends at the provided + symbol in the current instance wherein the `hierpath.to` is located. + The operation can be lowered to `hw.hierpath` and is useful in cases where + we want to track the absolute path of an instance in the design, but not yet + want to use `hw.hierpath` operations due to the extra overhead involved in + maintaining said op if the instance hierarchy is modified. + + The operation (and pass) should only be used in cases where modules are + expected to be unique within the design, i.e. only a single instance exists + for any given module in between the module instance and the top level module + (note: this does not imply that the entire module hierarchy must be + elaborated); thus the user must either ensure this by design, or elaborate + parts of the design accordingly. + }]; + let arguments = (ins SymbolNameAttr:$sym_name, FlatSymbolRefAttr:$target); + let assemblyFormat = "$sym_name `(` $target `)` attr-dict"; +} + def HierPathOp : HWOp<"hierpath", [IsolatedFromAbove, Symbol, DeclareOpInterfaceMethods]> { diff --git a/include/circt/Dialect/HW/Passes.td b/include/circt/Dialect/HW/Passes.td index 6507a1e6b43c..1098582f4ebf 100644 --- a/include/circt/Dialect/HW/Passes.td +++ b/include/circt/Dialect/HW/Passes.td @@ -58,4 +58,9 @@ def VerifyInnerRefNamespace : Pass<"hw-verify-irn"> { let constructor = "circt::hw::createVerifyInnerRefNamespacePass()"; } +def LowerHierPathToOps : Pass<"hw-lower-hierpathto-ops"> { + let summary = "Lower hw.hierpath.to operations to hw.hierpath operations."; + let constructor = "circt::hw::createLowerHierPathToOpsPass()"; +} + #endif // CIRCT_DIALECT_HW_PASSES_TD diff --git a/lib/Dialect/HW/Transforms/CMakeLists.txt b/lib/Dialect/HW/Transforms/CMakeLists.txt index 86fbb1b64a2b..00c58bbbf31b 100644 --- a/lib/Dialect/HW/Transforms/CMakeLists.txt +++ b/lib/Dialect/HW/Transforms/CMakeLists.txt @@ -4,6 +4,7 @@ add_circt_dialect_library(CIRCTHWTransforms PrintHWModuleGraph.cpp FlattenIO.cpp VerifyInnerRefNamespace.cpp + LowerHierPathToOps.cpp DEPENDS CIRCTHWTransformsIncGen diff --git a/lib/Dialect/HW/Transforms/LowerHierPathToOps.cpp b/lib/Dialect/HW/Transforms/LowerHierPathToOps.cpp new file mode 100644 index 000000000000..dd8011a6095e --- /dev/null +++ b/lib/Dialect/HW/Transforms/LowerHierPathToOps.cpp @@ -0,0 +1,109 @@ +//===- LowerHierPathToOps.cpp - Lower hw.hierpath.to ops --------*- 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 +//===----------------------------------------------------------------------===// +// +// Lowers `hw.hierpath.to` operations to `hw.hierpath` ops. +// +//===----------------------------------------------------------------------===// + +#include "PassDetails.h" +#include "circt/Dialect/HW/HWAttributes.h" +#include "circt/Dialect/HW/HWPasses.h" +#include "circt/Support/InstanceGraph.h" +#include "mlir/Transforms/DialectConversion.h" + +using namespace circt; +using namespace hw; + +namespace { + +struct HierPathToOpConversionPattern + : public OpConversionPattern { + HierPathToOpConversionPattern(MLIRContext *ctx, + igraph::InstanceGraph &instanceGraph) + : OpConversionPattern(ctx), instanceGraph(instanceGraph) {} + + LogicalResult + matchAndRewrite(hw::HierPathToOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + auto parentOp = dyn_cast(op->getParentOp()); + if (!parentOp) + return op.emitError("hw.hierpath.to must be in an op implementing " + "igraph::ModuleOpInterface."); + + // Process the inner level. + igraph::InstanceGraphNode *parentNode = instanceGraph.lookup(parentOp); + llvm::SmallVector path{ + hw::InnerRefAttr::get(parentNode->getModule().getModuleNameAttr(), + op.getTargetAttr().getAttr())}; + + while (parentNode) { + // Verify that exactly one instance of the parent exists. + size_t nUses = parentNode->getNumUses(); + if (nUses > 1) { + auto err = op.emitError( + "cannot lower hierpath.to ops in module hierarchies with " + "multiple instantiations."); + for (igraph::InstanceRecord *use : parentNode->uses()) + err.attachNote(use->getInstance().getLoc()) + << "instantiated here: " << use->getInstance(); + return err; + } + if (nUses == 0) { + // End of hierarchy. + break; + } + + igraph::InstanceRecord *instanceNode = *parentNode->uses().begin(); + igraph::InstanceGraphNode *instantiatedIn = instanceNode->getParent(); + if (!instantiatedIn) { + // We've recursed up to the top of the instance graph. + break; + } + + igraph::InstanceOpInterface instance = instanceNode->getInstance(); + path.insert( + path.begin(), + hw::InnerRefAttr::get(instantiatedIn->getModule().getModuleNameAttr(), + instance.getInstanceNameAttr())); + + // Recurse up to parent of parent, if any. + parentNode = instantiatedIn; + } + + // Create a new path op. + rewriter.replaceOpWithNewOp(op, op.getName(), + rewriter.getArrayAttr(path)); + return success(); + } + + igraph::InstanceGraph &instanceGraph; +}; // namespace + +struct LowerHierPathToOpsPass + : public LowerHierPathToOpsBase { + void runOnOperation() override; +}; +} // namespace + +void LowerHierPathToOpsPass::runOnOperation() { + Operation *parent = getOperation(); + igraph::InstanceGraph &instanceGraph = getAnalysis(); + + ConversionTarget target(getContext()); + target.addLegalDialect(); + target.addIllegalOp(); + + RewritePatternSet patterns(&getContext()); + patterns.add(&getContext(), instanceGraph); + + if (failed(applyPartialConversion(parent, target, std::move(patterns)))) + signalPassFailure(); +} + +std::unique_ptr circt::hw::createLowerHierPathToOpsPass() { + return std::make_unique(); +} diff --git a/test/Dialect/HW/path_to_here.mlir b/test/Dialect/HW/path_to_here.mlir new file mode 100644 index 000000000000..935ae839d4e6 --- /dev/null +++ b/test/Dialect/HW/path_to_here.mlir @@ -0,0 +1,98 @@ +// RUN: circt-opt --hw-lower-hierpathto-ops --split-input-file --verify-diagnostics %s | FileCheck %s + +hw.module @Root() { + // CHECK: hw.hierpath @sym [@ParentOne::@root, @Root::@w] + hw.hierpath.to @sym(@w) + %c0 = hw.constant false + hw.wire %c0 sym @w : i1 +} + +hw.module @ParentOne() { + hw.instance "root" @Root() -> () +} + +// ----- + +hw.module @Root() { + // CHECK: hw.hierpath @sym [@ParentTwo::@parentOne, @ParentOne::@root, @Root::@w] + hw.hierpath.to @sym(@w) + %c0 = hw.constant false + hw.wire %c0 sym @w : i1 +} + +hw.module @ParentOne() { + hw.instance "root" @Root() -> () +} + +hw.module @ParentTwo() { + hw.instance "parentOne" @ParentOne() -> () +} + +// ----- + +// CHECK-LABEL: hw.module @NoParent() { +// CHECK: hw.hierpath @sym [@NoParent::@w] +hw.module @NoParent() { + hw.hierpath.to @sym(@w) + %c0 = hw.constant false + hw.wire %c0 sym @w : i1 +} + +// ----- + +// Test non-unique instance hierarchy at the first level + +hw.module @Root() { + // expected-error @below {{cannot lower hierpath.to ops in module hierarchies with multiple instantiations.}} + // expected-error @below {{failed to legalize operation 'hw.hierpath.to' that was explicitly marked illegal}} + hw.hierpath.to @sym(@w) + %c0 = hw.constant false + hw.wire %c0 sym @w : i1 +} + +hw.module @FirstUser() { + // expected-note@below {{instantiated here: hw.instance "root" @Root() -> ()}} + hw.instance "root" @Root() -> () +} + +hw.module @SecondUser() { + // expected-note@below {{instantiated here: hw.instance "root" @Root() -> ()}} + hw.instance "root" @Root() -> () +} + +hw.module @Top() { + hw.instance "firstUser" @FirstUser() -> () + hw.instance "secondUser" @SecondUser() -> () +} + +// ----- + +// Test non-unique instance hierarchy at the second level. + +hw.module @Root() { + // expected-error @below {{cannot lower hierpath.to ops in module hierarchies with multiple instantiations.}} + // expected-error @below {{failed to legalize operation 'hw.hierpath.to' that was explicitly marked illegal}} + hw.hierpath.to @sym(@w) + %c0 = hw.constant false + hw.wire %c0 sym @w : i1 +} + +hw.module @RootParent() { + hw.instance "root" @Root() -> () +} + + +hw.module @FirstUser() { + // expected-note@below {{instantiated here: hw.instance "rp" @RootParent() -> ()}} + hw.instance "rp" @RootParent() -> () +} + +hw.module @SecondUser() { + // expected-note@below {{instantiated here: hw.instance "rp" @RootParent() -> ()}} + hw.instance "rp" @RootParent() -> () +} + +hw.module @Top() { + hw.instance "firstUser" @FirstUser() -> () + hw.instance "secondUser" @SecondUser() -> () +}