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:28] – [Macros and Functions] rajitlanguage:types2 [2026/08/09 13:10] (current) – [Non-strict template parameters] rajit
Line 153: Line 153:
 the type. Hence, when checking for type compatibility, the values of the type. Hence, when checking for type compatibility, the values of
 parameters are also taken into account. Hence, the full type for parameters are also taken into account. Hence, the full type for
-instance ''a2'' above is in fact ''adder<5>'', not just+instance ''a2'' above is in fact ''adder<16>'', not just
 ''adder''. Types such as ''fulladder'' that do not have ''adder''. Types such as ''fulladder'' that do not have
 parameters are more completely specified as ''fulladder<>'', parameters are more completely specified as ''fulladder<>'',
Line 160: 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 205: Line 248:
 is detailed in the section on connections. is detailed in the section on connections.
  
-===== Macros and Functions =====+===== Macros and Functions within User-defined types =====
  
 User-defined types support additional methods (beyond the special ones for channels and data types). User-defined types support additional methods (beyond the special ones for channels and data types).
Line 213: Line 256:
  
 ==== Macros ==== ==== 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>
 +
 +Macros can also use the ''chp-txt'' language. The syntax for this is shown below.
 +
 +<code act>
 +defproc stack (chan(int)? in; chan(int)! out)
 +
 +  ...
 +  methods {
 +      macro push(int val) chp-txt {
 +          send(in,val)
 +      }
 +      macro pop(int res) chp-txt {
 +          recv(out,res)
 +      }
 +  }
 +}
 +</code>
  
 ==== Functions ==== ==== 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.
 +
 +==== Example user-defined type with macros and methods ====
 +
 +Combining some of these ideas, the following is an example of a user-defined type corresponding to an N-bit signed integer. Only some methods are defined.
 +
 +<code act>
 +template<pint W>
 +deftype signed_int (int<W> x)
 +{
 +    { W > 1 : "Need sign bit plus one bit at a minumum" };
 +    methods {
 +        function plus (signed_int<W> s) : signed_int<W>
 +        {
 +            chp {
 +               self.x := x + s.x // 2's complement!
 +             }
 +         }
 +        macro display()
 +        {
 +            [ x{W-1} = 1 -> log_p("-", (~x+1)) 
 +            [] else -> log_p(x)
 +            ]
 +         }
 +         macro set(int<W> v)
 +         {  
 +              x := v
 +          }
 +     }
 +}
 +</code>
 +
 +In this scenario, the following CHP adds two numbers, and then displays the result using a ''log'' statement.
 +<code act>
 +defproc test()
 +{
 +   signed_int<8> u, v;
 +   chp {
 +       v.set(1);
 +       u.set(-3);
 +       v := v + u;
 +       log_st("");
 +       v.display();
 +       log_nl("")
 +    }
 +}
 +</code>
 +This should display ''-2'' as a log message when run using ''actsim''.
 +
 +===== Non-strict template parameters  =====
 +
 +The following ACT program is a buffer that adds a constant value to its input:
 +<code act>
 +template<pint W>
 +defproc addbuf(chan?(int<W>) L; chan!(int<W>) R)
 +{
 +   int<W> x;
 +   chp {
 +      *[ L?x; R!(x+17) ]
 +   }
 +}
 +...
 +addbuf<8> mybuf;
 +...
 +</code>
 +Since ''17'' is a constant, the circuit generated for this can use this constant value to optimize the adder.
 +A parameterized version of this buffer is:
 +<code act>
 +template<pint W, VAL>
 +defproc addbuf(chan?(int<W>) L; chan!(int<W>) R)
 +{
 +   int<W> x;
 +   chp {
 +      *[ L?x; R!(x+VAL) ]
 +   }
 +}
 +
 +...
 +addbuf<8,17> mybuf;
 +...
 +</code>
 +This will also result in the same circuit as the earlier example, because the value ''17'' is a parameter and so, after expansion, the CHP to be synthesized will have the constant value ''17'' as a fixed input to the adder.
 +
 +What if we wanted to create an array of these adders? While 
 +<code act>
 +...
 +addbuf<8,17> mybuf[8];
 +</code>
 +is fine, the problem arises if we want each ''addbuf'' to add a //different// value. The following program
 +<code act>
 +addbuf<8,17> mybuf[0..0];
 +addbuf<8,18> mybuf[1..1];
 +</code>
 +will result in the error message
 +<code>
 +-[ERROR]-> Sparse array type error on mybuf
 +                   Orig type: addbuf<8,17>
 +                   New type: addbuf<8,18>
 +</code>
 +This is because elements of an array must have the same type. As this is a useful use-case, ACT has support for a special type of template parameter introduced for this scenario.
 +
 +Template parameters for processes can be of two types:
 +   * //strict// parameters, which are the ones we have been considering so far. Elements of an array must have the same strict template parameters.
 +   * //non-strict// parameters, which can be varied within an array.
 +
 +
 +Non-strict parameters are separated from strict parameters by a vertical bar. The example above would be written as follows:
 +<code act>
 +template<pint W | pint VAL>
 +defproc addbuf(chan?(int<W>) L; chan!(int<W>) R)
 +{
 +   int<W> x;
 +   chp {
 +      *[ L?x; R!(x+VAL) ]
 +   }
 +}
 +
 +...
 +addbuf<8,17> mybuf[0..0];
 +addbuf<8,18> mybuf[1..1];
 +...
 +</code>
 +ACT will allow this syntax. This comes with a few caveats:
 +    * Any ports for the type can only use strict parameters.  In other words, the type signature can only depend on strict parameters. This is what permits different process types to be mixed within a single array, since the external interface to the array elements remains unchanged. So<code act>
 +template<pint W | pint VAL>
 +defproc oddbuf(chan?(int<W>) L; chan!(int<W+VAL>) R)
 +{
 +... 
 +
 +</code>will lead to the following error<code>
 +-[ERROR]-> Expressions in port parameter list can only use strict template parameters
 +</code>
 +   * Even if the array is dense, it will be treated as a sparse array with multiple chunks where each chunk can have different non-strict parameters.
 +   * Sub-array expressions cannot be used where different elements have different types.
 +
 +Since the general ACT syntax can be used, the following would also be valid:
 +<code act>
 +...
 +(i:8: addbuf<8,17+i> mybuf[i..i];)
 +...
 +</code>