A Maia program, like a C program, is a collection of external functions. The entry point is the 'main' function, which does not have parameters in Maia. 'main' must be declared with a void or int return type (see Appendix 2 in the LRM if you need to find the value returned by a program). The simplest 'hello world' program is therefore:

void main() {
   report("Hello World!\n");

This program compiles and runs, even without a DUT section; the DUT section is required only if access to a DUT is required.

Functions do not need to be declared (with the exception of foreign functions), and may appear in any order in the source code (in fact, almost any external object, or type definition, can be forward-referenced).

Functions may be divided into 4 categories:

  1. User functions are conventional functions which are called by naming them as part of an expression. They run in the thread from which they were called; 'main' is in thread 0. The general form of the formal parameter list, and the return type specification, are identical to C++, except that Maia functions can return arrays, and can not return references.
  2. Foreign functions are functions written in another language (currently, the 'function' must be a Verilog task). They are called in the same way as user functions, but must return values by reference. These functions must be declared, since the compiler can't see their source code.
  3. Thread functions are functions which are named as the target of an exec statement. The exec statement creates a new thread, and executes the thread function in that thread. A function which is named as the target of an exec statement cannot also be called, from any part of the program, as a conventional user function (in other words, functions cannot be both user functions and thread functions).
  4. Trigger functions are posted for later execution by a trigger statement; they cannot be called explicitly. Trigger functions have a leading @ character in their name. These functions run in a new (implicit) thread whenever they are triggered, and must complete in zero simulation time (they must not contain any time-consuming statements).

Thread and trigger functions cannot return a value to the 'caller', since there is no caller. Thread functions have a number of usage restrictions:

  • They must be declared with a void return type
  • They must have at least one formal parameter. The first formal must be a reference to an int. Inside the thread function, the formal will contain the thread identifier (a small non-zero positive integer) for the new thread; this is also returned to the caller (which is why it must be a reference). The exec statement which creates the thread executes in zero simulation time, and the first actual will contain the new thread ID on completion of the exec.
  • Since there is no 'caller', none of the remaining function parameters (if any) may be passed by reference.
// some examples of function definitions:
void f1(void) {...}                       // f1 has no formals, and return nothing
int f2(struct s1 a[2], int b) {...}       // f2 has two formals, and returns an int
// the f3 declaration is valid only if _StrictChecking is 0:
f3(a, b) {...}                            // this is equivalent to...
uvar f4(uvar a, uvar b) {...}

// and a foreign function declaration, for a task in a generate loop, with 2
// 40-bit reference parameters:
foreign void \test2.A[0].user_code\(var40&, var40&);

If a function has no parameters, the keyword void may optionally be used for the parameter list.

A return statement may be used to terminate the function and, for user functions, to return a value. User functions also have an implicit result variable, and the current value of this variable is returned if the return statement does not itself specify a value. If the function 'falls off the end' without executing a return statement, then the current value of result will be returned. result is automatically initialised to 0 (for functions which return 2-state data) or X (for functions which return 4-state data). The use of an implicit result variable is common to a number of other languages, including e.

int f1(void) {
   result = 2;          // f1 returns 2

int f2(void) {
   result = 2;
   return 4;            // f2 returns 4

int f3(void) {
   return;              // f3 returns 0

struct s1 {
   bit4 a;
   var4 b;
   int  c;

struct s1 f4(void) {
      (result.a == 4'b0000) &&
      (result.b == 4'bxxxx) &&
      (result.c == 0));

Function names can be overloaded, as long as the number of parameters are different.

Functions may technically be called recursively, but this is not supported in mtv's Verilog code generator.