Skip to content

[Buffers] fix non-CFDFC paths having non-zero occupancy - #979

Open
ziadomalik wants to merge 4 commits into
mainfrom
fix/ziad/non-zero-occ-at-non-cfc-channels
Open

[Buffers] fix non-CFDFC paths having non-zero occupancy#979
ziadomalik wants to merge 4 commits into
mainfrom
fix/ziad/non-zero-occ-at-non-cfc-channels

Conversation

@ziadomalik

Copy link
Copy Markdown
Collaborator

Problem: The implementation of the FPGA24 MILP's were missing constraint (10) and (11) described in this paper, resulting in patterns that are not in CFC's have a non-zero occupancy, which makes no sense, because occupancy tries to cater for incoming tokens in a loop body. (Initiation Interval).

This PR: Adds the missing constraints and re-works the rest of the code to adapt, as well as explicitly ignore patterns in the occupancy LP that have forks that are not part of a CFC.

@ziadomalik
ziadomalik requested a review from Jiahui17 June 15, 2026 15:27
auto latOrFail =
timingDB.getLatency(node.op, SignalType::DATA, targetPeriod);
if (succeeded(latOrFail) && *latOrFail > 0.0)
constPart += *latOrFail;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is II in the original constraint?

Comment thread lib/Transforms/BufferPlacement/Utils/BufferPlacementMILP.cpp
@Jiahui17

Copy link
Copy Markdown
Member

@ziadomalik

Another thing that came across: could you generate a ONE_SLOT_BREAK_DV instead of a counter buffer if lat == 1 and slots == 1?

@Jiahui17

Copy link
Copy Markdown
Member

Not related to the PR but we should remember to check if it helps

@ziadomalik

Copy link
Copy Markdown
Collaborator Author

Hi, turns out I thought adding the filtering was going to be enough so I kept the style where I formulated the constraint for all channels in a CFDFC. This is bad, since we emit one global equality per path pair, instead of one per CFDFC and a path shared by two CFDFCs with different IIs needs a separate constraint under each II, and the code was disregarding that. Also good catch on missing the II division, I think it was indirectly caused by the above. Now that we do per-CFDFC we can use the entries in computedCFDFCIIs that we already computed, the way we did it before wouldn't have made it possible. Like what II would I even pick. I think this should be fine now, no major improvements in terms of FF and LUT's.

Also tried the setting of ONE_SLOT_BREAK_DV for the case you mentioned above, nothing has changed, I will keep investigating though...

/// 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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a remark saying why we care about this? (i guess this is for setting the occupancy to be 1 for every channel that is not part of a cfdfc, but has latency > 0?

Comment on lines +175 to +176
if (cfdfcChannels.contains(channel))
requiredOccupancy[channel] = static_cast<double>(latency) / targetII;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a duplicate of the logic below?

Comment on lines +1359 to +1401
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.
if (forkNode.type == DataflowGraphNode::REGULAR &&
llvm::none_of(cfdfcs, [&](CFDFC *cfdfc) {
return cfdfc->units.contains(forkNode.op);
}))
continue;

std::vector<SimplePath> simplePaths =
enumerateSimplePaths(*graph, rp.forkNodeId, rp.joinNodeId, rp.nodeIds);
if (simplePaths.size() < 2)
continue;

// For each simple path, the sum of channel occupancies (N_c) and the total
// latency of the units.
std::vector<std::pair<LinExpr, double>> pathTerms;
for (const auto &path : simplePaths) {
LinExpr occupancySum;
double latencySum = 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)
latencySum += *latOrFail;
}

for (EdgeIdType edgeId : path.edges) {
Value channel = graph->edges[edgeId].channel;
if (channelOccupancy.count(channel))
occupancySum += channelOccupancy[channel];
}

pathTerms.emplace_back(std::move(occupancySum), latencySum);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't convince myself that this logic corresponds to

Image

Does it make sense to do something like:

for (auto cf : CFDFCs)

  for (auto rp : reconvergentPaths) {
    if (/* the BB sequence of "rp" is contained in the CFDFC */)
      /* add occupancy balancing constraint */
  }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants