[Handshake] Re-implemented handshake-runner using mlir interface (#332)

Re-implemented handshake-runner using mlir interface to handshake ops:

1. Added memory interface for memory ops to allocate memory

2. Added executable interface for general ops to execute

3. Added try execute interface for elastic ops to check the precondition to execute and execute if the precondition holds
This commit is contained in:
Jianyi Cheng 2021-01-14 02:04:25 -08:00 committed by GitHub
parent 74f3d040f1
commit c9f53f0f01
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 1330 additions and 1011 deletions

View File

@ -53,6 +53,62 @@ def MergeLikeOpInterface : OpInterface<"MergeLikeOpInterface"> {
}];
}
def GeneralOpInterface : OpInterface<"GeneralOpInterface"> {
let description =
[{"Simulate the Execution of ops. The op takes a set of input values and "
"returns the corresponding outputs assuming the precondition to "
"execute holds."}];
let methods = [
InterfaceMethod<
"Simulate the Execution of the general op with given inputs", "void",
"execute",
(ins "std::vector<llvm::Any> &" : $ins,
"std::vector<llvm::Any> &" : $outs)>,
];
}
def ExecutableOpInterface : OpInterface<"ExecutableOpInterface"> {
let description = [{"Simulate the Execution of ops"}];
let methods = [
InterfaceMethod<
"The op checks its precondition to execute. If the precondition holds, "
"the op executes with the given input values and returns zero. "
"Otherwise, it returns a false value to suggest the simulator to "
"reschedule. The execution interface of hpx ops take the following "
"arguments: "
"bool = returns whether the op has been executed;"
"valueMap = a map of all values in the code;"
"memoryMap = a map of memory ops to simualte;"
"timeMap = a map of the last arrival time of all values;"
"store = The store associates each allocation in the program"
"(represented by a int) with a vector of values which can be"
"accessed by it."
"scheduleList = a list of values to be scheduled.",
"bool", "tryExecute",
(ins "llvm::DenseMap<mlir::Value, llvm::Any> &" : $valueMap,
"llvm::DenseMap<unsigned, unsigned> &" : $memoryMap,
"llvm::DenseMap<mlir::Value, double> &" : $timeMap,
"std::vector<std::vector<llvm::Any>> &" : $store,
"std::vector<mlir::Value> &" : $scheduleList)>,
];
}
def MemoryOpInterface : OpInterface<"MemoryOpInterface"> {
let description =
[{"Allocate the memory to the meory map in the simulation. "}];
let methods = [
InterfaceMethod<
"Simulate the memory allocation in the memoryMap", "bool",
"allocateMemory",
(ins "llvm::DenseMap<unsigned, unsigned> &" : $memoryMap,
"std::vector<std::vector<llvm::Any>> &" : $store,
"std::vector<double> &" : $storeTimes)>,
];
}
def HasClock : NativeOpTrait<"HasClock">;
#endif // HANDSHAKE_INTERFACES

View File

