mirror of https://github.com/llvm/circt.git
[HW] Add PruneZeroValuedLogic to PrepareForEmission (#3935)
An attempt at #2219 (#2909 related), and motivated by `HandshakeToHW` wanting to delegate pruning of all data-less logic to after the lowering phase, instead of having to sprinkle special-case logic all over the lowering pass itself. This is not the support that is imediately needed for #2219, which concerns itself mostly with values that index into memories (singleton memories cannot be indexed => indexing wire is `i0`) but I'd expect something like this to inevitably be needed in the long run regardless. Instead, this commit concerns itself with combinational and sequential logic which uses `i0`-typed values. This commit takes an aggressive approach and prunes all arithmetic (and `seq.compreg`) operations which uses `i0` values under the assumption that any arithmetic taking part in an `i0` chain will not materialize to anything in hardware.
This commit is contained in:
parent
8c05af9467
commit
2cf788cc4f
|
@ -4,6 +4,7 @@ add_circt_translation_library(CIRCTExportVerilog
|
|||
LegalizeNames.cpp
|
||||
PrepareForEmission.cpp
|
||||
RearrangableOStream.cpp
|
||||
PruneZeroValuedLogic.cpp
|
||||
|
||||
ADDITIONAL_HEADER_DIRS
|
||||
|
||||
|
@ -20,4 +21,5 @@ add_circt_translation_library(CIRCTExportVerilog
|
|||
CIRCTSV
|
||||
MLIRPass
|
||||
MLIRSideEffectInterfaces
|
||||
MLIRTransforms
|
||||
)
|
||||
|
|
|
@ -2779,6 +2779,7 @@ void StmtEmitter::emitStatementExpression(Operation *op) {
|
|||
|
||||
// This is invoked for expressions that have a non-single use. This could
|
||||
// either be because they are dead or because they have multiple uses.
|
||||
// todo: use_empty could be prurned prior to emission.
|
||||
if (op->getResult(0).use_empty()) {
|
||||
indent() << "// Unused: ";
|
||||
--numStatementsEmitted;
|
||||
|
|
|
@ -305,6 +305,8 @@ bool isExpressionEmittedInline(Operation *op);
|
|||
void prepareHWModule(Block &block, const LoweringOptions &options);
|
||||
void prepareHWModule(hw::HWModuleOp module, const LoweringOptions &options);
|
||||
|
||||
void pruneZeroValuedLogic(hw::HWModuleOp module);
|
||||
|
||||
/// Rewrite module names and interfaces to not conflict with each other or with
|
||||
/// Verilog keywords.
|
||||
GlobalNameTable legalizeGlobalNames(ModuleOp topLevel);
|
||||
|
|
|
@ -970,6 +970,9 @@ static void legalizeHWModule(Block &block, const LoweringOptions &options) {
|
|||
|
||||
void ExportVerilog::prepareHWModule(hw::HWModuleOp module,
|
||||
const LoweringOptions &options) {
|
||||
// Zero-valued logic pruning.
|
||||
pruneZeroValuedLogic(module);
|
||||
|
||||
// Legalization.
|
||||
legalizeHWModule(*module.getBodyBlock(), options);
|
||||
|
||||
|
|
|
@ -0,0 +1,128 @@
|
|||
//===- PruneZeroValuedLogic.cpp - Prune zero-valued logic -----------------===//
|
||||
//
|
||||
// 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 transform removes zero-valued logic from a `hw.module`.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "ExportVerilogInternals.h"
|
||||
#include "circt/Dialect/Comb/CombOps.h"
|
||||
#include "circt/Dialect/HW/HWOps.h"
|
||||
#include "circt/Dialect/HW/HWPasses.h"
|
||||
#include "circt/Dialect/Seq/SeqOps.h"
|
||||
#include "mlir/IR/Builders.h"
|
||||
#include "mlir/IR/PatternMatch.h"
|
||||
#include "mlir/Transforms/DialectConversion.h"
|
||||
|
||||
using namespace llvm;
|
||||
using namespace mlir;
|
||||
using namespace circt;
|
||||
using namespace hw;
|
||||
|
||||
// Returns true if 't' is zero-width logic.
|
||||
// For now, this strictly relies on the announced bit-width of the type.
|
||||
static bool isZeroWidthLogic(Type t) {
|
||||
if (!t.isa<IntegerType>())
|
||||
return false;
|
||||
return t.getIntOrFloatBitWidth() == 0;
|
||||
}
|
||||
|
||||
static bool noI0Type(TypeRange types) {
|
||||
return llvm::none_of(types, [](Type type) { return isZeroWidthLogic(type); });
|
||||
}
|
||||
|
||||
static bool noI0TypedValue(ValueRange values) {
|
||||
return noI0Type(values.getTypes());
|
||||
}
|
||||
|
||||
static SmallVector<Value> removeI0Typed(ValueRange values) {
|
||||
SmallVector<Value> result;
|
||||
llvm::copy_if(values, std::back_inserter(result),
|
||||
[](Value value) { return !isZeroWidthLogic(value.getType()); });
|
||||
return result;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
class PruneTypeConverter : public mlir::TypeConverter {
|
||||
public:
|
||||
PruneTypeConverter() {
|
||||
addConversion([&](Type type, SmallVectorImpl<Type> &results) {
|
||||
if (!isZeroWidthLogic(type))
|
||||
results.push_back(type);
|
||||
return success();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// The NoI0OperandsConversionPattern will aggressively remove any operation
|
||||
// which has a zero-valued operand.
|
||||
template <typename TOp>
|
||||
struct NoI0OperandsConversionPattern : public OpConversionPattern<TOp> {
|
||||
public:
|
||||
using OpConversionPattern<TOp>::OpConversionPattern;
|
||||
using OpAdaptor = typename OpConversionPattern<TOp>::OpAdaptor;
|
||||
|
||||
LogicalResult
|
||||
matchAndRewrite(TOp op, OpAdaptor adaptor,
|
||||
ConversionPatternRewriter &rewriter) const override {
|
||||
if (noI0TypedValue(adaptor.getOperands()))
|
||||
return failure();
|
||||
|
||||
// Part of i0-typed logic - prune!
|
||||
rewriter.eraseOp(op);
|
||||
return success();
|
||||
}
|
||||
};
|
||||
|
||||
template <typename... TOp>
|
||||
static void addNoI0OperandsLegalizationPattern(ConversionTarget &target) {
|
||||
target.addDynamicallyLegalOp<TOp...>(
|
||||
[&](auto op) { return noI0TypedValue(op->getOperands()); });
|
||||
}
|
||||
|
||||
// A generic pruning pattern which prunes any operation which has an operand
|
||||
// with an i0 typed value. Similarly, an operation is legal if all of its
|
||||
// operands are not i0 typed.
|
||||
template <typename TOp>
|
||||
struct NoI0OperandPruningPattern {
|
||||
using ConversionPattern = NoI0OperandsConversionPattern<TOp>;
|
||||
static void addLegalizer(ConversionTarget &target) {
|
||||
addNoI0OperandsLegalizationPattern<TOp>(target);
|
||||
}
|
||||
};
|
||||
|
||||
// Adds a pruning pattern to the conversion target. TPattern is expected to
|
||||
// provides ConversionPattern definition and an addLegalizer function.
|
||||
template <typename... TPattern>
|
||||
static void addPruningPattern(ConversionTarget &target,
|
||||
RewritePatternSet &patterns,
|
||||
PruneTypeConverter &typeConverter) {
|
||||
(patterns.add<typename TPattern::ConversionPattern>(typeConverter,
|
||||
patterns.getContext()),
|
||||
...);
|
||||
(TPattern::addLegalizer(target), ...);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void ExportVerilog::pruneZeroValuedLogic(hw::HWModuleOp module) {
|
||||
ConversionTarget target(*module.getContext());
|
||||
RewritePatternSet patterns(module.getContext());
|
||||
PruneTypeConverter typeConverter;
|
||||
|
||||
target.addLegalDialect<sv::SVDialect, comb::CombDialect, hw::HWDialect>();
|
||||
|
||||
// Generic conversion and legalization patterns for operations that we
|
||||
// expect to be using i0 valued logic.
|
||||
addPruningPattern<NoI0OperandPruningPattern<sv::PAssignOp>,
|
||||
NoI0OperandPruningPattern<sv::BPAssignOp>,
|
||||
NoI0OperandPruningPattern<sv::AssignOp>>(target, patterns,
|
||||
typeConverter);
|
||||
|
||||
(void)applyPartialConversion(module, target, std::move(patterns));
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
// RUN: circt-opt %s --export-verilog --verify-diagnostics -o %t | FileCheck %s --strict-whitespace
|
||||
|
||||
// CHECK-LABEL: module zeroWidthPAssign(
|
||||
// CHECK: always_ff @(posedge clk) begin
|
||||
// CHECK-NEXT: end
|
||||
hw.module @zeroWidthPAssign(%arg0: i0, %clk: i1) -> (out: i0) {
|
||||
%0 = sv.reg {hw.verilogName = "_GEN"} : !hw.inout<i0>
|
||||
sv.alwaysff(posedge %clk) {
|
||||
sv.passign %0, %arg0 : i0
|
||||
}
|
||||
%1 = sv.read_inout %0 : !hw.inout<i0>
|
||||
hw.output %1 : i0
|
||||
}
|
||||
// CHECK-LABEL: module zeroWidthAssign(
|
||||
// CHECK: // Zero width: wire /*Zero Width*/ _GEN;
|
||||
// CHECK-NEXT: // Zero width: assign out = _GEN;
|
||||
hw.module @zeroWidthAssign(%arg0: i0, %clk: i1, %a: i0, %b: i1) -> (out: i0) {
|
||||
sv.assign %0, %1 : i0
|
||||
%0 = sv.wire {hw.verilogName = "_GEN"} : !hw.inout<i0>
|
||||
%1 = sv.read_inout %0 : !hw.inout<i0>
|
||||
hw.output %1 : i0
|
||||
}
|
Loading…
Reference in New Issue