This is an old revision of the document!
First-in first-out buffer in CHP
(It is a bit unfortunate that the computer science community uses the term buffer to refer to a fifo, while the circuits community uses the term buffer to refer to an electrical circuit that introduces gain to improve signal drive strength.)
A one-place buffer
ACT supports behavioral descriptions using the CHP language. The CHP language is a message-passing programming language. Its syntax is inspired by Dijkstra's guarded commands language and Hoare's CSP language.
A one-place buffer is a component that repeatedly does the following: it receives a new input; once the input is received, it sends the new value on its output. Suppose we want to describe a CHP component that has an input channel L
an output channel R
, and has an internal variable x
used to hold the received value. The statement
L?x
receives a value from channel L
and stores it into variable x
. This is a blocking receive: if there is a pending input, the communication action succeeds; otherwise the action waits until an input is available.
Once we have successfully received the input, we can follow this operation by sending the value x
on the output channel R
.
R!x
The send operation is also blocking; if no one is ready to receive the value, the send will block until a receiver arrives. Since these two actions occur in sequence, we write:
L?x; R!x
Finally, the one-place buffer corresponds to an infinite repetition of this sequence of operations. This is written as follows:
*[ L?x; R!x ]
To make this into a valid ACT program, we need to declare all the variables, and define the one-place buffer component.
defproc one_place_buffer (chan?(int) L; chan!(int) R) { int x; chp { *[ L?x; R!x ] } }
This defines a process called one_place_buffer
. The name of the process is followed by its port list. Only signals/variables in the port list are accessible from outside the process. We have specified two ports: L
, and R
. L
is an input port, and we must also specify the data type that is communicated on the port (int
in this example, corresponding to an unsigned integer). L
has type chan?(int)
: an input (the ?
) end of a channel (the chan
) that carries integer data (int
). Note that since the bit-width of the integer was not specified, it is implicitly 32 bits. R
has a similar declaration. Finally, the definition of the process includes the declaration of the variable x
, and the behavior of the process specified in the CHP sub-language that is surrounded by chp { … }
.
The same process that manipulates 8-bit integers would be written as follows:
defproc one_place_buffer (chan?(int<8>) L; chan!(int<8>) R) { int<8> x; chp { *[ L?x; R!x ] } }
Note the use of angle brackets to specify the bit-width. In ACT, angle brackets are used to specify parameters to types (similar to C++).
A ten-place buffer
A buffer that can hold ten values can be obtained by connecting ten one-place buffers to each other, where each buffer operates in parallel. When we defined a one_place_buffer
earlier, we actually defined a new ACT type. This ACT type is a process with the specified port list. We can now use this type to create multiple instances of the process, just like we created x
as an instance of an integer earlier.
one_place_buffer b0; // create buffer called b0 one_place_buffer b1; // create buffer called b1
ACT is a hardware description language; hence all processes operate in parallel. So far we have created two parallel one-place buffers. To convert this into a two-place buffer, we need to connect the output of b0
to the input of b1
.
ACT provides a flexible syntax for connecting components/ports/instances to each other. The basic connection syntax is a = b
, which connects a
and b
to each other. We can use this syntax to connect the output R
of b0
to the input L
of b1
as follows:
b0.R = b1.L;
We can repeat this process to create a ten-place buffer. Since linear arrays of processes are common in circuit design, ACT provides array syntax to simplify this process. We can create ten buffers as follows:
one_place_buffer b[10];
This creates ten concurrent buffers, named b[0]
, b[1]
, …, b[9]
. We can connect them to each other to create a ten-place buffer like this:
b[0].R = b[1].L; b[1].R = b[2].L; ... b[8].R = b[9].L;
Since typing all of these connections gets tedious, ACT provides syntax for repetitive constructions. The same sequence of connections can be written:
(i : 9 : b[i].R = b[i+1].L;)
Here, i
is a fresh variable that takes on values 0
, 1
, …, 8
(one less than 9 specified between the two colons).
The complete ten-place buffer with its primary input and output channels is given by:
defproc tenplace_buffer (chan?(int) L; chan!(int) R) { one_place_buffer b[10]; (i : 9 : b[i].R = b[i+1].L;) b[0].L = L; b[9].R = R; }
Note that we connected the b[0]
input to the primary input to the process, and the b[9]
output to the primary output R
.
An N-place buffer
Now that we have defined a ten-place buffer, how about a twenty-place one? Or some other capacity buffer? ACT provides parameters that can be used to guide circuit construction. An integer parameter in ACT has type pint
to distinguish it from a normal int
that is part of a physical circuit. Given a parameter N
, we can define a parameterized buffer below by generalizing the ten-place buffer example above:
one_place_buffer b[N]; (i : N-1 : b[i].R = b[i+1].L;) b[0].L = L; b[N-1].R = R;
Finally, we need to define a process that is parameterized by N
. The complete definition is:
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; }
If we want to create a ten-place buffer, we would instantiate it as follows:
buffer<10> b; // parameter is set to 10
Note once again the use of angle brackets to specify template parameters.