diff --git a/data/rtl-config-vhdl.json b/data/rtl-config-vhdl.json index 3d278943c..492ebda85 100644 --- a/data/rtl-config-vhdl.json +++ b/data/rtl-config-vhdl.json @@ -458,6 +458,26 @@ "name": "handshake.shrsi", "generator": "python $DYNAMATIC/tools/unit-generators/vhdl/vhdl-unit-generator.py -n $MODULE_NAME -o $OUTPUT_DIR/$MODULE_NAME.vhd -t shrsi -p bitwidth=$BITWIDTH extra_signals=$EXTRA_SIGNALS" }, + { + "name": "handshake.init", + "parameters": [ + { + "name": "INITIAL_VALUE", + "type": "unsigned" + } + ], + "generator": "python $DYNAMATIC/tools/unit-generators/vhdl/vhdl-unit-generator.py -n $MODULE_NAME -o $OUTPUT_DIR/$MODULE_NAME.vhd -t init -p bitwidth=$BITWIDTH extra_signals=$EXTRA_SIGNALS initial_value=$INITIAL_VALUE" + }, + { + "name": "handshake.repeating_init", + "parameters": [ + { + "name": "INITIAL_VALUE", + "type": "unsigned" + } + ], + "generator": "python $DYNAMATIC/tools/unit-generators/vhdl/vhdl-unit-generator.py -n $MODULE_NAME -o $OUTPUT_DIR/$MODULE_NAME.vhd -t repeating_init -p initial_value=$INITIAL_VALUE" + }, { "name": "handshake.shrui", "generator": "python $DYNAMATIC/tools/unit-generators/vhdl/vhdl-unit-generator.py -n $MODULE_NAME -o $OUTPUT_DIR/$MODULE_NAME.vhd -t shrui -p bitwidth=$BITWIDTH extra_signals=$EXTRA_SIGNALS" diff --git a/docs/DeveloperGuide/DynamaticFeaturesAndOptimizations/EagerlyElastic.md b/docs/DeveloperGuide/DynamaticFeaturesAndOptimizations/EagerlyElastic.md new file mode 100644 index 000000000..a15db43d5 --- /dev/null +++ b/docs/DeveloperGuide/DynamaticFeaturesAndOptimizations/EagerlyElastic.md @@ -0,0 +1,19 @@ +# Eagerly Elastic + +The eagerly elastic pass implements eager execution through two main Rewrites, specifically Rewrite A and Rewrite D from the [EagerlyElastic Paper](https://dl.acm.org/doi/pdf/10.1145/3748173.3779196). To execute this pass during compilation, it must be paired with fast token delivery by specifying both the `--fast-token-delivery` and `--eagerlyelastic` flags. + +When no buffer algorithm is specified, no speedup can be achieved. However, with the buffer algorithm `fpga20` the necessary buffers are placed and the desired speedup can be achieved. + +## Code Structure + +The pass begins by converting all conditional branches into suppressors. This means each branch discards tokens on its true path by routing them to a sink, while allowing tokens on the false path to proceed normally through the circuit. If a branch does not initially have a sink, it is split into two separate branches where one uses an inverted condition by adding a Not operation. Next, the pass enters its main phase by executing Rewrite A as many times as possible and then enters a loop driven by the user-defined `numRewriteD` parameter. This loop alternates between applying Rewrite D once and then running Rewrite A as often as possible to move all suppressors as far down the circuit as possible. The execution sequence follows the pattern: A, (D, A)^n. + +### Rewrite A +Rewrite A pushes suppressors past eligible downstream operations. An operation is eligible if it is pure and matched, such as arithmetic operations, and all of its other inputs either match the suppressor's condition or originate from a constant source. To find whether all inputs match the suppressor's condition, we have to traverse upwards through the IR and account for inversion (= Not operations) which can either be part of the suppressor condition or originate from the earlier pass. + +![Def](Figures/EagerlyElastic/RewriteA.png) + +### Rewrite D +Rewrite D identifies loop multiplexers and moves the suppressor past them. When a suppressor is connected to the true data path of the multiplexer, this rewrite builds an additional control structure. This structure is simplified in the code into a `RepeatingInitOp`, which implements the pink circuit seen in the picture below. This allows the suppressor to safely move past the mux. After Rewrite D has created its new structure and moved the suppressor past the mux, Rewrite A is applied to move this suppressor further down the loop. + +![Def](Figures/EagerlyElastic/RewriteD.png) diff --git a/docs/DeveloperGuide/DynamaticFeaturesAndOptimizations/Figures/EagerlyElastic/RewriteA.png b/docs/DeveloperGuide/DynamaticFeaturesAndOptimizations/Figures/EagerlyElastic/RewriteA.png new file mode 100644 index 000000000..f267892e0 Binary files /dev/null and b/docs/DeveloperGuide/DynamaticFeaturesAndOptimizations/Figures/EagerlyElastic/RewriteA.png differ diff --git a/docs/DeveloperGuide/DynamaticFeaturesAndOptimizations/Figures/EagerlyElastic/RewriteD.png b/docs/DeveloperGuide/DynamaticFeaturesAndOptimizations/Figures/EagerlyElastic/RewriteD.png new file mode 100644 index 000000000..c61a5fb33 Binary files /dev/null and b/docs/DeveloperGuide/DynamaticFeaturesAndOptimizations/Figures/EagerlyElastic/RewriteD.png differ diff --git a/experimental/include/experimental/Transforms/Passes.td b/experimental/include/experimental/Transforms/Passes.td index 1563a2314..88e617f57 100644 --- a/experimental/include/experimental/Transforms/Passes.td +++ b/experimental/include/experimental/Transforms/Passes.td @@ -126,6 +126,16 @@ def PredictedConstantDuplication : Pass<"predicted-constant-duplication", "mlir: }]; } - +def EagerlyElasticAD : Pass<"eagerly-elastic-a-d", "mlir::ModuleOp"> { + let summary = "Applies the Rewrites A and D of the EagerlyElastic Paper"; + let description = [{ + This pass prepares the conditional branches into suppressors and then applies Rewrite A + as often as possible. In the next step it applies Rewrite D and then again Rewrite A and + repeats this `numRewriteD` times. + }]; + let options = + [Option<"numRewriteD", "num-rewrite-d", "unsigned", "1", + "The number of times that Rewrite D should be applied.">]; +} #endif // EXPERIMENTAL_TRANSFORMS_PASSES_TD diff --git a/experimental/lib/Transforms/CMakeLists.txt b/experimental/lib/Transforms/CMakeLists.txt index 50cf1c085..ec418fced 100644 --- a/experimental/lib/Transforms/CMakeLists.txt +++ b/experimental/lib/Transforms/CMakeLists.txt @@ -21,3 +21,4 @@ add_subdirectory(Speculation) add_subdirectory(LSQSizing) add_subdirectory(Rigidification) add_subdirectory(Duplication) +add_subdirectory(EagerlyElastic) diff --git a/experimental/lib/Transforms/EagerlyElastic/CMakeLists.txt b/experimental/lib/Transforms/EagerlyElastic/CMakeLists.txt new file mode 100644 index 000000000..657d7290c --- /dev/null +++ b/experimental/lib/Transforms/EagerlyElastic/CMakeLists.txt @@ -0,0 +1,17 @@ +add_dynamatic_library(DynamaticEagerlyElastic + EagerlyElasticAD.cpp + + DEPENDS + DynamaticTransformsPassIncGen + + LINK_LIBS PUBLIC + MLIRIR + MLIRMemRefDialect + MLIRFuncDialect + MLIRSupport + MLIRTransformUtils + DynamaticHandshake + DynamaticSupport + DynamaticExperimentalSupport + DynamaticBufferPlacement + ) diff --git a/experimental/lib/Transforms/EagerlyElastic/EagerlyElasticAD.cpp b/experimental/lib/Transforms/EagerlyElastic/EagerlyElasticAD.cpp new file mode 100644 index 000000000..286df3985 --- /dev/null +++ b/experimental/lib/Transforms/EagerlyElastic/EagerlyElasticAD.cpp @@ -0,0 +1,416 @@ +#include "dynamatic/Analysis/NameAnalysis.h" +#include "dynamatic/Dialect/Handshake/HandshakeAttributes.h" +#include "dynamatic/Dialect/Handshake/HandshakeOps.h" +#include "dynamatic/Support/DynamaticPass.h" +#include "mlir/Pass/PassManager.h" +#include "mlir/Support/LogicalResult.h" + +// NOTE: The code wrapped in LLVM_DEBUG(...) is executed when +// - Dynamatic is built in debug mode +// - dynamatic-opt is called with `--debug` or `--debug-only=`. +#define DEBUG_TYPE "eagerly-elastic" + +using namespace dynamatic; +using namespace mlir; + +enum class BypassResult : bool { Ineligible = false, Eligible = true }; + +// [START Boilerplate code for the MLIR pass] +#include "experimental/Transforms/Passes.h" // IWYU pragma: keep +namespace dynamatic { +namespace experimental { +#define GEN_PASS_DEF_EAGERLYELASTICAD +#include "experimental/Transforms/Passes.h.inc" +} // namespace experimental +} // namespace dynamatic +// [END Boilerplate code for the MLIR pass] + +struct EagerlyElasticADPass + : public dynamatic::experimental::impl::EagerlyElasticADBase< + EagerlyElasticADPass> { + using EagerlyElasticADBase::EagerlyElasticADBase; + + void runOnOperation() override; + +private: + void setHandshakeAttrs(Attribute bbAttr, NameAnalysis &namer, + ArrayRef ops); + + DenseSet + prepareSuppressors(handshake::FuncOp funcOp, NameAnalysis &namer); + + BypassResult isEligibleForBypass(handshake::ConditionalBranchOp branchOp, + Operation *targetOp); + + void moveSuppressorPastOp(handshake::ConditionalBranchOp branchOp, + Operation *targetOp, + DenseSet &frontier, + NameAnalysis &namer, int DRewrite = 0); + + bool checkConditionsMatch(Value valA, Value valB, bool expectSamePolarity); + bool isSourced(Value value); + + void computeEagerly(DenseSet &frontier, + NameAnalysis &namer); + + void applyEnterLoopEagerly(handshake::MuxOp dataMux, + handshake::ConditionalBranchOp branchOp, + handshake::InitOp initOp, + DenseSet &frontier, + NameAnalysis &namer); + + void enterLoopEagerly(DenseSet &frontier, + NameAnalysis &namer); +}; + +/// Helper function to add the bbAttr and name to new operations. +void EagerlyElasticADPass::setHandshakeAttrs(Attribute bbAttr, + NameAnalysis &namer, + ArrayRef ops) { + for (Operation *op : ops) { + assert(op); + if (bbAttr) + op->setAttr("handshake.bb", bbAttr); + namer.setName(op); + } +} + +/// Identifies conditional branches and converts them into suppressors which +/// eliminate their token on the True Result path and returns a vector +/// containing all suppressors. +/// 1. Branches whose True Result is a sink are not changed +/// 2. Branches whose False Result is a sink are inverted +/// 3. Branches without sinks are split into two suppressors using an inverted +/// and the normal condition respectively. +DenseSet +EagerlyElasticADPass::prepareSuppressors(handshake::FuncOp funcOp, + NameAnalysis &namer) { + + // final vector containing all suppressors of the function + DenseSet suppressors; + + // iterate over all operations in the function to find the condbranchops + for (auto branchOp : funcOp.getOps()) { + suppressors.insert(branchOp); + + // a suppressor has to eliminate the token on a true signal + // if the true result has no uses it is a sink, as desired + if (branchOp.getTrueResult().use_empty()) { + continue; + } + + OpBuilder builder(branchOp); + auto bbAttr = branchOp->getAttr("handshake.bb"); + + // create inverted condition + Value condition = branchOp.getConditionOperand(); + Location loc = branchOp.getLoc(); + auto invertedCondition = builder.create(loc, condition); + // Tag the NotIOp so we know it belongs to a suppressor condition + invertedCondition->setAttr("is_suppressor_not", builder.getUnitAttr()); + setHandshakeAttrs(bbAttr, namer, {invertedCondition}); + + // the false result is a sink -> switch results with inverted condition + if (branchOp.getFalseResult().use_empty()) { + // update the branch condition and all downstream uses + branchOp.getConditionOperandMutable().assign(invertedCondition); + branchOp.getTrueResult().replaceAllUsesWith(branchOp.getFalseResult()); + continue; + } + + // convert suppressors without sinks into two suppressors + Value data = branchOp.getDataOperand(); + + // create a suppressor for true path with inverted condition + auto suppressorInverted = builder.create( + loc, invertedCondition, data); + setHandshakeAttrs(bbAttr, namer, {suppressorInverted}); + + // rewire the true path downstream consumers to the inverted suppressor + branchOp.getTrueResult().replaceAllUsesWith( + suppressorInverted.getFalseResult()); + suppressors.insert(suppressorInverted); + } + return suppressors; +} + +/// Checks whether two condition values originate from the same root source. +bool EagerlyElasticADPass::checkConditionsMatch(Value valA, Value valB, + bool expectSamePolarity) { + bool invA = false, invB = false; + + // Trace valA back through NotIOps + while (auto notOp = + dyn_cast_or_null(valA.getDefiningOp())) { + invA = !invA; + valA = notOp.getOperand(); + } + + // Trace valB back through NotIOps + while (auto notOp = + dyn_cast_or_null(valB.getDefiningOp())) { + invB = !invB; + valB = notOp.getOperand(); + } + + // They must originate from the exact same root wire + if (valA != valB) + return false; + + return expectSamePolarity ? (invA == invB) : (invA != invB); +} + +/// Recursive function to determine whether a value originates from a constant +/// source. +bool EagerlyElasticADPass::isSourced(Value value) { + Operation *definingOp = value.getDefiningOp(); + if (!definingOp) + return false; + + // No constant source possible here + if (isa(definingOp) or + isa(definingOp)) + return false; + + if (isa(value.getDefiningOp())) + return true; + + // If all operands of the defining operation are sourced, the value is also + // sourced. + return llvm::all_of(value.getDefiningOp()->getOperands(), + [this](Value v) { return isSourced(v); }); +} + +/// Checks if a suppressor (BranchOp) can be pushed past its downstream +/// operation. Eligible operations are pure and matched and need +/// to have the same condition on all their input operands or a source. +BypassResult EagerlyElasticADPass::isEligibleForBypass( + handshake::ConditionalBranchOp branchOp, Operation *targetOp) { + + // verify the targetOp is an eagerly executable unit + if (!isa(targetOp) || + ((isa(targetOp)) && + targetOp->getNumOperands() != 1)) { + return BypassResult::Ineligible; // reject anything that isn't a PM unit or + // a 1-input merge + } + + // loadOps receive address control independently, the can always be bypassed + if (isa(targetOp)) { + return BypassResult::Eligible; + } + + // only move past NotOps if they aren't part of a suppressor condition + if (targetOp->hasAttr("is_suppressor_not")) { + return BypassResult::Ineligible; + } + + // ensure all other inputs to the target op match this suppressor's condition + Value currentCond = branchOp.getConditionOperand(); + for (Value operand : targetOp->getOperands()) { + // if we have a branch it must have the same condition + if (auto siblingBranch = dyn_cast_or_null( + operand.getDefiningOp())) { + if (siblingBranch == branchOp) + continue; + // if the branch has more than one use, a fork needs to be created first + if (!siblingBranch.getFalseResult().hasOneUse()) { + return BypassResult::Ineligible; + } + // check whether condition matches indirectly + if (!checkConditionsMatch(currentCond, + siblingBranch.getConditionOperand(), true)) { + return BypassResult::Ineligible; + } + } else if (!isSourced(operand)) { + return BypassResult::Ineligible; + } + } + return BypassResult::Eligible; +} + +/// Move the suppressors past the following operation, targetOp. Erase all the +/// suppressors going into targetOp and create new suppressors on every output +/// of targetOp. +void EagerlyElasticADPass::moveSuppressorPastOp( + handshake::ConditionalBranchOp branchOp, Operation *targetOp, + DenseSet &frontier, NameAnalysis &namer, + int DRewrite) { + + LLVM_DEBUG(llvm::errs() << "Moving past: " + << targetOp->getAttr("handshake.name") << '\n'); + + Location loc = targetOp->getLoc(); + Value condition = branchOp.getConditionOperand(); + + // rewire the suppressor + for (OpOperand &use : targetOp->getOpOperands()) { + auto incomingBranch = dyn_cast_or_null( + use.get().getDefiningOp()); + if (!incomingBranch) + continue; + + // for Rewrite D, move only the suppressor connected to A of the Mux + if (incomingBranch != branchOp && DRewrite) { + continue; + } + + // rewire the target operand directly to the suppressor's input data + use.set(incomingBranch.getDataOperand()); + + // check if the suppressor has any remaining downstream uses + if (incomingBranch.getFalseResult().use_empty()) { + frontier.erase(incomingBranch); + incomingBranch->erase(); + } + } + + OpBuilder builder(targetOp); + builder.setInsertionPointAfter(targetOp); + + // place the new suppressors on every result of the targetOp + for (Value result : targetOp->getResults()) { + // If this is a LoadOp, skip index 0 (the address output going to the MC) + if (isa(targetOp) && + llvm::cast(result).getResultNumber() == 0) { + continue; + } + + auto newBranch = + builder.create(loc, condition, result); + setHandshakeAttrs(targetOp->getAttr("handshake.bb"), namer, {newBranch}); + + // reroute downstream consumers to look at the new branch's FalseResult + result.replaceAllUsesExcept(newBranch.getFalseResult(), newBranch); + frontier.insert(newBranch); + } +} + +/// Apply rewrite A from the eagerlyelastic paper as often as possible +/// This function loops over all conditional branches (the suppressors). For +/// every branch, it looks at the operations connected to its FalseResult to +/// check whether the branch can be moved past. Because moving these branches +/// adds and removes branches in our tracking set `frontier`, the function +/// restarts the loop after every change. +void EagerlyElasticADPass::computeEagerly( + DenseSet &frontier, NameAnalysis &namer) { + + bool frontierUpdated; + do { + frontierUpdated = false; + + for (auto branchOp : frontier) { + Value dataPath = branchOp.getFalseResult(); + Operation *eligibleTarget = nullptr; + + // search for any consumer of the suppressor that is eligible + for (Operation *user : dataPath.getUsers()) { + if (isEligibleForBypass(branchOp, user) == BypassResult::Eligible) { + eligibleTarget = user; + break; + } + } + + if (eligibleTarget) { + frontierUpdated = true; + moveSuppressorPastOp(branchOp, eligibleTarget, frontier, namer); + // frontier was mutated, break and restart the loop + break; + } + } + } while (frontierUpdated); +} + +/// Apply Rewrite D from the eagerlyelastic paper once by connecting a +/// repeatingInitOp to the circuit and then moving the suppressor past the mux. +void EagerlyElasticADPass::applyEnterLoopEagerly( + handshake::MuxOp dataMux, handshake::ConditionalBranchOp branchOp, + handshake::InitOp initOp, + DenseSet &frontier, NameAnalysis &namer) { + + Location loc = dataMux->getLoc(); + auto bbAttr = dataMux->getAttr("handshake.bb"); + LLVM_DEBUG(llvm::errs() << "Perform Rewrite D for: " + << dataMux->getAttr("handshake.name") << '\n'); + Value conditionC = initOp.getOperand(); + + // build the additional control structure + OpBuilder builder(dataMux); + + auto repeatingInit = + builder.create(loc, conditionC, 1); + setHandshakeAttrs(bbAttr, namer, {repeatingInit}); + Value specOutput = repeatingInit.getResult(); + + // connect the repeatingInit to the loop init + initOp.getOperandMutable().assign(specOutput); + + // update the suppressor's condition - must be inverted relative to the init + auto notOp = dyn_cast_or_null( + branchOp.getConditionOperand().getDefiningOp()); + if (notOp && notOp.getResult().hasOneUse()) + notOp.getOperandMutable().assign(specOutput); + else { // create a new isolated NotIOp + builder.setInsertionPoint(notOp ? notOp : branchOp); + auto newNotOp = + builder.create(branchOp.getLoc(), specOutput); + setHandshakeAttrs(bbAttr, namer, {newNotOp}); + newNotOp->setAttr("is_suppressor_not", builder.getUnitAttr()); + branchOp.getConditionOperandMutable().assign(newNotOp.getResult()); + } + + // move suppressor past the mux + moveSuppressorPastOp(branchOp, dataMux, frontier, namer, 1); +} + +// Identify all branches before loop muxes and apply Rewrite D from the +// eagerlyelastic paper on them. +void EagerlyElasticADPass::enterLoopEagerly( + DenseSet &frontier, NameAnalysis &namer) { + + SmallVector initialFrontier(frontier.begin(), + frontier.end()); + for (auto branchOp : initialFrontier) { + for (auto *nextOp : branchOp.getFalseResult().getUsers()) { + if (auto mux = dyn_cast(nextOp)) { + // identify loops (mux connected to init) + auto init = dyn_cast_or_null( + mux.getSelectOperand().getDefiningOp()); + + if (init) { + if (!checkConditionsMatch(branchOp.getConditionOperand(), + init.getOperand(), false)) { + continue; + } + // check whether the suppressor is connected to path A of the mux + if (mux.getDataOperands()[1] == branchOp.getFalseResult()) { + applyEnterLoopEagerly(mux, branchOp, init, frontier, namer); + // break; + } else + continue; + } + } + } + } +} + +void EagerlyElasticADPass::runOnOperation() { + ModuleOp modOp = getOperation(); + NameAnalysis &namer = getAnalysis(); + + handshake::FuncOp funcOp = + cast(&modOp.getBodyRegion().front().front()); + assert(funcOp && "No funcOp found!"); + + // identify and prepare suppressors and return a list of all of them + auto frontier = prepareSuppressors(funcOp, namer); + + computeEagerly(frontier, namer); + + for (unsigned i = 0; i < numRewriteD; i++) { + enterLoopEagerly(frontier, namer); + computeEagerly(frontier, namer); + } +} diff --git a/include/dynamatic/Dialect/Handshake/HandshakeOps.td b/include/dynamatic/Dialect/Handshake/HandshakeOps.td index e36441571..453559099 100644 --- a/include/dynamatic/Dialect/Handshake/HandshakeOps.td +++ b/include/dynamatic/Dialect/Handshake/HandshakeOps.td @@ -2329,5 +2329,40 @@ def ValidMergerOp : Handshake_Op<"valid_merger",[ }]; } +def RepeatingInitOp : Handshake_Op<"repeating_init", [ + HasClock, SameOperandsAndResultType, + IsIntSizedChannel<1, "operand">, + IsIntSizedChannel<1, "result"> +]> { + let summary = "repeating init operation"; + let description = [{ + A single-slot buffer which contains a token in its reset state. + Similar to a buffer, its timing behavior is configurable. + }]; + + let arguments = (ins ChannelType:$operand, UI1Attr:$initialValue); + let results = (outs ChannelType:$result); + + let extraClassDeclaration = [{ + static constexpr ::llvm::StringLiteral INITIAL_VALUE_ATTR_NAME = "INITIAL_VALUE", + TIMING_ATTR_NAME = "TIMING"; + }]; + + let assemblyFormat = [{ + $operand attr-dict `:` type($operand) + }]; + + let extraClassDefinition = [{ + ::mlir::FailureOr<::llvm::SmallVector<::mlir::NamedAttribute>> + RepeatingInitOp::getRTLParameters() { + ::mlir::MLIRContext *ctx = (*this)->getContext(); + return ::llvm::SmallVector<::mlir::NamedAttribute>{ + {::mlir::StringAttr::get(ctx, "INITIAL_VALUE"), + getInitialValueAttr()} + }; + } + }]; +} + #endif // DYNAMATIC_DIALECT_HANDSHAKE_HANDSHAKE_OPS_TD diff --git a/integration-test/simple_example_2/simple_example_2.c b/integration-test/simple_example_2/simple_example_2.c new file mode 100644 index 000000000..72e49a563 --- /dev/null +++ b/integration-test/simple_example_2/simple_example_2.c @@ -0,0 +1,27 @@ +#include "simple_example_2.h" +#include "dynamatic/Integration.h" + +void simple_example_2(inout_int_t a[N], int c) { + int cond = c > 0; + int i = 0; + do { + if (cond) { + int x = c * 3; + a[i] = x; + } + i++; + } while (i < N); +} + +int main(void) { + in_int_t a[N]; + in_int_t b[N]; + int c; + for (unsigned j = 0; j < N; ++j) { + a[j] = j; + b[j] = j; + } + + CALL_KERNEL(simple_example_2, a, c); + return 0; +} diff --git a/integration-test/simple_example_2/simple_example_2.h b/integration-test/simple_example_2/simple_example_2.h new file mode 100644 index 000000000..eefb28ddf --- /dev/null +++ b/integration-test/simple_example_2/simple_example_2.h @@ -0,0 +1,7 @@ +#define N 100 + +typedef int in_int_t; +typedef int out_int_t; +typedef int inout_int_t; + +void simple_example_2(inout_int_t a[N], int c); diff --git a/lib/Conversion/HandshakeToHW/HandshakeToHW.cpp b/lib/Conversion/HandshakeToHW/HandshakeToHW.cpp index 7d51f52ba..3003d44c6 100644 --- a/lib/Conversion/HandshakeToHW/HandshakeToHW.cpp +++ b/lib/Conversion/HandshakeToHW/HandshakeToHW.cpp @@ -763,6 +763,21 @@ ModuleDiscriminator::ModuleDiscriminator(Operation *op) { addUnsigned("DATA_WIDTH", resType.getElementTypeBitWidth()); addUnsigned("SIZE", resType.getNumElements()); }) + .Case([&](handshake::InitOp initOp) { + auto paramsAttr = + initOp->getAttrOfType("hw.parameters"); + if (paramsAttr) { + auto initTokenAttr = + paramsAttr.get("INIT_TOKEN").dyn_cast_or_null(); + int initialValue = + (initTokenAttr && initTokenAttr.getValue()) ? 1 : 0; + addUnsigned("INITIAL_VALUE", initialValue); + } else + addUnsigned("INITIAL_VALUE", 0); + }) + .Case([&](handshake::RepeatingInitOp initOp) { + addUnsigned("INITIAL_VALUE", initOp.getInitialValue()); + }) .Default([&](auto) { op->emitError() << "This operation cannot be lowered to RTL " "due to a lack of an RTL implementation for it."; @@ -2188,6 +2203,7 @@ class HandshakeToHWPass ConvertToHWInstance, ConvertToHWInstance, ConvertToHWInstance, + ConvertToHWInstance, // Arith operations ConvertToHWInstance, diff --git a/lib/Support/RTL/RTL.cpp b/lib/Support/RTL/RTL.cpp index 47a4ae33b..47fd524bf 100644 --- a/lib/Support/RTL/RTL.cpp +++ b/lib/Support/RTL/RTL.cpp @@ -337,7 +337,9 @@ LogicalResult RTLMatch::registerBitwidthParameter(hw::HWModuleExternOp &modOp, handshakeOp == "handshake.spec_commit" || handshakeOp == "handshake.spec_save_commit" || handshakeOp == "handshake.sharing_wrapper" || - handshakeOp == "handshake.non_spec" + handshakeOp == "handshake.non_spec" || + handshakeOp == "handshake.init" || + handshakeOp == "handshake.repeating_init" // clang-format on ) { // Default @@ -493,7 +495,9 @@ RTLMatch::registerExtraSignalParameters(hw::HWModuleExternOp &modOp, handshakeOp == "handshake.load" || handshakeOp == "handshake.store" || handshakeOp == "handshake.spec_commit" || - handshakeOp == "handshake.speculating_branch" + handshakeOp == "handshake.speculating_branch" || + handshakeOp == "handshake.init" || + handshakeOp == "handshake.repeating_init" // clang-format on ) { diff --git a/lib/Transforms/BufferPlacement/FPGA20Buffers.cpp b/lib/Transforms/BufferPlacement/FPGA20Buffers.cpp index f7c084714..7d912efdb 100644 --- a/lib/Transforms/BufferPlacement/FPGA20Buffers.cpp +++ b/lib/Transforms/BufferPlacement/FPGA20Buffers.cpp @@ -76,6 +76,11 @@ void FPGA20Buffers::extractResult(BufferPlacement &placement) { result.numOneSlotR = 1; } + if (srcOp && isa(srcOp)) { + result.numOneSlotDV = 1; + result.numOneSlotR = 2; + } + placement[channel] = result; } diff --git a/lib/Transforms/BufferPlacement/Utils/BufferPlacementMILP.cpp b/lib/Transforms/BufferPlacement/Utils/BufferPlacementMILP.cpp index 840c17472..fd125afcb 100644 --- a/lib/Transforms/BufferPlacement/Utils/BufferPlacementMILP.cpp +++ b/lib/Transforms/BufferPlacement/Utils/BufferPlacementMILP.cpp @@ -762,6 +762,7 @@ void BufferPlacementMILP::addSteadyStateReachabilityConstraints(CFDFC &cfdfc) { // get if the channel is a backedge as an integer unsigned backedge = cfdfc.backedges.contains(channel) ? 1 : 0; + unsigned fromRepInit = dyn_cast(srcOp) ? 1 : 0; // If the channel isn't a backedge, its steady-state occupancy // equals the difference between the fluid retiming variables @@ -772,7 +773,8 @@ void BufferPlacementMILP::addSteadyStateReachabilityConstraints(CFDFC &cfdfc) { // // occupancy of the channel places a limit on throughput // if a buffer breaking data and valid is placed on the channel - model->addConstr(chTokenOccupancy == backedge + retDst - retSrc, + model->addConstr(chTokenOccupancy == + backedge + fromRepInit + retDst - retSrc, "throughput_channelRetiming"); } } diff --git a/tools/dynamatic-opt/CMakeLists.txt b/tools/dynamatic-opt/CMakeLists.txt index 9cbe81f35..918d584d0 100644 --- a/tools/dynamatic-opt/CMakeLists.txt +++ b/tools/dynamatic-opt/CMakeLists.txt @@ -17,6 +17,7 @@ target_link_libraries(dynamatic-opt DynamaticLowerScfToCf DynamaticBufferPlacement DynamaticLSQSizing + DynamaticEagerlyElastic DynamaticSpeculation DynamaticRigidification DynamaticFormalPropertyAnnotation diff --git a/tools/dynamatic/dynamatic.cpp b/tools/dynamatic/dynamatic.cpp index 8a8ba017d..5dcc797a5 100644 --- a/tools/dynamatic/dynamatic.cpp +++ b/tools/dynamatic/dynamatic.cpp @@ -308,6 +308,7 @@ class Compile : public Command { "enable-duplication"; static constexpr llvm::StringLiteral CALCULATE_PATH_DELAYS = "calculate-path-delays"; + static constexpr llvm::StringLiteral EAGERLYELASTIC = "eagerlyelastic"; Compile(FrontendState &state) : Command("compile", @@ -351,6 +352,8 @@ class Compile : public Command { "After buffer placement, re-run the MILP with the buffering " "decisions locked in to calculate the path delays the MILP " "believes are present in the circuit."}); + addFlag({EAGERLYELASTIC, + "Enable eager execution. Requires fast token delivery."}); } CommandResult execute(CommandArguments &args) override; @@ -797,13 +800,15 @@ CommandResult Compile::execute(CommandArguments &args) { args.flags.contains(ENABLE_DUPLICATION) ? "1" : "0"; std::string calculatePathDelays = args.flags.contains(CALCULATE_PATH_DELAYS) ? "1" : "0"; + std::string eagerlyelastic = args.flags.contains(EAGERLYELASTIC) ? "1" : "0"; return execCmd(script, state.dynamaticPath, state.getKernelDir(), state.getOutputDir(), state.getKernelName(), buffers, floatToString(state.targetCP, 3), sharing, state.fpUnitsGenerator, rigidification, kInduction, disableLSQ, fastTokenDelivery, milpSolver, straightToQueue, speculation, - enableShortCircuit, enableDuplication, calculatePathDelays); + enableShortCircuit, enableDuplication, calculatePathDelays, + eagerlyelastic); } CommandResult WriteHDL::execute(CommandArguments &args) { diff --git a/tools/dynamatic/scripts/compile.sh b/tools/dynamatic/scripts/compile.sh index 55e790bcb..4dbe693cc 100755 --- a/tools/dynamatic/scripts/compile.sh +++ b/tools/dynamatic/scripts/compile.sh @@ -25,6 +25,7 @@ SPECULATION=${15} ENABLE_SHORT_CIRCUIT=${16} ENABLE_DUPLICATION=${17:-0} CALCULATE_PATH_DELAYS=${18} +EAGERLYELASTIC=${19} LLVM=$DYNAMATIC_DIR/llvm-project DYNAMATIC_BINS=$DYNAMATIC_DIR/bin @@ -57,6 +58,7 @@ F_CF_DYN_TRANSFORMED_MEM_DEP_MARKED="$COMP_DIR/cf_transformed_mem_interface_mark F_PROFILER_BIN="$COMP_DIR/$KERNEL_NAME-profile" F_PROFILER_INPUTS="$COMP_DIR/profiler-inputs.txt" F_HANDSHAKE="$COMP_DIR/handshake.mlir" +F_EAGERLYELASTIC="$COMP_DIR/handshake_eagerlyelastic.mlir" F_HANDSHAKE_TRANSFORMED="$COMP_DIR/handshake_transformed.mlir" F_HANDSHAKE_SPECULATION="$COMP_DIR/handshake_speculation.mlir" F_HANDSHAKE_BUFFERED="$COMP_DIR/handshake_buffered.mlir" @@ -294,6 +296,20 @@ else exit_on_fail "Failed to compile cf to handshake" "Compiled cf to handshake" fi +# do eager execution +if [[ $EAGERLYELASTIC -ne 0 ]]; then + # error out immediately if FTD is disabled but eagerlyelastic was requested + if [[ $FAST_TOKEN_DELIVERY -eq 0 ]]; then + echo "Error: Eager execution requires Fast Token Delivery enabled" + else + "$DYNAMATIC_OPT_BIN" "$F_HANDSHAKE" \ + --eagerly-elastic-a-d="num-rewrite-d=1" \ + > "$F_EAGERLYELASTIC" + exit_on_fail "Failed to apply eager execution" "Applied eager execution" + F_HANDSHAKE="$F_EAGERLYELASTIC" + fi +fi + if [[ $STRAIGHT_TO_QUEUE -ne 0 ]]; then echo_info "Using FPGA'23 for LSQ connection" diff --git a/tools/unit-generators/vhdl/generators/handshake/repeating_init.py b/tools/unit-generators/vhdl/generators/handshake/repeating_init.py new file mode 100644 index 000000000..7e4e742a2 --- /dev/null +++ b/tools/unit-generators/vhdl/generators/handshake/repeating_init.py @@ -0,0 +1,43 @@ +def generate_repeating_init(name, params): + initial_value = params["initial_value"] + + return f""" +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +-- Entity of repeating_init +entity {name} is + port ( + clk, rst : in std_logic; + ins : in std_logic_vector(0 downto 0); + ins_valid : in std_logic; + ins_ready : out std_logic; + outs : out std_logic_vector(0 downto 0); + outs_valid : out std_logic; + outs_ready : in std_logic + ); +end entity; + +-- Architecture of repeating_init +architecture arch of {name} is + signal emit_init : std_logic; +begin + process(clk) + begin + if rising_edge(clk) then + if rst = '1' then + emit_init <= '1'; + else + if outs_valid and outs_ready then + emit_init <= not outs(0); + end if; + end if; + end if; + end process; + + outs <= "{initial_value}" when emit_init else ins; + outs_valid <= emit_init or ins_valid; + ins_ready <= not emit_init and outs_ready; +end architecture; +""" diff --git a/tools/unit-generators/vhdl/vhdl-unit-generator.py b/tools/unit-generators/vhdl/vhdl-unit-generator.py index 0d1ee570f..7bb3f2252 100644 --- a/tools/unit-generators/vhdl/vhdl-unit-generator.py +++ b/tools/unit-generators/vhdl/vhdl-unit-generator.py @@ -117,6 +117,7 @@ def main(generators): generators.add("handshake.speculation", "speculating_branch") generators.add("handshake.speculation", "speculator") generators.add("handshake.speculation", "non_spec") + generators.add("handshake", "repeating_init") generators.add("support", "mem_to_bram") generators.add("handshake", "extui") generators.add("handshake", "shli")