[LegalizeHLSCpp] Support to create AXI interfaces as an optional transform; [QoREstimation][EmitHLSCpp] Support memref CopyOp; [DSE] Move applyLegalizeToHLSCpp to the begining of the pass

This commit is contained in:
Hanchen Ye 2022-03-07 22:39:51 -06:00
parent ad2bf8b7c6
commit 626cfea264
9 changed files with 80 additions and 20 deletions

View File

@ -47,10 +47,10 @@ public:
// Memref-related statements. // Memref-related statements.
memref::AllocOp, memref::AllocaOp, memref::LoadOp, memref::StoreOp, memref::AllocOp, memref::AllocaOp, memref::LoadOp, memref::StoreOp,
memref::DeallocOp, memref::TensorStoreOp, tensor::ReshapeOp, memref::DeallocOp, memref::CopyOp, memref::TensorStoreOp,
memref::ReshapeOp, memref::CollapseShapeOp, memref::ExpandShapeOp, tensor::ReshapeOp, memref::ReshapeOp, memref::CollapseShapeOp,
memref::ReinterpretCastOp, bufferization::ToMemrefOp, memref::ExpandShapeOp, memref::ReinterpretCastOp,
bufferization::ToTensorOp, bufferization::ToMemrefOp, bufferization::ToTensorOp,
// HLSCpp primitive operations. // HLSCpp primitive operations.
MulPrimOp, CastPrimOp, AssignOp, MulPrimOp, CastPrimOp, AssignOp,
@ -135,6 +135,7 @@ public:
HANDLE(memref::LoadOp); HANDLE(memref::LoadOp);
HANDLE(memref::StoreOp); HANDLE(memref::StoreOp);
HANDLE(memref::DeallocOp); HANDLE(memref::DeallocOp);
HANDLE(memref::CopyOp);
HANDLE(memref::TensorStoreOp); HANDLE(memref::TensorStoreOp);
HANDLE(tensor::ReshapeOp); HANDLE(tensor::ReshapeOp);
HANDLE(memref::ReshapeOp); HANDLE(memref::ReshapeOp);

View File

