Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
language:types2 [2024/07/18 16:15] – [User-defined types] rajitlanguage:types2 [2025/05/02 10:18] (current) – [Default parameters] rajit
Line 20: Line 20:
  
  
- 
-===== Methods ===== 
  
 Process, channel, and data types can include //methods// that provide mechanisms to Process, channel, and data types can include //methods// that provide mechanisms to
-manipulate the type or access parts of the type. There are a number of built-in+manipulate the type or access parts of the type. There are a number of special built-in
 method names that can be specified for data types and channel types. method names that can be specified for data types and channel types.
-==== Data 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. 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. 
  
-<code act> 
-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] 
-     } 
-   } 
-} 
-</code> 
- 
-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.) 
- 
- 
-==== Channel methods ==== 
- 
- 
-There are eight possible methods that can be defined for a channel type: 
- 
-   * Methods for sending and receiving values on the channel 
-      * ''set'', ''send_up'', ''send_rest'': together these three operations implement a send operation on the channel. The send operation consists of two parts: (i) setting the data value to be sent (''set''); (ii) completing the synchronization (''send_up''); and (iii) completing the rest of the protocol (''send_rest''). 
-      * ''get'', ''recv_up'', ''recv_rest'': together these three operations implement a receive operation on the channel. The receive operation consists of three parts: (i) getting the value that has been transmitted along the channel (''get''); (ii) completing the synchronization operation (''recv_up''); and (iii) completing the rest of the protocol (''recv_rest''). 
-   * Methods for initializing  the channel on reset, if needed. 
-      * ''send_init'' : this is used to initialize the sender end of the channel on reset. 
-      * ''recv_init'' : this is used to initialize the receiver end of the channel on reset. 
- 
-For channels, there are two special methods that are used for probe operations with different syntax. Both of these have to be specified via an expression, rather than the normal method syntax. 
-      * ''send_probe'': this is the probe operation for the sending end of the channel. It corresponds to the receiver being ready to communicate. 
-      * ''recv_probe'': this is the probe operation for the receiving end of the channel. It corresponds to the sending being ready to communicate. 
- 
-The send operation ''X!e'' in the CHP language corresponds to two 
-parts: setting the data value, followed by the synchronization 
-operation, and possibly the reset phase of the handshake.  
-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 ''X?v'' in the CHP language corresponds to 
-three parts: receiving the data value, followed by the synchronization 
-operation, and finally the rest of the handshake.  
-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, multiple get operations can be 
-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. Note that for a channel, 
-''self'' corresponds to the type of the data being sent or received 
-on the channel. 
- 
-<code act> 
-defchan e1of2 <: chan(bool) (bool d0,d1,e) 
-{ 
-   spec { 
-    exclhi(d0,d1) 
-   } 
-   methods { 
-    set { 
-      [e];[self->d1+[]~self->d0+] 
-    } 
-    send_up { 
-      [~e] 
-    } 
-    send_rest { 
-      d0-,d1- 
-    } 
-    get { 
-     [d0->self-[]d1->self+] 
-    } 
-    recv_up { 
-     e- 
-    } 
-    recv_rest { 
-     [~d0&~d1];e+ 
-    } 
-    recv_probe = (d0|d1); 
-   } 
-} 
-</code> 
- 
-In the example above, the ''set'', ''send_up'', and ''send_rest'' methods 
-specify the sequence of operations on the channel variables that are 
-invoked for a send action. The ''get'', ''recv_up'', and ''recv_rest'' methods 
-specify the sequence of operations used to perform a receive. The 
-special variable ''self'' is used to specify the ''bool'' value 
-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 ''recv_probe'' method 
-expression specifies the Boolean expression corresponding to the probe 
-at the receiver end of the channel. A ''send_probe'' can be 
-specified in a similar way when the sender is passive and receiver is 
-active. 
- 
-The ''e1of2'' channel has been specified to perform a four-phase 
-handshake protocol. If the channel were to correspond to a two-phase 
-protocol, a different sequence of actions can be specified instead. 
- 
-When defining an exchange channel, the special variable ''selfack'' is 
-used to specify the value being received by the sender, and being sent by the receiver. 
  
 ===== Instantiating user-defined types ===== ===== Instantiating user-defined types =====
Line 300: Line 160:
 adder and five-bit adder. adder and five-bit adder.
  
 +==== Default parameters ====
  
