Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
d97255d
extracting local flow equations
Jan 23, 2026
f00908f
gaussian elimination
Jan 23, 2026
bea38a4
fixed some issues to make it run
Jan 23, 2026
f613376
fixed some unit generators
Feb 18, 2026
1a84b75
changed reconvergent path invariant to put all equations into a singl…
Jan 26, 2026
84dd161
DSL for FlowExpressions, ignore some channels
Jan 29, 2026
3b94beb
linear algebra into separate module
Feb 2, 2026
27898e3
skip loadOp
Feb 5, 2026
c417fd8
added some debug information
Feb 11, 2026
7ec2e8d
support for multiple-slot buffers
Feb 16, 2026
9aa123c
first adjustments to support internal names
Feb 18, 2026
07af701
latency induced slot namer
Feb 19, 2026
cd64ccf
unit generator names for latency-induced slots now match internal sta…
Feb 19, 2026
7785970
start of plus-minus lambdas
Feb 20, 2026
f1bc0d6
lambdas with plus-minus, with convenient usage
Feb 21, 2026
80fdda6
small fix in mux local equation extraction
Feb 21, 2026
7984367
removed debug code and old, unused code
Feb 21, 2026
7ca4f85
added constrained buffer slots/eager fork sents
Feb 26, 2026
5e5d4f6
added json to namers
Feb 27, 2026
7bdbf50
extraction of flowExpression to separate file
Feb 27, 2026
780c2ea
annotate plus/minus variables
Mar 2, 2026
a91c7ae
cleanup of code and fixing some small mistakes
Mar 2, 2026
13d8203
further cleanup
Mar 2, 2026
7c66fd6
comments for clarity
Mar 2, 2026
729cd7e
+- contrains for buffer slots
Mar 2, 2026
7326558
representation of FlowVariable using std::variant
Mar 4, 2026
16ba2ed
plus/minus variables -> constrained variables
Mar 8, 2026
90603b1
fixed some logic issues that caused equations to be missing / wrong
Mar 8, 2026
d230df4
annotate index channels as its own pass
Mar 11, 2026
1785b5a
cleaned up FlowExpression code by adding comments and clarifying names
Mar 13, 2026
cc76bde
references for boost matrix data types
Mar 13, 2026
b7b8d7b
split local equation extraction into smaller functions
Mar 13, 2026
fd271e4
annotating branch equations, further cleanup of local equation extrac…
Mar 13, 2026
8fe6e53
comments for explanation: FlowExpression DSL, reconvergent path expla…
Mar 17, 2026
37fa697
small fix from merging
Mar 17, 2026
672b349
fixed naming inconsistencies
Mar 17, 2026
c59d5d9
explicit handling of operations, while outputting an error for all ot…
Mar 23, 2026
7a320ee
fixed errors when compiling in release mode
Mar 23, 2026
060f422
fixed some more errors by changing assert -> report fatal error
Mar 23, 2026
433fd13
propagate errors up using logical result
Mar 25, 2026
94cb5f3
annotate memory controller <-> load port equations
Mar 30, 2026
f876e09
Cleanup of the flow equation extracting code: Handle each operation i…
Mar 30, 2026
cb1c311
VariableRegistry abstraction, move FlowSystem to FlowExpression.cpp
Mar 30, 2026
196ca36
added explanations for each extracted equation
Mar 31, 2026
6de40ce
wrapper for shared_ptr<InternalStateNamer> to simplify the API of Flo…
Mar 31, 2026
2b13122
new representation of IndexTokenTrackers to better convey what the in…
Mar 31, 2026
7134f35
removed some debug stuff
Mar 31, 2026
71cfea0
use ArithOpInterface rather than manually listing operations
Apr 1, 2026
aabeb76
small change in Linear algebra cmake file
Apr 1, 2026
fab9182
merge
Apr 1, 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
377 changes: 377 additions & 0 deletions experimental/include/experimental/Support/FlowExpression.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,377 @@
#ifndef DYNAMATIC_SUPPORT_FLOW_EXPRESSION_H
#define DYNAMATIC_SUPPORT_FLOW_EXPRESSION_H

#include "dynamatic/Analysis/IndexChannelAnalysis.h"
#include "dynamatic/Dialect/Handshake/HandshakeInterfaces.h"
#include "dynamatic/Dialect/Handshake/HandshakeOps.h"
#include "dynamatic/Support/LLVM.h"
#include "dynamatic/Support/LinearAlgebra/Gaussian.h"
#include "mlir/IR/Value.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/Support/JSON.h"
#include <bitset>
#include <variant>

