Skip to content

Commit

Permalink
[Handshake] Add handshake::BundleOp and handshake::UnbundleOp
Browse files Browse the repository at this point in the history
This introduces two new reciprocal operations to Handshake that allow to
go back and forth between "compound Handshake types"
(`!handshake.channel` and `!handshake.control`) and the individual
signals that make them up (making it possible to manipulate them
individually). These have rather complex (though almost identical)
verification logic to ensure that the conversion, whatever the
direction, is valid. Many new unit tests check that the implementation
produced an error for all possible types of invalid conversions.

The operations' assembly format is a bit verbose at the moment, this
should be improved in a future commit.
  • Loading branch information
lucas-rami committed Jul 9, 2024
1 parent bebfb02 commit 9fef8a5
Show file tree
Hide file tree
Showing 10 changed files with 486 additions and 23 deletions.
1 change: 1 addition & 0 deletions include/dynamatic/Dialect/Handshake/HandshakeOps.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#define DYNAMATIC_DIALECT_HANDSHAKE_HANDSHAKE_OPS_H

#include "dynamatic/Dialect/Handshake/HandshakeInterfaces.h"
#include "dynamatic/Dialect/Handshake/HandshakeTypes.h"
#include "dynamatic/Support/LLVM.h"
#include "mlir/Dialect/Affine/Analysis/AffineAnalysis.h"
#include "mlir/IR/OpDefinition.h"
Expand Down
127 changes: 127 additions & 0 deletions include/dynamatic/Dialect/Handshake/HandshakeOps.td
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ include "mlir/IR/BuiltinTypes.td"
include "mlir/IR/BuiltinAttributeInterfaces.td"
include "mlir/Interfaces/InferTypeOpInterface.td"
include "dynamatic/Dialect/Handshake/HandshakeAttributes.td"
include "dynamatic/Dialect/Handshake/HandshakeTypes.td"

/// Base class for Handshake dialect operations.
class Handshake_Op<string mnemonic, list<Trait> traits = []>
Expand Down Expand Up @@ -1146,5 +1147,131 @@ SameOperandsAndResultType
let assemblyFormat = " `[` $dataOperands `]` `,` `[` $sharedOpResult `]` attr-dict `:` `(` qualified(type($dataOperands)) `)` `,` `(` qualified(type($sharedOpResult)) `)` `->` `(` qualified(type($dataOut)) `)` ";
}

//===----------------------------------------------------------------------===//
// Type manipulation
//===----------------------------------------------------------------------===//

def BundleOp : Handshake_Op<"bundle", [Pure]> {
let summary = [{
Bundles individual signals into a `handshake::ChannelType` or `handshake::ControlType`.
}];
let description = [{
Combines individual signals into a channel-like value, producing upstream
signals as results and taking downstream signals as arguments. Note
that combining individual signals into a `handshake::ChannelType` is a
two-step process.
1. First, bundle a `i1` value, which produces a `!handshake.control` and
`i1` (representing the upstream ready signal) as results.
2. Then, bundle the `!handshake.control` along with a value representing the
data signal (of a compatible signal type e.g., `i32`), which produces
a `!handshake.channel<i32>`.

Example:
```
// Bundling into a channel with a downstream extra signal
%channel = bundle %ctrl, %data, %extra :
(!handshake.control, i32, i1) -> (!handshake.channel<i32, [extra: i1]>)

// -----

// Bundling into a channel with an upstream extra signal
%channel, %extra = bundle %ctrl, %data :
(!handshake.control, i32) -> (!handshake.channel<i32, [extra: i1]>, i1)

// -----

// Bundling into a control-only channel
%ctrl, %ready = unbundle %valid : (i1) -> (!handhsake.control, i1)
```
}];

let arguments = (ins Variadic<AnyType>:$signals);
let results = (outs ChannelLikeType:$channel, Variadic<SignalType>: $upstreams);

let skipDefaultBuilders = 1;
let builders = [
OpBuilder<(ins "::mlir::Value":$valid), [{
mlir::Type validReadyType = $_builder.getIntegerType(1);
assert(valid.getType() == validReadyType && "incorrect valid signal");

$_state.addOperands(valid);
$_state.addTypes({
::dynamatic::handshake::ControlType::get($_builder.getContext()),
validReadyType
});
}]>,
OpBuilder<(ins "::mlir::Value":$ctrl, "::mlir::Value":$data,
"::mlir::ValueRange":$downstreams,
"::dynamatic::handshake::ChannelType":$channelType)
>
];

let assemblyFormat = "$signals attr-dict `:` functional-type(operands, results)";

let hasVerifier = 1;
}

