This is an old revision of the document!
ACT Introduction
The Asynchronous Circuit Toolkit (ACT) is a suite of programs that can be used to design, verify, and test asynchronous circuits. The tools share a common input language (specified in a text .act
file) which can be used to describe circuits at different levels of detail. In what follows, we assume that the reader is familiar with asynchronous design methodologies and terminology.
ACT is a hierarchical, lexically scoped circuit description language. A single ACT file can be used to describe the transistor implementation of a circuit as well as a high-level functional description of the same circuit.
There are three steps used to process an ACT file:
- reading in an ACT file;
- an expansion/elaboration phase where parameters are expanded; and
- a circuit construction and instantiation phase.
In the parsing phase, the input is converted into internal data structures. In the expansion phase, parameters are substituted, creating a concrete hierarchical netlist. In the instantiation phase, data structures are created for every circuit element specified in the ACT file. Errors may be reported in any of these stages. As a rule of thumb, errors are reported as soon as there is sufficient information to determine the error.
A simple example
To get a feel for how a circuit is described in ACT, we begin with a simple example circuit. The purpose of this circuit is to create a dual rail channel (called a1of2
for a one-of-two encoded data channel and an acknowledge) and attach a bit-bucket to it.
/* my first act program */ defchan a1of2 <: chan(bool) (bool d0,d1,a) { spec { exclhi(d0,d1) // exclusive high directive } // channel description omitted } defproc bitbucket (a1of2 d) { prs { d.d0 | d.d1 -> d.a+ ~d.d0 & ~d.d1 -> d.a- } } bitbucket b; a1of2 c; b.d = c;
defchan a1of2
is used to create a new channel type named a1of2
with a port list that consists of three Boolean values named d0
, d1
, and a
. A bool
is a Boolean-valued electrical node in the circuit, and is a built-in type. Identifier names such as d0
and a1of2
conform to the C naming convention. The body of the type a1of2
is enclosed in braces.
The defchan
construct specifies that a1of2
is a channel type, and the signature <: chan(bool)
is used to specify that a1of2
is an implementation of a channel of type chan(bool)
. Boolean-valued variables can be sent or received on this channel since the type of the value communicated on the chan
that it implements is bool
. chan
is also a built-in type that is used for communication channels.
The definition of type a1of2
consists of a spec
body. The construct name { … }
is used to specify a language-specific body within a type definition. A type can have any number of these language-specific bodies. A program using the ACT library typically will examine a subset of all possible language bodies. In our example, the word spec
is recognized to be a specification body.
The specification body contains an assertion indicating that the nodes d0
and d1
are exclusive high, meaning that at most one of them can be true at any time (a form of an invariant). Whenever a circuit element of type a1of2
is created, this specification will be automatically attached to it. Tools that read ACT files can use this information for a variety of purposes, and can also check if this invariant is violated in case of an error in the design of a circuit that uses this channel.
The process bitbucket
is defined to take a variable d
of type a1of2
as its argument. bitbucket
is a process rather than a channel because it was created using defproc
instead of defchan
. The body of bitbucket
contains a different language-specific body, namely a prs
body. prs
bodies correspond to production rule set descriptions of the process. In the example, the production rules for bitbucket
corresponds to what is commonly referred to as a ``bit-bucket or a ``token sink
for a four phase dual rail channel.
The statement bitbucket b
creates an instance of type bitbucket
named b
. The statement is said to be an
instantiation. Execution of this statement creates variable b
of type bitbucket
, production rules corresponding to the body of a bitbucket
, and the specification body for the a1of2
variable b.d
. Similarly, the statement a1of2 d
creates an instance of type a1of2
. ACT uses the standard dot-notation to access the names that are in port lists, since they are analogous to the fields of types/structures in conventional programming languages.
The final statement b.d=c
connects the two a1of2
types b.d
and c
. The effect of connecting two types is to make the two instances aliases for each other. Therefore, the connect statement also specifies that the Booleans b.d.d0
, b.d.d1
, b.d.a
are the same as the Booleans d.d0
, d.d1
, d.a
respectively. Electrically, those Booleans correspond to the same circuit node.
ACT recognizes both C and C++ style comments, and they are treated as white space along with spaces, tabs, and new lines. A C-style comment begins with the characters /*
and ends with */
. Everything between the beginning and end of the comment is treated as whitespace. A C++ style comment begins with two forward slashes, and continues till the end of line.
Variables and expressions
Variables in ACT fall into two basic categories: parameters
and circuit elements. A parameter is a variable that is used to
parameterize a circuit element in some way and must be of type integer
(pint
for unsigned integer or pints
for signed integer),
real (preal
), or Boolean (pbool
). Circuit elements
consist of Booleans (bool
), integers (int
), or channels
(chan
).
A variable identifier can be a sequence of digits, letters, and underscores. The following declarations are legal:
bitbucket b; a1of2 x1; a1of2 _2; bool x5;
On the other hand, the following declaration is incorrect.
pbool 5; -[ERROR]-> Expecting bnf-item `instance_id', got `5'
Errors use names from the ACT BNF to describe the item that the parser was expecting. Error messages are accompanied by the file name, line number, and column number of the item that resulted in the error.
The names in the port list of a user-defined type are the only parts of
the type that are visible externally. Other parts of the defined type
cannot be accessed outside the body of the type itself. For example,
consider the following definition of bitbucket
.
defproc bitbucket(a1of2 d) { bool p; prs { d.d0 | d.d1 -> d.a+ ~d.d0 & ~d.d1 -> d.a- } }
If we had used this definition, then although b.p
is a bool
within the bit-bucket b
, we cannot access it by statements
outside the body. Therefore, a statement such as b.p=c.d0
would
result in the following message:
bitbucket b; a1of2 c; b.p = c.d0; -[ERROR]-> `p' is not a port for `bitbucket'
Expressions look very much like C expressions. Expressions can be of two
types: numeric or logical. Numeric expressions can be constructed from
identifiers, numeric constants, parentheses for grouping, and the
arithmetic operators +
, -
, *
, and /
for
addition, subtraction, multiplication, and division respectively. The
unary minus operator is also supported. The operator %
is used
for computing the remainder. Logical expressions can be constructed from
logical variables, the logical constants true
and false
,
and the logical 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.
Arrays
Most circuits contain a set of components that are replicated a number of times. This is especially common in datapath circuits. @sc{act} has a very flexible array mechanism that can be used to construct complex circuits. The simplest way to create an array is shown below.
bool x[10]; a1of2 y[5][3];
The first statement creates an array of ten bool
s named x
whose
elements are x[0]
, x[1]
, …, x[9]
. The
second statement creates a two-dimensional array of a1of2
variables named y
whose elements are y[0][0]
,
y[0][1]
, …, y[4][2]
. The entire array range can also
be specified as shown below
bool w[4..7]; // create Booleans w[4], ..., w[7]
ACT also contains a mechanism for constructing sparse arrays. A sparse array is one that has 'holes' in it; in other words, valid indices of the array do not form a contiguous, rectangular block. Consider the following instantiation:
bool x[10]; bool x[12..14];
The first statement creates x[0]
, …, x[9]
; the
second creates x[12]
, …, x[14]
. This is a valid
sequence of statements, and it makes x
a sparse array. The
following, on the other hand, is not valid.
bool x[10]; bool x[9..14]; -[ERROR]-> Sparse array: overlap in range in instantiation Oiginal: [10]; adding: [9..14]
Each index of an array can only be created once.
Arrays can be connected to others using the =
operation. If two
arrays have the same size, the same type, and the same number of
dimensions, the connection is valid. Conceptually, connections are
performed by converting each array into an ordered list of individual
elements, where the order is specified by the lexicographic ordering
on their indices (the leftmost index has precedence). Finally, an
element-by-element connection is performed. This is illustrated
below.
bool x[10]; bool x[12..14]; bool y[2]; x=y; -[ERROR]-> Types `bool[ [10]+[12..14] ]' and `bool[2]' are not compatible
Note the syntax used to report the sparse array type as a combination of two sub-arrays.
The dimensionality of the two arrays must match for a connection to succeed, and their shapes also have to be compatible.
bool x[12]; bool w[4][3]; x=w; -[ERROR]-> Type-checking failed in connection Types `bool[12]' and `bool[4][3]' are not compatible
The following are examples of valid connections:
bool x[10]; bool x[10..12]; bool y[13]; x=y; // success! bool u[4][3]; bool v[4][3]; u=v; // success!