From 668114283c3d4ea067eb2f1742cf399b3cdde94a Mon Sep 17 00:00:00 2001 From: Ziad Malik Date: Sat, 13 Jun 2026 23:36:01 +0200 Subject: [PATCH 1/4] chore: fix non-CFDFC paths having non-zero occupancy chore: add constraints 10 and 15 --- .../Utils/BufferPlacementMILP.h | 8 +++ .../BufferPlacement/FPGA24Buffers.cpp | 47 +++++++------ .../Utils/BufferPlacementMILP.cpp | 66 +++++++++++++++++++ 3 files changed, 101 insertions(+), 20 deletions(-) diff --git a/include/dynamatic/Transforms/BufferPlacement/Utils/BufferPlacementMILP.h b/include/dynamatic/Transforms/BufferPlacement/Utils/BufferPlacementMILP.h index 2d725b0d6..09404bf70 100644 --- a/include/dynamatic/Transforms/BufferPlacement/Utils/BufferPlacementMILP.h +++ b/include/dynamatic/Transforms/BufferPlacement/Utils/BufferPlacementMILP.h @@ -437,6 +437,14 @@ class BufferPlacementMILP : public MILP { void addBackedgeConstraints(ArrayRef cfdfcs, llvm::MapVector &channelOccupancy); + /// [FPGA24] Adds path-level occupancy equality constraints for reconvergent + /// paths that are entirely within a CFDFC (Paper: Section 5, Equations 10-11). + /// We skip paths with forks outside all CFDFC's. + void addPathOccupancyEqualityConstraints( + ArrayRef reconvergentPaths, + ArrayRef cfdfcs, + llvm::MapVector &channelOccupancy); + /// [FPGA24] Adds imbalance constraints for reconvergent paths in LP1. void addReconvergentPathConstraints( ArrayRef reconvergentPaths); diff --git a/lib/Transforms/BufferPlacement/FPGA24Buffers.cpp b/lib/Transforms/BufferPlacement/FPGA24Buffers.cpp index fc8fda484..d8c4e38ee 100644 --- a/lib/Transforms/BufferPlacement/FPGA24Buffers.cpp +++ b/lib/Transforms/BufferPlacement/FPGA24Buffers.cpp @@ -26,6 +26,7 @@ #include "mlir/IR/Value.h" #include "mlir/Support/LLVM.h" #include "mlir/Support/LogicalResult.h" +#include "llvm/ADT/DenseSet.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallVector.h" #include "llvm/Support/Debug.h" @@ -150,44 +151,45 @@ void OccupancyBalancingLP::setup() { return; } - /// Target II = 1 for maximum throughput double targetII = latencyResult.targetII; if (targetII <= 0.0) { targetII = 1.0; } - /// Create variables for each channel - /// N_c: Maximal token occupancy on channel c. + /// (Paper: Section 5, Table 2) + /// N_c: Maximal token occupancy on channel c. this->addOccupancyVars(allChannels, channelOccupancy, MAX_OCCUPANCY); - /// (Paper: Section 5, Equation 8): N_c >= L_c / II - /// We enforce this for the global II, but also for each CFDFC's specific II - /// to ensure sufficient buffering in faster loops. - /// (Paper: Section 5, Equation 15): Making the required occupancy the - /// maximum of all CFDFCs' II. + /// Determine which channels belong to at least one CFDFC. + llvm::DenseSet cfdfcChannels; + for (CFDFC *cfdfc : cfdfcs) + for (Value channel : cfdfc->channels) + cfdfcChannels.insert(channel); + /// (Paper: Section 5, Equation 8): N_c >= L_c / II + /// Per-channel lower bound ensuring the pipeline has enough tokens in flight. + /// For non-CFDFC channels (executed once), N_c >= 1 suffices. llvm::MapVector requiredOccupancy; - - // Initialize with global II constraint for (Value channel : allChannels) { unsigned latency = latencyResult.channelExtraLatency.lookup(channel); - requiredOccupancy[channel] = static_cast(latency) / targetII; + if (cfdfcChannels.contains(channel)) + requiredOccupancy[channel] = static_cast(latency) / targetII; + else + requiredOccupancy[channel] = (latency > 0) ? 1.0 : 0.0; } - // Update by taking the maximum of the per-CFDFC constraints + /// (Paper: Section 5, Equation 15): Take the maximum across per-CFDFC IIs. for (CFDFC *cfdfc : cfdfcs) { double cfdfcII = latencyResult.cfdfcTargetIIs.lookup(cfdfc); if (cfdfcII < 1.0) continue; - for (Value channel : cfdfc->channels) { - if (requiredOccupancy.count(channel)) { - unsigned latency = latencyResult.channelExtraLatency.lookup(channel); - double specificOcc = static_cast(latency) / cfdfcII; - if (specificOcc > requiredOccupancy[channel]) { - requiredOccupancy[channel] = specificOcc; - } - } + if (!requiredOccupancy.count(channel)) + continue; + unsigned latency = latencyResult.channelExtraLatency.lookup(channel); + double specificOcc = static_cast(latency) / cfdfcII; + if (specificOcc > requiredOccupancy[channel]) + requiredOccupancy[channel] = specificOcc; } } @@ -195,6 +197,11 @@ void OccupancyBalancingLP::setup() { addBackedgeConstraints(cfdfcs, channelOccupancy); addChannelPropertyOccupancyConstraints(channelOccupancy); + /// (Paper: Section 5, Equations 10-11): Path occupancy equality. + addPathOccupancyEqualityConstraints(reconvergentPaths, cfdfcs, + channelOccupancy); + + /// (Paper: Section 5, Equation 14): Minimize sum(B_c * N_c). this->setOccupancyBalancingObjective(channelOccupancy); markReadyToOptimize(); diff --git a/lib/Transforms/BufferPlacement/Utils/BufferPlacementMILP.cpp b/lib/Transforms/BufferPlacement/Utils/BufferPlacementMILP.cpp index 7c617b3a9..1afda9caa 100644 --- a/lib/Transforms/BufferPlacement/Utils/BufferPlacementMILP.cpp +++ b/lib/Transforms/BufferPlacement/Utils/BufferPlacementMILP.cpp @@ -1350,6 +1350,72 @@ void BufferPlacementMILP::addBackedgeConstraints( } } +void BufferPlacementMILP::addPathOccupancyEqualityConstraints( + ArrayRef reconvergentPaths, + ArrayRef cfdfcs, + llvm::MapVector &channelOccupancy) { + + // Collect all operations that belong to at least one CFDFC. + DenseSet cfdfcUnits; + for (CFDFC *cfdfc : cfdfcs) + for (Operation *op : cfdfc->units) + cfdfcUnits.insert(op); + + for (auto [pathIdx, pathWithGraph] : llvm::enumerate(reconvergentPaths)) { + const ReconvergentPath &rp = pathWithGraph.path; + const CFGTransitionSequenceSubgraph *graph = pathWithGraph.graph; + + // We skip paths whose fork is outside all CFDFCs. + const DataflowGraphNode &forkNode = graph->nodes[rp.forkNodeId]; + if (forkNode.type == DataflowGraphNode::REGULAR && + !cfdfcUnits.contains(forkNode.op)) + continue; + + std::vector simplePaths = + enumerateSimplePaths(*graph, rp.forkNodeId, rp.joinNodeId, rp.nodeIds); + if (simplePaths.size() < 2) + continue; + + // Compute the occupancy expression for each path (Eq 10): + // Occupancy(p) = sum(L_u / II for pipelined units u) + sum(N_c) + std::vector> pathOccupancies; + for (const auto &path : simplePaths) { + LinExpr varPart; + double constPart = 0.0; + + for (NodeIdType nodeId : path.nodes) { + if (nodeId == rp.forkNodeId || nodeId == rp.joinNodeId) + continue; + const DataflowGraphNode &node = graph->nodes[nodeId]; + if (node.type != DataflowGraphNode::REGULAR) + continue; + auto latOrFail = + timingDB.getLatency(node.op, SignalType::DATA, targetPeriod); + if (succeeded(latOrFail) && *latOrFail > 0.0) + constPart += *latOrFail; + } + + for (EdgeIdType edgeId : path.edges) { + Value channel = graph->edges[edgeId].channel; + if (channelOccupancy.count(channel)) + varPart += channelOccupancy[channel]; + } + + pathOccupancies.emplace_back(std::move(varPart), constPart); + } + + // (Eq 11): Occupancy(p0) = Occupancy(pi) + for (size_t i = 1; i < pathOccupancies.size(); ++i) { + auto &[vars0, const0] = pathOccupancies[0]; + auto &[varsI, constI] = pathOccupancies[i]; + + std::string name = + llvm::formatv("pathOccEq_{0}_{1}", pathIdx, i).str(); + model->addConstr(vars0 + const0 == varsI + constI, name); + } + } +} + void BufferPlacementMILP::addReconvergentPathConstraints( ArrayRef reconvergentPaths) { for (size_t pathIdx = 0; pathIdx < reconvergentPaths.size(); ++pathIdx) { From 02cb855c0a86f3b54ae765ecfff343236534f560 Mon Sep 17 00:00:00 2001 From: Ziad Malik Date: Mon, 15 Jun 2026 17:22:38 +0200 Subject: [PATCH 2/4] chore: clang-format --- .../Transforms/BufferPlacement/Utils/BufferPlacementMILP.h | 4 ++-- .../BufferPlacement/Utils/BufferPlacementMILP.cpp | 6 ++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/include/dynamatic/Transforms/BufferPlacement/Utils/BufferPlacementMILP.h b/include/dynamatic/Transforms/BufferPlacement/Utils/BufferPlacementMILP.h index 09404bf70..2205a462a 100644 --- a/include/dynamatic/Transforms/BufferPlacement/Utils/BufferPlacementMILP.h +++ b/include/dynamatic/Transforms/BufferPlacement/Utils/BufferPlacementMILP.h @@ -438,8 +438,8 @@ class BufferPlacementMILP : public MILP { llvm::MapVector &channelOccupancy); /// [FPGA24] Adds path-level occupancy equality constraints for reconvergent - /// paths that are entirely within a CFDFC (Paper: Section 5, Equations 10-11). - /// We skip paths with forks outside all CFDFC's. + /// paths that are entirely within a CFDFC (Paper: Section 5, Equations + /// 10-11). We skip paths with forks outside all CFDFC's. void addPathOccupancyEqualityConstraints( ArrayRef reconvergentPaths, ArrayRef cfdfcs, diff --git a/lib/Transforms/BufferPlacement/Utils/BufferPlacementMILP.cpp b/lib/Transforms/BufferPlacement/Utils/BufferPlacementMILP.cpp index 1afda9caa..d3468b612 100644 --- a/lib/Transforms/BufferPlacement/Utils/BufferPlacementMILP.cpp +++ b/lib/Transforms/BufferPlacement/Utils/BufferPlacementMILP.cpp @@ -1352,8 +1352,7 @@ void BufferPlacementMILP::addBackedgeConstraints( void BufferPlacementMILP::addPathOccupancyEqualityConstraints( ArrayRef reconvergentPaths, - ArrayRef cfdfcs, - llvm::MapVector &channelOccupancy) { + ArrayRef cfdfcs, llvm::MapVector &channelOccupancy) { // Collect all operations that belong to at least one CFDFC. DenseSet cfdfcUnits; @@ -1409,8 +1408,7 @@ void BufferPlacementMILP::addPathOccupancyEqualityConstraints( auto &[vars0, const0] = pathOccupancies[0]; auto &[varsI, constI] = pathOccupancies[i]; - std::string name = - llvm::formatv("pathOccEq_{0}_{1}", pathIdx, i).str(); + std::string name = llvm::formatv("pathOccEq_{0}_{1}", pathIdx, i).str(); model->addConstr(vars0 + const0 == varsI + constI, name); } } From 8cc38079b517454edb5a8ed04a0093ed3ab5fc56 Mon Sep 17 00:00:00 2001 From: Ziad Malik Date: Mon, 13 Jul 2026 23:11:14 +0200 Subject: [PATCH 3/4] chore: add per-cfdfc ii into the constraint --- .../Utils/BufferPlacementMILP.h | 1 + .../BufferPlacement/FPGA24Buffers.cpp | 1 + .../Utils/BufferPlacementMILP.cpp | 54 +++++++++++-------- 3 files changed, 33 insertions(+), 23 deletions(-) diff --git a/include/dynamatic/Transforms/BufferPlacement/Utils/BufferPlacementMILP.h b/include/dynamatic/Transforms/BufferPlacement/Utils/BufferPlacementMILP.h index 2205a462a..523efa807 100644 --- a/include/dynamatic/Transforms/BufferPlacement/Utils/BufferPlacementMILP.h +++ b/include/dynamatic/Transforms/BufferPlacement/Utils/BufferPlacementMILP.h @@ -443,6 +443,7 @@ class BufferPlacementMILP : public MILP { void addPathOccupancyEqualityConstraints( ArrayRef reconvergentPaths, ArrayRef cfdfcs, + const llvm::MapVector &cfdfcIIs, llvm::MapVector &channelOccupancy); /// [FPGA24] Adds imbalance constraints for reconvergent paths in LP1. diff --git a/lib/Transforms/BufferPlacement/FPGA24Buffers.cpp b/lib/Transforms/BufferPlacement/FPGA24Buffers.cpp index d8c4e38ee..5d6cfea4d 100644 --- a/lib/Transforms/BufferPlacement/FPGA24Buffers.cpp +++ b/lib/Transforms/BufferPlacement/FPGA24Buffers.cpp @@ -199,6 +199,7 @@ void OccupancyBalancingLP::setup() { /// (Paper: Section 5, Equations 10-11): Path occupancy equality. addPathOccupancyEqualityConstraints(reconvergentPaths, cfdfcs, + latencyResult.cfdfcTargetIIs, channelOccupancy); /// (Paper: Section 5, Equation 14): Minimize sum(B_c * N_c). diff --git a/lib/Transforms/BufferPlacement/Utils/BufferPlacementMILP.cpp b/lib/Transforms/BufferPlacement/Utils/BufferPlacementMILP.cpp index d3468b612..36301eff6 100644 --- a/lib/Transforms/BufferPlacement/Utils/BufferPlacementMILP.cpp +++ b/lib/Transforms/BufferPlacement/Utils/BufferPlacementMILP.cpp @@ -1352,22 +1352,20 @@ void BufferPlacementMILP::addBackedgeConstraints( void BufferPlacementMILP::addPathOccupancyEqualityConstraints( ArrayRef reconvergentPaths, - ArrayRef cfdfcs, llvm::MapVector &channelOccupancy) { - - // Collect all operations that belong to at least one CFDFC. - DenseSet cfdfcUnits; - for (CFDFC *cfdfc : cfdfcs) - for (Operation *op : cfdfc->units) - cfdfcUnits.insert(op); + ArrayRef cfdfcs, + const llvm::MapVector &cfdfcIIs, + llvm::MapVector &channelOccupancy) { for (auto [pathIdx, pathWithGraph] : llvm::enumerate(reconvergentPaths)) { const ReconvergentPath &rp = pathWithGraph.path; const CFGTransitionSequenceSubgraph *graph = pathWithGraph.graph; + const DataflowGraphNode &forkNode = graph->nodes[rp.forkNodeId]; // We skip paths whose fork is outside all CFDFCs. - const DataflowGraphNode &forkNode = graph->nodes[rp.forkNodeId]; if (forkNode.type == DataflowGraphNode::REGULAR && - !cfdfcUnits.contains(forkNode.op)) + llvm::none_of(cfdfcs, [&](CFDFC *cfdfc) { + return cfdfc->units.contains(forkNode.op); + })) continue; std::vector simplePaths = @@ -1375,12 +1373,11 @@ void BufferPlacementMILP::addPathOccupancyEqualityConstraints( if (simplePaths.size() < 2) continue; - // Compute the occupancy expression for each path (Eq 10): - // Occupancy(p) = sum(L_u / II for pipelined units u) + sum(N_c) - std::vector> pathOccupancies; + // For each simple path, the sum of channel occupancies (N_c) and the total latency of the units. + std::vector> pathTerms; for (const auto &path : simplePaths) { - LinExpr varPart; - double constPart = 0.0; + LinExpr occupancySum; + double latencySum = 0.0; for (NodeIdType nodeId : path.nodes) { if (nodeId == rp.forkNodeId || nodeId == rp.joinNodeId) @@ -1391,25 +1388,36 @@ void BufferPlacementMILP::addPathOccupancyEqualityConstraints( auto latOrFail = timingDB.getLatency(node.op, SignalType::DATA, targetPeriod); if (succeeded(latOrFail) && *latOrFail > 0.0) - constPart += *latOrFail; + latencySum += *latOrFail; } for (EdgeIdType edgeId : path.edges) { Value channel = graph->edges[edgeId].channel; if (channelOccupancy.count(channel)) - varPart += channelOccupancy[channel]; + occupancySum += channelOccupancy[channel]; } - pathOccupancies.emplace_back(std::move(varPart), constPart); + pathTerms.emplace_back(std::move(occupancySum), latencySum); } - // (Eq 11): Occupancy(p0) = Occupancy(pi) - for (size_t i = 1; i < pathOccupancies.size(); ++i) { - auto &[vars0, const0] = pathOccupancies[0]; - auto &[varsI, constI] = pathOccupancies[i]; + // (Paper: Section 5, Equation 11): Occupancy(p0) = Occupancy(pi) + for (auto [cfdfcIdx, cfdfc] : llvm::enumerate(cfdfcs)) { + if (forkNode.type == DataflowGraphNode::REGULAR && + !cfdfc->units.contains(forkNode.op)) + continue; + + double cfdfcII = std::max(1.0, cfdfcIIs.lookup(cfdfc)); + + for (size_t i = 1; i < pathTerms.size(); ++i) { + auto &[vars0, lat0] = pathTerms[0]; + auto &[varsI, latI] = pathTerms[i]; - std::string name = llvm::formatv("pathOccEq_{0}_{1}", pathIdx, i).str(); - model->addConstr(vars0 + const0 == varsI + constI, name); + std::string name = + llvm::formatv("pathOccEq_{0}_cfdfc{1}_{2}", pathIdx, cfdfcIdx, i) + .str(); + model->addConstr(vars0 + lat0 / cfdfcII == varsI + latI / cfdfcII, + name); + } } } } From e142eab9e3c1568347690c0b79139f2814f0697b Mon Sep 17 00:00:00 2001 From: Ziad Malik Date: Mon, 13 Jul 2026 23:12:58 +0200 Subject: [PATCH 4/4] chore: clang-format --- .../BufferPlacement/Utils/BufferPlacementMILP.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/Transforms/BufferPlacement/Utils/BufferPlacementMILP.cpp b/lib/Transforms/BufferPlacement/Utils/BufferPlacementMILP.cpp index 36301eff6..11c65688c 100644 --- a/lib/Transforms/BufferPlacement/Utils/BufferPlacementMILP.cpp +++ b/lib/Transforms/BufferPlacement/Utils/BufferPlacementMILP.cpp @@ -1352,8 +1352,7 @@ void BufferPlacementMILP::addBackedgeConstraints( void BufferPlacementMILP::addPathOccupancyEqualityConstraints( ArrayRef reconvergentPaths, - ArrayRef cfdfcs, - const llvm::MapVector &cfdfcIIs, + ArrayRef cfdfcs, const llvm::MapVector &cfdfcIIs, llvm::MapVector &channelOccupancy) { for (auto [pathIdx, pathWithGraph] : llvm::enumerate(reconvergentPaths)) { @@ -1373,7 +1372,8 @@ void BufferPlacementMILP::addPathOccupancyEqualityConstraints( if (simplePaths.size() < 2) continue; - // For each simple path, the sum of channel occupancies (N_c) and the total latency of the units. + // For each simple path, the sum of channel occupancies (N_c) and the total + // latency of the units. std::vector> pathTerms; for (const auto &path : simplePaths) { LinExpr occupancySum;