mirror of https://github.com/llvm/circt.git
1919 lines
80 KiB
C++
1919 lines
80 KiB
C++
//===- LLHDToLLVM.cpp - LLHD to LLVM Conversion Pass ----------------------===//
|
|
//
|
|
// 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 LLHD to LLVM Conversion Pass Implementation.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "circt/Conversion/LLHDToLLVM.h"
|
|
#include "circt/Conversion/CombToArith.h"
|
|
#include "circt/Conversion/CombToLLVM.h"
|
|
#include "circt/Conversion/HWToLLVM.h"
|
|
#include "circt/Dialect/LLHD/IR/LLHDDialect.h"
|
|
#include "circt/Dialect/LLHD/IR/LLHDOps.h"
|
|
#include "circt/Support/LLVM.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/FuncToLLVM/ConvertFuncToLLVMPass.h"
|
|
#include "mlir/Conversion/LLVMCommon/ConversionTarget.h"
|
|
#include "mlir/Conversion/LLVMCommon/Pattern.h"
|
|
#include "mlir/Conversion/ReconcileUnrealizedCasts/ReconcileUnrealizedCasts.h"
|
|
#include "mlir/Dialect/Arith/IR/Arith.h"
|
|
#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
|
|
#include "mlir/IR/IRMapping.h"
|
|
#include "mlir/Pass/Pass.h"
|
|
#include "mlir/Transforms/DialectConversion.h"
|
|
|
|
namespace circt {
|
|
#define GEN_PASS_DEF_CONVERTLLHDTOLLVM
|
|
#include "circt/Conversion/Passes.h.inc"
|
|
} // namespace circt
|
|
|
|
using namespace mlir;
|
|
using namespace circt;
|
|
using namespace circt::llhd;
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Helpers
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
/// Get an existing global string.
|
|
static Value getGlobalString(Location loc, OpBuilder &builder,
|
|
const TypeConverter *typeConverter,
|
|
LLVM::GlobalOp &str) {
|
|
auto voidPtrTy = LLVM::LLVMPointerType::get(builder.getContext());
|
|
auto i32Ty = IntegerType::get(builder.getContext(), 32);
|
|
|
|
auto addr = builder.create<LLVM::AddressOfOp>(loc, voidPtrTy, str.getName());
|
|
auto idx = builder.create<LLVM::ConstantOp>(loc, i32Ty,
|
|
builder.getI32IntegerAttr(0));
|
|
std::array<Value, 2> idxs({idx, idx});
|
|
return builder.create<LLVM::GEPOp>(loc, voidPtrTy, str.getType(), addr, idxs);
|
|
}
|
|
|
|
/// Looks up a symbol and inserts a new functino at the beginning of the
|
|
/// module's region in case the function does not exists. If
|
|
/// insertBodyAndTerminator is set, also adds the entry block and return
|
|
/// terminator.
|
|
static LLVM::LLVMFuncOp
|
|
getOrInsertFunction(ModuleOp &module, ConversionPatternRewriter &rewriter,
|
|
Location loc, std::string name, Type signature,
|
|
bool insertBodyAndTerminator = false) {
|
|
auto func = module.lookupSymbol<LLVM::LLVMFuncOp>(name);
|
|
if (!func) {
|
|
OpBuilder moduleBuilder(module.getBodyRegion());
|
|
func = moduleBuilder.create<LLVM::LLVMFuncOp>(loc, name, signature);
|
|
if (insertBodyAndTerminator) {
|
|
func.addEntryBlock(moduleBuilder);
|
|
OpBuilder b(func.getBody());
|
|
b.create<LLVM::ReturnOp>(loc, ValueRange());
|
|
}
|
|
}
|
|
return func;
|
|
}
|
|
|
|
/// Return the LLVM type used to represent a signal. It corresponds to a struct
|
|
/// with the format: {valuePtr, bitOffset, instanceIndex, globalIndex}.
|
|
static Type getLLVMSigType(LLVM::LLVMDialect *dialect) {
|
|
auto voidPtrTy = LLVM::LLVMPointerType::get(dialect->getContext());
|
|
auto i64Ty = IntegerType::get(dialect->getContext(), 64);
|
|
return LLVM::LLVMStructType::getLiteral(dialect->getContext(),
|
|
{voidPtrTy, i64Ty, i64Ty, i64Ty});
|
|
}
|
|
|
|
/// Extract the details from the given signal struct. The details are returned
|
|
/// in the original struct order.
|
|
static std::vector<Value> getSignalDetail(ConversionPatternRewriter &rewriter,
|
|
LLVM::LLVMDialect *dialect,
|
|
Location loc, Value signal,
|
|
bool extractIndices = false) {
|
|
|
|
auto voidPtrTy = LLVM::LLVMPointerType::get(dialect->getContext());
|
|
auto i64Ty = IntegerType::get(dialect->getContext(), 64);
|
|
auto sigTy = getLLVMSigType(dialect);
|
|
|
|
std::vector<Value> result;
|
|
|
|
// Extract the value and offset elements.
|
|
auto sigPtrPtr = rewriter.create<LLVM::GEPOp>(loc, voidPtrTy, sigTy, signal,
|
|
ArrayRef<LLVM::GEPArg>({0, 0}));
|
|
result.push_back(rewriter.create<LLVM::LoadOp>(loc, voidPtrTy, sigPtrPtr));
|
|
|
|
auto offsetPtr = rewriter.create<LLVM::GEPOp>(loc, voidPtrTy, sigTy, signal,
|
|
ArrayRef<LLVM::GEPArg>({0, 1}));
|
|
result.push_back(rewriter.create<LLVM::LoadOp>(loc, i64Ty, offsetPtr));
|
|
|
|
// Extract the instance and global indices.
|
|
if (extractIndices) {
|
|
auto instIndexPtr = rewriter.create<LLVM::GEPOp>(
|
|
loc, voidPtrTy, sigTy, signal, ArrayRef<LLVM::GEPArg>({0, 2}));
|
|
result.push_back(rewriter.create<LLVM::LoadOp>(loc, i64Ty, instIndexPtr));
|
|
|
|
auto globalIndexPtr = rewriter.create<LLVM::GEPOp>(
|
|
loc, voidPtrTy, sigTy, signal, ArrayRef<LLVM::GEPArg>({0, 3}));
|
|
result.push_back(rewriter.create<LLVM::LoadOp>(loc, i64Ty, globalIndexPtr));
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/// Create a subsignal struct.
|
|
static Value createSubSig(LLVM::LLVMDialect *dialect,
|
|
ConversionPatternRewriter &rewriter, Location loc,
|
|
std::vector<Value> originDetail, Value newPtr,
|
|
Value newOffset) {
|
|
auto i32Ty = IntegerType::get(dialect->getContext(), 32);
|
|
auto sigTy = getLLVMSigType(dialect);
|
|
|
|
// Create signal struct.
|
|
auto sigUndef = rewriter.create<LLVM::UndefOp>(loc, sigTy);
|
|
auto storeSubPtr =
|
|
rewriter.create<LLVM::InsertValueOp>(loc, sigUndef, newPtr, 0);
|
|
auto storeSubOffset =
|
|
rewriter.create<LLVM::InsertValueOp>(loc, storeSubPtr, newOffset, 1);
|
|
auto storeSubInstIndex = rewriter.create<LLVM::InsertValueOp>(
|
|
loc, storeSubOffset, originDetail[2], 2);
|
|
auto storeSubGlobalIndex = rewriter.create<LLVM::InsertValueOp>(
|
|
loc, storeSubInstIndex, originDetail[3], 3);
|
|
|
|
// Allocate and store the subsignal.
|
|
auto oneC = rewriter.create<LLVM::ConstantOp>(loc, i32Ty,
|
|
rewriter.getI32IntegerAttr(1));
|
|
auto allocaSubSig = rewriter.create<LLVM::AllocaOp>(
|
|
loc, LLVM::LLVMPointerType::get(dialect->getContext()), sigTy, oneC, 4);
|
|
rewriter.create<LLVM::StoreOp>(loc, storeSubGlobalIndex, allocaSubSig);
|
|
|
|
return allocaSubSig;
|
|
}
|
|
|
|
/// Returns true if the given value is passed as an argument to the destination
|
|
/// block of the given WaitOp.
|
|
static bool isWaitDestArg(WaitOp op, Value val) {
|
|
for (auto arg : op.getDestOps()) {
|
|
if (arg == val)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Returns true if the given operation is used as a destination argument in a
|
|
// WaitOp.
|
|
static bool isWaitDestArg(Operation *op) {
|
|
for (auto user : op->getUsers()) {
|
|
if (auto wait = dyn_cast<WaitOp>(user))
|
|
return isWaitDestArg(wait, op->getResult(0));
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// Gather the types of values that are used outside of the block they're
|
|
/// defined in. An LLVMType structure containing those types, in order of
|
|
/// appearance, is returned.
|
|
static Type getProcPersistenceTy(LLVM::LLVMDialect *dialect,
|
|
const TypeConverter *converter, ProcOp &proc) {
|
|
SmallVector<Type, 3> types = SmallVector<Type, 3>();
|
|
proc.walk([&](Operation *op) -> void {
|
|
if (op->isUsedOutsideOfBlock(op->getBlock()) || isWaitDestArg(op)) {
|
|
auto ty = op->getResult(0).getType();
|
|
auto convertedTy = converter->convertType(ty);
|
|
types.push_back(convertedTy);
|
|
}
|
|
});
|
|
|
|
// Also persist block arguments escaping their defining block.
|
|
for (auto &block : proc.getBlocks()) {
|
|
// Skip entry block (contains the function signature in its args).
|
|
if (block.isEntryBlock())
|
|
continue;
|
|
|
|
for (auto arg : block.getArguments()) {
|
|
if (arg.isUsedOutsideOfBlock(&block)) {
|
|
types.push_back(converter->convertType(arg.getType()));
|
|
}
|
|
}
|
|
}
|
|
|
|
return LLVM::LLVMStructType::getLiteral(dialect->getContext(), types);
|
|
}
|
|
|
|
/// Insert a comparison block that either jumps to the trueDest block, if the
|
|
/// resume index mathces the current index, or to falseDest otherwise. If no
|
|
/// falseDest is provided, the next block is taken insead.
|
|
static void insertComparisonBlock(ConversionPatternRewriter &rewriter,
|
|
LLVM::LLVMDialect *dialect, Location loc,
|
|
Region *body, Value resumeIdx, int currIdx,
|
|
Block *trueDest, ValueRange trueDestArgs,
|
|
Block *falseDest = nullptr) {
|
|
auto i32Ty = IntegerType::get(dialect->getContext(), 32);
|
|
auto secondBlock = ++body->begin();
|
|
auto newBlock = rewriter.createBlock(body, secondBlock);
|
|
auto cmpIdx = rewriter.create<LLVM::ConstantOp>(
|
|
loc, i32Ty, rewriter.getI32IntegerAttr(currIdx));
|
|
auto cmpRes = rewriter.create<LLVM::ICmpOp>(loc, LLVM::ICmpPredicate::eq,
|
|
resumeIdx, cmpIdx);
|
|
|
|
// Default to jumping to the next block for the false case, if no explicit
|
|
// block is provided.
|
|
if (!falseDest)
|
|
falseDest = &*secondBlock;
|
|
|
|
rewriter.create<LLVM::CondBrOp>(loc, cmpRes, trueDest, trueDestArgs,
|
|
falseDest, ValueRange());
|
|
|
|
// Redirect the entry block terminator to the new comparison block.
|
|
auto entryTer = body->front().getTerminator();
|
|
entryTer->setSuccessor(newBlock, 0);
|
|
}
|
|
|
|
/// Insert a GEP operation to the pointer of the i-th value in the process
|
|
/// persistence table.
|
|
static Value gepPersistenceState(LLVM::LLVMDialect *dialect, Location loc,
|
|
ConversionPatternRewriter &rewriter,
|
|
Type stateTy, int index, Value state) {
|
|
return rewriter.create<LLVM::GEPOp>(
|
|
loc, LLVM::LLVMPointerType::get(dialect->getContext()), stateTy, state,
|
|
ArrayRef<LLVM::GEPArg>({0, 3, index}));
|
|
}
|
|
|
|
/// Persist a `Value` by storing it into the process persistence table, and
|
|
/// substituting the uses that escape the block the operation is defined in with
|
|
/// a load from the persistence table.
|
|
static void persistValue(LLVM::LLVMDialect *dialect, Location loc,
|
|
const TypeConverter *converter,
|
|
ConversionPatternRewriter &rewriter, Type stateTy,
|
|
int &i, Value state, Value persist) {
|
|
auto elemTy = cast<LLVM::LLVMStructType>(
|
|
cast<LLVM::LLVMStructType>(stateTy).getBody()[3])
|
|
.getBody()[i];
|
|
|
|
if (auto arg = dyn_cast<BlockArgument>(persist)) {
|
|
rewriter.setInsertionPointToStart(arg.getParentBlock());
|
|
} else {
|
|
rewriter.setInsertionPointAfter(persist.getDefiningOp());
|
|
}
|
|
|
|
Value convPersist = converter->materializeTargetConversion(
|
|
rewriter, loc, converter->convertType(persist.getType()), {persist});
|
|
|
|
auto gep0 = gepPersistenceState(dialect, loc, rewriter, stateTy, i, state);
|
|
|
|
Value toStore;
|
|
if (auto ptr = dyn_cast<PtrType>(persist.getType())) {
|
|
// Unwrap the pointer and store it's value.
|
|
auto elemTy = converter->convertType(ptr.getUnderlyingType());
|
|
toStore = rewriter.create<LLVM::LoadOp>(loc, elemTy, convPersist);
|
|
} else if (isa<SigType>(persist.getType())) {
|
|
// Unwrap and store the signal struct.
|
|
toStore = rewriter.create<LLVM::LoadOp>(loc, getLLVMSigType(dialect),
|
|
convPersist);
|
|
} else {
|
|
// Store the value directly.
|
|
toStore = convPersist;
|
|
}
|
|
|
|
rewriter.create<LLVM::StoreOp>(loc, toStore, gep0);
|
|
|
|
// Load the value from the persistence table and substitute the original
|
|
// use with it, whenever it is in a different block.
|
|
for (auto &use : llvm::make_early_inc_range(persist.getUses())) {
|
|
auto user = use.getOwner();
|
|
if (isa<PtrType>(persist.getType()) && user != toStore.getDefiningOp() &&
|
|
user != convPersist.getDefiningOp() &&
|
|
persist.getParentBlock() == user->getBlock()) {
|
|
// Redirect uses of the pointer in the same block to the pointer in the
|
|
// persistence state. This ensures that stores and loads all operate on
|
|
// the same value.
|
|
use.set(gep0);
|
|
} else if (persist.getParentBlock() != user->getBlock() ||
|
|
(isa<WaitOp>(user) &&
|
|
isWaitDestArg(cast<WaitOp>(user), persist))) {
|
|
// The destination args of a wait op have to be loaded in the entry block
|
|
// of the function, before jumping to the resume destination, so they can
|
|
// be passed as block arguments by the comparison block.
|
|
if (isa<WaitOp>(user) && isWaitDestArg(cast<WaitOp>(user), persist))
|
|
rewriter.setInsertionPoint(
|
|
user->getParentRegion()->front().getTerminator());
|
|
else
|
|
rewriter.setInsertionPointToStart(user->getBlock());
|
|
|
|
auto gep1 =
|
|
gepPersistenceState(dialect, loc, rewriter, stateTy, i, state);
|
|
// Use the pointer in the state struct directly for pointer and signal
|
|
// types.
|
|
if (isa<PtrType, SigType>(persist.getType())) {
|
|
use.set(gep1);
|
|
} else {
|
|
auto load1 = rewriter.create<LLVM::LoadOp>(loc, elemTy, gep1);
|
|
// Load the value otherwise.
|
|
use.set(load1);
|
|
}
|
|
}
|
|
}
|
|
i++;
|
|
}
|
|
|
|
/// Insert the blocks and operations needed to persist values across suspension,
|
|
/// as well as ones needed to resume execution at the right spot.
|
|
static void insertPersistence(const TypeConverter *converter,
|
|
ConversionPatternRewriter &rewriter,
|
|
LLVM::LLVMDialect *dialect, Location loc,
|
|
ProcOp &proc, Type &stateTy,
|
|
LLVM::LLVMFuncOp &converted,
|
|
Operation *splitEntryBefore) {
|
|
auto i32Ty = IntegerType::get(dialect->getContext(), 32);
|
|
|
|
auto &firstBB = converted.getBody().front();
|
|
|
|
// Split entry block such that all the operations contained in it in the
|
|
// original process appear after the comparison blocks.
|
|
auto splitFirst =
|
|
rewriter.splitBlock(&firstBB, splitEntryBefore->getIterator());
|
|
|
|
// Insert dummy branch terminator at the new end of the function's entry
|
|
// block.
|
|
rewriter.setInsertionPointToEnd(&firstBB);
|
|
rewriter.create<LLVM::BrOp>(loc, ValueRange(), splitFirst);
|
|
|
|
// Load the resume index from the process state argument.
|
|
rewriter.setInsertionPoint(firstBB.getTerminator());
|
|
auto gep = rewriter.create<LLVM::GEPOp>(
|
|
loc, LLVM::LLVMPointerType::get(dialect->getContext()), i32Ty,
|
|
converted.getArgument(1), ArrayRef<LLVM::GEPArg>({1}));
|
|
|
|
auto larg = rewriter.create<LLVM::LoadOp>(loc, i32Ty, gep);
|
|
|
|
auto body = &converted.getBody();
|
|
|
|
// Insert an abort block as the last block.
|
|
auto abortBlock = rewriter.createBlock(body, body->end());
|
|
rewriter.create<LLVM::ReturnOp>(loc, ValueRange());
|
|
|
|
// Redirect the entry block to a first comparison block. If on a first
|
|
// execution, jump to the new (splitted) entry block, else the process is in
|
|
// an illegal state and jump to the abort block.
|
|
insertComparisonBlock(rewriter, dialect, loc, body, larg, 0, splitFirst,
|
|
ValueRange(), abortBlock);
|
|
|
|
// Keep track of the index in the presistence table of the operation we
|
|
// are currently processing.
|
|
int i = 0;
|
|
// Keep track of the current resume index for comparison blocks.
|
|
int waitInd = 0;
|
|
|
|
// Insert operations required to persist values across process suspension.
|
|
converted.walk([&](Operation *op) -> void {
|
|
if ((op->isUsedOutsideOfBlock(op->getBlock()) || isWaitDestArg(op)) &&
|
|
op->getResult(0) != larg.getResult()) {
|
|
persistValue(dialect, loc, converter, rewriter, stateTy, i,
|
|
converted.getArgument(1), op->getResult(0));
|
|
}
|
|
|
|
// Insert a comparison block for wait operations.
|
|
if (auto wait = dyn_cast<WaitOp>(op)) {
|
|
insertComparisonBlock(rewriter, dialect, loc, body, larg, ++waitInd,
|
|
wait.getDest(), wait.getDestOps());
|
|
|
|
// Insert the resume index update at the wait operation location.
|
|
rewriter.setInsertionPoint(op);
|
|
auto procState = op->getParentOfType<LLVM::LLVMFuncOp>().getArgument(1);
|
|
auto resumeIdxC = rewriter.create<LLVM::ConstantOp>(
|
|
loc, i32Ty, rewriter.getI32IntegerAttr(waitInd));
|
|
auto resumeIdxPtr = rewriter.create<LLVM::GEPOp>(
|
|
loc, LLVM::LLVMPointerType::get(dialect->getContext()), i32Ty,
|
|
procState, ArrayRef<LLVM::GEPArg>({1}));
|
|
rewriter.create<LLVM::StoreOp>(op->getLoc(), resumeIdxC, resumeIdxPtr);
|
|
}
|
|
});
|
|
|
|
// Also persist argument blocks escaping their defining block.
|
|
for (auto &block : converted.getBlocks()) {
|
|
// Skip entry block as it contains the function signature.
|
|
if (block.isEntryBlock())
|
|
continue;
|
|
|
|
for (auto arg : block.getArguments()) {
|
|
if (arg.isUsedOutsideOfBlock(&block)) {
|
|
persistValue(dialect, loc, converter, rewriter, stateTy, i,
|
|
converted.getArgument(1), arg);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Return a struct type of arrays containing one entry for each RegOp condition
|
|
/// that require more than one state of the trigger to infer it (i.e. `both`,
|
|
/// `rise` and `fall`).
|
|
static LLVM::LLVMStructType getRegStateTy(LLVM::LLVMDialect *dialect,
|
|
Operation *entity) {
|
|
SmallVector<Type, 4> types;
|
|
entity->walk([&](RegOp op) {
|
|
size_t count = 0;
|
|
for (size_t i = 0; i < op.getModes().size(); ++i) {
|
|
auto mode = op.getRegModeAt(i);
|
|
if (mode == RegMode::fall || mode == RegMode::rise ||
|
|
mode == RegMode::both)
|
|
++count;
|
|
}
|
|
if (count > 0)
|
|
types.push_back(LLVM::LLVMArrayType::get(
|
|
IntegerType::get(dialect->getContext(), 1), count));
|
|
});
|
|
return LLVM::LLVMStructType::getLiteral(dialect->getContext(), types);
|
|
}
|
|
|
|
/// Create a zext operation by one bit on the given value. This is useful when
|
|
/// passing unsigned indexes to a GEP instruction, which treats indexes as
|
|
/// signed values, to avoid unexpected "sign overflows".
|
|
static Value zextByOne(Location loc, ConversionPatternRewriter &rewriter,
|
|
Value value) {
|
|
auto valueTy = value.getType();
|
|
auto zextTy = IntegerType::get(valueTy.getContext(),
|
|
valueTy.getIntOrFloatBitWidth() + 1);
|
|
return rewriter.create<LLVM::ZExtOp>(loc, zextTy, value);
|
|
}
|
|
|
|
/// Adjust the bithwidth of value to be the same as targetTy's bitwidth.
|
|
static Value adjustBitWidth(Location loc, ConversionPatternRewriter &rewriter,
|
|
Type targetTy, Value value) {
|
|
auto valueWidth = value.getType().getIntOrFloatBitWidth();
|
|
auto targetWidth = targetTy.getIntOrFloatBitWidth();
|
|
|
|
if (valueWidth < targetWidth)
|
|
return rewriter.create<LLVM::ZExtOp>(loc, targetTy, value);
|
|
|
|
if (valueWidth > targetWidth)
|
|
return rewriter.create<LLVM::TruncOp>(loc, targetTy, value);
|
|
|
|
return value;
|
|
}
|
|
|
|
static unsigned getIndexOfOperandResult(Operation *op, Value result) {
|
|
for (unsigned j = 0, e = op->getNumResults(); j < e; ++j) {
|
|
if (result == result.getDefiningOp()->getResult(j))
|
|
return j;
|
|
}
|
|
llvm_unreachable(
|
|
"no way to recurse to an operation that does not return any value");
|
|
}
|
|
|
|
/// Recursively clone the init origin of a sig operation into the init function,
|
|
/// up to the initial constant value(s). This is required to clone the
|
|
/// initialization of array and struct signals, where the init operand cannot
|
|
/// originate from a constant operation.
|
|
static Value recursiveCloneInit(OpBuilder &initBuilder, IRMapping &mapping,
|
|
Value init) {
|
|
SmallVector<Value> clonedOperands;
|
|
Operation *initOp = init.getDefiningOp();
|
|
|
|
// If we end up at a value that we get via BlockArgument or as a result of a
|
|
// llhd.prb op, return a nullptr to signal that something went wrong, because
|
|
// these cases are not supported.
|
|
if (!initOp || isa<llhd::PrbOp>(initOp))
|
|
return nullptr;
|
|
|
|
for (size_t i = 0, e = initOp->getNumOperands(); i < e; ++i) {
|
|
Value operand = initOp->getOperand(i);
|
|
|
|
// If we have some value that is used multiple times (e.g., broadcasted to
|
|
// an array) then don't emit the ops to create this value several times,
|
|
// but instead remember the cloned value and use it again.
|
|
if (auto memorizedOperand = mapping.lookupOrNull(operand)) {
|
|
clonedOperands.push_back(memorizedOperand);
|
|
continue;
|
|
}
|
|
|
|
// Recursively follow operands.
|
|
Value clonedOperand = recursiveCloneInit(initBuilder, mapping, operand);
|
|
if (!clonedOperand)
|
|
return nullptr;
|
|
|
|
mapping.map(operand, clonedOperand);
|
|
clonedOperands.push_back(clonedOperand);
|
|
}
|
|
|
|
Operation *clone = initOp->clone();
|
|
clone->setOperands(clonedOperands);
|
|
|
|
// If we have cloned an operation that returns several values, we have to
|
|
// find the result value of the cloned operation we want to return.
|
|
unsigned index = getIndexOfOperandResult(initOp, init);
|
|
return initBuilder.insert(clone)->getResult(index);
|
|
}
|
|
|
|
/// Check if the given type is either of LLHD's ArrayType, StructType, or LLVM
|
|
/// array or struct type.
|
|
static bool isArrayOrStruct(Type type) {
|
|
return isa<LLVM::LLVMArrayType, LLVM::LLVMStructType, hw::ArrayType,
|
|
hw::StructType>(type);
|
|
}
|
|
|
|
/// Shift an integer signal pointer to obtain a view of the underlying value as
|
|
/// if it was shifted.
|
|
static std::pair<Value, Value>
|
|
shiftIntegerSigPointer(Location loc, LLVM::LLVMDialect *dialect,
|
|
ConversionPatternRewriter &rewriter, Value pointer,
|
|
Value index) {
|
|
auto voidPtrTy = LLVM::LLVMPointerType::get(dialect->getContext());
|
|
auto i64Ty = IntegerType::get(dialect->getContext(), 64);
|
|
|
|
auto ptrToInt = rewriter.create<LLVM::PtrToIntOp>(loc, i64Ty, pointer);
|
|
auto const8 = rewriter.create<LLVM::ConstantOp>(
|
|
loc, index.getType(), rewriter.getI64IntegerAttr(8));
|
|
auto ptrOffset = rewriter.create<LLVM::UDivOp>(loc, index, const8);
|
|
auto shiftedPtr = rewriter.create<LLVM::AddOp>(loc, ptrToInt, ptrOffset);
|
|
auto newPtr = rewriter.create<LLVM::IntToPtrOp>(loc, voidPtrTy, shiftedPtr);
|
|
|
|
// Compute the new offset into the first byte.
|
|
auto bitOffset = rewriter.create<LLVM::URemOp>(loc, index, const8);
|
|
|
|
return std::make_pair(newPtr, bitOffset);
|
|
}
|
|
|
|
/// Shift the pointer of a structured-type (array or struct) signal, to change
|
|
/// its view as if the desired slice/element was extracted.
|
|
static Value shiftStructuredSigPointer(Location loc,
|
|
ConversionPatternRewriter &rewriter,
|
|
Type elemTy, Value pointer,
|
|
LLVM::GEPArg index) {
|
|
// TODO: Remove unused args
|
|
auto voidPtrTy = LLVM::LLVMPointerType::get(rewriter.getContext());
|
|
return rewriter.create<LLVM::GEPOp>(loc, voidPtrTy, elemTy, pointer,
|
|
ArrayRef<LLVM::GEPArg>({0, index}));
|
|
}
|
|
|
|
/// Shift the pointer of an array-typed signal, to change its view as if the
|
|
/// desired slice/element was extracted.
|
|
static Value shiftArraySigPointer(Location loc,
|
|
ConversionPatternRewriter &rewriter,
|
|
Type arrTy, Value pointer,
|
|
LLVM::GEPArg index) {
|
|
if (auto indexValue = dyn_cast<Value>(index))
|
|
index = zextByOne(loc, rewriter, indexValue);
|
|
return shiftStructuredSigPointer(loc, rewriter, arrTy, pointer, index);
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Type conversions
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
static Type convertSigType(SigType type, LLVMTypeConverter &converter) {
|
|
auto &context = converter.getContext();
|
|
// auto i64Ty = IntegerType::get(&context, 64);
|
|
auto voidPtrTy = LLVM::LLVMPointerType::get(&context);
|
|
// LLVM::LLVMStructType::getLiteral(&context, {voidPtrTy, i64Ty, i64Ty,
|
|
// i64Ty})
|
|
return voidPtrTy;
|
|
}
|
|
|
|
static Type convertTimeType(TimeType type, LLVMTypeConverter &converter) {
|
|
auto i64Ty = IntegerType::get(&converter.getContext(), 64);
|
|
return LLVM::LLVMArrayType::get(i64Ty, 3);
|
|
}
|
|
|
|
static Type convertPtrType(PtrType type, LLVMTypeConverter &converter) {
|
|
// converter.convertType(type.getUnderlyingType())
|
|
return LLVM::LLVMPointerType::get(type.getContext());
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Unit conversions
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
namespace {
|
|
/// Convert an `llhd.entity` entity to LLVM dialect. The result is an
|
|
/// `llvm.func` which takes a pointer to the global simulation state, a pointer
|
|
/// to the entity's local state, and a pointer to the instance's signal table as
|
|
/// arguments.
|
|
struct EntityOpConversion : public ConvertToLLVMPattern {
|
|
explicit EntityOpConversion(MLIRContext *ctx,
|
|
LLVMTypeConverter &typeConverter,
|
|
size_t &sigCounter, size_t ®Counter)
|
|
: ConvertToLLVMPattern(llhd::EntityOp::getOperationName(), ctx,
|
|
typeConverter),
|
|
sigCounter(sigCounter), regCounter(regCounter) {}
|
|
|
|
LogicalResult
|
|
matchAndRewrite(Operation *op, ArrayRef<Value> operands,
|
|
ConversionPatternRewriter &rewriter) const override {
|
|
// Get adapted operands.
|
|
EntityOpAdaptor transformed(operands);
|
|
// Get entity operation.
|
|
auto entityOp = cast<EntityOp>(op);
|
|
|
|
// Collect used llvm types.
|
|
auto voidTy = getVoidType();
|
|
auto voidPtrTy = getVoidPtrType();
|
|
auto sigTy = getLLVMSigType(&getDialect());
|
|
|
|
regCounter = 0;
|
|
|
|
// Use an intermediate signature conversion to add the arguments for the
|
|
// state and signal table pointer arguments.
|
|
LLVMTypeConverter::SignatureConversion intermediate(
|
|
entityOp.getNumArguments());
|
|
// Add state and signal table arguments.
|
|
intermediate.addInputs(
|
|
std::array<Type, 3>({voidPtrTy, voidPtrTy, voidPtrTy}));
|
|
for (size_t i = 0, e = entityOp.getNumArguments(); i < e; ++i)
|
|
intermediate.addInputs(i, voidTy);
|
|
rewriter.applySignatureConversion(entityOp.getBodyBlock(), intermediate,
|
|
typeConverter);
|
|
|
|
OpBuilder bodyBuilder =
|
|
OpBuilder::atBlockBegin(&entityOp.getBlocks().front());
|
|
LLVMTypeConverter::SignatureConversion final(
|
|
intermediate.getConvertedTypes().size());
|
|
final.addInputs(0, voidPtrTy);
|
|
final.addInputs(1, voidPtrTy);
|
|
final.addInputs(2, voidPtrTy);
|
|
|
|
// The first n elements of the signal table represent the entity arguments,
|
|
// while the remaining elements represent the entity's owned signals.
|
|
sigCounter = entityOp.getNumArguments();
|
|
for (size_t i = 0; i < sigCounter; ++i) {
|
|
// Create gep operations from the signal table for each original argument.
|
|
auto gep = bodyBuilder.create<LLVM::GEPOp>(op->getLoc(), voidPtrTy, sigTy,
|
|
entityOp.getArgument(2),
|
|
LLVM::GEPArg(i));
|
|
// Remap i-th original argument to the gep'd signal pointer.
|
|
final.remapInput(i + 3, gep.getResult());
|
|
}
|
|
|
|
rewriter.applySignatureConversion(entityOp.getBodyBlock(), final,
|
|
typeConverter);
|
|
|
|
// Get the converted entity signature.
|
|
auto funcTy =
|
|
LLVM::LLVMFunctionType::get(voidTy, {voidPtrTy, voidPtrTy, voidPtrTy});
|
|
|
|
// Create the a new llvm function to house the lowered entity.
|
|
auto llvmFunc = rewriter.create<LLVM::LLVMFuncOp>(
|
|
op->getLoc(), entityOp.getName(), funcTy);
|
|
|
|
// Add a return to the entity for later inclusion into the LLVM function.
|
|
rewriter.setInsertionPointToEnd(&entityOp.getBlocks().front());
|
|
rewriter.create<LLVM::ReturnOp>(op->getLoc(), ValueRange{});
|
|
|
|
// Inline the entity region in the new llvm function.
|
|
rewriter.inlineRegionBefore(entityOp.getBody(), llvmFunc.getBody(),
|
|
llvmFunc.end());
|
|
|
|
// Erase the original operation.
|
|
rewriter.eraseOp(op);
|
|
|
|
return success();
|
|
}
|
|
|
|
private:
|
|
size_t &sigCounter;
|
|
size_t ®Counter;
|
|
};
|
|
} // namespace
|
|
|
|
namespace {
|
|
/// Convert an `llhd.proc` operation to LLVM dialect. This inserts the required
|
|
/// logic to resume execution after an `llhd.wait` operation, as well as state
|
|
/// keeping for values that need to persist across suspension.
|
|
struct ProcOpConversion : public ConvertToLLVMPattern {
|
|
explicit ProcOpConversion(MLIRContext *ctx, LLVMTypeConverter &typeConverter)
|
|
: ConvertToLLVMPattern(ProcOp::getOperationName(), ctx, typeConverter) {}
|
|
|
|
LogicalResult
|
|
matchAndRewrite(Operation *op, ArrayRef<Value> operands,
|
|
ConversionPatternRewriter &rewriter) const override {
|
|
auto procOp = cast<ProcOp>(op);
|
|
|
|
// Get adapted operands.
|
|
ProcOpAdaptor transformed(operands);
|
|
|
|
// Collect used llvm types.
|
|
auto voidTy = getVoidType();
|
|
auto voidPtrTy = getVoidPtrType();
|
|
auto i32Ty = IntegerType::get(rewriter.getContext(), 32);
|
|
auto stateTy = LLVM::LLVMStructType::getLiteral(
|
|
rewriter.getContext(),
|
|
{/*currentInstance=*/i32Ty, /*resumeIndex=*/i32Ty,
|
|
/*senseFlags=*/voidPtrTy /*senseTableTy*/,
|
|
/*persistence=*/
|
|
getProcPersistenceTy(&getDialect(), typeConverter, procOp)});
|
|
auto sigTy = getLLVMSigType(&getDialect());
|
|
|
|
// Keep track of the original first operation of the process, to know where
|
|
// to split the first block to insert comparison blocks.
|
|
auto &firstOp = op->getRegion(0).front().front();
|
|
|
|
// Have an intermediate signature conversion to add the arguments for the
|
|
// state, process-specific state and signal table.
|
|
LLVMTypeConverter::SignatureConversion intermediate(
|
|
procOp.getNumArguments());
|
|
// Add state, process state table and signal table arguments.
|
|
std::array<Type, 3> procArgTys({voidPtrTy, voidPtrTy, voidPtrTy});
|
|
intermediate.addInputs(procArgTys);
|
|
for (size_t i = 0, e = procOp.getNumArguments(); i < e; ++i)
|
|
intermediate.addInputs(i, voidTy);
|
|
rewriter.applySignatureConversion(&procOp.getBlocks().front(), intermediate,
|
|
typeConverter);
|
|
|
|
// Get the final signature conversion.
|
|
OpBuilder bodyBuilder =
|
|
OpBuilder::atBlockBegin(&procOp.getBlocks().front());
|
|
LLVMTypeConverter::SignatureConversion final(
|
|
intermediate.getConvertedTypes().size());
|
|
final.addInputs(0, voidPtrTy);
|
|
final.addInputs(1, voidPtrTy);
|
|
final.addInputs(2, voidPtrTy);
|
|
|
|
for (size_t i = 0, e = procOp.getNumArguments(); i < e; ++i) {
|
|
// Create gep operations from the signal table for each original argument.
|
|
auto gep = bodyBuilder.create<LLVM::GEPOp>(op->getLoc(), voidPtrTy, sigTy,
|
|
procOp.getArgument(2),
|
|
LLVM::GEPArg(i));
|
|
|
|
// Remap the i-th original argument to the gep'd value.
|
|
final.remapInput(i + 3, gep.getResult());
|
|
}
|
|
|
|
// Get the converted process signature.
|
|
auto funcTy =
|
|
LLVM::LLVMFunctionType::get(voidTy, {voidPtrTy, voidPtrTy, voidPtrTy});
|
|
// Create a new llvm function to house the lowered process.
|
|
auto llvmFunc = rewriter.create<LLVM::LLVMFuncOp>(op->getLoc(),
|
|
procOp.getName(), funcTy);
|
|
llvmFunc->setAttr("llhd.argument_count",
|
|
rewriter.getI32IntegerAttr(procOp.getNumArguments()));
|
|
|
|
// Inline the process region in the new llvm function.
|
|
rewriter.inlineRegionBefore(procOp.getBody(), llvmFunc.getBody(),
|
|
llvmFunc.end());
|
|
|
|
insertPersistence(typeConverter, rewriter, &getDialect(), op->getLoc(),
|
|
procOp, stateTy, llvmFunc, &firstOp);
|
|
|
|
// Convert the block argument types after inserting the persistence, as this
|
|
// would otherwise interfere with the persistence generation.
|
|
if (failed(rewriter.convertRegionTypes(&llvmFunc.getBody(), *typeConverter,
|
|
&final))) {
|
|
return failure();
|
|
}
|
|
|
|
rewriter.eraseOp(op);
|
|
|
|
return success();
|
|
}
|
|
};
|
|
} // namespace
|
|
|
|
namespace {
|
|
/// Convert an `llhd.halt` operation to LLVM dialect. This zeroes out all the
|
|
/// senses and returns, effectively making the process unable to be invoked
|
|
/// again.
|
|
struct HaltOpConversion : public ConvertToLLVMPattern {
|
|
explicit HaltOpConversion(MLIRContext *ctx, LLVMTypeConverter &typeConverter)
|
|
: ConvertToLLVMPattern(HaltOp::getOperationName(), ctx, typeConverter) {}
|
|
|
|
LogicalResult
|
|
matchAndRewrite(Operation *op, ArrayRef<Value> operands,
|
|
ConversionPatternRewriter &rewriter) const override {
|
|
auto i1Ty = IntegerType::get(rewriter.getContext(), 1);
|
|
auto voidPtrTy = LLVM::LLVMPointerType::get(rewriter.getContext());
|
|
auto llvmFunc = op->getParentOfType<LLVM::LLVMFuncOp>();
|
|
auto procState = llvmFunc.getArgument(1);
|
|
|
|
// Get senses ptr from the process state argument.
|
|
auto sensePtrGep =
|
|
rewriter.create<LLVM::GEPOp>(op->getLoc(), voidPtrTy, voidPtrTy,
|
|
procState, ArrayRef<LLVM::GEPArg>({2}));
|
|
auto sensePtr =
|
|
rewriter.create<LLVM::LoadOp>(op->getLoc(), voidPtrTy, sensePtrGep);
|
|
|
|
// Zero out all the senses flags.
|
|
unsigned numSenseEntries =
|
|
llvmFunc->getAttrOfType<IntegerAttr>("llhd.argument_count")
|
|
.getValue()
|
|
.getZExtValue();
|
|
auto zeroB = rewriter.create<LLVM::ConstantOp>(op->getLoc(), i1Ty,
|
|
rewriter.getBoolAttr(false));
|
|
for (unsigned i = 0; i < numSenseEntries; ++i) {
|
|
auto senseElemPtr = rewriter.create<LLVM::GEPOp>(
|
|
op->getLoc(), voidPtrTy, i1Ty, sensePtr, ArrayRef<LLVM::GEPArg>({i}));
|
|
rewriter.create<LLVM::StoreOp>(op->getLoc(), zeroB, senseElemPtr);
|
|
}
|
|
|
|
rewriter.replaceOpWithNewOp<LLVM::ReturnOp>(op, ValueRange());
|
|
return success();
|
|
}
|
|
};
|
|
} // namespace
|
|
|
|
namespace {
|
|
/// Convert an `llhd.wait` operation to LLVM dialect. This sets the current
|
|
/// resume point, sets the observed senses (if present) and schedules the timed
|
|
/// wake up (if present).
|
|
struct WaitOpConversion : public ConvertToLLVMPattern {
|
|
explicit WaitOpConversion(MLIRContext *ctx, LLVMTypeConverter &typeConverter)
|
|
: ConvertToLLVMPattern(WaitOp::getOperationName(), ctx, typeConverter) {}
|
|
|
|
LogicalResult
|
|
matchAndRewrite(Operation *op, ArrayRef<Value> operands,
|
|
ConversionPatternRewriter &rewriter) const override {
|
|
auto waitOp = cast<WaitOp>(op);
|
|
WaitOpAdaptor transformed(operands, op->getAttrDictionary());
|
|
auto llvmFunc = op->getParentOfType<LLVM::LLVMFuncOp>();
|
|
|
|
auto voidTy = getVoidType();
|
|
auto voidPtrTy = getVoidPtrType();
|
|
auto i1Ty = IntegerType::get(rewriter.getContext(), 1);
|
|
auto i64Ty = IntegerType::get(rewriter.getContext(), 64);
|
|
|
|
// Get the llhdSuspend runtime function.
|
|
auto llhdSuspendTy = LLVM::LLVMFunctionType::get(
|
|
voidTy, {voidPtrTy, voidPtrTy, i64Ty, i64Ty, i64Ty});
|
|
auto module = op->getParentOfType<ModuleOp>();
|
|
auto llhdSuspendFunc = getOrInsertFunction(module, rewriter, op->getLoc(),
|
|
"llhdSuspend", llhdSuspendTy);
|
|
|
|
auto statePtr = llvmFunc.getArgument(0);
|
|
auto procState = llvmFunc.getArgument(1);
|
|
|
|
// Get senses ptr.
|
|
auto sensePtrGep =
|
|
rewriter.create<LLVM::GEPOp>(op->getLoc(), voidPtrTy, voidPtrTy,
|
|
procState, ArrayRef<LLVM::GEPArg>({2}));
|
|
auto sensePtr =
|
|
rewriter.create<LLVM::LoadOp>(op->getLoc(), voidPtrTy, sensePtrGep);
|
|
|
|
// Reset sense table, if not all signals are observed.
|
|
unsigned numSenseEntries =
|
|
llvmFunc->getAttrOfType<IntegerAttr>("llhd.argument_count")
|
|
.getValue()
|
|
.getZExtValue();
|
|
if (waitOp.getObs().size() < numSenseEntries) {
|
|
auto zeroB = rewriter.create<LLVM::ConstantOp>(
|
|
op->getLoc(), i1Ty, rewriter.getBoolAttr(false));
|
|
for (size_t i = 0; i < numSenseEntries; ++i) {
|
|
auto senseElemPtr =
|
|
rewriter.create<LLVM::GEPOp>(op->getLoc(), voidPtrTy, i1Ty,
|
|
sensePtr, ArrayRef<LLVM::GEPArg>({i}));
|
|
rewriter.create<LLVM::StoreOp>(op->getLoc(), zeroB, senseElemPtr);
|
|
}
|
|
}
|
|
|
|
// Set sense flags for observed signals.
|
|
for (auto observed : transformed.getObs()) {
|
|
auto instIndexPtr =
|
|
rewriter.create<LLVM::GEPOp>(op->getLoc(), voidPtrTy, i64Ty, observed,
|
|
ArrayRef<LLVM::GEPArg>({2}));
|
|
auto instIndex =
|
|
rewriter.create<LLVM::LoadOp>(op->getLoc(), i64Ty, instIndexPtr)
|
|
.getResult();
|
|
auto oneB = rewriter.create<LLVM::ConstantOp>(op->getLoc(), i1Ty,
|
|
rewriter.getBoolAttr(true));
|
|
auto senseElementPtr =
|
|
rewriter.create<LLVM::GEPOp>(op->getLoc(), voidPtrTy, i1Ty, sensePtr,
|
|
ArrayRef<LLVM::GEPArg>({instIndex}));
|
|
rewriter.create<LLVM::StoreOp>(op->getLoc(), oneB, senseElementPtr);
|
|
}
|
|
|
|
// Update and store the new resume index in the process state.
|
|
// Spawn scheduled event, if present.
|
|
if (waitOp.getTime()) {
|
|
auto realTime = rewriter.create<LLVM::ExtractValueOp>(
|
|
op->getLoc(), transformed.getTime(), 0);
|
|
auto delta = rewriter.create<LLVM::ExtractValueOp>(
|
|
op->getLoc(), transformed.getTime(), 1);
|
|
auto eps = rewriter.create<LLVM::ExtractValueOp>(
|
|
op->getLoc(), transformed.getTime(), 2);
|
|
|
|
std::array<Value, 5> args({statePtr, procState, realTime, delta, eps});
|
|
rewriter.create<LLVM::CallOp>(op->getLoc(), std::nullopt,
|
|
SymbolRefAttr::get(llhdSuspendFunc), args);
|
|
}
|
|
|
|
rewriter.replaceOpWithNewOp<LLVM::ReturnOp>(op, ValueRange());
|
|
return success();
|
|
}
|
|
};
|
|
} // namespace
|
|
|
|
namespace {
|
|
/// Lower an llhd.inst operation to LLVM dialect. This generates malloc calls
|
|
/// and allocSignal calls (to store the pointer into the state) for each signal
|
|
/// in the instantiated entity.
|
|
struct InstOpConversion : public ConvertToLLVMPattern {
|
|
explicit InstOpConversion(MLIRContext *ctx, LLVMTypeConverter &typeConverter)
|
|
: ConvertToLLVMPattern(InstOp::getOperationName(), ctx, typeConverter) {}
|
|
|
|
LogicalResult
|
|
matchAndRewrite(Operation *op, ArrayRef<Value> operands,
|
|
ConversionPatternRewriter &rewriter) const override {
|
|
// Get the inst operation.
|
|
auto instOp = cast<InstOp>(op);
|
|
// Get the parent module.
|
|
auto module = op->getParentOfType<ModuleOp>();
|
|
auto entity = op->getParentOfType<EntityOp>();
|
|
|
|
auto voidTy = getVoidType();
|
|
auto voidPtrTy = getVoidPtrType();
|
|
auto i1Ty = IntegerType::get(rewriter.getContext(), 1);
|
|
auto i32Ty = IntegerType::get(rewriter.getContext(), 32);
|
|
auto i64Ty = IntegerType::get(rewriter.getContext(), 64);
|
|
|
|
// Init function signature: (i8* %state) -> void.
|
|
auto initFuncTy = LLVM::LLVMFunctionType::get(voidTy, {voidPtrTy});
|
|
auto initFunc =
|
|
getOrInsertFunction(module, rewriter, op->getLoc(), "llhd_init",
|
|
initFuncTy, /*insertBodyAndTerminator=*/true);
|
|
|
|
// Get or insert the malloc function definition.
|
|
// Malloc function signature: (i64 %size) -> i8* %pointer.
|
|
auto mallocSigFuncTy = LLVM::LLVMFunctionType::get(voidPtrTy, {i64Ty});
|
|
auto mallFunc = getOrInsertFunction(module, rewriter, op->getLoc(),
|
|
"malloc", mallocSigFuncTy);
|
|
|
|
// Get or insert the allocSignal library call definition.
|
|
// allocSignal function signature: (i8* %state, i8* %sig_name, i8*
|
|
// %sig_owner, i32 %value) -> i32 %sig_index.
|
|
auto allocSigFuncTy = LLVM::LLVMFunctionType::get(
|
|
i32Ty, {voidPtrTy, i32Ty, voidPtrTy, voidPtrTy, i64Ty});
|
|
auto sigFunc = getOrInsertFunction(module, rewriter, op->getLoc(),
|
|
"allocSignal", allocSigFuncTy);
|
|
|
|
// Add information about the elements of an array signal to the state.
|
|
// Signature: (i8* state, i32 signalIndex, i32 size, i32 numElements) ->
|
|
// void
|
|
auto addSigArrElemFuncTy =
|
|
LLVM::LLVMFunctionType::get(voidTy, {voidPtrTy, i32Ty, i32Ty, i32Ty});
|
|
auto addSigElemFunc =
|
|
getOrInsertFunction(module, rewriter, op->getLoc(),
|
|
"addSigArrayElements", addSigArrElemFuncTy);
|
|
|
|
// Add information about one element of a struct signal to the state.
|
|
// Signature: (i8* state, i32 signalIndex, i32 offset, i32 size) -> void
|
|
auto addSigStructElemFuncTy =
|
|
LLVM::LLVMFunctionType::get(voidTy, {voidPtrTy, i32Ty, i32Ty, i32Ty});
|
|
auto addSigStructFunc =
|
|
getOrInsertFunction(module, rewriter, op->getLoc(),
|
|
"addSigStructElement", addSigStructElemFuncTy);
|
|
|
|
// Get or insert allocProc library call definition.
|
|
auto allocProcFuncTy =
|
|
LLVM::LLVMFunctionType::get(voidTy, {voidPtrTy, voidPtrTy, voidPtrTy});
|
|
auto allocProcFunc = getOrInsertFunction(module, rewriter, op->getLoc(),
|
|
"allocProc", allocProcFuncTy);
|
|
|
|
// Get or insert allocEntity library call definition.
|
|
auto allocEntityFuncTy =
|
|
LLVM::LLVMFunctionType::get(voidTy, {voidPtrTy, voidPtrTy, voidPtrTy});
|
|
auto allocEntityFunc = getOrInsertFunction(
|
|
module, rewriter, op->getLoc(), "allocEntity", allocEntityFuncTy);
|
|
|
|
Value initStatePtr = initFunc.getArgument(0);
|
|
|
|
// Get a builder for the init function.
|
|
OpBuilder initBuilder =
|
|
OpBuilder::atBlockTerminator(&initFunc.getBody().getBlocks().front());
|
|
|
|
// Use the instance name to retrieve the instance from the state.
|
|
auto ownerName = entity.getName().str() + "." + instOp.getName().str();
|
|
|
|
// Get or create owner name string
|
|
Value owner;
|
|
auto parentSym =
|
|
module.lookupSymbol<LLVM::GlobalOp>("instance." + ownerName);
|
|
if (!parentSym) {
|
|
owner = LLVM::createGlobalString(
|
|
op->getLoc(), initBuilder, "instance." + ownerName, ownerName + '\0',
|
|
LLVM::Linkage::Internal);
|
|
parentSym = module.lookupSymbol<LLVM::GlobalOp>("instance." + ownerName);
|
|
} else {
|
|
owner =
|
|
getGlobalString(op->getLoc(), initBuilder, typeConverter, parentSym);
|
|
}
|
|
|
|
// Handle entity instantiation.
|
|
if (auto child = module.lookupSymbol<EntityOp>(instOp.getCallee())) {
|
|
auto regStateTy = getRegStateTy(&getDialect(), child.getOperation());
|
|
|
|
// Get reg state size.
|
|
auto regNull = initBuilder.create<LLVM::ZeroOp>(op->getLoc(), voidPtrTy);
|
|
auto regGep =
|
|
initBuilder.create<LLVM::GEPOp>(op->getLoc(), voidPtrTy, regStateTy,
|
|
regNull, ArrayRef<LLVM::GEPArg>({1}));
|
|
auto regSize =
|
|
initBuilder.create<LLVM::PtrToIntOp>(op->getLoc(), i64Ty, regGep);
|
|
|
|
// Malloc reg state.
|
|
auto regMall = initBuilder
|
|
.create<LLVM::CallOp>(op->getLoc(), voidPtrTy,
|
|
SymbolRefAttr::get(mallFunc),
|
|
ArrayRef<Value>({regSize}))
|
|
.getResult();
|
|
auto zeroB = initBuilder.create<LLVM::ConstantOp>(
|
|
op->getLoc(), i1Ty, rewriter.getBoolAttr(false));
|
|
|
|
// Zero-initialize reg state entries.
|
|
for (size_t i = 0,
|
|
e = cast<LLVM::LLVMStructType>(regStateTy).getBody().size();
|
|
i < e; ++i) {
|
|
size_t f = cast<LLVM::LLVMArrayType>(
|
|
cast<LLVM::LLVMStructType>(regStateTy).getBody()[i])
|
|
.getNumElements();
|
|
for (size_t j = 0; j < f; ++j) {
|
|
auto regGep = initBuilder.create<LLVM::GEPOp>(
|
|
op->getLoc(), voidPtrTy, regStateTy, regMall,
|
|
ArrayRef<LLVM::GEPArg>({0, i, j}));
|
|
initBuilder.create<LLVM::StoreOp>(op->getLoc(), zeroB, regGep);
|
|
}
|
|
}
|
|
|
|
// Add reg state pointer to global state.
|
|
initBuilder.create<LLVM::CallOp>(
|
|
op->getLoc(), std::nullopt, SymbolRefAttr::get(allocEntityFunc),
|
|
ArrayRef<Value>({initStatePtr, owner, regMall}));
|
|
|
|
// Index of the signal in the entity's signal table.
|
|
int initCounter = 0;
|
|
// Walk over the entity and generate mallocs for each one of its signals.
|
|
WalkResult sigWalkResult = child.walk([&](SigOp op) -> WalkResult {
|
|
// if (auto sigOp = dyn_cast<SigOp>(op)) {
|
|
auto underlyingTy = typeConverter->convertType(op.getInit().getType());
|
|
// Get index constant of the signal in the entity's signal table.
|
|
auto indexConst = initBuilder.create<LLVM::ConstantOp>(
|
|
op.getLoc(), i32Ty, rewriter.getI32IntegerAttr(initCounter));
|
|
initCounter++;
|
|
|
|
// Clone and insert the operation that defines the signal's init
|
|
// operand (assmued to be a constant/array op)
|
|
IRMapping mapping;
|
|
Value initDef = recursiveCloneInit(initBuilder, mapping, op.getInit());
|
|
|
|
if (!initDef)
|
|
return WalkResult::interrupt();
|
|
|
|
Value initDefCast = typeConverter->materializeTargetConversion(
|
|
initBuilder, initDef.getLoc(),
|
|
typeConverter->convertType(initDef.getType()), initDef);
|
|
|
|
// Compute the required space to malloc.
|
|
auto twoC = initBuilder.create<LLVM::ConstantOp>(
|
|
op.getLoc(), i64Ty, rewriter.getI32IntegerAttr(2));
|
|
auto nullPtr = initBuilder.create<LLVM::ZeroOp>(op.getLoc(), voidPtrTy);
|
|
auto sizeGep = initBuilder.create<LLVM::GEPOp>(
|
|
op.getLoc(), voidPtrTy, underlyingTy, nullPtr,
|
|
ArrayRef<LLVM::GEPArg>({1}));
|
|
auto size =
|
|
initBuilder.create<LLVM::PtrToIntOp>(op.getLoc(), i64Ty, sizeGep);
|
|
// Malloc double the required space to make sure signal
|
|
// shifts do not segfault.
|
|
auto mallocSize =
|
|
initBuilder.create<LLVM::MulOp>(op.getLoc(), i64Ty, size, twoC);
|
|
std::array<Value, 1> margs({mallocSize});
|
|
auto mall =
|
|
initBuilder
|
|
.create<LLVM::CallOp>(op.getLoc(), voidPtrTy,
|
|
SymbolRefAttr::get(mallFunc), margs)
|
|
.getResult();
|
|
|
|
// Store the initial value.
|
|
initBuilder.create<LLVM::StoreOp>(op.getLoc(), initDefCast, mall);
|
|
|
|
// Get the amount of bytes required to represent an integer underlying
|
|
// type. Use the whole size of the type if not an integer.
|
|
Value passSize;
|
|
if (auto intTy = dyn_cast<IntegerType>(underlyingTy)) {
|
|
auto byteWidth = llvm::divideCeil(intTy.getWidth(), 8);
|
|
passSize = initBuilder.create<LLVM::ConstantOp>(
|
|
op.getLoc(), i64Ty, rewriter.getI64IntegerAttr(byteWidth));
|
|
} else {
|
|
passSize = size;
|
|
}
|
|
|
|
std::array<Value, 5> args(
|
|
{initStatePtr, indexConst, owner, mall, passSize});
|
|
auto sigIndex =
|
|
initBuilder
|
|
.create<LLVM::CallOp>(op.getLoc(), i32Ty,
|
|
SymbolRefAttr::get(sigFunc), args)
|
|
.getResult();
|
|
|
|
// Add structured underlying type information.
|
|
if (auto arrayTy = dyn_cast<LLVM::LLVMArrayType>(underlyingTy)) {
|
|
auto numElements = initBuilder.create<LLVM::ConstantOp>(
|
|
op.getLoc(), i32Ty,
|
|
rewriter.getI32IntegerAttr(arrayTy.getNumElements()));
|
|
|
|
// Get element size.
|
|
auto null = initBuilder.create<LLVM::ZeroOp>(op.getLoc(), voidPtrTy);
|
|
auto gepFirst = initBuilder.create<LLVM::GEPOp>(
|
|
op.getLoc(), voidPtrTy, arrayTy, null,
|
|
ArrayRef<LLVM::GEPArg>({0, 1}));
|
|
auto toInt = initBuilder.create<LLVM::PtrToIntOp>(op.getLoc(), i32Ty,
|
|
gepFirst);
|
|
|
|
// Add information to the state.
|
|
initBuilder.create<LLVM::CallOp>(
|
|
op.getLoc(), std::nullopt, SymbolRefAttr::get(addSigElemFunc),
|
|
ArrayRef<Value>({initStatePtr, sigIndex, toInt, numElements}));
|
|
} else if (auto structTy =
|
|
dyn_cast<LLVM::LLVMStructType>(underlyingTy)) {
|
|
auto null = initBuilder.create<LLVM::ZeroOp>(op.getLoc(), voidPtrTy);
|
|
for (size_t i = 0, e = structTy.getBody().size(); i < e; ++i) {
|
|
// Get pointer offset.
|
|
auto gepElem = initBuilder.create<LLVM::GEPOp>(
|
|
op.getLoc(), voidPtrTy, structTy, null,
|
|
ArrayRef<LLVM::GEPArg>({0, i}));
|
|
auto elemToInt = initBuilder.create<LLVM::PtrToIntOp>(
|
|
op.getLoc(), i32Ty, gepElem);
|
|
|
|
// Get element size.
|
|
auto elemNull =
|
|
initBuilder.create<LLVM::ZeroOp>(op.getLoc(), voidPtrTy);
|
|
auto gepElemSize = initBuilder.create<LLVM::GEPOp>(
|
|
op.getLoc(), voidPtrTy, structTy.getBody()[i], elemNull,
|
|
ArrayRef<LLVM::GEPArg>({1}));
|
|
auto elemSizeToInt = initBuilder.create<LLVM::PtrToIntOp>(
|
|
op.getLoc(), i32Ty, gepElemSize);
|
|
|
|
// Add information to the state.
|
|
initBuilder.create<LLVM::CallOp>(
|
|
op.getLoc(), std::nullopt, SymbolRefAttr::get(addSigStructFunc),
|
|
ArrayRef<Value>(
|
|
{initStatePtr, sigIndex, elemToInt, elemSizeToInt}));
|
|
}
|
|
}
|
|
return WalkResult::advance();
|
|
});
|
|
|
|
if (sigWalkResult.wasInterrupted())
|
|
return failure();
|
|
|
|
} else if (auto proc = module.lookupSymbol<ProcOp>(instOp.getCallee())) {
|
|
// Handle process instantiation.
|
|
auto sensesTy = LLVM::LLVMArrayType::get(i1Ty, proc.getNumArguments());
|
|
auto procStateTy = LLVM::LLVMStructType::getLiteral(
|
|
rewriter.getContext(),
|
|
{i32Ty, i32Ty, voidPtrTy /*ptr(sensesTy)*/,
|
|
getProcPersistenceTy(&getDialect(), typeConverter, proc)});
|
|
|
|
auto zeroC = initBuilder.create<LLVM::ConstantOp>(
|
|
op->getLoc(), i32Ty, rewriter.getI32IntegerAttr(0));
|
|
|
|
// Malloc space for the process state.
|
|
auto procStateNullPtr =
|
|
initBuilder.create<LLVM::ZeroOp>(op->getLoc(), voidPtrTy);
|
|
auto procStateGep = initBuilder.create<LLVM::GEPOp>(
|
|
op->getLoc(), voidPtrTy, procStateTy, procStateNullPtr,
|
|
ArrayRef<LLVM::GEPArg>({1}));
|
|
auto procStateSize = initBuilder.create<LLVM::PtrToIntOp>(
|
|
op->getLoc(), i64Ty, procStateGep);
|
|
std::array<Value, 1> procStateMArgs({procStateSize});
|
|
auto procStateMall = initBuilder
|
|
.create<LLVM::CallOp>(
|
|
op->getLoc(), voidPtrTy,
|
|
SymbolRefAttr::get(mallFunc), procStateMArgs)
|
|
.getResult();
|
|
|
|
// Store the initial resume index.
|
|
auto resumeGep = initBuilder.create<LLVM::GEPOp>(
|
|
op->getLoc(), voidPtrTy, procStateTy, procStateMall,
|
|
ArrayRef<LLVM::GEPArg>({0, 1}));
|
|
initBuilder.create<LLVM::StoreOp>(op->getLoc(), zeroC, resumeGep);
|
|
|
|
// Malloc space for the senses table.
|
|
auto sensesNullPtr =
|
|
initBuilder.create<LLVM::ZeroOp>(op->getLoc(), voidPtrTy);
|
|
auto sensesGep = initBuilder.create<LLVM::GEPOp>(
|
|
op->getLoc(), voidPtrTy, sensesTy, sensesNullPtr,
|
|
ArrayRef<LLVM::GEPArg>({1}));
|
|
auto sensesSize =
|
|
initBuilder.create<LLVM::PtrToIntOp>(op->getLoc(), i64Ty, sensesGep);
|
|
std::array<Value, 1> senseMArgs({sensesSize});
|
|
auto sensesMall =
|
|
initBuilder
|
|
.create<LLVM::CallOp>(op->getLoc(), voidPtrTy,
|
|
SymbolRefAttr::get(mallFunc), senseMArgs)
|
|
.getResult();
|
|
|
|
// Set all initial senses to 1.
|
|
auto oneB = initBuilder.create<LLVM::ConstantOp>(
|
|
op->getLoc(), i1Ty, rewriter.getBoolAttr(true));
|
|
for (size_t i = 0, e = sensesTy.getNumElements(); i < e; ++i) {
|
|
auto senseGep = initBuilder.create<LLVM::GEPOp>(
|
|
op->getLoc(), voidPtrTy, i1Ty, sensesMall,
|
|
ArrayRef<LLVM::GEPArg>({i}));
|
|
initBuilder.create<LLVM::StoreOp>(op->getLoc(), oneB, senseGep);
|
|
}
|
|
|
|
// Store the senses pointer in the process state.
|
|
auto procStateSensesPtr = initBuilder.create<LLVM::GEPOp>(
|
|
op->getLoc(), voidPtrTy, procStateTy, procStateMall,
|
|
ArrayRef<LLVM::GEPArg>({0, 2}));
|
|
initBuilder.create<LLVM::StoreOp>(op->getLoc(), sensesMall,
|
|
procStateSensesPtr);
|
|
|
|
std::array<Value, 3> allocProcArgs({initStatePtr, owner, procStateMall});
|
|
initBuilder.create<LLVM::CallOp>(op->getLoc(), std::nullopt,
|
|
SymbolRefAttr::get(allocProcFunc),
|
|
allocProcArgs);
|
|
}
|
|
|
|
rewriter.eraseOp(op);
|
|
return success();
|
|
}
|
|
};
|
|
} // namespace
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Signal conversions
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
namespace {
|
|
/// Convert an `llhd.sig` operation to LLVM dialect. The i-th signal of an
|
|
/// entity get's lowered to a load of the i-th element of the signal table,
|
|
/// passed as an argument.
|
|
struct SigOpConversion : public ConvertToLLVMPattern {
|
|
explicit SigOpConversion(MLIRContext *ctx, LLVMTypeConverter &typeConverter,
|
|
size_t &sigCounter)
|
|
: ConvertToLLVMPattern(llhd::SigOp::getOperationName(), ctx,
|
|
typeConverter),
|
|
sigCounter(sigCounter) {}
|
|
|
|
LogicalResult
|
|
matchAndRewrite(Operation *op, ArrayRef<Value> operands,
|
|
ConversionPatternRewriter &rewriter) const override {
|
|
// Get the adapted opreands.
|
|
SigOpAdaptor transformed(operands);
|
|
|
|
// Collect the used llvm types.
|
|
auto voidPtrTy = LLVM::LLVMPointerType::get(rewriter.getContext());
|
|
auto sigTy = getLLVMSigType(&getDialect());
|
|
|
|
// Get the signal table pointer from the arguments.
|
|
Value sigTablePtr = op->getParentOfType<LLVM::LLVMFuncOp>().getArgument(2);
|
|
|
|
// Get the index in the signal table and increase counter.
|
|
// Insert a gep to the signal index in the signal table argument.
|
|
rewriter.replaceOpWithNewOp<LLVM::GEPOp>(op, voidPtrTy, sigTy, sigTablePtr,
|
|
LLVM::GEPArg(sigCounter));
|
|
++sigCounter;
|
|
|
|
return success();
|
|
}
|
|
|
|
private:
|
|
size_t &sigCounter;
|
|
};
|
|
} // namespace
|
|
|
|
namespace {
|
|
/// Convert an `llhd.prb` operation to LLVM dialect. The result is a library
|
|
/// call to the
|
|
/// `@probe_signal` function. The signal details are then extracted and used to
|
|
/// load the final probe value.
|
|
struct PrbOpConversion : public ConvertToLLVMPattern {
|
|
explicit PrbOpConversion(MLIRContext *ctx, LLVMTypeConverter &typeConverter)
|
|
: ConvertToLLVMPattern(llhd::PrbOp::getOperationName(), ctx,
|
|
typeConverter) {}
|
|
|
|
LogicalResult
|
|
matchAndRewrite(Operation *op, ArrayRef<Value> operands,
|
|
ConversionPatternRewriter &rewriter) const override {
|
|
// Get the adapted operands.
|
|
PrbOpAdaptor transformed(operands);
|
|
// Get the prb operation.
|
|
auto prbOp = cast<PrbOp>(op);
|
|
|
|
// Collect the used llvm types.
|
|
auto resTy = prbOp.getType();
|
|
auto finalTy = typeConverter->convertType(resTy);
|
|
|
|
// Get the signal details from the signal struct.
|
|
auto sigDetail = getSignalDetail(rewriter, &getDialect(), op->getLoc(),
|
|
transformed.getSignal());
|
|
|
|
if (isa<IntegerType>(resTy)) {
|
|
// Get the amount of bytes to load. An extra byte is always loaded to
|
|
// cover the case where a subsignal spans halfway in the last byte.
|
|
int resWidth = resTy.getIntOrFloatBitWidth();
|
|
int loadWidth = (llvm::divideCeil(resWidth, 8) + 1) * 8;
|
|
auto loadTy = IntegerType::get(rewriter.getContext(), loadWidth);
|
|
|
|
auto loadSig =
|
|
rewriter.create<LLVM::LoadOp>(op->getLoc(), loadTy, sigDetail[0]);
|
|
|
|
// Shift the loaded value by the offset and truncate to the final width.
|
|
auto trOff = adjustBitWidth(op->getLoc(), rewriter, loadTy, sigDetail[1]);
|
|
auto shifted =
|
|
rewriter.create<LLVM::LShrOp>(op->getLoc(), loadTy, loadSig, trOff);
|
|
rewriter.replaceOpWithNewOp<LLVM::TruncOp>(op, finalTy, shifted);
|
|
|
|
return success();
|
|
}
|
|
|
|
if (isa<hw::ArrayType, hw::StructType>(resTy)) {
|
|
rewriter.replaceOpWithNewOp<LLVM::LoadOp>(op, finalTy, sigDetail[0]);
|
|
|
|
return success();
|
|
}
|
|
|
|
return failure();
|
|
}
|
|
};
|
|
} // namespace
|
|
|
|
namespace {
|
|
/// Convert an `llhd.drv` operation to LLVM dialect. The result is a library
|
|
/// call to the
|
|
/// `@driveSignal` function, which declaration is inserted at the beginning of
|
|
/// the module if missing. The required arguments are either generated or
|
|
/// fetched.
|
|
struct DrvOpConversion : public ConvertToLLVMPattern {
|
|
explicit DrvOpConversion(MLIRContext *ctx, LLVMTypeConverter &typeConverter)
|
|
: ConvertToLLVMPattern(llhd::DrvOp::getOperationName(), ctx,
|
|
typeConverter) {}
|
|
|
|
LogicalResult
|
|
matchAndRewrite(Operation *op, ArrayRef<Value> operands,
|
|
ConversionPatternRewriter &rewriter) const override {
|
|
// Get the adapted operands.
|
|
DrvOpAdaptor transformed(operands);
|
|
// Get the drive operation.
|
|
auto drvOp = cast<DrvOp>(op);
|
|
// Get the parent module.
|
|
auto module = op->getParentOfType<ModuleOp>();
|
|
|
|
// Collect used llvm types.
|
|
auto voidTy = getVoidType();
|
|
auto voidPtrTy = getVoidPtrType();
|
|
auto i1Ty = IntegerType::get(rewriter.getContext(), 1);
|
|
auto i32Ty = IntegerType::get(rewriter.getContext(), 32);
|
|
auto i64Ty = IntegerType::get(rewriter.getContext(), 64);
|
|
|
|
// Get or insert the drive library call.
|
|
auto drvFuncTy = LLVM::LLVMFunctionType::get(
|
|
voidTy, {voidPtrTy, voidPtrTy, voidPtrTy, i64Ty, i64Ty, i64Ty, i64Ty});
|
|
auto drvFunc = getOrInsertFunction(module, rewriter, op->getLoc(),
|
|
"driveSignal", drvFuncTy);
|
|
|
|
// Get the state pointer from the function arguments.
|
|
Value statePtr = op->getParentOfType<LLVM::LLVMFuncOp>().getArgument(0);
|
|
|
|
// Get signal width.
|
|
Value sigWidth;
|
|
auto underlyingTy = drvOp.getValue().getType();
|
|
if (isArrayOrStruct(underlyingTy)) {
|
|
auto underlyingTyConv = typeConverter->convertType(underlyingTy);
|
|
auto eightC = rewriter.create<LLVM::ConstantOp>(
|
|
op->getLoc(), i64Ty, rewriter.getI64IntegerAttr(8));
|
|
auto nullPtr = rewriter.create<LLVM::ZeroOp>(op->getLoc(), voidPtrTy);
|
|
auto gepOne = rewriter.create<LLVM::GEPOp>(op->getLoc(), voidPtrTy,
|
|
underlyingTyConv, nullPtr,
|
|
ArrayRef<LLVM::GEPArg>({1}));
|
|
auto toInt =
|
|
rewriter.create<LLVM::PtrToIntOp>(op->getLoc(), i64Ty, gepOne);
|
|
sigWidth = rewriter.create<LLVM::MulOp>(op->getLoc(), toInt, eightC);
|
|
} else {
|
|
sigWidth = rewriter.create<LLVM::ConstantOp>(
|
|
op->getLoc(), i64Ty,
|
|
rewriter.getI64IntegerAttr(underlyingTy.getIntOrFloatBitWidth()));
|
|
}
|
|
|
|
// Insert enable comparison. Skip if the enable operand is 0.
|
|
if (auto gate = drvOp.getEnable()) {
|
|
auto block = op->getBlock();
|
|
auto continueBlock =
|
|
rewriter.splitBlock(rewriter.getInsertionBlock(), op->getIterator());
|
|
auto drvBlock = rewriter.createBlock(continueBlock);
|
|
rewriter.setInsertionPointToEnd(drvBlock);
|
|
rewriter.create<LLVM::BrOp>(op->getLoc(), ValueRange(), continueBlock);
|
|
|
|
rewriter.setInsertionPointToEnd(block);
|
|
auto oneC = rewriter.create<LLVM::ConstantOp>(
|
|
op->getLoc(), i1Ty, rewriter.getI16IntegerAttr(1));
|
|
auto cmp = rewriter.create<LLVM::ICmpOp>(
|
|
op->getLoc(), LLVM::ICmpPredicate::eq, transformed.getEnable(), oneC);
|
|
rewriter.create<LLVM::CondBrOp>(op->getLoc(), cmp, drvBlock,
|
|
continueBlock);
|
|
|
|
rewriter.setInsertionPointToStart(drvBlock);
|
|
}
|
|
|
|
Type valTy = typeConverter->convertType(transformed.getValue().getType());
|
|
Value castVal = typeConverter->materializeTargetConversion(
|
|
rewriter, transformed.getValue().getLoc(), valTy,
|
|
transformed.getValue());
|
|
|
|
auto oneConst = rewriter.create<LLVM::ConstantOp>(
|
|
op->getLoc(), i32Ty, rewriter.getI32IntegerAttr(1));
|
|
|
|
// This assumes that alloca does always allocate full bytes (round up to a
|
|
// multiple of 8 bits).
|
|
auto alloca = rewriter.create<LLVM::AllocaOp>(op->getLoc(), voidPtrTy,
|
|
valTy, oneConst, 4);
|
|
rewriter.create<LLVM::StoreOp>(op->getLoc(), castVal, alloca);
|
|
|
|
// Get the time values.
|
|
auto realTime = rewriter.create<LLVM::ExtractValueOp>(
|
|
op->getLoc(), transformed.getTime(), 0);
|
|
auto delta = rewriter.create<LLVM::ExtractValueOp>(
|
|
op->getLoc(), transformed.getTime(), 1);
|
|
auto eps = rewriter.create<LLVM::ExtractValueOp>(op->getLoc(),
|
|
transformed.getTime(), 2);
|
|
|
|
// Define the driveSignal library call arguments.
|
|
std::array<Value, 7> args({statePtr, transformed.getSignal(), alloca,
|
|
sigWidth, realTime, delta, eps});
|
|
// Create the library call.
|
|
rewriter.create<LLVM::CallOp>(op->getLoc(), std::nullopt,
|
|
SymbolRefAttr::get(drvFunc), args);
|
|
|
|
rewriter.eraseOp(op);
|
|
return success();
|
|
}
|
|
};
|
|
} // namespace
|
|
|
|
namespace {
|
|
/// Convert an `llhd.reg` operation to LLVM dialect. This generates a series of
|
|
/// comparisons (blocks) that end up driving the signal with the arguments of
|
|
/// the first matching trigger from the trigger list.
|
|
struct RegOpConversion : public ConvertToLLVMPattern {
|
|
explicit RegOpConversion(MLIRContext *ctx, LLVMTypeConverter &typeConverter,
|
|
size_t ®Counter)
|
|
: ConvertToLLVMPattern(RegOp::getOperationName(), ctx, typeConverter),
|
|
regCounter(regCounter) {}
|
|
|
|
LogicalResult
|
|
matchAndRewrite(Operation *op, ArrayRef<Value> operands,
|
|
ConversionPatternRewriter &rewriter) const override {
|
|
auto regOp = cast<RegOp>(op);
|
|
RegOpAdaptor transformed(operands, op->getAttrDictionary());
|
|
|
|
auto voidPtrTy = LLVM::LLVMPointerType::get(rewriter.getContext());
|
|
auto i1Ty = IntegerType::get(rewriter.getContext(), 1);
|
|
auto func = op->getParentOfType<LLVM::LLVMFuncOp>();
|
|
|
|
// Retrieve and update previous trigger values for rising/falling edge
|
|
// detection.
|
|
size_t triggerIndex = 0;
|
|
SmallVector<Value, 4> prevTriggers;
|
|
for (int i = 0, e = regOp.getValues().size(); i < e; ++i) {
|
|
auto mode = regOp.getRegModeAt(i);
|
|
if (mode == RegMode::both || mode == RegMode::fall ||
|
|
mode == RegMode::rise) {
|
|
auto gep = rewriter.create<LLVM::GEPOp>(
|
|
op->getLoc(), voidPtrTy, i1Ty, func.getArgument(1),
|
|
ArrayRef<LLVM::GEPArg>({0, regCounter, triggerIndex++}));
|
|
prevTriggers.push_back(
|
|
rewriter.create<LLVM::LoadOp>(op->getLoc(), i1Ty, gep));
|
|
rewriter.create<LLVM::StoreOp>(op->getLoc(),
|
|
transformed.getTriggers()[i], gep);
|
|
}
|
|
}
|
|
|
|
// Create blocks for drive and continue.
|
|
auto block = op->getBlock();
|
|
auto continueBlock = block->splitBlock(op);
|
|
|
|
auto drvBlock = rewriter.createBlock(continueBlock);
|
|
auto valArg = drvBlock->addArgument(transformed.getValues()[0].getType(),
|
|
transformed.getValues()[0].getLoc());
|
|
auto delayArg = drvBlock->addArgument(transformed.getDelays()[0].getType(),
|
|
transformed.getDelays()[0].getLoc());
|
|
auto gateArg = drvBlock->addArgument(i1Ty, rewriter.getUnknownLoc());
|
|
|
|
// Create a drive with the block arguments.
|
|
rewriter.setInsertionPointToStart(drvBlock);
|
|
rewriter.create<DrvOp>(op->getLoc(), regOp.getSignal(), valArg, delayArg,
|
|
gateArg);
|
|
rewriter.create<LLVM::BrOp>(op->getLoc(), ValueRange(), continueBlock);
|
|
|
|
int j = prevTriggers.size() - 1;
|
|
// Create a comparison block for each of the reg tuples.
|
|
for (int i = regOp.getValues().size() - 1, e = i; i >= 0; --i) {
|
|
auto cmpBlock = rewriter.createBlock(block->getNextNode());
|
|
rewriter.setInsertionPointToStart(cmpBlock);
|
|
|
|
Value gate;
|
|
if (regOp.hasGate(i)) {
|
|
gate = regOp.getGateAt(i);
|
|
} else {
|
|
gate = rewriter.create<LLVM::ConstantOp>(op->getLoc(), i1Ty,
|
|
rewriter.getBoolAttr(true));
|
|
}
|
|
|
|
auto drvArgs = std::array<Value, 3>(
|
|
{transformed.getValues()[i], transformed.getDelays()[i], gate});
|
|
|
|
RegMode mode = regOp.getRegModeAt(i);
|
|
|
|
// Create comparison constants for all modes other than both.
|
|
Value rhs;
|
|
if (mode == RegMode::low || mode == RegMode::fall) {
|
|
rhs = rewriter.create<LLVM::ConstantOp>(op->getLoc(), i1Ty,
|
|
rewriter.getBoolAttr(false));
|
|
} else if (mode == RegMode::high || mode == RegMode::rise) {
|
|
rhs = rewriter.create<LLVM::ConstantOp>(op->getLoc(), i1Ty,
|
|
rewriter.getBoolAttr(true));
|
|
}
|
|
|
|
// Create comparison for non-both modes.
|
|
Value comp;
|
|
if (rhs)
|
|
comp =
|
|
rewriter.create<LLVM::ICmpOp>(op->getLoc(), LLVM::ICmpPredicate::eq,
|
|
transformed.getTriggers()[i], rhs);
|
|
|
|
// Create comparison for modes needing more than one state of the trigger.
|
|
Value brCond;
|
|
if (mode == RegMode::rise || mode == RegMode::fall ||
|
|
mode == RegMode::both) {
|
|
|
|
auto cmpPrev = rewriter.create<LLVM::ICmpOp>(
|
|
op->getLoc(), LLVM::ICmpPredicate::ne, transformed.getTriggers()[i],
|
|
prevTriggers[j--]);
|
|
if (mode == RegMode::both)
|
|
brCond = cmpPrev;
|
|
else
|
|
brCond =
|
|
rewriter.create<LLVM::AndOp>(op->getLoc(), i1Ty, comp, cmpPrev);
|
|
} else {
|
|
brCond = comp;
|
|
}
|
|
|
|
Block *nextBlock;
|
|
nextBlock = cmpBlock->getNextNode();
|
|
// Don't go to next block for last comparison's false branch (skip the
|
|
// drive block).
|
|
if (i == e)
|
|
nextBlock = continueBlock;
|
|
|
|
rewriter.create<LLVM::CondBrOp>(op->getLoc(), brCond, drvBlock, drvArgs,
|
|
nextBlock, ValueRange());
|
|
}
|
|
|
|
rewriter.setInsertionPointToEnd(block);
|
|
rewriter.create<LLVM::BrOp>(op->getLoc(), ArrayRef<Value>(),
|
|
block->getNextNode());
|
|
|
|
rewriter.eraseOp(op);
|
|
|
|
++regCounter;
|
|
|
|
return success();
|
|
}
|
|
|
|
private:
|
|
size_t ®Counter;
|
|
}; // namespace
|
|
} // namespace
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Value creation conversions
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
namespace {
|
|
/// Lower an LLHD constant operation to an equivalent LLVM dialect constant
|
|
/// operation.
|
|
struct ConstantTimeOpConversion : public ConvertToLLVMPattern {
|
|
explicit ConstantTimeOpConversion(MLIRContext *ctx,
|
|
LLVMTypeConverter &typeConverter)
|
|
: ConvertToLLVMPattern(llhd::ConstantTimeOp::getOperationName(), ctx,
|
|
typeConverter) {}
|
|
|
|
LogicalResult
|
|
matchAndRewrite(Operation *op, ArrayRef<Value> operand,
|
|
ConversionPatternRewriter &rewriter) const override {
|
|
// Get the ConstOp.
|
|
auto constOp = cast<ConstantTimeOp>(op);
|
|
// Get the constant's attribute.
|
|
TimeAttr timeAttr = constOp.getValueAttr();
|
|
// Handle the time const special case: create a new array containing the
|
|
// three time values.
|
|
auto timeTy = typeConverter->convertType(constOp.getResult().getType());
|
|
|
|
// Convert real-time element to ps.
|
|
llvm::StringMap<uint64_t> map = {
|
|
{"s", 12}, {"ms", 9}, {"us", 6}, {"ns", 3}, {"ps", 0}};
|
|
uint64_t adjusted =
|
|
std::pow(10, map[timeAttr.getTimeUnit()]) * timeAttr.getTime();
|
|
|
|
// Get sub-steps.
|
|
uint64_t delta = timeAttr.getDelta();
|
|
uint64_t eps = timeAttr.getEpsilon();
|
|
|
|
// Create time constant.
|
|
auto denseAttr =
|
|
DenseElementsAttr::get(RankedTensorType::get(3, rewriter.getI64Type()),
|
|
{adjusted, delta, eps});
|
|
rewriter.replaceOpWithNewOp<LLVM::ConstantOp>(op, timeTy, denseAttr);
|
|
return success();
|
|
}
|
|
};
|
|
} // namespace
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Extraction operation conversions
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
namespace {
|
|
/// Convert a DynExtractSliceOp to LLVM dialect.
|
|
struct SigArraySliceOpConversion
|
|
: public ConvertOpToLLVMPattern<llhd::SigArraySliceOp> {
|
|
using ConvertOpToLLVMPattern<llhd::SigArraySliceOp>::ConvertOpToLLVMPattern;
|
|
|
|
LogicalResult
|
|
matchAndRewrite(llhd::SigArraySliceOp op, OpAdaptor adaptor,
|
|
ConversionPatternRewriter &rewriter) const override {
|
|
|
|
Type llvmArrTy = typeConverter->convertType(op.getInputArrayType());
|
|
Type inputTy = typeConverter->convertType(op.getInput().getType());
|
|
Type lowIndexTy = typeConverter->convertType(op.getLowIndex().getType());
|
|
Value castInput = typeConverter->materializeTargetConversion(
|
|
rewriter, op->getLoc(), inputTy, op.getInput());
|
|
Value castLowIndex = typeConverter->materializeTargetConversion(
|
|
rewriter, op->getLoc(), lowIndexTy, op.getLowIndex());
|
|
|
|
auto sigDetail = getSignalDetail(rewriter, &getDialect(), op->getLoc(),
|
|
castInput, /*extractIndices=*/true);
|
|
|
|
auto adjustedPtr = shiftArraySigPointer(op->getLoc(), rewriter, llvmArrTy,
|
|
sigDetail[0], castLowIndex);
|
|
rewriter.replaceOp(op, createSubSig(&getDialect(), rewriter, op->getLoc(),
|
|
sigDetail, adjustedPtr, sigDetail[1]));
|
|
return success();
|
|
}
|
|
};
|
|
} // namespace
|
|
|
|
namespace {
|
|
/// Convert a DynExtractSliceOp to LLVM dialect.
|
|
struct SigExtractOpConversion
|
|
: public ConvertOpToLLVMPattern<llhd::SigExtractOp> {
|
|
using ConvertOpToLLVMPattern<llhd::SigExtractOp>::ConvertOpToLLVMPattern;
|
|
|
|
LogicalResult
|
|
matchAndRewrite(llhd::SigExtractOp op, OpAdaptor adaptor,
|
|
ConversionPatternRewriter &rewriter) const override {
|
|
|
|
Type inputTy = typeConverter->convertType(op.getInput().getType());
|
|
Type lowBitTy = typeConverter->convertType(op.getLowBit().getType());
|
|
Value castInput = typeConverter->materializeTargetConversion(
|
|
rewriter, op->getLoc(), inputTy, op.getInput());
|
|
Value castLowBit = typeConverter->materializeTargetConversion(
|
|
rewriter, op->getLoc(), lowBitTy, op.getLowBit());
|
|
|
|
auto sigDetail = getSignalDetail(rewriter, &getDialect(), op->getLoc(),
|
|
castInput, /*extractIndices=*/true);
|
|
|
|
auto zextStart = adjustBitWidth(op->getLoc(), rewriter,
|
|
rewriter.getI64Type(), castLowBit);
|
|
// Adjust the slice starting point by the signal's offset.
|
|
auto adjustedStart =
|
|
rewriter.create<LLVM::AddOp>(op->getLoc(), sigDetail[1], zextStart);
|
|
|
|
auto adjusted = shiftIntegerSigPointer(
|
|
op->getLoc(), &getDialect(), rewriter, sigDetail[0], adjustedStart);
|
|
// Create a new subsignal with the new pointer and offset.
|
|
rewriter.replaceOp(op, createSubSig(&getDialect(), rewriter, op->getLoc(),
|
|
sigDetail, adjusted.first,
|
|
adjusted.second));
|
|
return success();
|
|
}
|
|
};
|
|
} // namespace
|
|
|
|
namespace {
|
|
/// Convert
|
|
struct SigStructExtractOpConversion
|
|
: public ConvertOpToLLVMPattern<llhd::SigStructExtractOp> {
|
|
using ConvertOpToLLVMPattern<
|
|
llhd::SigStructExtractOp>::ConvertOpToLLVMPattern;
|
|
|
|
LogicalResult
|
|
matchAndRewrite(llhd::SigStructExtractOp op, OpAdaptor adaptor,
|
|
ConversionPatternRewriter &rewriter) const override {
|
|
|
|
Type llvmStructTy = typeConverter->convertType(op.getStructType());
|
|
Type inputTy = typeConverter->convertType(op.getInput().getType());
|
|
Value castInput = typeConverter->materializeTargetConversion(
|
|
rewriter, op->getLoc(), inputTy, op.getInput());
|
|
|
|
std::vector<Value> sigDetail =
|
|
getSignalDetail(rewriter, &getDialect(), op->getLoc(), castInput,
|
|
/*extractIndices=*/true);
|
|
|
|
uint32_t index = HWToLLVMEndianessConverter::llvmIndexOfStructField(
|
|
op.getStructType(), op.getField());
|
|
|
|
Value adjusted = shiftStructuredSigPointer(
|
|
op->getLoc(), rewriter, llvmStructTy, sigDetail[0], index);
|
|
|
|
rewriter.replaceOp(op, createSubSig(&getDialect(), rewriter, op->getLoc(),
|
|
sigDetail, adjusted, sigDetail[1]));
|
|
|
|
return success();
|
|
}
|
|
};
|
|
} // namespace
|
|
|
|
namespace {
|
|
/// Convert a DynExtractElementOp to LLVM dialect.
|
|
struct SigArrayGetOpConversion
|
|
: public ConvertOpToLLVMPattern<llhd::SigArrayGetOp> {
|
|
using ConvertOpToLLVMPattern<llhd::SigArrayGetOp>::ConvertOpToLLVMPattern;
|
|
|
|
LogicalResult
|
|
matchAndRewrite(llhd::SigArrayGetOp op, OpAdaptor adaptor,
|
|
ConversionPatternRewriter &rewriter) const override {
|
|
|
|
auto llvmArrTy = typeConverter->convertType(op.getArrayType());
|
|
Type inputTy = typeConverter->convertType(op.getInput().getType());
|
|
Type indexTy = typeConverter->convertType(op.getIndex().getType());
|
|
Value castInput = typeConverter->materializeTargetConversion(
|
|
rewriter, op->getLoc(), inputTy, op.getInput());
|
|
Value castIndex = typeConverter->materializeTargetConversion(
|
|
rewriter, op->getLoc(), indexTy, op.getIndex());
|
|
|
|
auto sigDetail = getSignalDetail(rewriter, &getDialect(), op->getLoc(),
|
|
castInput, /*extractIndices=*/true);
|
|
|
|
auto adjustedPtr = shiftArraySigPointer(op->getLoc(), rewriter, llvmArrTy,
|
|
sigDetail[0], castIndex);
|
|
rewriter.replaceOp(op, createSubSig(&getDialect(), rewriter, op->getLoc(),
|
|
sigDetail, adjustedPtr, sigDetail[1]));
|
|
|
|
return success();
|
|
}
|
|
};
|
|
} // namespace
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Memory operations
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
namespace {
|
|
/// Lower a `llhd.var` operation to the LLVM dialect. This results in an alloca,
|
|
/// followed by storing the initial value.
|
|
struct VarOpConversion : ConvertToLLVMPattern {
|
|
explicit VarOpConversion(MLIRContext *ctx, LLVMTypeConverter &typeConverter)
|
|
: ConvertToLLVMPattern(VarOp::getOperationName(), ctx, typeConverter) {}
|
|
|
|
LogicalResult
|
|
matchAndRewrite(Operation *op, ArrayRef<Value> operands,
|
|
ConversionPatternRewriter &rewriter) const override {
|
|
VarOpAdaptor transformed(operands);
|
|
|
|
auto i32Ty = IntegerType::get(rewriter.getContext(), 32);
|
|
Type initTy = typeConverter->convertType(transformed.getInit().getType());
|
|
|
|
auto oneC = rewriter.create<LLVM::ConstantOp>(
|
|
op->getLoc(), i32Ty, rewriter.getI32IntegerAttr(1));
|
|
auto alloca = rewriter.create<LLVM::AllocaOp>(
|
|
op->getLoc(), LLVM::LLVMPointerType::get(rewriter.getContext()), initTy,
|
|
oneC, 4);
|
|
rewriter.create<LLVM::StoreOp>(op->getLoc(), transformed.getInit(), alloca);
|
|
rewriter.replaceOp(op, alloca.getResult());
|
|
return success();
|
|
}
|
|
};
|
|
} // namespace
|
|
|
|
namespace {
|
|
/// Convert an `llhd.store` operation to LLVM. This lowers the store
|
|
/// one-to-one as an LLVM store, but with the operands flipped.
|
|
struct StoreOpConversion : ConvertToLLVMPattern {
|
|
explicit StoreOpConversion(MLIRContext *ctx, LLVMTypeConverter &typeConverter)
|
|
: ConvertToLLVMPattern(llhd::StoreOp::getOperationName(), ctx,
|
|
typeConverter) {}
|
|
|
|
LogicalResult
|
|
matchAndRewrite(Operation *op, ArrayRef<Value> operands,
|
|
ConversionPatternRewriter &rewriter) const override {
|
|
llhd::StoreOpAdaptor transformed(operands);
|
|
|
|
rewriter.replaceOpWithNewOp<LLVM::StoreOp>(op, transformed.getValue(),
|
|
transformed.getPointer());
|
|
|
|
return success();
|
|
}
|
|
};
|
|
} // namespace
|
|
|
|
using LoadOpConversion =
|
|
OneToOneConvertToLLVMPattern<llhd::LoadOp, LLVM::LoadOp>;
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Pass initialization
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
namespace {
|
|
struct LLHDToLLVMLoweringPass
|
|
: public circt::impl::ConvertLLHDToLLVMBase<LLHDToLLVMLoweringPass> {
|
|
void runOnOperation() override;
|
|
};
|
|
} // namespace
|
|
|
|
void circt::populateLLHDToLLVMConversionPatterns(LLVMTypeConverter &converter,
|
|
RewritePatternSet &patterns,
|
|
size_t &sigCounter,
|
|
size_t ®Counter) {
|
|
MLIRContext *ctx = converter.getDialect()->getContext();
|
|
|
|
// Value creation conversion patterns.
|
|
patterns.add<ConstantTimeOpConversion>(ctx, converter);
|
|
|
|
// Extract conversion patterns.
|
|
patterns.add<SigExtractOpConversion, SigArraySliceOpConversion,
|
|
SigArrayGetOpConversion, SigStructExtractOpConversion>(
|
|
converter);
|
|
|
|
// Unit conversion patterns.
|
|
patterns.add<ProcOpConversion, WaitOpConversion, HaltOpConversion>(ctx,
|
|
converter);
|
|
patterns.add<EntityOpConversion>(ctx, converter, sigCounter, regCounter);
|
|
|
|
// Signal conversion patterns.
|
|
patterns.add<PrbOpConversion, DrvOpConversion>(ctx, converter);
|
|
patterns.add<SigOpConversion>(ctx, converter, sigCounter);
|
|
patterns.add<RegOpConversion>(ctx, converter, regCounter);
|
|
|
|
// Memory conversion patterns.
|
|
patterns.add<VarOpConversion, StoreOpConversion>(ctx, converter);
|
|
patterns.add<LoadOpConversion>(converter);
|
|
}
|
|
|
|
void circt::populateLLHDToLLVMTypeConversions(LLVMTypeConverter &converter) {
|
|
converter.addConversion(
|
|
[&](SigType sig) { return convertSigType(sig, converter); });
|
|
converter.addConversion(
|
|
[&](TimeType time) { return convertTimeType(time, converter); });
|
|
converter.addConversion(
|
|
[&](PtrType ptr) { return convertPtrType(ptr, converter); });
|
|
}
|
|
|
|
void LLHDToLLVMLoweringPass::runOnOperation() {
|
|
Namespace globals;
|
|
SymbolCache cache;
|
|
cache.addDefinitions(getOperation());
|
|
globals.add(cache);
|
|
|
|
// Keep a counter to infer a signal's index in his entity's signal table.
|
|
size_t sigCounter = 0;
|
|
|
|
// Keep a counter to infer a reg's index in his entity.
|
|
size_t regCounter = 0;
|
|
|
|
RewritePatternSet patterns(&getContext());
|
|
auto converter = mlir::LLVMTypeConverter(&getContext());
|
|
populateLLHDToLLVMTypeConversions(converter);
|
|
|
|
// Also populate with HW type conversions
|
|
populateHWToLLVMTypeConversions(converter);
|
|
|
|
// Apply a partial conversion first, lowering only the instances, to generate
|
|
// the init function.
|
|
patterns.add<InstOpConversion>(&getContext(), converter);
|
|
|
|
LLVMConversionTarget target(getContext());
|
|
target.addIllegalOp<InstOp>();
|
|
cf::populateControlFlowToLLVMConversionPatterns(converter, patterns);
|
|
arith::populateArithToLLVMConversionPatterns(converter, patterns);
|
|
|
|
// Apply the partial conversion.
|
|
if (failed(
|
|
applyPartialConversion(getOperation(), target, std::move(patterns))))
|
|
return signalPassFailure();
|
|
patterns.clear();
|
|
|
|
// Setup the full conversion.
|
|
populateFuncToLLVMConversionPatterns(converter, patterns);
|
|
populateLLHDToLLVMConversionPatterns(converter, patterns, sigCounter,
|
|
regCounter);
|
|
|
|
// Populate with HW and Comb conversion patterns
|
|
DenseMap<std::pair<Type, ArrayAttr>, LLVM::GlobalOp> constAggregateGlobalsMap;
|
|
populateHWToLLVMConversionPatterns(converter, patterns, globals,
|
|
constAggregateGlobalsMap);
|
|
populateCombToLLVMConversionPatterns(converter, patterns);
|
|
populateCombToArithConversionPatterns(converter, patterns);
|
|
arith::populateArithToLLVMConversionPatterns(converter, patterns);
|
|
|
|
target.addLegalOp<ModuleOp>();
|
|
|
|
if (failed(applyFullConversion(getOperation(), target, std::move(patterns))))
|
|
return signalPassFailure();
|
|
}
|
|
|
|
/// Create an LLHD to LLVM conversion pass.
|
|
std::unique_ptr<OperationPass<ModuleOp>> circt::createConvertLLHDToLLVMPass() {
|
|
return std::make_unique<LLHDToLLVMLoweringPass>();
|
|
}
|