Differences
This shows you the differences between two versions of the page.
Both sides previous revisionPrevious revisionNext revision | Previous revision | ||
language:langs:dflow [2021/04/27 17:08] – [Deterministic and non-deterministic merge] rajit | language:langs:dflow [2024/07/22 17:54] (current) – [Function] rajit | ||
---|---|---|---|
Line 2: | Line 2: | ||
The dataflow sublanguage provides a convenient short-hand when designing asynchronous circuits using pipelined asynchronous circuits. The dataflow language operates exclusively on channels, and treats channels as variables to specify the dataflow computation. | The dataflow sublanguage provides a convenient short-hand when designing asynchronous circuits using pipelined asynchronous circuits. The dataflow language operates exclusively on channels, and treats channels as variables to specify the dataflow computation. | ||
- | < | + | < |
chan(int) a, b, c; | chan(int) a, b, c; | ||
Line 10: | Line 10: | ||
</ | </ | ||
This corresponds to an adder, with inputs on channels '' | This corresponds to an adder, with inputs on channels '' | ||
- | < | + | < |
*[ a? | *[ a? | ||
</ | </ | ||
Line 26: | Line 26: | ||
===== Function ===== | ===== Function ===== | ||
- | The syntax shown above corresponds to the // | + | The syntax shown in the first example |
===== Split ===== | ===== Split ===== | ||
The //split// dataflow element receives a control token and a data token, and uses the value of the control token to route the data token to one of the output channels. If the control token=0, the first channel is used; if it is 1, then the next channel is used; etc. The syntax for this is shown below: | The //split// dataflow element receives a control token and a data token, and uses the value of the control token to route the data token to one of the output channels. If the control token=0, the first channel is used; if it is 1, then the next channel is used; etc. The syntax for this is shown below: | ||
- | < | + | < |
dataflow { | dataflow { | ||
{c} I -> O0, O1 | {c} I -> O0, O1 | ||
Line 37: | Line 37: | ||
</ | </ | ||
The data input on channel '' | The data input on channel '' | ||
- | < | + | < |
dataflow { | dataflow { | ||
{c} I -> *, O | {c} I -> *, O | ||
Line 45: | Line 45: | ||
Splits with more than two outputs use the same syntax. The control token is assumed to take on an integer value specifying which output channel is used. | Splits with more than two outputs use the same syntax. The control token is assumed to take on an integer value specifying which output channel is used. | ||
- | < | + | < |
dataflow { | dataflow { | ||
{c} I -> O0, O1, O2, ..., On | {c} I -> O0, O1, O2, ..., On | ||
Line 51: | Line 51: | ||
</ | </ | ||
+ | The bit-width of channel '' | ||
+ | |||
+ | The control channel, input data, and output data can only be channels. If a control expression is needed, one must use a combination of a split as well as a function. For example, if a split takes an input from '' | ||
+ | |||
+ | <code act> | ||
+ | dataflow { | ||
+ | {c = 0 ? 0 : 1} A -> X, Y | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Instead, use the following: | ||
+ | < | ||
+ | dataflow { | ||
+ | c = 0 ? 0 : 1 -> ctrl; | ||
+ | {ctrl} A -> X, Y | ||
+ | } | ||
+ | </ | ||
===== Controlled merge ===== | ===== Controlled merge ===== | ||
The // | The // | ||
- | < | + | < |
dataflow { | dataflow { | ||
{c} I0, I1 -> O | {c} I0, I1 -> O | ||
Line 60: | Line 77: | ||
</ | </ | ||
In this example, if a 0 is received on '' | In this example, if a 0 is received on '' | ||
- | < | + | < |
dataflow { | dataflow { | ||
{c} I0, I1, ..., Ik -> O | {c} I0, I1, ..., Ik -> O | ||
} | } | ||
</ | </ | ||
+ | The bit-width of '' | ||
===== Implicit copy and explicit buffers ===== | ===== Implicit copy and explicit buffers ===== | ||
Copies are implicit, and are automatically introduced when the same channel name is used multiple times on the left hand side. In the example below, an input token is received on '' | Copies are implicit, and are automatically introduced when the same channel name is used multiple times on the left hand side. In the example below, an input token is received on '' | ||
- | < | + | < |
dataflow { | dataflow { | ||
a + b -> sum; | a + b -> sum; | ||
Line 75: | Line 92: | ||
} | } | ||
</ | </ | ||
- | The fact that '' | + | The fact that '' |
In pipelined circuits, it is important to be able to introduce slack to optimize performance. The syntax for this is the following: | In pipelined circuits, it is important to be able to introduce slack to optimize performance. The syntax for this is the following: | ||
- | < | + | < |
dataflow { | dataflow { | ||
a + b -> [4] sum | a + b -> [4] sum | ||
Line 88: | Line 105: | ||
Finally, we need to be able to introduce initial tokens with pre-specified initial values. The bracket notation is overloaded for this purpose. | Finally, we need to be able to introduce initial tokens with pre-specified initial values. The bracket notation is overloaded for this purpose. | ||
- | < | + | < |
dataflow { | dataflow { | ||
a + b -> [4,2] sum | a + b -> [4,2] sum | ||
Line 100: | Line 117: | ||
There are two other primitives that are also supported, because they can be useful in certain circumstances. They are both variations of the controlled merge. The first is the // | There are two other primitives that are also supported, because they can be useful in certain circumstances. They are both variations of the controlled merge. The first is the // | ||
- | < | + | < |
dataflow { | dataflow { | ||
{*} I0, I1 -> O | {*} I0, I1 -> O | ||
Line 106: | Line 123: | ||
</ | </ | ||
The '' | The '' | ||
- | < | + | < |
dataflow { | dataflow { | ||
{|} I0, I1 -> O | {|} I0, I1 -> O | ||
Line 114: | Line 131: | ||
Often it is helpful to know what decision was made by the arbiter. To support this, we permit an optional second channel on the right hand side of the dataflow expression as follows: | Often it is helpful to know what decision was made by the arbiter. To support this, we permit an optional second channel on the right hand side of the dataflow expression as follows: | ||
- | < | + | < |
dataflow { | dataflow { | ||
{|} I0, I1 -> O, c | {|} I0, I1 -> O, c | ||
Line 120: | Line 137: | ||
</ | </ | ||
For each output generated, the control channel '' | For each output generated, the control channel '' | ||
+ | This syntax is also supported for the deterministic merge. | ||
+ | <code act> | ||
+ | dataflow { | ||
+ | {*} I0, I1 -> O, c | ||
+ | } | ||
+ | </ | ||
+ | In both these cases, the control output channel must have the exact bitwidth needed to specify which input token was routed to the output. | ||
===== Sink ===== | ===== Sink ===== | ||
A dataflow sink simply receives and discards a token from a channel. Sinks are not needed in general, since the channel that corresponds to the sink can be optimized away by an implementation. However, sinks can be useful when a particular process is re-used in a context when one of its outputs is not used. The syntax is the following: | A dataflow sink simply receives and discards a token from a channel. Sinks are not needed in general, since the channel that corresponds to the sink can be optimized away by an implementation. However, sinks can be useful when a particular process is re-used in a context when one of its outputs is not used. The syntax is the following: | ||
- | < | + | < |
dataflow { | dataflow { | ||
c -> * | c -> * | ||
Line 136: | Line 159: | ||
As a simple example, consider a multiply-accumulate block. The block can be specified as follows: | As a simple example, consider a multiply-accumulate block. The block can be specified as follows: | ||
- | < | + | < |
dataflow { | dataflow { | ||
a * b -> mul; | a * b -> mul; | ||
Line 147: | Line 170: | ||
Suppose we augment this with an external control token on '' | Suppose we augment this with an external control token on '' | ||
- | < | + | < |
dataflow { | dataflow { | ||
a * b -> mul; | a * b -> mul; | ||
Line 158: | Line 181: | ||
</ | </ | ||
+ | ====== Clusters and Ordering ====== | ||
+ | |||
+ | It can be convenient to group dataflow elements into clusters. The syntax for grouping dataflow elements is: | ||
+ | |||
+ | <code act> | ||
+ | dataflow { | ||
+ | ... | ||
+ | | ||
+ | a + b -> c; | ||
+ | a - b -> d | ||
+ | } | ||
+ | ... | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Dataflow clusters are hints to the implementation that these dataflow elements should be grouped together---for example, by having a single control that is shared by all the elements of the cluster. | ||
+ | |||
+ | Finally, consider the following dataflow example: | ||
+ | <code act> | ||
+ | dataflow { | ||
+ | a + b -> c; // produce an output on channel c | ||
+ | d + e -> out // sum d and e and produce the output on out | ||
+ | } | ||
+ | </ | ||
+ | Furthermore, | ||
+ | |||
+ | When optimizing the dataflow block, one may decide to group the control for the two dataflow elements. However, doing so would result in deadlock, because the combined dataflow block would wait for inputs to arrive on '' | ||
+ | |||
+ | To simplify optimizations, | ||
+ | <code act> | ||
+ | dataflow { | ||
+ | order { | ||
+ | c < e // c must be produced before e is available | ||
+ | } | ||
+ | a + b -> c; | ||
+ | d + e -> out | ||
+ | } | ||
+ | </ | ||
+ | In general, the order block contains a semi-colon separated list of directives. Each directive is a list of comma-separated channels followed by ''<'' | ||
+ | |||
+ | ====== Syntactic replication ====== | ||
+ | |||
+ | The dataflow sub-language has support for syntactic replication for splits, merges, mixers, and arbiters. For a split, the output side can use syntactic replication; | ||
+ | For example, the following syntax is legal (assuming everything is of the right type): | ||
+ | <code act> | ||
+ | dataflow { | ||
+ | {ctrl} l -> (, i : 8 : out[i]) | ||
+ | } | ||
+ | </ |