[MooreToCore] Support ConditionalOp (#7501)

Add a lowering pattern from `moore.conditional` to `scf.if`. This
currently relies on the condition being a two-valued integer after
lowering. Once we support four-valued integers at the core dialect
level, the lowering of `moore.conditional` will become a lot more
complicated.
This commit is contained in:
Fabian Schuiki 2024-08-10 11:46:18 -07:00 committed by GitHub
parent 43608bea24
commit 42401036d8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 93 additions and 25 deletions

View File

@ -516,7 +516,8 @@ def ConvertMooreToCore : Pass<"convert-moore-to-core", "mlir::ModuleOp"> {
}];
let constructor = "circt::createConvertMooreToCorePass()";
let dependentDialects = ["comb::CombDialect", "hw::HWDialect",
"llhd::LLHDDialect", "mlir::cf::ControlFlowDialect"];
"llhd::LLHDDialect", "mlir::cf::ControlFlowDialect",
"mlir::scf::SCFDialect"];
}
//===----------------------------------------------------------------------===//

View File

@ -1224,24 +1224,21 @@ def UnionExtractRefOp : MooreOp<"union_extract_ref"> {
def ConditionalOp : MooreOp<"conditional",[
RecursiveMemoryEffects,
NoRegionArguments,
SingleBlockImplicitTerminator<"moore::YieldOp">
]> {
let summary = "Conditional operation";
let description = [{
If cond_predicate is true, the operator returns the value of the first
expression without evaluating the second expression; if false, it returns
the value of the second expression without evaluating the first expression.
If cond_predicate evaluates to an ambiguous value (x or z), then both the
first expression and the second expression shall be evaluated, and compared
for logical equivalence. If that comparison is true (1), the operator shall
return either the first or second expression. Otherwise the operator returns
a result based on the data types of the expressions.
If the condition is true, this op evaluates the first region and returns its
result without evaluating the second region. If the the condition is false,
this op evaluates the second region and returns its result without
evaluating the first region.
When both the first and second expressions are of integral types, if the
cond_predicate evaluates to an ambiguous value and the expressions are not
logically equivalent, their results shall be combined bit by bit using the
table below to calculate the final result. The first and second expressions
are extended to the same width.
If the condition is unknown (X or Z), _both_ regions are evaluated. If both
results are equal as per `case_eq`, one of the results is returned. If the
results are not equal, this op returns a value based on the data types of
the results.
In case the results of the first and second region are of an integral type,
they are merged by applying the following bit-wise truth table:
|?: | 0 | 1 | X | Z |
|---|---|---|---|---|
@ -1250,6 +1247,7 @@ def ConditionalOp : MooreOp<"conditional",[
| X | X | X | X | X |
| Z | X | X | X | X |
Non-integral data types define other rules which are not yet implemented.
See IEEE 1800-2017 § 11.4.11 "Conditional operator".
}];
let arguments = (ins AnySingleBitType:$condition);
@ -1280,7 +1278,6 @@ def YieldOp : MooreOp<"yield", [
yielded.
}];
let arguments = (ins UnpackedType:$result);
let builders = [OpBuilder<(ins), [{ /* nothing to do */ }]>];
let assemblyFormat = [{
attr-dict $result `:` type($result)
}];

View File

@ -677,13 +677,13 @@ struct RvalueExprVisitor {
auto conditionalOp = builder.create<moore::ConditionalOp>(loc, type, value);
// Create blocks for true region and false region.
conditionalOp.getTrueRegion().emplaceBlock();
conditionalOp.getFalseRegion().emplaceBlock();
auto &trueBlock = conditionalOp.getTrueRegion().emplaceBlock();
auto &falseBlock = conditionalOp.getFalseRegion().emplaceBlock();
OpBuilder::InsertionGuard g(builder);
// Handle left expression.
builder.setInsertionPointToStart(conditionalOp.getBody(0));
builder.setInsertionPointToStart(&trueBlock);
auto trueValue = context.convertRvalueExpression(expr.left());
if (!trueValue)
return {};
@ -692,7 +692,7 @@ struct RvalueExprVisitor {
builder.create<moore::YieldOp>(loc, trueValue);
// Handle right expression.
builder.setInsertionPointToStart(conditionalOp.getBody(1));
builder.setInsertionPointToStart(&falseBlock);
auto falseValue = context.convertRvalueExpression(expr.right());
if (!falseValue)
return {};

View File

@ -8,12 +8,13 @@ add_circt_conversion_library(CIRCTMooreToCore
Core
LINK_LIBS PUBLIC
CIRCTMoore
CIRCTLLHD
CIRCTHW
CIRCTComb
CIRCTHW
CIRCTLLHD
CIRCTMoore
MLIRControlFlowDialect
MLIRFuncDialect
MLIRTransforms
MLIRSCFDialect
MLIRSideEffectInterfaces
MLIRTransforms
)

View File

@ -17,6 +17,7 @@
#include "circt/Dialect/Moore/MooreOps.h"
#include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h"
#include "mlir/Dialect/Func/IR/FuncOps.h"
#include "mlir/Dialect/SCF/IR/SCF.h"
#include "mlir/IR/BuiltinDialect.h"
#include "mlir/Interfaces/SideEffectInterfaces.h"
#include "mlir/Pass/Pass.h"
@ -960,6 +961,39 @@ struct AssignOpConversion : public OpConversionPattern<OpTy> {
}
};
struct ConditionalOpConversion : public OpConversionPattern<ConditionalOp> {
using OpConversionPattern::OpConversionPattern;
LogicalResult
matchAndRewrite(ConditionalOp op, OpAdaptor adaptor,
ConversionPatternRewriter &rewriter) const override {
// TODO: This lowering is only correct if the condition is two-valued. If
// the condition is X or Z, both branches of the conditional must be
// evaluated and merged with the appropriate lookup table. See documentation
// for `ConditionalOp`.
auto type = typeConverter->convertType(op.getType());
auto ifOp =
rewriter.create<scf::IfOp>(op.getLoc(), type, adaptor.getCondition());
rewriter.inlineRegionBefore(op.getTrueRegion(), ifOp.getThenRegion(),
ifOp.getThenRegion().end());
rewriter.inlineRegionBefore(op.getFalseRegion(), ifOp.getElseRegion(),
ifOp.getElseRegion().end());
rewriter.replaceOp(op, ifOp);
return success();
}
};
struct YieldOpConversion : public OpConversionPattern<YieldOp> {
using OpConversionPattern::OpConversionPattern;
LogicalResult
matchAndRewrite(YieldOp op, OpAdaptor adaptor,
ConversionPatternRewriter &rewriter) const override {
rewriter.replaceOpWithNewOp<scf::YieldOp>(op, adaptor.getResult());
return success();
}
};
} // namespace
//===----------------------------------------------------------------------===//
@ -992,6 +1026,8 @@ static void populateLegality(ConversionTarget &target) {
addGenericLegality<cf::CondBranchOp>(target);
addGenericLegality<cf::BranchOp>(target);
addGenericLegality<scf::IfOp>(target);
addGenericLegality<scf::YieldOp>(target);
addGenericLegality<func::CallOp>(target);
addGenericLegality<func::ReturnOp>(target);
addGenericLegality<UnrealizedConversionCastOp>(target);
@ -1081,7 +1117,8 @@ static void populateOpConversion(RewritePatternSet &patterns,
ExtractOpConversion, DynExtractOpConversion, DynExtractRefOpConversion,
ConversionOpConversion, ReadOpConversion, NamedConstantOpConv,
StructExtractOpConversion, StructExtractRefOpConversion,
ExtractRefOpConversion, StructCreateOpConversion,
ExtractRefOpConversion, StructCreateOpConversion, ConditionalOpConversion,
YieldOpConversion,
// Patterns of unary operations.
ReduceAndOpConversion, ReduceOrOpConversion, ReduceXorOpConversion,

View File

@ -258,6 +258,38 @@ func.func @Expressions(%arg0: !moore.i1, %arg1: !moore.l1, %arg2: !moore.i6, %ar
moore.wildcard_eq %arg0, %arg0 : !moore.i1 -> !moore.i1
moore.wildcard_ne %arg0, %arg0 : !moore.i1 -> !moore.i1
// CHECK-NEXT: [[RES:%.+]] = scf.if %arg0 -> (i6) {
// CHECK-NEXT: scf.yield %arg2 : i6
// CHECK-NEXT: } else {
// CHECK-NEXT: [[TMP:%.+]] = hw.constant 19 : i6
// CHECK-NEXT: scf.yield [[TMP]] : i6
// CHECK-NEXT: }
// CHECK-NEXT: comb.parity [[RES]] : i6
%k0 = moore.conditional %arg0 : i1 -> i6 {
moore.yield %arg2 : i6
} {
%0 = moore.constant 19 : i6
moore.yield %0 : i6
}
moore.reduce_xor %k0 : i6 -> i1
// CHECK-NEXT: [[RES:%.+]] = scf.if %arg1 -> (i6) {
// CHECK-NEXT: [[TMP:%.+]] = hw.constant 0 : i6
// CHECK-NEXT: scf.yield [[TMP]] : i6
// CHECK-NEXT: } else {
// CHECK-NEXT: [[TMP:%.+]] = hw.constant 19 : i6
// CHECK-NEXT: scf.yield [[TMP]] : i6
// CHECK-NEXT: }
// CHECK-NEXT: comb.parity [[RES]] : i6
%k1 = moore.conditional %arg1 : l1 -> l6 {
%0 = moore.constant bXXXXXX : l6
moore.yield %0 : l6
} {
%0 = moore.constant 19 : l6
moore.yield %0 : l6
}
moore.reduce_xor %k1 : l6 -> l1
// CHECK-NEXT: return
return
}