circt/lib/Conversion/SCFToCalyx/SCFToCalyx.cpp

1838 lines
80 KiB
C++

//===- SCFToCalyx.cpp - SCF to Calyx pass entry point -----------*- 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
//
//===----------------------------------------------------------------------===//
//
// This is the main SCF to Calyx conversion pass implementation.
//
//===----------------------------------------------------------------------===//
#include "circt/Conversion/SCFToCalyx.h"
#include "../PassDetail.h"
#include "circt/Dialect/Calyx/CalyxHelpers.h"
#include "circt/Dialect/Calyx/CalyxLoweringUtils.h"
#include "circt/Dialect/Calyx/CalyxOps.h"
#include "circt/Dialect/Comb/CombOps.h"
#include "circt/Dialect/HW/HWOps.h"
#include "mlir/Conversion/LLVMCommon/ConversionTarget.h"
#include "mlir/Conversion/LLVMCommon/Pattern.h"
#include "mlir/Dialect/Arith/IR/Arith.h"
#include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h"
#include "mlir/Dialect/Func/IR/FuncOps.h"
#include "mlir/Dialect/MemRef/IR/MemRef.h"
#include "mlir/Dialect/SCF/IR/SCF.h"
#include "mlir/IR/AsmState.h"
#include "mlir/IR/Matchers.h"
#include "mlir/Support/LogicalResult.h"
#include "mlir/Transforms/GreedyPatternRewriteDriver.h"
#include "llvm/ADT/TypeSwitch.h"
#include <variant>
using namespace llvm;
using namespace mlir;
using namespace mlir::arith;
using namespace mlir::cf;
using namespace mlir::func;
namespace circt {
class ComponentLoweringStateInterface;
namespace scftocalyx {
//===----------------------------------------------------------------------===//
// Utility types
//===----------------------------------------------------------------------===//
class ScfWhileOp : public calyx::WhileOpInterface<scf::WhileOp> {
public:
explicit ScfWhileOp(scf::WhileOp op)
: calyx::WhileOpInterface<scf::WhileOp>(op) {}
Block::BlockArgListType getBodyArgs() override {
return getOperation().getAfterArguments();
}
Block *getBodyBlock() override { return &getOperation().getAfter().front(); }
Block *getConditionBlock() override {
return &getOperation().getBefore().front();
}
Value getConditionValue() override {
return getOperation().getConditionOp().getOperand(0);
}
std::optional<int64_t> getBound() override { return std::nullopt; }
};
class ScfForOp : public calyx::RepeatOpInterface<scf::ForOp> {
public:
explicit ScfForOp(scf::ForOp op) : calyx::RepeatOpInterface<scf::ForOp>(op) {}
Block::BlockArgListType getBodyArgs() override {
return getOperation().getRegion().getArguments();
}
Block *getBodyBlock() override {
return &getOperation().getRegion().getBlocks().front();
}
std::optional<int64_t> getBound() override {
return constantTripCount(getOperation().getLowerBound(),
getOperation().getUpperBound(),
getOperation().getStep());
}
};
//===----------------------------------------------------------------------===//
// Lowering state classes
//===----------------------------------------------------------------------===//
struct WhileScheduleable {
/// While operation to schedule.
ScfWhileOp whileOp;
};
struct ForScheduleable {
/// For operation to schedule.
ScfForOp forOp;
/// Bound
uint64_t bound;
};
struct CallScheduleable {
/// Instance for invoking.
calyx::InstanceOp instanceOp;
// CallOp for getting the arguments.
func::CallOp callOp;
};
/// A variant of types representing scheduleable operations.
using Scheduleable = std::variant<calyx::GroupOp, WhileScheduleable,
ForScheduleable, CallScheduleable>;
class WhileLoopLoweringStateInterface
: calyx::LoopLoweringStateInterface<ScfWhileOp> {
public:
SmallVector<calyx::GroupOp> getWhileLoopInitGroups(ScfWhileOp op) {
return getLoopInitGroups(std::move(op));
}
calyx::GroupOp buildWhileLoopIterArgAssignments(
OpBuilder &builder, ScfWhileOp op, calyx::ComponentOp componentOp,
Twine uniqueSuffix, MutableArrayRef<OpOperand> ops) {
return buildLoopIterArgAssignments(builder, std::move(op), componentOp,
uniqueSuffix, ops);
}
void addWhileLoopIterReg(ScfWhileOp op, calyx::RegisterOp reg, unsigned idx) {
return addLoopIterReg(std::move(op), reg, idx);
}
const DenseMap<unsigned, calyx::RegisterOp> &
getWhileLoopIterRegs(ScfWhileOp op) {
return getLoopIterRegs(std::move(op));
}
void setWhileLoopLatchGroup(ScfWhileOp op, calyx::GroupOp group) {
return setLoopLatchGroup(std::move(op), group);
}
calyx::GroupOp getWhileLoopLatchGroup(ScfWhileOp op) {
return getLoopLatchGroup(std::move(op));
}
void setWhileLoopInitGroups(ScfWhileOp op,
SmallVector<calyx::GroupOp> groups) {
return setLoopInitGroups(std::move(op), std::move(groups));
}
};
class ForLoopLoweringStateInterface
: calyx::LoopLoweringStateInterface<ScfForOp> {
public:
SmallVector<calyx::GroupOp> getForLoopInitGroups(ScfForOp op) {
return getLoopInitGroups(std::move(op));
}
calyx::GroupOp buildForLoopIterArgAssignments(
OpBuilder &builder, ScfForOp op, calyx::ComponentOp componentOp,
Twine uniqueSuffix, MutableArrayRef<OpOperand> ops) {
return buildLoopIterArgAssignments(builder, std::move(op), componentOp,
uniqueSuffix, ops);
}
void addForLoopIterReg(ScfForOp op, calyx::RegisterOp reg, unsigned idx) {
return addLoopIterReg(std::move(op), reg, idx);
}
const DenseMap<unsigned, calyx::RegisterOp> &getForLoopIterRegs(ScfForOp op) {
return getLoopIterRegs(std::move(op));
}
calyx::RegisterOp getForLoopIterReg(ScfForOp op, unsigned idx) {
return getLoopIterReg(std::move(op), idx);
}
void setForLoopLatchGroup(ScfForOp op, calyx::GroupOp group) {
return setLoopLatchGroup(std::move(op), group);
}
calyx::GroupOp getForLoopLatchGroup(ScfForOp op) {
return getLoopLatchGroup(std::move(op));
}
void setForLoopInitGroups(ScfForOp op, SmallVector<calyx::GroupOp> groups) {
return setLoopInitGroups(std::move(op), std::move(groups));
}
};
/// Handles the current state of lowering of a Calyx component. It is mainly
/// used as a key/value store for recording information during partial lowering,
/// which is required at later lowering passes.
class ComponentLoweringState : public calyx::ComponentLoweringStateInterface,
public WhileLoopLoweringStateInterface,
public ForLoopLoweringStateInterface,
public calyx::SchedulerInterface<Scheduleable> {
public:
ComponentLoweringState(calyx::ComponentOp component)
: calyx::ComponentLoweringStateInterface(component) {}
};
//===----------------------------------------------------------------------===//
// Conversion patterns
//===----------------------------------------------------------------------===//
/// Iterate through the operations of a source function and instantiate
/// components or primitives based on the type of the operations.
class BuildOpGroups : public calyx::FuncOpPartialLoweringPattern {
using FuncOpPartialLoweringPattern::FuncOpPartialLoweringPattern;
LogicalResult
partiallyLowerFuncToComp(FuncOp funcOp,
PatternRewriter &rewriter) const override {
/// We walk the operations of the funcOp to ensure that all def's have
/// been visited before their uses.
bool opBuiltSuccessfully = true;
funcOp.walk([&](Operation *_op) {
opBuiltSuccessfully &=
TypeSwitch<mlir::Operation *, bool>(_op)
.template Case<arith::ConstantOp, ReturnOp, BranchOpInterface,
/// SCF
scf::YieldOp, scf::WhileOp, scf::ForOp,
/// memref
memref::AllocOp, memref::AllocaOp, memref::LoadOp,
memref::StoreOp,
/// standard arithmetic
AddIOp, SubIOp, CmpIOp, ShLIOp, ShRUIOp, ShRSIOp,
AndIOp, XOrIOp, OrIOp, ExtUIOp, ExtSIOp, TruncIOp,
MulIOp, DivUIOp, DivSIOp, RemUIOp, RemSIOp,
SelectOp, IndexCastOp, CallOp>(
[&](auto op) { return buildOp(rewriter, op).succeeded(); })
.template Case<FuncOp, scf::ConditionOp>([&](auto) {
/// Skip: these special cases will be handled separately.
return true;
})
.Default([&](auto op) {
op->emitError() << "Unhandled operation during BuildOpGroups()";
return false;
});
return opBuiltSuccessfully ? WalkResult::advance()
: WalkResult::interrupt();
});
return success(opBuiltSuccessfully);
}
private:
/// Op builder specializations.
LogicalResult buildOp(PatternRewriter &rewriter, scf::YieldOp yieldOp) const;
LogicalResult buildOp(PatternRewriter &rewriter,
BranchOpInterface brOp) const;
LogicalResult buildOp(PatternRewriter &rewriter,
arith::ConstantOp constOp) const;
LogicalResult buildOp(PatternRewriter &rewriter, SelectOp op) const;
LogicalResult buildOp(PatternRewriter &rewriter, AddIOp op) const;
LogicalResult buildOp(PatternRewriter &rewriter, SubIOp op) const;
LogicalResult buildOp(PatternRewriter &rewriter, MulIOp op) const;
LogicalResult buildOp(PatternRewriter &rewriter, DivUIOp op) const;
LogicalResult buildOp(PatternRewriter &rewriter, DivSIOp op) const;
LogicalResult buildOp(PatternRewriter &rewriter, RemUIOp op) const;
LogicalResult buildOp(PatternRewriter &rewriter, RemSIOp op) const;
LogicalResult buildOp(PatternRewriter &rewriter, ShRUIOp op) const;
LogicalResult buildOp(PatternRewriter &rewriter, ShRSIOp op) const;
LogicalResult buildOp(PatternRewriter &rewriter, ShLIOp op) const;
LogicalResult buildOp(PatternRewriter &rewriter, AndIOp op) const;
LogicalResult buildOp(PatternRewriter &rewriter, OrIOp op) const;
LogicalResult buildOp(PatternRewriter &rewriter, XOrIOp op) const;
LogicalResult buildOp(PatternRewriter &rewriter, CmpIOp op) const;
LogicalResult buildOp(PatternRewriter &rewriter, TruncIOp op) const;
LogicalResult buildOp(PatternRewriter &rewriter, ExtUIOp op) const;
LogicalResult buildOp(PatternRewriter &rewriter, ExtSIOp op) const;
LogicalResult buildOp(PatternRewriter &rewriter, ReturnOp op) const;
LogicalResult buildOp(PatternRewriter &rewriter, IndexCastOp op) const;
LogicalResult buildOp(PatternRewriter &rewriter, memref::AllocOp op) const;
LogicalResult buildOp(PatternRewriter &rewriter, memref::AllocaOp op) const;
LogicalResult buildOp(PatternRewriter &rewriter, memref::LoadOp op) const;
LogicalResult buildOp(PatternRewriter &rewriter, memref::StoreOp op) const;
LogicalResult buildOp(PatternRewriter &rewriter, scf::WhileOp whileOp) const;
LogicalResult buildOp(PatternRewriter &rewriter, scf::ForOp forOp) const;
LogicalResult buildOp(PatternRewriter &rewriter, CallOp callOp) const;
/// buildLibraryOp will build a TCalyxLibOp inside a TGroupOp based on the
/// source operation TSrcOp.
template <typename TGroupOp, typename TCalyxLibOp, typename TSrcOp>
LogicalResult buildLibraryOp(PatternRewriter &rewriter, TSrcOp op,
TypeRange srcTypes, TypeRange dstTypes) const {
SmallVector<Type> types;
llvm::append_range(types, srcTypes);
llvm::append_range(types, dstTypes);
auto calyxOp =
getState<ComponentLoweringState>().getNewLibraryOpInstance<TCalyxLibOp>(
rewriter, op.getLoc(), types);
auto directions = calyxOp.portDirections();
SmallVector<Value, 4> opInputPorts;
SmallVector<Value, 4> opOutputPorts;
for (auto dir : enumerate(directions)) {
if (dir.value() == calyx::Direction::Input)
opInputPorts.push_back(calyxOp.getResult(dir.index()));
else
opOutputPorts.push_back(calyxOp.getResult(dir.index()));
}
assert(
opInputPorts.size() == op->getNumOperands() &&
opOutputPorts.size() == op->getNumResults() &&
"Expected an equal number of in/out ports in the Calyx library op with "
"respect to the number of operands/results of the source operation.");
/// Create assignments to the inputs of the library op.
auto group = createGroupForOp<TGroupOp>(rewriter, op);
rewriter.setInsertionPointToEnd(group.getBodyBlock());
for (auto dstOp : enumerate(opInputPorts))
rewriter.create<calyx::AssignOp>(op.getLoc(), dstOp.value(),
op->getOperand(dstOp.index()));
/// Replace the result values of the source operator with the new operator.
for (auto res : enumerate(opOutputPorts)) {
getState<ComponentLoweringState>().registerEvaluatingGroup(res.value(),
group);
op->getResult(res.index()).replaceAllUsesWith(res.value());
}
return success();
}
/// buildLibraryOp which provides in- and output types based on the operands
/// and results of the op argument.
template <typename TGroupOp, typename TCalyxLibOp, typename TSrcOp>
LogicalResult buildLibraryOp(PatternRewriter &rewriter, TSrcOp op) const {
return buildLibraryOp<TGroupOp, TCalyxLibOp, TSrcOp>(
rewriter, op, op.getOperandTypes(), op->getResultTypes());
}
/// Creates a group named by the basic block which the input op resides in.
template <typename TGroupOp>
TGroupOp createGroupForOp(PatternRewriter &rewriter, Operation *op) const {
Block *block = op->getBlock();
auto groupName = getState<ComponentLoweringState>().getUniqueName(
loweringState().blockName(block));
return calyx::createGroup<TGroupOp>(
rewriter, getState<ComponentLoweringState>().getComponentOp(),
op->getLoc(), groupName);
}
/// buildLibraryBinaryPipeOp will build a TCalyxLibBinaryPipeOp, to
/// deal with MulIOp, DivUIOp and RemUIOp.
template <typename TOpType, typename TSrcOp>
LogicalResult buildLibraryBinaryPipeOp(PatternRewriter &rewriter, TSrcOp op,
TOpType opPipe, Value out) const {
StringRef opName = TSrcOp::getOperationName().split(".").second;
Location loc = op.getLoc();
Type width = op.getResult().getType();
// Pass the result from the Operation to the Calyx primitive.
op.getResult().replaceAllUsesWith(out);
auto reg = createRegister(
op.getLoc(), rewriter, getComponent(), width.getIntOrFloatBitWidth(),
getState<ComponentLoweringState>().getUniqueName(opName));
// Operation pipelines are not combinational, so a GroupOp is required.
auto group = createGroupForOp<calyx::GroupOp>(rewriter, op);
OpBuilder builder(group->getRegion(0));
getState<ComponentLoweringState>().addBlockScheduleable(op->getBlock(),
group);
rewriter.setInsertionPointToEnd(group.getBodyBlock());
rewriter.create<calyx::AssignOp>(loc, opPipe.getLeft(), op.getLhs());
rewriter.create<calyx::AssignOp>(loc, opPipe.getRight(), op.getRhs());
// Write the output to this register.
rewriter.create<calyx::AssignOp>(loc, reg.getIn(), out);
// The write enable port is high when the pipeline is done.
rewriter.create<calyx::AssignOp>(loc, reg.getWriteEn(), opPipe.getDone());
// Set pipelineOp to high as long as its done signal is not high.
// This prevents the pipelineOP from executing for the cycle that we write
// to register. To get !(pipelineOp.done) we do 1 xor pipelineOp.done
hw::ConstantOp c1 = createConstant(loc, rewriter, getComponent(), 1, 1);
rewriter.create<calyx::AssignOp>(
loc, opPipe.getGo(), c1,
comb::createOrFoldNot(group.getLoc(), opPipe.getDone(), builder));
// The group is done when the register write is complete.
rewriter.create<calyx::GroupDoneOp>(loc, reg.getDone());
// Register the values for the pipeline.
getState<ComponentLoweringState>().registerEvaluatingGroup(out, group);
getState<ComponentLoweringState>().registerEvaluatingGroup(opPipe.getLeft(),
group);
getState<ComponentLoweringState>().registerEvaluatingGroup(
opPipe.getRight(), group);
return success();
}
/// Creates assignments within the provided group to the address ports of the
/// memoryOp based on the provided addressValues.
void assignAddressPorts(PatternRewriter &rewriter, Location loc,
calyx::GroupInterface group,
calyx::MemoryInterface memoryInterface,
Operation::operand_range addressValues) const {
IRRewriter::InsertionGuard guard(rewriter);
rewriter.setInsertionPointToEnd(group.getBody());
auto addrPorts = memoryInterface.addrPorts();
if (addressValues.empty()) {
assert(
addrPorts.size() == 1 &&
"We expected a 1 dimensional memory of size 1 because there were no "
"address assignment values");
// Assign to address 1'd0 in memory.
rewriter.create<calyx::AssignOp>(
loc, addrPorts[0],
createConstant(loc, rewriter, getComponent(), 1, 0));
} else {
assert(addrPorts.size() == addressValues.size() &&
"Mismatch between number of address ports of the provided memory "
"and address assignment values");
for (auto address : enumerate(addressValues))
rewriter.create<calyx::AssignOp>(loc, addrPorts[address.index()],
address.value());
}
}
};
LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
memref::LoadOp loadOp) const {
Value memref = loadOp.getMemref();
auto memoryInterface =
getState<ComponentLoweringState>().getMemoryInterface(memref);
auto group = createGroupForOp<calyx::GroupOp>(rewriter, loadOp);
assignAddressPorts(rewriter, loadOp.getLoc(), group, memoryInterface,
loadOp.getIndices());
rewriter.setInsertionPointToEnd(group.getBodyBlock());
bool needReg = true;
Value res;
Value regWriteEn =
createConstant(loadOp.getLoc(), rewriter, getComponent(), 1, 1);
if (memoryInterface.readEnOpt().has_value()) {
auto oneI1 =
calyx::createConstant(loadOp.getLoc(), rewriter, getComponent(), 1, 1);
rewriter.create<calyx::AssignOp>(loadOp.getLoc(), memoryInterface.readEn(),
oneI1);
regWriteEn = memoryInterface.readDone();
if (calyx::noStoresToMemory(memref) &&
calyx::singleLoadFromMemory(memref)) {
// Single load from memory; we do not need to write the output to a
// register. The readData value will be held until readEn is asserted
// again
needReg = false;
rewriter.create<calyx::GroupDoneOp>(loadOp.getLoc(),
memoryInterface.readDone());
// We refrain from replacing the loadOp result with
// memoryInterface.readData, since multiple loadOp's need to be converted
// to a single memory's ReadData. If this replacement is done now, we lose
// the link between which SSA memref::LoadOp values map to which groups
// for loading a value from the Calyx memory. At this point of lowering,
// we keep the memref::LoadOp SSA value, and do value replacement _after_
// control has been generated (see LateSSAReplacement). This is *vital*
// for things such as calyx::InlineCombGroups to be able to properly track
// which memory assignment groups belong to which accesses.
res = loadOp.getResult();
}
}
if (needReg) {
// Multiple loads from the same memory; In this case, we _may_ have a
// structural hazard in the design we generate. To get around this, we
// conservatively place a register in front of each load operation, and
// replace all uses of the loaded value with the register output. Reading
// for sequential memories will cause a read to take at least 2 cycles,
// but it will usually be better because combinational reads on memories
// can significantly decrease the maximum achievable frequency.
auto reg = createRegister(
loadOp.getLoc(), rewriter, getComponent(),
loadOp.getMemRefType().getElementTypeBitWidth(),
getState<ComponentLoweringState>().getUniqueName("load"));
rewriter.setInsertionPointToEnd(group.getBodyBlock());
rewriter.create<calyx::AssignOp>(loadOp.getLoc(), reg.getIn(),
memoryInterface.readData());
rewriter.create<calyx::AssignOp>(loadOp.getLoc(), reg.getWriteEn(),
regWriteEn);
rewriter.create<calyx::GroupDoneOp>(loadOp.getLoc(), reg.getDone());
loadOp.getResult().replaceAllUsesWith(reg.getOut());
res = reg.getOut();
}
getState<ComponentLoweringState>().registerEvaluatingGroup(res, group);
getState<ComponentLoweringState>().addBlockScheduleable(loadOp->getBlock(),
group);
return success();
}
LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
memref::StoreOp storeOp) const {
auto memoryInterface = getState<ComponentLoweringState>().getMemoryInterface(
storeOp.getMemref());
auto group = createGroupForOp<calyx::GroupOp>(rewriter, storeOp);
// This is a sequential group, so register it as being scheduleable for the
// block.
getState<ComponentLoweringState>().addBlockScheduleable(storeOp->getBlock(),
group);
assignAddressPorts(rewriter, storeOp.getLoc(), group, memoryInterface,
storeOp.getIndices());
rewriter.setInsertionPointToEnd(group.getBodyBlock());
rewriter.create<calyx::AssignOp>(
storeOp.getLoc(), memoryInterface.writeData(), storeOp.getValueToStore());
rewriter.create<calyx::AssignOp>(
storeOp.getLoc(), memoryInterface.writeEn(),
createConstant(storeOp.getLoc(), rewriter, getComponent(), 1, 1));
rewriter.create<calyx::GroupDoneOp>(storeOp.getLoc(),
memoryInterface.writeDone());
return success();
}
LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
MulIOp mul) const {
Location loc = mul.getLoc();
Type width = mul.getResult().getType(), one = rewriter.getI1Type();
auto mulPipe =
getState<ComponentLoweringState>()
.getNewLibraryOpInstance<calyx::MultPipeLibOp>(
rewriter, loc, {one, one, one, width, width, width, one});
return buildLibraryBinaryPipeOp<calyx::MultPipeLibOp>(
rewriter, mul, mulPipe,
/*out=*/mulPipe.getOut());
}
LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
DivUIOp div) const {
Location loc = div.getLoc();
Type width = div.getResult().getType(), one = rewriter.getI1Type();
auto divPipe =
getState<ComponentLoweringState>()
.getNewLibraryOpInstance<calyx::DivUPipeLibOp>(
rewriter, loc, {one, one, one, width, width, width, one});
return buildLibraryBinaryPipeOp<calyx::DivUPipeLibOp>(
rewriter, div, divPipe,
/*out=*/divPipe.getOut());
}
LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
DivSIOp div) const {
Location loc = div.getLoc();
Type width = div.getResult().getType(), one = rewriter.getI1Type();
auto divPipe =
getState<ComponentLoweringState>()
.getNewLibraryOpInstance<calyx::DivSPipeLibOp>(
rewriter, loc, {one, one, one, width, width, width, one});
return buildLibraryBinaryPipeOp<calyx::DivSPipeLibOp>(
rewriter, div, divPipe,
/*out=*/divPipe.getOut());
}
LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
RemUIOp rem) const {
Location loc = rem.getLoc();
Type width = rem.getResult().getType(), one = rewriter.getI1Type();
auto remPipe =
getState<ComponentLoweringState>()
.getNewLibraryOpInstance<calyx::RemUPipeLibOp>(
rewriter, loc, {one, one, one, width, width, width, one});
return buildLibraryBinaryPipeOp<calyx::RemUPipeLibOp>(
rewriter, rem, remPipe,
/*out=*/remPipe.getOut());
}
LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
RemSIOp rem) const {
Location loc = rem.getLoc();
Type width = rem.getResult().getType(), one = rewriter.getI1Type();
auto remPipe =
getState<ComponentLoweringState>()
.getNewLibraryOpInstance<calyx::RemSPipeLibOp>(
rewriter, loc, {one, one, one, width, width, width, one});
return buildLibraryBinaryPipeOp<calyx::RemSPipeLibOp>(
rewriter, rem, remPipe,
/*out=*/remPipe.getOut());
}
template <typename TAllocOp>
static LogicalResult buildAllocOp(ComponentLoweringState &componentState,
PatternRewriter &rewriter, TAllocOp allocOp) {
rewriter.setInsertionPointToStart(
componentState.getComponentOp().getBodyBlock());
MemRefType memtype = allocOp.getType();
SmallVector<int64_t> addrSizes;
SmallVector<int64_t> sizes;
for (int64_t dim : memtype.getShape()) {
sizes.push_back(dim);
addrSizes.push_back(calyx::handleZeroWidth(dim));
}
// If memref has no size (e.g., memref<i32>) create a 1 dimensional memory of
// size 1.
if (sizes.empty() && addrSizes.empty()) {
sizes.push_back(1);
addrSizes.push_back(1);
}
auto memoryOp = rewriter.create<calyx::SeqMemoryOp>(
allocOp.getLoc(), componentState.getUniqueName("mem"),
memtype.getElementType().getIntOrFloatBitWidth(), sizes, addrSizes);
// Externalize memories by default. This makes it easier for the native
// compiler to provide initialized memories.
memoryOp->setAttr("external",
IntegerAttr::get(rewriter.getI1Type(), llvm::APInt(1, 1)));
componentState.registerMemoryInterface(allocOp.getResult(),
calyx::MemoryInterface(memoryOp));
return success();
}
LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
memref::AllocOp allocOp) const {
return buildAllocOp(getState<ComponentLoweringState>(), rewriter, allocOp);
}
LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
memref::AllocaOp allocOp) const {
return buildAllocOp(getState<ComponentLoweringState>(), rewriter, allocOp);
}
LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
scf::YieldOp yieldOp) const {
if (yieldOp.getOperands().empty()) {
// If yield operands are empty, we assume we have a for loop.
auto forOp = dyn_cast<scf::ForOp>(yieldOp->getParentOp());
assert(forOp && "Empty yieldOps should only be located within ForOps");
ScfForOp forOpInterface(forOp);
// Get the ForLoop's Induction Register.
auto inductionReg =
getState<ComponentLoweringState>().getForLoopIterReg(forOpInterface, 0);
Type regWidth = inductionReg.getOut().getType();
// Adder should have same width as the inductionReg.
SmallVector<Type> types(3, regWidth);
auto addOp = getState<ComponentLoweringState>()
.getNewLibraryOpInstance<calyx::AddLibOp>(
rewriter, forOp.getLoc(), types);
auto directions = addOp.portDirections();
// For an add operation, we expect two input ports and one output port
SmallVector<Value, 2> opInputPorts;
Value opOutputPort;
for (auto dir : enumerate(directions)) {
switch (dir.value()) {
case calyx::Direction::Input: {
opInputPorts.push_back(addOp.getResult(dir.index()));
break;
}
case calyx::Direction::Output: {
opOutputPort = addOp.getResult(dir.index());
break;
}
}
}
// "Latch Group" increments inductionReg by forLoop's step value.
calyx::ComponentOp componentOp =
getState<ComponentLoweringState>().getComponentOp();
SmallVector<StringRef, 4> groupIdentifier = {
"incr", getState<ComponentLoweringState>().getUniqueName(forOp),
"induction", "var"};
auto groupOp = calyx::createGroup<calyx::GroupOp>(
rewriter, componentOp, forOp.getLoc(),
llvm::join(groupIdentifier, "_"));
rewriter.setInsertionPointToEnd(groupOp.getBodyBlock());
// Assign inductionReg.out to the left port of the adder.
Value leftOp = opInputPorts.front();
rewriter.create<calyx::AssignOp>(forOp.getLoc(), leftOp,
inductionReg.getOut());
// Assign forOp.getConstantStep to the right port of the adder.
Value rightOp = opInputPorts.back();
rewriter.create<calyx::AssignOp>(
forOp.getLoc(), rightOp,
createConstant(forOp->getLoc(), rewriter, componentOp,
regWidth.getIntOrFloatBitWidth(),
forOp.getConstantStep().value().getSExtValue()));
// Assign adder's output port to inductionReg.
buildAssignmentsForRegisterWrite(rewriter, groupOp, componentOp,
inductionReg, opOutputPort);
// Set group as For Loop's "latch" group.
getState<ComponentLoweringState>().setForLoopLatchGroup(forOpInterface,
groupOp);
getState<ComponentLoweringState>().registerEvaluatingGroup(opOutputPort,
groupOp);
return success();
}
// If yieldOp for a for loop is not empty, then we do not transform for loop.
if (dyn_cast<scf::ForOp>(yieldOp->getParentOp())) {
return yieldOp.getOperation()->emitError()
<< "Currently do not support non-empty yield operations inside for "
"loops. Run --scf-for-to-while before running --scf-to-calyx.";
}
auto whileOp = dyn_cast<scf::WhileOp>(yieldOp->getParentOp());
if (!whileOp) {
return yieldOp.getOperation()->emitError()
<< "Currently only support yield operations inside for and while "
"loops.";
}
ScfWhileOp whileOpInterface(whileOp);
auto assignGroup =
getState<ComponentLoweringState>().buildWhileLoopIterArgAssignments(
rewriter, whileOpInterface,
getState<ComponentLoweringState>().getComponentOp(),
getState<ComponentLoweringState>().getUniqueName(whileOp) + "_latch",
yieldOp->getOpOperands());
getState<ComponentLoweringState>().setWhileLoopLatchGroup(whileOpInterface,
assignGroup);
return success();
}
LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
BranchOpInterface brOp) const {
/// Branch argument passing group creation
/// Branch operands are passed through registers. In BuildBasicBlockRegs we
/// created registers for all branch arguments of each block. We now
/// create groups for assigning values to these registers.
Block *srcBlock = brOp->getBlock();
for (auto succBlock : enumerate(brOp->getSuccessors())) {
auto succOperands = brOp.getSuccessorOperands(succBlock.index());
if (succOperands.empty())
continue;
// Create operand passing group
std::string groupName = loweringState().blockName(srcBlock) + "_to_" +
loweringState().blockName(succBlock.value());
auto groupOp = calyx::createGroup<calyx::GroupOp>(rewriter, getComponent(),
brOp.getLoc(), groupName);
// Fetch block argument registers associated with the basic block
auto dstBlockArgRegs =
getState<ComponentLoweringState>().getBlockArgRegs(succBlock.value());
// Create register assignment for each block argument
for (auto arg : enumerate(succOperands.getForwardedOperands())) {
auto reg = dstBlockArgRegs[arg.index()];
calyx::buildAssignmentsForRegisterWrite(
rewriter, groupOp,
getState<ComponentLoweringState>().getComponentOp(), reg,
arg.value());
}
/// Register the group as a block argument group, to be executed
/// when entering the successor block from this block (srcBlock).
getState<ComponentLoweringState>().addBlockArgGroup(
srcBlock, succBlock.value(), groupOp);
}
return success();
}
/// For each return statement, we create a new group for assigning to the
/// previously created return value registers.
LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
ReturnOp retOp) const {
if (retOp.getNumOperands() == 0)
return success();
std::string groupName =
getState<ComponentLoweringState>().getUniqueName("ret_assign");
auto groupOp = calyx::createGroup<calyx::GroupOp>(rewriter, getComponent(),
retOp.getLoc(), groupName);
for (auto op : enumerate(retOp.getOperands())) {
auto reg = getState<ComponentLoweringState>().getReturnReg(op.index());
calyx::buildAssignmentsForRegisterWrite(
rewriter, groupOp, getState<ComponentLoweringState>().getComponentOp(),
reg, op.value());
}
/// Schedule group for execution for when executing the return op block.
getState<ComponentLoweringState>().addBlockScheduleable(retOp->getBlock(),
groupOp);
return success();
}
LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
arith::ConstantOp constOp) const {
/// Move constant operations to the compOp body as hw::ConstantOp's.
APInt value;
calyx::matchConstantOp(constOp, value);
auto hwConstOp = rewriter.replaceOpWithNewOp<hw::ConstantOp>(constOp, value);
hwConstOp->moveAfter(getComponent().getBodyBlock(),
getComponent().getBodyBlock()->begin());
return success();
}
LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
AddIOp op) const {
return buildLibraryOp<calyx::CombGroupOp, calyx::AddLibOp>(rewriter, op);
}
LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
SubIOp op) const {
return buildLibraryOp<calyx::CombGroupOp, calyx::SubLibOp>(rewriter, op);
}
LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
ShRUIOp op) const {
return buildLibraryOp<calyx::CombGroupOp, calyx::RshLibOp>(rewriter, op);
}
LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
ShRSIOp op) const {
return buildLibraryOp<calyx::CombGroupOp, calyx::SrshLibOp>(rewriter, op);
}
LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
ShLIOp op) const {
return buildLibraryOp<calyx::CombGroupOp, calyx::LshLibOp>(rewriter, op);
}
LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
AndIOp op) const {
return buildLibraryOp<calyx::CombGroupOp, calyx::AndLibOp>(rewriter, op);
}
LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
OrIOp op) const {
return buildLibraryOp<calyx::CombGroupOp, calyx::OrLibOp>(rewriter, op);
}
LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
XOrIOp op) const {
return buildLibraryOp<calyx::CombGroupOp, calyx::XorLibOp>(rewriter, op);
}
LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
SelectOp op) const {
return buildLibraryOp<calyx::CombGroupOp, calyx::MuxLibOp>(rewriter, op);
}
LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
CmpIOp op) const {
switch (op.getPredicate()) {
case CmpIPredicate::eq:
return buildLibraryOp<calyx::CombGroupOp, calyx::EqLibOp>(rewriter, op);
case CmpIPredicate::ne:
return buildLibraryOp<calyx::CombGroupOp, calyx::NeqLibOp>(rewriter, op);
case CmpIPredicate::uge:
return buildLibraryOp<calyx::CombGroupOp, calyx::GeLibOp>(rewriter, op);
case CmpIPredicate::ult:
return buildLibraryOp<calyx::CombGroupOp, calyx::LtLibOp>(rewriter, op);
case CmpIPredicate::ugt:
return buildLibraryOp<calyx::CombGroupOp, calyx::GtLibOp>(rewriter, op);
case CmpIPredicate::ule:
return buildLibraryOp<calyx::CombGroupOp, calyx::LeLibOp>(rewriter, op);
case CmpIPredicate::sge:
return buildLibraryOp<calyx::CombGroupOp, calyx::SgeLibOp>(rewriter, op);
case CmpIPredicate::slt:
return buildLibraryOp<calyx::CombGroupOp, calyx::SltLibOp>(rewriter, op);
case CmpIPredicate::sgt:
return buildLibraryOp<calyx::CombGroupOp, calyx::SgtLibOp>(rewriter, op);
case CmpIPredicate::sle:
return buildLibraryOp<calyx::CombGroupOp, calyx::SleLibOp>(rewriter, op);
}
llvm_unreachable("unsupported comparison predicate");
}
LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
TruncIOp op) const {
return buildLibraryOp<calyx::CombGroupOp, calyx::SliceLibOp>(
rewriter, op, {op.getOperand().getType()}, {op.getType()});
}
LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
ExtUIOp op) const {
return buildLibraryOp<calyx::CombGroupOp, calyx::PadLibOp>(
rewriter, op, {op.getOperand().getType()}, {op.getType()});
}
LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
ExtSIOp op) const {
return buildLibraryOp<calyx::CombGroupOp, calyx::ExtSILibOp>(
rewriter, op, {op.getOperand().getType()}, {op.getType()});
}
LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
IndexCastOp op) const {
Type sourceType = calyx::convIndexType(rewriter, op.getOperand().getType());
Type targetType = calyx::convIndexType(rewriter, op.getResult().getType());
unsigned targetBits = targetType.getIntOrFloatBitWidth();
unsigned sourceBits = sourceType.getIntOrFloatBitWidth();
LogicalResult res = success();
if (targetBits == sourceBits) {
/// Drop the index cast and replace uses of the target value with the source
/// value.
op.getResult().replaceAllUsesWith(op.getOperand());
} else {
/// pad/slice the source operand.
if (sourceBits > targetBits)
res = buildLibraryOp<calyx::CombGroupOp, calyx::SliceLibOp>(
rewriter, op, {sourceType}, {targetType});
else
res = buildLibraryOp<calyx::CombGroupOp, calyx::PadLibOp>(
rewriter, op, {sourceType}, {targetType});
}
rewriter.eraseOp(op);
return res;
}
LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
scf::WhileOp whileOp) const {
// Only need to add the whileOp to the BlockSchedulables scheduler interface.
// Everything else was handled in the `BuildWhileGroups` pattern.
ScfWhileOp scfWhileOp(whileOp);
getState<ComponentLoweringState>().addBlockScheduleable(
whileOp.getOperation()->getBlock(), WhileScheduleable{scfWhileOp});
return success();
}
LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
scf::ForOp forOp) const {
// Only need to add the forOp to the BlockSchedulables scheduler interface.
// Everything else was handled in the `BuildForGroups` pattern.
ScfForOp scfForOp(forOp);
// If we cannot compute the trip count of the for loop, then we should
// emit an error saying to use --scf-for-to-while
std::optional<uint64_t> bound = scfForOp.getBound();
if (!bound.has_value()) {
return scfForOp.getOperation()->emitError()
<< "Loop bound not statically known. Should "
"transform into while loop using `--scf-for-to-while` before "
"running --lower-scf-to-calyx.";
}
getState<ComponentLoweringState>().addBlockScheduleable(
forOp.getOperation()->getBlock(), ForScheduleable{
scfForOp,
bound.value(),
});
return success();
}
LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
CallOp callOp) const {
std::string instanceName = calyx::getInstanceName(callOp);
calyx::InstanceOp instanceOp =
getState<ComponentLoweringState>().getInstance(instanceName);
SmallVector<Value, 4> outputPorts;
auto portInfos = instanceOp.getReferencedComponent().getPortInfo();
for (auto [idx, portInfo] : enumerate(portInfos)) {
if (portInfo.direction == calyx::Direction::Output)
outputPorts.push_back(instanceOp.getResult(idx));
}
// Replacing a CallOp results in the out port of the instance.
for (auto [idx, result] : llvm::enumerate(callOp.getResults()))
rewriter.replaceAllUsesWith(result, outputPorts[idx]);
// CallScheduleanle requires an instance, while CallOp can be used to get the
// input ports.
getState<ComponentLoweringState>().addBlockScheduleable(
callOp.getOperation()->getBlock(), CallScheduleable{instanceOp, callOp});
return success();
}
/// Inlines Calyx ExecuteRegionOp operations within their parent blocks.
/// An execution region op (ERO) is inlined by:
/// i : add a sink basic block for all yield operations inside the
/// ERO to jump to
/// ii : Rewrite scf.yield calls inside the ERO to branch to the sink block
/// iii: inline the ERO region
/// TODO(#1850) evaluate the usefulness of this lowering pattern.
class InlineExecuteRegionOpPattern
: public OpRewritePattern<scf::ExecuteRegionOp> {
using OpRewritePattern::OpRewritePattern;
LogicalResult matchAndRewrite(scf::ExecuteRegionOp execOp,
PatternRewriter &rewriter) const override {
/// Determine type of "yield" operations inside the ERO.
TypeRange yieldTypes = execOp.getResultTypes();
/// Create sink basic block and rewrite uses of yield results to sink block
/// arguments.
rewriter.setInsertionPointAfter(execOp);
auto *sinkBlock = rewriter.splitBlock(
execOp->getBlock(),
execOp.getOperation()->getIterator()->getNextNode()->getIterator());
sinkBlock->addArguments(
yieldTypes,
SmallVector<Location, 4>(yieldTypes.size(), rewriter.getUnknownLoc()));
for (auto res : enumerate(execOp.getResults()))
res.value().replaceAllUsesWith(sinkBlock->getArgument(res.index()));
/// Rewrite yield calls as branches.
for (auto yieldOp :
make_early_inc_range(execOp.getRegion().getOps<scf::YieldOp>())) {
rewriter.setInsertionPointAfter(yieldOp);
rewriter.replaceOpWithNewOp<BranchOp>(yieldOp, sinkBlock,
yieldOp.getOperands());
}
/// Inline the regionOp.
auto *preBlock = execOp->getBlock();
auto *execOpEntryBlock = &execOp.getRegion().front();
auto *postBlock = execOp->getBlock()->splitBlock(execOp);
rewriter.inlineRegionBefore(execOp.getRegion(), postBlock);
rewriter.mergeBlocks(postBlock, preBlock);
rewriter.eraseOp(execOp);
/// Finally, erase the unused entry block of the execOp region.
rewriter.mergeBlocks(execOpEntryBlock, preBlock);
return success();
}
};
/// Creates a new Calyx component for each FuncOp in the program.
struct FuncOpConversion : public calyx::FuncOpPartialLoweringPattern {
using FuncOpPartialLoweringPattern::FuncOpPartialLoweringPattern;
LogicalResult
partiallyLowerFuncToComp(FuncOp funcOp,
PatternRewriter &rewriter) const override {
/// Maintain a mapping between funcOp input arguments and the port index
/// which the argument will eventually map to.
DenseMap<Value, unsigned> funcOpArgRewrites;
/// Maintain a mapping between funcOp output indexes and the component
/// output port index which the return value will eventually map to.
DenseMap<unsigned, unsigned> funcOpResultMapping;
/// Maintain a mapping between an external memory argument (identified by a
/// memref) and eventual component input- and output port indices that will
/// map to the memory ports. The pair denotes the start index of the memory
/// ports in the in- and output ports of the component. Ports are expected
/// to be ordered in the same manner as they are added by
/// calyx::appendPortsForExternalMemref.
DenseMap<Value, std::pair<unsigned, unsigned>> extMemoryCompPortIndices;
/// Create I/O ports. Maintain separate in/out port vectors to determine
/// which port index each function argument will eventually map to.
SmallVector<calyx::PortInfo> inPorts, outPorts;
FunctionType funcType = funcOp.getFunctionType();
unsigned extMemCounter = 0;
for (auto arg : enumerate(funcOp.getArguments())) {
if (arg.value().getType().isa<MemRefType>()) {
/// External memories
auto memName =
"ext_mem" + std::to_string(extMemoryCompPortIndices.size());
extMemoryCompPortIndices[arg.value()] = {inPorts.size(),
outPorts.size()};
calyx::appendPortsForExternalMemref(rewriter, memName, arg.value(),
extMemCounter++, inPorts, outPorts);
} else {
/// Single-port arguments
std::string inName;
if (auto portNameAttr = funcOp.getArgAttrOfType<StringAttr>(
arg.index(), scfToCalyx::sPortNameAttr))
inName = portNameAttr.str();
else
inName = "in" + std::to_string(arg.index());
funcOpArgRewrites[arg.value()] = inPorts.size();
inPorts.push_back(calyx::PortInfo{
rewriter.getStringAttr(inName),
calyx::convIndexType(rewriter, arg.value().getType()),
calyx::Direction::Input,
DictionaryAttr::get(rewriter.getContext(), {})});
}
}
for (auto res : enumerate(funcType.getResults())) {
std::string resName;
if (auto portNameAttr = funcOp.getResultAttrOfType<StringAttr>(
res.index(), scfToCalyx::sPortNameAttr))
resName = portNameAttr.str();
else
resName = "out" + std::to_string(res.index());
funcOpResultMapping[res.index()] = outPorts.size();
outPorts.push_back(calyx::PortInfo{
rewriter.getStringAttr(resName),
calyx::convIndexType(rewriter, res.value()), calyx::Direction::Output,
DictionaryAttr::get(rewriter.getContext(), {})});
}
/// We've now recorded all necessary indices. Merge in- and output ports
/// and add the required mandatory component ports.
auto ports = inPorts;
llvm::append_range(ports, outPorts);
calyx::addMandatoryComponentPorts(rewriter, ports);
/// Create a calyx::ComponentOp corresponding to the to-be-lowered function.
auto compOp = rewriter.create<calyx::ComponentOp>(
funcOp.getLoc(), rewriter.getStringAttr(funcOp.getSymName()), ports);
std::string funcName = "func_" + funcOp.getSymName().str();
rewriter.modifyOpInPlace(funcOp, [&]() { funcOp.setSymName(funcName); });
/// Mark this component as the toplevel.
compOp->setAttr("toplevel", rewriter.getUnitAttr());
/// Store the function-to-component mapping.
functionMapping[funcOp] = compOp;
auto *compState = loweringState().getState<ComponentLoweringState>(compOp);
compState->setFuncOpResultMapping(funcOpResultMapping);
/// Rewrite funcOp SSA argument values to the CompOp arguments.
for (auto &mapping : funcOpArgRewrites)
mapping.getFirst().replaceAllUsesWith(
compOp.getArgument(mapping.getSecond()));
/// Register external memories
for (auto extMemPortIndices : extMemoryCompPortIndices) {
/// Create a mapping for the in- and output ports using the Calyx memory
/// port structure.
calyx::MemoryPortsImpl extMemPorts;
unsigned inPortsIt = extMemPortIndices.getSecond().first;
unsigned outPortsIt = extMemPortIndices.getSecond().second +
compOp.getInputPortInfo().size();
extMemPorts.readData = compOp.getArgument(inPortsIt++);
extMemPorts.writeDone = compOp.getArgument(inPortsIt);
extMemPorts.writeData = compOp.getArgument(outPortsIt++);
unsigned nAddresses = extMemPortIndices.getFirst()
.getType()
.cast<MemRefType>()
.getShape()
.size();
for (unsigned j = 0; j < nAddresses; ++j)
extMemPorts.addrPorts.push_back(compOp.getArgument(outPortsIt++));
extMemPorts.writeEn = compOp.getArgument(outPortsIt);
/// Register the external memory ports as a memory interface within the
/// component.
compState->registerMemoryInterface(extMemPortIndices.getFirst(),
calyx::MemoryInterface(extMemPorts));
}
return success();
}
};
/// In BuildWhileGroups, a register is created for each iteration argumenet of
/// the while op. These registers are then written to on the while op
/// terminating yield operation alongside before executing the whileOp in the
/// schedule, to set the initial values of the argument registers.
class BuildWhileGroups : public calyx::FuncOpPartialLoweringPattern {
using FuncOpPartialLoweringPattern::FuncOpPartialLoweringPattern;
LogicalResult
partiallyLowerFuncToComp(FuncOp funcOp,
PatternRewriter &rewriter) const override {
LogicalResult res = success();
funcOp.walk([&](Operation *op) {
// Only work on ops that support the ScfWhileOp.
if (!isa<scf::WhileOp>(op))
return WalkResult::advance();
auto scfWhileOp = cast<scf::WhileOp>(op);
ScfWhileOp whileOp(scfWhileOp);
getState<ComponentLoweringState>().setUniqueName(whileOp.getOperation(),
"while");
/// Check for do-while loops.
/// TODO(mortbopet) can we support these? for now, do not support loops
/// where iterargs are changed in the 'before' region. scf.WhileOp also
/// has support for different types of iter_args and return args which we
/// also do not support; iter_args and while return values are placed in
/// the same registers.
for (auto barg :
enumerate(scfWhileOp.getBefore().front().getArguments())) {
auto condOp = scfWhileOp.getConditionOp().getArgs()[barg.index()];
if (barg.value() != condOp) {
res = whileOp.getOperation()->emitError()
<< loweringState().irName(barg.value())
<< " != " << loweringState().irName(condOp)
<< "do-while loops not supported; expected iter-args to "
"remain untransformed in the 'before' region of the "
"scf.while op.";
return WalkResult::interrupt();
}
}
/// Create iteration argument registers.
/// The iteration argument registers will be referenced:
/// - In the "before" part of the while loop, calculating the conditional,
/// - In the "after" part of the while loop,
/// - Outside the while loop, rewriting the while loop return values.
for (auto arg : enumerate(whileOp.getBodyArgs())) {
std::string name = getState<ComponentLoweringState>()
.getUniqueName(whileOp.getOperation())
.str() +
"_arg" + std::to_string(arg.index());
auto reg =
createRegister(arg.value().getLoc(), rewriter, getComponent(),
arg.value().getType().getIntOrFloatBitWidth(), name);
getState<ComponentLoweringState>().addWhileLoopIterReg(whileOp, reg,
arg.index());
arg.value().replaceAllUsesWith(reg.getOut());
/// Also replace uses in the "before" region of the while loop
whileOp.getConditionBlock()
->getArgument(arg.index())
.replaceAllUsesWith(reg.getOut());
}
/// Create iter args initial value assignment group(s), one per register.
SmallVector<calyx::GroupOp> initGroups;
auto numOperands = whileOp.getOperation()->getNumOperands();
for (size_t i = 0; i < numOperands; ++i) {
auto initGroupOp =
getState<ComponentLoweringState>().buildWhileLoopIterArgAssignments(
rewriter, whileOp,
getState<ComponentLoweringState>().getComponentOp(),
getState<ComponentLoweringState>().getUniqueName(
whileOp.getOperation()) +
"_init_" + std::to_string(i),
whileOp.getOperation()->getOpOperand(i));
initGroups.push_back(initGroupOp);
}
getState<ComponentLoweringState>().setWhileLoopInitGroups(whileOp,
initGroups);
return WalkResult::advance();
});
return res;
}
};
/// In BuildForGroups, a register is created for the iteration argument of
/// the for op. This register is then initialized to the lowerBound of the for
/// loop in a group that executes the for loop.
class BuildForGroups : public calyx::FuncOpPartialLoweringPattern {
using FuncOpPartialLoweringPattern::FuncOpPartialLoweringPattern;
LogicalResult
partiallyLowerFuncToComp(FuncOp funcOp,
PatternRewriter &rewriter) const override {
LogicalResult res = success();
funcOp.walk([&](Operation *op) {
// Only work on ops that support the ScfForOp.
if (!isa<scf::ForOp>(op))
return WalkResult::advance();
auto scfForOp = cast<scf::ForOp>(op);
ScfForOp forOp(scfForOp);
getState<ComponentLoweringState>().setUniqueName(forOp.getOperation(),
"for");
// Create a register for the InductionVar, and set that Register as the
// only IterReg for the For Loop
auto inductionVar = forOp.getOperation().getInductionVar();
SmallVector<std::string, 3> inductionVarIdentifiers = {
getState<ComponentLoweringState>()
.getUniqueName(forOp.getOperation())
.str(),
"induction", "var"};
std::string name = llvm::join(inductionVarIdentifiers, "_");
auto reg =
createRegister(inductionVar.getLoc(), rewriter, getComponent(),
inductionVar.getType().getIntOrFloatBitWidth(), name);
getState<ComponentLoweringState>().addForLoopIterReg(forOp, reg, 0);
inductionVar.replaceAllUsesWith(reg.getOut());
// Create InitGroup that sets the InductionVar to LowerBound
calyx::ComponentOp componentOp =
getState<ComponentLoweringState>().getComponentOp();
SmallVector<calyx::GroupOp> initGroups;
SmallVector<std::string, 4> groupIdentifiers = {
"init",
getState<ComponentLoweringState>()
.getUniqueName(forOp.getOperation())
.str(),
"induction", "var"};
std::string groupName = llvm::join(groupIdentifiers, "_");
auto groupOp = calyx::createGroup<calyx::GroupOp>(
rewriter, componentOp, forOp.getLoc(), groupName);
buildAssignmentsForRegisterWrite(rewriter, groupOp, componentOp, reg,
forOp.getOperation().getLowerBound());
initGroups.push_back(groupOp);
getState<ComponentLoweringState>().setForLoopInitGroups(forOp,
initGroups);
return WalkResult::advance();
});
return res;
}
};
/// Builds a control schedule by traversing the CFG of the function and
/// associating this with the previously created groups.
/// For simplicity, the generated control flow is expanded for all possible
/// paths in the input DAG. This elaborated control flow is later reduced in
/// the runControlFlowSimplification passes.
class BuildControl : public calyx::FuncOpPartialLoweringPattern {
using FuncOpPartialLoweringPattern::FuncOpPartialLoweringPattern;
LogicalResult
partiallyLowerFuncToComp(FuncOp funcOp,
PatternRewriter &rewriter) const override {
auto *entryBlock = &funcOp.getBlocks().front();
rewriter.setInsertionPointToStart(
getComponent().getControlOp().getBodyBlock());
auto topLevelSeqOp = rewriter.create<calyx::SeqOp>(funcOp.getLoc());
DenseSet<Block *> path;
return buildCFGControl(path, rewriter, topLevelSeqOp.getBodyBlock(),
nullptr, entryBlock);
}
private:
/// Sequentially schedules the groups that registered themselves with
/// 'block'.
LogicalResult scheduleBasicBlock(PatternRewriter &rewriter,
const DenseSet<Block *> &path,
mlir::Block *parentCtrlBlock,
mlir::Block *block) const {
auto compBlockScheduleables =
getState<ComponentLoweringState>().getBlockScheduleables(block);
auto loc = block->front().getLoc();
if (compBlockScheduleables.size() > 1) {
auto seqOp = rewriter.create<calyx::SeqOp>(loc);
parentCtrlBlock = seqOp.getBodyBlock();
}
for (auto &group : compBlockScheduleables) {
rewriter.setInsertionPointToEnd(parentCtrlBlock);
if (auto groupPtr = std::get_if<calyx::GroupOp>(&group); groupPtr) {
rewriter.create<calyx::EnableOp>(groupPtr->getLoc(),
groupPtr->getSymName());
} else if (auto whileSchedPtr = std::get_if<WhileScheduleable>(&group);
whileSchedPtr) {
auto &whileOp = whileSchedPtr->whileOp;
auto whileCtrlOp = buildWhileCtrlOp(
whileOp,
getState<ComponentLoweringState>().getWhileLoopInitGroups(whileOp),
rewriter);
rewriter.setInsertionPointToEnd(whileCtrlOp.getBodyBlock());
auto whileBodyOp =
rewriter.create<calyx::SeqOp>(whileOp.getOperation()->getLoc());
auto *whileBodyOpBlock = whileBodyOp.getBodyBlock();
/// Only schedule the 'after' block. The 'before' block is
/// implicitly scheduled when evaluating the while condition.
LogicalResult res = buildCFGControl(path, rewriter, whileBodyOpBlock,
block, whileOp.getBodyBlock());
// Insert loop-latch at the end of the while group
rewriter.setInsertionPointToEnd(whileBodyOpBlock);
calyx::GroupOp whileLatchGroup =
getState<ComponentLoweringState>().getWhileLoopLatchGroup(whileOp);
rewriter.create<calyx::EnableOp>(whileLatchGroup.getLoc(),
whileLatchGroup.getName());
if (res.failed())
return res;
} else if (auto *forSchedPtr = std::get_if<ForScheduleable>(&group);
forSchedPtr) {
auto forOp = forSchedPtr->forOp;
auto forCtrlOp = buildForCtrlOp(
forOp,
getState<ComponentLoweringState>().getForLoopInitGroups(forOp),
forSchedPtr->bound, rewriter);
rewriter.setInsertionPointToEnd(forCtrlOp.getBodyBlock());
auto forBodyOp =
rewriter.create<calyx::SeqOp>(forOp.getOperation()->getLoc());
auto *forBodyOpBlock = forBodyOp.getBodyBlock();
// Schedule the body of the for loop.
LogicalResult res = buildCFGControl(path, rewriter, forBodyOpBlock,
block, forOp.getBodyBlock());
// Insert loop-latch at the end of the while group.
rewriter.setInsertionPointToEnd(forBodyOpBlock);
calyx::GroupOp forLatchGroup =
getState<ComponentLoweringState>().getForLoopLatchGroup(forOp);
rewriter.create<calyx::EnableOp>(forLatchGroup.getLoc(),
forLatchGroup.getName());
if (res.failed())
return res;
} else if (auto *callSchedPtr = std::get_if<CallScheduleable>(&group)) {
auto instanceOp = callSchedPtr->instanceOp;
OpBuilder::InsertionGuard g(rewriter);
auto callBody = rewriter.create<calyx::SeqOp>(instanceOp.getLoc());
rewriter.setInsertionPointToStart(callBody.getBodyBlock());
std::string initGroupName = "init_" + instanceOp.getSymName().str();
rewriter.create<calyx::EnableOp>(instanceOp.getLoc(), initGroupName);
SmallVector<Value, 4> instancePorts;
auto inputPorts = callSchedPtr->callOp.getOperands();
llvm::copy(instanceOp.getResults().take_front(inputPorts.size()),
std::back_inserter(instancePorts));
rewriter.create<calyx::InvokeOp>(
instanceOp.getLoc(), instanceOp.getSymName(), instancePorts,
inputPorts, ArrayAttr::get(rewriter.getContext(), {}),
ArrayAttr::get(rewriter.getContext(), {}));
} else
llvm_unreachable("Unknown scheduleable");
}
return success();
}
/// Schedules a block by inserting a branch argument assignment block (if any)
/// before recursing into the scheduling of the block innards.
/// Blocks 'from' and 'to' refer to blocks in the source program.
/// parentCtrlBlock refers to the control block wherein control operations are
/// to be inserted.
LogicalResult schedulePath(PatternRewriter &rewriter,
const DenseSet<Block *> &path, Location loc,
Block *from, Block *to,
Block *parentCtrlBlock) const {
/// Schedule any registered block arguments to be executed before the body
/// of the branch.
rewriter.setInsertionPointToEnd(parentCtrlBlock);
auto preSeqOp = rewriter.create<calyx::SeqOp>(loc);
rewriter.setInsertionPointToEnd(preSeqOp.getBodyBlock());
for (auto barg :
getState<ComponentLoweringState>().getBlockArgGroups(from, to))
rewriter.create<calyx::EnableOp>(barg.getLoc(), barg.getSymName());
return buildCFGControl(path, rewriter, parentCtrlBlock, from, to);
}
LogicalResult buildCFGControl(DenseSet<Block *> path,
PatternRewriter &rewriter,
mlir::Block *parentCtrlBlock,
mlir::Block *preBlock,
mlir::Block *block) const {
if (path.count(block) != 0)
return preBlock->getTerminator()->emitError()
<< "CFG backedge detected. Loops must be raised to 'scf.while' or "
"'scf.for' operations.";
rewriter.setInsertionPointToEnd(parentCtrlBlock);
LogicalResult bbSchedResult =
scheduleBasicBlock(rewriter, path, parentCtrlBlock, block);
if (bbSchedResult.failed())
return bbSchedResult;
path.insert(block);
auto successors = block->getSuccessors();
auto nSuccessors = successors.size();
if (nSuccessors > 0) {
auto brOp = dyn_cast<BranchOpInterface>(block->getTerminator());
assert(brOp);
if (nSuccessors > 1) {
/// TODO(mortbopet): we could choose to support ie. std.switch, but it
/// would probably be easier to just require it to be lowered
/// beforehand.
assert(nSuccessors == 2 &&
"only conditional branches supported for now...");
/// Wrap each branch inside an if/else.
auto cond = brOp->getOperand(0);
auto condGroup = getState<ComponentLoweringState>()
.getEvaluatingGroup<calyx::CombGroupOp>(cond);
auto symbolAttr = FlatSymbolRefAttr::get(
StringAttr::get(getContext(), condGroup.getSymName()));
auto ifOp = rewriter.create<calyx::IfOp>(
brOp->getLoc(), cond, symbolAttr, /*initializeElseBody=*/true);
rewriter.setInsertionPointToStart(ifOp.getThenBody());
auto thenSeqOp = rewriter.create<calyx::SeqOp>(brOp.getLoc());
rewriter.setInsertionPointToStart(ifOp.getElseBody());
auto elseSeqOp = rewriter.create<calyx::SeqOp>(brOp.getLoc());
bool trueBrSchedSuccess =
schedulePath(rewriter, path, brOp.getLoc(), block, successors[0],
thenSeqOp.getBodyBlock())
.succeeded();
bool falseBrSchedSuccess = true;
if (trueBrSchedSuccess) {
falseBrSchedSuccess =
schedulePath(rewriter, path, brOp.getLoc(), block, successors[1],
elseSeqOp.getBodyBlock())
.succeeded();
}
return success(trueBrSchedSuccess && falseBrSchedSuccess);
} else {
/// Schedule sequentially within the current parent control block.
return schedulePath(rewriter, path, brOp.getLoc(), block,
successors.front(), parentCtrlBlock);
}
}
return success();
}
// Insert a Par of initGroups at Location loc. Used as helper for
// `buildWhileCtrlOp` and `buildForCtrlOp`.
void
insertParInitGroups(PatternRewriter &rewriter, Location loc,
const SmallVector<calyx::GroupOp> &initGroups) const {
PatternRewriter::InsertionGuard g(rewriter);
auto parOp = rewriter.create<calyx::ParOp>(loc);
rewriter.setInsertionPointToStart(parOp.getBodyBlock());
for (calyx::GroupOp group : initGroups)
rewriter.create<calyx::EnableOp>(group.getLoc(), group.getName());
}
calyx::WhileOp buildWhileCtrlOp(ScfWhileOp whileOp,
SmallVector<calyx::GroupOp> initGroups,
PatternRewriter &rewriter) const {
Location loc = whileOp.getLoc();
/// Insert while iter arg initialization group(s). Emit a
/// parallel group to assign one or more registers all at once.
insertParInitGroups(rewriter, loc, initGroups);
/// Insert the while op itself.
auto cond = whileOp.getConditionValue();
auto condGroup = getState<ComponentLoweringState>()
.getEvaluatingGroup<calyx::CombGroupOp>(cond);
auto symbolAttr = FlatSymbolRefAttr::get(
StringAttr::get(getContext(), condGroup.getSymName()));
return rewriter.create<calyx::WhileOp>(loc, cond, symbolAttr);
}
calyx::RepeatOp buildForCtrlOp(ScfForOp forOp,
SmallVector<calyx::GroupOp> const &initGroups,
uint64_t bound,
PatternRewriter &rewriter) const {
Location loc = forOp.getLoc();
// Insert for iter arg initialization group(s). Emit a
// parallel group to assign one or more registers all at once.
insertParInitGroups(rewriter, loc, initGroups);
// Insert the repeatOp that corresponds to the For loop.
return rewriter.create<calyx::RepeatOp>(loc, bound);
}
};
/// LateSSAReplacement contains various functions for replacing SSA values that
/// were not replaced during op construction.
class LateSSAReplacement : public calyx::FuncOpPartialLoweringPattern {
using FuncOpPartialLoweringPattern::FuncOpPartialLoweringPattern;
LogicalResult partiallyLowerFuncToComp(FuncOp funcOp,
PatternRewriter &) const override {
funcOp.walk([&](scf::WhileOp op) {
/// The yielded values returned from the while op will be present in the
/// iterargs registers post execution of the loop.
/// This is done now, as opposed to during BuildWhileGroups since if the
/// results of the whileOp were replaced before
/// BuildOpGroups/BuildControl, the whileOp would get dead-code
/// eliminated.
ScfWhileOp whileOp(op);
for (auto res :
getState<ComponentLoweringState>().getWhileLoopIterRegs(whileOp))
whileOp.getOperation()->getResults()[res.first].replaceAllUsesWith(
res.second.getOut());
});
funcOp.walk([&](memref::LoadOp loadOp) {
if (calyx::singleLoadFromMemory(loadOp)) {
/// In buildOpGroups we did not replace loadOp's results, to ensure a
/// link between evaluating groups (which fix the input addresses of a
/// memory op) and a readData result. Now, we may replace these SSA
/// values with their memoryOp readData output.
loadOp.getResult().replaceAllUsesWith(
getState<ComponentLoweringState>()
.getMemoryInterface(loadOp.getMemref())
.readData());
}
});
return success();
}
};
/// Erases FuncOp operations.
class CleanupFuncOps : public calyx::FuncOpPartialLoweringPattern {
using FuncOpPartialLoweringPattern::FuncOpPartialLoweringPattern;
LogicalResult matchAndRewrite(FuncOp funcOp,
PatternRewriter &rewriter) const override {
rewriter.eraseOp(funcOp);
return success();
}
LogicalResult
partiallyLowerFuncToComp(FuncOp funcOp,
PatternRewriter &rewriter) const override {
return success();
}
};
//===----------------------------------------------------------------------===//
// Pass driver
//===----------------------------------------------------------------------===//
class SCFToCalyxPass : public SCFToCalyxBase<SCFToCalyxPass> {
public:
SCFToCalyxPass()
: SCFToCalyxBase<SCFToCalyxPass>(), partialPatternRes(success()) {}
void runOnOperation() override;
LogicalResult setTopLevelFunction(mlir::ModuleOp moduleOp,
std::string &topLevelFunction) {
if (!topLevelFunctionOpt.empty()) {
if (SymbolTable::lookupSymbolIn(moduleOp, topLevelFunctionOpt) ==
nullptr) {
moduleOp.emitError() << "Top level function '" << topLevelFunctionOpt
<< "' not found in module.";
return failure();
}
topLevelFunction = topLevelFunctionOpt;
} else {
/// No top level function set; infer top level if the module only contains
/// a single function, else, throw error.
auto funcOps = moduleOp.getOps<FuncOp>();
if (std::distance(funcOps.begin(), funcOps.end()) == 1)
topLevelFunction = (*funcOps.begin()).getSymName().str();
else {
moduleOp.emitError()
<< "Module contains multiple functions, but no top level "
"function was set. Please see --top-level-function";
return failure();
}
}
return success();
}
struct LoweringPattern {
enum class Strategy { Once, Greedy };
RewritePatternSet pattern;
Strategy strategy;
};
//// Labels the entry point of a Calyx program.
/// Furthermore, this function performs validation on the input function,
/// to ensure that we've implemented the capabilities necessary to convert
/// it.
LogicalResult labelEntryPoint(StringRef topLevelFunction) {
// Program legalization - the partial conversion driver will not run
// unless some pattern is provided - provide a dummy pattern.
struct DummyPattern : public OpRewritePattern<mlir::ModuleOp> {
using OpRewritePattern::OpRewritePattern;
LogicalResult matchAndRewrite(mlir::ModuleOp,
PatternRewriter &) const override {
return failure();
}
};
ConversionTarget target(getContext());
target.addLegalDialect<calyx::CalyxDialect>();
target.addLegalDialect<scf::SCFDialect>();
target.addIllegalDialect<hw::HWDialect>();
target.addIllegalDialect<comb::CombDialect>();
// Only accept std operations which we've added lowerings for
target.addIllegalDialect<FuncDialect>();
target.addIllegalDialect<ArithDialect>();
target.addLegalOp<AddIOp, SelectOp, SubIOp, CmpIOp, ShLIOp, ShRUIOp,
ShRSIOp, AndIOp, XOrIOp, OrIOp, ExtUIOp, TruncIOp,
CondBranchOp, BranchOp, MulIOp, DivUIOp, DivSIOp, RemUIOp,
RemSIOp, ReturnOp, arith::ConstantOp, IndexCastOp, FuncOp,
ExtSIOp, CallOp>();
RewritePatternSet legalizePatterns(&getContext());
legalizePatterns.add<DummyPattern>(&getContext());
DenseSet<Operation *> legalizedOps;
if (applyPartialConversion(getOperation(), target,
std::move(legalizePatterns))
.failed())
return failure();
// Program conversion
return calyx::applyModuleOpConversion(getOperation(), topLevelFunction);
}
/// 'Once' patterns are expected to take an additional LogicalResult&
/// argument, to forward their result state (greedyPatternRewriteDriver
/// results are skipped for Once patterns).
template <typename TPattern, typename... PatternArgs>
void addOncePattern(SmallVectorImpl<LoweringPattern> &patterns,
PatternArgs &&...args) {
RewritePatternSet ps(&getContext());
ps.add<TPattern>(&getContext(), partialPatternRes, args...);
patterns.push_back(
LoweringPattern{std::move(ps), LoweringPattern::Strategy::Once});
}
template <typename TPattern, typename... PatternArgs>
void addGreedyPattern(SmallVectorImpl<LoweringPattern> &patterns,
PatternArgs &&...args) {
RewritePatternSet ps(&getContext());
ps.add<TPattern>(&getContext(), args...);
patterns.push_back(
LoweringPattern{std::move(ps), LoweringPattern::Strategy::Greedy});
}
LogicalResult runPartialPattern(RewritePatternSet &pattern, bool runOnce) {
assert(pattern.getNativePatterns().size() == 1 &&
"Should only apply 1 partial lowering pattern at once");
// During component creation, the function body is inlined into the
// component body for further processing. However, proper control flow
// will only be established later in the conversion process, so ensure
// that rewriter optimizations (especially DCE) are disabled.
GreedyRewriteConfig config;
config.enableRegionSimplification = false;
if (runOnce)
config.maxIterations = 1;
/// Can't return applyPatternsAndFoldGreedily. Root isn't
/// necessarily erased so it will always return failed(). Instead,
/// forward the 'succeeded' value from PartialLoweringPatternBase.
(void)applyPatternsAndFoldGreedily(getOperation(), std::move(pattern),
config);
return partialPatternRes;
}
private:
LogicalResult partialPatternRes;
std::shared_ptr<calyx::CalyxLoweringState> loweringState = nullptr;
};
void SCFToCalyxPass::runOnOperation() {
// Clear internal state. See https://github.com/llvm/circt/issues/3235
loweringState.reset();
partialPatternRes = LogicalResult::failure();
std::string topLevelFunction;
if (failed(setTopLevelFunction(getOperation(), topLevelFunction))) {
signalPassFailure();
return;
}
/// Start conversion
if (failed(labelEntryPoint(topLevelFunction))) {
signalPassFailure();
return;
}
loweringState = std::make_shared<calyx::CalyxLoweringState>(getOperation(),
topLevelFunction);
/// --------------------------------------------------------------------------
/// If you are a developer, it may be helpful to add a
/// 'getOperation()->dump()' call after the execution of each stage to
/// view the transformations that's going on.
/// --------------------------------------------------------------------------
/// A mapping is maintained between a function operation and its corresponding
/// Calyx component.
DenseMap<FuncOp, calyx::ComponentOp> funcMap;
SmallVector<LoweringPattern, 8> loweringPatterns;
calyx::PatternApplicationState patternState;
/// Creates a new Calyx component for each FuncOp in the inpurt module.
addOncePattern<FuncOpConversion>(loweringPatterns, patternState, funcMap,
*loweringState);
/// This pass inlines scf.ExecuteRegionOp's by adding control-flow.
addGreedyPattern<InlineExecuteRegionOpPattern>(loweringPatterns);
/// This pattern converts all index typed values to an i32 integer.
addOncePattern<calyx::ConvertIndexTypes>(loweringPatterns, patternState,
funcMap, *loweringState);
/// This pattern creates registers for all basic-block arguments.
addOncePattern<calyx::BuildBasicBlockRegs>(loweringPatterns, patternState,
funcMap, *loweringState);
addOncePattern<calyx::BuildCallInstance>(loweringPatterns, patternState,
funcMap, *loweringState);
/// This pattern creates registers for the function return values.
addOncePattern<calyx::BuildReturnRegs>(loweringPatterns, patternState,
funcMap, *loweringState);
/// This pattern creates registers for iteration arguments of scf.while
/// operations. Additionally, creates a group for assigning the initial
/// value of the iteration argument registers.
addOncePattern<BuildWhileGroups>(loweringPatterns, patternState, funcMap,
*loweringState);
/// This pattern creates registers for iteration arguments of scf.for
/// operations. Additionally, creates a group for assigning the initial
/// value of the iteration argument registers.
addOncePattern<BuildForGroups>(loweringPatterns, patternState, funcMap,
*loweringState);
/// This pattern converts operations within basic blocks to Calyx library
/// operators. Combinational operations are assigned inside a
/// calyx::CombGroupOp, and sequential inside calyx::GroupOps.
/// Sequential groups are registered with the Block* of which the operation
/// originated from. This is used during control schedule generation. By
/// having a distinct group for each operation, groups are analogous to SSA
/// values in the source program.
addOncePattern<BuildOpGroups>(loweringPatterns, patternState, funcMap,
*loweringState);
/// This pattern traverses the CFG of the program and generates a control
/// schedule based on the calyx::GroupOp's which were registered for each
/// basic block in the source function.
addOncePattern<BuildControl>(loweringPatterns, patternState, funcMap,
*loweringState);
/// This pass recursively inlines use-def chains of combinational logic (from
/// non-stateful groups) into groups referenced in the control schedule.
addOncePattern<calyx::InlineCombGroups>(loweringPatterns, patternState,
*loweringState);
/// This pattern performs various SSA replacements that must be done
/// after control generation.
addOncePattern<LateSSAReplacement>(loweringPatterns, patternState, funcMap,
*loweringState);
/// Eliminate any unused combinational groups. This is done before
/// calyx::RewriteMemoryAccesses to avoid inferring slice components for
/// groups that will be removed.
addGreedyPattern<calyx::EliminateUnusedCombGroups>(loweringPatterns);
/// This pattern rewrites accesses to memories which are too wide due to
/// index types being converted to a fixed-width integer type.
addOncePattern<calyx::RewriteMemoryAccesses>(loweringPatterns, patternState,
*loweringState);
/// This pattern removes the source FuncOp which has now been converted into
/// a Calyx component.
addOncePattern<CleanupFuncOps>(loweringPatterns, patternState, funcMap,
*loweringState);
/// Sequentially apply each lowering pattern.
for (auto &pat : loweringPatterns) {
LogicalResult partialPatternRes = runPartialPattern(
pat.pattern,
/*runOnce=*/pat.strategy == LoweringPattern::Strategy::Once);
if (succeeded(partialPatternRes))
continue;
signalPassFailure();
return;
}
//===----------------------------------------------------------------------===//
// Cleanup patterns
//===----------------------------------------------------------------------===//
RewritePatternSet cleanupPatterns(&getContext());
cleanupPatterns.add<calyx::MultipleGroupDonePattern,
calyx::NonTerminatingGroupDonePattern>(&getContext());
if (failed(applyPatternsAndFoldGreedily(getOperation(),
std::move(cleanupPatterns)))) {
signalPassFailure();
return;
}
if (ciderSourceLocationMetadata) {
// Debugging information for the Cider debugger.
// Reference: https://docs.calyxir.org/debug/cider.html
SmallVector<Attribute, 16> sourceLocations;
getOperation()->walk([&](calyx::ComponentOp component) {
return getCiderSourceLocationMetadata(component, sourceLocations);
});
MLIRContext *context = getOperation()->getContext();
getOperation()->setAttr("calyx.metadata",
ArrayAttr::get(context, sourceLocations));
}
}
} // namespace scftocalyx
//===----------------------------------------------------------------------===//
// Pass initialization
//===----------------------------------------------------------------------===//
std::unique_ptr<OperationPass<ModuleOp>> createSCFToCalyxPass() {
return std::make_unique<scftocalyx::SCFToCalyxPass>();
}
} // namespace circt