// FlowExpression together with FlowVariable implements a DSL for combining flow
// variables, i.e. variables that track the flow and state of tokens, into
// equations. A similar DSL exists for constraint programming in
// `ConstraintProgramming.h`, but it is not reused for the following reasons:
// 1. FlowExpression uses integer coefficients, whereas CPVars have doubles as
// coefficients
// 2. Metadata that is only necessary for FlowExpressions can easily be added
// (variant, index tracker)
// 3. No name is necessary, as each variable is uniquely defined by the metadata
// 4. Dedicated conversion function to put multiple flow expressions into a
// matrix, with an ordering according to whether the variables are annotatable
// in SMV for use with Gaussian elimination

namespace dynamatic {
namespace handshake {

// IndexTracker is used to represent flow variables that, instead of tracking
// any token, only track tokens of a specific index. It is automatically added
// for channel lambdas using IndexChannelAnalysis to determine if the channel
// deals with indices.
struct IndexTracker {
// numValues is the number of index values that are possible for this token.
// For example, if the token is used as the select input of a mux with 3 data
// inputs, numValues will be 3. The indices are always assumed to range from
// (0 .. numValues - 1), so all possible tokens would be {0, 1, 2} in the
// example.
// Note that this is slightly more information than simply the bit width of a
// channel: In the example, the bit width of the channel is 2 bits, but one of
// the 4 resulting options is invalid.
size_t numValues;
// Which token value does this variable track?
// e.g. 1 to only count tokens with value 1
// If trackedValue has no value, any value (0..numValues) is tracked
std::optional<size_t> trackedValue;

IndexTracker(size_t numValues) : numValues(numValues) {}
inline bool operator==(const IndexTracker &other) const {
return numValues == other.numValues && trackedValue == other.trackedValue;
}

inline llvm::json::Value toJSON() const {
return llvm::json::Object(
{{NUM_VALUES_LIT, numValues}, {SINGLE_VALUE_LIT, trackedValue}});
}

inline IndexTracker static fromJSON(const llvm::json::Value &value,
llvm::json::Path path) {
size_t numValues;
std::optional<size_t> singleValue;
llvm::json::ObjectMapper mapper(value, path);
if (!mapper || !mapper.map(NUM_VALUES_LIT, numValues) ||
!mapper.map(SINGLE_VALUE_LIT, singleValue)) {
llvm::report_fatal_error("json parsing of failed");
}

IndexTracker ret(numValues);
ret.trackedValue = singleValue;
return ret;
}

inline static const StringLiteral NUM_VALUES_LIT = "num_values";
inline static const StringLiteral SINGLE_VALUE_LIT = "single_value";
};

// IndexTokenTracker is used to represent flow variables that, instead of
// tracking any token, only track tokens of specific indices. It is
// automatically added for channel lambdas using IndexChannelAnalysis to
// determine if the channel deals with indices.
struct IndexTokenTracker {
using TrackerBitSet = std::bitset<8>;
// If trackedValues[i] is set, this tracker tracks tokens of value i
TrackerBitSet trackedValues;

// Tracker with the first `numValues` bits set to 1, and the rest to 0
IndexTokenTracker(TrackerBitSet trackedValues)
: trackedValues(trackedValues) {}
IndexTokenTracker(size_t numValues);
inline llvm::json::Value toJSON() const {
return llvm::json::Object({{TRACKED_VALUES_LIT, trackedValues.to_ulong()}});
}

IndexTokenTracker static fromJSON(const llvm::json::Value &value,
llvm::json::Path path);

inline void only(size_t value) { trackedValues &= (1 << value); }

size_t getSingleValue() const {
assert(trackedValues.count() == 1);
for (size_t index = 0; index < trackedValues.size(); ++index) {
if (trackedValues.test(index))
return index;
}
assert(false);
}

inline bool operator==(const IndexTokenTracker &other) const {
return trackedValues == other.trackedValues;
}

inline bool operator!=(const IndexTokenTracker &other) const {
return trackedValues != other.trackedValues;
}

inline static const StringLiteral TRACKED_VALUES_LIT = "tracked_values";
};

struct ChannelLambda {
mlir::Value channel;

ChannelLambda(mlir::Value channel) : channel(channel) {}
inline bool operator==(const ChannelLambda &other) const {
return channel == other.channel;
}
};

struct InternalLambda {
Operation *op;
unsigned index;

InternalLambda(Operation *op, unsigned index) : op(op), index(index) {}
inline bool operator==(const InternalLambda &other) const {
return op == other.op && index == other.index;
}
};

struct FlowInternalState {
// FlowInternalState is a wrapper around std::shared_ptr<InternalStateNamer>
// to represent variables within FlowVariable. It simplifies constructors, as
// it hides the creation of shared pointers. Shared pointers are required for
// the following reasons:
// 1. InternalStateNamer is an abstract class, meaning the size depends on
// which internal state (EagerForkSent / BufferSlotFull) it is, so it can only
// be used as a pointer or a reference.
// 2. FlowVariables are regularly copied (when they are part of multiple
// different equations), so the copy operation should be cheap. This excludes
// std::unique_ptr
inline FlowInternalState(const std::shared_ptr<InternalStateNamer> &namer)
: namer(namer) {}
template <typename T>
inline FlowInternalState(const T &typedNamer) {
std::shared_ptr<InternalStateNamer> anyNamer =
std::make_shared<T>(typedNamer);
namer = anyNamer;
}
inline bool operator==(const FlowInternalState &other) const {
return namer == other.namer;
}
std::shared_ptr<InternalStateNamer> namer;
};

struct FlowVariable {
// A flow variable can be defined by different parts of a circuit:
// 1. It can be a channel lambda, which tracks the number of tokens that
// propagated through a specific channel.
// 2. It can be an internal state, which represents any data that can be found
// in the final HDL: Most commonly, this is the data within a slot of a
// buffer, or the state keeping track of which results of an eager fork have
// been sent.
// 3. It can be an internal lambda, which acts similar to a channel lambda,
// except it does not count the tokens of a real channel in the handshake
// MLIR, but rather a fictional channel within an operation (e.g. a channel
// between slots of a buffer with multiple slots)
using Variants =
std::variant<FlowInternalState, ChannelLambda, InternalLambda>;
Variants variable;

// When indexTokenConstraint is set, only tokens of a specific value are
// tracked. This can be useful e.g. in the case of control merges, where it is
// known that a token at operand 0 leads to a token of value 0 at the index
// output, and correspondingly for a token at operand 1.
std::optional<IndexTokenTracker> indexTokenTracker;

FlowVariable(Variants variable)
: variable(std::move(variable)), indexTokenTracker() {}
FlowVariable(const IndexChannelAnalysis &indexChannels,
ChannelLambda channel);
FlowVariable(InternalLambda l) : FlowVariable(Variants(l)) {}
FlowVariable(FlowInternalState state) : FlowVariable(Variants(state)) {}

// Utility function for generating multiple internal channels for a single
// operation without collisions of indices
FlowVariable nextInternal() const;

// Compares the relevant struct fields to determine if two variables are equal
inline bool operator==(const FlowVariable &other) const {
return variable == other.variable &&
indexTokenTracker == other.indexTokenTracker;
}

// Utility functions for handling flow variables with index tokens
inline bool isIndex() const { return indexTokenTracker.has_value(); }
inline bool isNull() const {
return indexTokenTracker && indexTokenTracker->trackedValues == 0;
}
FlowVariable setTrackedTokens(size_t x) const;

std::string getDebugName() const;

// Get the annotater for internal state
// If the annotater does not exist, a nullptr is returned
// Usage example:
// if (auto annotater = var.getAnnotater()) {
// ...
// }
std::shared_ptr<InternalStateNamer> getAnnotater() const;
};

} // namespace handshake
} // namespace dynamatic

