diff --git a/include/circt/Dialect/LLHD/Transforms/Passes.h b/include/circt/Dialect/LLHD/Transforms/Passes.h index f83c3ca02346..4952f85f3dc3 100644 --- a/include/circt/Dialect/LLHD/Transforms/Passes.h +++ b/include/circt/Dialect/LLHD/Transforms/Passes.h @@ -36,6 +36,7 @@ std::unique_ptr> createEarlyCodeMotionPass(); std::unique_ptr> createTemporalCodeMotionPass(); +#define GEN_PASS_DECL_SIG2REG #define GEN_PASS_DECL_DESEQUENTIALIZATION #define GEN_PASS_REGISTRATION #include "circt/Dialect/LLHD/Transforms/Passes.h.inc" diff --git a/include/circt/Dialect/LLHD/Transforms/Passes.td b/include/circt/Dialect/LLHD/Transforms/Passes.td index 53d78203b4af..2f05f4edc046 100644 --- a/include/circt/Dialect/LLHD/Transforms/Passes.td +++ b/include/circt/Dialect/LLHD/Transforms/Passes.td @@ -111,6 +111,10 @@ def EarlyCodeMotion : Pass<"llhd-early-code-motion", "hw::HWModuleOp"> { let constructor = "circt::llhd::createEarlyCodeMotionPass()"; } +def Sig2Reg : Pass<"llhd-sig2reg", "hw::HWModuleOp"> { + let summary = "promote LLHD signals to SSA values"; +} + def TemporalCodeMotion : Pass<"llhd-temporal-code-motion", "hw::HWModuleOp"> { let summary = "move drive operations to the exit basic block in processes"; let description = [{ diff --git a/lib/Dialect/LLHD/Transforms/CMakeLists.txt b/lib/Dialect/LLHD/Transforms/CMakeLists.txt index 4f514a5dcdb7..1082f9ec3bfd 100644 --- a/lib/Dialect/LLHD/Transforms/CMakeLists.txt +++ b/lib/Dialect/LLHD/Transforms/CMakeLists.txt @@ -4,6 +4,7 @@ add_circt_dialect_library(CIRCTLLHDTransforms FunctionEliminationPass.cpp MemoryToBlockArgumentPass.cpp ProcessLoweringPass.cpp + Sig2RegPass.cpp TemporalCodeMotionPass.cpp TemporalRegions.cpp diff --git a/lib/Dialect/LLHD/Transforms/Sig2RegPass.cpp b/lib/Dialect/LLHD/Transforms/Sig2RegPass.cpp new file mode 100644 index 000000000000..f9f767a09406 --- /dev/null +++ b/lib/Dialect/LLHD/Transforms/Sig2RegPass.cpp @@ -0,0 +1,114 @@ +//===- Sig2RegPass.cpp - Implement the Sig2Reg Pass -----------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// Implement Pass to promote LLHD signals to SSA values. +// +//===----------------------------------------------------------------------===// + +#include "circt/Dialect/HW/HWOps.h" +#include "circt/Dialect/LLHD/IR/LLHDOps.h" +#include "circt/Dialect/LLHD/Transforms/Passes.h" +#include "llvm/Support/Debug.h" + +#define DEBUG_TYPE "llhd-sig2reg" + +namespace circt { +namespace llhd { +#define GEN_PASS_DEF_SIG2REG +#include "circt/Dialect/LLHD/Transforms/Passes.h.inc" +} // namespace llhd +} // namespace circt + +using namespace mlir; +using namespace circt; + +namespace { +struct Sig2RegPass : public circt::llhd::impl::Sig2RegBase { + void runOnOperation() override; +}; +} // namespace + +static LogicalResult promote(llhd::SignalOp sigOp) { + SmallVector probes; + llhd::DrvOp driveOp; + for (auto *user : sigOp.getResult().getUsers()) { + if (user->getBlock() != sigOp->getBlock()) { + LLVM_DEBUG( + { llvm::dbgs() << "Promotion failed: user in other block\n"; }); + return failure(); + } + + if (auto prbOp = dyn_cast(user)) { + probes.push_back(prbOp); + continue; + } + + if (auto drvOp = dyn_cast(user)) { + if (driveOp) { + LLVM_DEBUG({ llvm::dbgs() << "Promotion failed: multiple drivers\n"; }); + return failure(); + } + + if (drvOp.getEnable()) { + LLVM_DEBUG( + { llvm::dbgs() << "Promotion failed: conditional driver\n"; }); + return failure(); + } + + driveOp = drvOp; + continue; + } + + LLVM_DEBUG({ + llvm::dbgs() << "Promotion failed: user that is not a probe or drive: " + << *user << "\n"; + }); + return failure(); + } + + Value replacement; + if (driveOp) { + auto timeOp = driveOp.getTime().getDefiningOp(); + if (!timeOp) + return failure(); + + OpBuilder builder(driveOp); + if (timeOp.getValue().getTime() == 0 && timeOp.getValue().getDelta() == 0) + replacement = driveOp.getValue(); + else + replacement = builder.create( + driveOp.getLoc(), driveOp.getValue(), timeOp.getValue()); + } else { + replacement = sigOp.getInit(); + } + + for (auto prb : probes) { + prb.getResult().replaceAllUsesWith(replacement); + prb.erase(); + } + + if (driveOp) + driveOp.erase(); + + return success(); +} + +void Sig2RegPass::runOnOperation() { + hw::HWModuleOp moduleOp = getOperation(); + + for (auto sigOp : + llvm::make_early_inc_range(moduleOp.getOps())) { + LLVM_DEBUG( + { llvm::dbgs() << "\nAttempting to promote " << sigOp << "\n"; }); + if (failed(promote(sigOp))) + continue; + + LLVM_DEBUG({ llvm::dbgs() << "Successfully promoted!\n"; }); + sigOp.erase(); + } +} diff --git a/test/Dialect/LLHD/Transforms/sig2reg.mlir b/test/Dialect/LLHD/Transforms/sig2reg.mlir new file mode 100644 index 000000000000..fce837058af3 --- /dev/null +++ b/test/Dialect/LLHD/Transforms/sig2reg.mlir @@ -0,0 +1,57 @@ +// RUN: circt-opt --llhd-sig2reg %s | FileCheck %s + +func.func @getTime() -> !llhd.time { + %time = llhd.constant_time <1ns, 0d, 0e> + return %time : !llhd.time +} + +hw.module @basic(in %init : i32, in %cond : i1, in %in0 : i32, in %in1 : i32, out prb1 : i32, out prb2 : i32, out prb3 : i32, out prb4 : i32, out prb5 : i32, out prb6 : i32, out prb7 : i32) { + %opaque_time = func.call @getTime() : () -> !llhd.time + %epsilon = llhd.constant_time <0ns, 0d, 1e> + %delta = llhd.constant_time <0ns, 1d, 0e> + + // Promoted without delay op + %sig1 = llhd.sig %init : i32 + // Promoted with delay op + %sig2 = llhd.sig %init : i32 + // Not promoted because time is opaque, can support if the delay op takes a + // time value instead of attribute + %sig3 = llhd.sig %init : i32 + // Promoted to %init because no drive present + %sig4 = llhd.sig %init : i32 + // Not promoted because of drive condition + %sig5 = llhd.sig %init : i32 + // Not promoted because a user is in a nested region + %sig6 = llhd.sig %init : i32 + // Not promoted because of multiple drivers + %sig7 = llhd.sig %init : i32 + + llhd.drv %sig1, %in0 after %epsilon : !hw.inout + // CHECK: [[DELAY:%.+]] = llhd.delay %in0 by <0ns, 1d, 0e> : i32 + llhd.drv %sig2, %in0 after %delta : !hw.inout + llhd.drv %sig3, %in0 after %opaque_time : !hw.inout + + llhd.drv %sig5, %in0 after %epsilon if %cond : !hw.inout + + scf.if %cond { + llhd.drv %sig6, %in0 after %epsilon : !hw.inout + } + + llhd.drv %sig7, %in0 after %epsilon : !hw.inout + llhd.drv %sig7, %in1 after %delta : !hw.inout + + %prb1 = llhd.prb %sig1 : !hw.inout + %prb2 = llhd.prb %sig2 : !hw.inout + // CHECK: [[PRB3:%.+]] = llhd.prb %sig3 + %prb3 = llhd.prb %sig3 : !hw.inout + %prb4 = llhd.prb %sig4 : !hw.inout + // CHECK: [[PRB5:%.+]] = llhd.prb %sig5 + %prb5 = llhd.prb %sig5 : !hw.inout + // CHECK: [[PRB6:%.+]] = llhd.prb %sig6 + %prb6 = llhd.prb %sig6 : !hw.inout + // CHECK: [[PRB7:%.+]] = llhd.prb %sig7 + %prb7 = llhd.prb %sig7 : !hw.inout + + // CHECK: hw.output %in0, [[DELAY]], [[PRB3]], %init, [[PRB5]], [[PRB6]], [[PRB7]] : + hw.output %prb1, %prb2, %prb3, %prb4, %prb5, %prb6, %prb7 : i32, i32, i32, i32, i32, i32, i32 +}