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.