Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
7437ec3
chore: add new algorithm to `HandshakePlaceBuffers.cpp`
ziadomalik Nov 14, 2025
2274e03
chore: add the skeleton files
ziadomalik Nov 14, 2025
1906a7e
chore: add check for the new FPGA24 algorithm
ziadomalik Nov 14, 2025
e08bd6a
chore: get the compiler to use our new algorithm
ziadomalik Nov 14, 2025
56d066d
chore: add CFC node printing.
ziadomalik Nov 15, 2025
cfcc740
chore: start
ziadomalik Nov 29, 2025
dc24fcc
chore: graph generated
ziadomalik Nov 29, 2025
36d37d3
chore: latest update with cleaned up reconvergent paths
ziadomalik Dec 5, 2025
012eb9e
feat: new generic dataflow graph ds that's acyclic
ziadomalik Dec 9, 2025
5d75a21
chore: add reconvergent paths back
ziadomalik Dec 9, 2025
87f1a77
chore: linting
ziadomalik Dec 9, 2025
648d130
chore: refactor code with the correct understanding of transitions
ziadomalik Dec 13, 2025
f2960ab
chore: refactor dataflow graph & separate the logic from the pathfinding
ziadomalik Dec 20, 2025
630ccaf
chore: remove the deprecated logic
ziadomalik Dec 21, 2025
6d1471c
chore: synchronizing cycle operation that compiles
ziadomalik Dec 22, 2025
5723838
chore: remove the synchronizing cycle implementation for now
ziadomalik Dec 23, 2025
818e8d0
chore: remove formatter added by vscode automatically
ziadomalik Dec 23, 2025
9bd0c6c
chore: remove the debugging setup
ziadomalik Dec 23, 2025
d59e64e
chore: bring back correct join definitons
ziadomalik Dec 23, 2025
3b61642
chore: clang format
ziadomalik Dec 23, 2025
2ffa65b
chore: rename class to `DataflowSubgraphBase`
ziadomalik Dec 27, 2025
bba462e
chore: turn `DataflowSubgraphBase` into a struct
ziadomalik Dec 27, 2025
d353c5a
chore: wrap debug messages in `LLVM_DEBUG`
ziadomalik Dec 27, 2025
9b11758
chore: better naming in `enumerateTransitionSequences`
ziadomalik Dec 27, 2025
4b48c3e
chore: remove static from `enumerateTransitionSequences`
ziadomalik Dec 27, 2025
7c9fe65
chore: add note for node indexing
ziadomalik Dec 27, 2025
d75e783
chore: remove templates for simplicity
ziadomalik Dec 27, 2025
16f2d9d
chore: clang-format
ziadomalik Dec 27, 2025
5f74846
chore: apply feedback
ziadomalik Dec 30, 2025
82ecc3f
chore: clang-format
ziadomalik Dec 30, 2025
855b6a4
chore: cosmetics
ziadomalik Jan 1, 2026
68f02b1
chore: add the synchronizing cycle code
ziadomalik Jan 4, 2026
8611307
chore: add comments explaining SC's and the algorithm
ziadomalik Jan 4, 2026
d8cc5b0
Merge branch 'main' into feat/ziad/synchronizing-cycles
ziadomalik Jan 4, 2026
91e3376
chore: remove const for `SimpleCycle`
ziadomalik Jan 5, 2026
f1c96ba
chore: explain `CycleCollector`
ziadomalik Jan 5, 2026
173d2fd
chore: add clarifying comment for `graphPaths`
ziadomalik Jan 5, 2026
7de91ff
chore: turn if into asserts also run `clang-format`
ziadomalik Jan 5, 2026
eed3634
chrore: redefine joins for SC's (not resolved) and move fork/join che…
ziadomalik Jan 5, 2026
efe56b9
chore: comment clarification for visited vector
ziadomalik Jan 5, 2026
f54c5ba
chore: name change for DFS functions
ziadomalik Jan 5, 2026
98fcdd7
chore: remove unnessesary outer loop
ziadomalik Jan 5, 2026
657976d
chore: add better comment explaining the bfs
ziadomalik Jan 5, 2026
460a8b3
chore: turn std::vec to llvm vector
ziadomalik Jan 5, 2026
2fd909e
chore: add NodeIdType
ziadomalik Jan 5, 2026
f88771f
chore: remove redundant revAdjList
ziadomalik Jan 5, 2026
1d37422
chore: refactor the SCCId vector and use `node.size()`
ziadomalik Jan 5, 2026
75a54b4
chore: remove the `data/aig`
ziadomalik Jan 6, 2026
f43a06c
chore: Fix submodule pointer
ziadomalik Jan 6, 2026
e0daca6
chore: use SmallVector instead of `std::vector` where possible
ziadomalik Jan 6, 2026
8a13204
chore: add `JoinLikeOpInterface` and better checking syntax
ziadomalik Jan 6, 2026
446d10a
chore: rename `dfs1` and `dfs2`
ziadomalik Jan 9, 2026
b7874cd
chore: add `JoinLikeOpInterface` to `ConditionalBranchOp`
ziadomalik Jan 9, 2026
4f55d37
chore: add good comment for `findEdgesToJoin`
ziadomalik Jan 9, 2026
cb72447
chore: simplify dumpAllReconvergentPaths
ziadomalik Jan 9, 2026
f995c70
chore: remove aig
ziadomalik Jan 9, 2026
eaf6e37
chore: not aig again ugh
ziadomalik Jan 10, 2026
d3336c7
chore: simplify input for debug function
ziadomalik Jan 10, 2026
33e0b7c
chore: typo
ziadomalik Jan 10, 2026
a1d416a
chore: define EdgeIdType
ziadomalik Jan 10, 2026
ffa647d
chore: good old clang-format
ziadomalik Jan 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion data/aig
Submodule aig updated 450 files
1 change: 1 addition & 0 deletions include/dynamatic/Dialect/Handshake/HandshakeArithOps.td
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class Handshake_Arith_Op<string mnemonic, list<Trait> traits = []> :
class Handshake_Arith_BinaryOp<string mnemonic, list<Trait> traits = []> :
Handshake_Arith_Op<mnemonic, traits # [
SameOperandsAndResultType,
JoinLikeOpInterface,
DeclareOpInterfaceMethods<NamedIOInterface, ["getOperandName", "getResultName"]>,
]> {
let arguments = (ins ChannelType:$lhs, ChannelType:$rhs);
Expand Down
7 changes: 7 additions & 0 deletions include/dynamatic/Dialect/Handshake/HandshakeInterfaces.td
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,13 @@ def MergeLikeOpInterface : OpInterface<"MergeLikeOpInterface"> {
];
}

