Differences
This shows you the differences between two versions of the page.
Both sides previous revisionPrevious revisionNext revision | Previous revision | ||
languages:types2 [2021/11/21 16:40] – [Data type methods] rajit | languages:types2 [2021/11/21 16:57] (current) – removed rajit | ||
---|---|---|---|
Line 1: | Line 1: | ||
- | ===== User-defined types ===== | ||
- | User-defined type can be used to create complex circuit structures. | ||
- | new user-defined type name is introduced by using '' | ||
- | '' | ||
- | statements. All user-defined types have the same basic structure: | ||
- | - a type signature, that provides information about the interface to the type and the ports that are externally visible; and | ||
- | - a //body//, contained in braces, that specifies the detailed definition of the user-defined type. | ||
- | The type chosen for each port must be the most specific type used by | ||
- | that port in the body (see the implementation relation section). | ||
- | |||
- | User-defined types can also be parameterized, | ||
- | detail later. | ||
- | |||
- | ==== Processes and cells ==== | ||
- | |||
- | A process is a user-defined type that corresponds to a circuit | ||
- | entity. Other hardware description languages sometimes call it a module | ||
- | or a subcircuit. The basic syntax of a process definition is shown below. | ||
- | |||
- | < | ||
- | defproc test (bool n, m; bool p, q) | ||
- | { | ||
- | ... | ||
- | } | ||
- | </ | ||
- | |||
- | The definition above creates a new process, called '' | ||
- | a port list consisting of four '' | ||
- | contain any parameter types ('' | ||
- | |||
- | If the body of the user-defined type is replaced by a single | ||
- | semi-colon or is empty, the statement corresponds to a type | ||
- | // | ||
- | mutually recursive types. The declaration corresponding to type | ||
- | '' | ||
- | |||
- | < | ||
- | defproc test (bool n, m; bool p, q); | ||
- | </ | ||
- | |||
- | If the process is never defined, ACT assumes that it has an empty | ||
- | body. If a process declaration is followed by a definition, the type | ||
- | signature must match exactly. | ||
- | |||
- | < | ||
- | defproc test (bool n, m; bool p, q); | ||
- | defproc test (bool n, m; bool p) { } | ||
- | -[ERROR]-> | ||
- | </ | ||
- | |||
- | A type can only have one definition in a given scope. | ||
- | |||
- | < | ||
- | defproc test (bool n) { ... } | ||
- | defproc test (bool n) { ... } | ||
- | -[ERROR]-> | ||
- | </ | ||
- | |||
- | The body of a process specifies its implementation. This can use a | ||
- | combination of instances of other processes, connections, | ||
- | languages like production rules. Loops and conditional statements can | ||
- | also be used to construct a process. | ||
- | |||
- | Port lists have a syntax similar to instantiations. A type | ||
- | specifier can be followed by a list of identifiers rather than just a | ||
- | single identifier, similar to an instantiation. Semicolons are used | ||
- | to separate parameters of differing types, as shown in the example | ||
- | below. | ||
- | |||
- | < | ||
- | defproc test2(bool n,m; d1of2 p,q) { ... } | ||
- | </ | ||
- | |||
- | In this example we assumed that there was a user-defined type (or | ||
- | channel) called '' | ||
- | user-defined type in the port list must be either a data or channel | ||
- | type. Processes are supposed to correspond to circuit blocks, and so | ||
- | cannot be port parameters to other circuit blocks. | ||
- | |||
- | Square brackets can also be used following the identifier names to | ||
- | specify array ports. The meaning of these square brackets is identical | ||
- | to the ordinary array instantiation. However, the arrays in port lists | ||
- | are restricted to be dense arrays indexed at zero. This restriction is | ||
- | enforced by syntax, and will be reported as a parse error. | ||
- | |||
- | < | ||
- | defproc test1 (bool a,b,c, d[10]) { } // success! | ||
- | defproc test2 (bool a,b,c, d[0..9]) { } | ||
- | -[ERROR]-> | ||
- | </ | ||
- | |||
- | The ports themselves cannot be converted to sparse arrays within the | ||
- | body of a definition. This means that the following is illegal: | ||
- | |||
- | < | ||
- | defproc test1 (bool a, b, c, d[10]) | ||
- | { | ||
- | bool d[11..12]; | ||
- | ... | ||
- | } | ||
- | -[ERROR]-> | ||
- | </ | ||
- | |||
- | Type names and variable names do not share the same name space. | ||
- | Creating a type definition with the same name as an instance variable | ||
- | or vice versa is allowed, but deprecated. | ||
- | |||
- | Cells follow the same rules for definition as processes, except the | ||
- | keyword '' | ||
- | for separating cells from processes is that processes are supposed to | ||
- | correspond to logical entities that are meaningful semantic | ||
- | objects. For example, a process ordinarily has its origins in a CHP | ||
- | language description. Cells, on the other hand, | ||
- | can be fragments of logical processes. Examples of cells are standard | ||
- | gates like C-elements, NAND, or NOR gates, or commonly used circuit | ||
- | structures like completion detection logic. Cells are distinguished | ||
- | from processes to make it easier to write automation tools. | ||
- | |||
- | ==== Data types ==== | ||
- | |||
- | A data type is defined using '' | ||
- | to an integer or Boolean value, although 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/ | ||
- | |||
- | 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 | ||
- | '' | ||
- | '' | ||
- | |||
- | < | ||
- | deftype d1of2 <: int< | ||
- | { | ||
- | spec { | ||
- | exclhi(d0, | ||
- | } | ||
- | } | ||
- | </ | ||
- | |||
- | The body of the type is similar to a process, except it can only contain | ||
- | connections, | ||
- | following would result in an error: | ||
- | |||
- | < | ||
- | deftype d1of2 <: int< | ||
- | { | ||
- | bool p; | ||
- | spec { | ||
- | exclhi(d0, | ||
- | } | ||
- | } | ||
- | -[ERROR]-> | ||
- | </ | ||
- | |||
- | |||
- | |||
- | |||
- | ==== Channel types ==== | ||
- | |||
- | Channels are similar to data types. Instead of relating a user-defined | ||
- | channel to built-in data, we relate them to a built-in channel types | ||
- | instead. The methods required for supporting the full functionality of | ||
- | a channel are operations necessary to send and receive data on the | ||
- | channel, rather than a read and write operation on a data value. | ||
- | |||
- | < | ||
- | defchan e1of2 <: chan(bool) (bool d0,d1,e) | ||
- | { | ||
- | spec { | ||
- | exclhi(d0, | ||
- | } | ||
- | } | ||
- | </ | ||
- | |||
- | ==== Structures ==== | ||
- | |||
- | The '' | ||
- | These types can be used to group data fields or channel fields together. | ||
- | A structure is defined using '' | ||
- | a built-in type as in the examples above. So, for instance: | ||
- | < | ||
- | deftype mystruct (int< | ||
- | </ | ||
- | would declare a structure with two fields: '' | ||
- | |||
- | If all the fields within a structure are data values, this is a special | ||
- | case of a structure consisting purely of data. | ||
- | |||
- | ===== Methods ===== | ||
- | |||
- | Process, channel, and data types can include //methods// that provide mechanisms to | ||
- | manipulate the type or access parts of the type. | ||
- | |||
- | ==== Data type methods ==== | ||
- | |||
- | There are two 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. | ||
- | |||
- | < | ||
- | deftype d1of2 <: int< | ||
- | { | ||
- | spec { | ||
- | exclhi(d0, | ||
- | } | ||
- | | ||
- | set { | ||
- | | ||
- | } | ||
- | get { | ||
- | | ||
- | } | ||
- | } | ||
- | } | ||
- | </ | ||
- | |||
- | In the example above, the '' | ||
- | '' | ||
- | '' | ||
- | '' | ||
- | the methods specify conversion operations. | ||
- | |||
- | The selection statement in the '' | ||
- | selection operator '' | ||
- | that when the '' | ||
- | cannot both be '' | ||
- | specification body. Also, if both '' | ||
- | (i.e. an illegal state in which to execute a get operation), the | ||
- | variable '' | ||
- | one of '' | ||
- | data type. (This is different in the case of a channel, where the | ||
- | semantics of the channel permit waiting.) | ||
- | |||
- | 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. | ||
- | |||
- | ==== Channels ==== | ||
- | |||
- | |||
- | There are six possible methods that can be defined for a channel type: | ||
- | |||
- | * Methods for sending and receiving values on the channel | ||
- | * '' | ||
- | * '' | ||
- | * Methods for probing a channel to determine if there is synchronization operation being attempted. | ||
- | * '' | ||
- | * '' | ||
- | |||
- | The send operation '' | ||
- | parts: setting the data value, followed by the synchronization | ||
- | operation. Setting the data value also indicates that the sender is | ||
- | ready to communicate. It is illegal to set the data value multiple | ||
- | times without an intervening synchronization operation. Finally, | ||
- | attempting to set the data value might block if the previous channel | ||
- | operation has not completed as yet. Whether or not this could occur | ||
- | depends on the channel protocol. | ||
- | |||
- | The receive operation '' | ||
- | two parts: receiving the data value, followed by the synchronization | ||
- | operation. Attempting to get the data value from the channel will | ||
- | block if the sender has not provided any value. Once a value has been | ||
- | extracted from the channel, the synchronization operation can be | ||
- | executed. Prior to the synchronization, | ||
- | executed; the channel must be designed so that subsequent get | ||
- | operations will return the same value as the first one, and will be | ||
- | guaranteed not to block. The get operation is used to implement a CHP | ||
- | value probe, where the receiver can peek at the value pending in the | ||
- | channel without attempting a synchronization operation. | ||
- | |||
- | An example definition of a Boolean channel where the channel has an | ||
- | lazy-active send and passive receive is below. | ||
- | |||
- | < | ||
- | defchan e1of2 <: chan(bool) (bool d0,d1,e) | ||
- | { | ||
- | spec { | ||
- | exclhi(d0, | ||
- | } | ||
- | | ||
- | set { | ||
- | [e]; | ||
- | } | ||
- | send_rest { | ||
- | [~e]; | ||
- | } | ||
- | get { | ||
- | | ||
- | } | ||
- | recv_rest { | ||
- | | ||
- | } | ||
- | recv_probe = (d0|d1); | ||
- | } | ||
- | } | ||
- | </ | ||
- | |||
- | In the example above, the '' | ||
- | specify the sequence of operations on the channel variables that are | ||
- | invoked for a send action. The '' | ||
- | specify the sequence of operations used to perform a receive. The | ||
- | special variable '' | ||
- | that is being either sent or received on the channel. | ||
- | |||
- | This channel has an active send and passive receive, and hence probes | ||
- | are only supported at the receiver. The '' | ||
- | expression specifies the Boolean expression corresponding to the probe | ||
- | at the receiver end of the channel. A '' | ||
- | specified in a similar way when the sender is passive and receiver is | ||
- | active. | ||
- | |||
- | The '' | ||
- | handshake protocol. If the channel were to correspond to a two-phase | ||
- | protocol, a different sequence of actions can be specified instead. | ||
- | |||
- | Port lists for channel types can be data types (built-in or | ||
- | user-defined) or channels. Processes are not valid types for ports of a | ||
- | channel type. | ||
- | |||
- | ==== Instantiating user-defined types ==== | ||
- | |||
- | User-defined type variables can be instantiated in much the same manner | ||
- | as ordinary type variables. | ||
- | |||
- | < | ||
- | defproc test(bool N, n) { ... } | ||
- | test x; | ||
- | // x.N and x.n refer to the ports of '' | ||
- | </ | ||
- | |||
- | The ACT description above creates an instance of type '' | ||
- | '' | ||
- | ports listed as well as creating whatever is specified by the body of | ||
- | the type definition. The list of ports of a user-defined type can be | ||
- | accessed from the scope outside the type definition by using | ||
- | dot-notation. These externally visible ports are analogous to the | ||
- | //fields// of structures or record types in standard programming | ||
- | languages. | ||
- | |||
- | This analogy to records can be used to build complex data types, | ||
- | albeit with slightly different syntax compared to traditional | ||
- | programming languages. The following is a simple example that | ||
- | illustrates this. | ||
- | |||
- | < | ||
- | deftype mystruct <: int< | ||
- | { | ||
- | methods { | ||
- | set { | ||
- | | ||
- | | ||
- | | ||
- | } | ||
- | get { | ||
- | | ||
- | } | ||
- | } | ||
- | } | ||
- | </ | ||
- | |||
- | |||
- | |||
- | ===== Parameterized types ===== | ||
- | Parameterized types give ACT considerable flexibility in type | ||
- | definitions. Parameterized types come in two flavors: built-in types, | ||
- | and user-defined types. For user-defined types, ACT guarantees | ||
- | that the order in which parameters are created and initialized is from | ||
- | left to right. Therefore, one can use the value of one parameter in | ||
- | the definition of another one. | ||
- | |||
- | ==== Built-in integers and channels ==== | ||
- | |||
- | Although we have been describing the types '' | ||
- | as simple types, they are in fact paramterized. Omitting the | ||
- | parameters makes ACT use implicit default parameters for both of | ||
- | them. | ||
- | |||
- | The '' | ||
- | specify the integer. This bit-width can be specified using angle | ||
- | brackets, as shown below: | ||
- | |||
- | < | ||
- | int< | ||
- | int< | ||
- | </ | ||
- | |||
- | When interpreting these bits as integers, ACT assumes an unsigned | ||
- | binary representation. The default bit-width is thirty-two. | ||
- | |||
- | The channel type '' | ||
- | being sent and received on the channel. | ||
- | |||
- | < | ||
- | chan(bool) x; // x is a Boolean channel | ||
- | chan(int< | ||
- | </ | ||
- | |||
- | The default data type for a channel is assumed to be the default | ||
- | '' | ||
- | |||
- | Another built-in data type is the // | ||
- | enumeration type corresponds to integer-valued variables with a | ||
- | restricted range. | ||
- | |||
- | < | ||
- | enum< | ||
- | </ | ||
- | |||
- | For convenience, | ||
- | of expressions. Also, enumerations that have power-of-two ranges are | ||
- | type-equivalent to the approprate '' | ||
- | '' | ||
- | useful when specifying a data value that is a one-hot code. | ||
- | |||
- | ==== User-defined types ==== | ||
- | Processes, channels, and datatypes created using '' | ||
- | '' | ||
- | parameterization. Parameters are specified using the '' | ||
- | keyword. | ||
- | |||
- | Since the syntax for all three is the same, we use a process | ||
- | definition to illustrate this. To create a parameterized type, the | ||
- | definition of the type is preceeded by a template specifier as shown | ||
- | below. | ||
- | |||
- | < | ||
- | // A generic adder block | ||
- | template< | ||
- | defproc adder (e1of2 a[N], b[N]; e1of2 s[N]) | ||
- | { | ||
- | ... | ||
- | } | ||
- | </ | ||
- | |||
- | This example defines an '' | ||
- | parameter. Note that the value of '' | ||
- | arrays in the port list for the process. Instances of this | ||
- | '' | ||
- | |||
- | < | ||
- | adder< | ||
- | adder< | ||
- | </ | ||
- | |||
- | The value of '' | ||
- | 16. To illustrate how one might define this adder block, assume we | ||
- | have processes '' | ||
- | '' | ||
- | constant source of zeros, and a constant sink respectively. One | ||
- | possible definition of the adder would be: | ||
- | |||
- | < | ||
- | template< | ||
- | defproc adder (e1of2 a[N], b[N]; e1of2 s[N]) | ||
- | { | ||
- | | ||
- | ( i : N-1 : fa[i].a = a[i]; fa[i].b = b[i]; fa[i].s = s[i]; | ||
- | fa[i].co = fa[i+1].ci; ) | ||
- | | ||
- | | ||
- | | ||
- | | ||
- | } | ||
- | </ | ||
- | |||
- | This creates a parameterized ripple-carry adder. Notice the use of | ||
- | loops and arrays to connect the carry chain for the adder, and the | ||
- | inputs and outputs of the process to the '' | ||
- | |||
- | As shown in the example above, the arguments in the template parameter | ||
- | list are specified by listing them next to the type name. Trailing | ||
- | arguments can be omitted from the parameter list attached to the type | ||
- | as shown in the example below. | ||
- | |||
- | < | ||
- | template< | ||
- | defproc test (bool n[N]) { ... } | ||
- | |||
- | test< | ||
- | </ | ||
- | |||
- | Channels and data types can also be parameterized in the same way. For | ||
- | example, the following might be an N-bit dual rail definition. | ||
- | |||
- | < | ||
- | template< | ||
- | deftype d1of2 <: int< | ||
- | </ | ||
- | |||
- | Since the body of the type can use loops and selection statements in | ||
- | arbitrary ways, changing the parameters for the type can completely | ||
- | change the structure of the circuit. It can also change the ports for | ||
- | the type. Hence, when checking for type compatibility, | ||
- | parameters are also taken into account. Hence, the full type for | ||
- | instance '' | ||
- | '' | ||
- | parameters are more completely specified as '' | ||
- | although the angle brackets can be omitted. Arrays can only correspond | ||
- | to instances of the same type---so an array cannot contain a three-bit | ||
- | adder and five-bit adder. | ||
- | |||
- | ===== Directional types ===== | ||
- | |||
- | Data and channel types also support access permissions in terms of valid | ||
- | operations on the types. To illustrate this, consider the simplest data | ||
- | type, namely a '' | ||
- | type can be defined, and they are shown below: | ||
- | |||
- | < | ||
- | bool x; // Boolean that may be read or written | ||
- | bool! y; // Boolean that must be written, and may be read | ||
- | bool? z; // Boolean that must be read, and cannot be written | ||
- | </ | ||
- | |||
- | The '' | ||
- | can be accessed. The primary use of this is in port lists, where one can | ||
- | specify what variables are read and written by a process. The same | ||
- | syntax can be used (with the same meaning) for user-defined data types. | ||
- | |||
- | The following example shows a possible definition for a two-input nand | ||
- | gate that takes two inputs '' | ||
- | output on '' | ||
- | |||
- | < | ||
- | defcell nand2 (bool? a, b; bool! c) { ... } | ||
- | </ | ||
- | |||
- | Channels support a similar syntax, but the meaning is slightly | ||
- | different. | ||
- | |||
- | < | ||
- | chan(int) x; // Sends or receives are permitted | ||
- | chan!(int) y; // Only sends permitted | ||
- | chan?(int) z; // Only receives permitted | ||
- | </ | ||
- | |||
- | Again, the same syntax is valid for user-defined channels. These | ||
- | constructs are useful in libraries for additional error checking, and | ||
- | conveying more information to the user of the library. | ||
- | |||
- | ==== Interaction with user-defined types ==== | ||
- | |||
- | |||
- | Direction specifications can be used for built-in data and channel | ||
- | types, as well as user-defined types. Consider the '' | ||
- | user-defined channel type that we saw earlier: | ||
- | |||
- | < | ||
- | defchan e1of2 <: chan(bool) (bool d0,d1,e) | ||
- | { | ||
- | spec { | ||
- | exclhi(d0, | ||
- | } | ||
- | | ||
- | set { | ||
- | [e]; | ||
- | } | ||
- | send_rest { | ||
- | [~e]; | ||
- | } | ||
- | get { | ||
- | | ||
- | } | ||
- | recv_rest { | ||
- | | ||
- | } | ||
- | recv_probe = (d0|d1); | ||
- | } | ||
- | } | ||
- | </ | ||
- | |||
- | When we use '' | ||
- | specify the access permissions for the //port parameters// | ||
- | user-defined type. The convention used is that there are five possible | ||
- | ways to specify any constraints on access to port parameters. For our | ||
- | example, we can use one of the following variations in the port parameter list: | ||
- | |||
- | < | ||
- | bool d0; // No constraints; | ||
- | bool! d0; // Both e1of2? and e1of2! have bool! permissions | ||
- | bool? d0; // Both e1of2? and e1of2! have bool? permissions | ||
- | bool?! d0; // e1of2? has bool? permissions, | ||
- | bool!? d0; // e1of2? has bool! permissions, | ||
- | </ | ||
- | |||
- | Hence, a better definition of an '' | ||
- | completely specify the access permissions for the port parameters in | ||
- | the following way. | ||
- | |||
- | < | ||
- | defchan e1of2 <: chan(bool) (bool?! d0,d1; bool!? e) { ... } | ||
- | </ | ||
- | |||
- | A careful examination of the type signature reveals that the sender | ||
- | and receiver have the appropriate permissions. There is a subtle | ||
- | interaction between connections and directional types in ACT, and this | ||
- | is detailed in the section on connections. |