-
Notifications
You must be signed in to change notification settings - Fork 49
[Formal][Property Annotation] Annotate Reconvergent-paths using flow variables #716
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
50 commits
Select commit
Hold shift + click to select a range
d97255d
extracting local flow equations
f00908f
gaussian elimination
bea38a4
fixed some issues to make it run
f613376
fixed some unit generators
1a84b75
changed reconvergent path invariant to put all equations into a singl…
84dd161
DSL for FlowExpressions, ignore some channels
3b94beb
linear algebra into separate module
27898e3
skip loadOp
c417fd8
added some debug information
7ec2e8d
support for multiple-slot buffers
9aa123c
first adjustments to support internal names
07af701
latency induced slot namer
cd64ccf
unit generator names for latency-induced slots now match internal sta…
7785970
start of plus-minus lambdas
f1bc0d6
lambdas with plus-minus, with convenient usage
80fdda6
small fix in mux local equation extraction
7984367
removed debug code and old, unused code
7ca4f85
added constrained buffer slots/eager fork sents
5e5d4f6
added json to namers
7bdbf50
extraction of flowExpression to separate file
780c2ea
annotate plus/minus variables
a91c7ae
cleanup of code and fixing some small mistakes
13d8203
further cleanup
7c66fd6
comments for clarity
729cd7e
+- contrains for buffer slots
7326558
representation of FlowVariable using std::variant
16ba2ed
plus/minus variables -> constrained variables
90603b1
fixed some logic issues that caused equations to be missing / wrong
d230df4
annotate index channels as its own pass
1785b5a
cleaned up FlowExpression code by adding comments and clarifying names
cc76bde
references for boost matrix data types
b7b8d7b
split local equation extraction into smaller functions
fd271e4
annotating branch equations, further cleanup of local equation extrac…
8fe6e53
comments for explanation: FlowExpression DSL, reconvergent path expla…
37fa697
small fix from merging
672b349
fixed naming inconsistencies
c59d5d9
explicit handling of operations, while outputting an error for all ot…
7a320ee
fixed errors when compiling in release mode
060f422
fixed some more errors by changing assert -> report fatal error
433fd13
propagate errors up using logical result
94cb5f3
annotate memory controller <-> load port equations
f876e09
Cleanup of the flow equation extracting code: Handle each operation i…
cb1c311
VariableRegistry abstraction, move FlowSystem to FlowExpression.cpp
196ca36
added explanations for each extracted equation
6de40ce
wrapper for shared_ptr<InternalStateNamer> to simplify the API of Flo…
2b13122
new representation of IndexTokenTrackers to better convey what the in…
7134f35
removed some debug stuff
71cfea0
use ArithOpInterface rather than manually listing operations
aabeb76
small change in Linear algebra cmake file
fab9182
merge
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
377 changes: 377 additions & 0 deletions
377
experimental/include/experimental/Support/FlowExpression.h
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.