@ -26,6 +26,7 @@
#include "mlir/IR/Types.h"
#include "mlir/Interfaces/SideEffectInterfaces.h"
#include "mlir/Pass/Pass.h"
#include "llvm/ADT/Any.h"
namespace circt {
namespace handshake {

View File

@ -268,7 +268,10 @@ def BufferOp : Handshake_Op<"buffer", [NoSideEffect, HasClock]> {
}];
}
def ForkOp : Handshake_Op<"fork", [NoSideEffect, HasClock]> {
def ForkOp : Handshake_Op<"fork", [
NoSideEffect, DeclareOpInterfaceMethods<ExecutableOpInterface>,
HasClock, DeclareOpInterfaceMethods<GeneralOpInterface>
]> {
let summary = "fork operation";
let description = [{
@ -308,7 +311,10 @@ def LazyForkOp : Handshake_Op<"lazy_fork", [NoSideEffect]> {
}];
}
def MergeOp : Handshake_Op<"merge", [NoSideEffect, MergeLikeOpInterface]> {
def MergeOp : Handshake_Op<"merge", [
NoSideEffect, MergeLikeOpInterface,
DeclareOpInterfaceMethods<ExecutableOpInterface>
]> {
let summary = "merge operation";
let description = [{
The "handshake.merge" operation represents a (nondeterministic)
@ -325,7 +331,10 @@ def MergeOp : Handshake_Op<"merge", [NoSideEffect, MergeLikeOpInterface]> {
let builders = [OpBuilderDAG<(ins "Value":$operand, "int":$inputs)>];
}
def MuxOp : Handshake_Op<"mux", [NoSideEffect, MergeLikeOpInterface]> {
def MuxOp : Handshake_Op<"mux", [
NoSideEffect, MergeLikeOpInterface,
DeclareOpInterfaceMethods<ExecutableOpInterface>
]> {
let summary = "mux operation";
let description = [{
The "handshake.mux" operation represents a(deterministic)
@ -346,8 +355,10 @@ def MuxOp : Handshake_Op<"mux", [NoSideEffect, MergeLikeOpInterface]> {
let verifier = "return ::verify(*this);";
}
def ControlMergeOp : Handshake_Op<"control_merge",
[NoSideEffect, MergeLikeOpInterface, HasClock]> {
def ControlMergeOp : Handshake_Op<"control_merge", [
NoSideEffect, MergeLikeOpInterface, HasClock,
DeclareOpInterfaceMethods<ExecutableOpInterface>
]> {
let summary = "control merge operation";
let description = [{
The "handshake.control_merge" operation represents a
@ -371,7 +382,10 @@ def ControlMergeOp : Handshake_Op<"control_merge",
}];
}
def BranchOp : Handshake_Op<"branch", [NoSideEffect]> {
def BranchOp : Handshake_Op<"branch", [
NoSideEffect, DeclareOpInterfaceMethods<ExecutableOpInterface>,
DeclareOpInterfaceMethods<GeneralOpInterface>
]> {
let summary = "branch operation";
let description = [{
The "handshake.branch" operation represents an unconditional
@ -394,7 +408,9 @@ def BranchOp : Handshake_Op<"branch", [NoSideEffect]> {
}];
}
def ConditionalBranchOp : Handshake_Op<"conditional_branch", [NoSideEffect]> {
def ConditionalBranchOp : Handshake_Op<"conditional_branch", [
NoSideEffect, DeclareOpInterfaceMethods<ExecutableOpInterface>
]> {
let summary = "conditional branch operation";
let description = [{
The "handshake.cbranch" operation represents a conditional
@ -425,7 +441,8 @@ def ConditionalBranchOp : Handshake_Op<"conditional_branch", [NoSideEffect]> {
}];
}
def SinkOp : Handshake_Op<"sink", []> {
def SinkOp
: Handshake_Op<"sink", [DeclareOpInterfaceMethods<ExecutableOpInterface>]> {
let summary = "sink operation";
let description = [{
The "handshake.sink" operation discards any data that arrives at its
@ -448,7 +465,10 @@ def SourceOp : Handshake_Op<"source", [NoSideEffect]> {
let results = (outs AnyType);
}
def ConstantOp : Handshake_Op<"constant", [NoSideEffect]> {
def ConstantOp : Handshake_Op<"constant", [
NoSideEffect, DeclareOpInterfaceMethods<ExecutableOpInterface>,
DeclareOpInterfaceMethods<GeneralOpInterface>
]> {
let summary = "constant operation";
let description = [{
The "handshake.const" has a constant value.When triggered by its
@ -467,7 +487,8 @@ def ConstantOp : Handshake_Op<"constant", [NoSideEffect]> {
}];
}
def EndOp : Handshake_Op<"end"> {
def EndOp
: Handshake_Op<"end", [DeclareOpInterfaceMethods<ExecutableOpInterface>]> {
let summary = "end operation";
let description = [{
The "handshake.end" propagates the result of the appropriate
@ -481,7 +502,9 @@ def EndOp : Handshake_Op<"end"> {
let builders = [OpBuilderDAG<(ins "Value":$operand)>];
}
def StartOp : Handshake_Op<"start", [NoSideEffect]> {
def StartOp : Handshake_Op<"start", [
NoSideEffect, DeclareOpInterfaceMethods<ExecutableOpInterface>
]> {
let summary = "start operation";
let description = [{
Triggers execution of the control - only network. Placed in entry
@ -510,7 +533,10 @@ def TerminatorOp : Handshake_Op<"terminator", [Terminator]> {
}
def MemRefTypeAttr : TypeAttrBase<"MemRefType", "memref type attribute">;
def MemoryOp : Handshake_Op<"memory", [HasClock]> {
def MemoryOp : Handshake_Op<"memory", [
DeclareOpInterfaceMethods<ExecutableOpInterface>,
DeclareOpInterfaceMethods<MemoryOpInterface>, HasClock
]> {
let summary = "memory";
let description = [{
Each MemoryOp represents an independent memory or memory region (BRAM or external memory).
@ -549,7 +575,8 @@ def MemoryOp : Handshake_Op<"memory", [HasClock]> {
}];
}
def LoadOp : Handshake_Op<"load"> {
def LoadOp
: Handshake_Op<"load", [DeclareOpInterfaceMethods<ExecutableOpInterface>]> {
let summary = "load operation";
let description = [{
Load memory port, sends load requests to MemoryOp. From dataflow
@ -567,10 +594,12 @@ def LoadOp : Handshake_Op<"load"> {
let results = (outs AnyType, Variadic<Index>:$addressResults);
let builders = [OpBuilderDAG<(ins "Value":$memref, "ArrayRef<Value>":$indices)>];
}
def StoreOp : Handshake_Op<"store"> {
def StoreOp : Handshake_Op<"store", [
DeclareOpInterfaceMethods<ExecutableOpInterface>,
DeclareOpInterfaceMethods<GeneralOpInterface>
]> {
let summary = "store operation";
let description = [{
Store memory port, sends store requests to MemoryOp. From dataflow
@ -590,7 +619,10 @@ def StoreOp : Handshake_Op<"store"> {
[OpBuilderDAG<(ins "Value":$valueToStore, "ArrayRef<Value>":$indices)>];
}
def JoinOp : Handshake_Op<"join"> {
def JoinOp : Handshake_Op<"join", [
DeclareOpInterfaceMethods<ExecutableOpInterface>,
DeclareOpInterfaceMethods<GeneralOpInterface>
]> {
let summary = "join operation";
let description = [{
A control-only synchronizer. Produces a valid output when all

View File

@ -0,0 +1,32 @@
//===- Simulation.h -----------------------------------------------===//
//
// Copyright 2021 The CIRCT Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// =============================================================================
// Functions used to execute a restricted form of the standard dialect, and
// the handshake dialect.
#ifndef CIRCT_DIALECT_HANDSHAKE_SIMULATION_H
#define CIRCT_DIALECT_HANDSHAKE_SIMULATION_H
#include "mlir/IR/BuiltinOps.h"
#include "mlir/IR/MLIRContext.h"
#include <string>
bool simulate(llvm::StringRef toplevelFunction,
llvm::ArrayRef<std::string> inputArgs,
mlir::OwningModuleRef &module, mlir::MLIRContext &context);
#endif

View File

@ -29,16 +29,92 @@ using namespace circt::handshake;
#include "mlir/IR/IntegerSet.h"
#include "mlir/IR/PatternMatch.h"
#include "mlir/Transforms/InliningUtils.h"
#include "llvm/ADT/Any.h"
#include "llvm/ADT/SetVector.h"
#include "llvm/ADT/SmallBitVector.h"
#include "llvm/Support/Debug.h"
#define INDEX_WIDTH 32
namespace circt {
namespace handshake {
#include "circt/Dialect/Handshake/HandshakeOps.inc"
}
} // namespace circt
// Convert ValueRange to vectors
std::vector<mlir::Value> toVector(mlir::ValueRange range) {
return std::vector<mlir::Value>(range.begin(), range.end());
}
// Returns whether the precondition holds for a general op to execute
bool isReadyToExecute(ArrayRef<mlir::Value> ins, ArrayRef<mlir::Value> outs,
llvm::DenseMap<mlir::Value, llvm::Any> &valueMap) {
for (auto in : ins)
if (valueMap.count(in) == 0)
return false;
for (auto out : outs)
if (valueMap.count(out) > 0)
return false;
return true;
}
// Fetch values from the value map and consume them
std::vector<llvm::Any>
fetchValues(ArrayRef<mlir::Value> values,
llvm::DenseMap<mlir::Value, llvm::Any> &valueMap) {
std::vector<llvm::Any> ins;
for (auto &value : values) {
assert(valueMap[value].hasValue());
ins.push_back(valueMap[value]);
valueMap.erase(value);
}
return ins;
}
// Store values to the value map
void storeValues(std::vector<llvm::Any> &values, ArrayRef<mlir::Value> outs,
llvm::DenseMap<mlir::Value, llvm::Any> &valueMap) {
assert(values.size() == outs.size());
for (unsigned long i = 0; i < outs.size(); ++i)
valueMap[outs[i]] = values[i];
}
// Update the time map after the execution
void updateTime(ArrayRef<mlir::Value> ins, ArrayRef<mlir::Value> outs,
llvm::DenseMap<mlir::Value, double> &timeMap, double latency) {
double time = 0;
for (auto &in : ins)
time = std::max(time, timeMap[in]);
time += latency;
for (auto &out : outs)
timeMap[out] = time;
}
bool tryToExecute(Operation *op,
llvm::DenseMap<mlir::Value, llvm::Any> &valueMap,
llvm::DenseMap<mlir::Value, double> &timeMap,
std::vector<mlir::Value> &scheduleList, double latency) {
auto ins = toVector(op->getOperands());
auto outs = toVector(op->getResults());
if (isReadyToExecute(ins, outs, valueMap)) {
auto in = fetchValues(ins, valueMap);
std::vector<llvm::Any> out(outs.size());
auto generalOp = dyn_cast<handshake::GeneralOpInterface>(op);
if (!generalOp)
op->emitError("Undefined execution for the current op");
generalOp.execute(in, out);
storeValues(out, outs, valueMap);
updateTime(ins, outs, timeMap, latency);
scheduleList = outs;
return true;
} else
return false;
}
//===----------------------------------------------------------------------===//
// HandshakeOpsDialect
//===----------------------------------------------------------------------===//
@ -68,11 +144,27 @@ void ForkOp::build(OpBuilder &builder, OperationState &result, Value operand,
bool isControl = operand.getType().isa<NoneType>() ? true : false;
result.addAttribute("control", builder.getBoolAttr(isControl));
}
void handshake::ForkOp::getCanonicalizationPatterns(
OwningRewritePatternList &results, MLIRContext *context) {
results.insert<circt::handshake::EliminateSimpleForksPattern>(context);
}
void handshake::ForkOp::execute(std::vector<llvm::Any> &ins,
std::vector<llvm::Any> &outs) {
for (auto &out : outs)
out = ins[0];
}
bool handshake::ForkOp::tryExecute(
llvm::DenseMap<mlir::Value, llvm::Any> &valueMap,
llvm::DenseMap<unsigned, unsigned> &memoryMap,
llvm::DenseMap<mlir::Value, double> &timeMap,
std::vector<std::vector<llvm::Any>> &store,
std::vector<mlir::Value> &scheduleList) {
return tryToExecute(getOperation(), valueMap, timeMap, scheduleList, 1);
}
void LazyForkOp::build(OpBuilder &builder, OperationState &result,
Value operand, int outputs) {
@ -114,6 +206,34 @@ void MergeOp::getCanonicalizationPatterns(OwningRewritePatternList &results,
results.insert<circt::handshake::EliminateSimpleMergesPattern>(context);
}
bool handshake::MergeOp::tryExecute(
llvm::DenseMap<mlir::Value, llvm::Any> &valueMap,
llvm::DenseMap<unsigned, unsigned> &memoryMap,
llvm::DenseMap<mlir::Value, double> &timeMap,
std::vector<std::vector<llvm::Any>> &store,
std::vector<mlir::Value> &scheduleList) {
auto op = getOperation();
bool found = false;
int i = 0;
for (mlir::Value in : op->getOperands()) {
if (valueMap.count(in) == 1) {
if (found)
op->emitError("More than one valid input to Merge!");
auto t = valueMap[in];
valueMap[op->getResult(0)] = t;
timeMap[op->getResult(0)] = timeMap[in];
// Consume the inputs.
valueMap.erase(in);
found = true;
}
i++;
}
if (!found)
op->emitError("No valid input to Merge!");
scheduleList.push_back(getResult());
return true;
}
void MuxOp::build(OpBuilder &builder, OperationState &result, Value operand,
int inputs) {
@ -128,6 +248,35 @@ void MuxOp::build(OpBuilder &builder, OperationState &result, Value operand,
result.addOperands(operand);
}
bool handshake::MuxOp::tryExecute(
llvm::DenseMap<mlir::Value, llvm::Any> &valueMap,
llvm::DenseMap<unsigned, unsigned> &memoryMap,
llvm::DenseMap<mlir::Value, double> &timeMap,
std::vector<std::vector<llvm::Any>> &store,
std::vector<mlir::Value> &scheduleList) {
auto op = getOperation();
mlir::Value control = op->getOperand(0);
if (valueMap.count(control) == 0)
return false;
auto controlValue = valueMap[control];
auto controlTime = timeMap[control];
mlir::Value in = llvm::any_cast<APInt>(controlValue) == 0 ? op->getOperand(1)
: op->getOperand(2);
if (valueMap.count(in) == 0)
return false;
auto inValue = valueMap[in];
auto inTime = timeMap[in];
double time = std::max(controlTime, inTime);
valueMap[op->getResult(0)] = inValue;
timeMap[op->getResult(0)] = time;
// Consume the inputs.
valueMap.erase(control);
valueMap.erase(in);
scheduleList.push_back(getResult());
return true;
}
static LogicalResult verify(MuxOp op) {
unsigned numDataOperands = static_cast<int>(op.dataOperands().size());
if (numDataOperands < 2)
@ -172,12 +321,46 @@ void ControlMergeOp::build(OpBuilder &builder, OperationState &result,
result.addAttribute("control", builder.getBoolAttr(true));
}
// void ControlMergeOp::getCanonicalizationPatterns(OwningRewritePatternList
// &results,
// MLIRContext *context) {
// results.insert<circt::handshake::EliminateSimpleControlMergesPattern>(context);
// }
bool handshake::ControlMergeOp::tryExecute(
llvm::DenseMap<mlir::Value, llvm::Any> &valueMap,
llvm::DenseMap<unsigned, unsigned> &memoryMap,
llvm::DenseMap<mlir::Value, double> &timeMap,
std::vector<std::vector<llvm::Any>> &store,
std::vector<mlir::Value> &scheduleList) {
auto op = getOperation();
bool found = false;
int i = 0;
for (mlir::Value in : op->getOperands()) {
if (valueMap.count(in) == 1) {
if (found)
op->emitError("More than one valid input to CMerge!");
auto t = valueMap[in];
valueMap[op->getResult(0)] = t;
timeMap[op->getResult(0)] = timeMap[in];
valueMap[op->getResult(1)] = APInt(INDEX_WIDTH, i);
timeMap[op->getResult(1)] = timeMap[in];
// Consume the inputs.
valueMap.erase(in);
found = true;
}
i++;
}
if (!found)
op->emitError("No valid input to CMerge!");
scheduleList = toVector(op->getResults());
return true;
}
void handshake::BranchOp::build(OpBuilder &builder, OperationState &result,
Value dataOperand) {
@ -194,11 +377,26 @@ void handshake::BranchOp::build(OpBuilder &builder, OperationState &result,
: false;
result.addAttribute("control", builder.getBoolAttr(isControl));
}
void handshake::BranchOp::getCanonicalizationPatterns(
OwningRewritePatternList &results, MLIRContext *context) {
results.insert<circt::handshake::EliminateSimpleBranchesPattern>(context);
}
void handshake::BranchOp::execute(std::vector<llvm::Any> &ins,
std::vector<llvm::Any> &outs) {
outs[0] = ins[0];
}
bool handshake::BranchOp::tryExecute(
llvm::DenseMap<mlir::Value, llvm::Any> &valueMap,
llvm::DenseMap<unsigned, unsigned> &memoryMap,
llvm::DenseMap<mlir::Value, double> &timeMap,
std::vector<std::vector<llvm::Any>> &store,
std::vector<mlir::Value> &scheduleList) {
return tryToExecute(getOperation(), valueMap, timeMap, scheduleList, 0);
}
void handshake::ConditionalBranchOp::build(OpBuilder &builder,
OperationState &result,
Value condOperand,
@ -219,6 +417,36 @@ void handshake::ConditionalBranchOp::build(OpBuilder &builder,
result.addAttribute("control", builder.getBoolAttr(isControl));
}
bool handshake::ConditionalBranchOp::tryExecute(
llvm::DenseMap<mlir::Value, llvm::Any> &valueMap,
llvm::DenseMap<unsigned, unsigned> &memoryMap,
llvm::DenseMap<mlir::Value, double> &timeMap,
std::vector<std::vector<llvm::Any>> &store,
std::vector<mlir::Value> &scheduleList) {
auto op = getOperation();
mlir::Value control = op->getOperand(0);
if (valueMap.count(control) == 0)
return false;
auto controlValue = valueMap[control];
auto controlTime = timeMap[control];
mlir::Value in = op->getOperand(1);
if (valueMap.count(in) == 0)
return false;
auto inValue = valueMap[in];
auto inTime = timeMap[in];
mlir::Value out = llvm::any_cast<APInt>(controlValue) != 0 ? op->getResult(0)
: op->getResult(1);
double time = std::max(controlTime, inTime);
valueMap[out] = inValue;
timeMap[out] = time;
scheduleList.push_back(out);
// Consume the inputs.
valueMap.erase(control);
valueMap.erase(in);
return true;
}
void StartOp::build(OpBuilder &builder, OperationState &result) {
// Control-only output, has no type
auto type = builder.getNoneType();
@ -226,11 +454,29 @@ void StartOp::build(OpBuilder &builder, OperationState &result) {
result.addAttribute("control", builder.getBoolAttr(true));
}
bool handshake::StartOp::tryExecute(
llvm::DenseMap<mlir::Value, llvm::Any> &valueMap,
llvm::DenseMap<unsigned, unsigned> &memoryMap,
llvm::DenseMap<mlir::Value, double> &timeMap,
std::vector<std::vector<llvm::Any>> &store,
std::vector<mlir::Value> &scheduleList) {
return true;
}
void EndOp::build(OpBuilder &builder, OperationState &result, Value operand) {
result.addOperands(operand);
}
bool handshake::EndOp::tryExecute(
llvm::DenseMap<mlir::Value, llvm::Any> &valueMap,
llvm::DenseMap<unsigned, unsigned> &memoryMap,
llvm::DenseMap<mlir::Value, double> &timeMap,
std::vector<std::vector<llvm::Any>> &store,
std::vector<mlir::Value> &scheduleList) {
return true;
}
void handshake::ReturnOp::build(OpBuilder &builder, OperationState &result,
ArrayRef<Value> operands) {
@ -242,6 +488,16 @@ void SinkOp::build(OpBuilder &builder, OperationState &result, Value operand) {
result.addOperands(operand);
}
bool handshake::SinkOp::tryExecute(
llvm::DenseMap<mlir::Value, llvm::Any> &valueMap,
llvm::DenseMap<unsigned, unsigned> &memoryMap,
llvm::DenseMap<mlir::Value, double> &timeMap,
std::vector<std::vector<llvm::Any>> &store,
std::vector<mlir::Value> &scheduleList) {
valueMap.erase(getOperand());
return true;
}
void handshake::ConstantOp::build(OpBuilder &builder, OperationState &result,
Attribute value, Value operand) {
@ -253,6 +509,21 @@ void handshake::ConstantOp::build(OpBuilder &builder, OperationState &result,
result.addAttribute("value", value);
}
void handshake::ConstantOp::execute(std::vector<llvm::Any> &ins,
std::vector<llvm::Any> &outs) {
auto attr = getAttrOfType<mlir::IntegerAttr>("value");
outs[0] = attr.getValue();
}
bool handshake::ConstantOp::tryExecute(
llvm::DenseMap<mlir::Value, llvm::Any> &valueMap,
llvm::DenseMap<unsigned, unsigned> &memoryMap,
llvm::DenseMap<mlir::Value, double> &timeMap,
std::vector<std::vector<llvm::Any>> &store,
std::vector<mlir::Value> &scheduleList) {
return tryToExecute(getOperation(), valueMap, timeMap, scheduleList, 0);
}
void handshake::TerminatorOp::build(OpBuilder &builder, OperationState &result,
ArrayRef<Block *> successors) {
// Add all the successor blocks of the block which contains this terminator
@ -293,6 +564,121 @@ void MemoryOp::build(OpBuilder &builder, OperationState &result,
}
}
bool handshake::MemoryOp::allocateMemory(
llvm::DenseMap<unsigned, unsigned> &memoryMap,
std::vector<std::vector<llvm::Any>> &store,
std::vector<double> &storeTimes) {
unsigned id = getID();
if (memoryMap.count(id))
return false;
auto type = getMemRefType();
std::vector<llvm::Any> in;
ArrayRef<int64_t> shape = type.getShape();
int allocationSize = 1;
unsigned count = 0;
for (int64_t dim : shape) {
if (dim > 0)
allocationSize *= dim;
else {
assert(count < in.size());
allocationSize *= llvm::any_cast<APInt>(in[count++]).getSExtValue();
}
}
unsigned ptr = store.size();
store.resize(ptr + 1);
storeTimes.resize(ptr + 1);
store[ptr].resize(allocationSize);
storeTimes[ptr] = 0.0;
mlir::Type elementType = type.getElementType();
int width = elementType.getIntOrFloatBitWidth();
for (int i = 0; i < allocationSize; i++) {
if (elementType.isa<mlir::IntegerType>()) {
store[ptr][i] = APInt(width, 0);
} else if (elementType.isa<mlir::FloatType>()) {
store[ptr][i] = APFloat(0.0);
} else {
llvm_unreachable("Unknown result type!\n");
}
}
memoryMap[id] = ptr;
return true;
}
bool handshake::MemoryOp::tryExecute(
llvm::DenseMap<mlir::Value, llvm::Any> &valueMap,
llvm::DenseMap<unsigned, unsigned> &memoryMap,
llvm::DenseMap<mlir::Value, double> &timeMap,
std::vector<std::vector<llvm::Any>> &store,
std::vector<mlir::Value> &scheduleList) {
auto op = getOperation();
int opIndex = 0;
bool notReady = false;
unsigned id = getID(); // The ID of this memory.
unsigned buffer = memoryMap[id];
for (unsigned i = 0; i < getStCount().getZExtValue(); i++) {
mlir::Value data = op->getOperand(opIndex++);
mlir::Value address = op->getOperand(opIndex++);
mlir::Value nonceOut = op->getResult(getLdCount().getZExtValue() + i);
if ((!valueMap.count(data) || !valueMap.count(address))) {
notReady = true;
continue;
}
auto addressValue = valueMap[address];
auto addressTime = timeMap[address];
auto dataValue = valueMap[data];
auto dataTime = timeMap[data];
assert(buffer < store.size());
auto &ref = store[buffer];
unsigned offset = llvm::any_cast<APInt>(addressValue).getZExtValue();
assert(offset < ref.size());
ref[offset] = dataValue;
// Implicit none argument
APInt apnonearg(1, 0);
valueMap[nonceOut] = apnonearg;
double time = std::max(addressTime, dataTime);
timeMap[nonceOut] = time;
scheduleList.push_back(nonceOut);
// Consume the inputs.
valueMap.erase(data);
valueMap.erase(address);
}
for (unsigned i = 0; i < getLdCount().getZExtValue(); i++) {
mlir::Value address = op->getOperand(opIndex++);
mlir::Value dataOut = op->getResult(i);
mlir::Value nonceOut = op->getResult(getLdCount().getZExtValue() +
getStCount().getZExtValue() + i);
if (!valueMap.count(address)) {
notReady = true;
continue;
}
auto addressValue = valueMap[address];
auto addressTime = timeMap[address];
assert(buffer < store.size());
auto &ref = store[buffer];
unsigned offset = llvm::any_cast<APInt>(addressValue).getZExtValue();
assert(offset < ref.size());
valueMap[dataOut] = ref[offset];
timeMap[dataOut] = addressTime;
// Implicit none argument
APInt apnonearg(1, 0);
valueMap[nonceOut] = apnonearg;
timeMap[nonceOut] = addressTime;
scheduleList.push_back(dataOut);
scheduleList.push_back(nonceOut);
// Consume the inputs.
valueMap.erase(address);
}
return (notReady) ? false : true;
}
void handshake::LoadOp::build(OpBuilder &builder, OperationState &result,
Value memref, ArrayRef<Value> indices) {
@ -310,6 +696,49 @@ void handshake::LoadOp::build(OpBuilder &builder, OperationState &result,
result.types.append(indices.size(), builder.getIndexType());
}
bool handshake::LoadOp::tryExecute(
llvm::DenseMap<mlir::Value, llvm::Any> &valueMap,
llvm::DenseMap<unsigned, unsigned> &memoryMap,
llvm::DenseMap<mlir::Value, double> &timeMap,
std::vector<std::vector<llvm::Any>> &store,
std::vector<mlir::Value> &scheduleList) {
auto op = getOperation();
mlir::Value address = op->getOperand(0);
mlir::Value data = op->getOperand(1);
mlir::Value nonce = op->getOperand(2);
mlir::Value addressOut = op->getResult(1);
mlir::Value dataOut = op->getResult(0);
if ((valueMap.count(address) && !valueMap.count(nonce)) ||
(!valueMap.count(address) && valueMap.count(nonce)) ||
(!valueMap.count(address) && !valueMap.count(nonce) &&
!valueMap.count(data)))
return false;
if (valueMap.count(address) && valueMap.count(nonce)) {
auto addressValue = valueMap[address];
auto addressTime = timeMap[address];
auto nonceValue = valueMap[nonce];
auto nonceTime = timeMap[nonce];
valueMap[addressOut] = addressValue;
double time = std::max(addressTime, nonceTime);
timeMap[addressOut] = time;
scheduleList.push_back(addressOut);
// Consume the inputs.
valueMap.erase(address);
valueMap.erase(nonce);
} else if (valueMap.count(data)) {
auto dataValue = valueMap[data];
auto dataTime = timeMap[data];
valueMap[dataOut] = dataValue;
timeMap[dataOut] = dataTime;
scheduleList.push_back(dataOut);
// Consume the inputs.
valueMap.erase(data);
} else {
llvm_unreachable("why?");
}
return true;
}
void handshake::StoreOp::build(OpBuilder &builder, OperationState &result,
Value valueToStore, ArrayRef<Value> indices) {
@ -326,6 +755,22 @@ void handshake::StoreOp::build(OpBuilder &builder, OperationState &result,
result.types.append(indices.size(), builder.getIndexType());
}
void handshake::StoreOp::execute(std::vector<llvm::Any> &ins,
std::vector<llvm::Any> &outs) {
// Forward the address and data to the memory op.
outs[0] = ins[0];
outs[1] = ins[1];
}
bool handshake::StoreOp::tryExecute(
llvm::DenseMap<mlir::Value, llvm::Any> &valueMap,
llvm::DenseMap<unsigned, unsigned> &memoryMap,
llvm::DenseMap<mlir::Value, double> &timeMap,
std::vector<std::vector<llvm::Any>> &store,
std::vector<mlir::Value> &scheduleList) {
return tryToExecute(getOperation(), valueMap, timeMap, scheduleList, 1);
}
void JoinOp::build(OpBuilder &builder, OperationState &result,
ArrayRef<Value> operands) {
@ -337,6 +782,20 @@ void JoinOp::build(OpBuilder &builder, OperationState &result,
result.addAttribute("control", builder.getBoolAttr(true));
}
void handshake::JoinOp::execute(std::vector<llvm::Any> &ins,
std::vector<llvm::Any> &outs) {
outs[0] = ins[0];
}
bool handshake::JoinOp::tryExecute(
llvm::DenseMap<mlir::Value, llvm::Any> &valueMap,
llvm::DenseMap<unsigned, unsigned> &memoryMap,
llvm::DenseMap<mlir::Value, double> &timeMap,
std::vector<std::vector<llvm::Any>> &store,
std::vector<mlir::Value> &scheduleList) {
return tryToExecute(getOperation(), valueMap, timeMap, scheduleList, 1);
}
// for let printer/parser/verifier in Handshake_Op class
/*static LogicalResult verify(ForkOp op) {
return success();

View File

@ -1,7 +1,7 @@
get_property(dialect_libs GLOBAL PROPERTY MLIR_DIALECT_LIBS)
get_property(conversion_libs GLOBAL PROPERTY MLIR_CONVERSION_LIBS)
add_llvm_executable(handshake-runner handshake-runner.cpp)
add_llvm_executable(handshake-runner handshake-runner.cpp Simulation.cpp)
llvm_update_compile_flags(handshake-runner)
target_link_libraries(handshake-runner PRIVATE

View File

@ -0,0 +1,722 @@
//===- Simulation.cpp -----------------------------------------------===//
//
// Copyright 2021 The CIRCT Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// =============================================================================
// Functions used to execute a restricted form of the standard dialect, and
// the handshake dialect.
#include <list>
#include "mlir/Dialect/StandardOps/IR/Ops.h"
#include "circt/Dialect/Handshake/HandshakeOps.h"
#include "circt/Dialect/Handshake/Simulation.h"
#define DEBUG_TYPE "runner"
#define INDEX_WIDTH 32
using namespace llvm;
using namespace mlir;
using namespace circt;
STATISTIC(instructionsExecuted, "Instructions Executed");
STATISTIC(simulatedTime, "Simulated Time");
void executeOp(mlir::ConstantIndexOp op, std::vector<Any> &in,
std::vector<Any> &out) {
auto attr = op.getAttrOfType<mlir::IntegerAttr>("value");
out[0] = attr.getValue().sextOrTrunc(INDEX_WIDTH);
}
void executeOp(mlir::ConstantIntOp op, std::vector<Any> &in,
std::vector<Any> &out) {
auto attr = op.getAttrOfType<mlir::IntegerAttr>("value");
out[0] = attr.getValue();
}
void executeOp(mlir::AddIOp op, std::vector<Any> &in, std::vector<Any> &out) {
out[0] = any_cast<APInt>(in[0]) + any_cast<APInt>(in[1]);
}
void executeOp(mlir::AddFOp op, std::vector<Any> &in, std::vector<Any> &out) {
out[0] = any_cast<APFloat>(in[0]) + any_cast<APFloat>(in[1]);
}
void executeOp(mlir::CmpIOp op, std::vector<Any> &in, std::vector<Any> &out) {
APInt in0 = any_cast<APInt>(in[0]);
APInt in1 = any_cast<APInt>(in[1]);
APInt out0(1, mlir::applyCmpPredicate(op.getPredicate(), in0, in1));
out[0] = out0;
}
void executeOp(mlir::CmpFOp op, std::vector<Any> &in, std::vector<Any> &out) {
APFloat in0 = any_cast<APFloat>(in[0]);
APFloat in1 = any_cast<APFloat>(in[1]);
APInt out0(1, mlir::applyCmpPredicate(op.getPredicate(), in0, in1));
out[0] = out0;
}
void executeOp(mlir::SubIOp op, std::vector<Any> &in, std::vector<Any> &out) {
out[0] = any_cast<APInt>(in[0]) - any_cast<APInt>(in[1]);
}
void executeOp(mlir::SubFOp op, std::vector<Any> &in, std::vector<Any> &out) {
out[0] = any_cast<APFloat>(in[0]) + any_cast<APFloat>(in[1]);
}
void executeOp(mlir::MulIOp op, std::vector<Any> &in, std::vector<Any> &out) {
out[0] = any_cast<APInt>(in[0]) * any_cast<APInt>(in[1]);
}
void executeOp(mlir::MulFOp op, std::vector<Any> &in, std::vector<Any> &out) {
out[0] = any_cast<APFloat>(in[0]) * any_cast<APFloat>(in[1]);
}
void executeOp(mlir::SignedDivIOp op, std::vector<Any> &in,
std::vector<Any> &out) {
assert(any_cast<APInt>(in[1]).getZExtValue() && "Division By Zero!");
out[0] = any_cast<APInt>(in[0]).sdiv(any_cast<APInt>(in[1]));
}
void executeOp(mlir::UnsignedDivIOp op, std::vector<Any> &in,
std::vector<Any> &out) {
assert(any_cast<APInt>(in[1]).getZExtValue() && "Division By Zero!");
out[0] = any_cast<APInt>(in[0]).udiv(any_cast<APInt>(in[1]));
}
void executeOp(mlir::DivFOp op, std::vector<Any> &in, std::vector<Any> &out) {
out[0] = any_cast<APFloat>(in[0]) / any_cast<APFloat>(in[1]);
}
void executeOp(mlir::IndexCastOp op, std::vector<Any> &in,
std::vector<Any> &out) {
out[0] = in[0];
}
void executeOp(mlir::SignExtendIOp op, std::vector<Any> &in,
std::vector<Any> &out) {
int64_t width = op.getType().getIntOrFloatBitWidth();
out[0] = any_cast<APInt>(in[0]).sext(width);
}
void executeOp(mlir::ZeroExtendIOp op, std::vector<Any> &in,
std::vector<Any> &out) {
int64_t width = op.getType().getIntOrFloatBitWidth();
out[0] = any_cast<APInt>(in[0]).zext(width);
}
// Allocate a new matrix with dimensions given by the type, in the
// given store. Return the pseuddo-pointer to the new matrix in the
// store (i.e. the first dimension index)
unsigned allocateMemRef(mlir::MemRefType type, std::vector<Any> &in,
std::vector<std::vector<Any>> &store,
std::vector<double> &storeTimes) {
ArrayRef<int64_t> shape = type.getShape();
int64_t allocationSize = 1;
unsigned count = 0;
for (int64_t dim : shape) {
if (dim > 0)
allocationSize *= dim;
else {
assert(count < in.size());
allocationSize *= any_cast<APInt>(in[count++]).getSExtValue();
}
}
unsigned ptr = store.size();
store.resize(ptr + 1);
storeTimes.resize(ptr + 1);
store[ptr].resize(allocationSize);
storeTimes[ptr] = 0.0;
mlir::Type elementType = type.getElementType();
int64_t width = elementType.getIntOrFloatBitWidth();
for (int i = 0; i < allocationSize; i++) {
if (elementType.isa<mlir::IntegerType>()) {
store[ptr][i] = APInt(width, 0);
} else if (elementType.isa<mlir::FloatType>()) {
store[ptr][i] = APFloat(0.0);
} else {
llvm_unreachable("Unknown result type!\n");
}
}
return ptr;
}
void executeOp(mlir::LoadOp op, std::vector<Any> &in, std::vector<Any> &out,
std::vector<std::vector<Any>> &store) {
ArrayRef<int64_t> shape = op.getMemRefType().getShape();
unsigned address = 0;
for (unsigned i = 0; i < shape.size(); i++) {
address = address * shape[i] + any_cast<APInt>(in[i + 1]).getZExtValue();
}
unsigned ptr = any_cast<unsigned>(in[0]);
assert(ptr < store.size());
auto &ref = store[ptr];
assert(address < ref.size());
// LLVM_DEBUG(dbgs() << "Load " << ref[address] << " from " << ptr << "[" <<
// address << "]\n");
Any result = ref[address];
out[0] = result;
}
void executeOp(mlir::StoreOp op, std::vector<Any> &in, std::vector<Any> &out,
std::vector<std::vector<Any>> &store) {
ArrayRef<int64_t> shape = op.getMemRefType().getShape();
unsigned address = 0;
for (unsigned i = 0; i < shape.size(); i++) {
address = address * shape[i] + any_cast<APInt>(in[i + 2]).getZExtValue();
}
unsigned ptr = any_cast<unsigned>(in[1]);
assert(ptr < store.size());
auto &ref = store[ptr];
// LLVM_DEBUG(dbgs() << "Store " << in[0] << " to " << ptr << "[" << address
// << "]\n");
assert(address < ref.size());
ref[address] = in[0];
}
void debugArg(const std::string &head, mlir::Value op, const APInt &value,
double time) {
LLVM_DEBUG(dbgs() << " " << head << ": " << op << " = " << value
<< " (APInt<" << value.getBitWidth() << ">) @" << time
<< "\n");
}
void debugArg(const std::string &head, mlir::Value op, const APFloat &value,
double time) {
LLVM_DEBUG(dbgs() << " " << head << ": " << op << " = ";
value.print(dbgs()); dbgs() << " ("
<< "float"
<< ") @" << time << "\n");
}
void debugArg(const std::string &head, mlir::Value op, const Any &value,
double time) {
if (any_isa<APInt>(value)) {
debugArg(head, op, any_cast<APInt>(value), time);
} else if (any_isa<APFloat>(value)) {
debugArg(head, op, any_cast<APFloat>(value), time);
} else if (any_isa<unsigned>(value)) {
// Represents an allocated buffer.
LLVM_DEBUG(dbgs() << " " << head << ": " << op << " = Buffer "
<< any_cast<unsigned>(value) << "\n");
} else {
llvm_unreachable("unknown type");
}
}
Any readValueWithType(mlir::Type type, std::string in) {
std::stringstream arg(in);
if (type.isIndex()) {
int64_t x;
arg >> x;
int64_t width = INDEX_WIDTH;
APInt aparg(width, x);
return aparg;
} else if (type.isa<mlir::IntegerType>()) {
int64_t x;
arg >> x;
int64_t width = type.getIntOrFloatBitWidth();
APInt aparg(width, x);
return aparg;
// } else if (type.isF16()) {
// half x;
// arg >> x;
// APFloat aparg(x);
// return aparg;
} else if (type.isF32()) {
float x;
arg >> x;
APFloat aparg(x);
return aparg;
} else if (type.isF64()) {
double x;
arg >> x;
APFloat aparg(x);
return aparg;
} else {
assert(0 && "unknown argument type!\n");
}
}
std::string printAnyValueWithType(mlir::Type type, Any &value) {
std::stringstream out;
if (type.isa<mlir::IntegerType>() || type.isa<mlir::IndexType>()) {
out << any_cast<APInt>(value).getSExtValue();
return out.str();
} else if (type.isa<mlir::FloatType>()) {
out << any_cast<APFloat>(value).convertToDouble();
return out.str();
} else if (type.isa<mlir::NoneType>()) {
return "none";
} else {
llvm_unreachable("Unknown result type!");
}
}
void scheduleIfNeeded(std::list<mlir::Operation *> &readyList,
llvm::DenseMap<mlir::Value, Any> &valueMap,
mlir::Operation *op) {
if (std::find(readyList.begin(), readyList.end(), op) == readyList.end()) {
readyList.push_back(op);
}
}
void scheduleUses(std::list<mlir::Operation *> &readyList,
llvm::DenseMap<mlir::Value, Any> &valueMap,
mlir::Value value) {
for (auto &use : value.getUses()) {
scheduleIfNeeded(readyList, valueMap, use.getOwner());
}
}
bool executeStdOp(mlir::Operation &op, std::vector<Any> &inValues,
std::vector<Any> &outValues) {
if (auto stdOp = dyn_cast<mlir::ConstantIndexOp>(op))
executeOp(stdOp, inValues, outValues);
else if (auto stdOp = dyn_cast<mlir::ConstantIntOp>(op))
executeOp(stdOp, inValues, outValues);
else if (auto stdOp = dyn_cast<mlir::AddIOp>(op))
executeOp(stdOp, inValues, outValues);
else if (auto stdOp = dyn_cast<mlir::AddFOp>(op))
executeOp(stdOp, inValues, outValues);
else if (auto stdOp = dyn_cast<mlir::SubIOp>(op))
executeOp(stdOp, inValues, outValues);
else if (auto stdOp = dyn_cast<mlir::SubFOp>(op))
executeOp(stdOp, inValues, outValues);
else if (auto stdOp = dyn_cast<mlir::CmpIOp>(op))
executeOp(stdOp, inValues, outValues);
else if (auto stdOp = dyn_cast<mlir::CmpFOp>(op))
executeOp(stdOp, inValues, outValues);
else if (auto stdOp = dyn_cast<mlir::MulIOp>(op))
executeOp(stdOp, inValues, outValues);
else if (auto stdOp = dyn_cast<mlir::MulFOp>(op))
executeOp(stdOp, inValues, outValues);
else if (auto stdOp = dyn_cast<mlir::UnsignedDivIOp>(op))
executeOp(stdOp, inValues, outValues);
else if (auto stdOp = dyn_cast<mlir::SignedDivIOp>(op))
executeOp(stdOp, inValues, outValues);
else if (auto stdOp = dyn_cast<mlir::DivFOp>(op))
executeOp(stdOp, inValues, outValues);
else if (auto stdOp = dyn_cast<mlir::IndexCastOp>(op))
executeOp(stdOp, inValues, outValues);
else if (auto stdOp = dyn_cast<mlir::SignExtendIOp>(op))
executeOp(stdOp, inValues, outValues);
else if (auto stdOp = dyn_cast<mlir::ZeroExtendIOp>(op))
executeOp(stdOp, inValues, outValues);
else
return false;
return true;
}
void executeFunction(mlir::FuncOp &toplevel,
llvm::DenseMap<mlir::Value, Any> &valueMap,
llvm::DenseMap<mlir::Value, double> &timeMap,
std::vector<Any> &results,
std::vector<double> &resultTimes,
std::vector<std::vector<Any>> &store,
std::vector<double> &storeTimes) {
mlir::Block &entryBlock = toplevel.getBody().front();
// An iterator which walks over the instructions.
mlir::Block::iterator instIter = entryBlock.begin();
// Main executive loop. Start at the first instruction of the entry
// block. Fetch and execute instructions until we hit a terminator.
while (true) {
mlir::Operation &op = *instIter;
int64_t i = 0;
std::vector<Any> inValues(op.getNumOperands());
std::vector<Any> outValues(op.getNumResults());
LLVM_DEBUG(dbgs() << "OP: " << op.getName() << "\n");
double time = 0.0;
for (mlir::Value in : op.getOperands()) {
inValues[i] = valueMap[in];
time = std::max(time, timeMap[in]);
LLVM_DEBUG(debugArg("IN", in, inValues[i], timeMap[in]));
i++;
}
if (executeStdOp(op, inValues, outValues)) {
} else if (auto allocOp = dyn_cast<mlir::AllocOp>(op)) {
outValues[0] =
allocateMemRef(allocOp.getType(), inValues, store, storeTimes);
unsigned ptr = any_cast<unsigned>(outValues[0]);
storeTimes[ptr] = time;
} else if (auto loadOp = dyn_cast<mlir::LoadOp>(op)) {
executeOp(loadOp, inValues, outValues, store);
unsigned ptr = any_cast<unsigned>(inValues[0]);
double storeTime = storeTimes[ptr];
LLVM_DEBUG(dbgs() << "STORE: " << storeTime << "\n");
time = std::max(time, storeTime);
storeTimes[ptr] = time;
} else if (auto storeOp = dyn_cast<mlir::StoreOp>(op)) {
executeOp(storeOp, inValues, outValues, store);
unsigned ptr = any_cast<unsigned>(inValues[1]);
double storeTime = storeTimes[ptr];
LLVM_DEBUG(dbgs() << "STORE: " << storeTime << "\n");
time = std::max(time, storeTime);
storeTimes[ptr] = time;
} else if (auto branchOp = dyn_cast<mlir::BranchOp>(op)) {
mlir::Block *dest = branchOp.getDest();
unsigned arg = 0;
for (mlir::Value out : dest->getArguments()) {
LLVM_DEBUG(debugArg("ARG", out, inValues[arg], time));
valueMap[out] = inValues[arg];
timeMap[out] = time;
arg++;
}
instIter = dest->begin();
continue;
} else if (auto condBranchOp = dyn_cast<mlir::CondBranchOp>(op)) {
APInt condition = any_cast<APInt>(inValues[0]);
mlir::Block *dest;
std::vector<Any> inArgs;
double time = 0.0;
if (condition != 0) {
dest = condBranchOp.getTrueDest();
inArgs.resize(condBranchOp.getNumTrueOperands());
for (mlir::Value in : condBranchOp.getTrueOperands()) {
inArgs[i] = valueMap[in];
time = std::max(time, timeMap[in]);
LLVM_DEBUG(debugArg("IN", in, inArgs[i], timeMap[in]));
i++;
}
} else {
dest = condBranchOp.getFalseDest();
inArgs.resize(condBranchOp.getNumFalseOperands());
for (mlir::Value in : condBranchOp.getFalseOperands()) {
inArgs[i] = valueMap[in];
time = std::max(time, timeMap[in]);
LLVM_DEBUG(debugArg("IN", in, inArgs[i], timeMap[in]));
i++;
}
}
int64_t arg = 0;
for (mlir::Value out : dest->getArguments()) {
LLVM_DEBUG(debugArg("ARG", out, inArgs[arg], time));
valueMap[out] = inArgs[arg];
timeMap[out] = time;
arg++;
}
instIter = dest->begin();
continue;
} else if (auto returnOp = dyn_cast<mlir::ReturnOp>(op)) {
for (unsigned i = 0; i < results.size(); i++) {
results[i] = inValues[i];
resultTimes[i] = timeMap[returnOp.getOperand(i)];
}
return;
} else if (auto callOp = dyn_cast<mlir::CallOpInterface>(op)) {
// implement function calls.
mlir::Operation *calledOp = callOp.resolveCallable();
if (auto funcOp = dyn_cast<mlir::FuncOp>(calledOp)) {
mlir::FunctionType ftype = funcOp.getType();
unsigned inputs = ftype.getNumInputs();
unsigned outputs = ftype.getNumResults();
llvm::DenseMap<mlir::Value, Any> newValueMap;
llvm::DenseMap<mlir::Value, double> newTimeMap;
std::vector<Any> results(outputs);
std::vector<double> resultTimes(outputs);
std::vector<std::vector<Any>> store;
std::vector<double> storeTimes;
mlir::Block &entryBlock = funcOp.getBody().front();
mlir::Block::BlockArgListType blockArgs = entryBlock.getArguments();
for (unsigned i = 0; i < inputs; i++) {
newValueMap[blockArgs[i]] = inValues[i];
newTimeMap[blockArgs[i]] = timeMap[op.getOperand(i)];
}
executeFunction(funcOp, newValueMap, newTimeMap, results, resultTimes,
store, storeTimes);
i = 0;
for (mlir::Value out : op.getResults()) {
valueMap[out] = results[i];
timeMap[out] = resultTimes[i];
i++;
}
instIter++;
continue;
} else {
llvm_unreachable("Callable was not a Function!\n");
}
} else {
llvm_unreachable("Unknown operation!\n");
}
i = 0;
for (mlir::Value out : op.getResults()) {
LLVM_DEBUG(debugArg("OUT", out, outValues[i], time));
valueMap[out] = outValues[i];
timeMap[out] = time + 1;
i++;
}
instIter++;
instructionsExecuted++;
}
}
void executeHandshakeFunction(handshake::FuncOp &toplevel,
llvm::DenseMap<mlir::Value, Any> &valueMap,
llvm::DenseMap<mlir::Value, double> &timeMap,
std::vector<Any> &results,
std::vector<double> &resultTimes,
std::vector<std::vector<Any>> &store,
std::vector<double> &storeTimes) {
mlir::Block &entryBlock = toplevel.getBody().front();
// The arguments of the entry block.
mlir::Block::BlockArgListType blockArgs = entryBlock.getArguments();
// A list of operations which might be ready to execute.
std::list<mlir::Operation *> readyList;
// A map of memory ops
llvm::DenseMap<unsigned, unsigned> memoryMap;
// Pre-allocate memory
toplevel.walk([&](Operation *op) {
if (auto handshakeMemoryOp = dyn_cast<handshake::MemoryOpInterface>(op))
if (!handshakeMemoryOp.allocateMemory(memoryMap, store, storeTimes))
llvm_unreachable("Memory op does not have unique ID!\n");
});
for (unsigned i = 0; i < blockArgs.size(); i++)
scheduleUses(readyList, valueMap, blockArgs[i]);
#define EXTRA_DEBUG
while (true) {
#ifdef EXTRA_DEBUG
LLVM_DEBUG(
for (auto t
: readyList) { dbgs() << "READY: " << *t << "\n"; } dbgs()
<< "Live: " << valueMap.size() << "\n";
for (auto t
: valueMap) {
debugArg("Value:", t.first, t.second, 0.0);
// dbgs() << "Value: " << *(t.first) << " " << t.second <<
// "\n";
});
#endif
assert(readyList.size() > 0);
mlir::Operation &op = *readyList.front();
readyList.pop_front();
/* for(mlir::Value out : op.getResults()) {
if(valueMap.count(out) != 0) {
LLVM_DEBUG(dbgs() << "OP: " << op << "\n");
for(auto t : valueMap) {
dbgs() << "Value: " << *(t.first) << " " << t.second << "\n";
}
assert(false);
}
}*/
// Execute handshake ops through ExecutableOpInterface
if (auto handshakeOp = dyn_cast<handshake::ExecutableOpInterface>(op)) {
std::vector<mlir::Value> scheduleList;
if (!handshakeOp.tryExecute(valueMap, memoryMap, timeMap, store,
scheduleList))
readyList.push_back(&op);
for (mlir::Value out : scheduleList)
scheduleUses(readyList, valueMap, out);
continue;
}
int64_t i = 0;
std::vector<Any> inValues(op.getNumOperands());
std::vector<Any> outValues(op.getNumResults());
bool reschedule = false;
LLVM_DEBUG(dbgs() << "OP: (" << op.getNumOperands() << "->"
<< op.getNumResults() << ")" << op << "\n");
double time;
for (mlir::Value in : op.getOperands()) {
if (valueMap.count(in) == 0) {
reschedule = true;
continue;
}
inValues[i] = valueMap[in];
time = std::max(time, timeMap[in]);
LLVM_DEBUG(debugArg("IN", in, inValues[i], timeMap[in]));
i++;
}
if (reschedule) {
LLVM_DEBUG(dbgs() << "Rescheduling data...\n");
readyList.push_back(&op);
continue;
}
// Consume the inputs.
for (mlir::Value in : op.getOperands()) {
valueMap.erase(in);
}
if (executeStdOp(op, inValues, outValues)) {
} else if (auto returnOp = dyn_cast<handshake::ReturnOp>(op)) {
for (unsigned i = 0; i < results.size(); i++) {
results[i] = inValues[i];
resultTimes[i] = timeMap[returnOp.getOperand(i)];
}
return;
//} else {
// implement function calls.
} else {
llvm_unreachable("Unknown operation!\n");
}
i = 0;
for (mlir::Value out : op.getResults()) {
LLVM_DEBUG(debugArg("OUT", out, outValues[i], time));
assert(outValues[i].hasValue());
valueMap[out] = outValues[i];
timeMap[out] = time + 1;
scheduleUses(readyList, valueMap, out);
i++;
}
instructionsExecuted++;
}
}
bool simulate(StringRef toplevelFunction, ArrayRef<std::string> inputArgs,
mlir::OwningModuleRef &module, mlir::MLIRContext &context) {
// The store associates each allocation in the program
// (represented by a int) with a vector of values which can be
// accessed by it. Currently values are assumed to be an integer.
std::vector<std::vector<Any>> store;
std::vector<double> storeTimes;
// The valueMap associates each SSA statement in the program
// (represented by a Value*) with it's corresponding value.
// Currently the value is assumed to be an integer.
llvm::DenseMap<mlir::Value, Any> valueMap;
// The timeMap associates each value with the time it was created.
llvm::DenseMap<mlir::Value, double> timeMap;
// We need three things in a function-type independent way.
// The type signature of the function.
mlir::FunctionType ftype;
// The arguments of the entry block.
mlir::Block::BlockArgListType blockArgs;
// The number of inputs to the function in the IR.
unsigned inputs;
unsigned outputs;
// The number of 'real' inputs. This avoids the dummy input
// associated with the handshake control logic for handshake
// functions.
unsigned realInputs;
unsigned realOutputs;
if (mlir::FuncOp toplevel =
module->lookupSymbol<mlir::FuncOp>(toplevelFunction)) {
ftype = toplevel.getType();
mlir::Block &entryBlock = toplevel.getBody().front();
blockArgs = entryBlock.getArguments();
// Get the primary inputs of toplevel off the command line.
inputs = ftype.getNumInputs();
realInputs = inputs;
outputs = ftype.getNumResults();
realOutputs = outputs;
} else if (handshake::FuncOp toplevel =
module->lookupSymbol<handshake::FuncOp>(toplevelFunction)) {
ftype = toplevel.getType();
mlir::Block &entryBlock = toplevel.getBody().front();
blockArgs = entryBlock.getArguments();
// Get the primary inputs of toplevel off the command line.
inputs = ftype.getNumInputs();
realInputs = inputs - 1;
outputs = ftype.getNumResults();
realOutputs = outputs - 1;
if (inputs == 0) {
errs() << "Function " << toplevelFunction << " is expected to have "
<< "at least one dummy argument.\n";
return 1;
}
if (outputs == 0) {
errs() << "Function " << toplevelFunction << " is expected to have "
<< "at least one dummy result.\n";
return 1;
}
// Implicit none argument
APInt apnonearg(1, 0);
valueMap[blockArgs[blockArgs.size() - 1]] = apnonearg;
} else {
llvm_unreachable("Function not supported.\n");
}
if (inputArgs.size() != realInputs) {
errs() << "Toplevel function " << toplevelFunction << " has " << realInputs
<< " actual arguments, but " << inputArgs.size()
<< " arguments were provided on the command line.\n";
return 1;
}
for (unsigned i = 0; i < realInputs; i++) {
mlir::Type type = ftype.getInput(i);
if (type.isa<mlir::MemRefType>()) {
// We require this memref type to be fully specified.
auto memreftype = type.dyn_cast<mlir::MemRefType>();
std::vector<Any> nothing;
std::string x;
unsigned buffer = allocateMemRef(memreftype, nothing, store, storeTimes);
valueMap[blockArgs[i]] = buffer;
timeMap[blockArgs[i]] = 0.0;
int64_t i = 0;
std::stringstream arg(inputArgs[i]);
while (!arg.eof()) {
getline(arg, x, ',');
store[buffer][i++] = readValueWithType(memreftype.getElementType(), x);
}
} else {
Any value = readValueWithType(type, inputArgs[i]);
valueMap[blockArgs[i]] = value;
timeMap[blockArgs[i]] = 0.0;
}
}
std::vector<Any> results(realOutputs);
std::vector<double> resultTimes(realOutputs);
if (mlir::FuncOp toplevel =
module->lookupSymbol<mlir::FuncOp>(toplevelFunction)) {
executeFunction(toplevel, valueMap, timeMap, results, resultTimes, store,
storeTimes);
} else if (handshake::FuncOp toplevel =
module->lookupSymbol<handshake::FuncOp>(toplevelFunction)) {
executeHandshakeFunction(toplevel, valueMap, timeMap, results, resultTimes,
store, storeTimes);
}
double time = 0.0;
for (unsigned i = 0; i < results.size(); i++) {
mlir::Type t = ftype.getResult(i);
outs() << printAnyValueWithType(t, results[i]) << " ";
time = std::max(resultTimes[i], time);
}
// Go back through the arguments and output any memrefs.
for (unsigned i = 0; i < realInputs; i++) {
mlir::Type type = ftype.getInput(i);
if (type.isa<mlir::MemRefType>()) {
// We require this memref type to be fully specified.
auto memreftype = type.dyn_cast<mlir::MemRefType>();
unsigned buffer = any_cast<unsigned>(valueMap[blockArgs[i]]);
auto elementType = memreftype.getElementType();
for (int j = 0; j < memreftype.getNumElements(); j++) {
if (j != 0)
outs() << ",";
outs() << printAnyValueWithType(elementType, store[buffer][j]);
}
outs() << " ";
}
}
outs() << "\n";
simulatedTime += (int)time;
return 0;
}

File diff suppressed because it is too large Load Diff