There are a small number of tasks proposed in order to provide dataflow graph semantics not possible with normal tasks. These primitive tasks provide conditional evaluation, and stream subset specification, as well as regular linear remappings along a dimension.
The primary justification for identifying these as tasks, when they might be better classified as functions, is that in most cases they do not actually result in any computation being done on data machines. They are instead applied by the control machine as filters to demands for data during the evaluation of a dataflow graph.
The Multiple Conditional, or mcond, task provides a mechanism for conditional evaluation of input data. In a normal task, all the input parameters must be present in memory readily accessible by a data machine before the task will be allowed to execute. An mcond task requires that all the parameters used to evaluate the conditional expressions be defined (present in memory) before determining the winning condition and demanding the required input stream. Input streams not required for the winning condition are never demanded6. It is equivalent to the following expression:
mcond = if( cond1 ) then stream1; else if( cond2 ) then stream2; else if ... fi;
The conditions are currently limited to the following form: param1 <op> param2 where the parameters may be either constants or streams. The monadic context query constant (#) may be one of the parameters. The operation must be an arithmetic comparison operation -- equal, not-equal, less-than, less-than-or-equal, greater-than, or greater-than-or-equal. The parameters being compared must be of equivalent type, there is no automatic type casting. There is no explicit default assertion. The programmer can include one as the last conditional pair, with a condition of true (1 eq 1).
The mcond task provides a means of combining multiple streams into a single one. If the conditions all have the form #.d <op> constant, the output is predefined and the mcond operation has little impact on performance.
The select task provides a dual to the mcond task. It is used to select a subset of a stream along a single dimension, and also, through the use of special values for offset, to synchronize a stream along two dimensions.
A select task specifies a multidimensional access pattern to be applied to a single stream, producing a single stream. In a common form, select is used to choose a single location along a dimension (direct addressing), eliminating the dimension from further streams and processing.
The range is either the following parameters passed as constants, or a stream with the following information provided as separate integers along a specified dimension :
A range is simply another multidimensional access pattern descriptor. Several ``special'' values are available for use when specifying a range that aren't typically used in normal function access patterns. They are:
These special values are generally used for one of two purposes. The first is referencing the limits of a data stream along a dimension for which the limits are relatively static yet unknown at compile-time. Once the amount of data along the dimension is specified, it doesn't change. The second use is referencing the limits of a data stream which is growing over time. In this case, the select task synchronizes one dimension with another. Further discussion of these infinite values along with examples is provided in a separate article7.
The Q resample task provides a limited form of the generic stream indexing operator (@). It is useful for either shifting or decimating a stream along a particular dimension. The output stream consists of one out of every scale elements in the input stream. The offset and scale parameters must be integers. More formally:
resample.d( x, scale, offset ) = x @.d ((#.d * scale) + offset);
An example of the resample function would be:
pipe1: orig_stm = src( prev_stm ); new_stm = resample.x( orig_stm, 2, 0 ); ... = dst1( orig_stm ); ... = dst2( new_stm ); pipe2: orig_stm = src( prev_stm ); new_stm = resample.x( orig_stm, 2, 0 ); ... = dst1( new_stm ); ... = dst2( new_stm );
In pipe1, the resample primitive (in this case simply taking every even sample along dimension x) will be incorporated into the input of the dst2 task. In pipe2, the filter will be incorporated into the output of src, in which case it might affect the demands made on prev_stm.
Several common intensional operators are implemented at the DFG level using the resample task:
first.d x = resample.d( x, 0, 0 ); next.d x = resample.d( x, 1, 1 ); prev.d x = resample.d( x, 1, -1 ); rel.d( x, n ) = resample.d( x, 1, n );
The resample and upsample tasks have stream access patterns that vary depending on other parameters. For simplicity this capability is currently limited to these primitives, although it could be utilized by user-defined functions. The output stream access pattern is scalar, as are the scale and offset parameter accesses. The data input parameter stream access pattern for resample has an offset equal to the demanded output location along the specified dimension times the function's scale parameter plus the offset parameter ((#.d * scale) + offset), an extent of one, and step equal to the scale parameter.
The Q upsample task provides another limited form of the generic stream indexing operator (@) operator. It is useful for interpolating a stream along a particular dimension. It may be viewed as performing the same basic operation as resample with a scale which is the reciprocal of the integer provided. This task produces an upsampled stream with either the samples replicated, or with ``new'' samples zeroed (zero-padded).
A more exact definition follows. The divides are integer (as is all the math).
upsample.d( x, scale, offset, ctl ) = if( scale == 0 ) then x @.d offset; else if( ctl == REPLICATE ) then x @.d ((#.d / scale) + offset); else if(((#.d - 1) / scale) == (#.d / scale)) then 0; // ZERO-PAD else x @.d ((#.d / scale) + offset); fi;
The upsample primitive has a stream access pattern that varies depending on other input parameters. The x input parameter stream access pattern for upsample has an offset equal to the demanded output location along the specified dimension divided by the function's scale parameter plus the offset parameter ((#.d / scale) + offset), an extent of one, and step equal to the scale parameter. The output stream access pattern is scalar, as are the scale and offset parameter accesses.