diff --git a/experimental/include/experimental/Transforms/Passes.td b/experimental/include/experimental/Transforms/Passes.td index b7a61d56f2..a54365032c 100644 --- a/experimental/include/experimental/Transforms/Passes.td +++ b/experimental/include/experimental/Transforms/Passes.td @@ -60,6 +60,17 @@ def HandshakeSpeculation : DynamaticPass<"handshake-speculation"> { "specified in the JSON-formatted file.">]; } +def HandshakeSpecPostBuffer : DynamaticPass<"handshake-spec-post-buffer"> { + let summary = "Post-buffering speculation pass"; + let description = [{ + Speculation integration requires some steps to be performed after the + buffer placement pass: + - Finalize speculative units + }]; + let options = [ + ]; +} + def HandshakePlaceBuffersCustom : DynamaticPass<"handshake-placebuffers-custom"> { let summary = "Place buffers on specific channels"; let description = [{ Placing a single buffer on a specific output channel of diff --git a/experimental/include/experimental/Transforms/Speculation/PlacementFinder.h b/experimental/include/experimental/Transforms/Speculation/PlacementFinder.h index d21d7e3d9c..f33b4697df 100644 --- a/experimental/include/experimental/Transforms/Speculation/PlacementFinder.h +++ b/experimental/include/experimental/Transforms/Speculation/PlacementFinder.h @@ -22,7 +22,6 @@ namespace dynamatic { namespace experimental { -namespace speculation { class PlacementFinder { @@ -63,7 +62,6 @@ class PlacementFinder { LogicalResult findSaveCommitsTraversal(llvm::DenseSet &visited, Operation *currOp); }; -} // namespace speculation } // namespace experimental } // namespace dynamatic diff --git a/experimental/include/experimental/Transforms/Speculation/SpeculationPlacement.h b/experimental/include/experimental/Transforms/Speculation/SpeculationPlacement.h index c1dd6840f4..a548c88a6f 100644 --- a/experimental/include/experimental/Transforms/Speculation/SpeculationPlacement.h +++ b/experimental/include/experimental/Transforms/Speculation/SpeculationPlacement.h @@ -30,7 +30,6 @@ namespace dynamatic { namespace experimental { -namespace speculation { struct PlacementOperand { std::string opName; @@ -100,7 +99,6 @@ class SpeculationPlacements { void setSaveCommitsFifoDepth(unsigned int depth); }; -} // namespace speculation } // namespace experimental } // namespace dynamatic diff --git a/experimental/lib/Transforms/Speculation/CMakeLists.txt b/experimental/lib/Transforms/Speculation/CMakeLists.txt index c113de5da9..fa5bb59409 100644 --- a/experimental/lib/Transforms/Speculation/CMakeLists.txt +++ b/experimental/lib/Transforms/Speculation/CMakeLists.txt @@ -1,6 +1,9 @@ +include_directories(${DYNAMATIC_SOURCE_DIR}/experimental/include/experimental/Transforms/Speculation) + add_dynamatic_library(DynamaticSpeculation SpeculationPlacement.cpp HandshakeSpeculation.cpp + HandshakeSpecPostBuffer.cpp PlacementFinder.cpp DEPENDS diff --git a/experimental/lib/Transforms/Speculation/HandshakeSpecPostBuffer.cpp b/experimental/lib/Transforms/Speculation/HandshakeSpecPostBuffer.cpp new file mode 100644 index 0000000000..ff3ca851f6 --- /dev/null +++ b/experimental/lib/Transforms/Speculation/HandshakeSpecPostBuffer.cpp @@ -0,0 +1,231 @@ +// [START Boilerplate code for the MLIR pass] +#include "experimental/Transforms/Passes.h" // IWYU pragma: keep +namespace dynamatic { +namespace experimental { +#define GEN_PASS_DEF_HANDSHAKESPECPOSTBUFFER +#include "experimental/Transforms/Passes.h.inc" +} // namespace experimental +} // namespace dynamatic +// [END Boilerplate code for the MLIR pass] + +#include "dynamatic/Dialect/Handshake/HandshakeInterfaces.h" +#include "dynamatic/Dialect/Handshake/HandshakeOps.h" +#include "dynamatic/Support/CFG.h" +#include "dynamatic/Support/LLVM.h" +#include "mlir/IR/AsmState.h" +#include "mlir/IR/Builders.h" +#include "mlir/IR/Diagnostics.h" +#include "mlir/IR/OperationSupport.h" +#include "mlir/IR/Value.h" +#include "mlir/Support/LLVM.h" +#include "mlir/Support/LogicalResult.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/Support/ErrorHandling.h" + +using namespace llvm::sys; +using namespace mlir; +using namespace dynamatic; +using namespace dynamatic::handshake; +using namespace dynamatic::experimental; + +namespace { + +struct HandshakeSpecPostBufferPass + : public dynamatic::experimental::impl:: + HandshakeSpecPostBufferBase { + using HandshakeSpecPostBufferBase::HandshakeSpecPostBufferBase; + void runDynamaticPass() override; +}; + +static Operation *getUserSkippingBuffers(Value val) { + Operation *uniqueUser = *val.getUsers().begin(); + if (auto bufOp = dyn_cast(uniqueUser)) { + return getUserSkippingBuffers(bufOp.getResult()); + } + return uniqueUser; +} + +static handshake::ConditionalBranchOp findControlBranch(FuncOp funcOp, + unsigned bb) { + for (auto condBrOp : funcOp.getOps()) { + if (auto brBB = getLogicBB(condBrOp); !brBB || brBB != bb) + continue; + + for (Value result : condBrOp->getResults()) { + for (Operation *user : result.getUsers()) { + + if (isBackedge(result, user)) + return condBrOp; + } + } + } + + return nullptr; +} + +static FailureOr constructSaveCommitControl(SpeculatorOp speculator) { + OpBuilder builder(speculator.getContext()); + builder.setInsertionPoint(speculator); + unsigned specBB = getLogicBB(speculator).value(); + + // Construct Save Commit Control + auto branchDiscardCondNonMisspec = cast( + getUserSkippingBuffers(speculator.getSCIsMisspec())); + + // This branch will propagate the signal SCCommitControl according to + // the control branch condition, which comes from branchDiscardCondNonMisSpec + auto branchReplicated = builder.create( + branchDiscardCondNonMisspec.getLoc(), + branchDiscardCondNonMisspec.getTrueResult(), + speculator.getSCCommitCtrl()); + setBB(branchReplicated, specBB); + + // We create a Merge operation to join SCCSaveCtrl and SCCommitCtrl signals + SmallVector mergeOperands; + mergeOperands.push_back(speculator.getSCSaveCtrl()); + + ConditionalBranchOp controlBranch = + findControlBranch(speculator->getParentOfType(), specBB); + if (controlBranch == nullptr) { + speculator->emitError() + << "Could not find backedge within speculation bb: " << specBB << ".\n"; + return failure(); + } + + // Helper function to check if a value leads to a Backedge + auto isBranchBackedge = [&](Value result) { + return llvm::any_of(result.getUsers(), [&](Operation *user) { + return isBackedge(result, user); + }); + }; + + // We need to send the control token to the same path that the speculative + // token followed. Hence, if any branch output leads to a backedge, replicate + // the branch in the SaveCommit control path. + + // Check if trueResult of controlBranch leads to a backedge (loop) + if (isBranchBackedge(controlBranch.getTrueResult())) { + mergeOperands.push_back(branchReplicated.getTrueResult()); + } + // Check if falseResult of controlBranch leads to a backedge (loop) + else if (isBranchBackedge(controlBranch.getFalseResult())) { + mergeOperands.push_back(branchReplicated.getFalseResult()); + } + // If neither trueResult nor falseResult leads to a backedge, handle the error + else { + controlBranch->emitError() + << "Could not find the backedge in the Control Branch " << specBB + << "\n"; + return failure(); + } + + // All the inputs to the merge operation are ready + auto mergeOp = builder.create(branchReplicated.getLoc(), + mergeOperands); + mergeOp->setAttr("specv1_sc_merge", builder.getUnitAttr()); + setBB(mergeOp, specBB); + + return mergeOp.getResult(); +} + +static LogicalResult placeAdditionalBuffers(SpeculatorOp speculator) { + FuncOp funcOp = speculator->getParentOfType(); + OpBuilder builder(funcOp.getContext()); + + for (auto commitOp : funcOp.getOps()) { + builder.setInsertionPoint(commitOp); + // To maintain high throughput: commit op *sometimes* joins a control from + // the iteration `i` with data from the iteration `i-1`. We need a 1-slot + // buffer to hold the control signal + auto bufOp1 = + builder.create(builder.getUnknownLoc(), commitOp.getCtrl(), 1, + BufferType::FIFO_BREAK_NONE); + inheritBB(commitOp, bufOp1); + bufOp1.getOperand().replaceAllUsesExcept(bufOp1.getResult(), bufOp1); + } + + // To avoid deadlock: on misspeculation, `kill` is only sent after `resend` is + // accepted. Buffering algorithm ignores `resend`, and insufficient buffering + // may cause deadlock. We buffer dataOut and commitCtrl of speculator and the + // merged save-commit control for a `resend` iteration. + + auto bufDataOut = + builder.create(builder.getUnknownLoc(), speculator.getDataOut(), + 1, BufferType::FIFO_BREAK_NONE); + inheritBB(speculator, bufDataOut); + speculator.getDataOut().replaceAllUsesExcept(bufDataOut.getResult(), + bufDataOut); + + auto bufCommitCtrl = builder.create(builder.getUnknownLoc(), + speculator.getCommitCtrl(), 1, + BufferType::FIFO_BREAK_NONE); + inheritBB(speculator, bufCommitCtrl); + speculator.getCommitCtrl().replaceAllUsesExcept(bufCommitCtrl.getResult(), + bufCommitCtrl); + + MergeOp merge; + bool mergeFound = false; + for (auto candidate : funcOp.getOps()) { + if (candidate->hasAttr("specv1_sc_merge")) { + merge = candidate; + mergeFound = true; + break; + } + } + if (!mergeFound) { + funcOp.emitError("specv1_sc_merge not found"); + return failure(); + } + auto bufSCControl = + builder.create(builder.getUnknownLoc(), merge.getResult(), 1, + BufferType::FIFO_BREAK_NONE); + inheritBB(speculator, bufSCControl); + merge.getResult().replaceAllUsesExcept(bufSCControl.getResult(), + bufSCControl); + + return success(); +} + +void HandshakeSpecPostBufferPass::runDynamaticPass() { + ModuleOp modOp = getOperation(); + + // Support only one funcOp + assert(std::distance(modOp.getOps().begin(), + modOp.getOps().end()) == 1 && + "Expected a single FuncOp in the module"); + + FuncOp funcOp = *modOp.getOps().begin(); + + SpecPreBufferOp1 specOp1 = *funcOp.getOps().begin(); + SpecPreBufferOp2 specOp2 = *funcOp.getOps().begin(); + + unsigned specBB = getLogicBB(specOp1).value(); + + OpBuilder builder(&getContext()); + builder.setInsertionPoint(specOp1); + + // Build the (post-buffer) SpeculatorOp + SpeculatorOp speculator = builder.create( + specOp1.getLoc(), specOp1.getDataOut().getType(), specOp2.getDataIn(), + specOp1.getTrigger(), specOp1.getFifoDepth()); + setBB(speculator, specBB); + + specOp1.getDataOut().replaceAllUsesWith(speculator.getDataOut()); + specOp2.getCommitCtrl().replaceAllUsesWith(speculator.getCommitCtrl()); + specOp2.getSCIsMisspec().replaceAllUsesWith(speculator.getSCIsMisspec()); + + auto scControl = constructSaveCommitControl(speculator); + if (failed(scControl)) + return signalPassFailure(); + + specOp1.getSCSaveCtrl().replaceAllUsesWith(scControl.value()); + + specOp1->erase(); + specOp2->erase(); + + if (failed(placeAdditionalBuffers(speculator))) + return signalPassFailure(); +} + +} // namespace diff --git a/experimental/lib/Transforms/Speculation/HandshakeSpeculation.cpp b/experimental/lib/Transforms/Speculation/HandshakeSpeculation.cpp index 28eaa480ff..e7631cd7f1 100644 --- a/experimental/lib/Transforms/Speculation/HandshakeSpeculation.cpp +++ b/experimental/lib/Transforms/Speculation/HandshakeSpeculation.cpp @@ -11,7 +11,6 @@ //===----------------------------------------------------------------------===// #include "dynamatic/Analysis/NameAnalysis.h" -#include "dynamatic/Dialect/Handshake/HandshakeAttributes.h" #include "dynamatic/Dialect/Handshake/HandshakeInterfaces.h" #include "dynamatic/Dialect/Handshake/HandshakeOps.h" #include "dynamatic/Dialect/Handshake/HandshakeTypes.h" @@ -19,7 +18,6 @@ #include "dynamatic/Support/DynamaticPass.h" #include "experimental/Transforms/Speculation/PlacementFinder.h" #include "experimental/Transforms/Speculation/SpeculationPlacement.h" -#include "mlir/IR/Attributes.h" #include "mlir/IR/BuiltinOps.h" #include "mlir/IR/MLIRContext.h" #include "mlir/IR/OperationSupport.h" @@ -35,7 +33,6 @@ using namespace mlir; using namespace dynamatic; using namespace dynamatic::handshake; using namespace dynamatic::experimental; -using namespace dynamatic::experimental::speculation; // [START Boilerplate code for the MLIR pass] #include "experimental/Transforms/Passes.h" // IWYU pragma: keep @@ -59,7 +56,8 @@ struct HandshakeSpeculationPass private: SpeculationPlacements placements; - SpeculatorOp specOp; + SpecPreBufferOp1 specOp1; + SpecPreBufferOp2 specOp2; // In the placeCommits method, commit units are temporarily connected to // this value as an alternative to control signals and are subsequently @@ -94,7 +92,6 @@ struct HandshakeSpeculationPass // their type requirements. LogicalResult addNonSpecOp(); }; -} // namespace // The list item to trace the branches that need to be replicated struct BranchTracingItem { @@ -189,7 +186,7 @@ static std::optional findExistingBranch(Value condition, // branches it finds in the way. It stops at commits and connects them to the // newly created path with value ctrlSignal static void -routeCommitControlRecursive(MLIRContext *ctx, SpeculatorOp &specOp, +routeCommitControlRecursive(MLIRContext *ctx, SpecPreBufferOp2 &specOp, llvm::DenseSet &arrived, OpOperand &currOpOperand, std::vector &branchTrace) { @@ -241,7 +238,7 @@ routeCommitControlRecursive(MLIRContext *ctx, SpeculatorOp &specOp, /*trueResultType=*/conditionOperand.getType(), /*falseResultType=*/conditionOperand.getType(), /*specTag=*/valueForSpecTag, conditionOperand); - inheritBB(specOp, *branchDiscardNonSpec); + inheritBB(valueForSpecTag.getDefiningOp(), *branchDiscardNonSpec); } std::optional branchReplicated = @@ -254,7 +251,7 @@ routeCommitControlRecursive(MLIRContext *ctx, SpeculatorOp &specOp, branchDiscardNonSpec->getLoc(), /*condition=*/branchDiscardNonSpec->getTrueResult(), /*data=*/ctrlSignal); - inheritBB(specOp, *branchReplicated); + inheritBB(*branchDiscardNonSpec, *branchReplicated); } // Update ctrlSignal @@ -314,17 +311,17 @@ LogicalResult HandshakeSpeculationPass::routeCommitControl() { llvm::DenseSet arrived; std::vector branchTrace; // Start traversal from the speculator - for (OpOperand &succOpOperand : specOp.getDataOut().getUses()) { - routeCommitControlRecursive(&getContext(), specOp, arrived, succOpOperand, + for (OpOperand &succOpOperand : specOp1.getDataOut().getUses()) { + routeCommitControlRecursive(&getContext(), specOp2, arrived, succOpOperand, branchTrace); } // Start traversal from save-commit units for (auto saveCommitOp : - mlir::cast(specOp->getParentOp()).getOps()) { + mlir::cast(specOp1->getParentOp()).getOps()) { for (OpOperand &succOpOperand : saveCommitOp.getDataOut().getUses()) { branchTrace.clear(); - routeCommitControlRecursive(&getContext(), specOp, arrived, succOpOperand, - branchTrace); + routeCommitControlRecursive(&getContext(), specOp2, arrived, + succOpOperand, branchTrace); } } @@ -334,7 +331,7 @@ LogicalResult HandshakeSpeculationPass::routeCommitControl() { LogicalResult HandshakeSpeculationPass::placeCommits() { // Create a temporal value to connect the commits - Value commitCtrl = specOp.getCommitCtrl(); + Value commitCtrl = specOp2.getCommitCtrl(); OpBuilder builder(&getContext()); // Build a temporary control value using mlir::UnrealizedConversionCastOp @@ -345,7 +342,7 @@ LogicalResult HandshakeSpeculationPass::placeCommits() { fakeControlForCommits = builder .create( - specOp->getLoc(), commitCtrl.getType(), ValueRange{}) + specOp2->getLoc(), commitCtrl.getType(), ValueRange{}) .getResult(0); // Place commits and connect to the fake control signal @@ -360,7 +357,7 @@ LogicalResult HandshakeSpeculationPass::placeCommits() { SpecCommitOp newOp = builder.create( dstOp->getLoc(), /*resultType=*/srcOpResult.getType(), /*dataIn=*/srcOpResult, /*ctrl=*/fakeControlForCommits.value()); - inheritBB(dstOp, newOp); + inheritBB(srcOpResult.getDefiningOp(), newOp); // Connect the new CommitOp to dstOp operand->set(newOp.getResult()); @@ -400,12 +397,8 @@ LogicalResult HandshakeSpeculationPass::placeSaveCommits(Value ctrlSignal) { return success(); } -static handshake::ConditionalBranchOp findControlBranch(Operation *op) { - handshake::FuncOp funcOp = op->getParentOfType(); - assert(funcOp && "op should have parent function"); - auto handshakeBlocks = getLogicBBs(funcOp); - unsigned bb = getLogicBB(op).value(); - +static handshake::ConditionalBranchOp findControlBranch(FuncOp funcOp, + unsigned bb) { for (auto condBrOp : funcOp.getOps()) { if (auto brBB = getLogicBB(condBrOp); !brBB || brBB != bb) continue; @@ -428,9 +421,13 @@ FailureOr HandshakeSpeculationPass::generateSaveCommitCtrl() { // The save commits are a result of a control branch being in the BB // The control path for the SC needs to replicate the branch - ConditionalBranchOp controlBranch = findControlBranch(specOp); + handshake::FuncOp funcOp = specOp1->getParentOfType(); + auto handshakeBlocks = getLogicBBs(funcOp); + unsigned bb = getLogicBB(specOp1).value(); + + ConditionalBranchOp controlBranch = findControlBranch(funcOp, bb); if (controlBranch == nullptr) { - specOp->emitError() << "Could not find backedge within speculation bb.\n"; + specOp1->emitError() << "Could not find backedge within speculation bb.\n"; return failure(); } @@ -439,75 +436,36 @@ FailureOr HandshakeSpeculationPass::generateSaveCommitCtrl() { // The tokens take differents paths. One (SCSaveCtrl) needs to always reach // the SC, the other (SCCommitCtrl) should follow the actual branches // similarly to the Commits - builder.setInsertionPointAfterValue(specOp.getSCCommitCtrl()); + builder.setInsertionPointAfterValue(specOp2.getSCIsMisspec()); // First, discard if speculation didn't happen auto conditionOperand = controlBranch.getConditionOperand(); // trueResultType and falseResultType are tentative and will be updated in the // addSpecTag algorithm later. + // Operands are temporary. Will be updated at the end of the pass. auto branchDiscardCondNonSpec = builder.create( controlBranch.getLoc(), /*trueResultType=*/conditionOperand.getType(), /*falseResultType=*/conditionOperand.getType(), - /*specTag=*/specOp.getDataOut(), conditionOperand); - inheritBB(specOp, branchDiscardCondNonSpec); + /*specTag=*/specOp1.getDataOut(), conditionOperand); + inheritBB(specOp1, branchDiscardCondNonSpec); + branchDiscardCondNonSpec->setAttr("specv1_branchDiscardCondNonSpec", + builder.getUnitAttr()); // Second, discard if speculation happened but it was correct // Create a conditional branch driven by SCBranchControl from speculator // SCBranchControl discards the commit-like signal when speculation is correct auto branchDiscardCondNonMisspec = builder.create( - branchDiscardCondNonSpec.getLoc(), specOp.getSCIsMisspec(), + branchDiscardCondNonSpec.getLoc(), specOp2.getSCIsMisspec(), branchDiscardCondNonSpec.getTrueResult()); - inheritBB(specOp, branchDiscardCondNonMisspec); - - // This branch will propagate the signal SCCommitControl according to - // the control branch condition, which comes from branchDiscardCondNonMisSpec - auto branchReplicated = builder.create( - branchDiscardCondNonMisspec.getLoc(), - branchDiscardCondNonMisspec.getTrueResult(), specOp.getSCCommitCtrl()); - inheritBB(specOp, branchReplicated); - - // We create a Merge operation to join SCCSaveCtrl and SCCommitCtrl signals - SmallVector mergeOperands; - mergeOperands.push_back(specOp.getSCSaveCtrl()); - - // Helper function to check if a value leads to a Backedge - auto isBranchBackedge = [&](Value result) { - return llvm::any_of(result.getUsers(), [&](Operation *user) { - return isBackedge(result, user); - }); - }; - - // We need to send the control token to the same path that the speculative - // token followed. Hence, if any branch output leads to a backedge, replicate - // the branch in the SaveCommit control path. - - // Check if trueResult of controlBranch leads to a backedge (loop) - if (isBranchBackedge(controlBranch.getTrueResult())) { - mergeOperands.push_back(branchReplicated.getTrueResult()); - } - // Check if falseResult of controlBranch leads to a backedge (loop) - else if (isBranchBackedge(controlBranch.getFalseResult())) { - mergeOperands.push_back(branchReplicated.getFalseResult()); - } - // If neither trueResult nor falseResult leads to a backedge, handle the error - else { - unsigned bb = getLogicBB(specOp).value(); - controlBranch->emitError() - << "Could not find the backedge in the Control Branch " << bb << "\n"; - return failure(); - } - - // All the inputs to the merge operation are ready - auto mergeOp = builder.create(branchReplicated.getLoc(), - mergeOperands); - inheritBB(specOp, mergeOp); + inheritBB(specOp2, branchDiscardCondNonMisspec); - // The control signal is the result of the merge op. - return mergeOp.getResult(); + // Tentatively use specOp1.getSCSaveCtrl for the control signal for + // save-commits. Post-buffering pass replaces this. + return specOp1.getSCSaveCtrl(); } std::optional findControlInputToBB(handshake::FuncOp &funcOp, @@ -585,17 +543,20 @@ LogicalResult HandshakeSpeculationPass::placeSpeculator() { // resultType is tentative and will be updated in the addSpecTag algorithm // later. - specOp = builder.create( + specOp1 = builder.create( dstOp->getLoc(), /*resultType=*/srcOpResult.getType(), - /*dataIn=*/srcOpResult, /*specIn=*/specTrigger.value(), fifoDepth); + /*specIn=*/specTrigger.value(), fifoDepth); + specOp2 = builder.create(dstOp->getLoc(), + /*dataIn=*/srcOpResult, fifoDepth); // Replace uses of the original source operation's result with the // speculator's result, except in the speculator's operands (otherwise this // would create a self-loop from the speculator to the speculator) - srcOpResult.replaceAllUsesExcept(specOp.getDataOut(), specOp); + srcOpResult.replaceAllUsesExcept(specOp1.getDataOut(), specOp2); // Assign a Basic Block to the speculator - inheritBB(dstOp, specOp); + inheritBB(dstOp, specOp1); + inheritBB(dstOp, specOp2); return success(); } @@ -775,12 +736,13 @@ addSpecTagToSpecRegionRecursive(MLIRContext &ctx, OpOperand &opOperand, LogicalResult HandshakeSpeculationPass::addSpecTagToSpecRegion() { llvm::DenseSet visited; - visited.insert(specOp); + visited.insert(specOp1); + visited.insert(specOp2); // For the speculator, perform downstream traversal to only dataOut, skipping // control signals. The upstream dataIn will be handled by the recursive // traversal. - for (OpOperand &opOperand : specOp.getDataOut().getUses()) { + for (OpOperand &opOperand : specOp1.getDataOut().getUses()) { if (failed(addSpecTagToSpecRegionRecursive(getContext(), opOperand, true, visited))) return failure(); @@ -789,7 +751,7 @@ LogicalResult HandshakeSpeculationPass::addSpecTagToSpecRegion() { } LogicalResult HandshakeSpeculationPass::addNonSpecOp() { - auto funcOp = cast(specOp->getParentOp()); + auto funcOp = cast(specOp1->getParentOp()); OpBuilder builder(&getContext()); for (auto mergeLikeOp : funcOp.getOps()) { @@ -848,9 +810,6 @@ void HandshakeSpeculationPass::runDynamaticPass() { llvm::errs() << "Error: Placement of save units is not supported.\n"; return signalPassFailure(); } - // Place Save operations - // if (failed(placeUnits(this->specOp.getSaveCtrl()))) - // return signalPassFailure(); if (!placements.getPlacements().empty()) { // Generate Place SaveCommit operations and the SaveCommit control path @@ -881,4 +840,25 @@ void HandshakeSpeculationPass::runDynamaticPass() { // to satisfy their type requirements. if (failed(addNonSpecOp())) return signalPassFailure(); + + // Quick fix: branchDiscardCondNonspec's operands must be the loop condition + // The real loop condition only turns out after the placement of speculative + // units (Speculator or save-commit unit may produce this) + handshake::FuncOp funcOp = specOp1->getParentOfType(); + for (auto branch : funcOp.getOps()) { + if (branch->getAttr("specv1_branchDiscardCondNonSpec")) { + unsigned bb = getLogicBB(specOp1).value(); + ConditionalBranchOp controlBranch = findControlBranch(funcOp, bb); + if (controlBranch == nullptr) { + specOp1->emitError() + << "Could not find backedge within speculation bb.\n"; + return signalPassFailure(); + } + auto conditionOperand = controlBranch.getConditionOperand(); + branch->setOperand(0, conditionOperand); + branch->setOperand(1, conditionOperand); + } + } } + +} // namespace diff --git a/experimental/lib/Transforms/Speculation/PlacementFinder.cpp b/experimental/lib/Transforms/Speculation/PlacementFinder.cpp index 742db19562..d4e9955bcf 100644 --- a/experimental/lib/Transforms/Speculation/PlacementFinder.cpp +++ b/experimental/lib/Transforms/Speculation/PlacementFinder.cpp @@ -27,7 +27,6 @@ using namespace mlir; using namespace dynamatic; using namespace dynamatic::handshake; using namespace dynamatic::experimental; -using namespace dynamatic::experimental::speculation; PlacementFinder::PlacementFinder(SpeculationPlacements &placements) : placements(placements) { diff --git a/experimental/lib/Transforms/Speculation/SpeculationPlacement.cpp b/experimental/lib/Transforms/Speculation/SpeculationPlacement.cpp index 51d41b25a5..e05613d8eb 100644 --- a/experimental/lib/Transforms/Speculation/SpeculationPlacement.cpp +++ b/experimental/lib/Transforms/Speculation/SpeculationPlacement.cpp @@ -26,7 +26,6 @@ using namespace mlir; using namespace dynamatic; using namespace dynamatic::handshake; using namespace dynamatic::experimental; -using namespace dynamatic::experimental::speculation; // SpeculationPlacements Methods diff --git a/include/dynamatic/Dialect/Handshake/HandshakeOps.td b/include/dynamatic/Dialect/Handshake/HandshakeOps.td index 8a728fba01..11e92f4c2f 100644 --- a/include/dynamatic/Dialect/Handshake/HandshakeOps.td +++ b/include/dynamatic/Dialect/Handshake/HandshakeOps.td @@ -1161,6 +1161,68 @@ def SpeculatorOp : Handshake_Op<"speculator", [ }]; } +def SpecPreBufferOp1 : Handshake_Op<"spec_prebuffer1", [ + HasValidSpecTag<"dataOut">, + IsSimpleHandshake<"SCSaveCtrl">, + IsIntSizedChannel<3, "SCSaveCtrl">, +]> { + let summary = ""; + let description = [{ + }]; + + let arguments = (ins ControlType:$trigger, + UI32Attr:$fifoDepth); + let results = (outs ChannelType:$dataOut, + ChannelType:$SCSaveCtrl); + + let assemblyFormat = [{ + `[` $trigger `]` attr-dict `:` type($trigger) `,` + type($dataOut) `,` type($SCSaveCtrl) + }]; + + // Infer the type of the control signals + let builders = [OpBuilder<(ins "Type":$dataOutType, "Value":$trigger, "unsigned":$fifoDepth), [{ + $_state.addOperands({trigger}); + $_state.addAttribute("fifoDepth", $_builder.getUI32IntegerAttr(fifoDepth)); + + ChannelType wideControlType = ChannelType::get($_builder.getIntegerType(3)); + + $_state.addTypes({dataOutType, wideControlType}); + }]>]; +} + +def SpecPreBufferOp2 : Handshake_Op<"spec_prebuffer2", [ + HasValidSpecTag<"dataIn">, + IsSimpleHandshake<"commitCtrl">, + IsIntSizedChannel<1, "commitCtrl">, + IsSimpleHandshake<"SCIsMisspec">, + IsIntSizedChannel<1, "SCIsMisspec"> +]> { + let summary = ""; + let description = [{ + }]; + + let arguments = (ins ChannelType:$dataIn, + UI32Attr:$fifoDepth); + let results = (outs ChannelType:$commitCtrl, + ChannelType:$SCIsMisspec); + + let assemblyFormat = [{ + $dataIn attr-dict `:` type($dataIn) `,` type($commitCtrl) `,` + type($SCIsMisspec) + }]; + + // Infer the type of the control signals + let builders = [OpBuilder<(ins "Value":$dataIn, "unsigned":$fifoDepth), [{ + $_state.addOperands({dataIn}); + $_state.addAttribute("fifoDepth", $_builder.getUI32IntegerAttr(fifoDepth)); + + ChannelType ctrlType = ChannelType::get($_builder.getIntegerType(1)); + + $_state.addTypes({ctrlType, ctrlType, ctrlType}); + }]>]; +} + def SpecSaveOp : Handshake_Op<"spec_save", [ AllDataTypesMatch<["dataIn", "dataOut"]>, AllExtraSignalsMatchExcept<"spec", ["dataIn", "dataOut"]>, diff --git a/integration-test/fixed/buffer.json b/integration-test/fixed/buffer.json deleted file mode 100644 index 159aedab29..0000000000 --- a/integration-test/fixed/buffer.json +++ /dev/null @@ -1,16 +0,0 @@ -[ - { - "pred": "speculating_branch0", - "outid": 0, - "slots": 3, - "type": "fifo_break_none", - "comment": "To avoid deadlock" - }, - { - "pred": "fork6", - "outid": 4, - "slots": 2, - "type": "fifo_break_none", - "comment": "To achieve better II" - } -] diff --git a/integration-test/fixed/results.md b/integration-test/fixed/results.md index 86d9141780..a38eaa0c17 100644 --- a/integration-test/fixed/results.md +++ b/integration-test/fixed/results.md @@ -5,5 +5,5 @@ Since the variables `x0` and `x1` depend on values from previous iterations, the | | No Speculation | Speculation | |----------------------|------------------|-------------------| | II (Haoran’s thesis) | 16 | 6 | -| II | 14 | 5 | -| Cycles (Test Bench) | 705 (End: 704) | 270 (End: 265) | +| II | 13 | 4 | +| Cycles (Test Bench) | 657 | 216 | diff --git a/integration-test/fixed/spec.json b/integration-test/fixed/spec.json index 73252e6426..3e98fff1d9 100644 --- a/integration-test/fixed/spec.json +++ b/integration-test/fixed/spec.json @@ -2,7 +2,7 @@ "speculator": { "operation-name": "fork5", "operand-idx": 0, - "fifo-depth": 3 + "fifo-depth": 4 }, - "save-commits-fifo-depth": 3 + "save-commits-fifo-depth": 4 } diff --git a/integration-test/if_convert/results.md b/integration-test/if_convert/results.md index 717367c9ce..2de6d1e8f3 100644 --- a/integration-test/if_convert/results.md +++ b/integration-test/if_convert/results.md @@ -5,5 +5,5 @@ The optimal II was achieved with speculation. | | No Speculation | Speculation | |----------------------|------------------|-------------------| | II (Haoran’s thesis) | 7 | 1 | -| II | 6 | 1 | -| Cycles (Test Bench) | 1129 (End: 1127) | 309 (End: 307) | +| II | 5 | 1 | +| Cycles (Test Bench) | 943 | 315 | diff --git a/integration-test/loop_path/results.md b/integration-test/loop_path/results.md index 4462e6b3fe..aeca88e076 100644 --- a/integration-test/loop_path/results.md +++ b/integration-test/loop_path/results.md @@ -7,5 +7,5 @@ The II is only 2, even in the non-speculation case. This is because the break co | | No Speculation | Speculation | |----------------------|------------------|-------------------| | II (Haoran’s thesis) | 6 | 1 | -| II | 2 | 1 | -| Cycles (Test Bench) | 341 (End: 339) | 175 (End: 173) | +| II | 3 | 1 | +| Cycles (Test Bench) | 508 | 176 | diff --git a/integration-test/nested_loop/buffer.json b/integration-test/nested_loop/buffer.json deleted file mode 100644 index c96f590b31..0000000000 --- a/integration-test/nested_loop/buffer.json +++ /dev/null @@ -1,58 +0,0 @@ -[ - { - "pred": "speculating_branch1", - "outid": 0, - "slots": 5, - "type": "fifo_break_none", - "comment": "To avoid deadlock" - }, - { - "pred": "fork18", - "outid": 0, - "slots": 4, - "type": "fifo_break_none", - "comment": "To absorb the initial latency (sc ctrl)" - }, - { - "pred": "fork14", - "outid": 0, - "slots": 2, - "type": "fifo_break_none", - "comment": "To absorb latency for spec_commit8 (ctrl)" - }, - { - "pred": "spec_commit9", - "outid": 0, - "slots": 2, - "type": "fifo_break_none", - "comment": "Buffer non-spec token to prevent II=2 locking" - }, - { - "pred": "spec_commit8", - "outid": 0, - "slots": 2, - "type": "fifo_break_none", - "comment": "Buffer non-spec token to prevent II=2 locking" - }, - { - "pred": "extsi3", - "outid": 0, - "slots": 4, - "type": "fifo_break_none", - "comment": "To absorb latency for spec_commit1 (data)" - }, - { - "pred": "addi0", - "outid": 0, - "slots": 4, - "type": "fifo_break_none", - "comment": "To absorb latency for spec_commit9 (data)" - }, - { - "pred": "cmpi0", - "outid": 0, - "slots": 2, - "type": "fifo_break_none", - "comment": "To prevent II=2 locking" - } -] diff --git a/integration-test/nested_loop/results.md b/integration-test/nested_loop/results.md index e4f477db12..c232c35fee 100644 --- a/integration-test/nested_loop/results.md +++ b/integration-test/nested_loop/results.md @@ -5,5 +5,5 @@ The result matched that of Haoran's thesis. | | No Speculation | Speculation | |----------------------------------------|------------------|-------------------| | II of the inner loop (Haoran’s thesis) | 6 | 1 | -| II of the inner loop | 6 | 1 | -| Cycles (Test Bench) | 2423 (End: 2421) | 437 (End: 433) | +| II of the inner loop | 5 | 1 | +| Cycles (Test Bench) | 2018 | 428 | diff --git a/integration-test/single_loop/buffer.json b/integration-test/single_loop/buffer.json deleted file mode 100644 index e61dfa731e..0000000000 --- a/integration-test/single_loop/buffer.json +++ /dev/null @@ -1,44 +0,0 @@ -[ - { - "pred": "speculating_branch0", - "outid": 0, - "slots": 5, - "type": "fifo_break_none", - "comment": "To avoid deadlock" - }, - { - "pred": "trunci0", - "outid": 0, - "slots": 4, - "type": "fifo_break_none", - "comment": "To absorb latency for spec_commit4 (data)" - }, - { - "pred": "extsi1", - "outid": 0, - "slots": 5, - "type": "fifo_break_none", - "comment": "To absorb latency for spec_commit1 (data)" - }, - { - "pred": "fork9", - "outid": 0, - "slots": 2, - "type": "fifo_break_none", - "comment": "To absorb latency for spec_commit5 (ctrl)" - }, - { - "pred": "spec_commit4", - "outid": 0, - "slots": 2, - "type": "fifo_break_none", - "comment": "Buffer non-spec token to prevent II=2 locking" - }, - { - "pred": "spec_commit5", - "outid": 0, - "slots": 2, - "type": "fifo_break_none", - "comment": "Buffer non-spec token to prevent II=2 locking" - } -] diff --git a/integration-test/single_loop/results.md b/integration-test/single_loop/results.md index 7733df43e9..a141993a9d 100644 --- a/integration-test/single_loop/results.md +++ b/integration-test/single_loop/results.md @@ -6,4 +6,4 @@ The result matched that of Haoran's thesis. |----------------------|------------------|-------------------| | II (Haoran’s thesis) | 6 | 1 | | II | 6 | 1 | -| Cycles (Test Bench) | 3013 (End: 3011) | 516 (End: 511) | +| Cycles (Test Bench) | 3013 | 513 | diff --git a/integration-test/sparse/buffer.json b/integration-test/sparse/buffer.json deleted file mode 100644 index 601f7ad9ef..0000000000 --- a/integration-test/sparse/buffer.json +++ /dev/null @@ -1,9 +0,0 @@ -[ - { - "pred": "speculating_branch0", - "outid": 0, - "slots": 2, - "type": "fifo_break_none", - "comment": "To avoid deadlock" - } -] diff --git a/integration-test/sparse/results.md b/integration-test/sparse/results.md index dd40173365..f2d8c0bdc9 100644 --- a/integration-test/sparse/results.md +++ b/integration-test/sparse/results.md @@ -5,5 +5,5 @@ Although Haoran's thesis attempted data speculation and achieved an II of 1, it | | No Speculation | Speculation | |----------------------|------------------|-------------------| | II (Haoran’s thesis) | 16 | 1 | -| II | 15 | 10 | -| Cycles (Test Bench) | 1237 (End: 1235) | 835 (End: 831) | +| II | 14 | 9 | +| Cycles (Test Bench) | 1155 | 750 | diff --git a/integration-test/subdiag/buffer.json b/integration-test/subdiag/buffer.json deleted file mode 100644 index be772be938..0000000000 --- a/integration-test/subdiag/buffer.json +++ /dev/null @@ -1,30 +0,0 @@ -[ - { - "pred": "speculating_branch0", - "outid": 0, - "slots": 8, - "type": "fifo_break_none", - "comment": "To avoid deadlock" - }, - { - "pred": "cmpi1", - "outid": 0, - "slots": 8, - "type": "fifo_break_none", - "comment": "To achieve better II" - }, - { - "pred": "load2", - "outid": 1, - "slots": 7, - "type": "fifo_break_none", - "comment": "To achieve better II" - }, - { - "pred": "trunci2", - "outid": 0, - "slots": 2, - "type": "fifo_break_none", - "comment": "To achieve better II" - } -] diff --git a/integration-test/subdiag/results.md b/integration-test/subdiag/results.md index ed82a09ce4..75328a90a2 100644 --- a/integration-test/subdiag/results.md +++ b/integration-test/subdiag/results.md @@ -7,5 +7,5 @@ Additionally, the current implementation of `cmpf` seems to generate output with | | No Speculation | Speculation | |----------------------|------------------|-------------------| | II (Haoran’s thesis) | 15 | 1 | -| II | 16 | 2 | -| Cycles (Test Bench) | 1623 (End: 1621) | 228 (End: 222) | +| II | 15 | 2 | +| Cycles (Test Bench) | 1522 | 223 | diff --git a/integration-test/subdiag/spec.json b/integration-test/subdiag/spec.json index e08516fe79..cd2bbd91e3 100644 --- a/integration-test/subdiag/spec.json +++ b/integration-test/subdiag/spec.json @@ -2,7 +2,7 @@ "speculator": { "operation-name": "fork3", "operand-idx": 0, - "fifo-depth": 8 + "fifo-depth": 9 }, - "save-commits-fifo-depth": 8 + "save-commits-fifo-depth": 9 } diff --git a/integration-test/subdiag_fast/buffer.json b/integration-test/subdiag_fast/buffer.json deleted file mode 100644 index 268c238228..0000000000 --- a/integration-test/subdiag_fast/buffer.json +++ /dev/null @@ -1,23 +0,0 @@ -[ - { - "pred": "speculating_branch0", - "outid": 0, - "slots": 15, - "type": "fifo_break_none", - "comment": "To avoid deadlock" - }, - { - "pred": "cmpi1", - "outid": 0, - "slots": 15, - "type": "fifo_break_none", - "comment": "To achieve better II" - }, - { - "pred": "load2", - "outid": 1, - "slots": 13, - "type": "fifo_break_none", - "comment": "To achieve better II" - } -] diff --git a/integration-test/subdiag_fast/results.md b/integration-test/subdiag_fast/results.md index 3a817d8486..9e36dcf0e2 100644 --- a/integration-test/subdiag_fast/results.md +++ b/integration-test/subdiag_fast/results.md @@ -5,4 +5,4 @@ In contrast to the original `subdiag`, it successfully achieved an II of 1. | | No Speculation | Speculation | |--------------------------|------------------|-------------------| | II | 15 | 1 | -| Cycles (Test Bench) | 1522 (End: 1520) | 126 (End: 120) | +| Cycles (Test Bench) | 1421 | 121 | diff --git a/lib/Support/CFG.cpp b/lib/Support/CFG.cpp index 718eca63f0..1c70dff30f 100644 --- a/lib/Support/CFG.cpp +++ b/lib/Support/CFG.cpp @@ -153,7 +153,7 @@ static bool followToBlock(Operation *op, unsigned &bb, /// outside blocks during backedge identification. static inline bool canGoThroughOutsideBlocks(Operation *op) { return isa(op); + handshake::TruncIOp, handshake::BufferOp>(op); } /// Attempts to backtrack through forks and bitwidth modification operations diff --git a/tools/integration/run_spec_integration.py b/tools/integration/run_spec_integration.py new file mode 100644 index 0000000000..126bdeb486 --- /dev/null +++ b/tools/integration/run_spec_integration.py @@ -0,0 +1,391 @@ +""" +Script for running Dynamatic speculative integration tests. +""" +import json +import os +import shutil +import subprocess +import sys +import argparse +from pathlib import Path + +DYNAMATIC_ROOT = Path(__file__).parent.parent.parent +INTEGRATION_FOLDER = DYNAMATIC_ROOT / "integration-test" + +CLANGXX_BIN = DYNAMATIC_ROOT / "bin" / "clang++" +DYNAMATIC_OPT_BIN = DYNAMATIC_ROOT / "build" / "bin" / "dynamatic-opt" +DYNAMATIC_PROFILER_BIN = DYNAMATIC_ROOT / "bin" / "exp-frequency-profiler" +EXPORT_DOT_BIN = DYNAMATIC_ROOT / "build" / "bin" / "export-dot" +EXPORT_RTL_BIN = DYNAMATIC_ROOT / "build" / "bin" / "export-rtl" +SIMULATE_SH = DYNAMATIC_ROOT / "tools" / \ + "dynamatic" / "scripts" / "simulate.sh" + +RTL_CONFIG = DYNAMATIC_ROOT / "data" / "rtl-config-vhdl-beta.json" + + +class TermColors: + """ + Contains ANSI color escape sequences for colored terminal output. + """ + HEADER = "\033[95m" + OKBLUE = "\033[94m" + OKCYAN = "\033[96m" + OKGREEN = "\033[92m" + WARNING = "\033[93m" + FAIL = "\033[91m" + ENDC = "\033[0m" + BOLD = "\033[1m" + UNDERLINE = "\033[4m" + + +def color_print(string: str, color: str): + """ + Prints colored text to stdout using ANSI escape sequences. + + Arguments: + `string` -- The text to be outputted + `color` -- ANSI escape seq. for the desired color; use TermColors constants + """ + print(f"{color}{string}{TermColors.ENDC}") + + +def run_test(c_file: str, spec: bool, cp) -> bool: + """ + Runs the specified integration test. + """ + + print("Running", c_file, "Speculation Enabled:", spec) + + # Get the c_file directory + c_file_dir = os.path.dirname(c_file) + kernel_name = os.path.splitext(os.path.basename(c_file))[0] + + # Get out dir name + out_dir = os.path.join(c_file_dir, "out") + # Remove previous out directory + if os.path.isdir(out_dir): + shutil.rmtree(out_dir) + Path(out_dir).mkdir() + + comp_out_dir = os.path.join(out_dir, "comp") + Path(comp_out_dir).mkdir() + + # Custom compilation flow + + # Start with copying the .cf file to the out_dir + cf_file_base = os.path.join(c_file_dir, "cf.mlir") + cf_file = os.path.join(comp_out_dir, "cf.mlir") + shutil.copy(cf_file_base, cf_file) + + # cf transformations (standard) + cf_transformed = os.path.join(comp_out_dir, "cf_transformed.mlir") + with open(cf_transformed, "w") as f: + result = subprocess.run([ + DYNAMATIC_OPT_BIN, cf_file, + "--canonicalize", "--cse", "--sccp", "--symbol-dce", + "--control-flow-sink", "--loop-invariant-code-motion", "--canonicalize"], + stdout=f, + stderr=sys.stdout + ) + if result.returncode == 0: + print("Applied standard transformations to cf") + else: + color_print( + "Failed to apply standard transformations to cf", TermColors.FAIL) + return False + + # cf transformations (dynamatic) + cf_dyn_transformed = os.path.join(comp_out_dir, "cf_dyn_transformed.mlir") + with open(cf_dyn_transformed, "w") as f: + result = subprocess.run([ + DYNAMATIC_OPT_BIN, cf_transformed, + "--arith-reduce-strength=max-adder-depth-mul=1", + "--push-constants", + "--mark-memory-interfaces" + ], + stdout=f, + stderr=sys.stdout + ) + if result.returncode == 0: + print("Applied Dynamatic transformations to cf") + else: + color_print( + "Failed to apply Dynamatic transformations to cf", TermColors.FAIL) + return False + + # cf level -> handshake level + handshake = os.path.join(comp_out_dir, "handshake.mlir") + with open(handshake, "w") as f: + result = subprocess.run([ + DYNAMATIC_OPT_BIN, cf_dyn_transformed, + "--lower-cf-to-handshake" + ], + stdout=f, + stderr=sys.stdout + ) + if result.returncode == 0: + print("Compiled cf to handshake") + else: + color_print("Failed to compile cf to handshake", TermColors.FAIL) + return False + + # handshake transformations + handshake_transformed = os.path.join( + comp_out_dir, "handshake_transformed.mlir") + with open(handshake_transformed, "w") as f: + result = subprocess.run([ + DYNAMATIC_OPT_BIN, handshake, + "--handshake-analyze-lsq-usage", "--handshake-replace-memory-interfaces", + "--handshake-minimize-cst-width", "--handshake-optimize-bitwidths", + "--handshake-materialize", "--handshake-infer-basic-blocks" + ], + stdout=f, + stderr=sys.stdout + ) + if result.returncode == 0: + print("Applied transformations to handshake") + else: + color_print("Failed to apply transformations to handshake", + TermColors.FAIL) + return False + + # handshake canonicalization + handshake_canonicalized = os.path.join( + comp_out_dir, "handshake_canonicalized.mlir") + with open(handshake_canonicalized, "w") as f: + result = subprocess.run([ + DYNAMATIC_OPT_BIN, handshake_transformed, + "--handshake-canonicalize", + "--handshake-hoist-ext-instances" + ], + stdout=f, + stderr=sys.stdout + ) + if result.returncode == 0: + print("Canonicalized handshake") + else: + color_print("Failed to canonicalize Handshake", TermColors.FAIL) + return False + + # Speculation + if spec: + handshake_speculation = os.path.join( + comp_out_dir, "handshake_speculation.mlir") + spec_json = os.path.join(c_file_dir, "spec.json") + with open(handshake_speculation, "w") as f: + result = subprocess.run([ + DYNAMATIC_OPT_BIN, handshake_canonicalized, + f"--handshake-speculation=json-path={spec_json}", + "--handshake-materialize", + "--handshake-canonicalize" + ], + stdout=f, + stderr=sys.stdout + ) + if result.returncode == 0: + print("Added speculative units") + else: + color_print("Failed to add speculative units", TermColors.FAIL) + return False + else: + handshake_speculation = handshake_canonicalized + + # Buffer placement (fpga20) + profiler_bin = os.path.join(comp_out_dir, "profile") + result = subprocess.run([ + CLANGXX_BIN, c_file, + "-D", "PRINT_PROFILING_INFO", + "-I", str(DYNAMATIC_ROOT / "include"), + "-Wno-deprecated", + "-o", profiler_bin + ]) + if result.returncode == 0: + print("Built kernel for profiling") + else: + print("Failed to place simple buffers") + + profiler_inputs = os.path.join(comp_out_dir, "profiler-inputs.txt") + with open(profiler_inputs, "w") as f: + result = subprocess.run([profiler_bin], + stdout=f, + stderr=sys.stdout + ) + if result.returncode == 0: + print("Ran kernel for profiling") + else: + print("Failed to kernel for profiling") + + frequencies = os.path.join(comp_out_dir, "frequencies.csv") + with open(frequencies, "w") as f: + result = subprocess.run([ + DYNAMATIC_PROFILER_BIN, cf_dyn_transformed, + "--top-level-function=" + kernel_name, + "--input-args-file=" + profiler_inputs, + ], + stdout=f, + stderr=sys.stdout + ) + if result.returncode == 0: + print("Profiled cf-level") + else: + print("Failed to profile cf-level") + + # Buffer placement (FPGA20) + handshake_buffered = os.path.join(comp_out_dir, "handshake_buffered.mlir") + timing_model = DYNAMATIC_ROOT / "data" / "components.json" + with open(handshake_buffered, "w") as f: + result = subprocess.run([ + DYNAMATIC_OPT_BIN, handshake_speculation, + "--handshake-set-buffering-properties=version=fpga20", + f"--handshake-place-buffers=algorithm=fpga20 frequencies={frequencies} timing-models={timing_model} target-period={cp} timeout=300 dump-logs" + ], + stdout=f, + stderr=sys.stdout, + cwd=comp_out_dir + ) + if result.returncode == 0: + print("Placed simple buffers") + else: + print("Failed to place simple buffers") + + # Speculation post-buffering + if spec: + handshake_spec_post_buffer = os.path.join( + comp_out_dir, "handshake_spec_post_buffer.mlir") + with open(handshake_spec_post_buffer, "w") as f: + result = subprocess.run([ + DYNAMATIC_OPT_BIN, handshake_buffered, + f"--handshake-spec-post-buffer", + "--handshake-materialize" + ], + stdout=f, + stderr=sys.stdout + ) + if result.returncode == 0: + print("Performed post-buffering speculation procedure") + else: + print("Failed to perform post-buffering speculation procedure") + else: + handshake_spec_post_buffer = handshake_buffered + + handshake_export = os.path.join( + comp_out_dir, "handshake_export.mlir") + shutil.copy(handshake_spec_post_buffer, handshake_export) + + # Export dot file + dot = os.path.join(comp_out_dir, f"{kernel_name}.dot") + with open(dot, "w") as f: + result = subprocess.run([ + EXPORT_DOT_BIN, handshake_export, + "--edge-style=spline", "--label-type=uname" + ], + stdout=f, + stderr=sys.stdout + ) + if result.returncode == 0: + print("Created dot file") + else: + color_print("Failed to export dot file", TermColors.FAIL) + return False + + # Convert DOT graph to PNG + png = os.path.join(comp_out_dir, f"{kernel_name}.png") + with open(png, "w") as f: + result = subprocess.run([ + "dot", "-Tpng", dot + ], + stdout=f, + stderr=sys.stdout + ) + if result.returncode == 0: + print("Created PNG file") + else: + color_print("Failed to create PNG file", TermColors.FAIL) + return False + + # handshake level -> hw level + hw = os.path.join(comp_out_dir, "hw.mlir") + with open(hw, "w") as f: + result = subprocess.run([ + DYNAMATIC_OPT_BIN, handshake_export, + "--lower-handshake-to-hw" + ], + stdout=f, + stderr=sys.stdout + ) + if result.returncode == 0: + print("Lowered handshake to hw") + else: + color_print("Failed to lower handshake to hw", TermColors.FAIL) + return False + + # Export hdl + hdl_dir = os.path.join(out_dir, "hdl") + + result = subprocess.run([ + EXPORT_RTL_BIN, hw, hdl_dir, RTL_CONFIG, + "--dynamatic-path", DYNAMATIC_ROOT, "--hdl", "vhdl" + ]) + if result.returncode == 0: + print("Exported hdl") + else: + color_print("Failed to export hdl", TermColors.FAIL) + return False + + # Simulate + print("Simulator launching") + + # simulate now needs the vivado path + should we use vivado floating point units + # this should probably be changed + result = subprocess.run([ + SIMULATE_SH, DYNAMATIC_ROOT, c_file_dir, out_dir, kernel_name, "", "false" + ]) + + if result.returncode == 0: + print("Simulation succeeded") + + result = os.path.join(out_dir, "sim/report.txt") + with open(result, "r") as f: + report = f.read() + # Match Latency = \d+ cycles + latency_match = report.split("Latency = ") + if len(latency_match) > 1: + latency = latency_match[1].split(" cycles")[0] + print(f"Latency: {latency} cycles") + else: + print("Latency not found in report") + else: + color_print("Failed to simulate", TermColors.FAIL) + return False + + return True + + +def main(): + """ + Entry point for the script. + """ + + parser = argparse.ArgumentParser( + description="Run speculation integration test") + parser.add_argument( + "test_name", type=str, help="Name of the test to run") + parser.add_argument( + "--disable-spec", action="store_false", dest="spec", + help="Run without speculation (but with custom compilation flow)") + parser.add_argument( + "--cp", type=str, help="clock period", default="10.000") + + args = parser.parse_args() + test_name = args.test_name + spec = args.spec + cp = args.cp + + success = run_test(INTEGRATION_FOLDER / test_name / + f"{test_name}.c", spec, cp) + if success: + color_print("Test passed", TermColors.OKGREEN) + + +if __name__ == "__main__": + main() diff --git a/tools/unit-generators/vhdl/generators/handshake/speculation/spec_save_commit.py b/tools/unit-generators/vhdl/generators/handshake/speculation/spec_save_commit.py index b9b3f2169e..f26a9143f4 100644 --- a/tools/unit-generators/vhdl/generators/handshake/speculation/spec_save_commit.py +++ b/tools/unit-generators/vhdl/generators/handshake/speculation/spec_save_commit.py @@ -148,17 +148,25 @@ def _generate_spec_save_commit(name, bitwidth, fifo_depth): outs_spec <= "0"; elsif ctrl_valid = '1' and ctrl = "100" then -- NO_CMP - -- TODO: When Empty = '1', input data should be bypassed, - -- just like when PASS or PASS_KILL, for better performance. -- Head = Curr is assumed from the specification. - -- `not Empty` ensures Curr < Tail. - CurrEn <= outs_ready and not Empty; - HeadEn <= outs_ready and not Empty; + if Empty = '1' then + CurrEn <= outs_ready and ins_valid and not Full; + HeadEn <= outs_ready and ins_valid and not Full; - ctrl_ready <= outs_ready and not Empty; - outs_valid <= not Empty; - {data("outs <= Memory(Head);", bitwidth)} - outs_spec <= "0"; + ctrl_ready <= outs_ready and ins_valid and not Full; + outs_valid <= ins_valid and not Full; + {data("outs <= ins;", bitwidth)} + outs_spec <= "0"; + else + -- `Empty = '0'` ensures Curr < Tail. + CurrEn <= outs_ready; + HeadEn <= outs_ready; + + ctrl_ready <= outs_ready; + outs_valid <= '1'; + {data("outs <= Memory(Head);", bitwidth)} + outs_spec <= "0"; + end if; end if; end process; @@ -244,8 +252,8 @@ def _generate_spec_save_commit(name, bitwidth, fifo_depth): else -- if only filling but not emptying if (TailEn = '1') and (HeadEn = '0') then - -- if new tail index will reach head index - if ((Tail +1) mod {fifo_depth} = Head) then + -- if new tail index will reach head index - 1 (ring buffer full) + if ((Tail + 2) mod {fifo_depth} = Head) then Full <= '1'; end if; elsif (TailEn = '0') and (HeadEn = '1') then @@ -269,7 +277,7 @@ def _generate_spec_save_commit(name, bitwidth, fifo_depth): -- if only emptying but not filling if (TailEn = '0') and (HeadEn = '1') then -- if new head index will reach tail index - if ((Head +1) mod {fifo_depth} = Tail) then + if ((Head + 1) mod {fifo_depth} = Tail) then Empty <= '1'; end if; elsif (TailEn = '1') and (HeadEn = '0') then @@ -293,7 +301,7 @@ def _generate_spec_save_commit(name, bitwidth, fifo_depth): -- if only emptying but not filling if (TailEn = '0') and (CurrEn = '1') then -- if new head index will reach tail index - if ((Curr +1) mod {fifo_depth} = Tail) then + if ((Curr + 1) mod {fifo_depth} = Tail) then CurrEmpty <= '1'; end if; elsif (TailEn = '1') and (CurrEn = '0') then diff --git a/tools/unit-generators/vhdl/generators/handshake/speculation/speculator.py b/tools/unit-generators/vhdl/generators/handshake/speculation/speculator.py index 1c24be9a83..0c4cb3b600 100644 --- a/tools/unit-generators/vhdl/generators/handshake/speculation/speculator.py +++ b/tools/unit-generators/vhdl/generators/handshake/speculation/speculator.py @@ -713,8 +713,8 @@ def _generate_predFifo(name, bitwidth, fifo_depth): else -- if only filling but not emptying if (TailEn = '1') and (HeadEn = '0') then - -- if new tail index will reach head index - if ((Tail +1) mod {fifo_depth} = Head) then + -- if new tail index will reach head index - 1 (ring buffer full) + if ((Tail + 2) mod {fifo_depth} = Head) then Full <= '1'; end if; elsif (TailEn = '0') and (HeadEn = '1') then