using namespace dynamatic;
using namespace dynamatic::handshake;
template <>
struct std::hash<FlowVariable::Variants> {
size_t operator()(const FlowVariable::Variants &vars) const {
using std::hash;
size_t chunk = hash<size_t>()(vars.index());
if (auto *namer = std::get_if<FlowInternalState>(&vars)) {
return chunk ^ hash<std::string>()(namer->namer->getSMVName());
}
if (auto *channel = std::get_if<ChannelLambda>(&vars)) {
return chunk ^ mlir::hash_value(channel->channel);
}
if (auto *internal = std::get_if<InternalLambda>(&vars)) {
return chunk ^ hash<Operation *>()(internal->op) ^
hash<unsigned>()(internal->index);
}
llvm::report_fatal_error("non-exhaustive pattern");
}
};
// Hash implementation required so that FlowVariable can be used in an
// unordered_map
template <>
struct std::hash<FlowVariable> {
size_t operator()(const FlowVariable &var) const {
using std::hash;
if (var.indexTokenTracker) {
return hash<FlowVariable::Variants>()(var.variable) ^
hash<unsigned>()(0) ^
hash<IndexTokenTracker::TrackerBitSet>()(
var.indexTokenTracker->trackedValues);
}
return hash<FlowVariable::Variants>()(var.variable) ^ hash<unsigned>()(1);
}
};