def UnbundleOp : Handshake_Op<"unbundle", [
Pure,
DeclareOpInterfaceMethods<InferTypeOpInterface, ["inferReturnTypes"]>
]> {
let summary = [{
Unbundles a `handshake::ChannelType` or `handshake::ControlType` into individual signals.
}];
let description = [{
Splits a channel-like value into its individual signals, producing
downstream signals as results and taking upstream signals as arguments. Note
that getting the individual valid and ready signals from a
`handshake::ChannelType` is a two-step process.
1. First, unbundle the `!handshake.channel`, which produces a
`!handshake.control` as result (i.e., the control is considered a
"downstream bundle").
2. Then, unbundle the `!handshake.control` along with an `i1` value
representing the ready signal, which produces an `i1` result representing
the valid signal.

Example:
```
// Unbundling a channel with a downstream extra signal
%ctrl, %data, %extra = unbundle %channel :
(!handshake.channel<i32, [extra: i2]>) -> (!handshake.control, i32, i2)

// -----

// Unbundling a channel with an upstream extra signal
%ctrl, %data = unbundle %channel [%extra] :
(!handshake.channel<i32, [extra: i2 (U)]>) -> (!handshake.control, i32, i2)

// -----

// Unbundling a control-only channel
%valid = unbundle %ctrl [%ready] : (!handshake.control, i1) -> i1
```
}];

let arguments = (ins ChannelLikeType:$channel, Variadic<SignalType>: $upstreams);
let results = (outs Variadic<AnyType>:$signals);

let builders = [
OpBuilder<(ins "::mlir::Value":$channel), [{
$_state.addOperands(channel);

// Infer return types
::llvm::SmallVector<::mlir::Type, 2> inferredReturnTypes;
if (::mlir::succeeded(UnbundleOp::inferReturnTypes($_builder.getContext(),
$_state.location, $_state.operands,
$_state.attributes.getDictionary($_state.getContext()),
$_state.getRawProperties(),
$_state.regions, inferredReturnTypes)))
$_state.addTypes(inferredReturnTypes);
else
::llvm::report_fatal_error("Failed to infer result type(s).");
}]>
];

let assemblyFormat = "$channel (`[` $upstreams^ `]`)? attr-dict `:` functional-type(operands, results)";

let hasVerifier = 1;
}

#endif // DYNAMATIC_DIALECT_HANDSHAKE_HANDSHAKE_OPS_TD
6 changes: 6 additions & 0 deletions include/dynamatic/Dialect/Handshake/HandshakeTypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@ llvm::hash_code hash_value(const ExtraSignal &signal);
} // namespace handshake
} // namespace dynamatic

namespace mlir {
class IndexType;
class IntegerType;
class FloatType;
} // namespace mlir

#define GET_TYPEDEF_CLASSES
#include "dynamatic/Dialect/Handshake/HandshakeTypes.h.inc"

Expand Down
32 changes: 31 additions & 1 deletion include/dynamatic/Dialect/Handshake/HandshakeTypes.td
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#define DYNAMATIC_DIALECT_HANDSHAKE_HANDSHAKE_TYPES_TD

include "mlir/IR/AttrTypeBase.td"
include "dynamatic/Dialect/Handshake/Handshake.td"

/// Base class for types in the Handshake dialect.
class Handshake_Type<string name, string typeMnemonic, list<Trait> traits = []>
Expand Down Expand Up @@ -48,7 +49,6 @@ def ExtraSignals : TypeParameter<
let cppStorageType = "::llvm::SmallVector<::dynamatic::handshake::ExtraSignal::Storage>";
let convertFromStorage = [{convertExtraSignalsFromStorage($_self)}];
let comparator = cppType # "($_lhs) == " # cppType # "($_rhs)";

let defaultValue = cppType # "()";
}

Expand Down Expand Up @@ -77,6 +77,36 @@ def ChannelType : Handshake_Type<"Channel", "channel"> {
let hasCustomAssemblyFormat = 1;
let genVerifyDecl = 1;

let extraClassDeclaration = [{
/// Returns the number of extra signals.
unsigned getNumExtraSignals() const {
return getExtraSignals().size();
}

/// Returns the number of downstream extra signals.
unsigned getNumDownstreamExtraSignals() const;

/// Returns the number of upstream extra signals.
unsigned getNumUpstreamExtraSignals() const {
return getNumExtraSignals() - getNumDownstreamExtraSignals();
}

/// Determines whether a type is supported as the data type or as the type
/// of an extra signal.
static bool isSupportedSignalType(::mlir::Type type) {
return ::mlir::isa<::mlir::IndexType, ::mlir::IntegerType, ::mlir::FloatType>(type);
}
}];
}

def SignalType : Type<
CPred<"::dynamatic::handshake::ChannelType::isSupportedSignalType($_self)">,
"must be an `IndexType`, `IntegerType` or `FloatType`"> {
}

def ChannelLikeType : Type<
CPred<"::mlir::isa<::dynamatic::handshake::ControlType, ::dynamatic::handshake::ChannelType>($_self)">,
"must be a `handshake::ControlType` or `handshake::ChannelType` type"> {
}

#endif // DYNAMATIC_DIALECT_HANDSHAKE_HANDSHAKE_TYPES_TD
Loading

0 comments on commit 9fef8a5

Please sign in to comment.