def JoinLikeOpInterface : OpInterface<"JoinLikeOpInterface"> {
let cppNamespace = "::dynamatic::handshake";
let description = [{
A two-input handshake operation that contains a join.
}];
}

def BufferLikeOpInterface : OpInterface<"BufferLikeOpInterface"> {
let cppNamespace = "::dynamatic::handshake";
let description = [{
Expand Down
1 change: 1 addition & 0 deletions include/dynamatic/Dialect/Handshake/HandshakeOps.td
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,7 @@ def BranchOp : Handshake_Op<"br", [
}

def ConditionalBranchOp : Handshake_Op<"cond_br", [
JoinLikeOpInterface,
AllTypesMatch<["dataOperand", "trueResult", "falseResult"]>,
AllExtraSignalsMatch<["conditionOperand", "dataOperand", "trueResult", "falseResult"]>,
IsIntSizedChannel<1, "conditionOperand">,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,12 @@

#pragma once

#include "dynamatic/Dialect/Handshake/HandshakeInterfaces.h"
#include "dynamatic/Dialect/Handshake/HandshakeOps.h"
#include "dynamatic/Transforms/BufferPlacement/CFDFC.h"
#include "experimental/Support/StdProfiler.h"
#include "mlir/IR/Operation.h"
#include "llvm/ADT/ArrayRef.h"
#include <set>

using namespace dynamatic::experimental;
Expand All @@ -24,6 +27,9 @@ using namespace dynamatic::experimental;

namespace dynamatic {

using NodeIdType = size_t;
using EdgeIdType = size_t;

/// NOTE: No current implementation differentiates between intra-BB and inter-BB
/// edges. Right now, it's quite useful for visualizing the graph in GraphViz.
enum DataflowGraphEdgeType {
Expand All @@ -33,20 +39,20 @@ enum DataflowGraphEdgeType {

struct DataflowGraphNode {
mlir::Operation *op; // <-- The underlying Operation.
size_t id; // <-- Unique id in the nodes vector to help with traversal.
NodeIdType id; // <-- Unique id in the nodes vector to help with traversal.

DataflowGraphNode(mlir::Operation *op, size_t id) : op(op), id(id) {}
DataflowGraphNode(mlir::Operation *op, NodeIdType id) : op(op), id(id) {}
};

struct DataflowGraphEdge {
size_t srcId;
size_t dstId;
NodeIdType srcId;
NodeIdType dstId;

mlir::Value channel;
DataflowGraphEdgeType type;

DataflowGraphEdge(
size_t srcId, size_t dstId, mlir::Value channel,
NodeIdType srcId, NodeIdType dstId, mlir::Value channel,
DataflowGraphEdgeType type = DataflowGraphEdgeType::INTRA_BB)
: srcId(srcId), dstId(dstId), channel(channel), type(type) {}
};
Expand All @@ -64,11 +70,11 @@ struct DataflowSubgraphBase {

/// Virtual Methods ///

virtual bool isForkNode(size_t nodeId) const = 0;
virtual bool isJoinNode(size_t nodeId) const = 0;
virtual bool isForkNode(NodeIdType nodeId) const = 0;
virtual bool isJoinNode(NodeIdType nodeId) const = 0;

virtual std::string getNodeLabel(size_t nodeId) const = 0;
virtual std::string getNodeDotId(size_t nodeId) const = 0;
virtual std::string getNodeLabel(NodeIdType nodeId) const = 0;
virtual std::string getNodeDotId(NodeIdType nodeId) const = 0;

/// Getters ///

Expand All @@ -80,18 +86,18 @@ struct DataflowSubgraphBase {
std::vector<DataflowGraphEdge> edges;

/// NOTE: Uses node ID to index the nodes.
std::vector<llvm::SmallVector<size_t, 4>> adjList;
std::vector<llvm::SmallVector<size_t, 4>> revAdjList;
std::vector<llvm::SmallVector<EdgeIdType, 4>> adjList;
std::vector<llvm::SmallVector<EdgeIdType, 4>> revAdjList;

size_t addNode(mlir::Operation *op) {
size_t id = nodes.size();
NodeIdType addNode(mlir::Operation *op) {
NodeIdType id = nodes.size();
nodes.emplace_back(op, id);
adjList.emplace_back();
revAdjList.emplace_back();
return nodes.size() - 1;
}

void addEdge(size_t srcId, size_t dstId, mlir::Value channel,
void addEdge(NodeIdType srcId, NodeIdType dstId, mlir::Value channel,
DataflowGraphEdgeType type = DataflowGraphEdgeType::INTRA_BB) {
edges.emplace_back(srcId, dstId, channel, type);
adjList[srcId].push_back(edges.size() - 1);
Expand All @@ -102,11 +108,11 @@ struct DataflowSubgraphBase {
/// A reconvergent path is a subgraph where multiple paths diverge from a fork
/// and reconverge at a join. This is important for latency balancing.
struct ReconvergentPath {
size_t forkNodeId; // The divergence point
size_t joinNodeId; // The convergence point
std::set<size_t> nodeIds; // All nodes on paths from fork to join.
NodeIdType forkNodeId; // The divergence point
NodeIdType joinNodeId; // The convergence point
std::set<NodeIdType> nodeIds; // All nodes on paths from fork to join.

ReconvergentPath(size_t fork, size_t join, std::set<size_t> nodes)
ReconvergentPath(NodeIdType fork, NodeIdType join, std::set<NodeIdType> nodes)
: forkNodeId(fork), joinNodeId(join), nodeIds(std::move(nodes)) {}
};

Expand All @@ -119,8 +125,8 @@ struct ReconvergentPath {
/// 1 -> 1 (self-loop)
/// enumerateTransitionSequences(transitions, 3);
/// Output: [1, 2, 3], [1, 1, 2], [1, 1, 1]
std::vector<std::vector<ArchBB>>
enumerateTransitionSequences(const std::vector<ArchBB> &transitions,
inline std::vector<std::vector<ArchBB>>
enumerateTransitionSequences(llvm::ArrayRef<ArchBB> transitions,
size_t sequenceLength) {
// 'sequenceLength' is the number of steps to visit.
// Number of transitions needed = sequenceLength - 1.
Expand Down Expand Up @@ -172,23 +178,35 @@ enumerateTransitionSequences(const std::vector<ArchBB> &transitions,
/// IMPORTANT: This class assumes the graph an ACYCLIC transition sequence.
class ReconvergentPathFinderGraph : public DataflowSubgraphBase {
public:
bool isForkNode(size_t nodeId) const override;
bool isJoinNode(size_t nodeId) const override;
bool isForkNode(NodeIdType nodeId) const override {
return isa<handshake::ForkOp, handshake::LazyForkOp,
handshake::EagerForkLikeOpInterface>(nodes[nodeId].op);
}

// The only nodes with two inputs that allow for both
// inputs to be active at the same time. Unlike: ControlMergeOp and MergeOp.
/// NOTE: When it belongs to a CFDFC, MuxOp behaves like a join node.
bool isJoinNode(NodeIdType nodeId) const override {
return isa<handshake::MuxOp, handshake::JoinLikeOpInterface,
handshake::ConditionalBranchOp>(nodes[nodeId].op);
}

std::string getNodeLabel(size_t nodeId) const override;
std::string getNodeDotId(size_t nodeId) const override;
std::string getNodeLabel(NodeIdType nodeId) const override;
std::string getNodeDotId(NodeIdType nodeId) const override;

std::vector<ReconvergentPath> findReconvergentPaths() const;

/// Build the graph from a given transition sequence.
void buildGraphFromSequence(handshake::FuncOp funcOp,
const std::vector<ArchBB> &sequence);
llvm::ArrayRef<ArchBB> sequence);

/// Within the transition sequence, we may have transitions that look like
/// Step 0: BB1 -> Step 1: BB1 -> Step 2: BB2. Steps are the way to
/// distinguish between specific operations of the same BB accross different
/// transitions.
unsigned getNodeStep(size_t nodeId) const { return nodeIdToStep.at(nodeId); };
unsigned getNodeStep(NodeIdType nodeId) const {
return nodeIdToStep.at(nodeId);
};

/// Get the BB id for a given step. Returns -1 if step not found.
unsigned getStepBB(unsigned step) const {
Expand All @@ -198,26 +216,31 @@ class ReconvergentPathFinderGraph : public DataflowSubgraphBase {

// Debugging Methods //

void dumpReconvergentPaths(const std::vector<ReconvergentPath> &paths,
void dumpReconvergentPaths(llvm::ArrayRef<ReconvergentPath> paths,
llvm::StringRef filename) const;

void dumpTransitionGraph(llvm::StringRef filename) const;

/// Dump multiple graphs to a single GraphViz file.
/// Each graph is placed in its own cluster subgraph.
static void
dumpAllGraphs(const std::vector<ReconvergentPathFinderGraph> &graphs,
llvm::StringRef filename);
static void dumpAllGraphs(llvm::ArrayRef<ReconvergentPathFinderGraph> graphs,
llvm::StringRef filename);

/// Dump all reconvergent paths from multiple graphs to a single GraphViz
/// file. Each path is placed in its own cluster subgraph with a graph index
/// prefix. The input is a vector of (sequenceIndex, (graph, paths)) pairs.
static void dumpAllReconvergentPaths(
const std::vector<
std::pair<size_t, std::pair<const ReconvergentPathFinderGraph *,
std::vector<ReconvergentPath>>>>
&graphPaths,
llvm::StringRef filename);
/// prefix. The input is a vector of GraphPathsForDumping objects. Each object
/// contains:
/// - graph: Pointer to the ReconvergentPathFinderGraph for this sequence.
/// - paths: Vector of ReconvergentPath objects for this sequence.

struct GraphPathsForDumping {
const ReconvergentPathFinderGraph *graph;
std::vector<ReconvergentPath> paths;
};

static void
dumpAllReconvergentPaths(llvm::ArrayRef<GraphPathsForDumping> graphPaths,
llvm::StringRef filename);

private:
std::map<unsigned, unsigned> stepToBB;
Expand All @@ -226,19 +249,120 @@ class ReconvergentPathFinderGraph : public DataflowSubgraphBase {
std::map<unsigned, unsigned> nodeIdToStep;

/// Maps (Operation*, step) to node ID for O(1) lookup.
std::map<std::pair<mlir::Operation *, unsigned>, size_t> nodeMap;
std::map<std::pair<mlir::Operation *, unsigned>, NodeIdType> nodeMap;

/// Get the node ID for an operation at a given step, creating it if needed.
size_t getOrAddNode(mlir::Operation *op, unsigned step) {
NodeIdType getOrAddNode(mlir::Operation *op, unsigned step) {
auto key = std::make_pair(op, step);
if (auto it = nodeMap.find(key); it != nodeMap.end())
return it->second;

size_t id = addNode(op);
NodeIdType id = addNode(op);
nodeMap[key] = id;
nodeIdToStep[id] = step;
return id;
}
};

struct SimpleCycle {
llvm::SmallVector<NodeIdType> nodes; // <-- The node IDs of the cycle.
SimpleCycle(llvm::ArrayRef<NodeIdType> nodes) : nodes(nodes) {}

/// Check if this cycle shares any nodes with another cycle.
bool isDisjointFrom(const SimpleCycle &other) const;
};

struct EdgesToJoin {
NodeIdType joinId;

/// Edge indices (into nonCyclicAdjList) on any path from cycle to join.
std::vector<EdgeIdType> edgesFromCycleOne;
std::vector<EdgeIdType> edgesFromCycleTwo;

EdgesToJoin(NodeIdType join) : joinId(join) {}
};

struct SynchronizingCyclePair {
SimpleCycle cycleOne;
SimpleCycle cycleTwo;

std::vector<EdgesToJoin> edgesToJoins;

SynchronizingCyclePair(SimpleCycle one, SimpleCycle two,
std::vector<EdgesToJoin> edges)
: cycleOne(std::move(one)), cycleTwo(std::move(two)),
edgesToJoins(std::move(edges)) {}
};

class SynchronizingCyclesFinderGraph : public DataflowSubgraphBase {
public:
/// Build the graph from a CFDFC.
void buildFromCFDFC(handshake::FuncOp funcOp, const buffer::CFDFC &cfdfc);

/// Find all simple cycles in the graph.
std::vector<SimpleCycle> findAllCycles() const;

/// Find all pairs of synchronizing cylces.
std::vector<SynchronizingCyclePair> findSynchronizingCyclePairs();

bool isForkNode(NodeIdType nodeId) const override {
return isa<handshake::ForkOp, handshake::LazyForkOp,
handshake::EagerForkLikeOpInterface>(nodes[nodeId].op);
}

/// NOTE: When it belongs to a CFDFC, MuxOp behaves like a join node.
bool isJoinNode(NodeIdType nodeId) const override {
return isa<handshake::MuxOp, handshake::JoinLikeOpInterface,
handshake::ConditionalBranchOp>(nodes[nodeId].op);
}

std::string getNodeLabel(NodeIdType nodeId) const override;
std::string getNodeDotId(NodeIdType nodeId) const override;

/// Dump a single synchronizing cycle pair to a GraphViz file.
void dumpSynchronizingCyclePair(const SynchronizingCyclePair &pair,
llvm::StringRef filename) const;

/// Dump all synchronizing cycle pairs to a single GraphViz file.
void
dumpAllSynchronizingCyclePairs(llvm::ArrayRef<SynchronizingCyclePair> pairs,
llvm::StringRef filename) const;

private:
std::map<mlir::Operation *, NodeIdType> opToNodeId;

/// Adjacency list for the non-cyclic subgraph (stores edge indices).
std::vector<std::vector<EdgeIdType>> nonCyclicAdjList;

NodeIdType getOrAddNode(mlir::Operation *op);

void computeSccsAndBuildNonCyclicSubgraph();

/// Find all edges (from nonCyclicAdjList) on any path from cycle to join.
/// An edge is included if its source is reachable from the cycle and its
/// destination can reach the join.
/// @returns A vector of indices into the edges vector.
std::vector<EdgeIdType> findEdgesToJoin(const SimpleCycle &cycle,
NodeIdType joinId) const;

/// Get all join node IDs in the graph.
std::vector<NodeIdType> getAllJoins() const;
};

// Helper struct needed for Boost's tiernan_all_cycles algorithm.
// As opposed to just returning cycles directly, it calls a method on a
// user-provided visitor object each time a cycle is found.
struct CycleCollector {
std::vector<SimpleCycle> &cycles;

template <typename Path, typename Graph>
void cycle(const Path &p, const Graph &) {
llvm::SmallVector<NodeIdType> nodeIds;
for (auto v : p) {
nodeIds.push_back(v);
}
cycles.emplace_back(std::move(nodeIds));
}
};

} // namespace dynamatic
Loading