mirror of https://github.com/llvm/circt.git
718 lines
27 KiB
C++
718 lines
27 KiB
C++
//===- LowerArcToLLVM.cpp -------------------------------------------------===//
|
|
//
|
|
// 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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "circt/Conversion/ArcToLLVM.h"
|
|
#include "circt/Conversion/CombToArith.h"
|
|
#include "circt/Conversion/CombToLLVM.h"
|
|
#include "circt/Conversion/HWToLLVM.h"
|
|
#include "circt/Dialect/Arc/ArcOps.h"
|
|
#include "circt/Dialect/Arc/ModelInfo.h"
|
|
#include "circt/Dialect/Comb/CombOps.h"
|
|
#include "circt/Dialect/Seq/SeqOps.h"
|
|
#include "circt/Support/Namespace.h"
|
|
#include "mlir/Conversion/ArithToLLVM/ArithToLLVM.h"
|
|
#include "mlir/Conversion/ControlFlowToLLVM/ControlFlowToLLVM.h"
|
|
#include "mlir/Conversion/FuncToLLVM/ConvertFuncToLLVM.h"
|
|
#include "mlir/Conversion/IndexToLLVM/IndexToLLVM.h"
|
|
#include "mlir/Conversion/LLVMCommon/ConversionTarget.h"
|
|
#include "mlir/Conversion/LLVMCommon/TypeConverter.h"
|
|
#include "mlir/Conversion/SCFToControlFlow/SCFToControlFlow.h"
|
|
#include "mlir/Dialect/ControlFlow/IR/ControlFlow.h"
|
|
#include "mlir/Dialect/Func/IR/FuncOps.h"
|
|
#include "mlir/Dialect/Index/IR/IndexOps.h"
|
|
#include "mlir/Dialect/LLVMIR/FunctionCallUtils.h"
|
|
#include "mlir/Dialect/LLVMIR/LLVMAttrs.h"
|
|
#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
|
|
#include "mlir/Dialect/SCF/IR/SCF.h"
|
|
#include "mlir/IR/BuiltinDialect.h"
|
|
#include "mlir/Pass/Pass.h"
|
|
#include "mlir/Transforms/DialectConversion.h"
|
|
#include "llvm/Support/Debug.h"
|
|
|
|
#define DEBUG_TYPE "lower-arc-to-llvm"
|
|
|
|
namespace circt {
|
|
#define GEN_PASS_DEF_LOWERARCTOLLVM
|
|
#include "circt/Conversion/Passes.h.inc"
|
|
} // namespace circt
|
|
|
|
using namespace mlir;
|
|
using namespace circt;
|
|
using namespace arc;
|
|
using namespace hw;
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Lowering Patterns
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
static llvm::Twine evalSymbolFromModelName(StringRef modelName) {
|
|
return modelName + "_eval";
|
|
}
|
|
|
|
namespace {
|
|
|
|
struct ModelOpLowering : public OpConversionPattern<arc::ModelOp> {
|
|
using OpConversionPattern::OpConversionPattern;
|
|
LogicalResult
|
|
matchAndRewrite(arc::ModelOp op, OpAdaptor adaptor,
|
|
ConversionPatternRewriter &rewriter) const final {
|
|
{
|
|
IRRewriter::InsertionGuard guard(rewriter);
|
|
rewriter.setInsertionPointToEnd(&op.getBodyBlock());
|
|
func::ReturnOp::create(rewriter, op.getLoc());
|
|
}
|
|
auto funcName =
|
|
rewriter.getStringAttr(evalSymbolFromModelName(op.getName()));
|
|
auto funcType =
|
|
rewriter.getFunctionType(op.getBody().getArgumentTypes(), {});
|
|
auto func =
|
|
mlir::func::FuncOp::create(rewriter, op.getLoc(), funcName, funcType);
|
|
rewriter.inlineRegionBefore(op.getRegion(), func.getBody(), func.end());
|
|
rewriter.eraseOp(op);
|
|
return success();
|
|
}
|
|
};
|
|
|
|
struct AllocStorageOpLowering
|
|
: public OpConversionPattern<arc::AllocStorageOp> {
|
|
using OpConversionPattern::OpConversionPattern;
|
|
LogicalResult
|
|
matchAndRewrite(arc::AllocStorageOp op, OpAdaptor adaptor,
|
|
ConversionPatternRewriter &rewriter) const final {
|
|
auto type = typeConverter->convertType(op.getType());
|
|
if (!op.getOffset().has_value())
|
|
return failure();
|
|
rewriter.replaceOpWithNewOp<LLVM::GEPOp>(op, type, rewriter.getI8Type(),
|
|
adaptor.getInput(),
|
|
LLVM::GEPArg(*op.getOffset()));
|
|
return success();
|
|
}
|
|
};
|
|
|
|
template <class ConcreteOp>
|
|
struct AllocStateLikeOpLowering : public OpConversionPattern<ConcreteOp> {
|
|
using OpConversionPattern<ConcreteOp>::OpConversionPattern;
|
|
using OpConversionPattern<ConcreteOp>::typeConverter;
|
|
using OpAdaptor = typename ConcreteOp::Adaptor;
|
|
|
|
LogicalResult
|
|
matchAndRewrite(ConcreteOp op, OpAdaptor adaptor,
|
|
ConversionPatternRewriter &rewriter) const final {
|
|
// Get a pointer to the correct offset in the storage.
|
|
auto offsetAttr = op->template getAttrOfType<IntegerAttr>("offset");
|
|
if (!offsetAttr)
|
|
return failure();
|
|
Value ptr = LLVM::GEPOp::create(
|
|
rewriter, op->getLoc(), adaptor.getStorage().getType(),
|
|
rewriter.getI8Type(), adaptor.getStorage(),
|
|
LLVM::GEPArg(offsetAttr.getValue().getZExtValue()));
|
|
rewriter.replaceOp(op, ptr);
|
|
return success();
|
|
}
|
|
};
|
|
|
|
struct StateReadOpLowering : public OpConversionPattern<arc::StateReadOp> {
|
|
using OpConversionPattern::OpConversionPattern;
|
|
LogicalResult
|
|
matchAndRewrite(arc::StateReadOp op, OpAdaptor adaptor,
|
|
ConversionPatternRewriter &rewriter) const final {
|
|
auto type = typeConverter->convertType(op.getType());
|
|
rewriter.replaceOpWithNewOp<LLVM::LoadOp>(op, type, adaptor.getState());
|
|
return success();
|
|
}
|
|
};
|
|
|
|
struct StateWriteOpLowering : public OpConversionPattern<arc::StateWriteOp> {
|
|
using OpConversionPattern::OpConversionPattern;
|
|
LogicalResult
|
|
matchAndRewrite(arc::StateWriteOp op, OpAdaptor adaptor,
|
|
ConversionPatternRewriter &rewriter) const final {
|
|
if (adaptor.getCondition()) {
|
|
rewriter.replaceOpWithNewOp<scf::IfOp>(
|
|
op, adaptor.getCondition(), [&](auto &builder, auto loc) {
|
|
LLVM::StoreOp::create(builder, loc, adaptor.getValue(),
|
|
adaptor.getState());
|
|
scf::YieldOp::create(builder, loc);
|
|
});
|
|
} else {
|
|
rewriter.replaceOpWithNewOp<LLVM::StoreOp>(op, adaptor.getValue(),
|
|
adaptor.getState());
|
|
}
|
|
return success();
|
|
}
|
|
};
|
|
|
|
struct AllocMemoryOpLowering : public OpConversionPattern<arc::AllocMemoryOp> {
|
|
using OpConversionPattern::OpConversionPattern;
|
|
LogicalResult
|
|
matchAndRewrite(arc::AllocMemoryOp op, OpAdaptor adaptor,
|
|
ConversionPatternRewriter &rewriter) const final {
|
|
auto offsetAttr = op->getAttrOfType<IntegerAttr>("offset");
|
|
if (!offsetAttr)
|
|
return failure();
|
|
Value ptr = LLVM::GEPOp::create(
|
|
rewriter, op.getLoc(), adaptor.getStorage().getType(),
|
|
rewriter.getI8Type(), adaptor.getStorage(),
|
|
LLVM::GEPArg(offsetAttr.getValue().getZExtValue()));
|
|
|
|
rewriter.replaceOp(op, ptr);
|
|
return success();
|
|
}
|
|
};
|
|
|
|
struct StorageGetOpLowering : public OpConversionPattern<arc::StorageGetOp> {
|
|
using OpConversionPattern::OpConversionPattern;
|
|
LogicalResult
|
|
matchAndRewrite(arc::StorageGetOp op, OpAdaptor adaptor,
|
|
ConversionPatternRewriter &rewriter) const final {
|
|
Value offset = LLVM::ConstantOp::create(
|
|
rewriter, op.getLoc(), rewriter.getI32Type(), op.getOffsetAttr());
|
|
Value ptr = LLVM::GEPOp::create(
|
|
rewriter, op.getLoc(), adaptor.getStorage().getType(),
|
|
rewriter.getI8Type(), adaptor.getStorage(), offset);
|
|
rewriter.replaceOp(op, ptr);
|
|
return success();
|
|
}
|
|
};
|
|
|
|
struct MemoryAccess {
|
|
Value ptr;
|
|
Value withinBounds;
|
|
};
|
|
|
|
static MemoryAccess prepareMemoryAccess(Location loc, Value memory,
|
|
Value address, MemoryType type,
|
|
ConversionPatternRewriter &rewriter) {
|
|
auto zextAddrType = rewriter.getIntegerType(
|
|
cast<IntegerType>(address.getType()).getWidth() + 1);
|
|
Value addr = LLVM::ZExtOp::create(rewriter, loc, zextAddrType, address);
|
|
Value addrLimit =
|
|
LLVM::ConstantOp::create(rewriter, loc, zextAddrType,
|
|
rewriter.getI32IntegerAttr(type.getNumWords()));
|
|
Value withinBounds = LLVM::ICmpOp::create(
|
|
rewriter, loc, LLVM::ICmpPredicate::ult, addr, addrLimit);
|
|
Value ptr = LLVM::GEPOp::create(
|
|
rewriter, loc, LLVM::LLVMPointerType::get(memory.getContext()),
|
|
rewriter.getIntegerType(type.getStride() * 8), memory, ValueRange{addr});
|
|
return {ptr, withinBounds};
|
|
}
|
|
|
|
struct MemoryReadOpLowering : public OpConversionPattern<arc::MemoryReadOp> {
|
|
using OpConversionPattern::OpConversionPattern;
|
|
LogicalResult
|
|
matchAndRewrite(arc::MemoryReadOp op, OpAdaptor adaptor,
|
|
ConversionPatternRewriter &rewriter) const final {
|
|
auto type = typeConverter->convertType(op.getType());
|
|
auto memoryType = cast<MemoryType>(op.getMemory().getType());
|
|
auto access =
|
|
prepareMemoryAccess(op.getLoc(), adaptor.getMemory(),
|
|
adaptor.getAddress(), memoryType, rewriter);
|
|
|
|
// Only attempt to read the memory if the address is within bounds,
|
|
// otherwise produce a zero value.
|
|
rewriter.replaceOpWithNewOp<scf::IfOp>(
|
|
op, access.withinBounds,
|
|
[&](auto &builder, auto loc) {
|
|
Value loadOp = LLVM::LoadOp::create(
|
|
builder, loc, memoryType.getWordType(), access.ptr);
|
|
scf::YieldOp::create(builder, loc, loadOp);
|
|
},
|
|
[&](auto &builder, auto loc) {
|
|
Value zeroValue = LLVM::ConstantOp::create(
|
|
builder, loc, type, builder.getI64IntegerAttr(0));
|
|
scf::YieldOp::create(builder, loc, zeroValue);
|
|
});
|
|
return success();
|
|
}
|
|
};
|
|
|
|
struct MemoryWriteOpLowering : public OpConversionPattern<arc::MemoryWriteOp> {
|
|
using OpConversionPattern::OpConversionPattern;
|
|
LogicalResult
|
|
matchAndRewrite(arc::MemoryWriteOp op, OpAdaptor adaptor,
|
|
ConversionPatternRewriter &rewriter) const final {
|
|
auto access = prepareMemoryAccess(
|
|
op.getLoc(), adaptor.getMemory(), adaptor.getAddress(),
|
|
cast<MemoryType>(op.getMemory().getType()), rewriter);
|
|
auto enable = access.withinBounds;
|
|
if (adaptor.getEnable())
|
|
enable = LLVM::AndOp::create(rewriter, op.getLoc(), adaptor.getEnable(),
|
|
enable);
|
|
|
|
// Only attempt to write the memory if the address is within bounds.
|
|
rewriter.replaceOpWithNewOp<scf::IfOp>(
|
|
op, enable, [&](auto &builder, auto loc) {
|
|
LLVM::StoreOp::create(builder, loc, adaptor.getData(), access.ptr);
|
|
scf::YieldOp::create(builder, loc);
|
|
});
|
|
return success();
|
|
}
|
|
};
|
|
|
|
/// A dummy lowering for clock gates to an AND gate.
|
|
struct ClockGateOpLowering : public OpConversionPattern<seq::ClockGateOp> {
|
|
using OpConversionPattern::OpConversionPattern;
|
|
LogicalResult
|
|
matchAndRewrite(seq::ClockGateOp op, OpAdaptor adaptor,
|
|
ConversionPatternRewriter &rewriter) const final {
|
|
rewriter.replaceOpWithNewOp<LLVM::AndOp>(op, adaptor.getInput(),
|
|
adaptor.getEnable());
|
|
return success();
|
|
}
|
|
};
|
|
|
|
/// Lower 'seq.clock_inv x' to 'llvm.xor x true'
|
|
struct ClockInvOpLowering : public OpConversionPattern<seq::ClockInverterOp> {
|
|
using OpConversionPattern::OpConversionPattern;
|
|
LogicalResult
|
|
matchAndRewrite(seq::ClockInverterOp op, OpAdaptor adaptor,
|
|
ConversionPatternRewriter &rewriter) const final {
|
|
auto constTrue = LLVM::ConstantOp::create(rewriter, op->getLoc(),
|
|
rewriter.getI1Type(), 1);
|
|
rewriter.replaceOpWithNewOp<LLVM::XOrOp>(op, adaptor.getInput(), constTrue);
|
|
return success();
|
|
}
|
|
};
|
|
|
|
struct ZeroCountOpLowering : public OpConversionPattern<arc::ZeroCountOp> {
|
|
using OpConversionPattern::OpConversionPattern;
|
|
LogicalResult
|
|
matchAndRewrite(arc::ZeroCountOp op, OpAdaptor adaptor,
|
|
ConversionPatternRewriter &rewriter) const override {
|
|
// Use poison when input is zero.
|
|
IntegerAttr isZeroPoison = rewriter.getBoolAttr(true);
|
|
|
|
if (op.getPredicate() == arc::ZeroCountPredicate::leading) {
|
|
rewriter.replaceOpWithNewOp<LLVM::CountLeadingZerosOp>(
|
|
op, adaptor.getInput().getType(), adaptor.getInput(), isZeroPoison);
|
|
return success();
|
|
}
|
|
|
|
rewriter.replaceOpWithNewOp<LLVM::CountTrailingZerosOp>(
|
|
op, adaptor.getInput().getType(), adaptor.getInput(), isZeroPoison);
|
|
return success();
|
|
}
|
|
};
|
|
|
|
struct SeqConstClockLowering : public OpConversionPattern<seq::ConstClockOp> {
|
|
using OpConversionPattern::OpConversionPattern;
|
|
LogicalResult
|
|
matchAndRewrite(seq::ConstClockOp op, OpAdaptor adaptor,
|
|
ConversionPatternRewriter &rewriter) const override {
|
|
rewriter.replaceOpWithNewOp<LLVM::ConstantOp>(
|
|
op, rewriter.getI1Type(), static_cast<int64_t>(op.getValue()));
|
|
return success();
|
|
}
|
|
};
|
|
|
|
template <typename OpTy>
|
|
struct ReplaceOpWithInputPattern : public OpConversionPattern<OpTy> {
|
|
using OpConversionPattern<OpTy>::OpConversionPattern;
|
|
using OpAdaptor = typename OpTy::Adaptor;
|
|
LogicalResult
|
|
matchAndRewrite(OpTy op, OpAdaptor adaptor,
|
|
ConversionPatternRewriter &rewriter) const override {
|
|
rewriter.replaceOp(op, adaptor.getInput());
|
|
return success();
|
|
}
|
|
};
|
|
|
|
} // namespace
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Simulation Orchestration Lowering Patterns
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
namespace {
|
|
|
|
struct ModelInfoMap {
|
|
size_t numStateBytes;
|
|
llvm::DenseMap<StringRef, StateInfo> states;
|
|
mlir::FlatSymbolRefAttr initialFnSymbol;
|
|
mlir::FlatSymbolRefAttr finalFnSymbol;
|
|
};
|
|
|
|
template <typename OpTy>
|
|
struct ModelAwarePattern : public OpConversionPattern<OpTy> {
|
|
ModelAwarePattern(const TypeConverter &typeConverter, MLIRContext *context,
|
|
llvm::DenseMap<StringRef, ModelInfoMap> &modelInfo)
|
|
: OpConversionPattern<OpTy>(typeConverter, context),
|
|
modelInfo(modelInfo) {}
|
|
|
|
protected:
|
|
Value createPtrToPortState(ConversionPatternRewriter &rewriter, Location loc,
|
|
Value state, const StateInfo &port) const {
|
|
MLIRContext *ctx = rewriter.getContext();
|
|
return LLVM::GEPOp::create(rewriter, loc, LLVM::LLVMPointerType::get(ctx),
|
|
IntegerType::get(ctx, 8), state,
|
|
LLVM::GEPArg(port.offset));
|
|
}
|
|
|
|
llvm::DenseMap<StringRef, ModelInfoMap> &modelInfo;
|
|
};
|
|
|
|
/// Lowers SimInstantiateOp to a malloc and memset call. This pattern will
|
|
/// mutate the global module.
|
|
struct SimInstantiateOpLowering
|
|
: public ModelAwarePattern<arc::SimInstantiateOp> {
|
|
using ModelAwarePattern::ModelAwarePattern;
|
|
|
|
LogicalResult
|
|
matchAndRewrite(arc::SimInstantiateOp op, OpAdaptor adaptor,
|
|
ConversionPatternRewriter &rewriter) const final {
|
|
auto modelIt = modelInfo.find(
|
|
cast<SimModelInstanceType>(op.getBody().getArgument(0).getType())
|
|
.getModel()
|
|
.getValue());
|
|
ModelInfoMap &model = modelIt->second;
|
|
|
|
ModuleOp moduleOp = op->getParentOfType<ModuleOp>();
|
|
if (!moduleOp)
|
|
return failure();
|
|
|
|
ConversionPatternRewriter::InsertionGuard guard(rewriter);
|
|
|
|
// FIXME: like the rest of MLIR, this assumes sizeof(intptr_t) ==
|
|
// sizeof(size_t) on the target architecture.
|
|
Type convertedIndex = typeConverter->convertType(rewriter.getIndexType());
|
|
|
|
FailureOr<LLVM::LLVMFuncOp> mallocFunc =
|
|
LLVM::lookupOrCreateMallocFn(rewriter, moduleOp, convertedIndex);
|
|
if (failed(mallocFunc))
|
|
return mallocFunc;
|
|
|
|
FailureOr<LLVM::LLVMFuncOp> freeFunc =
|
|
LLVM::lookupOrCreateFreeFn(rewriter, moduleOp);
|
|
if (failed(freeFunc))
|
|
return freeFunc;
|
|
|
|
Location loc = op.getLoc();
|
|
Value numStateBytes = LLVM::ConstantOp::create(
|
|
rewriter, loc, convertedIndex, model.numStateBytes);
|
|
Value allocated = LLVM::CallOp::create(rewriter, loc, mallocFunc.value(),
|
|
ValueRange{numStateBytes})
|
|
.getResult();
|
|
Value zero =
|
|
LLVM::ConstantOp::create(rewriter, loc, rewriter.getI8Type(), 0);
|
|
LLVM::MemsetOp::create(rewriter, loc, allocated, zero, numStateBytes,
|
|
false);
|
|
|
|
// Call the model's 'initial' function if present.
|
|
if (model.initialFnSymbol) {
|
|
auto initialFnType = LLVM::LLVMFunctionType::get(
|
|
LLVM::LLVMVoidType::get(op.getContext()),
|
|
{LLVM::LLVMPointerType::get(op.getContext())});
|
|
LLVM::CallOp::create(rewriter, loc, initialFnType, model.initialFnSymbol,
|
|
ValueRange{allocated});
|
|
}
|
|
|
|
// Execute the body.
|
|
rewriter.inlineBlockBefore(&adaptor.getBody().getBlocks().front(), op,
|
|
{allocated});
|
|
|
|
// Call the model's 'final' function if present.
|
|
if (model.finalFnSymbol) {
|
|
auto finalFnType = LLVM::LLVMFunctionType::get(
|
|
LLVM::LLVMVoidType::get(op.getContext()),
|
|
{LLVM::LLVMPointerType::get(op.getContext())});
|
|
LLVM::CallOp::create(rewriter, loc, finalFnType, model.finalFnSymbol,
|
|
ValueRange{allocated});
|
|
}
|
|
|
|
LLVM::CallOp::create(rewriter, loc, freeFunc.value(),
|
|
ValueRange{allocated});
|
|
rewriter.eraseOp(op);
|
|
|
|
return success();
|
|
}
|
|
};
|
|
|
|
struct SimSetInputOpLowering : public ModelAwarePattern<arc::SimSetInputOp> {
|
|
using ModelAwarePattern::ModelAwarePattern;
|
|
|
|
LogicalResult
|
|
matchAndRewrite(arc::SimSetInputOp op, OpAdaptor adaptor,
|
|
ConversionPatternRewriter &rewriter) const final {
|
|
auto modelIt =
|
|
modelInfo.find(cast<SimModelInstanceType>(op.getInstance().getType())
|
|
.getModel()
|
|
.getValue());
|
|
ModelInfoMap &model = modelIt->second;
|
|
|
|
auto portIt = model.states.find(op.getInput());
|
|
if (portIt == model.states.end()) {
|
|
// If the port is not found in the state, it means the model does not
|
|
// actually use it. Thus this operation is a no-op.
|
|
rewriter.eraseOp(op);
|
|
return success();
|
|
}
|
|
|
|
StateInfo &port = portIt->second;
|
|
Value statePtr = createPtrToPortState(rewriter, op.getLoc(),
|
|
adaptor.getInstance(), port);
|
|
rewriter.replaceOpWithNewOp<LLVM::StoreOp>(op, adaptor.getValue(),
|
|
statePtr);
|
|
|
|
return success();
|
|
}
|
|
};
|
|
|
|
struct SimGetPortOpLowering : public ModelAwarePattern<arc::SimGetPortOp> {
|
|
using ModelAwarePattern::ModelAwarePattern;
|
|
|
|
LogicalResult
|
|
matchAndRewrite(arc::SimGetPortOp op, OpAdaptor adaptor,
|
|
ConversionPatternRewriter &rewriter) const final {
|
|
auto modelIt =
|
|
modelInfo.find(cast<SimModelInstanceType>(op.getInstance().getType())
|
|
.getModel()
|
|
.getValue());
|
|
ModelInfoMap &model = modelIt->second;
|
|
|
|
auto type = typeConverter->convertType(op.getValue().getType());
|
|
if (!type)
|
|
return failure();
|
|
auto portIt = model.states.find(op.getPort());
|
|
if (portIt == model.states.end()) {
|
|
// If the port is not found in the state, it means the model does not
|
|
// actually set it. Thus this operation returns 0.
|
|
rewriter.replaceOpWithNewOp<LLVM::ConstantOp>(op, type, 0);
|
|
return success();
|
|
}
|
|
|
|
StateInfo &port = portIt->second;
|
|
Value statePtr = createPtrToPortState(rewriter, op.getLoc(),
|
|
adaptor.getInstance(), port);
|
|
rewriter.replaceOpWithNewOp<LLVM::LoadOp>(op, type, statePtr);
|
|
|
|
return success();
|
|
}
|
|
};
|
|
|
|
struct SimStepOpLowering : public ModelAwarePattern<arc::SimStepOp> {
|
|
using ModelAwarePattern::ModelAwarePattern;
|
|
|
|
LogicalResult
|
|
matchAndRewrite(arc::SimStepOp op, OpAdaptor adaptor,
|
|
ConversionPatternRewriter &rewriter) const final {
|
|
StringRef modelName = cast<SimModelInstanceType>(op.getInstance().getType())
|
|
.getModel()
|
|
.getValue();
|
|
|
|
StringAttr evalFunc =
|
|
rewriter.getStringAttr(evalSymbolFromModelName(modelName));
|
|
rewriter.replaceOpWithNewOp<LLVM::CallOp>(op, mlir::TypeRange(), evalFunc,
|
|
adaptor.getInstance());
|
|
|
|
return success();
|
|
}
|
|
};
|
|
|
|
/// Lowers SimEmitValueOp to a printf call. The integer will be printed in its
|
|
/// entirety if it is of size up to size_t, and explicitly truncated otherwise.
|
|
/// This pattern will mutate the global module.
|
|
struct SimEmitValueOpLowering
|
|
: public OpConversionPattern<arc::SimEmitValueOp> {
|
|
using OpConversionPattern::OpConversionPattern;
|
|
|
|
LogicalResult
|
|
matchAndRewrite(arc::SimEmitValueOp op, OpAdaptor adaptor,
|
|
ConversionPatternRewriter &rewriter) const final {
|
|
auto valueType = dyn_cast<IntegerType>(adaptor.getValue().getType());
|
|
if (!valueType)
|
|
return failure();
|
|
|
|
Location loc = op.getLoc();
|
|
|
|
ModuleOp moduleOp = op->getParentOfType<ModuleOp>();
|
|
if (!moduleOp)
|
|
return failure();
|
|
|
|
// Cast the value to a size_t.
|
|
// FIXME: like the rest of MLIR, this assumes sizeof(intptr_t) ==
|
|
// sizeof(size_t) on the target architecture.
|
|
Value toPrint = adaptor.getValue();
|
|
DataLayout layout = DataLayout::closest(op);
|
|
llvm::TypeSize sizeOfSizeT =
|
|
layout.getTypeSizeInBits(rewriter.getIndexType());
|
|
assert(!sizeOfSizeT.isScalable() &&
|
|
sizeOfSizeT.getFixedValue() <= std::numeric_limits<unsigned>::max());
|
|
bool truncated = false;
|
|
if (valueType.getWidth() > sizeOfSizeT) {
|
|
toPrint = LLVM::TruncOp::create(
|
|
rewriter, loc,
|
|
IntegerType::get(getContext(), sizeOfSizeT.getFixedValue()), toPrint);
|
|
truncated = true;
|
|
} else if (valueType.getWidth() < sizeOfSizeT)
|
|
toPrint = LLVM::ZExtOp::create(
|
|
rewriter, loc,
|
|
IntegerType::get(getContext(), sizeOfSizeT.getFixedValue()), toPrint);
|
|
|
|
// Lookup of create printf function symbol.
|
|
auto printfFunc = LLVM::lookupOrCreateFn(
|
|
rewriter, moduleOp, "printf", LLVM::LLVMPointerType::get(getContext()),
|
|
LLVM::LLVMVoidType::get(getContext()), true);
|
|
if (failed(printfFunc))
|
|
return printfFunc;
|
|
|
|
// Insert the format string if not already available.
|
|
SmallString<16> formatStrName{"_arc_sim_emit_"};
|
|
formatStrName.append(truncated ? "trunc_" : "full_");
|
|
formatStrName.append(adaptor.getValueName());
|
|
LLVM::GlobalOp formatStrGlobal;
|
|
if (!(formatStrGlobal =
|
|
moduleOp.lookupSymbol<LLVM::GlobalOp>(formatStrName))) {
|
|
ConversionPatternRewriter::InsertionGuard insertGuard(rewriter);
|
|
|
|
SmallString<16> formatStr = adaptor.getValueName();
|
|
formatStr.append(" = ");
|
|
if (truncated)
|
|
formatStr.append("(truncated) ");
|
|
formatStr.append("%zx\n");
|
|
SmallVector<char> formatStrVec{formatStr.begin(), formatStr.end()};
|
|
formatStrVec.push_back(0);
|
|
|
|
rewriter.setInsertionPointToStart(moduleOp.getBody());
|
|
auto globalType =
|
|
LLVM::LLVMArrayType::get(rewriter.getI8Type(), formatStrVec.size());
|
|
formatStrGlobal = LLVM::GlobalOp::create(
|
|
rewriter, loc, globalType, /*isConstant=*/true,
|
|
LLVM::Linkage::Internal,
|
|
/*name=*/formatStrName, rewriter.getStringAttr(formatStrVec),
|
|
/*alignment=*/0);
|
|
}
|
|
|
|
Value formatStrGlobalPtr =
|
|
LLVM::AddressOfOp::create(rewriter, loc, formatStrGlobal);
|
|
rewriter.replaceOpWithNewOp<LLVM::CallOp>(
|
|
op, printfFunc.value(), ValueRange{formatStrGlobalPtr, toPrint});
|
|
|
|
return success();
|
|
}
|
|
};
|
|
|
|
} // namespace
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Pass Implementation
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
namespace {
|
|
struct LowerArcToLLVMPass
|
|
: public circt::impl::LowerArcToLLVMBase<LowerArcToLLVMPass> {
|
|
void runOnOperation() override;
|
|
};
|
|
} // namespace
|
|
|
|
void LowerArcToLLVMPass::runOnOperation() {
|
|
// Collect the symbols in the root op such that the HW-to-LLVM lowering can
|
|
// create LLVM globals with non-colliding names.
|
|
Namespace globals;
|
|
SymbolCache cache;
|
|
cache.addDefinitions(getOperation());
|
|
globals.add(cache);
|
|
|
|
// Setup the conversion target. Explicitly mark `scf.yield` legal since it
|
|
// does not have a conversion itself, which would cause it to fail
|
|
// legalization and for the conversion to abort. (It relies on its parent op's
|
|
// conversion to remove it.)
|
|
LLVMConversionTarget target(getContext());
|
|
target.addLegalOp<mlir::ModuleOp>();
|
|
target.addLegalOp<scf::YieldOp>(); // quirk of SCF dialect conversion
|
|
|
|
// Setup the arc dialect type conversion.
|
|
LLVMTypeConverter converter(&getContext());
|
|
converter.addConversion([&](seq::ClockType type) {
|
|
return IntegerType::get(type.getContext(), 1);
|
|
});
|
|
converter.addConversion([&](StorageType type) {
|
|
return LLVM::LLVMPointerType::get(type.getContext());
|
|
});
|
|
converter.addConversion([&](MemoryType type) {
|
|
return LLVM::LLVMPointerType::get(type.getContext());
|
|
});
|
|
converter.addConversion([&](StateType type) {
|
|
return LLVM::LLVMPointerType::get(type.getContext());
|
|
});
|
|
converter.addConversion([&](SimModelInstanceType type) {
|
|
return LLVM::LLVMPointerType::get(type.getContext());
|
|
});
|
|
|
|
// Setup the conversion patterns.
|
|
RewritePatternSet patterns(&getContext());
|
|
|
|
// MLIR patterns.
|
|
populateSCFToControlFlowConversionPatterns(patterns);
|
|
populateFuncToLLVMConversionPatterns(converter, patterns);
|
|
cf::populateControlFlowToLLVMConversionPatterns(converter, patterns);
|
|
arith::populateArithToLLVMConversionPatterns(converter, patterns);
|
|
index::populateIndexToLLVMConversionPatterns(converter, patterns);
|
|
populateAnyFunctionOpInterfaceTypeConversionPattern(patterns, converter);
|
|
|
|
// CIRCT patterns.
|
|
DenseMap<std::pair<Type, ArrayAttr>, LLVM::GlobalOp> constAggregateGlobalsMap;
|
|
populateHWToLLVMConversionPatterns(converter, patterns, globals,
|
|
constAggregateGlobalsMap);
|
|
populateHWToLLVMTypeConversions(converter);
|
|
populateCombToArithConversionPatterns(converter, patterns);
|
|
populateCombToLLVMConversionPatterns(converter, patterns);
|
|
|
|
// Arc patterns.
|
|
// clang-format off
|
|
patterns.add<
|
|
AllocMemoryOpLowering,
|
|
AllocStateLikeOpLowering<arc::AllocStateOp>,
|
|
AllocStateLikeOpLowering<arc::RootInputOp>,
|
|
AllocStateLikeOpLowering<arc::RootOutputOp>,
|
|
AllocStorageOpLowering,
|
|
ClockGateOpLowering,
|
|
ClockInvOpLowering,
|
|
MemoryReadOpLowering,
|
|
MemoryWriteOpLowering,
|
|
ModelOpLowering,
|
|
ReplaceOpWithInputPattern<seq::ToClockOp>,
|
|
ReplaceOpWithInputPattern<seq::FromClockOp>,
|
|
SeqConstClockLowering,
|
|
SimEmitValueOpLowering,
|
|
StateReadOpLowering,
|
|
StateWriteOpLowering,
|
|
StorageGetOpLowering,
|
|
ZeroCountOpLowering
|
|
>(converter, &getContext());
|
|
// clang-format on
|
|
|
|
SmallVector<ModelInfo> models;
|
|
if (failed(collectModels(getOperation(), models))) {
|
|
signalPassFailure();
|
|
return;
|
|
}
|
|
|
|
llvm::DenseMap<StringRef, ModelInfoMap> modelMap(models.size());
|
|
for (ModelInfo &modelInfo : models) {
|
|
llvm::DenseMap<StringRef, StateInfo> states(modelInfo.states.size());
|
|
for (StateInfo &stateInfo : modelInfo.states)
|
|
states.insert({stateInfo.name, stateInfo});
|
|
modelMap.insert(
|
|
{modelInfo.name,
|
|
ModelInfoMap{modelInfo.numStateBytes, std::move(states),
|
|
modelInfo.initialFnSym, modelInfo.finalFnSym}});
|
|
}
|
|
|
|
patterns.add<SimInstantiateOpLowering, SimSetInputOpLowering,
|
|
SimGetPortOpLowering, SimStepOpLowering>(
|
|
converter, &getContext(), modelMap);
|
|
|
|
// Apply the conversion.
|
|
if (failed(applyFullConversion(getOperation(), target, std::move(patterns))))
|
|
signalPassFailure();
|
|
}
|
|
|
|
std::unique_ptr<OperationPass<ModuleOp>> circt::createLowerArcToLLVMPass() {
|
|
return std::make_unique<LowerArcToLLVMPass>();
|
|
}
|