Control flow
Complex datapath elements are usually comprised of arrayed versions of simpler cells. Although arrays can be created directly using arrayed instantiations, ACT supports looping constructs which can be a convenient way to create arrayed structures. More complex structures such as trees can be easily created using recursive instantiations.
Loops
An example of the loop construct in ACT is shown below:
( i : 10 : bool x[i..i]; )
The variable i
is the loop index and its scope is
delimited by the angle brackets. The colons separate the different parts
of the loop construct. The number 10
is an abbreviation for the
range 0..9
. The body of the loop is the statement bool x[i..i];
. The effect of this statement is the same as
bool x[0..0]; bool x[1..1]; ... bool x[9..9];
The body of the loop can contain any ACT body, and therefore can have multiple statements in it. A more common use of the loop statement is shown below:
register r[1..8]; (i : 1..8 : r[i](in[i],out[i],control); )
In the example above, registers numbered 1
through 8
are
created. Their first two parameters are connected to the corresponding
elements of arrays in
and out
. However, they have a shared
control that is passed in as the third parameter.
Since loops are part of ACT bodies, they can occur in the body of another loop. Thus, nested loops are also supported. However, types cannot be defined in the body of a loop.
A second more general looping construct is borrowed from the guarded command language.
pint i; i=0; *[ i < 10 -> bool x[i..i]; i = i + 1; ]
This builds an array element by element, using the guarded command
syntax for a general while loop. Note that in this case we are
modifying a pint
variable i
. This sort of construct can
only be used in the body of a type definition, since pint
types
are immutable in global scopes.
The first form of a loop is a special case of syntactic replication. The general loop syntax can include a separator, which is used in other contexts.
In production rule bodies, the loop
(&i:3: x[i])
expands to
x[0] & x[1] & x[2]
(notice the absence of a trailing &
is critical for the correct
syntax). The &
symbol separates the body of the loop that is
instantiated for different values of i
.
The syntactic replication construct is written as follows:
(sym id : range : body )
The sym
(symbol) might be empty. id
is a variable that can be used in body
, and takes the range specified by range
. range
can be either an integer-valued expression or start .. end
to indicate a start and end index. The result of the replication is
body(id set to lo) sym body(id set to lo+1) sym ... sym body(id set to hi)
where lo
is the starting index of the range, and hi
is the ending index, and body(id set to x)
means body
with the value of x
substituted by the constant id
.
Selections
Conditional execution is supported by the selection statement. The syntax of a selection statement is:
[ boolean_expression -> body [] boolean_expression -> body .. ]
The last Boolean expression in the conditional can be the keyword
else
, which is short-hand for 'all other guards are false.'
Any one body whose corresponding Boolean expression is true is executed. For instance, we can create 32 registers with something special for register 0 as follows:
(i : 32 : [ i = 0 -> r0(in[i],out[i],control); [] else -> r[i](in[i],out[i],control); ] )
Boolean expressions can be constructed from Boolean variables, the
constants true
and false
, and the Boolean operators
&
, |
, and ~
denoting the and, or, and negation
operations respectively. Numeric expressions can be compared using
<
, < =
, >
, >=
, =
, and !=
for
the operators less than, less than or equal to, greater than, greater
than or equal to, equal to, and not equal to respectively.
Recursion
Type definitions can be recursive. For instance, the following definition can be used to create a tree structure.
template<pint N> defproc tree (bool a[N]) { [ N = 1 -> leaf l(a[0]); [] N > 1 -> tree<N/2> t0(a[0..N/2-1]); tree<N-N/2> t1(a[N/2..N-1]); ] }