====== User-defined data types ======
A user-defined data type is defined using ''deftype''. These are classified into three categories:
* //data types//, which correspond to representations of the built-in ''int'' and ''bool'' types.
* //structures//, which are a collection of data types.
* //parameter structures//, which are a collection of parameter types.
In addition, data types can also be //enumerations//, which are data types with symbolic values.
===== Data types =====
A data type corresponds to an integer or Boolean value, even though it
could also be a composite construct like a record or structure (from software
programming languages). The syntax is similar to a process, and the constraints
about declarations/etc. apply here as well. These data types are handled as
the underlying built-in ''int'' or ''bool'' that they correspond to at the CHP
level of abstraction.
Often data types have some additional structure that is not required for a
process. In particular, the body of the data type and its type
signature provide information that relates a user-defined data type to
a previously defined or built-in data type. When a user-defined data
type is specified, a method for setting the value of the data type and
reading its value must also be specified. If omitted, certain features
of data types will not be enabled for the defined type.
The following is a simple example of a datatype that creates a dual-rail
representation for a Boolean variable. The first line specifies that
''d1of2'' is a new data type, and it implements the built-in type
''int<1>''---a one-bit integer.
deftype d1of2 <: int<1> (bool d0,d1)
{
spec {
exclhi(d0,d1)
}
}
The body of the type is similar to a process, except it can only contain
connections, ''spec'' bodies, and special //methods//. The
following would result in an error:
deftype d1of2 <: int<1> (bool d0,d1)
{
bool p;
spec {
exclhi(d0,d1)
}
}
-[ERROR]-> Expecting bnf-item `methods_body', got `bool'
Port lists for data types can be either built-in data types or
user-defined data types. Channels (built-in or user-defined) and
processes are not valid types for ports of a data type, since a data
type is supposed to represent a circuit structure that is used to
represent a data value.
==== Special data methods ====
There are two special methods that can be specified for a data type:
- a //set method//, used to write a value to the type;
- a //get method//, used to read the value of the type.
One can think of these as type conversion methods invoked
automatically to read or write the data type. When a normal
data type is used, the special variable ''self'' is implicitly
defined to be the built-in type that is implemented by the
user-defined data type.
deftype d1of2 <: int<1> (bool d0,d1)
{
spec {
exclhi(d0,d1)
}
methods {
set {
[self=1->d1-;d0+ [] self=0->d0-;d1+]
}
get {
[d0->self:=1 [] d1->self:=0]
}
}
}
In the example above, the ''set'' method says that the way to set a
''d1of2'' data type to the value ''0'' is to set ''d0'' to
''false'' and ''d1'' to ''true''. The special variable
''self'' is used to specify the ''int<1>'' value of the type, and
the methods specify conversion operations.
The selection statement in the ''get'' method uses the deterministic
selection operator ''[]'' (see [[language:langs:hse|the hse sublanguage]]). This is an implicit check
that when the ''get'' method is invoked, signals ''d0'' and ''d1''
cannot both be ''true''. We have also made this explicit in the
specification body. Also, if both ''d0'' and ''d1'' are false
(i.e. an illegal state in which to execute a get operation), the
variable ''self'' is not assigned; the operation waits for at least
one of ''d0'' or ''d1'' to be true. This is viewed as an error for a
data type. (This is different in the case of a channel, where the
semantics of the channel permit waiting.)
===== Structures =====
The ''deftype'' syntax can also be used to define structures/record types.
These types can be used to group data fields or channel fields together.
A structure is defined using ''deftype'', except it is not related to
a built-in type as in the examples above. So, for instance:
deftype mystruct (int<4> a; int<5> b) { }
would declare a structure with two fields: ''a'' and ''b'' of the specified type.
If all the fields within a structure are data values, this is a special
case of a structure consisting purely of data.
The distinction between a structure and another data type is that other data types are
implementations of one of the built-in types like ''int'' or ''bool.''
==== Pure structures ====
A pure structure is one that only contains other pure structures or ''int'' and ''bool'' components.
In other words, they are structures that don't include channels. Structures like this correspond
to what is normally viewed as a record/struct in a normal software programming language.
Pure structures can be used with special function methods, providing a degree of object-oriented
programming capability within ACT.
The following is an example of a pure structure:
deftype purestruct (int<4> a; int<8> b; bool q) { }
Pure structures can be converted to and from an ''int'' type of the appropriate bit-width.
purestruct p;
int<13> x;
...
chp {
...
x := int(p);
...
p := purestruct(x);
...
}
In the example above, the 'int()' operator can be used to convert the pure structure into a single integer with a bit-width that is large enough to hold all the elements of the structure. In this example, we require 13 bits to hold the 'int<4>', 'int<8>', and 'bool' member of the structure. The order of packing is left to right, so the assignment is the same as:
...
x := {p.a, p.b, p.q};
...
Similarly, the ''purestruct()'' constructor can be used to unpack an integer of the appropriate bit-width assuming that the integer is of the format generated by ''int()''.
===== Parameter structures =====
It can be convenient to group a collection of parameters for readability/organization. To support this, ACT includes parameter structures. The following is an example of a parameter structure:
defptype myps (pint a, b; pbool c) { }
This defines ''myps'' as a parameter type, which means it can be used as a template parameter in process definitions. For example, the following would be valid:
template defproc myprocess (bool x[10])
{
// use p.a, p.b etc.
}
Parameter structures can be initialized element-by-element in the usual way, or by using a built-in constructor for them:
myps p;
p.a = 5; // element initialization
myps q;
q = myps (4, 8, false); // constructor
===== Enumerations =====
A user-defined enumeration can be specified as follows:
defenum myenum {
ADD, SUB, MULT
};
This defines an enumeration called ''myenum'', and three elements. Internally, the symbolic names are replaced with integers starting with ''0'', and the circuits generated for the enumeration are similar to normal integer types but with a bitwidth that is large enough to represent all the enumeration values.
The enumeration constants can be accessed using the enumeration type name as follows:
...
myenum x;
...
chp {
...
x := myenum.SUB;
...
}
Trying to set an enumeration to an integer will result in a type error:
chp {
...
x := 1;
...
}
will lead to:
-[ERROR]-> Typechecking failed on CHP assignment
stmt: x := 1
Enumeration/non-enumeration types are incompatible
However, sometimes a user may want to use user-defined enumerations as integers. In order to do so,
the enumeration can be declared to be a valid integer as follows:
defenum myenum : int {
ADD, SUB, MULT
};
This version will permit integer assignments. Note that it is up to the user to ensure that integer assignments are within range. The ACT simulator ''actsim'' will check, but in principle any assignment that fits within the smallest number of bits required to represent the enumeration might be generated by circuit synthesis. Hence, using the '':int'' decorator to allow integer assignments is not recommended.