[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:
Morten Borup Petersen 2022-09-29 10:25:46 +02:00 committed by GitHub
parent 8c05af9467
commit 2cf788cc4f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 158 additions and 0 deletions

View File

@ -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
)

View File

@ -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;

View File

@ -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);

View File

@ -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);

View File

@ -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));
}

View File

@ -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
}