When scheduling a pipeline, Vitis HLS must also satisfy an additional set of constraints:
- Cyclic data dependence constraints, due to loop-carried dependences.
These dependences mean that the write of a scalar or array element at one iteration
affects a read of the same scalar or element N iterations of the pipeline later.
Dependences with N=0 (that is to say, within the same loop iteration) are called intradependences, while those with N>0 are called interdependences.
This N is called the “dependence distance” of a cyclic dependence. This code example contains a distance-1 loop-carried dependence, because the write of
aat iterationiaffects the read ofaat iterationi+1:int a = 1; for (int i=0; i<N; i++) { #pragma HLS pipeline a *= ...; }This code example contains a distance 2 loop-carried dependence, because the write of
A[i+2]at iterationiaffects the read ofA[i]at iterationi+2.int A[N]; for (int i=0; i<N-2; i++) { #pragma HLS pipeline A[i+2] = A[i] * ...; }The scheduler must schedule every operation involved in a dependence cycle – the read, multiply, and write operations in the preceding examples – within II cycles.
- Top argument synchronization constraints. Operations that read and
write any input or inout scalar or array argument of the enclosing design top
function must occur within the first II clock cycles. These constraints force all
operations that write an output top argument to be executed in the last II.
If the output operations for an inout top argument have a large delay before them, for example because they depend on operations with latency>1 to meet the required clock frequency, the II might increase.
Note: Sometimes inout top ports are created due to read, modify, write, and partial-write accesses to top scalar arguments, or to elements of a top array or structure argument.For example, assume that the chosen clock frequency implies the use of a floating point multiplier with a total latency of four clock cycles before the accesses to the input argument inout of the top function of this design. The dependence pragma is used to avoid II issues due to dependencies.
void top(int i, double in, double inout[8]) { #pragma HLS pipeline #pragma HLS dependence inter false variable=inout inout[i] *= in; }The II of the function must be at least 6 to enable the write of inout within the first II cycles (1 for the read of inout, 4 for the multiply, and 1 for the write of inout).
- Loop exit condition operation constraints. Operations that determine
the exit from a loop must be scheduled within the first II clock cycles. If they
depend on operations with latency>1, the II might increase.
This can happen frequently if the loop bound includes the multiplication of several variables (for example, when manually or automatically flattening loops). For example, in the following case, where the floating point multiplication and comparison take several clock cycles to compute, the II of the loop is increased to allow that computation in the first II cycles.
void top(hls::stream<double> &in, double &out) { double t; do { #pragma HLS pipeline t = in.read(); out = t; } while (t * 24. > -27.); }