Alternate Buffer

We start with the previous definition of a templated buffer:

template<pint N>
defproc buffer (chan?(int) L; chan!(int) R)
{
   one_place_buffer b[N];
   (i : N-1 : b[i].R = b[i+1].L;)
   b[0].L = L;
   b[N-1].R = R;
}

In this version, we used the = operation to connect channels to each other. There are other ways this can be done, and here we describe the syntax for a number of different options that achieve the same result in terms of creating a FIFO.

Using auxillary variables

We can create an array of size N-1 to correspond to the internal channels between the N buffers.

template<pint N>
defproc buffer (chan?(int) L; chan!(int) R)
{
   one_place_buffer b[N];
   chan(int) ch[N-1];
   (i : N-1 : b[i].R = ch[i];)
   (i : N-1 : b[i+1].L = ch[i];)
   b[0].L = L;
   b[N-1].R = R;
}

This can be re-written as follows:

template<pint N>
defproc buffer (chan?(int) L; chan!(int) R)
{
   one_place_buffer b[N];
   chan(int) ch[N-1];
   (i : 1 .. N-1 : b[i].L = ch[i-1]; b[i].R = ch[i]; )
   b[0].L = L; b[0].R = ch[0];
   b[N-1].L = ch[N-2]; b[N-1].R = R;
}

Another version of this is the following:

template<pint N>
defproc buffer (chan?(int) L; chan!(int) R)
{
   one_place_buffer b[N];
   chan(int) ch[N-1];
   (i : N :  
      [ i = 0 ->    b[i].L = L; b[i].R = ch[i];
     [] i = N-1 ->  b[i].L = ch[i-1]; b[i].R = R;
     [] else -> b[i].L = ch[i-1]; b[i].R = ch[i];
      ]
   )
}

Using port connections directly

Instead of using the dot notation to explicitly connect ports, we observe that each set of connections operates on the same instance b[i]. This can be written as follows:

template<pint N>
defproc buffer (chan?(int) L; chan!(int) R)
{
   one_place_buffer b[N];
   chan(int) ch[N-1];
   (i : N :  
      [ i = 0 ->    b[i](L,ch[i]);
     [] i = N-1 ->  b[i](ch[i-1],R);
     [] else -> b[i](ch[i-1], ch[i]);
      ]
   )
}

This approach relies on the order of ports being known; if instead it is preferable to connect ports by name, the following syntax is also valid.

template<pint N>
defproc buffer (chan?(int) L; chan!(int) R)
{
   one_place_buffer b[N];
   chan(int) ch[N-1];
   (i : N :  
      [ i = 0 ->    b[i](.L=L, .R=ch[i]);
     [] i = N-1 ->  b[i](.L=ch[i-1], .R=R);
     [] else -> b[i](.L=ch[i-1], .R=ch[i]);
      ]
   )
}

Dynamic creation of buffer instances

ACT also permits instances to be created one at a time. So rather than declaring a single buffer array of size N, we can create it one segment of the array at a time.

template<pint N>
defproc buffer (chan?(int) L; chan!(int) R)
{
   chan(int) ch[N-1];
   (i : N :  
       one_place_buffer b[i..i];
      [ i = 0 ->    b[i](.L=L, .R=ch[i]);
     [] i = N-1 ->  b[i](.L=ch[i-1], .R=R);
     [] else -> b[i](.L=ch[i-1], .R=ch[i]);
      ]
   )
}

Each b[i..i] slice augments the array b.

Note that when connecting to ports, you can leave connections dangling and then refer to them later. For example, the following avoids using the extra channel array.

template<pint N>
defproc buffer (chan?(int) L; chan!(int) R)
{
   chan(int) ch[N-1];
   (i : N :  
       one_place_buffer b[i..i];
      [ i = 0 ->    b[i](.L=L);
     [] i = N-1 ->  b[i](.L=b[i-1].R, .R=R);
     [] else -> b[i](.L=b[i-1].R);
      ]
   )
}

… which may be easier to read as follows:

template<pint N>
defproc buffer (chan?(int) L; chan!(int) R)
{
   chan(int) ch[N-1];
   (i : N :  
       one_place_buffer b[i..i];
      [ i = 0 ->    b[i].L=L;
     [] i = N-1 ->  b[i](.L=b[i-1].R, .R=R);
     [] else -> b[i].L=b[i-1].R;
      ]
   )
}

This version is similar to the original version we started with.