namespace dynamatic {
namespace handshake {
struct FlowExpression {
std::unordered_map<FlowVariable, int> terms;
FlowExpression() = default;
FlowExpression(const FlowVariable &v);

llvm::json::Value toJSON() const;
static FlowExpression fromJSON(const llvm::json::Value &value,
llvm::json::Path path);

std::string debug() const;

inline static const StringLiteral COEFFICIENT_LIT = "coefficient";
inline static const StringLiteral STATE_LIT = "state";
inline static const StringLiteral CONSTRAINT_LIT = "constraint";
};

FlowExpression operator-(FlowExpression expr);

FlowExpression operator+(FlowExpression left, const FlowExpression &right);

FlowExpression operator*(int coef, FlowExpression expr);

FlowExpression operator-(FlowExpression left, const FlowExpression &right);

void operator+=(FlowExpression &left, const FlowExpression &right);

void operator-=(FlowExpression &left, const FlowExpression &right);

struct FlowEquationExtractor {
std::vector<FlowExpression> equations;
const IndexChannelAnalysis &indexChannelAnalysis;

FlowEquationExtractor(const IndexChannelAnalysis &ica)
: equations(), indexChannelAnalysis(ica) {}
LogicalResult extractAll(ModuleOp modOp);

LogicalResult extractSlotEquation(const FlowVariable &in,
const FlowVariable &out,
const FlowVariable &slot);
LogicalResult extractEagerSentEquation(const FlowVariable &in,
const FlowVariable &out,
const FlowVariable &sent);

LogicalResult extractArithmeticJoinOp(Operation &op);
LogicalResult extractBranchOp(ConditionalBranchOp branchOp);
LogicalResult extractBufferOp(BufferOp bufferOp);
LogicalResult extractControlMergeOp(ControlMergeOp cmergeOp);
LogicalResult extractEndOp(EndOp endOp);
LogicalResult extractForkOp(ForkOp forkOp);
LogicalResult extractLoadOp(LoadOp loadOp);
LogicalResult extractMemoryControllerOp(MemoryControllerOp memCon);
LogicalResult extractMuxOp(MuxOp muxOp);
LogicalResult extractPipeline(LatencyInterface op, FlowVariable &internal);
LogicalResult extractStoreOp(StoreOp storeOp);
};

namespace {
// This is a helper class to create a bidirectional mapping between variables
// and indices
class VariableRegistry {
public:
size_t addVariable(const FlowVariable &var) {
if (auto it = varToIndex.find(var); it != varToIndex.end()) {
return it->second;
}
size_t newIdx = indexToVar.size();
varToIndex[var] = newIdx;
indexToVar.push_back(var);
return newIdx;
}

bool verify() {
if (!(varToIndex.size() == indexToVar.size()))
return false;
for (size_t i = 0; i < indexToVar.size(); ++i) {
FlowVariable &a = indexToVar[i];
size_t j = varToIndex[a];
if (i != j)
return false;
}
return true;
}

inline size_t getIndex(const FlowVariable &var) const {
return varToIndex.at(var);
}
inline const FlowVariable &getVar(size_t idx) const {
return indexToVar.at(idx);
}
inline size_t size() const { return indexToVar.size(); }

private:
std::unordered_map<FlowVariable, size_t> varToIndex;
std::vector<FlowVariable> indexToVar;
};
} // namespace

// FlowSystem is a wrapper for combining flow equations with a corresponding
// matrix. It keeps track of which variable corresponds to which column index,
// and makes sure that low indices are resesrved for variables that cannot be
// annotated to ensure they are eliminated first in the row-echelon form
struct FlowSystem {
VariableRegistry registry;
size_t nLambdas;
MatIntType matrix;

FlowExpression getRowAsExpression(size_t row) const;

FlowSystem() = default;
FlowSystem(const std::vector<FlowExpression> &exprs);
};
} // namespace handshake
} // namespace dynamatic

#endif
Loading
Loading