@ -46,6 +46,9 @@ def MultipleLevelDSE : Pass<"dse", "ModuleOp"> {
let options = [ let options = [
Option<"topFunc", "top-func", "std::string", /*default=*/"\"main\"", Option<"topFunc", "top-func", "std::string", /*default=*/"\"main\"",
"The top function for HLS synthesis">, "The top function for HLS synthesis">,
Option<"axiInterf", "axi-interf", "bool", /*default=*/"false",
"Whether to create AXI interfaces for the top function">,
Option<"outputPath", "output-path", "std::string", Option<"outputPath", "output-path", "std::string",
/*default=*/"\"./\"", /*default=*/"\"./\"",
"File path: the path for dumping the MLIR of pareto design points">, "File path: the path for dumping the MLIR of pareto design points">,
@ -155,7 +158,9 @@ def LegalizeToHLSCpp : Pass<"legalize-to-hlscpp", "FuncOp"> {
let options = [ let options = [
Option<"topFunc", "top-func", "std::string", /*default=*/"\"main\"", Option<"topFunc", "top-func", "std::string", /*default=*/"\"main\"",
"The top function for HLS synthesis"> "The top function for HLS synthesis">,
Option<"axiInterf", "axi-interf", "bool", /*default=*/"false",
"Whether to create AXI interfaces for the top function">
]; ];
} }

View File

@ -63,6 +63,10 @@ public:
bool visitOp(memref::StoreOp op, int64_t begin) { bool visitOp(memref::StoreOp op, int64_t begin) {
return setTiming(op, begin, begin + 1, 1, 1), true; return setTiming(op, begin, begin + 1, 1, 1), true;
} }
bool visitOp(memref::CopyOp op, int64_t begin) {
auto type = op.target().getType().cast<MemRefType>();
return setTiming(op, begin, begin + type.getNumElements(), 1, 1), true;
}
/// Handle operations with profiled latency. /// Handle operations with profiled latency.
#define HANDLE(OPTYPE, KEYNAME) \ #define HANDLE(OPTYPE, KEYNAME) \

View File

@ -69,7 +69,7 @@ bool applyRemoveVariableBound(AffineLoopBand &band);
/// the innermost loop with the original loop order. /// the innermost loop with the original loop order.
bool applyLoopTiling(AffineLoopBand &band, TileList tileList); bool applyLoopTiling(AffineLoopBand &band, TileList tileList);
bool applyLegalizeToHLSCpp(FuncOp func, bool topFunc); bool applyLegalizeToHLSCpp(FuncOp func, bool topFunc, bool axiInterf = false);
/// Apply loop pipelining to the pipelineLoc of the input loop band, all inner /// Apply loop pipelining to the pipelineLoc of the input loop band, all inner
/// loops are automatically fully unrolled. /// loops are automatically fully unrolled.

View File

@ -54,7 +54,8 @@ struct MemrefStoreRewritePattern : public OpRewritePattern<memref::StoreOp> {
}; };
} // namespace } // namespace
bool scalehls::applyLegalizeToHLSCpp(FuncOp func, bool isTopFunc) { bool scalehls::applyLegalizeToHLSCpp(FuncOp func, bool isTopFunc,
bool axiInterf) {
auto builder = OpBuilder(func); auto builder = OpBuilder(func);
// We constrain functions to only contain one block. // We constrain functions to only contain one block.
@ -72,6 +73,38 @@ bool scalehls::applyLegalizeToHLSCpp(FuncOp func, bool isTopFunc) {
setParallel(loop); setParallel(loop);
}); });
if (axiInterf) {
auto loc = builder.getUnknownLoc();
// Convert each argument memory kind to DRAM and buffer each of them.
for (auto arg : func.getArguments()) {
if (auto type = arg.getType().dyn_cast<MemRefType>()) {
arg.setType(MemRefType::get(type.getShape(), type.getElementType(),
type.getLayout().getAffineMap(),
(unsigned)MemoryKind::DRAM));
// Allocate an on-chip buffer and create a copy from DRAM to the buffer.
builder.setInsertionPointToStart(&func.front());
auto buf = builder.create<memref::AllocOp>(loc, type);
arg.replaceAllUsesWith(buf);
builder.create<memref::CopyOp>(loc, arg, buf);
// If the buffer's state is written in the function, create a copy from
// the buffer to DRAM.
if (llvm::any_of(buf->getUsers(), [](Operation *op) {
return isa<AffineWriteOpInterface, memref::StoreOp>(op);
})) {
builder.setInsertionPoint(func.back().getTerminator());
builder.create<memref::CopyOp>(loc, buf, arg);
}
}
}
// Finally, update the type of the function.
func.setType(builder.getFunctionType(func.front().getArgumentTypes(),
func.getResultTypes()));
}
// Insert AssignOp when an arguments or result of ConstantOp are directly // Insert AssignOp when an arguments or result of ConstantOp are directly
// connected to ReturnOp. // connected to ReturnOp.
auto returnOp = func.front().getTerminator(); auto returnOp = func.front().getTerminator();
@ -108,7 +141,8 @@ struct LegalizeToHLSCpp : public LegalizeToHLSCppBase<LegalizeToHLSCpp> {
void runOnOperation() override { void runOnOperation() override {
auto func = getOperation(); auto func = getOperation();
applyLegalizeToHLSCpp(func, func.getName() == topFunc); auto isTop = func.getName() == topFunc;
applyLegalizeToHLSCpp(func, isTop, isTop && axiInterf);
} }
}; };
} // namespace } // namespace

View File

@ -889,7 +889,9 @@ struct MultipleLevelDSE : public MultipleLevelDSEBase<MultipleLevelDSE> {
// Optimize the top function. // Optimize the top function.
// TODO: Handle sub-functions and dataflowed or pipelined functions. // TODO: Handle sub-functions and dataflowed or pipelined functions.
for (auto func : module.getOps<FuncOp>()) { for (auto func : module.getOps<FuncOp>()) {
if (func.getName() == topFunc) auto isTop = func.getName() == topFunc;
applyLegalizeToHLSCpp(func, isTop, isTop && axiInterf);
if (isTop)
optimizer.applyMultipleLevelDSE(func, directiveOnly, outputPath, optimizer.applyMultipleLevelDSE(func, directiveOnly, outputPath,
csvPath); csvPath);
} }

View File

@ -668,7 +668,8 @@ TimingAttr ScaleHLSEstimator::estimateBlock(Block &block, int64_t begin) {
// If either the depOp or the current operation is a function call, // If either the depOp or the current operation is a function call,
// dependency exists and the schedule level should be updated. // dependency exists and the schedule level should be updated.
if (isa<CallOp>(op) || isa<CallOp>(depOp)) { if (isa<CallOp, memref::CopyOp>(op) ||
isa<CallOp, memref::CopyOp>(depOp)) {
opBegin = max(opBegin, depOpEnd); opBegin = max(opBegin, depOpEnd);
continue; continue;
} }

View File

@ -146,9 +146,6 @@ bool scalehls::applyOptStrategy(AffineLoopBand &band, FuncOp func,
if (!func->isProperAncestor(band.front())) if (!func->isProperAncestor(band.front()))
return false; return false;
// Apply legalization. TODO: Only support top-function for now.
applyLegalizeToHLSCpp(func, /*isTopFunc=*/true);
// Apply loop tiling. // Apply loop tiling.
if (!applyLoopTiling(band, tileList)) if (!applyLoopTiling(band, tileList))
return false; return false;
@ -172,9 +169,6 @@ bool scalehls::applyOptStrategy(FuncOp func, ArrayRef<TileList> tileLists,
assert(bands.size() == tileLists.size() && bands.size() == targetIIs.size() && assert(bands.size() == tileLists.size() && bands.size() == targetIIs.size() &&
"unexpected size of tile lists or target IIs"); "unexpected size of tile lists or target IIs");
// Apply legalization. TODO: Only support top-function for now.
applyLegalizeToHLSCpp(func, /*isTopFunc=*/true);
// Apply loop tiling to all loop bands. // Apply loop tiling to all loop bands.
for (unsigned i = 0, e = bands.size(); i < e; ++i) for (unsigned i = 0, e = bands.size(); i < e; ++i)
if (!applyLoopTiling(bands[i], tileLists[i])) if (!applyLoopTiling(bands[i], tileLists[i]))

View File

@ -218,6 +218,7 @@ public:
template <typename OpType> void emitAlloc(OpType op); template <typename OpType> void emitAlloc(OpType op);
void emitLoad(memref::LoadOp op); void emitLoad(memref::LoadOp op);
void emitStore(memref::StoreOp op); void emitStore(memref::StoreOp op);
void emitMemCpy(memref::CopyOp op);
void emitTensorStore(memref::TensorStoreOp op); void emitTensorStore(memref::TensorStoreOp op);
template <typename OpType> void emitReshape(OpType op); template <typename OpType> void emitReshape(OpType op);
void emitTensorToMemref(bufferization::ToMemrefOp op); void emitTensorToMemref(bufferization::ToMemrefOp op);
@ -400,6 +401,7 @@ public:
bool visitOp(memref::LoadOp op) { return emitter.emitLoad(op), true; } bool visitOp(memref::LoadOp op) { return emitter.emitLoad(op), true; }
bool visitOp(memref::StoreOp op) { return emitter.emitStore(op), true; } bool visitOp(memref::StoreOp op) { return emitter.emitStore(op), true; }
bool visitOp(memref::DeallocOp op) { return true; } bool visitOp(memref::DeallocOp op) { return true; }
bool visitOp(memref::CopyOp op) { return emitter.emitMemCpy(op), true; }
bool visitOp(memref::TensorStoreOp op) { bool visitOp(memref::TensorStoreOp op) {
return emitter.emitTensorStore(op), true; return emitter.emitTensorStore(op), true;
} }
@ -1180,6 +1182,20 @@ void ModuleEmitter::emitStore(memref::StoreOp op) {
emitInfoAndNewLine(op); emitInfoAndNewLine(op);
} }
void ModuleEmitter::emitMemCpy(memref::CopyOp op) {
indent() << "memcpy(";
emitValue(op.target());
os << ", ";
emitValue(op.source());
os << ", ";
auto type = op.target().getType().cast<MemRefType>();
os << type.getNumElements() << " * sizeof(" << getTypeName(op.target())
<< "));";
emitInfoAndNewLine(op);
os << "\n";
}
void ModuleEmitter::emitTensorStore(memref::TensorStoreOp op) { void ModuleEmitter::emitTensorStore(memref::TensorStoreOp op) {
auto rank = emitNestedLoopHeader(op.getOperand(0)); auto rank = emitNestedLoopHeader(op.getOperand(0));
indent(); indent();
@ -1656,9 +1672,11 @@ void ModuleEmitter::emitFunctionDirectives(FuncOp func,
indent(); indent();
os << "#pragma HLS interface"; os << "#pragma HLS interface";
// For now, we set the offset of all m_axi interfaces as slave. // For now, we set the offset of all m_axi interfaces as slave.
if (MemoryKind(memrefType.getMemorySpaceAsInt()) == MemoryKind::DRAM) if (MemoryKind(memrefType.getMemorySpaceAsInt()) ==
os << " m_axi offset=slave"; MemoryKind::DRAM) {
else os << " m_axi offset=slave bundle=";
emitValue(port);
} else
os << " bram"; os << " bram";
os << " port="; os << " port=";
@ -1722,7 +1740,7 @@ void ModuleEmitter::emitFunction(FuncOp func) {
if (auto resource = getResource(func)) { if (auto resource = getResource(func)) {
os << "/// DSP=" << resource.getDsp(); os << "/// DSP=" << resource.getDsp();
// os << ", BRAM=" << resource.getBram(); os << ", BRAM=" << resource.getBram();
// os << ", LUT=" << resource.getLut(); // os << ", LUT=" << resource.getLut();
os << "\n"; os << "\n";
} }
@ -1799,6 +1817,7 @@ void ModuleEmitter::emitModule(ModuleOp module) {
#include <hls_stream.h> #include <hls_stream.h>
#include <math.h> #include <math.h>
#include <stdint.h> #include <stdint.h>
#include <string.h>
using namespace std; using namespace std;