-===== Direction flags and user-defined types =====+When defining complex user-defined types with many parameters, it can be useful to have 
 +default parameter values. ACT has syntax to support default parameter values for trailing 
 +parameters in a template definition. 
 + 
 +<code act> 
 +template <pint N; pbool active_high true> 
 +defproc driver(bool? inp; bool! outp) 
 +
 +  bool sig; 
 +  prs { 
 +    inp => sig- 
 +  } 
 +  [active_high -> prs { sig => outp- } 
 +  [] else -> sig outp; 
 +  ] 
 +
 +</code> 
 +(Note: this is not a real signal driver, but the idea here is the you have a parameterized 
 +driver that can drive a fanout of ''N'' gates.) This definition has a default value for the  
 +''active_high'' parameter as ''true'' So an instance 
 +<code act> 
 +driver<4> x; 
 +</code> 
 +will have four production rules: 
 +<code act> 
 + x.inp -> sig- 
 +~x.inp -> sig+ 
 + sig -> x.outp- 
 +~sig -> x.outp+ 
 +</code> 
 +However, this behavior can be changed by using: 
 +<code act> 
 +driver<4,false> x; 
 +</code> 
 +In this case, ''sig'' will be connected to ''x.outp''
 + 
 +Note that ACT is very strict about type-checking; so, for example, ''driver<4>'' and ''driver<4,true>'' are //not// treated as the same type even though the default parameter value for the second template parameter is ''true''
 + 
 +==== Grouping parameters ==== 
 + 
 +Parameters can be combined into [[language:types2:data#parameter_structures|parameter structures]], to organize a large number of parameters and treat them as a group. Parameter structures can also be used as a template parameter types
 + 
 +===== Direction flags  =====
  
  
Line 344: Line 247:
 interaction between connections and directional types in ACT, and this interaction between connections and directional types in ACT, and this
 is detailed in the section on connections. is detailed in the section on connections.
 +
 +===== Macros and Functions within User-defined types =====
 +
 +User-defined types support additional methods (beyond the special ones for channels and data types).
 +These methods are of two types:
 +    * //macros//, which correspond to CHP fragments that are used for in-place substitution; and
 +    * //functions// which are supported by [[language:types2:data#pure_structures|pure structures]], which are similar to traditional [[language:expressions#functions|functions]].
 +
 +==== Macros ====
 +
 +A macro is, as the name sounds, a CHP fragment. This fragment is used to substitute for the macro in the places where it is used. Macros can be defined for any user-defined type (except for parameter structures).  As an example, consider a process that implements a data structure like a stack.
 +
 +<code act>
 +defproc stack (chan(int)? in; chan(int)!out )
 +
 +  ...
 +}
 +</code>
 +
 +In ordinary circumstances, one would instantiate a copy of the stack, and then use the ''in'' and ''out'' channels to push or pop elements from the stack as follows:
 +
 +<code act>
 +...
 +stack s;
 +int x;
 +...
 +chp {
 +   ...
 +   s.in!5; // push value 5
 +   ...
 +   s.pop?x; // pop x out of the stack
 +   ...
 +}
 +</code>
 +
 +As an alternative, the user could provide //macros// to push and pop elements from the stack as follows:
 +
 +<code act>
 +defproc stack (chan(int)? in; chan(int)!out )
 +
 +  ...
 +  methods {
 +      macro push(int val) {
 +          in!val
 +      }
 +      macro pop(int res) {
 +          out?res
 +      }
 +  }
 +}
 +</code>
 +
 +The same stack use case can be written as:
 +<code act>
 +...
 +stack s;
 +int x;
 +...
 +chp {
 +   ...
 +   s.push(5);
 +   ...
 +   s.pop(x);
 +   ...
 +}
 +</code>
 +
 +==== Functions ====
 +
 +In addition to macros, [[language:types2:data#pure_structures|pure structures]] can also include user-defined functions. User-defined functions within pure structures have similar syntax to macros. The following is an illustrative example
 +
 +<code act>
 +deftype signed_int (bool s; int<7> v)
 +{
 +    methods {
 +        function negative() : bool
 +        {
 +            chp {
 +              self := s
 +            }
 +        }
 +        function mag() : int<7>
 +        {
 +            chp {
 +               self := v
 +            }
 +        }       
 +   }
 +}
 +</code>
 +
 +With this definition, a user can use method calls to access the fields of the structure as follows:
 +
 +<code act>
 + signed_int s;
 + ...
 +chp {
 +    ... 
 +    [ s.negative() -> log ("Negative number!")
 +    [] else -> log ("Positive number!")
 +    ]
 +}    
 +...
 +</code>
 +
 +Note that functions cannot have any side-effects; in particular, this means that a function cannot change
 +any of the members of the pure structure. Macros can be used to change those.
 +
 +=== Operator overloading ===
 +
 +Functions within pure structures are also used to support operator overloading. In particular, the following function methods are interpreted to be the definition of operator overloading for arithmetic operators:
 +   * ''plus'', ''minus'', ''mult'', ''div'', ''mod'' : addition, subtraction, multiplication, division, and modulo operators
 +   * ''uminus'' : unary minus
 +   * ''and'', ''or'', ''xor'' : logical operators
 +   * ''lsl'', ''lsr'', ''asr'' : logical shift left, logical shift right, arithmetic shift right
 +   * ''lt'', ''gt'', ''le'', ''ge'', ''eq'', ''ne'' : comparison operators
 +   * ''not'' : logical negation
 +An example of a fixed-point arithmetic datatype is provided in the [[https://github.com/asyncvlsi/stdlib/blob/main/math/fxp.act|ACT standard library]], and serves as a useful reference for using operator overloading.
 +In the linked example, ''fixpoint<a,b>'' corresponds to a fixed-point number with ''a'' integer bits and ''b'' fractional bits following the standard Q(a,b) format.
 +