[Handshake] Using `LazyFork` can produce combinatorial cycles.
It seems that handshake's LazyFork violates a required property for composable dataflow circuits:
Consider the following example:
handshake.func @test(%arg0: none) -> (none) {
%0:2 = lazy_fork [2] %arg0 : none
%outCtrl = join %0#0, %0#1 : none, none
return %outCtrl: none
}
when lowering this to FIRRTL and feeding it into firtool it will complain that there is a cycle involving the valid and ready signals of %0. LazyFork will only set the output's valid signal once all of them are ready to receive something. Join on the other hand, waits until all inputs are valid, before it sets their ready signals.
The paper which introduces compositional dataflow circuits states the following:
We avoid combinational cycles by insisting each cycle in the dataflow network have at least one data and one control buffer (see Section 4) and by insisting no block has a combinational path from a ready to a valid signal.
While we ensure the first property with buffer insertions, the latter is violated by LazyFork.
It seems that a correct LazyFork implementation requires changes to ensure it can be used correctly. Not sure if it will then still be "cheaper" than a normal/eager fork, though.