Following up from [Handshake] Deprecating the FIRRTL lowering · Issue #5102 · llvm/circt · GitHub -
To me, the main issue with the Handshake dialect, as of this moment, is composability. Currently, the only way to compose the handshake dialect is to have handshake module instantiations + external handshake functions that eventually lower to a hw.module
with a handshake-compatible interface. What if i want to have some parts of a module interface with a handshaking signal interface, and others without? or what if I want to embed an FSM inside a module that uses handshake operators, but I don’t want that FSM to be a second-class citizen wrt. control within the module (i.e., the FSM is forced to use handshake to communicate with other things).
Here’s two directions which the dialect could go in:
Make SSA values with Handshake semantics implicit through typing
That is, introduce an e.g. !handshake<T>
type which would be required for all handshake operations. By introducing this, we will thus remove the notion that all SSA values have handshake semantics and thus allow for the embedding of other operations alongside handshake ops. To interact with non-handshake operations, we could introduce a handshake.unit
operation, which wraps I/O signals with unit-rate actor semantics, e.g.:
handshake.func @foo(...) {
...
%0 = arith.add %1, %2 : i32
}
becomes
handshake.func @foo(...) {
...
%out = handshake.unit (%0, %1) : (!handshake<i32>, !handshake<i32>) -> (!handshake<i32>) {
^bb0(%in0 : i32, in1 : i32):
%res = arith.addi %in0, %in1 : i32
return %res : i32
}
}
One counterpoint to this may be that in case you previously had a chain of non-handshake operations, those would now have to be represented as a chain of handshake.unit
operations, which clearly would be harder to analyze and reason about.
As @stephenneuendorffer points out in the issue linked at the top of this post, there seems to be two competing uses/wants from the Handshake dialect:
- Modeling designs with KPN-type semantics.
- Modeling RTL designs with handshake signals as a shorthand to avoid explicitly writing valid/ready signal everywhere.
This proposed change would apply to the 2nd use, easing the use in which handshake can be composed with other designs. However, it makes modelling KPN designs more difficult, since this (arguably) the whole point of having implicit handshake semantics on all SSA values.
Which leads to a second, hybrid proposal
SSA semantics based on parent container
A second design point would be to allow handshake operations to exist outside of a handshake.func
operation. We could thus say (i.e. verify) that
- whenever handshake operations live within a
handshake.func
s, SSA value semantics are of implicit handshake signals, and handshake operations may take any type. “implicit handshake semantics” thus becomes a property of thehandshake.func
container. - Whenever handshake operations live outside of a
handshake.func
(e.g. inside ahw.module
or some other container), SSA value semantics are of explicit handshake signals (since we want to allow other signalling standards/semantics to live side by side with the ops). Handshake operations would therefore only accept!handshake<T>
-typed values.
e.g. the following would be legal:
handshake.func @x2(%0 : i32) -> (i32) {
%1:2 = fork [2] %0 : i32
%2 = arith.addi %1#0 ,%1#1 : i32
return %2 : i32
}
hw.module @x4(%0 : !handshake<i32>, %1 : !handshake<i32>) -> (!handshake<i32>) {
%0 = handshake.instance @x2(%0) : (!handshake<i32>) -> (!handshake<i32>)
%1 = handshake.instance @x2(%1) : (!handshake<i32>) -> (!handshake<i32>)
%2 = handshake.unit (%0, %1) : (!handshake<i32>, !handshake<i32>) -> (!handshake<i32>) {
^bb0(%in0 : i32, in1 : i32):
%res = arith.addi %in0, %in1 : i32
return %res : i32
}
return %2 : !handshake<i32>
}
At first glance, this seems like the best of both worlds. If people want the KPN semantics, they can create their designs (with ease) inside a handshake.func
. If instead it makes more sense to compose handshake operations alongside other kinds of operations, the second approach can be employed.
The semantics of the handshake
operations remain the same, and I’d expect lowering paths to be shared close to completely.
CC people who may be interested in this:
@Dinistro @stephenneuendorffer @Lucas @mikeurbach @jdd