Type system

Maia supports three data types (int, bit, and var), as well as additional specialisations of the bit and var types (ubit, uvar, and kmap). The remaining types are a boolean type (bool), a structure type (struct), a stream type (stream), and an array type.

The structure type is essentially identical to C's structure type. The array type is equivalent to the structure type, with the difference that structures encapsulate objects which are potentially of different types (they are heteregenous collections), while arrays encapsulate objects of the same type (they are homogeneous collections). Arrays are therefore more flexible than C arrays (17).

File I/O is implemented using a dedicated stream type. The rationale behind using a dedicated type, rather than a set of library routines (as in C, for example) is that most verification work involves only the reading and writing of data files with a limited set of formats, and these operations can be more efficiently handled using a dedicated type.

There is no string type in 2019.11. The report statement uses a format string in the same way as C's printf statement, but there is currently no way to manipulate the contents of a string. None of the operators support string operands.


The boolean type

The bool type has the conventional boolean behaviour, but its precise semantics depends on the level of the _StrictChecking pragma. At levels which are less than or equal to 1, a value of true is simply the one-bit constant 1'b1, while false is the one-bit constant 1'b0. Boolean objects may therefore be used arbitrarily in arithmetic expressions. At level 2, however, bool is a distinct type, and is supported by a smaller set of operands.

Various statements and operators (the if and looping statements, and the conditional operator) require an expression or an operand which is tested against 'true' or 'false'. For levels 0 and 1, the expression is actually tested against the numeric value zero, rather than the boolean literal false. At level 2, an arithmetic expression is first implicitly converted to a boolean. In all these cases, a value of zero is therefore considered to be 'false', while any other value is considered to be 'true'. Note that, when testing 4-state objects, this behaviour differs from Verilog's behaviour. In Verilog, the if and looping statements consider 'false' to be 'all zero or contains any metadata'. Verilog's conditional operator uses yet another interpretation.


The data types

There are three basic data types: int and bit hold 2-state (0,1) data, while var holds 4-state (0,1,X,Z) data. The int type has a fixed size (which defaults to 32 bits) and is signed. int is of limited value for hardware design and verification. However, it is useful for general software 'housekeeping' operations, such as indexing and counting (which are frequently simpler with a signed type).

The bit and var types are essentially identical. Any operator which accepts a bit operand will also accept a var operand, and vice-versa. The specific behaviour of the operator will differ, of course; a logic operation on a var operand will produce a 4-state var result, while a logic operation on a bit operand will produce a 2-state bit result. When operating on 2-state objects Maia uses conventional binary arithmetic and logic; when operating on 4-state objects, Maia uses Verilog semantics (in other words, the results of arithmetic and logic operations are as defined in the V-2005 LRM).

When assigning a var to a bit, the metavalues (X and Z) are converted to 1. This is consistent with the interpretation of 'false' and 'true' as zero and non-zero.

The size of a bit or var data object is specified as part of its declaration, as a trailing integer. The size may be arbitrary, and can range from 1 up to a compiler-determined maximum.

bit1    b;          // 1-bit 2-state object; bit range [0,0]
bit1000 c;          // 1000-bit 2-state object; bit range [0,999]

Data objects are always indexed in a descending fashion, with the LSB being bit 0:

var32 foo;                                   // equivalent to:
reg[31:0] foo;                               // in Verilog, or
variable foo: std_logic_vector(31 downto 0); // in VHDL (but only 4-state)

The names bit and var are not intended to connote that an object holds 'integers'. The objects may contain any data pattern, including data which can be interpreted as a floating-point number. Maia has no dedicated 'floating-point' type for the simple reason that the language is not, generally speaking, concerned with the contents of an object. However, three keywords - real1, real2, and real3 - may be used to simplify the handling of floating-point data. These are not additional types, but are simply synonyms for a bit which is correctly sized to hold IEC single, double, and extended double precision data:

real1 a;            // identical to 'bit32 a' on most systems
real2 b;            // identical to 'bit64 b' on most systems
real3 c;            // identical to 'bit80 c' or 'bit128 c' on most systems

The kmap type is a specialisation of var, which simplifies the specification and handling of objects which are defined in terms of a Karnaugh map. This type is discussed in more detail here.


Unconstrained data types

When creating generic functions, it is often convenient to leave the size of one or more input parameters unspecified. If, for example, you need to calculate the parity of a data word, and your system supports 10 different word sizes, then writing and maintaining a single generic function is clearly preferable to writing and maintaining 10 different specialised functions. Maia provides specialisations of bit and var for this purpose: ubit is an unconstrained 2-state type, while uvar is an unconstrained 4-state type. These types may appear only as function formal parameters, or as a function return type.

When a formal parameter is of an unconstrained type, its actual size may be retrieved with the 'size attribute. Alternatively, a for all loop will automatically loop over all values of the actual.

When a function returns an unconstrained object, that object will be sized to whatever was returned by the function. Different paths through the function may return a 10-bit or a 12-bit object, for example, on different calls.

The example code below returns true if the unconstrained input has odd parity, and false otherwise. parity_gen.tv is a complete test program which uses this code:

bool odd_parity(ubit a) {
   result = false;
   for(int i = 0; i < a'size; i++)
      result ^= a.(i);
}

Data object properties

bit and var data objects have essentially no properties, apart from their size. These objects are not 'signed', or 'unsigned', or 'integer', or 'floating-point'; they are simply data. The interpretation of the bit pattern in an object is a higher-level concern.

In Maia, complexity is provided by operators, rather than types. There are different operators for integer and floating-point addition, for example, or for signed and unsigned comparison, in exactly the same way that hardware design uses different function units for these operations. This is essentially the opposite of general-purpose languages, which have evolved from basic simple typing through to object-orientation, by adding more and more complexity to the objects themselves.

See FAQ 21 for example code which demonstrates the difference between a signed and an unsigned operator.