Maia LRM26
Maia LRM26
Maia LRM26
This document contains proprietary information. The original recipient of this document may duplicate this
document in whole or in part for internal business purposes only, provided that this entire notice appears in all
copies. In duplicating any part of this document, the recipient agrees to make every reasonable effort to prevent
the unauthorised use and distribution of the proprietary information.
Page 1/164
LRM 2.6 © 2008-2019 Maia EDA
Contents
1 INTRODUCTION ............................................................................................................................................................. 8
1.1 PROGRAM STRUCTURE .................................................................................................................................................. 8
1.2 SYNTAX DEFINITION ................................................................................................................................................... 10
1.2.1 Regular expressions ........................................................................................................................................... 10
1.2.2 Line termination and whitespace ....................................................................................................................... 11
1.3 THE PREPROCESSOR .................................................................................................................................................... 11
2 LEXICAL CONVENTIONS........................................................................................................................................... 12
2.1 FILE STRUCTURE ......................................................................................................................................................... 12
2.2 CHARACTER SET ......................................................................................................................................................... 12
2.2.1 Line terminators and whitespace ....................................................................................................................... 12
2.3 COMMENTS ................................................................................................................................................................. 13
2.4 STATEMENT TERMINATION ......................................................................................................................................... 13
2.5 IDENTIFIERS ................................................................................................................................................................ 13
2.6 STRINGS ...................................................................................................................................................................... 14
2.6.1 Escape sequences ............................................................................................................................................... 14
2.7 CONSTANTS ................................................................................................................................................................ 15
2.7.1 Cinteger ............................................................................................................................................................. 15
2.7.2 Vinteger .............................................................................................................................................................. 16
2.7.3 Floating constants .............................................................................................................................................. 18
2.7.4 Boolean constants .............................................................................................................................................. 19
2.8 KEYWORDS ................................................................................................................................................................. 20
2.9 PREDEFINED IDENTIFIERS ........................................................................................................................................... 21
3 CONCEPTS...................................................................................................................................................................... 22
3.1 TYPE CHECKING .......................................................................................................................................................... 22
3.1.1 Implicit variables ............................................................................................................................................... 23
3.1.2 Function formal types ........................................................................................................................................ 23
3.1.3 Function return types ......................................................................................................................................... 24
3.1.4 Port size checking .............................................................................................................................................. 24
3.1.5 Boolean type....................................................................................................................................................... 25
3.2 DECLARATION ORDER................................................................................................................................................. 25
3.3 SCOPE ......................................................................................................................................................................... 26
3.4 NAMESPACES .............................................................................................................................................................. 27
3.5 STORAGE DURATION ................................................................................................................................................... 28
3.6 DEFAULT INITIALISATION ........................................................................................................................................... 28
3.7 TYPES ......................................................................................................................................................................... 28
3.7.1 Introduction ....................................................................................................................................................... 28
3.7.2 Assignment compatibility ................................................................................................................................... 30
3.7.3 ubit and uvar ...................................................................................................................................................... 31
3.7.4 ivar operations ................................................................................................................................................... 31
3.7.5 int ....................................................................................................................................................................... 33
3.7.6 bit ....................................................................................................................................................................... 33
3.7.7 var ...................................................................................................................................................................... 33
3.7.8 kmap ................................................................................................................................................................... 35
3.7.9 bool .................................................................................................................................................................... 37
3.7.10 struct .................................................................................................................................................................. 38
3.7.11 stream................................................................................................................................................................. 40
3.7.12 array................................................................................................................................................................... 49
4 OPERATORS AND EXPRESSIONS ............................................................................................................................ 52
4.1 INTRODUCTION ........................................................................................................................................................... 52
4.2 OPERATOR SYNTAX .................................................................................................................................................... 53
Page 2/164
LRM 2.6 © 2008-2019 Maia EDA
4.3 SIGNED OPERATORS .................................................................................................................................................... 53
4.4 EXPRESSION EVALUATION .......................................................................................................................................... 54
4.4.1 sub-expression evaluation .................................................................................................................................. 54
4.5 OPERATORS ................................................................................................................................................................ 55
4.5.1 Precedence and order of evaluation .................................................................................................................. 55
4.5.2 Operator equivalents ......................................................................................................................................... 56
4.5.3 Primary expressions ........................................................................................................................................... 56
4.5.4 Postfix operators ................................................................................................................................................ 57
4.5.5 Unary operators ................................................................................................................................................. 61
4.5.6 Cast operators .................................................................................................................................................... 62
4.5.7 Multiplicative operators ..................................................................................................................................... 62
4.5.8 Additive operators .............................................................................................................................................. 63
4.5.9 Shift and rotate operators .................................................................................................................................. 64
4.5.10 Relational operators .......................................................................................................................................... 64
4.5.11 Equality operators ............................................................................................................................................. 65
4.5.12 Bitwise AND operator ........................................................................................................................................ 66
4.5.13 Bitwise exclusive OR operator ........................................................................................................................... 66
4.5.14 Bitwise inclusive OR operator ........................................................................................................................... 66
4.5.15 Logical AND operator ....................................................................................................................................... 67
4.5.16 Logical OR operator .......................................................................................................................................... 67
4.5.17 Conditional operator ......................................................................................................................................... 67
4.5.18 Assignment operators ......................................................................................................................................... 68
4.5.19 Comma operator ................................................................................................................................................ 70
4.6 FLOATING-POINT OPERATORS AND EXPRESSIONS........................................................................................................ 70
4.6.1 Introduction ....................................................................................................................................................... 70
4.6.2 Declarations....................................................................................................................................................... 72
4.6.3 Operators ........................................................................................................................................................... 72
5 DECLARATIONS ........................................................................................................................................................... 75
5.1 INTRODUCTION ........................................................................................................................................................... 75
5.2 ARRAY DIMENSIONALITY ........................................................................................................................................... 75
5.3 INITIALISATION ........................................................................................................................................................... 76
5.4 INT, BIT, VAR, AND BOOL............................................................................................................................................. 78
5.5 STRUCT ....................................................................................................................................................................... 78
5.6 STREAM ...................................................................................................................................................................... 79
5.7 KMAP .......................................................................................................................................................................... 80
6 STATEMENTS ................................................................................................................................................................ 81
6.1 INTRODUCTION ........................................................................................................................................................... 81
6.2 COMPOUND STATEMENT ............................................................................................................................................. 82
6.3 EXPRESSION AND NULL STATEMENTS ......................................................................................................................... 82
6.4 SELECTION STATEMENTS ............................................................................................................................................ 82
6.4.1 The if statement .................................................................................................................................................. 83
6.4.2 The if-else statement .......................................................................................................................................... 83
6.4.3 The switch statement .......................................................................................................................................... 83
6.5 ITERATION STATEMENTS ............................................................................................................................................. 84
6.5.1 The while statement ............................................................................................................................................ 84
6.5.2 The do statement ................................................................................................................................................ 84
6.5.3 The for statement................................................................................................................................................ 84
6.5.4 The for all statement .......................................................................................................................................... 85
6.6 JUMP STATEMENTS ..................................................................................................................................................... 85
6.6.1 The continue statement....................................................................................................................................... 86
6.6.2 The break statement ........................................................................................................................................... 87
6.6.3 The return statement .......................................................................................................................................... 87
6.7 TRIGGER STATEMENT ................................................................................................................................................. 88
6.8 DRIVE STATEMENT ..................................................................................................................................................... 89
Page 3/164
LRM 2.6 © 2008-2019 Maia EDA
6.9 WAIT STATEMENT....................................................................................................................................................... 90
6.10 EXEC STATEMENT ....................................................................................................................................................... 90
6.11 EXIT STATEMENT ........................................................................................................................................................ 90
6.12 ASSERT STATEMENT ................................................................................................................................................... 90
6.13 REPORT STATEMENT ................................................................................................................................................... 91
6.13.1 Length modifiers ................................................................................................................................................ 92
6.13.2 Conversion specifiers ......................................................................................................................................... 92
6.13.3 fprintf compatibility ........................................................................................................................................... 94
7 FUNCTIONS .................................................................................................................................................................... 95
7.1 INTRODUCTION ........................................................................................................................................................... 95
7.2 SYNTAX ...................................................................................................................................................................... 95
7.3 PARAMETER PASSING SEMANTICS ............................................................................................................................... 96
7.4 FUNCTION SIGNATURES .............................................................................................................................................. 97
7.5 USER FUNCTIONS ........................................................................................................................................................ 97
7.6 THREAD FUNCTIONS ................................................................................................................................................... 98
7.7 TRIGGER FUNCTIONS .................................................................................................................................................. 98
7.8 INTER-FUNCTION COMMUNICATION ............................................................................................................................ 99
8 DUT SECTION .............................................................................................................................................................. 100
8.1 INTRODUCTION ......................................................................................................................................................... 100
8.2 MODULE DECLARATION ............................................................................................................................................ 101
8.2.1 Parameterised modules .................................................................................................................................... 102
8.2.2 Module declaration error checking ................................................................................................................. 103
8.2.3 Module input, output, and inout declarations .................................................................................................. 103
8.2.4 Syntax ............................................................................................................................................................... 104
8.3 DRIVE DECLARATION ................................................................................................................................................ 105
8.3.1 Syntax ............................................................................................................................................................... 105
8.3.2 Clocked and combinatorial drive declarations ................................................................................................ 106
8.3.3 Sequential and triggered drive declarations .................................................................................................... 106
8.3.4 Clocked drives .................................................................................................................................................. 107
8.3.5 Mixing clocked and combinatorial signals ...................................................................................................... 108
8.3.6 Combinatorial drives ....................................................................................................................................... 109
8.3.7 Sequential declaration signature ..................................................................................................................... 110
8.4 SIGNAL DECLARATION .............................................................................................................................................. 110
8.4.1 Syntax ............................................................................................................................................................... 111
8.5 CLOCK DECLARATION ............................................................................................................................................... 111
8.5.1 Syntax ............................................................................................................................................................... 111
8.5.2 Period declaration ........................................................................................................................................... 112
8.5.3 Waveform declaration ...................................................................................................................................... 112
8.5.4 Pipeline declaration ......................................................................................................................................... 113
8.5.5 Examples .......................................................................................................................................................... 114
8.6 ENABLE DECLARATION ............................................................................................................................................. 114
8.6.1 Syntax ............................................................................................................................................................... 114
8.6.2 Manual bidirectional control example ............................................................................................................. 115
8.6.3 Automatic bidirectional control example ......................................................................................................... 116
8.7 TIMESCALE DECLARATION ........................................................................................................................................ 116
8.8 TIME PRECISION AND REPRESENTATION .................................................................................................................... 117
8.8.1 Floating-point values in parameter lists .......................................................................................................... 117
8.9 TIMING CONSTRAINT DECLARATION ......................................................................................................................... 118
8.9.1 Syntax ............................................................................................................................................................... 118
8.9.2 Input constraint definition ................................................................................................................................ 119
8.9.3 Output constraint definition ............................................................................................................................. 120
8.9.4 Input setup and hold constraints ...................................................................................................................... 120
8.9.5 Output hold and delay constraints ................................................................................................................... 121
8.9.6 Wildcard constraints ........................................................................................................................................ 122
Page 4/164
LRM 2.6 © 2008-2019 Maia EDA
8.9.7 Constraint conflicts .......................................................................................................................................... 123
9 DRIVE STATEMENT .................................................................................................................................................. 126
9.1 INTRODUCTION ......................................................................................................................................................... 126
9.2 STATEMENT FORMAT ................................................................................................................................................ 127
9.2.1 Drive statements with both input and output expressions ................................................................................ 127
9.2.2 Input-only drive statements .............................................................................................................................. 127
9.2.3 Output-only drive statements ........................................................................................................................... 128
9.2.4 Pipelined drive statements ............................................................................................................................... 128
9.3 DRIVE DIRECTIVES .................................................................................................................................................... 130
9.3.1 .C ...................................................................................................................................................................... 130
9.3.2 .X and .Z ........................................................................................................................................................... 130
9.3.3 .R ...................................................................................................................................................................... 130
9.3.4 Don't care conditions ....................................................................................................................................... 131
9.4 LABELLED DRIVE STATEMENTS ................................................................................................................................ 131
10 SCHEDULING MODEL........................................................................................................................................... 132
10.1 INTRODUCTION ......................................................................................................................................................... 132
10.2 THREADS .................................................................................................................................................................. 132
10.3 PROGRAM TERMINATION .......................................................................................................................................... 133
10.4 ADVANCING TIME ..................................................................................................................................................... 133
10.5 THREAD FUNCTIONS ................................................................................................................................................. 133
10.6 HDL SIGNAL DRIVERS .............................................................................................................................................. 134
10.7 OPERATING POINT..................................................................................................................................................... 135
10.7.1 Clocked drive statements ................................................................................................................................. 135
10.7.2 Combinatorial drive statements ....................................................................................................................... 136
10.7.3 DUT output testing ........................................................................................................................................... 136
10.8 SEQUENTIAL COMBINATORIAL DRIVE STATEMENTS .................................................................................................. 136
10.9 SEQUENTIAL CLOCKED DRIVE STATEMENTS ............................................................................................................. 136
10.10 TRIGGERED DRIVE STATEMENTS ........................................................................................................................... 137
10.11 MANUAL DUT TESTING AT THE OPERATING POINT ............................................................................................... 137
10.11.1 Input driving................................................................................................................................................. 138
10.11.2 Output testing ............................................................................................................................................... 138
10.11.3 Summary of manual testing requirements .................................................................................................... 139
11 RUN-TIME ERROR CHECKING .......................................................................................................................... 140
11.1 ARRAY INDEXING ERRORS ........................................................................................................................................ 140
11.2 BITSLICE INDEXING ERRORS ..................................................................................................................................... 140
11.3 CHECKER PIPELINE SIZE ERRORS .............................................................................................................................. 140
11.4 CHECKER PIPELINE OVER-WRITE ERRORS ................................................................................................................. 140
11.5 TRIGGER OVER-RUN.................................................................................................................................................. 140
11.6 LAST VALUE PIPELINE ERRORS.................................................................................................................................. 141
12 PREPROCESSOR ..................................................................................................................................................... 142
12.1 INTRODUCTION ......................................................................................................................................................... 142
12.2 PREPROCESSOR TRANSLATION PHASES ..................................................................................................................... 143
12.2.1 Trigraph replacement ...................................................................................................................................... 143
12.2.2 Digraph replacement ....................................................................................................................................... 144
12.2.3 Line terminator conversion .............................................................................................................................. 144
12.2.4 Whitespace conversion ..................................................................................................................................... 144
12.2.5 UTF-8 validation ............................................................................................................................................. 145
12.2.6 Line continuation ............................................................................................................................................. 145
12.2.7 String preservation........................................................................................................................................... 145
12.2.8 Comments......................................................................................................................................................... 146
12.2.9 Whitespace compression .................................................................................................................................. 146
12.2.10 Directive processing .................................................................................................................................... 146
Page 5/164
LRM 2.6 © 2008-2019 Maia EDA
12.2.11 Macro expansion .......................................................................................................................................... 146
12.3 PREPROCESSOR DIRECTIVES...................................................................................................................................... 146
12.3.1 Conditional inclusion directives ...................................................................................................................... 147
12.3.2 include directives ............................................................................................................................................. 149
12.3.3 Line directives .................................................................................................................................................. 150
12.3.4 Warning and error directives ........................................................................................................................... 150
12.3.5 define directives ............................................................................................................................................... 150
12.3.6 undef directive .................................................................................................................................................. 152
12.4 MACRO EXPANSION .................................................................................................................................................. 152
12.4.1 Self-referential macros ..................................................................................................................................... 152
12.4.2 Object-like macro expansion ........................................................................................................................... 153
12.4.3 Function-like macro expansion ........................................................................................................................ 153
12.5 TOKENISATION ......................................................................................................................................................... 155
12.5.1 Preprocessor Identifiers ................................................................................................................................... 156
12.5.2 constant expression evaluation ........................................................................................................................ 156
12.6 PREDEFINED MACRO NAMES ..................................................................................................................................... 156
12.7 PRAGMA DIRECTIVES ................................................................................................................................................ 157
13 GLOSSARY ............................................................................................................................................................... 158
Page 6/164
LRM 2.6 © 2008-2019 Maia EDA
TABLE 1: SIMPLE ESCAPE SEQUENCES ........................................................................................................................................ 14
TABLE 2: KEYWORDS 1 .............................................................................................................................................................. 20
TABLE 3: KEYWORDS 2 .............................................................................................................................................................. 20
TABLE 4: KEYWORDS 3 .............................................................................................................................................................. 20
TABLE 5: RESERVED WORDS....................................................................................................................................................... 20
TABLE 6: ADDITIONAL LEXER TOKENS ....................................................................................................................................... 21
TABLE 7: PREDEFINED VARIABLE NAMES ................................................................................................................................... 21
TABLE 8: LEVEL-SPECIFIC CHECKING.......................................................................................................................................... 23
TABLE 9: 4-STATE LOGIC OPERATIONS........................................................................................................................................ 35
TABLE 10: KMAP OPERATORS ..................................................................................................................................................... 37
TABLE 11: BOOLEAN OPERATORS ............................................................................................................................................... 38
TABLE 12: STRUCTURE OPERATORS ............................................................................................................................................ 40
TABLE 13: MODE 1 STREAM OPERATORS ..................................................................................................................................... 46
TABLE 14: MODE 2 STREAM OPERATORS ..................................................................................................................................... 48
TABLE 15: ARRAY OPERATORS ................................................................................................................................................... 51
TABLE 16: PRECEDENCE AND ASSOCIATIVITY OF OPERATORS..................................................................................................... 55
TABLE 17: OPERATOR EQUIVALENTS .......................................................................................................................................... 56
TABLE 18: SINGLE-PRECISION REAL OPERATORS ........................................................................................................................ 73
TABLE 19: DOUBLE-PRECISION REAL OPERATORS ....................................................................................................................... 73
TABLE 20: DOUBLE EXTENDED PRECISION REAL OPERATORS...................................................................................................... 74
TABLE 21: TRIGRAPHS .............................................................................................................................................................. 143
TABLE 22: DIGRAPHS ................................................................................................................................................................ 144
TABLE 23: LINE TERMINATORS ................................................................................................................................................. 144
TABLE 24: WHITESPACE ............................................................................................................................................................ 145
TABLE 25: MPL OPERATORS .................................................................................................................................................... 149
TABLE 26: PREDEFINED MACRO NAMES .................................................................................................................................... 157
TABLE 27: MTV ENVIRONMENT VARIABLES .............................................................................................................................. 161
TABLE 28: RTV ENVIRONMENT VARIABLES ............................................................................................................................... 161
Page 7/164
LRM 2.6 © 2008-2019 Maia EDA
1 INTRODUCTION
The purpose of a Maia program is to apply stimulus to an HDL module, to read data back from that
module, and to determine whether or not that data has the expected value. The HDL module ('Device
Under Test', or DUT) is not part of the Maia program, and is written in a language such as VHDL or
Verilog. Maia generates a testbench for the DUT; the generated testbench must then be executed on
an HDL simulator.
Maia requires a DUT definition in order to communicate with the HDL code. This definition is the DUT
Section, which is described in chapter 8.
Maia communicates with the DUT by using drive statements, or 'test vectors', which are described in
chapter 9, or by direct access to DUT signals. Drive statements automate the process of applying timed
stimulus to the DUT, and checking the DUT outputs.
A drive statement evaluates a set of expressions which are used to drive the DUT inputs, and compares
the DUT outputs against another set of expressions. Drive statements may be executed within various
control flow constructs, to allow the creation of reactive testbenches. These facilities are provided by a
simple imperative control language, which is described in chapters 2 through 7. These facilities are
similar, and in many cases identical, to those provided by C and related languages.
In the second form (procedural-program), a program is composed of at least one function (the
program entry point, which must be named main). There may optionally be a single DUT definition,
and additional functions and declarations. In this form, the drive statements are executed as part of the
normal program flow, inside a function.
The two examples below are complete examples of a testbench for a two-bit counter with a
synchronous reset, coded in these two styles. The first is a test vector program, and tests the DUT by
applying directives and constants to the DUT inputs, and comparing the DUT outputs against the
expected values:
DUT {
module counter(input CLK, RST; output [1:0] Q)
create_clock CLK
[CLK, RST] -> [Q]
}
[.C, 1] -> [0] // sync reset
[.C, 0] -> [1]
[.C, 0] -> [2]
[.C, 0] -> [3]
[.C, 0] -> [0] // roll-over
Example 1
Page 8/164
LRM 2.6 © 2008-2019 Maia EDA
The second example is the procedural equivalent of the test vector program. In this form, the DUT may
be driven with and tested against arbitrary expressions, and drive statements can be enclosed in
control and looping constructs:
DUT {
module counter(input CLK, RST; output [1:0] Q)
create_clock CLK
[CLK, RST] -> [Q]
}
int main() {
[.C, 1] -> [0]; // sync reset
Both programs execute 5 drive statements, and produce a log file entry stating that all 5 vectors have
passed (assuming, of course, that the counter module has been correctly implemented):
(Log) (50 ns) 5 vectors executed (5 passes, 0 fails)
Syntax
maia-program :
testvector-program
procedural-program
testvector-program : tp-section-list
tp-section-list :
tp-section
tp-section-list tp-section
tp-section :
DUT-definition
labelopt vfile-drive-statement semicolonopt
semicolon : ;
procedural-program : external-declaration-list
external-declaration-list:
external-declaration
external-declaration-list external-declaration
external-declaration :
DUT-definition
function-definition
declaration
Page 9/164
LRM 2.6 © 2008-2019 Maia EDA
1.2 Syntax definition
The language grammar is presented in a simplified form throughout the text. The grammar is not
exhaustive, and is not sufficient to construct a parser. Its purpose is merely to illustrate correct syntax,
where the grammar is more concise or more complete than a textual description.
Terminals which are presented in a typewriter style should be entered literally. Note that terminals
are not necessarily keywords (2.8); the ones that are not keywords may also appear as user-defined
names, if they have the appropriate form for a name.
A number of the base operators (those shaded in Table 16) may optionally be signed and sized (4.2),
or may have an alternative textual name (4.5.2). These alternatives are not listed in the grammar. In
this production, for example:
shift-expression :
...
shift-expression >> additive-expression
The $n form is used to represent any valid operator size. n must be greater than 0, and less than or
equal to a compiler-determined maximum, which is at least 224.
bitn and varn represent a bit or var type mark which is optionally sized. If n is present, it must be
greater than 0, and less than or equal to a compiler-determined maximum, which is at least 224.
A string is therefore composed of a double quote character, followed by zero or more characters which
are not a double quote or a newline, followed by a second double-quote character.
A regular expression may also refer to a production, which is enclosed in braces { and }:
macro-name-lparen :: {pp-identifier}(
Page 10/164
LRM 2.6 © 2008-2019 Maia EDA
1.2.2 Line termination and whitespace
The language allows a number of UTF-8 code points to represent line terminators and whitespace.
However, the preprocessor converts all line terminators to \n (LF, U+000A)1, and all whitespace (with
the exception of HT) to a space character (SP, U+0020)2. On completion of preprocessing, all line
terminator and whitespace code points will appear as either LF (U+000A), SP (U+0020), or HT
(U+0009). Any reference to \n, "newline", or "whitespace", outside the context of the preprocessor (in
other words, any reference outside chapter 12), refers to the preprocessor output.
The preprocessor defines a Macro Processing Language, or MPL. The MPL provides a number of
facilities, including the creation of macros with the #define directive (which allows an identifier to be
replaced by an arbitrary sequence of characters), and the inclusion of a source file inside another
source file, with the #include directive.
The operation of the preprocessor is logically distinct from the operation of the compiler, and is
described in chapter 12. The preprocessor grammar is presented in the same form as the compiler
grammar (1.2), but the two grammars are distinct. The grammars have a common definition of
identifiers and constants, but do not otherwise reference each other. The use of the preprocessor is
optional (12.1). However, the preprocessor provides a number of facilities beyond the MPL itself
(including the checking of UTF-8 input), and the compiler is unlikely to be able to translate programs
which have not been through the preprocessing stage.
1
LF (Linefeed) is also variously known as "newline" and "NL". \n is an escape sequence which represents LF; see 2.6.1.
2
See 12.2.3 and 12.2.4.
Page 11/164
LRM 2.6 © 2008-2019 Maia EDA
2 LEXICAL CONVENTIONS
All invalid byte sequences are rejected as errors, with the exception that the two-byte sequence 0xC0,
0x80 is treated as a null character (U+0000)3. In particular, CESU-8 encodings are not supported.
Maia is a "free-form" language, in the sense that line terminators and whitespace in the source are not
generally significant, and are normally present simply for readability. A number of exceptions are listed
below (the ‘for all’ keyword, for example, cannot be spelt as ‘forall’). Another exception occurs
when two adjacent textual tokens must be parsed as separate identifiers or keywords. In this case,
they must be separated by line terminators or whitespace:
// function 'foo':
real2 foo(real2 x) { return x + / 2; }
1
UTF-8 is a Unicode multibyte character encoding. Characters which are not part of the ASCII subset (U+0000 through
U+007F) are represented by a multi-byte sequence, with a maximum length of 4 bytes. UTF-8 is backwards-compatible with
ASCII, and any source file which is valid ASCII is also valid UTF-8.
2
Some Windows programs may add the three-byte sequence 0xEF, 0xBB, 0xBF to the start of any file saved as UTF-8. This
is the UTF-8 encoding of the Unicode byte order mark, although byte order is not relevant to UTF-8.
3
This exception is generally known as ‘modified UTF-8’.
Page 12/164
LRM 2.6 © 2008-2019 Maia EDA
2.3 Comments
The // characters introduce a line comment. The compiler ignores everything after these characters,
up to the end of the current line.
Comments may also be introduced by the /* characters, and terminated by the */ characters. This
second form has the advantage that the comment may be spread over multiple lines, and is known as a
block comment. These comments do not nest, and cannot be inserted within strings.
Some examples of comments are:
bit128 foo; // this is a line comment
/* this is a
* multi-line block comment */
Within a function, braces { and } are used to group declarations and statements into a compound
statement, which is syntactically equivalent to a single statement. There is no terminating semicolon
after the closing brace of a compound statement.
Statements within a DUT section may optionally be terminated by a semicolon, if desired. External drive
statements in a testvector-program may similarly be terminated with a semicolon, if desired; the
termination is not required in either case.
2.5 Identifiers
Identifiers may contain a set of alphabetic characters. The alphabetic characters are defined as a
through z, A through Z, and all multibyte UTF-8 characters, with the exception of the multibyte line
terminators (12.2.3), and the multibyte whitespace characters (12.2.4). The alphabetic characters are
defined below as ident-alpha.
Legal identifiers consist of a combination of the alphabetic characters, the decimal digits 0 to 9, and
underscore (_, U+005F). The first character may not be a decimal digit.
User-defined identifiers must start with an alphabetic character, and should not be the same as a
keyword. All identifiers which start with an underscore are reserved. Some of these reserved names
may be legally used, and have predefined meanings, which are documented in 2.9 below. Identifiers
may contain any number of characters up to a compiler-determined maximum, which is at least 212.
Syntax
identifier ::
[{ident-alpha}_][{ident_alpha}_0-9]*
ident-alpha ::
[U+0061-U+007A] | [U+0041-U+005A] |
[U+0080-U+0084] | [U+0086-U+2027] | [U+202A-U+10FFFF]
Page 13/164
LRM 2.6 © 2008-2019 Maia EDA
2.6 Strings
Strings are arbitrary character sequences which are enclosed in double quotation marks ("). Strings
cannot be continued onto a new line; it is an error if a single line of input contains an unterminated
string. Within a function, adjacent strings are automatically concatenated, even across line boundaries.
However, adjacent strings are never concatenated within a DUT section.
A string does not have a value, and may not be manipulated in an expression.
The simple escape sequences are listed in Table 1 below. An octal escape sequence is composed of a
maximal-length sequence of octal digits (1, 2, or 3 digits), while a hexadecimal escape sequence is
composed of a maximal-length sequence of hexadecimal digits (1 or 2 digits).
Newline LF (NL) \n audible alert BEL \a
Horizontal tab HT \t backslash \ \\
vertical tab VT \v question mark ? \?
Backspace BS \b single quote ' \'
carriage return CR \r double quote " \"
Formfeed FF \f
Syntax
string ::
"[^"\n]*"
Page 14/164
LRM 2.6 © 2008-2019 Maia EDA
2.7 Constants
Constants may represent integer, floating-point, or boolean values.
Integer constants can be specified as either a Cinteger, or a Vinteger. the Cinteger is based on C
integer constants, while the Vinteger is based on Verilog integer constants. Cintegers are unsized 2-
value integers (in other words, each bit can take on one of only 2 values; 0 or 1). The Vinteger is an
optionally-sized 4-value integer (each bit can take on one of the 4 values 0, 1, X, or Z).
A leading minus sign, if it is present, is not part of the constant; it is instead interpreted as a unary
negation operator.
Syntax
constant :
cinteger-constant
vinteger-constant
floating-constant
boolean-constant
2.7.1 Cinteger
Cintegers are specified in a C-like form, with the addition that 0b and 0B prefixes may be used to
specify binary data. A leading 0x or 0X specifies hexadecimal, while a leading zero otherwise specifies
octal. Underscores may also be inserted arbitrarily into the constant to improve readability, although
not as the first character, and not inside the base specifier.
Syntax
cinteger-binary :: 0[bB][01_]+
cinteger-octal :: 0[0-7_]*
cinteger-decimal :: [1-9][0-9_]*
cinteger-hexadecimal :: 0[xX][a-fA-F0-9_]+
cinteger-constant :
cinteger-binary
cingteger-octal
cinteger-decimal
cinteger-hexadecimal
In other words, a Cinteger may be one of:
1
ISO/IEC 9899:1999 (E), §6.4.4.2
Page 15/164
LRM 2.6 © 2008-2019 Maia EDA
1. A binary integer, which is prefixed by either 0b or 0B, and which is followed by one or more
characters in the range 0 to 1;
2. An octal integer, which starts with 0, optionally followed by one or more characters in the
range 0 to 7;
3. A decimal integer, which starts with a character in the range 1 to 9, optionally followed by
one or more characters in the range 0 to 9;
4. A hexadecimal integer, which is prefixed by either 0x or 0X, followed by one or more case-
insensitive characters in the range 0 to 9, or A to F.
These constants may not include metadata (unknown and high-impedance bits), and are scanned to
the number of bits set by the _DefaultWordSize pragma (which defaults to 32). An overflow is
reported if the constant cannot be represented in this many bits.
2.7.2 Vinteger
Vintegers are specified in a Verilog-like form. This allows metadata to be entered, and also allows
constants of arbitrary width to be specified.
Syntax
size-prefix :: [0-9]*['`]
vh-digit :: [xXzZ?0-9a-fA-F]
vb-base :: [bB]
vo-base :: [oO]
vd-base :: [dD]
vh-base :: [hH]
vinteger-constant ::
{size-prefix}({vb-base}|{vo-base}|{vd-base}|{vh-base}){vh-digit}({vh-digit}|_)*
vh_digit is shown as including the full case-insensitive hex character set, for simplicity. The
characters used must, however, be valid for the specified base. The vh_digit may also be specified
(in any base) as x or X for an unknown value, or z, Z, or ? for a high-impedance value; these 5
characters are the 'metavalues'.
A metavalue character specifies 1 bit for the binary base, 3 bits for octal, and 4 bits for hex. The
integer 4'hx, for example, is equivalent to 4'bxxxx. Metavalues are illegal in decimal numbers, unless
the entire integer is composed of a single metavalue. In this case, every bit of the integer is set to the
metavalue. The integer 32'dx, for example, contains 32 unknown bits.
Vintegers must not contain any whitespace; the entire constant is one token.
These integers are essentially identical to Verilog "based constants". There are, however, a number of
differences between Vintegers and Verilog based constants:
there must be no whitespace anywhere in the constant. "5 'd 4" is interpeted in Verilog as
0b00100, but is illegal in Maia; it should instead be specified as "5'd4"
the 's' designator is illegal (as in Verilog-1995)
if a size prefix is present and the constant cannot be represented in the specified size, an
overflow error is reported (overflow is not an error in Verilog)
if a size prefix is not present, the constant is scanned to the number of bits specified by
_DefaultWordSize, and an overflow error is reported if it cannot be represented in this
many bits
if there is not enough data in the constant to fill the specified number of bits, then the data is
always padded with 0 bits to the left; x and z padding is never used. The one exception is
the case of the single-metavalue decimal Vinteger, as noted above.
Some examples of Vinteger constants are:
#pragma _DefaultWordSize 24 // scan unsized integer constants to 24 bits
var5 i; // a 5-bit four-state variable
i = 'b1010; // 24-bit decimal 10, truncated to 5 bits on assignment
i = 4'B00_1010; // 4-bit decimal 10, no overflow
i = 4'B01_1010; // cannot scan to 4 bits; overflow error
i = 'o12; // 24-bit decimal 10, truncated on assignment
i = 'd10; // 24-bit decimal 10, truncated on assignment
i = 4'D10; // 4-bit decimal 10, 0-extended on assignment
i =# 4'D10; // 4-bit decimal 10, sign-extended to 5'b11010 on assignment
i = 'dz; // 24 z bits, truncated to 5 bits on assignment
i = 'h1x; // i == 5'b1xxxx
i = 4'h1x; // overflow error
Example 6
1
The apostrophe character is used for Verilog compatibility. However, this character may cause difficulties with tools for C-like
languages (such as editors), and the grave accent (or ‘back-tick’) may be used as an alternative.
Page 17/164
LRM 2.6 © 2008-2019 Maia EDA
2.7.3 Floating constants
Maia floating constants are lexically identical to C99 floating constants. A float constant is composed of
a significand part, followed by an optional exponent part, and an optional suffix. The suffix specifies the
precision of the constant; it may be either f or F for single-precision, or l or L for extended double-
precision. The constant is double-precision if the suffix is omitted.
The Verilog code generator supports only double-precision constants (14.7.1). However, any
expressions which can be statically evaluated by the compiler may use any, or all, of the floating-point
precisions.
For a hexadecimal constant, the exponent is interpreted as a decimal number, which specifies the
power of two by which the significand is scaled. For a decimal constant, the exponent is interpreted as
a decimal number, which specifies the power of ten by which the significand is scaled.
The components of the significand part may include a digit sequence representing the whole-number
part, followed by a period (.), followed by a digit sequence representing the fraction part. At least one
of the whole-number part and the fraction part must be present.
The components of the exponent part are an e or E (for a decimal constant), or p or P (for a
hexadecimal constant), followed by an exponent consisting of an optionally signed decimal digit
sequence.
For decimal floating constants, either the period or the exponent part has to be present. The exponent
is always required for hexadecimal floating constants.
Syntax
floating-constant :
dec-floating-constant
hex-floating-constant
dec-floating-constant :
dec-fractional-constant exponent10-partopt floating-suffixopt
dec-digit-sequence exponent10-part floating-suffixopt
hex-floating-constant :
hex-prefix hex-fractional-constant exponent2-part floating-suffixopt
hex-prefix hex-digit-sequence exponent2-part floating-suffixopt
dec-fractional-constant :
dec-digit-sequenceopt . dec-digit-sequence
dec-digit-sequence .
hex-fractional-constant :
hex-digit-sequenceopt . hex-digit-sequence
hex-digit-sequence .
exponent10-part :
e signopt dec-digit-sequence
Page 18/164
LRM 2.6 © 2008-2019 Maia EDA
E signopt dec-digit-sequence
exponent2-part :
p signopt dec-digit-sequence
P signopt dec-digit-sequence
dec-digit-sequence :
dec-digit
dec-digit-sequence digit
hex-digit-sequence :
hex-digit
hex-digit-sequence hex-digit
sign :: + | -
hex-prefix :: 0x | 0X
floating-suffix :: f | F | l | L
Examples
Some examples of floating-point constants are given below; the comments include the report statement
output. These are all double-precision constants. Single- and double-extended constants are specified
identically, but with a trailing case-insensitive F or L, respectively.
report("'%5.2f'\n", 3.14159); // ' 3.14'
report("'%5.2f'\n", 3.14159E0); // ' 3.14'
report("'%5.2f'\n", 31.4159e-1); // ' 3.14'
report("'%5.2f'\n", .314159e+1); // ' 3.14'
report("'%5.2f'\n", 1.); // ' 1.00'
report("'%5.2f'\n", 1e0); // ' 1.00'
report("'%5.2f'\n", .1E1); // ' 1.00'
report("'%5.2f'\n", .5); // ' 0.50'
report("'%5.2f'\n", 0x0.8p0); // ' 0.50' (0000.1000)
report("%5.2f'\n", 0x0.8p1); // ' 1.00' (ie. 0000.1000 x 2^1)
report("%5.2f'\n", 0x0.4p2); // ' 1.00' (ie. 0000.0100 x 2^2)
report("%5.2f'\n", 0x0.3p+4); // ' 3.00' (ie. 0000.0011 x 2^4)
Example 7
Note that care must be taken when displaying a boolean value with report. The %d and %i
conversion specifications treat their argument as a signed integer quantity, and the displayed output
may not be as expected1. Booleans should be printed with an unsigned conversion (%b, %o, %u, %x, or
%X), or with the boolean specification (%l). %l produces the output false if the corresponding
expression is false, and true otherwise.
1
Most Verilog simulators will display a signed 1'b1 as '-1' , although at least one displays it as '0'.
Page 19/164
LRM 2.6 © 2008-2019 Maia EDA
Syntax
boolean-constant :: true | false
2.8 Keywords
The language keywords are listed in the tables below. These keywords may not be used as identifiers.
The tables are separated for convenience; all the keywords are reserved in all parts of a program. Note
that var[0-9]*, kmap[0-9]*, and bit[0-9]* are regular expressions, and not literal tokens. In
other words, var itself is a keyword, and any token which is composed of var immediately followed by
one or more decimal integers is also a keyword. The tokens of multi-word keywords (when all and
for all) should be separated by whitespace.
Table 2 lists the DUT-related keywords. Note that name, period, pipeline, and waveform are
listed, but have no significance unless they are immediately preceded by a hyphen character.
create_clock create_enable DUT inout input
macromodule module -name negedge output
-period -pipeline posedge signal timescale
-waveform
Table 2: Keywords 1
Table 3: Keywords 2
Table 4 lists the remaining keywords. true and false are listed as keywords for simplicity, although
they are technically boolean literals.
and assert break case continue
default do else exec exit
false for for all if or
report return static switch trigger
true void wait when when all
while
Table 4: Keywords 3
Table 5 list tokens which are reserved for future use as keywords; they may not be used as identifiers.
new delete enum function typedef
ref in out goto fork
join
Table 5: Reserved words
Page 20/164
LRM 2.6 © 2008-2019 Maia EDA
Table 6 lists multi-character tokens which must not include whitespace. There are also a number of
multi-character operator tokens (*=, for example) which may not include whitespace; these are listed
in Table 16. The meta, msb, size and offset operators must be immediately preceded by either an
apostrophe character, or a grave accent (backtick) character, with no intervening whitespace; the table
lists only the apostrophe version, for simplicity.
@( :: -> 'meta 'msb
'size 'offset 'last
Page 21/164
LRM 2.6 © 2008-2019 Maia EDA
3 CONCEPTS
The level of static checking which is carried out is determined by the user, by setting the
_StrictChecking pragma. This has three possible values (0, 1, and 2), where the level corresponds
to the level of 'strictness' of the checking. Level 0 defines a minimal level of 'weak' checking, which is
generally associated with scripting languages. The default level is 1, which gives a level of checking
which is approximately equivalent to that found in C and similar languages. A program which compiles
without error at a given checking level is guaranteed to compile without error at any lower level.
It is generally possible to write more compact (and possibly more understandable) code with level 0.
However, this disables a number of checks which might otherwise identify erroneous code, and should
generally be considered to be suitable only for simple, and relatively short, tests.
The type checking level can be set only with the _StrictChecking and _Implicits pragmas.
These pragmas are program-wide, and should appear once in the source code, before any functions are
analysed. There are no corresponding compiler switches to set the level. The intention is to ensure that
a given program will always compile with the same level of checking, irrespective of the manner in
which it is compiled.
The level-specific checks and features are listed in Table 8 below, together with the corresponding
_StrictChecking level:
Page 22/164
LRM 2.6 © 2008-2019 Maia EDA
Level: 0 1 2
Implicit variables (3.1.1) Y N N
Default type for function formal (3.1.2) Y N N
Default type for function return (3.1.3) Y Y N
Port size checking (3.1.4) N Y Y
Extended boolean checking (3.1.5) N N Y
Separate boolean type (3.1.5) N N Y
Implicit variables can be enabled or disabled independently of the checking level by using the
_Implicits pragma. If this pragma is used, the requested action takes precedence over the action
implied from the _StrictChecking level. #pragma _Implicits 0 disables the use of implicits,
while #pragma _Implicits 1 enables implicits.
Implicit variables which are created in a function have function scope, rather than block scope. The
scope of the variable starts at the point at which it is first assigned to, and ends at the end of the
associated function.
1
If implicits are enabled, creation of a var object creates an object with a size given by _DefaultWordSize. When implicits
are not enabled, however, var is simply a synonym for var1, and an object declared as a var is a 1-bit object.
Page 23/164
LRM 2.6 © 2008-2019 Maia EDA
3.1.3 Function return types
If a function is declared with no type specifier in levels 0 and 1, a return type of uvar (an
unconstrained var) will be assumed. A syntax error is reported at any higher level.
1. When the checking level is 0, objects may be extended or truncated as required, with no
restrictions;
2. When the checking level is greater than 0, extension and truncation are disabled and will result
in a syntax error, with the exceptions noted in 3.1.4.1 below;
3. The exceptions of 3.1.4.1 apply only when using a drive statement. If the DUT ports are driven
or read directly then they do not apply.
A bitslice of an object has the same size as that object, and this can complicate the use of
bitslices to drive DUT ports. A port may therefore be driven directly if the slice has constant
indexes, and the slice width is the same as the port width.
Various examples of valid and invalid drive statements are shown in Example 10 below.
DUT {
module adder(input[15:0] A, B, output[15:0] C);
[A,B] -> [C];
}
main() {
bit32 in1 = 10;
bit16 in2 = 6;
int x=30, y=0;
Level 2 introduces a separate boolean type. In level 2, an arithmetic object may still be used wherever
a boolean is required by the syntax, and the object will be implicitly converted to a boolean. However,
booleans undergo some additional type checking; two boolean objects may not be added by using the
+ operator, for example.
while(true) // Ok for all levels; true and false are not level-specific
...
if(foo()) // Ok for all levels
...
if(foo() != 0) // Ok for all levels
...
The specific rules are listed below; some of these are simply a reformulation of the scope requirements
of (3.3).
1. There is no requirement for functions to be declared; a function's definition also serves as its
declaration
2. There is no requirement that a function definition should precede any use of that function in
the source code. The functions which make up a program may therefore appear in the source
code in any order
3. The DUT definition is treated in the same way as a function, and may appear anywhere that a
function may appear
4. There is no requirement that external variable declarations should precede any use of that
variable in the source code
5. There is no requirement that external type declarations should precede any use of that type in
the source code, with the exception noted in (6) below
Page 25/164
LRM 2.6 © 2008-2019 Maia EDA
6. If an external structure declaration contains a member which is of a user-defined type (a
stream or another structure), then that type must have already been analysed
7. Automatic and static variables in a function must be declared before they are used. If,
however, implicit variables are enabled, and the compiler encounters a write operation to an
unknown variable, then it will implicitly declare it to be of type var
8. local type declarations (for structures and streams) in a function must precede any use of that
type in the function
9. In a function, declarations may appear at any location; it is not necessary for declarations to
appear at the beginning of the function.
DUT {} // 3: the DUT defn may appear anywhere where a function may appear
struct s1 { int x, y; };
struct s2 {
struct s1 a; // 6: the declaration of 's1' must appear before its use here
} b; // 4: the declaration of 'b' may appear after its use in 'main'
Example 12
3.3 Scope
An identifier can denote an object, a function, a tag or a member of a structure or stream, a label
name, a macro name, or a macro parameter. Macro names and parameters are expanded before
translation, and so are not considered further here. The same identifier can denote different entities at
different points in the source code.
An identifier that denotes a given entity is visible only within a specific region of the source code,
known as its scope. The various entities denoted by an identifier have different scopes, or are in
different namespaces. There are three kinds of scope: function, block, and global.
Any identifier which is a function name or is declared outside a function has global scope. These
identifiers include DUT port and signal names, and drive declaration label names (8.3.7). These
identifiers are visible throughout the source code that makes up the program, with one exception. This
Page 26/164
LRM 2.6 © 2008-2019 Maia EDA
exception occurs when a structure or stream tag is used in another structure (in other words, when the
second structure includes an instance of the first structure or stream). In this case, the first tag is
considered to have a scope that starts at its introduction for the purposes of inclusion in any other
structure; it is visible throughout the entire source code for any other purposes (this is the exclusion
listed in item (6) of section 3.2).
Every other identifier is introduced in a function parameter list, or within a function. The identifier has
function scope if it introduces a new implicit variable (3.1.1), and otherwise has block scope. The scope
of an implicit starts at the point at which it is first assigned to, and ends at the end of the associated
function. The scope otherwise starts at the point of introduction and terminates at the end of the
associated block.
An identifier at a given scope level (the outer scope) may be hidden by the same identifier in an
enclosed scope (the inner scope). An entity with global scope may always be accessed by using the
global scope operator1, ::. The example below shows a number of cases where an outer scope
identifier is hidden, and prints '4321':
int i = 1; // global scope
main() { // block scope level 1
int i = 2;
do { // block scope level 2
int i = 3;
{ // block scope level 3
int i = 4;
report("%d", i); // 4
}
report("%d", i); // 3
} while(false);
report("%d", i); // 2
report("%d\n", ::i); // 1 (global scope operator)
}
Example 13
3.4 Namespaces
Under some circumstances, an identifier may potentially refer to more than one entity at a given point
in the source code. This is possible where the surrounding syntactic context allows the use of the
identifier to be disambiguated, and is formalised in the concept of a namespace. A namespace is
therefore a context for an identifier. The possible namespaces are:
drive statement label names, which are disambiguated by the syntax of their declaration and use;
the tags of structures and streams, which are disambiguated by following the keywords struct
or stream. The tags of structures and streams share the same namespace;
the members of structures of streams, which are disambiguated by following the . operator. Each
structure and stream creates its own namespace;
1
:: is not, strictly speaking, an operator, although it may be considered to be a prefix operator in most circumstances.
Page 27/164
LRM 2.6 © 2008-2019 Maia EDA
3.5 Storage duration
An object has a storage duration that determines its lifetime. There are two storage durations: static,
and automatic. The lifetime of an object is the portion of program execution during which storage is
guaranteed to be reserved for it. An object exists, and retains its last-stored value, throughout its
lifetime.
An external object, or one declared with the static modifier, has static storage duration. Its lifetime is
the entire execution of the program and its stored value is initialized only once, prior to program
startup. If no initialization is specified for the object, a default initialisation (3.6) is carried out.
Any other object has automatic storage duration. The lifetime of such an object extends from the point
of its declaration, until the enclosing scope terminates. If an initialisation is specified for the object, it is
performed each time the declaration is reached in the execution of the function; otherwise, a default
initialisation (3.6) is carried out each time the declaration is reached.
Stream objects are always automatically initialised; the "default" initialisation can be considered to be
the initial value of the stream.
All objects are either data, boolean, or stream objects, or an aggregate of these basic objects. An
aggregate is default-initialised by initialising all basic objects within the aggregate. If a basic object
within an aggregate has no explicit initialiser, then that basic object is default-initialised.
3.7 Types
3.7.1 Introduction
Every expression has a type which is known at compile time. The type of the expression determines the
operations that can be carried out on that expression, and the values which can be stored in, or read
from, that expression.
At its simplest level, an expression is simply an identifier which has been declared as an object or as a
function name. In this case, the type of the identifier determines the values which may be stored in or
read from the object, or the values which can be returned from the function.
There are three data types (int, bit, and var, together with a number of specialisations), which are
appropriate for representing 'data' items. The remaining types are a boolean type (bool); a stream
type (stream); a structure type (struct); and an array type.
Some objects have no value, and so have no type; these objects are said to be of a void type. A
function may be declared to be of type void when it is not required to return a value.
Page 28/164
LRM 2.6 © 2008-2019 Maia EDA
3.7.1.1 Data types
The int, bit, and var types are intended to store numeric 'data'. int and bit represent 2-state
(binary) data, while var represents 4-state data. Objects of these three types are collectively known as
ivar objects.
There are additionally a number of specialisations of the bit and var types. kmap is a specialisation of
var, which simplifies the handling and manipulation of data representing Karnaugh maps. kmap and
var support a different set of operators, and so are considered to be distinct types. The terms
arithmetic type and data type are used to make this distinction; 'arithmetic' excludes kmap, while 'data'
includes it.
ubit is an unconstrained bit, while uvar is an unconstrained var. These two specialisations are used
to represent objects whose size is not known in advance. However, the underlying object is a bit or
var object (albeit of an unknown size), so these specialisations do not introduce new types.
real1, real2, and real3 are provided 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. On most systems, real1 is identical to bit32; real2 is identical
to bit64; and real3 is identical to either bit80 or bit128.
bool is a synonym for bit1 at levels 0 and 1 (3.1.5), and so can be considered to be a data type.
The stream type represents files on the host operating system, and handles file input and output
operations. The use of a dedicated stream type allows common vector file operations to be handled
simply, without the use of an external I/O library.
bool is a distinct non-data type at Level 2 (3.1.5). There is no string type; the contents of a string
cannot be manipulated.
Page 29/164
LRM 2.6 © 2008-2019 Maia EDA
The int type represents 'small' two's complement integers. int objects are signed and have a size
which is given by the _DefaultWordSize pragma, which itself defaults to 32 bits. int is provided as a
programmer convenience (for indexing and looping operations, for example), and is not intended for
modelling hardware.
When using bit and var objects, complexity is provided by operators, rather than by the type itself.
There are, for example, different operators for signed and unsigned integer comparisons, and integer
and floating-point addition. This behaviour reflects the structure of the electronic systems that Maia is
intended to model and verify. In these systems, data is a secondary concern, and simply represents the
contents of a storage location. Electronic systems are primarily concerned with the transformation of
data, in function units. The same storage location may be connected to, for example, an unsigned
integer comparator, a signed integer comparator, or a floating-point adder, at different times.
Maia is therefore fundamentally different from general-purpose object-oriented languages in which data
is the primary concern, and in which complexity is provided by layering properties on top of the data
(in, for example, classes).
Arrays a and b are assignment-compatible if both arrays are of the same rank, each rank has the same
bounds, and the array elements are both assignment-compatible and have the same size (3.7.12.4).
5. a and b are struct objects where both are instances of the same structure definition
6. a and b are stream objects where both are instances of the same stream definition
7. If a is a mode 1 stream, and b is an object of an int or bit type, then the assignment a=b (but
not b=a) is allowable
Page 30/164
LRM 2.6 © 2008-2019 Maia EDA
3.7.3 ubit and uvar
A 2-state data object whose size is not known in advance should be declared as a ubit. Similarly, a 4-
state data object whose size is not known in advance should be declared as a uvar. ubit and uvar
may appear only as a function formal parameter, 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 calls1.
This example returns true if the unconstrained input has odd parity, and false otherwise:
bool oddParity(ubit a) {
result = false;
for(int i = 0; i < a'size; i++)
result ^= a.(i);
}
Example 15
The compiler must statically determine the maximum possible size of an unconstrained formal or return
value. There are some circumstances in which this may be difficult or impossible; this might happen,
for example, in a circular chain of function calls in which there is a cycle of connected unconstrained
return values and unconstrained actuals. In these circumstances it might be possible to complete sizing
by increasing the number of sizing iterations performed by the compiler; see (14.4).
1
The returned object is actually statically sized to the maximum size returned by any call of the function; this value may be
found by applying the 'size attribute to the function call. In practice, the return size can almost always be considered to be
dynamically set in the current function call.
Page 31/164
LRM 2.6 © 2008-2019 Maia EDA
3.7.4.1 Assignment compatibility
ivar objects are assignment-compatible. When a 2-state ivar object is assigned to a 4-state ivar object
the destination bits will have the same value as the source bits. When a 4-state ivar object is assigned
to a 2-state ivar object any metavalue bits in the source are converted to 11. Under most
circumstances2, an ivar object may be assigned to a narrower ivar object, in which case the source data
is truncated. Similarly, an ivar object may be assigned to a wider ivar object, in which case the source
data is either zero-extended, or sign-extended, depending on which assignment operator is used:
bit3 a = 3'b101;
bit4 b, c;
var5 d = 5'b1xz01;
The int type supports the same operators, with the exception that the sized and signed versions of
the operators may not be used where any of the operands are of type int. The 'plain' versions of the
operators will correctly return a signed result.
The 'size and 'meta operators return a bit and a bool, respectively, for any ivar operand (0). The
remaining unary operators have a return type which is the same as the type of the operand.
1
This is consistent with a definition of 'false' as 0, and 'true' as non-zero.
2
There is an exception if DUT port size checking is enabled; see (3.1.4).
3
This behaviour differs from Verilog. in Verilog, an expression is 'true' if it is non-zero and does not contain metavalues, and is
'false' otherwise. In Maia, an expression is false if it is zero, and is true otherwise. The expression 2'b1x is therefore false in
Verilog, and true in Maia.
Page 32/164
LRM 2.6 © 2008-2019 Maia EDA
3. If both operands are of type int or bit, the operation is carried out using conventional 2-
state arithmetic or logic. If either operand is of type bit, the result is of type bit; otherwise,
the result is of type int.
3.7.5 int
The int type is primarily intended for general 'software' operations which require a signed type, where
the size of that type is not a specific concern. int is signed, and has a size given by the
_DefaultWordSize pragma (12.7). _DefaultWordSize itself defaults to 32 bits if it has not been
set.
3.7.6 bit
The bit type represents two-state data, where each bit can take on one of the values 0 or 1. The size
of a bit object must be set explicitly, as a decimal integer suffix which immediately follows the bit
keyword, with no intervening whitespace. The suffix may be omitted for a single-bit object (in other
words, bit x is equivalent to bit1 x).
bit a; // 'a' is a one-bit two-state data object
bit18 b; // 'b' is an 18-bit two-state data object (indexed as 17:0)
bit192 c; // 'c' is a 192-bit two-state data object (indexed as 191:0)
Example 18
3.7.7 var
The var type represents four-state data, where each bit can take on one of the values 0, 1, X, or Z. X
and Z are metavalues, and represent "unknown" and "tristate", respectively, in electronic systems. The
'meta postfix operator may be used to determine whether or not an expression contains any
metavalues. expr'meta will return true if expr contains any metavalue bits, and false otherwise.
var is essentially identical to bit, except that operations on var objects are carried out using 4-state
arithmetic and logic, as defined in (3.7.7.3) through (3.7.7.6) below.
1. Any ports or signals declared in a DUT section are implicitly declared as a correctly-sized
external var
2. A rank-zero K-map (3.7.8) is a one-bit var (a var1)
3. At level 0, implicitly-declared variables are created as a default-sized var (undeclared object x,
for example, is implicitly declared as varnn x, where nn is equal to _DefaultWordSize)
4. At level 0, a function formal parameter which does not have a type specifier is an
unconstrained object of type var (a uvar)
5. At levels 0 and 1, a function which has no return type specifier returns an unconstrained
object of type var (a uvar).
Page 33/164
LRM 2.6 © 2008-2019 Maia EDA
3.7.7.2 var operations
The 4-state arithmetic and logic operations are defined in (3.7.7.3) through (3.7.7.6) below. For the
operators which are not listed in these subclauses, the 4-state behaviour is as follows:
2. the logical AND and logical OR operators will convert 4-state operands into boolean values
according to Error! Reference source not found. Error! Reference source not found.;
3. the conditional operator will convert its first expression into a boolean according to Error!
Reference source not found.;
5. the remaining operators have the same behaviour for bit and var operands.
1
This is same as the corresponding Verilog behaviour.
Page 34/164
LRM 2.6 © 2008-2019 Maia EDA
Table 9 below defines the results of the 4-state bitwise (&, |, ^, and ~) operators1. The corresponding
tables for the 2-state operators can be found by simply ignoring the (shaded) X and Z rows and
columns.
& 0 1 X Z | 0 1 X Z ^ 0 1 X Z ~
0 0 0 0 0 0 0 1 X X 0 0 1 X X 0 1
1 0 1 X X 1 1 1 1 1 1 1 0 X X 1 0
X 0 X X X X X 1 X X X X X X X X X
Z 0 X X X Z X 1 X X Z X X X X Z X
3.7.8 kmap
The kmap type is a specialisation of var, and is used to simplify the specification and testing of
combinatorial functions of several variables. An n-variable kmap is essentially a multi-dimensional var
array of rank n, in which the element addressing has been modified into a reflected-binary Gray-coded
form. For example, Figure 1 below shows a 5-variable Karnaugh map:
ABC
000 001 011 010 110 111 101 100
00 1 1 1 0 1 0 1 1
DE
01 0 1 0 1 0 1 0 1
11 1 0 1 0 1 0 1 0
10 0 1 1 1 1 1 0 1
The diagram is shown in a standard form, and shows the required output of a logic function of 5
variables: fn(A,B,C,D,E). These inputs are encoded in a 5-bit binary word, with A being the most
significant input (with a weighting of 24) and E being the least significant bit (with a weighting of 20).
This logic function is coded in Maia as follows:
// declare a 5-variable Karnaugh map
kmap fn =
1 1 1 0 1 0 1 1
0 1 0 1 0 1 0 1
1 0 1 0 1 0 1 0
0 1 1 1 1 1 0 1;
The indexes into a kmap must be binary (in other words, an index variable may not contain a
metavalue). The output, however, is a 4-state value, which must be coded as a one-bit literal constant
(and not a constant expression). The metavalues 1'bx and 1'bz may alternatively be specified as a
case-insensitive X or Z for simplicity1.
K-map initialiser lists may optionally be enclosed in braces, in the same way that scalar initialisers may
optionally be brace-enclosed. These two initialisers are identical:
kmap a = {
0 X 1 Z
1 0 0 1
};
kmap b = 0 X 1 Z 1 0 0 1;
Example 23
The precise format of the initialiser list is not important. Each element must be separated by
whitespace, but newlines are not significant. The second initialiser contains a list of 8 elements, so
must encode a 3-variable kmap, with 2 rows and 4 columns.
K-map objects may be declared either as a kmap, or as a kmapn, where n is the number of variables in
the K-map. Objects declared as a kmap must be completely initialised in their declaration to allow the
compiler to derive the number of variables. Objects declared as a kmapn may be left uninitialised if
desired, in which case they are given a default value of all X:
kmap a = 0 1 1 0; // inferred as a 2-variable kmap
kmap2 b = 0 1 1 0; // declared as a 2-variable kmap
kmap2 c; // initialised to {X X X X}
c = b; // c now contains {0 1 1 0}
kmap d; // error: must be initialised
kmap e = 0 1 1 0 1; // error: initialisation must be complete
kmap3 f = 0 1 1 0 1; // Ok: initialised to {0 1 1 0 1 X X X}
Example 24
A single element extracted from a K-map using an indexing operation is of type var (a var1). It is not
possible to extract anything other than a single element (a row or column, for example) from a K-map.
K-map objects may be passed to and returned from functions in the normal way.
1
This is the only place in which the literals X and Z may be used as constants; these literals will result in a syntax error if used
in any other context.
Page 36/164
LRM 2.6 © 2008-2019 Maia EDA
3.7.8.2 K-map operations
The operators which support K-map operands are listed in Table 10 below. The logic operators use 4-
state logic (3.7.7.6).
Operator result Operation
type
x'size bit Returns the number of elements in x (2n, where n is the number of
variables)
x'meta bool Return true if x contains any metavalues, and false otherwise
~x kmap Bitwise negation; inverts the entire K-map
x = y kmap Assign K-map y to K-map x
x == y bool Test x and y for equality
x != y bool Test x and y for inequality
& | ^ kmap Bitwise and, or, and xor
&= |= ^= kmap Bitwise compound assignment
(e1)?x:y kmap Returns K-map x if expression e1 evaluates true, and K-map y otherwise
(..., x) kmap The comma operator; returns K-map x if x is the last expression
Table 10: kmap operators
3.7.9 bool
An object which has been declared to be of type bool has only two potential values: false, and true.
The specific behaviour of booleans depends on the level of the _StrictChecking pragma (12.7). At
levels 0 and 1 the bool type has no special significance, and no boolean-related type checking is carried
out.
The syntax requires a boolean as the controlling expression for the if, do, while, for, and assert
statements, as an operand of the logical operators, and as the first operand of the conditional operator.
An expression that evaluates to an int, bit or a var may instead be used in these contexts, and is
implicitly converted to a boolean according to (3.7.4.2).
3.7.9.1 Levels 0, 1
bool is not a distinct type at levels 0 and 1; an object declared as a bool is simply a one-bit bit (a
bit1). The false literal is defined as 1'b0, while the true literal is defined as 1'b1.
An ivar object may additionally be used anywhere where a boolean is required by the syntax. If the
object has an all-zero value, then it is considered to have a value of false; it otherwise has the value
true.
3.7.9.2 Level 2
bool is a distinct type at level 2, and supports only the operators listed in Table 11 below. A boolean
object is assignment-compatible only with another boolean, or with an ivar object.
Page 37/164
LRM 2.6 © 2008-2019 Maia EDA
The binary logical and equality operators listed are defined if both operands are boolean, or if one is
boolean and the other is an ivar:
Operator result type Operation
x'size bit Returns 1
x'meta bool Returns false
!x bool Logical negation
= bool Assignment
== != bool Equality
&& || bool Logical AND, OR
(e1)?x:y bool Conditional operator; x and y must both be boolean
(..., x) bool Comma operator; returns boolean x if x is the last expression
Table 11: boolean operators
As for levels 0 and 1, an ivar object may alternatively be used anywhere where a boolean is required by
the syntax.
3.7.10 struct
A structure is a collection of one or more objects, possibly of different types, into a single named
object. Structures are defined and declared conventionally, and may contain scalar or array objects of
any type. Structure elements are accessed conventionally, using a dotted identifier notation.
Structures and streams are, in many respects, syntactically identical. The discussion of structure
definition and declaration below is equally applicable to streams. Structures and stream tags occupy the
same namespace (3.4); it is illegal to give a structure and a stream the same name when they are in
the same scope.
Structures may be passed to and returned from functions in the normal way. Note that the keyword
struct (or stream) must be used when declaring formal parameters and function return types1:
struct s1 {...};
// function 'foo' has a single structure parameter and returns a structure
s1 foo(s1 param) {...} // error; must be...
struct s1 foo(struct s1 param) {...} // Ok
Example 25
1
The keywords are required in C, but not in C++.
Page 38/164
LRM 2.6 © 2008-2019 Maia EDA
// create named structure type 's1'
struct s1 { int x,y; }; // declaration of 'struct s1'
// create two objects: 'a' is of type 'struct s1', 'b' is of type 'array[4]
// of struct s1'
struct s1 a, b[4]; // definition of a, b
// create objects 'c' and 'd'; both are of type 'array[3] of struct s1'
struct s1[3] c, d; // definition of c, d
// create an anonymous struct type, and objects 'e' and 'f' of that type
struct { int x,y; } e, f; // definition of e, f
// create named struct type s3, and objects 'g' and 'h' of that type
struct s3 { int x,y; } g; // definition of g
struct s3 h; // definition of h
Example 26
Note that the declaration of both stream and structure types must be terminated with a ';' character,
even when there is no trailing object list (as in the first example above). The semicolon is required
because the object list is optional; without it, the parser would find it difficult to distinguish between a
type declaration with a trailing object list, or a type declaration with no object list, immediately followed
by a new statement.
Structures may contain declarations of other structures. The name of the nested structure is placed in
the same scope as the structure in which it is nested. This code is therefore legal1:
struct S { struct T {...}; };
struct T x;
Example 27
The 'offset operator may also be applied to any member in a structure to find that member's offset
within the structure, in bits.
1
This again follows C practice; the code is illegal in C++.
Page 39/164
LRM 2.6 © 2008-2019 Maia EDA
Two assignment-compatible structures may be tested for equality and inequality; the structures are
equal if every member contains the same data. For a member which is a stream, the stream identifiers
(or 'handles') are tested for equality; the identifiers will compare equal if they refer to the same stream.
Operator result Operation
type
x'size bit Return the size of structure x, in bits
x'meta bool Return true if x contains any metavalues, and false otherwise
x.m any Return member m in x
x = y struct Assign struct y to struct x
x == y bool Test x and y for equality
x != y bool Test x and y for inequality
(e1)?x:y struct Returns structure x if expression e1 evaluates true, and structure y otherwise
(..., x) struct The comma operator; returns structure x if x is the last expression
Table 12: structure operators
3.7.10.4 Limitations
Structures may contain other structure objects, but not references to those objects; this means that
linked lists of structures cannot be built.
3.7.11 stream
Stream objects handle file read and write operations. stream is not a single type; it is instead a family
of types which are specialised for specific file operations. Two stream types are provided: mode 1
streams provide random read access into text data files, while mode 2 streams provide sequential write
access to text data files. These two stream types do not provide generalised file I/O; they are instead
specialised to allow the trivial creation and reading of data files which contain whitespace-separated
data fields.
The syntax of stream definitions and declarations is essentially identical to the equivalent structure
syntax (see 3.7.10.1). In a structure, members are explicitly declared with a type and a name. In a
stream, by contrast, there are no explicit members; the compiler automatically creates members using
the information found in the stream's format property. These members can then be accessed in exactly
the same way as structure members, using a dotted notation. Mode 1 and 2 stream members
correspond to data fields within text files.
Objects of a stream type may be viewed as either handles to the underlying stream or, alternatively, as
references to that stream; both views are equivalent. The term handle is generally used here for clarity;
this does not imply that the stream is represented by a small integer. There are no operators which
provide access to the underlying implementation of a stream (a stream handle cannot, for example, be
read into an integer)1.
1
However, the space occupied by a stream within a structure can be found with the 'offset operator, and will be consistent
with the size of a small integer.
Page 40/164
LRM 2.6 © 2008-2019 Maia EDA
The representation of a stream as a handle implies that streams are effectively passed to functions by
reference. In other words, if a stream is passed to a function, that function sees exactly the same
stream as the caller; if the function changes the state of the stream (by reading from it or writing to it)
then the caller will see the new state of the stream when the function returns.
Mode 1 and 2 streams provide no operators for opening and closing the stream; these operations are
carried out automatically. Global and static streams are opened at the start of program execution, and
remain open throughout the lifetime of the program; local streams (those declared within a function)
are opened when the definition is encountered, and are closed when the function returns. If it is
necessary for a local stream to retain state between function calls then that stream should be declared
as static.
Mode 1 streams provide no operations to open or close the associated text file, or to explicitly read
either entire lines or individual fields; these operations are handled automatically. The stream is
positioned to a given line in the (processed) file by assigning an integer to the stream. This operation
automatically reads the data fields in that line of the file. It is guaranteed that the stream will always
point to the first line of the file when it is first used, and that the data fields will contain the
corresponding data from the first line of the file.
3.7.11.1.1 Mode 1 pre-processing
The data file is located and processed during compilation. The compiler confirms that the file exists; it
then removes all comments and superfluous whitespace from a temporary version of the file, and
confirms that each line of the resulting file is appropriate for the format. An error is issued if any data
contains metavalues, or if any significant data bits have to be truncated to match a format
specification. A warning is issued if any sized data item must be extended to match a format
specification.
The resulting temporary file contains exactly one line for each set of data inputs in the source file. The
number of lines in this temporary file is defined as the 'size' of a mode 1 file. In the description below,
a line refers to this processed set of data inputs, which contains one or more data fields; offset refers
to the zero-based line number in the temporary file; and size refers to the number of lines in the
temporary file. The original file is not modified by pre-processing.
3.7.11.1.2 Mode 1 file positioning
A mode 1 stream is set to a given offset simply by writing an integer to the stream; this integer is the
required zero-based offset in the processed file. Note that the offset of a line in the original and the
processed files will not, in general, be the same. A specific data item may occur on, for example, line
20 of the input file, but might appear on line 10 of the processed file after stripping comments and
Page 41/164
LRM 2.6 © 2008-2019 Maia EDA
whitespace. A mode 1 offset is then essentially a 'meta'-offset which refers only to the significant lines
in the input file.
Any integer written to the stream is reduced modulo the size of the file; this means that it is not
possible to have a file positioning error. A file offset therefore has the same behaviour as a 'small' bit
or var object. The value of a 4-bit bit, for example, wraps around from 15 to 0 when incremented.
The offset of a file which contains 100 lines wraps around from 99 to 0 when incremented, or from 0 to
99 when decremented, in exactly the same way.
The current offset in a file can be retrieved using the 'offset operator. The addition and subtraction
operators are overloaded when one of the operands is a mode 1 stream and the other is an int or
bit. In this case, the operator reads the current file offset and returns the sum or difference of the
offset and the second operand. This behaviour can be used to step arbitrarily through the file. Some
examples of file positioning are given in the code below; this code assumes that the file contains at
least 15 lines.
stream s1 a;
a = 0; // file rewind (automatic when the stream is first used)
--a; // set to the last line in the file
assert(a'offset == a'size-1);
a = 10; // set to offset 10 (line 11)
a = 4 + a; // set to offset 14 (line 15)
a -= 3; // set to offset 11
a++; // set to offset 12
Example 29
Any file positioning operation automatically reads the data fields at the new offset.
3.7.11.1.3 Mode 1 stream declaration
A mode 1 stream type must be declared with three properties: a mode, a file, and a format. The mode
must be the integer 1. All three properties must be present in the declaration, and may appear in any
order. An example of a mode 1 stream declaration is:
stream s1 {
mode 1;
file "vectors/testfile.dat";
format "%8'i %64'h %f", f1, f2, f3;
} a;
Example 30
This declaration creates a new type of stream s1, and a new object a of this type. The corresponding
text file is searched for in directories which are relative to the location of the source file containing the
declaration. In this case, the compiler expects to find a directory named vectors in the same directory
as this source file, and expects to find a file named testfile.dat in that directory. The file may be
searched for in an absolute location by appropriately prefixing the filename (with '/' or 'C:\', for
example).
3.7.11.1.4 Mode 1 format property
The format property specifies the expected contents of each line of the input file. It is composed of a
string containing text which should be matched in the input, and one or more conversion specifications,
followed by one or more field names. Each conversion specification requires a corresponding field
Page 42/164
LRM 2.6 © 2008-2019 Maia EDA
name; the first conversion specification is associated with the first field name, and so on. There must
be exactly the same number of conversion specifications as field names.
For the example above, a line is expected to start with an 8-bit integer, followed by whitespace,
followed by a 64-bit hex integer, followed by whitespace, followed by a floating-point value. Any
subsequent fields on the line are ignored. If it was necessary to read only the first 8-bit integer on this
line, then the format string
format "%8'i", f1;
would be sufficient.
The format string must include exactly one name for each field. In the example above, the 8-bit integer
field is named f1, the 64-bit integer field is named f2, and the float field is named f3. These are user-
supplied names for automatically-created read-only members within the stream object, which contain
the current value of the corresponding field.
The field data can be read using the same dotted notation used for structures. In this case, the current
values of the 3 fields can be read as a.f1, a.f2, and a.f3. These values are automatically updated
when the stream offset changes, to give the field values at the new offset. The fields may be
considered to be read-only objects of a bit type, with a declared size given by the field width.
Any text in the format string which is not a conversion specification, and which is not whitespace, must
be matched exactly.
3.7.11.1.5 Mode 1 conversions
A conversion specification is composed of the % character, optionally followed by a field width, followed
by a conversion character. If the field width is present, it must be a decimal integer, which must be
followed by an apostrophe or a grave accent (back-tick) character. A conversion specification may also
be specified as %%, when it is necessary to match a single % character in the input. The conversion
characters supported for mode 1 are listed below.
f Matches a floating-point constant in the format defined in (2.7.3). If a field width is present,
it must be 0, 1, or 2, for single, double, or extended double precision, respectively; the
width defaults to 2 if it is not present. If the constant itself includes a precision suffix then
that suffix must match the field width1.
i Matches any integer constant in the format defined in (2.7). If the constant does not have a
size specification, it is assumed to have the size specified by _DefaultWordSize. In
normal use, no field width is specified, and the width is derived from the size of the input
data.
h d o b Matches integer data in base 16, 10, 8, or 2, respectively. The input should contain only
underscore characters and characters which are appropriate for the selected base; it must
not contain any prefix characters. A field width is mandatory for these conversions.
1
mtv's Verilog code generator supports only double-precision float data; an error will be reported if either the field width or the
suffix do not have the appropriate values for double-precision.
Page 43/164
LRM 2.6 © 2008-2019 Maia EDA
For all conversions, the file data may optionally be preceded by a '–' character. For the integer
conversions, the result is derived by taking the two's complement of the input data, for the appropriate
field width.
For the h, d, o, and b conversions, a field width must be provided, and the data in the input file may
not contain a size prefix. An error will be reported if any of the data items corresponding to this field
cannot be represented in the specified width.
For the i conversion, a field width will not normally be provided, and the field width is found from the
input data. In order for the compiler to derive the field width it is necessary for all the data items
corresponding to this field to have the same size; an error is reported if this is not the case.
For the i conversion, a field width may be provided if necessary. In this case, an error will be reported
if any data items cannot be represented in the field width, and a warning will be reported if any
explicitly-sized data items must be extended to reach the field width.
3.7.11.1.6 Mode 1 stream example
/* */ vector 4 /* */ data 42
Example 31
In this case, the processed file contains 4 lines, has a size of 4, and can be accessed with offsets of 0,
1, 2, and 3. This program is sufficient to read the entire file (assuming that the file is named 'test.dat',
and is in the same directory as the program) and to display all eight values:
/* this program produces the output:
offset 0: vector is 1; data is 10
offset 1: vector is 2; data is 20
offset 2: vector is 3; data is 30
offset 3: vector is 4; data is 42
*/
stream s1 {
mode 1;
file "test.dat";
format "vector %i data %i", f1, f2;
} a;
main() {
for(int i=0; i<a'size; i++) {
a = i; // set the file offset
report("offset %d: vector is %d; data is %d\n", a'offset, a.f1, a.f2);
}
}
Example 32
Page 44/164
LRM 2.6 © 2008-2019 Maia EDA
3.7.11.1.7 Mode 1 'for all' operation
A convenient way to iterate through all the lines in a mode 1 file is to use the for all statement. The
example above can be more compactly coded as:
main() {
for all a
report("offset %d: vector is %d; data is %d\n", a'offset, a.f1, a.f2);
}
Example 33
Only mode 1 streams of the same type are assignment-compatible. This definition of assignment
compatibility is the same as the corresponding one for structures; see 3.7.10.2.
3.7.11.1.9 Mode 1 stream operators
The operators which may be applied to mode 1 streams are listed in Table 13 below. When a stream is
read in an expression the value returned is the stream, except in one specific case: the addition and
subtraction operators are overloaded to read the current offset in the stream, rather than the stream
itself.
The first column in the table shows the operator. In this column, x, y, and z are expressions which
evaluate to the same stream type (and so are assignment-compatible), and i is an expression which
evaluates to a bit type. x, y, and z may be any expression that evaluates to a scalar stream. Note
that an object of type 'array of stream' is not a stream.
Page 45/164
LRM 2.6 © 2008-2019 Maia EDA
Operator result Operation
type
x'size bit Return the number of lines in stream x
x'offset bit Return the current offset in stream x
x++ stream Increment the offset in x, and return the old state of x
x-- stream Decrement the offset in x, and return the old state of x
x.f bit Return the value of field f in x
++x stream Increment the offset in x, and return the new state of x
--x stream Decrement the offset in x, and return the new state of x
x+i, i+x bit Read the stream offset from x, carry out the addition, and return the bit
result
x-i, i-x bit Read the stream offset from x, carry out the subtraction, and return the
bit result
x = y stream Assign stream y to stream x, and return stream x
x = i stream Set the offset of stream x to i, and return stream x
x += i stream Identical to (x = x+i), and so returns stream x
x -= i stream Identical to (x = x-i), and so returns stream x
(e1)?y:z; stream The ternary operator; y and z must be assignment-compatible. Returns
stream y if e1 evaluates true, and stream z otherwise
(..., x) stream The comma operator; returns stream x if x is the last expression
Table 13: mode 1 stream operators
The stream is written by assigning data to any fields defined by its format specification (3.7.11.2.2),
and then applying the pre-increment operator to the stream object. The increment operation writes the
current line, and increases the stream size by one. The output file is created when the object is
declared; if it already exists, it will be over-written. The first increment operation therefore writes the
first line of the file.
If a data field is not assigned to before incrementing the stream, that field will retain its last value. The
initial value of a field is undefined. An error is reported if a given field is never written to; however, no
error is reported if a field is written on some occasions, but not others.
Mode 2 streams are associated with a single write-only output file, and it is therefore not possible for
multiple stream objects to have their own private copy of this file. It therefore makes little sense to
Page 46/164
LRM 2.6 © 2008-2019 Maia EDA
declare multiple objects of the same mode 2 stream type. Where this does happen, the objects are
defined to be synonyms to, or references for, each other:
// a and b are defined to be the same object, which writes to a single output file:
stream m2 {
mode 2;
...
} a, b;
// but c and d are different objects, which read from their own private copies of
// an input file:
stream m1 {
mode 1;
...
} c, d;
Example 35
A mode 2 stream is defined in the same way as a mode 1 stream (3.7.11.1.3), except that the mode
must be the integer 2. The specified file is opened for writing; if the file already exists, it is over-
written.
3.7.11.2.2 Mode 2 format property
The format property specifies the required form of each line in the output file. It has the same form as
a mode 1 format (3.7.11.1.4), and is made up of a string containing whitespace1, text which is copied
to the output line, and conversion specifications; the string is followed by a list of field names.
3.7.11.2.3 Mode 2 conversions
The conversion specification is the same as the report statement conversion specification (6.13). The
conversion specifiers supported for mode 2 are:
f e E g G Floating-point output
Mode 2 stream fields are write-only objects with the properties of a bit, and can be assigned to from
any object which is assignment-compatible with an bit. The size of the field is determined statically by
the compiler, by examining the size of all objects which are assigned to the field 2. It is an error if a
given field is assigned to from multiple objects which do not have the same size.
1
Whitespace in the format string is collapsed into a single space in the output line in 2019.9.
2
For the report statement, the arguments are single expressions with a known size; for mode 2 format specifications, the
arguments (fields) are simply names, and their size must be determined from assignments to the fields.
Page 47/164
LRM 2.6 © 2008-2019 Maia EDA
stream m2 {
mode 2;
file "foo";
format "%x %6.3f", field1, field2;
} a;
int4 b;
var5 c;
a.field1 = b; // 'field1' sized at 4 bits
...
a.field1 = c; // error: is 'field1' 4 or 5 bits?
Example 36
Only mode 2 streams of the same type are assignment-compatible. However, since all objects of a
given mode 2 stream type are actually the same object, the concept of assignment compatibility is
essentially redundant. The assignment and ternary operators are therefore also redundant with mode 2
stream operands, but are defined to allow these operators to be used with structure operands which
contain mode 2 streams.
3.7.11.2.6 Mode 2 stream operators
The operators which may be applied to mode 2 streams are listed in Table 14 below. When a stream is
read in an expression the value returned is the stream itself.
The first column in the table shows the operator. In this column, x, y, and z are expressions which
evaluate to the same stream type (and so are assignment-compatible). Note that an object of type
'array of stream' is not a stream.
Operator result Operation
type
x'size bit Return the number of lines in stream x; will be 0 before the first increment
operation
x.f void Mode 2 stream field, write-only; see 3.7.11.2.1
++x stream Write the current line, increment the size of stream x, and return x
x = y stream Assign stream y to stream x, and return stream x
(e1)?y:z stream Conditional operator; y and z must be assignment-compatible. Returns
stream y if e1 evaluates true, and stream z otherwise
(..., x) stream Comma operator; returns stream x if x is the last expression
Table 14: mode 2 stream operators
Page 48/164
LRM 2.6 © 2008-2019 Maia EDA
3.7.12 array
Objects may be combined into arrays, which are aggregates of objects of the same type. Arrays are
declared by listing the maximum size of each dimension in square brackets. Consider, for example, the
array object defined by this declaration:
int [3][4][5] a;
Here a is a 3-dimensional array of 3 x 4 x 5 ints. The expression a[i] yields a two-dimensional array
of 4 x 5 ints; the expression a[i][j] yields a one-dimensional array of 5 ints; and, finally, the
expression a[i][j][k] yields a scalar object of type int. The rank of an expression or object is
defined as its dimensionality. a is a 3-dimensional array and has rank 3. The expression a[i],
however, has rank 2; the expression a[i][j] has rank 1; and the expression a[i][j][k] has rank
0. Any scalar object has rank 0.
Arrays are stored in memory in row-major order; in other words, the last subscript varies fastest.
However, it should be noted that only form 1 can be used to define a function which returns an array
object, and form 1 also makes it obvious that the object's dimensionality is part of the type of that
object1.
1
Form 2 is provided for compatibility with C and related languages. More recent languages tend to use form 1; Maia's ability to
use both forms, and a combination of the two, follows Java usage. Java, however, provides a (deprecated) feature to allow
functions to return arrays using form 2; Maia requires the use of form 1. C does not allow functions to return arrays.
Page 49/164
LRM 2.6 © 2008-2019 Maia EDA
3.7.12.3 Comma-separated dimension lists
Arrays may be declared, or accessed, using an alternative syntax, in which the array dimensions are
listed in a single pair of square brackets, with a comma-separated list of dimensions1. In this example,
both a and b are of type int[2][3] (or, equivalently, int[2,3]):
int [2][3] a = {{0,1,2}, {3,4,5}};
int b[2,3] = {{0,1,2}, {3,4,5}};
assert(a == b);
assert(a[0][1] == b[0,1]);
assert(a[0,1] == b[0][1]);
Example 38
The comma-separated list is more compact when accessing multi-dimensional arrays. kmap objects, in
particular, may have many dimensions and can be tedious to access using the fully-bracketed form.
When using this alternative syntax, array index expressions may not themselves be comma
expressions, unless the comma expression is enclosed in parentheses2.
This code shows various examples of arrays which are, or are not, assignment-compatible:
int [3,4,5] a;
int [4,5] b;
var32 [5] c;
bit7 [5] d;
a[0] = b; // Ok
b = a[1]; // Ok
c = a[0,0]; // Ok (_DefaultWordSize assumed to be 32)
d = a[0,0]; // error: a is an array of ints; d is an array of bit7
Example 39
1
The comma-separated list is used in Algol and derived languages; the fully-bracketed list is used in C and related languages.
2
Function argument lists (argument-expression-list), which are also comma-separated, have the same restriction.
Page 50/164
LRM 2.6 © 2008-2019 Maia EDA
3.7.12.5 Array operations
The operators which support array operands are listed in Table 15 below. For the ternary operator (?:)
x and y must be of the same type; for the remaining operators, they must simply be assignment-
compatible.
The 'offset operator may also be applied to any element in an array to find that element's offset
within the array, in bits.
Two assignment-compatible arrays may be tested for equality and inequality. The arrays are equal if all
corresponding elements contain the same data. For an array of streams, the stream identifiers (or
'handles') are tested for equality; the identifiers will compare equal if they refer to the same stream.
Operator result type Operation
x'size int Return the size of array x, in bits
x'meta bool Return true if x contains any metavalues, and false otherwise
x[e1] any Return element e1 in x
x = y x Assign array y to array x
x == y bool Test x and y for equality
x != y bool Test x and y for inequality
(e1)?x:y x Returns array x if expression e1 evaluates true, and array y otherwise
(..., x) x The comma operator; returns array x if x is the last expression
Table 15: array operators
Page 51/164
LRM 2.6 © 2008-2019 Maia EDA
4 OPERATORS AND EXPRESSIONS
4.1 Introduction
An object is a region of data storage which has an associated value. Every object is either a data object
(2-state or 4-state), a boolean, a stream, or an aggregate containing a collection of data, boolean, and
stream objects. Objects may be manipulated or combined using operators, in expressions.
The order in which the objects in an expression are combined is defined by the language's precedence
and associativity rules (4.5.1). Each such combination (an addition or subtraction, for example) defines
a sub-expression. Each sub-expression is evaluated in turn, and is replaced by a temporary object; this
temporary object itself has a value, which may be combined with the values of the remaining sub-
expressions.
An expression has a number of properties, which may be retrieved with the attribute operators (0). The
most significant of these is its size. The meaning of the size attribute depends on the type of the
object. However, in most cases, an object's size is the number of bits (2-state or 4-state) which are
required to store that object. An object's size may range from 1 bit, up to a compiler-determined
maximum, which is at least 224 bits.
With the exception of int objects, Maia does not specify any interpretation of the data pattern within a
data object. However, some operators (the signed comparisons, for example) may assume that the
data is in 2's complement format, and that the data may be sign-extended by copying the value of the
top bit. Other operators may assume that the data is in an IEC floating-point format. A non-int object
itself has no property that specifies what format the data is in; the data interpretation is a higher-level
concern, and is the responsibility of the programmer. In this respect, non-int data objects can be
thought of as memory locations within a digital electronic system. The storage location itself has no
properties, apart from its size; the control circuitry simply routes the contents of the storage location to
a function unit, and then writes the transformed data to the same, or another, storage location. The
function unit determines the operation to be carried out, and a given storage location may be
connected to any function unit as required. In Maia, the function unit corresponds to an operator.
Under most circumstances, objects can be combined in expressions in a simple and intuitive way. This
code, for example, carries out 4-state integer arithmetic operations on 24-bit variables:
var24 acc, b[10], c[10];
for(i=0; i<10; i++)
acc += b[i] * c[i];
Example 40
For this example, the multiplication and addition are automatically selected as 24-bit integer operators,
and the assignment to acc is selected as a 24-bit assignment. However, a number of potential
complications may arise in these cases:
All versions of an operator have the same precedence and associativity as the basic operator itself. An
operator which is signed or sized may optionally be enclosed in parentheses for clarity.
The base operators are listed in Table 16, while the floating-point operators are listed in Table 18,
Table 19, and Table 20. Some examples of operators are:
A = B - C; // unsigned subtraction, implicitly sized
A = B -# C; // signed subtraction, implicitly sized
A = B -$8 C; // unsigned 8-bit subtraction
A = B -#$21 C; // signed 21-bit subtraction
A = B (-#$21) C; // operators may be bracketed for clarity
var16 d;
A =$21 d; // unsigned (zero-extending) assignment (16 to 21 bits)
A =#$21 d; // signed (sign-extending) assignment (16 to 21 bits)
1. If an input operand requires extension, then it will be sign-extended for a signed operator,
and zero-extended for an unsigned operator.
2. For some operators the signed and unsigned versions of the operator may have different
behaviour, and produce different results when given the same operands. The affected
operators are the comparisons, right shift, division, and remainder (<, <=, >, >=, >>, /, %)1.
1
The left shift operator has named signed and unsigned alternatives (.SLA for <<#, and .SLL for <<), but both have the
same behaviour; the names are provided only for consistency with the right-shift versions (.SRA and .SRL).
Page 53/164
LRM 2.6 © 2008-2019 Maia EDA
4.4 Expression evaluation
When evaluating an expression, the current operator is first identified using the precedence and
associativity summarised in Table 16. This operator, together with its operand(s), forms the current
sub-expression. This sub-expression is evaluated according to (4.4.1), and is replaced with a temporary
object of the same type and size as the sub-expression. This procedure is repeated until the complete
expression has been evaluated.
Expressions may be arbitrarily parenthesised to specify the order in which operators should be
evaluated.
1
Left-to-right ordering is common in many languages (C# and Java, for example); the ordering is undefined in C.
2
If an operand does not have the required size (a single-precision operand is required for a double-precision operator, for
example) then it should be converted to the correct size using a cast operator (4.5.6).
Page 54/164
LRM 2.6 © 2008-2019 Maia EDA
4.5 Operators
An operator's associativity determines the grouping of operators at the same precedence level. The
addition and subtraction operators, for example, associate left-to-right, and the expression A–B+C is
therefore evaluated as (A–B)+C, rather than A–(B+C).
Operators Associativity
postfix: () [] ++ -- 'size 'msb 'meta left to right
'offset 'last .(x) .(x:y) .
prefix: ! ~ ++ -- + - (cast) right to left
binary: * / % left to right
+ - left to right
<< >> .R<< .R>> left to right
< <= > >= left to right
== != left to right
& left to right
^ left to right
| left to right
&& left to right
|| left to right
ternary: ?: right to left
assignment: = *= /= %= += -= &= right to left
^= |= <<= >>= .R<<= .R>>=
comma: , left to right
Table 16: precedence and associativity of operators
The operators which may optionally be signed and sized are shaded in the table.
1
Apart from some deletions (->, &, *, and sizeof) and additions ('size, 'msb, 'meta, 'offset, 'last, .(x),
.(x:y), .R<<, .R>>, .R<<=, and .R>>=), this table is otherwise identical to the corresponding table for C.
Page 55/164
LRM 2.6 © 2008-2019 Maia EDA
4.5.2 Operator equivalents
A number of operators have alternative names. These are listed in Table 17.
Operator Form 1 Form 2
Rotate Left .R<< .ROL
Rotate Right .R>> .ROR
Shift Left Logical << .SLL
Shift Left Arithmetic <<# .SLA
Shift Right Logical >> .SRL
Shift Right Arithmetic >># .SRA
Unsigned Less Than < .ULT
Unsigned Greater Than > .UGT
Unsigned Less than or Equal <= .ULE
Unsigned Greater than or Equal >= .UGE
Signed Less Than <# .SLT
Signed Greater Than ># .SGT
Signed Less than or Equal <=# .SLE
Signed Greater than or Equal >=# .SGE
Equality == .EQ
Inequality != .NE
Logical AND && and
Logical OR || or
Table 17: Operator equivalents
The textual alternative names for the shifts and comparisons are already implicitly signed or unsigned,
and so may not be followed by a # character. They may, however, be sized. Some examples of valid
and invalid operators are shown below.
.SRL // implicitly-sized right shift
.SRL# // invalid; .SRL is implicitly unsigned
.SRL$12 // 12-bit right-shift
.SRA# // invalid; .SRA is implicitly signed
>># // shift right arithmetic; equivalent to .SRA
Example 43
Page 56/164
LRM 2.6 © 2008-2019 Maia EDA
4.5.4 Postfix operators
Syntax
postfix-expression :
primary-expression
postfix-expression [ expression ]
postfix-expression ( argument-expression-listopt)
postfix-expression . identifier
postfix-expression ++
postfix-expression --
postfix-expression . bitslice
postfix-expression ` attribute-operator
postfix-expression ' attribute-operator
Syntax
argument-expression-list :
assignment-expression
argument-expression-list , assignment-expression
If the operand evaluates to a data object, then the increment or decrement operation is carried out by
adding or subtracting 1 to or from the least significant bit of the object (in other words, it is an integer
operation). If the operand evaluates to a mode 1 stream, then the increment or decrement operation is
applied to the file offset within that stream. It is an error if the operand evaluates to anything else.
Page 57/164
LRM 2.6 © 2008-2019 Maia EDA
Syntax
bitslice :
expr1.(expr2)
expr1.(expr-msb : expr-lsb)
expr1 : expression
expr2 : expression
expr-msb : expression
expr-lsb : expression
Semantics
Bitslices are addressed using descending indexes. If two indexes are specified, they may be equal to
address a single bit. In this case, the bitslice may be expressed in a more compact form by specifying
only one index.
The LSB of any object always has an index of 0. The full set of requirements for the indexes can
therefore be expressed as follows:
(expr2 < expr1'size) && (expr2 >= 0)
(expr-msb < expr1'size) && (expr-msb >= expr-lsb)
expr-lsb >= 0
Indexes are evaluated and checked at runtime, and a runtime error is raised if the equalities above are
violated.
The size of a bitslice expression is the size of the object being sliced (the postfix expression); it is not
the size implied by the slice indexes, which may change at runtime. The slice can be considered to be a
temporary object of the same size as the original object, with the required bits shifted to the bottom of
the temporary.
Size checking for bitslice expressions may be relaxed when writing to a DUT port in a drive statement;
see 3.1.4.
Page 58/164
LRM 2.6 © 2008-2019 Maia EDA
4.5.4.6 Attribute operators
A postfix expression followed by the ' (or `) operator and an attribute returns an attribute, or
property, of the operand.
Syntax
attribute-operator: one of
size offset msb meta last last(expression)
Semantics
size The size attribute returns the size of the operand. If the operand evaluates to a data object, a
boolean, a structure, or an array, then the value of the attribute is the total size, in bits, of that
data object, boolean, structure, or array. If the operand evaluates to a mode 1 or a mode 2
stream, then the value of the attribute is the number of lines in the corresponding text file. It is
an error if the operand evaluates to anything else.
offset The offset attribute returns an offset within an object. If the operand evaluates to a member
within a structure, or is an array indexing expression, then the value of the attribute is the
offset of that member or element within the structure or array, measured in bits. If the operand
evaluates to a mode 1 stream, then the value of the attribute is the current line number within
that stream. It is an error if the operand evaluates to anything else.
The offset of the first object within its container always has the value 0.
msb The operand must evaluate to an ivar object. The return type of the msb attribute is var1 for a
var object, and bit1 otherwise; its value is the value of the most significant bit of that operand.
meta The operand may evaluate to anything except a stream or a stream member. The meta attribute
returns true if the operand is, or contains, a data object which has a metavalue (X or Z), and
false otherwise. If the object is an aggregate which contains a stream, then that stream
contributes a value of false to the overall determination.
The 'size and 'offset attributes return a bit object. The size of this value is compiler determined,
but is at least 28 bits.
The 'last attribute returns a previous value of a DUT input or IO, as it would have been sampled by
the DUT1. The operand must be declared as a DUT input or IO, and must appear in a clocked drive
declaration (8.3.2), to allow the sample clock to be identified. The expression sig.last(n) returns
the n'th previous value of signal sig, as it would have been sampled by the relevant clock at the DUT.
The expression sig.last(1)returns the value of sig that would have been sampled on the previous
clock edge, while sig.last(2)returns the value that would have been sampled on the preceding
edge, and so on. The expression sig.last is equivalent to sig.last(1). If the edge count (n) is
1
The 'last attribute may be used to avoid race conditions where one thread generates a DUT input, while another thread reads
the same input (either directly or from a DUT output which has a combinatorial path from the input). In this case, the writer and
reader threads will execute in an arbitrary order, and the reader may read the value before it has been written. This condition
can be avoided by instead reading the input as it would have been seen by the DUT at the previous clock edge.
Page 59/164
LRM 2.6 © 2008-2019 Maia EDA
supplied, it must be greater than or equal to 1. The maximum value of n is compiler-determined, but
will be at least 4096. A run-time error will be raised if the edge count is out of range.
The value returned is maintained in a pipeline by the testbench itself, and is sampled on the relevant
sample clock, with the supplied or default setup time for the relevant signal. If the edge count for a
given signal is statically determinable then the compiler will generate the sample pipeline for that signal
with the size given by the maximum edge count in the source code. If the edge count cannot be
determined during compilation then the sample clock must instead be declared with a pipeline
specification, which gives the pipeline size required. If, for example, a 10-cycle sample history is
required for signal D, and the relevant clock is signal C, then C should be declared with
'create_clock C –pipeline 10'. In this case, a run-time error will be raised if, during execution
of the model, the edge count is found to be outside the range [1,10].
Examples
var24[2][3][4] x;
assert(
(x`size == 576) &&
(x[0]`size == 288) &&
(x[0][0]`size == 96) &&
(x[0][0][0]`size == 24));
Example 45
DUT {
module reg4 // 4-bit reg with sync reset
(input C, R,
input [3:0] D,
output [3:0] Q);
create_clock C -pipeline 2; // 2-level sample pipe
[C, R, D] -> [Q];
}
main() {
int level = 1;
[.C, 1, 0] -> [0]; // reset
[.C, 0, 1] -> [1]; // n+1 cycles required to prime n-cycle pipe
for(bit4 i=2; i<10; i++) {
[.C, 0, i] -> [i];
assert((D'last(level) == i) && (D'last(level+1) == i-1));
}
}
Example 46
Page 60/164
LRM 2.6 © 2008-2019 Maia EDA
4.5.5 Unary operators
Syntax
unary-expression:
postfix-expression
unary-operator unary-expression
( type-name ) unary-expression
unary-operator: one of
++ -- + - ~ ! float+ float-
If the operand evaluates to an int, bit or var object, then that object is incremented or
decremented using 2-state or 4-state integer arithmetic, in the same way as the postfix ++ and –-
operators (4.5.4.4). The result is the new value of the operand after the increment or decrement has
completed.
If the operand evaluates to a mode 1 stream, then the stream offset is incremented or decremented,
and the new state of the stream is returned. If the operand evaluates to a mode 2 stream, then ++E
writes the current line to E, increments the size of E, and returns the new state of E.
It is an error if the operand evaluates to anything else, or if the –- operator is applied to a mode 2
stream.
The unary addition operators return their operand unless that operand is a var which contains one or
more metavalues; in this case, the result has a value of all X, as if +E had been evaluated as (0+E).
The plain – operator carries out integer subtraction from 0 (with the exception noted in (4.6.1.2)),
while the floating-point equivalents carry out a floating-point subtraction from 0.0. An integer
subtraction from 0 is carried out using 2-state integer arithmetic if the operand is of type int or bit,
or 4-state integer arithmetic if the operand is of type var. The .F-, .F1-, .F2-, and .F3- operators
carry out a floating-point subtraction from 0.0, and return the value of the result.
For the floating-point unary operators, the operand is required to have the same size as the operator
(single, double, or extended-double precision).
The result of the complement operator ~ is the bitwise complement of its operand. The operand must
evaluate to a data object; the result has the same type and size as the operand. The complement
operation for 4-state objects is defined in (3.7.7.6).
Page 61/164
LRM 2.6 © 2008-2019 Maia EDA
The result of the logical negation operator ! is of type bool. It has value false if the operand
evaluates true, and value true otherwise.
Syntax
type-name: one of
real1 real2 real3 int bitn varn
Examples
var8 i = 255; // i is 8`hff
real1 a = (real1)i; // a is 255.0F
real2 b = (real2)i; // a is 255.0 (64`h406f_e000_0000_0000)
real3 c = (real3)i; // a is 255.0L
int j = (int)b; // j is an integer, with value 8`hff
Example 47
Floating-point data may also be converted to a sized integer, with unused high bits discarded:
real2 b;
int d, e, f;
for(b = 254.0; b .F< 258.0; b = b .F+ 1.0) {
d = (int) b;
e = (bit8)b;
f = (bit1)b;
report "d: %d; e: %d; f: %d\n", d, e, f;
}
Example 48
When using the floating-point versions of the operators, both operands, and the operator itself, are
required to have the same size (single, double, or extended-double precision), and the result has that
size.
Syntax
multiplicative-expression:
unary-expression
multiplicative-expression * unary-expression
multiplicative-expression / unary-expression
Page 62/164
LRM 2.6 © 2008-2019 Maia EDA
multiplicative-expression % unary-expression
multiplicative-expression float* unary-expression
multiplicative-expression float/ unary-expression
Semantics
The *, /, and % operators, with no suffix, implement unsigned integer multiplication, rational division,
and remainder, respectively. The *#, /#, and %# operators implement the signed versions of these
operations. The unsigned and signed versions of the operators differ as follows:
1 If an operand requires extension, then the unsigned operators will zero-extend that operand,
while the signed operators will sign-extend that operand;
2 The unsigned operators view their operands as positive binary integers, and carry out an
unsigned operation; the signed operators view their operands as two's complement integers, and
carry out a signed operation.
The / and /# operators return the rational result truncated towards 0; the % and %# operators return
the remainder of the corresponding division (/ or /#) operation. This is often referred to as "truncating
division"1:
7 /# 3 = 2 rem 1
-7 /# 3 = -2 rem –1
7 /# -3 = -2 rem 1
-7 /# -3 = 2 rem –1
The relationship dividend = quotient * divisor + remainder holds for these operators.
When using the floating-point versions of the operators, both operands, and the operator itself, are
required to have the same size (single, double, or extended-double precision), and the result has that
size.
Syntax
additive-expression:
multiplicative-expression
additive-expression + multiplicative-expression
additive-expression - multiplicative-expression
additive-expression float+ multiplicative-expression
additive-expression float- multiplicative-expression
Semantics
The + and – operators, with no suffix, implement unsigned integer addition and subtraction. The +#
and -# operators implement the signed version of the operation. If an operand requires extension,
1
% implements a remainder operation, and not a modulus operation. / and % have the same definition in Maia and C, although
% is sometimes referred to as the 'modulus' operator in C. % is equivalent to the MOD function in Fortran 90, and the rem
operator in Common Lisp, Ada, and VHDL.
Page 63/164
LRM 2.6 © 2008-2019 Maia EDA
then the unsigned operators will zero-extend that operand, while the signed operators will sign-extend
that operand. The results of the unsigned and signed integer operations are otherwise identical.
Syntax
shift-expression:
additive-expression
shift-expression << additive-expression
shift-expression >> additive-expression
shift-expression .R<< additive-expression
shift-expression .R>> additive-expression
Semantics
The <<, >>, .R<<, and .R>> operators, with no suffix, implement the unsigned versions of the
operation. The same operators with a # suffix implement the signed versions of the operations. The
unsigned and signed operations differ as follows:
1 If the left operand requires extension (4.4.1), then the unsigned operators will zero-extend that
operand, while the signed operators will sign-extend that operand;
2 The >> operator carries out a logical shift (by shifting in 0), while >># carries out an arithmetic
shift (by duplicating the sign bit). The remaining shift and rotate operators have the same
behaviour, irrespective of whether or not they have a # suffix.
The result of E1 << E2 is E1 left-shifted E2 bit positions; the vacated bits are filled with zeroes.
The result of E1 >> E2 is E1 right-shifted E2 bit positions. The vacated bits are filled with zeroes for
the unsigned operator, or with a copy of the top bit of E1 for the signed operator.
The result of E1 .R<< E2 is E1 left-rotated E2 bit positions. Rotation occurs within a word whose
size is given by the size of the operator (4.4.1). If E1 requires extension, then it will be zero-extended
to the operator size if the operator is unsigned, or sign-extended to the operator size if the operator is
signed, before the rotation is carried out.
The result of E1 .R>> E2 is E1 right-rotated E2 bit positions. Rotation occurs within a word whose
size is given by the size of the operator (4.4.1). If E1 requires extension, then it will be zero-extended
to the operator size if the operator is unsigned, or sign-extended to the operator size if the operator is
signed, before the rotation is carried out.
When using the floating-point versions of the operators, both operands, and the operator itself, are
required to have the same size (single, double, or extended-double precision).
Page 64/164
LRM 2.6 © 2008-2019 Maia EDA
Syntax
relational-expression:
shift-expression
relational-expression < shift-expression
relational-expression > shift-expression
relational-expression <= shift-expression
relational-expression >= shift-expression
relational-expression float-compare shift-expression
Semantics
The operators return a result of type bool; the result is true if the specified relationship is true, and
false otherwise.
The > (greater than), < (less than), >= (greater than or equal), and <= (less than or equal) operators,
with no suffix, implement the unsigned integer comparisons. The >#, <#, >=#, and <=# operators
implement the signed integer comparisons. The unsigned and signed versions of the operators differ as
follows:
1 If an operand requires extension, then the unsigned operators will zero-extend that operand,
while the signed operators will sign-extend that operand;
2 The unsigned operators assume that their operands are unsigned binary, while the signed
operators assume that their operands are 2's complement. This affects the operator result, as
shown in the examples below.
If the operands contain any metavalues, all four relationships will be false (3.7.7.4). Otherwise, at least
one of the relationships will be true.
Examples
var4 r1, r2;
r1 = 0b1001; // 9 or -7
r2 = 0b0011; // 3
assert(r1 > 0); // 9 > 0
assert(r1 <# 0); // -7 < 0
assert(r1 > r2); // 9 > 3
assert(r1 <# r2); // -7 < 3
Example 49
The size of the relational operators is defined by the normal operator sizing rules (4.4.1), and
determines the number of bits of the operands which will be compared:
var4 r1 = 0b0110;
var4 r2 = 0b1000;
assert(r1 >$3 r2); // 6 > 0
assert(r1 <$4 r2); // 6 < 8
Example 50
Page 65/164
LRM 2.6 © 2008-2019 Maia EDA
1. both operands must be of the same type, where that type is int, var, kmap, or bool; or
2. both operands must be assignment-compatible structures (3.7.10.2); or
3. both operands must be assignment-compatible arrays (3.7.12.4).
The equality operators carry out a bitwise comparison, and so may be used for both integer and
floating-point comparisons.
Syntax
equality-expression:
relational-expression
equality-expression == relational-expression
equality-expression != relational-expression
Semantics
The equality operators are analogous to the relational operators, but have a lower precedence, and
may be used to test K-maps, booleans, structures and arrays for equality. They return a result of type
bool; the result is true if the specified relationship is true, and false otherwise.
If the operands contain any metavalues, then those metavalues are included in the test (3.7.7.5). For
any pair of operands, exactly one of the relationships is true.
Syntax
AND-expression:
equality-expression
AND-expression & equality-expression
Syntax
exclusive-OR-expression:
AND-expression
exclusive-OR-expression ^ AND-expression
Syntax
inclusive-OR-expression:
exclusive-OR-expression
Page 66/164
LRM 2.6 © 2008-2019 Maia EDA
inclusive-OR-expression | exclusive-OR-expression
Syntax
logical-AND-expression:
inclusive-OR-expression
logical-AND-expression && inclusive-OR-expression
Semantics
The && operator return a result of type bool. E1 && E2 yields true if both E1 and E2 are true, and
false otherwise. E2 is not evaluated if E1 is false.
Syntax
logical-OR-expression:
logical-AND-expression
logical-OR-expression || logical-AND-expression
Semantics
The || operator return a result of type bool. E1 || E2 yields true if either E1 or E2 is true, and
false otherwise. E2 is not evaluated if E1 is true.
Syntax
conditional-expression:
logical-OR-expression
logical-OR-expression ? expression : conditional-expression
Semantics
For the expression E1?E2:E3, E1 is evaluated first. E2 is evaluated only if E1 is true; E3 is evaluated
only if E1 is false. The result has the value of whichever of E2 or E3 was evaluated.
If E2 and E3 are of an arithmetic type, and one or more of them is of type var, then the result is of
type var; otherwise, the result is of type int. E2 and E3 must otherwise be of the same type, and the
result is of that type.
Page 67/164
LRM 2.6 © 2008-2019 Maia EDA
4.5.18 Assignment operators
The assignment operators write a source location (an rvalue) to a destination location (an lvalue). The
left operand must be an lvalue. The left and right operands must be assignment-compatible.
Syntax
constant-assignment-expression :
assignment-expression
assignment-expression :
conditional-expression
unary-expression assignment_operator assignment-expression
assignment_operator: one of
= *= /= %= += -= <<= >>= .R<<= .R>>= &= ^= |=
Semantics
An assignment expression has the value of the left operand after the assignment, but is not an lvalue.
The type of the expression is the type of the left operand.
A simple assignment may optionally be sized or signed, or both. A size modifier specifies the number of
bits which will be copied from the source location, while the absence or presence of a # modifier
specifies whether these bits should be zero-extended or sign-extended, respectively. The compound
assignments, however, may not be signed or sized, because of the potential confusion over whether
the modifiers refer to the base operator, or to the assignment.
The assignment operator is best viewed as a hardware logic unit, which contains, and controls writes
to, a memory location. The unit has a fixed-size input bus, where the size of the bus is given by the
operation size. The input to the logic unit is found by truncating, or extending, the operand to the size
Page 68/164
LRM 2.6 © 2008-2019 Maia EDA
of the input bus. The unit always overwrites the entire memory location from the input bus, truncating
or extending the input bus as necessary.
This is illustrated in the diagram below, which shows a simple 2-input addition operation. In this
example, a 5-bit adder adds a 4-bit and a 3-bit register, both of which are zero-extended. The 5-bit
output is then sign-extended and written to a 6-bit register. The corresponding Maia code is:
var4 A;
var3 B;
var6 C;
C =# A +$5 B;
Example 51
The assignment in this example is unsized, but the operand is 5 bits, so the assignment operation size
is also 5 bits. The 5-bit result of the addition operation is then sign-extended to 6 bits when written to
the destination. The corresponding circuit is:
A
3 4
P 5
0 0 4
S
4 0 0
2 Q
0 0 C
B
Addition Assignment
operator operator
Examples
var6 d;
var2 e = 2;
int4 f = 4`b1001;
d = f; assert(d == 6`h09);
d =# f; assert(d == 6`h39);
d =$5 f; assert(d == 6`h09);
d =#$5 f; assert(d == 6`h39);
Example 52
Page 69/164
LRM 2.6 © 2008-2019 Maia EDA
4.5.19 Comma operator
Syntax
constant-expression :
expression
expression :
assignment-expression
expression , assignment-expression
Semantics
The left operand of a comma operator is evaluated as a void expression. The right operand is then
evaluated; the result has the type and value of the right operand.
4.6.1 Introduction
Maia provides floating-point arithmetic, comparison, and cast operators. Each operator is preceded by
.F, and has a different version for IEC 60559 single-precision, double-precision, and extended double-
precision operands1. These three sizes are identified by the suffixes 1, 2, and 3, respectively. The three
addition operators, for example, are .F1+, .F2+, and .F3+.
These operators are essentially equivalent to hardware floating-point units. The .F1+ operator, for
example, takes two single-precision operands, and returns a single-precision result. It is the
programmer's responsibility to ensure that the operands are appropriate. There are no dedicated
floating-point data types, and any data object may be used as an operand to a floating-point operator,
as long as it is correctly sized. From a hardware perspective, this is analogous to allowing any 64-bit
memory location to be connected to a 64-bit floating-point adder; the result returned by the adder will
make little sense if the input memory locations do not actually contain floating-point data.
While this is the obvious way to handle hardware descriptions, it is not how most general-purpose
programming languages (or HDLs) operate. Consider, for example, this C program2:
#include <stdio.h>
int main(void) {
double a = 2;
double b = 3 * a;
printf("3a is %3.1f (%lx)\n", b, *(long *)&b);
return 0;
}
Example 53
1
Verilog supports only a 64-bit real type, which corresponds to double-precision on all supported systems. The Verilog code
generator therefore does not support float and double-extended precisions; see (14.7.1).
2
This code assumes that 'long' and 'double' both contain the same number of bits; in general, it will only work on a 64-bit
machine. Note also that the actual bit pattern in a 'double' variable cannot simply be printed with an 'x' format; various casts are
required.
Page 70/164
LRM 2.6 © 2008-2019 Maia EDA
The Maia equivalent1 is:
int main(void) {
int64 a = 2.0; // or declare as real2 (4.6.2)
int64 b = 3.0 .F* a;
report("3a is %3.1f (%x)\n", b, b);
return 0;
}
Example 54
Maia makes no assumptions about the programmer's intentions. The constants 2.0 and 3.0 must
therefore be explicitly entered as floating-point values, and not integer values; the multiplication
operator must also be specified as .F*, rather than simply as a general-purpose *. (3.0 * a), for
example, carries out an integer multiplication, while (3 .F* a) multiplies the floating-point bit pattern
in a by the integer 3. In this context, only (3.0 .F* a) produces the expected answer of 6.0.
The strict requirements that floating-point constants must contain a decimal (or hexadecimal) point,
and that floating-point operators should be used for floating-point expressions, are relaxed in two
specific cases (4.6.1.1 and 4.6.1.2). These relaxations simplify the handling of time values.
(15) contains an example floating-point program, which calculates to 15 decimal digits. The program
is not as concise as one written in a general-purpose language, but Maia is a domain-specific language,
and will not normally be used for general floating-point arithmetic problems.
f() {
wait 1; // waits 1.0 time units
wait 1.0; // waits 1.0 time units
wait 2.0 .F* 1.5; // waits 3.0 time units
1
This code assumes that a double contains 64 bits, which is true of all supported systems. 'real2' may be used rather than
'int64', to avoid this assumption.
Page 71/164
LRM 2.6 © 2008-2019 Maia EDA
wait 2 .F* 1.5; // ERROR: '2' is not floating-point in this context
}
Example 55
4.6.2 Declarations
Maia has no floating-point data types, but any objects which are intended to hold float data must be
correctly sized for that data. On all currently-supported systems, single-precision data is 32-bit, and
double-precision data is 64-bit. However, extended double-precision may be 64-bit, 80-bit, or 128-bit.
The real1, real2, and real3 keywords are provided to avoid potential sizing problems; these are
correctly sized by the compiler for the underlying types. When used in a declaration, these keywords
are simply syntactic sugar for a correctly-sized variable:
real1 a; // equivalent to 'int32 a' on most systems
real2 b; // equivalent to 'int64 b' on most systems
real3 c; // equivalent to 'int64 c', 'int80 c', or 'int128 c' on most systems
report("real2 is %d bits\n", b`size);
Example 57
Note that these declarations do not flag to the compiler that a floating-point value is stored in a, b, or
c; the compiler has no interest in the contents of a data object. It is the programmer's responsibility to
track the meaning of any bit pattern in an object.
4.6.3 Operators
Table 18, Table 19, and Table 20 below list the floating-point arithmetic operators. These operators
have the same precedence and associativity as the corresponding integer arithmetic operators. The
binary arithmetic operators take two operands of the same size, and return a result of that size; the
comparison operators take two operands of the same size, and return a boolean result. An error will be
reported if the operands of any of these operators are incorrectly sized.
The operators have alternative textual names, which are listed in the tables below. If the size numeral
is omitted, it is assumed to be 2, for double-precision.
The compound assignment operators are not defined for floating-point data; the += operator, for
example, carries out an integer addition.
Page 72/164
LRM 2.6 © 2008-2019 Maia EDA
Floating-point data may be converted to a different precision, or to and from integer data using the
cast operators; see (4.5.6).
Syntax
float+: one of
.F1+ .F2+ .F3+ .F1ADD .F2ADD .F3ADD .F+ .FADD
float-: one of
.F1- .F2- .F3- .F1SUB .F2SUB .F3SUB .F- .FSUB
float*: one of
.F1* .F2* .F3* .F1MUL .F2MUL .F3MUL .F* .FMUL
float/: one of
.F1/ .F2/ .F3/ .F1DIV .F2DIV .F3DIV .F/ .FDIV
float-compare: one of
.F1< .F2< .F3< .F1LT .F2LT .F3LT .F< .FLT
.F1> .F2> .F3> .F1GT .F2GT .F3GT .F> .FGT
.F1<= .F2<= .F3<= .F1LE .F2LE .F3LE .F<= .FLE
.F1>= .F2>= .F3>= .F1GE .F2GE .F3GE .F>= .FGE
Page 73/164
LRM 2.6 © 2008-2019 Maia EDA
Double extended Form 1 Form 2
Page 74/164
LRM 2.6 © 2008-2019 Maia EDA
5 DECLARATIONS
5.1 Introduction
Syntax
declaration :
ivb-declaration ;
struct-declaration ;
stream-declaration ;
kmap-declaration ;
A declaration specifies the interpretation given to an identifier. A declaration that also reserves storage
is a definition; a definition creates an object. A definition specifies the type of the object (3.7), and its
storage duration and initialisation (3.5).
If the value of _StrictChecking is greater than 0, there must be exactly one declaration for every
unique identifier1 in a given scope (3.3) and namespace (3.4).
If the value of _StrictChecking is 0, scalar variables inside a function do not require an explicit
declaration (3.1.1). These objects are created when they are first written to, and are implicitly declared
to be of type var, with automatic storage duration. No initialisation is defined for these objects, since
they are created only when explicitly written to.
The general form of the declarations of all objects is the same. However, the declaration of a structure
or stream may simply declare a new type, rather than an object, and these declarations are therefore
listed separately in (5.5) and (5.6). The initialiser for a K-map has a unique form, and K-map
declarations are therefore listed separately in (5.7).
array-dimensionsA :
dimensionA
array-dimensionsA dimensionA
dimensionA :
[ constant-assignment-expressionopt ]
array-dimensionsB :
[ commaopt constant-expressionopt ]
When declaring an array (3.7.12), the dimensionality may be specified as part of the type, or following
the object name, or both; see (3.7.12.2).
1
The declaration for an identifier which is a function name occurs as part of the function definition.
Page 75/164
LRM 2.6 © 2008-2019 Maia EDA
The dimensionality may further be specified in two different forms. In the first form (array-
dimensionA) the dimensionality is specified as a list, with each dimension in its own [] brackets. In
the second form (array-dimensionB) (3.7.12.3), the dimensionality is specified as a comma-
separated list in a single pair of [] brackets.
When declaring an array, the first dimension expression may be omitted if it can be found from an
initialiser list:
int[] c = {0, 1, 2, 3, 4, 5}; // c is int[6]
int[][2] d = {{0,1}, {2,3}, {4,5}}; // d is int[3][2]
int[,2] e = {{0,1}, {2,3}, {4,5}}; // e is int[3,2]
int[,3,4] f; // ERROR: no initialiser
Example 58
5.3 Initialisation
Syntax
init-assignment :
= initialiser
initialiser :
assignment-expression
{ }
{ initialiser-list commaopt }
initialiser-list :
initialiser
initialiser-list , initialiser
comma : ,
The initialisers for all objects (except K-maps; see (5.7)) have the same form, which is given by init-
assignment.
An initialiser specifies the initial value of an object. If an object (or any sub-object within an aggregate)
has no initialiser, then that object or sub-object is given a default initial value (3.6).
All the expressions in an initialiser for an object which has static storage duration (3.5) must be
constant expressions.
The initialiser for a stream is always assigned automatically; if an explicit initialiser is given for a stream
(or a stream in an aggregate) then that initialiser is ignored.
The initialiser for a scalar object must be a single expression, optionally enclosed in braces. The
initialiser for a single K-map may also optionally be enclosed in braces.
The initialiser for a structure or array which has automatic storage duration must be a single expression
that is an assignment-compatible aggregate (3.7.10.2 and 3.7.12.4), or an aggregate initialiser, as
discussed below.
Page 76/164
LRM 2.6 © 2008-2019 Maia EDA
An aggregate initialiser is a brace-enclosed list of initialisers for the elements of the aggregate. The
aggregate object is initialised in order, by assigning successive expressions from the initialiser list to
successive element in the aggregate. Arrays are initialised in increasing subscript order (with the
rightmost subscript cycling fastest), and structures are initialised in member declaration order.
If an aggregate contains sub-aggregates, then the initialiser list may omit initialisation of a sub-
aggregate (by leaving the corresponding initialiser expression blank), or may specify a sub-aggregate
initialiser in braces. Initialisation therefore occurs recursively down through any braces in the initialiser.
An array of rank n therefore requires n brace levels for complete initialisation, if the array elements are
scalar objects.
If the initialiser for an aggregate expires before the aggregate is completely initialised, then the
remaining members of the aggregate are given default initialisations (3.6).
Examples
The initialisers for objects with static storage duration (external and static objects) must be constant
expressions:
int foo(void) { return 41; }
bar() {
static struct s1 {
int x, y;
} a = {foo(), 42}; // error: initialiser must be constant
struct s1 b = {foo(), 42}; // Ok
}
Example 60
1
Aggregate initialisation is, in practice, essentially identical to aggregate initialisation in C, except that conditions which are
generally flagged as warnings in C compilers (such as misaligned braces) are reported as errors in Maia. For this example, gcc
warns about missing braces on lines 8, 11, 14, and 15, while g++ warns about missing braces on lines 8, 11, and 15, and
reports an error on line 14. Maia reports errors for all of lines 8, 11, 14, and 15.
Page 77/164
LRM 2.6 © 2008-2019 Maia EDA
5.4 int, bit, var, and bool
Syntax
ivb-declaration :
storage-classopt typespec-ivb object-list
storage-class :
static
typespec-ivb :
typemark-ivb array-dimensionsopt
typemark-ivb : one of
int bitn ubit varn uvar bool
object-list :
object-item
object-list , object-item
object-item :
identifier array-dimensionsopt init-assignmentopt
5.5 struct
Syntax
struct-declaration :
struct-named-instance
struct-tdecl
struct-tdecl-with-objects
struct-named-instance :
storage-classopt typespec-struct object-list
typespec-struct :
struct identifier array-dimensionsopt
struct-tdecl :
struct { declaration-listopt }
struct identifier { declaration-listopt }
declaration-list :
declaration
declaration-list declaration
struct-tdecl-with-objects :
storage-classopt struct-tdecl array-dimensionsopt object-list
A struct-tdecl declares a new structure (3.7.10) type. A struct is an aggregate type, and consists
of a sequence of members, each of which may itself be of an arbitrary type. A struct may not,
however, contain an instance of itself.
Page 78/164
LRM 2.6 © 2008-2019 Maia EDA
5.6 stream
Syntax
stream-declaration :
stream-named-instance
stream-tdecl
stream-tdecl-with-objects
stream-named-instance :
storage-classopt typespec-stream object-list
typespec-stream :
stream identifier array-dimensionsopt
stream-tdecl :
stream { stream-defn-listopt }
stream identifier { stream-defn-listopt }
stream-defn-list :
stream-defn
stream-defn-list stream-defn
stream-defn :
mode constant-expression semicolonopt
file string semicolonopt
format string name-listopt semicolonopt
name-list :
identifier
name-list , identifier
stream-tdecl-with-objects :
storage-classopt stream-tdecl array-dimensionsopt object-list
A stream-tdecl declares a new stream (3.7.11) type. struct and stream declarations have the
same form, except that a stream declaration contains the attributes of a named file, rather than a
collection of members.
All three attributes (mode, file, and format) must be present in a stream declaration, and may occur
in any order.
Page 79/164
LRM 2.6 © 2008-2019 Maia EDA
5.7 kmap
Syntax
kmap-declaration :
storage-classopt typespec-kmap kmap-object-list
typespec-kmap :
kmap array-dimensionsopt
kmap-object-list :
kmap-object-item
kmap-object-list , kmap-object-item
kmap-object-item :
identifier array-dimensionsopt kmap-initialiseropt
kmap-initialiser :
= kmap-init
kmap-init :
kmap-const-list
{ }
{ kmap-init-list commaopt }
kmap-const-list :
kmap-constant
kmap-const-list kmap-constant
kmap-constant
constant
kmap-const
kmap-const : one of
x X z Z
kmap-init-list :
kmap-init
kmap-init-list , kmap-init
A kmap initialiser is composed of a list of constants, rather than assignment expressions, and so has
the form kmap-initialiser (rather than init-assignment). A kmap declaration otherwise has
the same form as the declaration of an object of any other type.
For the purposes of initialisation, a kmap is regarded as a scalar object. The initialiser for this scalar is a
kmap-const-list, which is a whitespace-separated list of constants or the characters x, X, z, or Z.
Any constants in the list must have one of the values 0 or 1.
Page 80/164
LRM 2.6 © 2008-2019 Maia EDA
6 STATEMENTS
6.1 Introduction
Syntax
statement :
statementA
statementB
statementA :
compound-statement
selection-statement
iteration-statement
jump-statement
trigger-statement
wait-statement
exec-statement
exit-statement
assert-statement
report-statement
label : statementA
statementB :
expression-statement
drive-statement
label : statementB
label : identifier
A statement specifies an action to be performed. Except where indicated otherwise, statements are
executed in sequence.
Statements may optionally be preceded by an identifier and a ':', where the identifier labels the
statement. The default label has special significance, and may not be used outside a switch
statement. Labels may be used to disambiguate drive statements (6.8), but otherwise have no
significance1.
Statements are divided into two groups (statementA and statementB). This division has no
significance, other than to define the statements which may follow an if statement or a while
statement, when the controlling expression for that if or while is not enclosed in parentheses. If the
parentheses are omitted, the following statement must be a statementA; if they are included, the
following statement may be any statement.
Simulation time may be advanced only by executing a wait statement, or a drive statement; all other
statements execute in zero time. Expression evaluation order is completely defined, and any expression
which includes time-consuming function calls always has a defined result.
1
C allows a label to be the destination of a goto statement.
Page 81/164
LRM 2.6 © 2008-2019 Maia EDA
6.2 Compound statement
Syntax
compound-statement :
{ block-item-listopt }
block-item-list :
block-item
block-item-list block-item
block-item :
declaration
statement
A compound statement groups a set of declarations and statements together as a single syntactic unit.
Syntax
selection-statement :
if expression statementA
if expression statementA else statement
if ( expression ) statement
if ( expression ) statement else statement
switch expression { switch-bodyopt }
switch-body :
labelled-statement-switch
switch-body labelled-statement-switch
labelled-statement-switch :
case constant-expression : block-item-list
default : block-item-list
Page 82/164
LRM 2.6 © 2008-2019 Maia EDA
Semantics
Parentheses may optionally be placed around the controlling expression if desired1. However, there is a
potential parsing ambiguity for the if and if-else statements if the parentheses are omitted, and
the associated statement is therefore required to be statementA (a subset of statement) in this
case. In other words, if the parentheses are omitted, the associated statement may not be a single
expression statement, or a single drive statement.
If the controlling expression evaluates true, the associated statement is executed; if it evaluates false,
the associated statement is not executed.
If the controlling expression evaluates true, the first statement is executed; if it evaluates false, the
second statement is instead executed.
The else clause is associated with the nearest lexically preceding if.
There may be at most one default label in the switch statement. A case label expression must be a
constant expression; the values of the case label expressions must all be unique within a given switch
statement2.
If there is no default label, and the value of the controlling expression does not match the value of
any of the case labels, then no part of the switch body is executed.
The controlling expression and the case labels may be of different types if one is of type var, and the
other is of type int or bit. In this case, the case label expression is converted to the type of the
controlling expression, as if by assignment, before comparison with the value of the controlling
expression.
1
C requires parentheses here.
2
If a switch statement itself includes one or more other switch statements, then those switch statements may themselves
have default labels, and may have case label expressions which duplicate the expressions in the first switch statement.
Page 83/164
LRM 2.6 © 2008-2019 Maia EDA
6.5 Iteration statements
The while, do, and for iteration statements execute a loop under the control of a controlling
expression. The for all statement executes a loop for all values of a control object.
Syntax
iteration-statement :
while expression loop-bodyA
while ( expression ) loop-body
do loop-body while expression ;
for ( expressionopt ; expressionopt ; expressionopt ) loop-body
for all identifier loop-body
loop-bodyA:
statementA
loop-body:
statement
Semantics
The controlling expression of the while, do, and for iteration statements must be of boolean type.
These statements execute the loop body (loop-bodyA or loop-body) while the controlling
expression is true; the iteration statement is terminated when the controlling expression evaluates
false.
The for all iteration statement executes the loop body for all values of the control object,
incrementing sequentially from 0.
Parentheses may optionally be placed around the controlling expression if desired1. However, there is a
potential parsing ambiguity if the parentheses are omitted, and the loop body is therefore required to
be statementA (a subset of statement) in this case. In other words, if the parentheses are omitted,
the loop body may not be a single expression statement, or a single drive statement.
1
C requires parentheses here.
2
C requires parentheses here.
Page 84/164
LRM 2.6 © 2008-2019 Maia EDA
1 If E1 is present, it is evaluated once, as a void expression. E1 is generally a loop initialisation
operation.
2 E2 is then evaluated; it is the controlling expression. If E2 is omitted, it is given the value true.
If E2 evaluates false, execution continues at the statement after the for statement; if it
evaluates true, the loop body is executed (step 3).
3 The loop body is executed; if E3 is present, it is then evaluated as a void expression. Execution
then resumes at step 2.
A continue statement in the loop body branches to a point just before E3; in other words, E3 is
always evaluated after a continue.
The for all statement is primarily useful for iterating over all values of a 'small' variable1, and avoids
the complexity of handling the wrap-around of the variable, and the use of signed or unsigned
comparisons in the equivalent loop control expression.
If the identifier names a mode 1 stream, the for all statement is equivalent to:
identifier = 0;
do {
loop-body
} while((++identifier)'offset != 0);
Example 62
The for all statement may therefore be used to iterate over all lines of a mode 1 stream
(3.7.11.1.7).
Syntax
jump_statement :
continue constant-expressionopt ;
break constant-expressionopt ;
1
This might be useful, for example, if it is necessary to apply all values of an 8-bit variable to a DUT. Care should be taken not
identifier`size
to use a 'large' variable as the loop control variable; the number of loop iterations is 2 .
Page 85/164
LRM 2.6 © 2008-2019 Maia EDA
return expressionopt ;
If the level expression is omitted, it defaults to 1. A one-level continue jumps to the end of the loop
body of the closest enclosing iteration statement1. continue 2 jumps to the end of the loop body of
the next enclosing iteration statement, and so on. It is an error if the continue level is less than 1, or
greater than the number of enclosing iteration statements2.
Examples
The "loop-continuation portion" of an iteration statement is an implicit null statement at the end of the
loop body. This null statement is jumped to by a continue:
while(a()) {
if(b())
continue; // jumps to label1
c();
label1: ;
}
do {
if(b())
continue; // jumps to label2
c();
label2: ;
} while(a());
for(;;) {
if(b())
continue; // jumps to label3
c();
label3: ;
}
for all x {
if(b())
continue; // jumps to label4
c();
label4: ;
}
Example 63
1
A continue which has no level specified therefore has the same behaviour as C's continue statement.
2
It is therefore an error if the continue statement does not appear inside an iteration statement.
Page 86/164
LRM 2.6 © 2008-2019 Maia EDA
6.6.2 The break statement
The break statement causes termination of an enclosing switch or iteration statement. The break
has an associated level, which is given by the optional constant expression, and which specifies which
enclosing switch or iteration statement should be terminated.
If the level expression is omitted, it defaults to 1. A one-level break terminates the closest enclosing
switch or iteration statement1. break 2 terminates the next enclosing switch or iteration
statement, and so on. It is an error if the break level is less than 1, or greater than the number of
enclosing switch and iteration statements2.
Examples
The optional return expression may be used to return a value to the caller. The expression is returned
as if by assignment to a temporary object which has the declared type of the function; the value of the
function is the value of this temporary object. It is an error if a function which has been declared to be
of void type contains any return statements with an associated return expression.
If a function has a non-void type, and control is returned to the caller by reaching the terminating } or
by executing a return statement with no associated return expression, then the value returned to the
caller will be the current value of the predefined result variable. Every non-void function has an
implicit result variable, which has the same type as the function itself. The result variable is
default-initialised (3.6) when the function is entered.
1
A break which has no level specified therefore has the same behaviour as C's break statement.
2
It is therefore an error if the break statement does not appear inside a switch or iteration statement.
Page 87/164
LRM 2.6 © 2008-2019 Maia EDA
Examples
int f1(void) {
result = 2; // f1() returns 2
}
int f2(void) {
result = 2;
return 4; // f2() returns 4
}
int f3(void) {
struct s1 res = f4();
assert(res.a == 4'b0000 && res.b == 4'bxxxx);
return; // f3 returns 0 (the default value of result, of type int)
}
struct s1 {
int4 a;
var4 b;
}
struct s1 f4(void) {}
Example 65
trigger-condition :
trigger-count expression
trigger-count : one of
when
when all
The trigger statement posts a trigger function (7.6) for later execution. The postfix-expression
must denote a trigger function; the () parentheses contain a possibly empty comma-separated list of
expressions. These expressions form the actual parameters to the trigger function; the number of
arguments must agree with the number of formal parameters to the function. The actual parameters
are sampled when the trigger statement is executed; the sampled values are stored, and are supplied
to the formal parameters, as if by assignment, when the trigger function starts execution.
The trigger function starts execution when the expression supplied in trigger-condition is
sampled true. This expression must be of boolean type. Sampling occurs on the edges of the sample
clock defined by the triggered drive declaration for the function (9.2.3).
If trigger-count is specified as when, the trigger function executes only once, when the trigger
condition is first sampled true.
If trigger-count is specified as when all, the trigger condition automatically re-arms when the
trigger function completes execution; the condition is then checked on subsequent sampling clocks. A
run-time error is reported if the trigger condition again becomes true while the function is running; it is
not possible to run multiple instances of the same trigger function.
Page 88/164
LRM 2.6 © 2008-2019 Maia EDA
6.8 Drive statement
Syntax
vfile-drive-statement :
base-drive-statement
drive-statement :
base-drive-statement
triggered-drive-statement
base-drive-statement :
[ hdl-inputs ]
[ hdl-inputs ] -> pipe-levelopt [ hdl-outputs ]
triggered-drive-statement :
-> [ hdl-outputs ]
pipe-level :
constant
identifier
( expression )
hdl-inputs :
hdl-expression-list
hdl-outputs :
hdl-expression-list
hdl-expression-list :
hdl-expression
hdl-expression-list , hdl-expression
hdl-expression :
assignment-expression
drive-directiveopt
drive-directive : one of
- .c .C .x .X .z .Z .r .R
The syntax of the base-drive-statement is shown only for procedural programs (drive-
statement), and not for testvector programs (vfile-drive-statement), for simplicity (1.1). For a
testvector program, an hdl-expression must be a constant-assignment-expression or a
directive, rather than an assignment-expression or a directive.
The optional pipe-level specifies the expected number of pipeline levels (9.2.4) on the hdl-
outputs; it defaults to 1 if it is omitted1. The level may be an arbitrary expression if required (and so
may change at runtime). If the level is not a constant or an identifier, it must be enclosed in
parentheses () to avoid parsing ambiguities.
1
In other words, hdl-inputs set up to a clock edge, and hdl-outputs are generated by the same clock edge.
Page 89/164
LRM 2.6 © 2008-2019 Maia EDA
6.9 Wait statement
Syntax
wait-statement :
wait expression ;
The wait statement causes the currently-executing function to pause execution for the time given by
the expression. The expression is interpreted as a floating-point number1, in the timescale units
specified in the DUT section (which default to nanoseconds).
function-name : identifier
The exec statement creates a new thread (10.5), and initiates execution of the named function in that
thread. The statement returns immediately (in zero simulation time), and the newly-created thread
starts execution immediately.
argument-expression-list must contain at least one actual parameter. The first actual must be the name
of an int object, which is passed by reference to the new thread function. The new Thread ID is
returned to the object.
The exit statement terminates program execution; the expression is an exit code. The exit code may,
or may not, be returned to the operating system, depending on the code generator (14.7.4).
1
Even if it has no decimal point; see (4.6.1.1)
Page 90/164
LRM 2.6 © 2008-2019 Maia EDA
The assert expression is required to be of boolean type. If the expression evaluates false, an error is
reported on stdout and in the log file (if output is enabled); the error message includes the source file
name and line number of the failing assert statement.
The assert expression may optionally be followed by a report statement. If the report statement
is present, the report message is included as part of the assertion failure output.
The number of assertion failures which are required to terminate a program is set by a compiler switch;
see (14.5).
printf-varargs :
string
string , pv-list
pv-list :
pv-element
pv-list , pv-element
pv-element :
assignment-expression
string
The report statement provides formatted output to the console (stdout). The first argument is a
format string, which specifies how subsequent arguments are converted for output. The number of
supplied arguments must match the number expected for the format.
The format is a character sequence, which is composed of zero or more ordinary characters (not %),
which are copied unmodified to stdout, and conversion specifications. The conversion specifications
result in the fetching of zero or more arguments, which are written to stdout.
report is broadly compatible with C's fprintf, with the exceptions noted in (6.13.3). However,
2019.9 relies on the Verilog simulator to generate output, and different simulators have widely different
support for formatted output. It is likely that there will be some deviation from this specification,
depending on which simulator is used (14.7.2).
A conversion specification is introduced by the character %. After the %, the following appear, in
sequence:
1. zero or more flags, in any order, which modify the meaning of the conversion specification.
The +, -, ' ' (space), #, and 0 flags are recognised, but are not implemented in 2019.9; a
warning is issued if these flags are detected.
Page 91/164
LRM 2.6 © 2008-2019 Maia EDA
2. An optional minimum field width. If the output has fewer characters than the field width, it is
padded with spaces on the left. The field width must be a decimal integer.
3. An optional precision that gives the minimum number of digits to appear for the d, i, b, o, u,
x, and X conversions, the number of digits to appear after the decimal point character for a,
A, e, E, and f conversions, the maximum number of significant digits for the g and G
conversions, or the maximum number of output characters for s conversion. The precision
takes the form of a period (.) followed by an optional decimal integer; if the integer is
omitted, it defaults to 0.
L Specifies that a following real conversion specifier applies to an extended double-precision float
If the length modifier is omitted, and the following conversion specifier is a real conversion specifier,
then the length modifier defaults to D. The Verilog code generator does not support the F and L length
modifiers, and an error is reported if they are used.
Page 92/164
LRM 2.6 © 2008-2019 Maia EDA
x,X (u), or unsigned hexadecimal (x and X). The letters abcdef are used for x conversion,
and ABCDEF for X conversion. The precision specifies the minimum number of digits to
output; if the argument can be specified in fewer digits, it is padded with leading zeros.
The default precision is 1.
f The argument is assumed to be floating-point, and is output in decimal in the style
[−]ddd.ddd, where the number of digits after the decimal point is equal to the precision
specification. The precision defaults to 6 if it not specified; if the precision is 0, no
decimal point character appears. If a decimal point character is output, at least one digit
will appear before it. The value is rounded to the appropriate number of digits.
The precision specifies the number of significant digits. If the precision is zero, it is taken
as 1. Trailing zeros are removed from the fractional portion of the result; a decimal-point
character appears only if it is followed by a digit.
The letters x, p, and abcdef are used for the a conversion, while the letters X, P, and
ABCDEF are used for the A conversion.
Page 93/164
LRM 2.6 © 2008-2019 Maia EDA
The exponent always contains at least one digit, and only as many more digits as
necessary to represent the decimal exponent. If the value is zero, the exponent is zero.
b) Length modifiers are not required for arithmetic objects which are to be output as integers;
the fprintf hh, h, l, ll, j, z, t, and L length modifiers are therefore unused. The l and t
modifiers are reused as conversion specifiers; the remaining modifiers are reported as errors
d) The t, T, l, and b conversion specifiers are added, to handle integer time, floating-point
time, logical, and binary output, respectively
1
ISO/IEC 9899:1999 (E), §7.19.6.1
Page 94/164
LRM 2.6 © 2008-2019 Maia EDA
7 FUNCTIONS
7.1 Introduction
Maia supports both user functions (7.5) and trigger functions (7.7). User functions are conventional
functions which are executed as part of the normal user-initiated sequential program flow, while trigger
functions are run automatically, in response to defined trigger conditions. Both user and trigger
functions may execute in either zero or non-zero simulation time.
User function calls are primary expressions; they may appear anywhere where an expression may
appear. User functions (of a non-void type) always have a value, whether or not they explicitly return
data. A user function which returns nothing has the default value of the result variable (3.6).
Trigger functions are posted for later execution using trigger statements (6.7). A trigger statement
specifies a set of conditions (normally DUT outputs) which are examined on every controlling clock
edge, and the function, together with its actual parameters, which will be called when the condition is
found to be true. A return statement in a trigger function simply terminates execution of that function;
the function may not return data, and may not assign to result, and has no value.
User functions may be run in a new thread using exec statements (6.10). Any function initiated in this
way is referred to as a thread function (7.6). Thread functions are simply user functions which have
been initiated with an exec statement, but they have a number of restrictions which do not apply to
general user functions, and so are documented separately here.
All function names are globally visible. If a program includes any functions, then one of these functions
must be a user function named main. The main function is the program entry point; main should have
no parameters in 2019.9. main may return a value, but this value may, or may not, be returned to the
operating system (14.7.4). A program may be terminated only by returning from main, or by calling
exit.
7.2 Syntax
function-definition :
user-function-definition
thread-function-defintion
trigger-function-definition
user-function-definition :
user-function-typespecopt
function-name ( formal-listopt ) { sf-block-item-listopt }
user-function-typespec :
typespec-ivb
typespec-struct
typespec-stream
typespec-kmap
thread-function-definition :
void function-name ( formal-list ) { sf-block-item-listopt }
Page 95/164
LRM 2.6 © 2008-2019 Maia EDA
trigger-function-definition :
@ function-name ( formal-listopt ) { tf-block-item-listopt }
function-name : identifier
formal-list :
void
formal-item-list
formal-item-list :
formal-item
formal-item-list , formal-item
formal-typespec : user-function-typespec
sf-block-item-list : block-item-list
tf-block-item-list : block-item-list
In 2019.9 parameters may only be passed by reference to a function if that function is not time-
consuming (10.4); an error will be reported if an attempt is made to use call-by-reference with a time-
consuming function.
When a stream object is passed to a function, the function receives a handle to that stream. Passing a
stream by value therefore has exactly the same effect as passing that stream by reference, and any use
of '&' is redundant.
main() {
int i = 1;
int j = 2;
swap_val(i, j);
report("i is %d; j is %d\n", i, j); // reports 'i is 1; j is 2'
Page 96/164
LRM 2.6 © 2008-2019 Maia EDA
swap_ref(i, j);
report("i is %d; j is %d\n", i, j); // reports 'i is 2; j is 1'
}
Functions do not need to be declared before use. The definition of a function also serves as its
declaration.
Page 97/164
LRM 2.6 © 2008-2019 Maia EDA
Recursion is not supported in 2019.9 (14.7.5).
While user and thread functions are syntactically and semantically identical, there are a number of
usage restrictions for thread functions.
Thread functions do not return to the caller, and so must be declared with a void return type. There
must be at least one formal parameter, and the first formal must be a reference to an integer. When
the function starts execution, this parameter will contain the new thread ID. This thread ID is also
returned to the caller:
void main() {
int tid;
exec f1(tid);
report("%t: started thread %d\n", _timeNow, tid); // "1 ns: started thread 1"
}
2 Trigger functions may only be posted for later execution via a trigger statement (6.7); they
cannot be 'called' in the conventional way. Trigger functions therefore have no value and
cannot be used in expressions. The parameters to a trigger function are sampled when the
function is posted, and not when the function eventually starts execution.
3 Trigger functions may not return a value; it is an error to use the result variable or to
return an expression
Page 98/164
LRM 2.6 © 2008-2019 Maia EDA
4 Trigger functions may not execute wait statements, and may not post trigger functions;
user functions can do both
5 User functions may use any sequential drive statement, but may not use any triggered drive
statements. Trigger functions may only use one drive statement; this is the appropriate
triggered drive statement declared in the DUT section (triggered-drive-
declaration)
6 A user function may call any other user function. A trigger function may only call user
functions that:
execute in zero time (in other words, are not time-consuming), and
do not execute trigger statements
A trigger function may advance time only by executing the drive statement associated with that trigger
function. When a trigger function is initiated, it may execute zero or more of these drive statements
before terminating.
Page 99/164
LRM 2.6 © 2008-2019 Maia EDA
8 DUT SECTION
8.1 Introduction
Maia communicates with an external HDL program which describes the DUT (Device Under Test). In
order to carry out this communication, Maia requires some information about the DUT, and about any
test vectors (drive statements) which will be used to test the DUT. This information is placed in the
DUT section.
A DUT section (DUT-definition) is only required if there are drive statements in the program. There
may be a maximum of one DUT section, which may appear anywhere where a function is permissible.
Syntax
DUT-definition :
DUT { dut-declaration-listopt }
dut-declaration-list :
dut-declaration
dut-declaration-list dut-declaration
dut-declaration :
module-declaration semicolonopt
sequential-drive-declaration semicolonopt
triggered-drive-declaration semicolonopt
dut-signal-declaration semicolonopt
clock-declaration semicolonopt
enable-declaration semicolonopt
timescale-declaration semicolonopt
timing-constraint semicolonopt
If the DUT section is present, and the program contains drive statements, then the DUT section must
include:
1. one module declaration
2. one or more drive declarations (sequential or triggered)
3. zero or more internal DUT signal declarations
4. zero or more clock declarations
5. zero or more enable declarations
6. zero or one timescale declarations
7. zero or more timing constraint declarations
The DUT section must contain at least one clock declaration (8.5) if any clocked logic is to be tested. If
it is only necessary to carry out delta-delay simulations on rising-edge clocked logic, then no timescale
or timing constraint declarations are required, and the default clock waveform is sufficient. If it is
necessary to carry out delta-delay simulations on falling-edge clocked logic, then the default waveform
must be replaced with one that has a falling edge before a rising edge (8.5.3).
Page 100/164
LRM 2.6 © 2008-2019 Maia EDA
DUT section declarations may appear in any order; declarations may optionally be semicolon-
terminated. DUT declarations have exactly the same lexical structure as the rest of a Maia program,
with the following exceptions:
a) If a module declaration contains a list of parameter values, then the parameter text
(modparam-text; everything between the #( and ) terminators) is not analysed, and is
duplicated exactly in the testbench output
b) Any Verilog-2001 attributes (attribute) are ignored and copied directly to the testbench
output, without analysis
c) Adjacent strings are not concatenated in a DUT section; every occurrence of a sequence of
characters between double-quote characters (") is a separate token.
The identifiers in a DUT section (with the exception of labels) must be valid identifiers for the target
language (dotted-identifier), since they will be duplicated exactly in the testbench output. If a
target identifier (a module or port name, for example) is not a valid Maia identifier, then it must be
enclosed in double quotes in the DUT section; for example:
DUT {
module "\VHDL.Extended.Ident\" (input A, B; output C);
[A, B] -> [C];
}
Example 69
If the DUT has a Verilog 2001-style module definition1, then that definition may normally be cut-and-
pasted directly into the Maia code. This Verilog definition of a FIFO module, for example, may be
entered directly, with no changes, as a Maia module declaration:
DUT {
module fifo // unmodified Verilog module definition; reused
(input [7:0] in, // as a Maia module declaration
input clk, read, write, reset,
output [7:0] out,
output full, empty);
...
}
Example 70
Maia understands Verilog port declarations, and ignores the information that it doesn't require (the
signed, reg, integer and time keywords, the various net_type keywords, attributes, and port
initialisers). This code implicitly declares 8 external variables which may be used anywhere in the Maia
code, as if the following explicit external declarations had been made:
1
Often (incorrectly) known as an 'ANSI-C' style definition
Page 101/164
LRM 2.6 © 2008-2019 Maia EDA
var8 in, out;
var1 clk, read, write, reset, full, empty;
If no Verilog module definition is available (the code is VHDL, for example), or if the module definition
is in a pre-2001 form, then it will be necessary to derive a 2001-style equivalent to place in the Maia
code (8.2.3).
If the module definition is parameterised, or if it is necessary to pass parameter values into the
instantiated module (to override default parameter values in that module), then the module definition
will require some modification before being re-used as a Maia module declaration. No changes to the
HDL source code are required.
This is a generic FIFO module, which defaults to 4-bit input and output buses, and a depth of 6 words.
A generic FIFO cannot be tested; a specific instance of that FIFO must be tested. The Maia declaration
is essentially an instantiation of a specific instance. To create the instantiation, two things must be
done:
2. any parameters required in the module must be passed into the module.
If it is necessary to test generic_fifo with 16-bit ports, and an 8-word depth, then the following
module declaration can be used:
DUT {
module generic_fifo
#(.MSB(15), .LSB(0), .DEPTH(8))
(input [15:0] in, // must use '15:0', not 'MSB:LSB'
input clk, read, write, reset,
output [15:0] out,
output full, empty);
...
}
Example 72
The mechanism used to assign parameters is identical to Verilog's "module instance parameter value
assignment", which is the preferred way to assign values to module parameters in Verilog-2001.
An @ (U+0040) character may be used to introduce the parameter list, rather than the # character, if
preferred. This may be necessary if an external preprocessor is used1. If the @( syntax is used, then
1
#( will generate an error in a C-compatible preprocessor.
Page 102/164
LRM 2.6 © 2008-2019 Maia EDA
there must be no whitespace between the two characters; if the #( syntax is used, then whitespace
may be inserted between the two characters.
The entire parameter list is copied verbatim to the testbench output, with no analysis. The list may
contain anything which is acceptable to the Verilog simulator. This example shows named association;
ordered list assignment may be used if preferred.
a) the module declaration contains an incorrect name, port size, direction, or parameter list
Note, however, that port length mismatches may not be reported as errors by the Verilog simulator.
This list (list-of-port-declarations) is also required for signal declarations (the 'ports' are
actually internal signals for signal declarations, but the syntax is identical). The list is identical to
Verilog's list_of_port_declarations2, with the exceptions that the Verilog definition has been
refactored to remove ambiguities, semicolon separators have been added (for compatibility with some
Verilog tools), and the @ syntax is added for parameter lists.
The full list-of-port-declarations is parsed and checked, but the items which are not required
are ignored (the attributes, modifiers, initial assignments, and so on).
1
The Verilog LRM requires commas in port lists, but some tools also support semicolons, due to historical confusion in the
LRM.
2
IEEE Std 1364-2005, 12.3.4
Page 103/164
LRM 2.6 © 2008-2019 Maia EDA
Note that a comma-separated list of names shares the same direction and port size, which appears at
the beginning of a sub-list; semicolons are illegal in this sub-list. Semicolons may only be used to
separate different groups of declarations (where a new input, output, or inout keyword appears).
8.2.4 Syntax
module-declaration :
attributeopt module module-identifier module-paramsopt list-of-port-declarations
module : one of
module macromodule
module-identifier : videntifier
module-params :
@( modparam-text )
#( modparam-text )
list-of-port-declarations : ( port-declaration-listopt )
port-declaration-list :
port-list-first-item
port-declaration-list port-list-next-item
port-list-first-item :
attributeopt inout iodecl-modifiersopt port-identifier
attributeopt input iodecl-modifiersopt port-identifier
attributeopt output iodecl-modifiersopt port-identifier
port-list-next-item :
, port-list-first-item
; port-list-first-item
, port-identifier
port-identifier :
videntifier
videntifier = constant-expression
videntifier :
dotted-identifier
string
iodecl-modifiers :
iodecl-modifier
iodecl-modifiers iodecl-modifier
iodecl-modifier :
range
modifier
modifier : one of
integer reg signed time supply0 supply1 tri tri0 tri1 triand trior uwire
wand wire wor
Page 104/164
LRM 2.6 © 2008-2019 Maia EDA
attribute : see below
dotted-identifier : see below
modparam-text : see below
The definitions of attribute, dotted-identifier, and modparam-text have been omitted for
simplicity. attribute is an optional Verilog-2001 attribute, while dotted-identifier is a Verilog
dotted identifier. modparam-text contains the entire contents of the parameter list, which is copied
verbatim to the output testbench, without analysis.
This example (a complete testvector-program) shows a simple declaration, and some drive
statements which use that declaration:
DUT {
module test1(input D1, D2, CLK; output Q)
create_clock CLK
[D1, D2, CLK] -> [Q] // the drive declaration
}
[0, 1, .C] -> [0] // drive statement 1
[1, 0, .C] -> [1] // drive statement 2
Example 74
The declaration is required to allow Maia to determine that, in the first clock cycle, signals D1 and D2
should be driven with 0 and 1 respectively, signal CLK should be driven with a default clock waveform,
and signal Q should be tested against 0.
Any signals used in a drive declaration must themselves be declared elsewhere as a DUT port (8.2.3),
or as an internal DUT signal (8.4). Any signals on the LHS of a drive declaration must have an input
or inout direction; any signals on the RHS must have an output or inout direction.
8.3.1 Syntax
sequential-drive-declaration :
drive-declaration
identifier : drive-declaration
triggered-drive-declaration :
@ identifier drive-declaration
@ identifier { constant-expressionopt } drive-declaration
drive-declaration :
[ hdl-inputs-decl ]
[ hdl-inputs-decl ] -> [ hdl-outputs-declopt ]
hdl-inputs-decl : videntifier-list
hdl-outputs-decl : videntifier-list
Page 105/164
LRM 2.6 © 2008-2019 Maia EDA
videntifier-list :
videntifier
videntifier-list , videntifier
For the sequential form, the LHS must contain at least one signal. The entire RHS is optional; it may be
omitted if the drive statement is expected to test nothing (if it used simply for internal DUT state
preload, for example). Sequential drive declarations may be either clocked or combinatorial (8.3.2).
The triggered form must be preceded by the name of the corresponding trigger function, including the
@ character. If there is any ambiguity in the function name, a complete signature should be provided
(with the number of parameters in {} braces). There must be exactly one signal on the LHS, and that
signal must be declared as a clock (a triggered drive declaration is therefore also a clocked drive
declaration).
main() {
// drive statements which are matched to declaration 1:
[x+y]; // decl 1: drive A with x+y
1
The term 'clocked' is used in preference to 'sequential' to avoid confusion with the software concept of sequential execution.
Page 106/164
LRM 2.6 © 2008-2019 Maia EDA
// drive statements which are matched to declaration 3:
[x] -> [y,z]; // B: test against y; C: test against z
[x] -> [-,z]; // B: no test; C: test against z
[x] -> [y,] ; // B: test against y; C: no test
}
@trigfunc() {
// any drive statements in this trigger function must be matched to declaration 4
-> [x+y, 2]; // E: test against x+y; F: test against 2
}
Maia assumes that all the signals on the LHS of a clocked drive (apart from the clock itself) have setup
and hold requirements to that clock, and that all the signals on the RHS are produced from that clock,
and can be sampled at some time after the active clock edge (the active clock edge is determined from
the clock declaration (8.5), the pipeline delay (9.2.4), and any timing declarations (8.7)). The required
input setup and hold times, and the required output hold and delay times, are determined from any
timing declarations; defaults are used if there are no timing declarations.
A sorted input event list is created for each drive declaration, for all the setup and hold events. The .C
directive (9.3.1) is translated into two events; one for the clock rising edge, and one for the clock
falling edge.
1
There is no corresponding 'preload' directive or event; any drive of an internal signal is treated as a force, or preload.
Page 107/164
LRM 2.6 © 2008-2019 Maia EDA
3 Time is advanced to the next event in the input list, and step 2 is repeated until there are no
more events in the input list;
4 When the input event list is empty, time is advanced to the end of the clock cycle, as defined by
the create_clock declaration.
The period of time covered by input event processing is always a complete clock cycle, as defined by a
create_clock declaration. Step 1 always occurs at time 0 in the waveform; step 4 advances to the
last time slot in the waveform. The next drive statement will advance another clock cycle, although it
will not necessarily be the same clock. An error is reported if a timing declaration requests input events
which do not fit into the declared (or default) clock waveform. Note that time 0 in the waveform is the
Operating Point (10.1); in other words, it is the time at which the statements before and after the drive
statement are executed.
When the first clock edge is encountered during input event processing, it is also used to trigger a
checker process for each RHS signal. The checker process confirms that the signal has the value of the
corresponding RHS expression at the output delay point, and also confirms that the signal does not
change at any time outside the window defined by the output hold and output delay times (the stability
window). It is the checker process which is responsible for incrementing the internal test pass and fail
counters (_passCount and _failCount), and for reporting any DUT failures. The checker process
runs for one clock cycle from the first clock edge; it is an error if a timing declaration sets an output
hold or delay time which does not fit into this period.
The checker processes for the various outputs run independently, and two or more failures at the same
simulation time may be reported in different orders by different simulators, or during different runs with
a single simulator. This may cause confusion in regression tests in which failures are expected. If this is
the case, the output should be sorted before comparison1.
1
Error and warning messages from mtv have a simple format, with a file name in field 2, a line number in field 4, a time in fi eld
5, and a signal name in field 7. This Unix command will sort the lines of a logfile according to simulation time, with identical
times sorted according to signal name:
Page 108/164
LRM 2.6 © 2008-2019 Maia EDA
Given this declaration, the resulting testbench will treat ARST as a synchronous signal, which will be
driven some time before the rising edge of CLK. This is likely to lead to a test failure when a test vector
activates ARST. A failure will be reported if the change in ARST causes the DUT to change Q at any
time outside the expected times (between Q's output hold and output delay times).
Maia has no knowledge of whether an input is 'clocked' or 'combinatorial'. The only information it has is
the drive declaration which, in this case, requests that ARST be tested in the same way as PLD, D, and
CLK. Since this drive declaration contains a declared clock, Maia treats it as a clocked drive. The correct
way to test this DUT is to have two drive declarations:
DUT {
module counter(input ARST, PLD, D, CLK, output Q)
create_clock CLK // default clock declaration
[ARST] -> [Q] // async reset testing
[PLD, D, CLK] -> [Q] // synchronous operation testing
}
[0] -> [.X] // DUT output unknown
[1] -> [0] // reset the DUT
[0] -> [0] // turn off reset before sync testing
[1, 4, .C] -> [4] // sync preload of data '4'
[0, -, .C] -> [5] // count up
Example 77
Maia creates a 'cycle' time for combinatorial drives by using any relevant timing specifications, if there
are any, or by using a default value otherwise. Each execution of a corresponding drive statement
advances by this 'cycle' time.
There are essentially two choices for driving multiple combinatorial signals within this cycle. In the first,
the inputs are all driven at the same time, and tested at their individual tOD specifications. in the
second, the input timing is adjusted such that the outputs should nominally all change at the same
time, and the outputs are all tested together.
The second scheme has the advantage that a waveform display will quickly show what the spread of
real, rather than specified, output delays is, and Maia currently uses this scheme. It should be noted
that neither scheme is ideal where there are path dependencies (in other words, a single input affects
multiple outputs, or multiple inputs affect a single output), and Maia may automatically relax specific
timing constraints if a conflict is present; see the discussion on Constraint conflicts (8.9.7). A warning is
always issued if a conflict is present.
Unconstrained input-to-output paths are given a default tOD of 5ns. If no paths within a drive statement
are constrained then all paths will be given the default timing, giving a 10ns cycle time. In this case,
Page 109/164
LRM 2.6 © 2008-2019 Maia EDA
the two alternative drive schemes become identical; the inputs are all driven at the same time, and are
all sampled 5ns later, with a further delay of 5ns before the start of the next cycle.
However, it may be necessary to have more than one sequential drive declaration which has the same
signal count. In this case, the declarations must be distinguished by adding a label (an identifier) to
them. This label must be repeated in the drive statement:
DUT {
module test1(input D1, D2, D3, D4, CLK; output Q)
create_clock CLK
v1: [D1, D2, CLK] -> [Q] // label v1; signature (v1:3:1)
v2: [D3, D4, CLK] -> [Q] // label v2; signature (v2:3:1)
}
v1: [0, 0, 0, .C] -> [0] // signature (v1:3:1)
v2: [0, 0, 0, .C] -> [1] // signature (v2:3:1)
[0, 1, 0, .C] -> [1] // ERROR: unknown signature
Example 79
A signal declared in this way is treated identically to a port name declared in a module declaration
(8.2). The signal is automatically declared as a global identifier, and so may be read or written directly,
or it may be used in a drive statement.
When a signal is written to, it is automatically set to the required level inside the DUT using a 'force'
mechanism. The force disables any other internal drivers on that signal, to allow it to take on the
required value. The force must be explicitly released by using the .R directive (9.3.3).
create_clock identifies a DUT signal as a clock, and defines the required clock waveform. This
declaration is required only if the .C directive is used in a drive statement, or if a virtual clock is
required. A minimal create_clock declaration simply names a clock signal:
create_clock CLK // minimal clock declaration, default waveform
The named signal (CLK, in this case) must be declared elsewhere as a single-bit DUT input or output
port. If no waveform is specified, a default waveform is used; this is symmetrical, and has a period of
10ns. The waveform starts and ends at a low level (the 'default level'), and the rising edge occurs after
a short delay.
A clock declaration must include exactly one clock name (clock-name). The period, waveform, and
pipeline specifications are optional. If a waveform is specified, then a period must also be specified.
There must be a maximum of one period, waveform, or pipeline specification.
If the clock name is preceded by –name then the clock is a virtual clock. A virtual clock is a clock which
is not connected to the DUT; it is an error if the virtual clock name is also the name of a DUT port1.
Conversely, a clock which is declared without –name is a physical clock which connects to a DUT port.
In this case, it is an error if the physical clock name is not also the name of a DUT port.
Any constant values in a clock declaration are interpreted as floating-point numbers (4.6.1.1), in the
units of the declared timescale. The timescale defaults to nanoseconds if it is not specified.
Where create_clock is used to declare the waveform of a clock which is a DUT output, care should
be taken to ensure that the declaration matches the physical clock. The generated testbench does not
synchronise to the clock produced by the DUT, but instead assumes that the declared clock is correct.
8.5.1 Syntax
clock-declaration:
create_clock clock-item-list
clock-item-list :
clock-item
clock-item-list clock-item
1
Virtual clocks may be used to drive or sample DUT signals at times which are unrelated to the timing of physical clocks.
Page 111/164
LRM 2.6 © 2008-2019 Maia EDA
clock-item :
clock-name
clock-decl
clock-name :
vnameopt videntifier
vnameopt { videntifier }
vname : -name
clock-decl :
period
waveform
pipeline
list-of-sdc-constants :
sdc-constant
list-of-sdc-constants sdc-constant
list-of-sdc-constants , sdc-constant
sdc-constant :
constant
( constant-expression )
The first entry (or, in general, the odd-numbered entries) correspond to rising edges (10.2ns for CLK1,
and 15ns for CLK2), while even-numbered entries correspond to falling edges (20.6ns for CLK1, and
5ns for CLK2).
A waveform may be declared with any even number of edge times, for compatibility with other tools. If
there are more than two edges, the edge times must increase monotonically. Any timing specifications
will be relative to the earliest edge in this waveform.
Page 112/164
LRM 2.6 © 2008-2019 Maia EDA
A clocked drive statement defines the input and expected output signal behaviour over a single clock
cycle, as defined by the waveform. In other words, the 'start' of the clocked drive statement occurs at
the start of the clock waveform. This imposes two further restrictions on clock waveforms.
An error will be reported during compilation if any setup and hold constraints cannot be met using the
specified waveform. In this case, the waveform should simply be adjusted to accommodate the
required setup or hold time.
main() {
int plevel;
1
Square brackets in intervals are inclusive; round brackets are exclusive. The interval [0, 20) therefore includes 0, and
excludes 20.
Page 113/164
LRM 2.6 © 2008-2019 Maia EDA
...
[expr1, expr2, .C] ->plevel [expr3];
}
Example 82
In this example, plevel can change at runtime, and the compiler cannot determine in advance the
length of the checker pipeline associated with this drive statement. In this case, the maximum expected
pipeline size must be specified as part of the clock declaration for the clock associated with the drive
statement.
The maximum size is specified with –pipeline; in this case, it is declared to be 6 levels. A run-time
error will be issued if plevel is found to be greater than 6 whenever this drive statement is executed.
The compiler can always determine if a pipeline specification is required, and will report an error if it
has not been supplied.
8.5.5 Examples
Some examples of clock declarations are:
timescale ns
// C: 30.5ns period, with a rising edge at 10.2ns, and a falling edge at 20.6ns
create_clock "\top/C\" -period 30.5 -waveform { 10.2 20.6 }
8.6.1 Syntax
enable-declaration :
create_enable enable-list
Page 114/164
LRM 2.6 © 2008-2019 Maia EDA
enable-list :
enable-item
enable-list , enable-item
enable-item :
enable-port enable-port-sliceopt enable-control
enable-port : identifier
enable-port-slice :
. ( constant-expression )
. ( constant-expression : constant-expression )
enable-control :
( enable-levelopt control-sig )
enable-level : !
control-sig : identifier
enable-port must be a DUT output or inout; it may not be an internal DUT signal.
enable-port-slice has the same semantics as a bitslice (4.5.4.5). The indexes must be in range
for enable-port, and must not overlap with any indexes specified in any other create_enable for
this enable-port.
...
create_clock CLK; // default clock waveform, 10ns period
[CLK, DEN, WE, ADR, D]; // clocked write; nothing to test
[DEN, ADR, D] -> [D]; // combinatorial data bus read; D appears on both sides
}
main() {
...
// 1: write data to the RAM
[.C, 0, 1, e1, e2]; // write data e2 to address e1
Page 115/164
LRM 2.6 © 2008-2019 Maia EDA
// 2: read data from the RAM
[1, e3, .Z] -> [e4]; // read data from address e3, test against e4
}
Example 84
During the write operation, the testbench must set DEN to 0, to disable the RAM's output drivers.
During the read operation, the testbench must set DEN to 1, to enable the RAM's output drivers; it must
also tristate it's own D output drivers, so that it can read back the data driven by the RAM. If the
testbench accidentally enables both sources (by setting DEN to 1, and driving D with anything other
than .Z), then it will read invalid data (probably X) from the DUT, and the test will fail.
main() {
...
// 2: read data from the RAM
[1, e3, -] -> [e4]; // read data from address e3, test against e4
}
Example 85
The declaration states that D is a tristate signal, and that the testbench may only drive D when DEN is
0. When DEN is non-zero, the testbench automatically drives D with Z, ignoring whatever value has
been requested in the drive statement (in this case, a '–' was specified; this is a don't care condition).
Syntax
timescale-declaration:
timescale timescale-units
timescale-units : one of
s ms us ns ps fs
Page 116/164
LRM 2.6 © 2008-2019 Maia EDA
8.8 Time precision and representation
Times are represented as ordinary expressions. Time values are required primarily in the DUT section,
but are also required for wait statements (6.9) in user functions. Times are not explicitly entered with
a timescale unit (such as 'ns'); there is instead a timescale declaration which sets the required unit.
Times may be specified with an arbitrary precision. The compiler deduces the required precision by
examining all constants in time expressions, and setting the precision to the maximum precision found.
DUT { // a minimal DUT section with timing
module test(input C, D; output Q)
[C, D] -> [Q] // drive declaration
timescale ns // timescale declaration
create_clock C // clock declaration
D -> posedge C = (0.1 : 2.340) // tSU/tIH
posedge C -> Q = (1 : 3.1) // tOH/tOD
}
Example 86
In this example, the maximum precision is 2 decimal digits (note that trailing zeroes are ignored),
which is equivalent to a precision of 10ps (assuming that the body of the code does not contain any
wait statements with a higher timing precision).
In some circumstances it is possible to specify a timescale and precision which cannot be supported by
the target simulator (a ns timescale, for example, with 7 decimal digits of precision, is equivalent to a
timing precision of 0.1 fs, which is not in the range supported by Verilog simulators). The compiler will
report an error in this case.
Hex floating-point values may not be used for time values, since the compiler needs to count decimal
digits.
All time values in a DUT section must be constants or constant expressions (in other words, expressions
which can be evaluated during compilation). The time delay in a wait statement must also be a
constant or a constant expression; a dynamic wait can be achieved by putting a constant wait inside a
loop.
A constant expression may include floating-point operations. The calculated precision is not affected by
floating-point operators; the precision is derived solely from the constants in the code. If a floating-
point operation is used, then care should be taken to ensure that the correct number formats and
operators are used; (2 * 0.1), for example, is not equal to 0.2. In this case, (2.0 .F* 0.1)
should be used; see (4.6.1.1) and (4.6.1.2).
The purpose of the timing declarations is to ensure that any synthesis constraints were correctly
specified, interpreted and applied, and that the resulting netlist correctly implements those constraints.
The values in the Maia timing declarations should therefore be the same as any values that are
specified in the synthesis constraints file. The synthesis constraints should therefore be translated
directly into the equivalent Maia format. For this reason, Maia timing declarations are generally referred
to as 'constraints' in this manual, although they are not of course synthesis constraints.
There are four constraints which can be applied to DUT signals, which are:
A synchronous input setup time (tSU)
A synchronous input hold time (tIH)
A synchronous or combinatorial output hold time (tOH)
A synchronous or combinatorial output delay (tOD). The symbol tOD is generally used in this
manual for both cases, although tCO may also be used for synchronous (clocked) outputs.
Constraints may be applied either to DUT ports, or to internal DUT signals. tSU and tIH constraints
may be applied to both inputs and inouts; tOH and tOD may be applied to outputs and inouts.
8.9.1 Syntax
timing-constraint :
timing-constraint-LHS = timing-constraint-RHS
( timing-constraint-LHS ) = timing-constraint-RHS
timing-constraint-LHS :
tidentifier-list timing-drive tidentifier-list
timing-edge videntifier timing-drive tidentifier-list
tidentifier-list timing-drive timing-edge videntifier
tidentifier-list :
*
videntifier-list
1
Care should be taken when constraining some paths to a combinatorial output, and not others; see (8.9.7).
Page 118/164
LRM 2.6 © 2008-2019 Maia EDA
videntifier-list :
videntifier
videntifier-list , videntifier
timing-drive : one of
-> to
timing-edge : one of
posedge negedge
timing-constraint-RHS :
( constant-expression )
( tconstraint-RHS-valopt : tconstraint-RHS-valopt )
tconstraint-RHS-val :
constant-expression
the new value of the D input must be valid by, at the latest, 3.2ns before the rising edge of C
the new value of the D input must remain active for at least 2.1ns after the rising edge of C
3.2 2.1
t1 t2 t3
To match the required behaviour, the Maia testbench drives D in such a way as to make the following
guarantees, which are independent of any race conditions:
the DUT is guaranteed to see an invalid level on D (3.2+) ns before the clock edge, and a valid
level on D 3.2ns before the clock edge, where is the minimum timing precision
Page 119/164
LRM 2.6 © 2008-2019 Maia EDA
the DUT is guaranteed to see a valid level on D 2.1ns after the clock edge, and an invalid level on
D (2.1+) ns after the clock edge.
These two constraints are treated identically, except that the combinatorial constraint is tested from
any change in A, while the clocked constraint is tested only from a rising edge of C (or a falling edge,
if negedge is instead specified). For simplicity, the discussion below considers only the clocked case.
The old value of the Q output must hold until, at the earliest, 2.5ns after the rising edge of C
The new value of the Q output must be valid by, at the latest, 5.3ns after the rising edge of C
These requirements are best represented in terms of an analog waveform:
5.3
2.5
t1 t2 t3
The Maia testbench samples Q in such a way as to guarantee that a pass will be reported if, and only if,
the following three conditions are fulfilled, independently of any race conditions:
Any sampling event external to the DUT at a time 2.5ns after the rising edge of C (at t2) will
sample the old value of Q
Any sampling event external to the DUT at a time 5.3ns after the rising edge of C (at t3) will
sample the expected new value of Q
Any sampling event external to the DUT after time t3, up to and including the next t2, will
sample the new value of Q
The time specified by the value of tSU must be before (or at the same time as) the time specified by
the value of tIH.
If the tSU and tIH constraints are present, Maia will drive the relevant DUT input as described in
(8.9.2). The examples below assume a ns timescale, and show the times over which the input is driven
with the required value, as an inclusive range relative to the clock edge. The time interval [-2.1, -0.1],
for example, means that the input is driven with the required value for a total time of 2.0ns, which
starts 2.1ns before the clock edge, and ends 0.1ns before the clock edge.
D -> posedge C = ( 2.1: 0.1) // valid 2.2ns: [-2.1, 0.1]
D -> posedge C = ( 2.1:-0.1) // valid 2.0ns: [-2.1,-0.1]
D -> posedge C = (-0.5: 0.7) // valid 0.2ns: [ 0.5, 0.7]
D -> posedge C = (-0.5:-0.1) // ERROR: cannot both be <0
If no constraints are applied to a synchronous DUT input, Maia will assume that the input is untimed,
and will drive the input a short time before the first clock edge, and will maintain the driven value for a
short time after the first clock edge. The specific times are arbitrary and are chosen simply to ensure a
clear waveform display in a waveform viewer. The input is driven to X's at all other times.
tOD is the time at which the DUT output is guaranteed to have its new value. tOD must therefore be
greater than tOH; it is illegal for both to have the same value, unless both are zero (tOH = tOD = 0 is
a special case, which corresponds to an untimed or delta-delay DUT).
Note that tOH is not equivalent to a minimum output delay. A tOH specification requires that the
output has not changed at time tOH, while a minimum output delay specification requires that an
1
A 'controlling input' is the relevant clock edge for a sequential drive statement, or the last input to change for a combinatorial
drive statement.
Page 121/164
LRM 2.6 © 2008-2019 Maia EDA
output has, or may have, changed at that time. A minimum delay specification for a device output is of
little or no use.
The tOD specification should normally be the same as the value specified in the synthesis constraints
file, and so will normally be the expected maximum output delay. There is no mechanism to specify
minimum, typical, and maximum output delays; it would make no more sense to specify these in Maia
than it would in a synthesis constraints file.
If the tOH and tOD constraints are present for a given DUT output, Maia will test the output as
described in (8.9.3).
Synchronous output constraints must name a declared clock on the LHS, and must have a posedge or
negedge qualifier; for example:
posedge C -> Q = (2.0) // tOD (tCO) from C to Q
Example 90
A constraint which does not have a posedge or negedge qualifier is a combinatorial constraint, and
should be applied only to combinatorial outputs; for example:
A -> B = (2.0) // tOD from A to B
Example 91
If no constraints are applied to a DUT output, Maia will assume that the output is untimed, and will
sample it a short time after any controlling input has changed value. No output hold test is carried out;
in other words, there is no test that the output retains its previous value after any controlling input
changes value.
The wildcard is a shorthand notation for all the signals on the LHS of a drive declaration (excluding the
clock itself, for a clocked constraint), or all the signals on the RHS of a drive declaration. It is not a
shorthand for all possible paths from an input to an output; Maia does not analyse the DUT to find
these paths, but instead relies on any drive declarations.
Page 122/164
LRM 2.6 © 2008-2019 Maia EDA
For case (1), all the signals on the LHS of any drive declaration which is clocked from CLK1 are
assumed to have the same setup and hold to CLK1 (in this case, 0.8 and 3.1).
For case (2), all the signals on the RHS of any drive declaration which is clocked from CLK2 are
assumed to have the same tCO specification from CLK2 (in this case, 9.6).
Case (3) is applied to any combinatorial drive declaration which lists D as an output. In this case, all
signals on the LHS of those drive declarations are given the same tOH and tOD specification to D (in
this case, 2.1 and 5.3).
Case (4) is applied to any combinatorial drive declaration which lists E as an input. In this case, all
signals on the RHS of those drive declarations are given the same tOD specification from E (in this
case, 4.2).
Care should be taken when using a wildcard in a combinatorial drive, to avoid possible constraint
conflicts; see (8.9.7).
If it is necessary to fully test the timing of combinatorial outputs, then separate drive declarations
should be created for each constrained input-to-output path, and each should be tested separately. Any
attempt to test multiple combinatorial paths using single drive declarations will rapidly become
unworkable; see (8.9.7.1) and (8.9.7.2).
In this case, Maia arranges the DUT timing such that A is driven at relative time 0, and B is driven at
time 6. If D is to change, it should therefore take on its new value at, or before, time 18 (Figure 5).
This method tests both tOD requirements, and guarantees that a tOD failure will be detected
(although, if there is a failure, it cannot determine which of the two paths has failed). However, it is
now impossible to test both tOH requirements.
If the DUT is correctly implemented, D will remain valid until at least time 12. However, if D does
change in the time range (12, 14], Maia cannot tell whether the change is allowable (as a result of a
Page 123/164
LRM 2.6 © 2008-2019 Maia EDA
change in B), or a violation (as a result of a change in A, before A's hold time specification has
expired). For these constraints, the compiler will report that the hold time requirement has been
relaxed, and that the hold specification from A to D has been dropped.
0 6 12 18
The situation is worse if there are any further inputs to D, but they have not been constrained. Consider
this drive declaration:
[A, B, C] -> [D] // test a 3-in combinatorial circuit
Example 95
If only the two paths A-D and B-D have been constrained, Maia will set the unconstrained path from C
to D as follows:
The tOD value in the wildcard constraint is arbitrary, since it will be overridden by the higher-priority
individual specifications; however, it should be at least 6 to avoid a syntax error.
Page 124/164
LRM 2.6 © 2008-2019 Maia EDA
Example 97
If the D output is considered, and the same procedure is followed as in (8.9.7.1), then the times at
which the testbench must drive A and B are again as shown in Figure 5. However, if the E output is
now considered, it is apparent that A and B are driven at arbitrary times. The consequence of this is
that the testbench now cannot detect a failure in the tOD specifications for the path A-E:
0 6 12 18
For these constraints, the compiler will warn that the timing on input A has been relaxed (and that the
output hold times on D and E have also been relaxed).
Page 125/164
LRM 2.6 © 2008-2019 Maia EDA
9 DRIVE STATEMENT
9.1 Introduction
A module declaration (8.2) creates a set of named external variables which correspond to the input,
output, and bidirectional ports of the DUT, while a signal declaration (8.4) creates equivalent named
variables which correspond to specified internal signals within the DUT. In principle, having direct
access to these variables is sufficient to allow simple manual testing of the DUT, in a procedure such
as:
wait until an input port setup point, using a wait statement
assign an expression to the input port
wait, set the clock active
wait until an output port is expected to be valid
read the output port and check it; report the results with report or assert
set the clock inactive, and wait until the start of the next cycle
However, this procedure is complex, and quickly becomes impractical when a number of ports have to
be driven and tested, or when the outputs are pipelined, or when various inputs and outputs have
different timing. Maia therefore automates this procedure using a drive statement. A drive statement
has a number of advantages over the manual process described above:
automated input, output, and bidirectional timing derived from constraints
automated stability testing (glitch checking)
automated pipelined output testing (outputs tested a known number of clock cycles after the
inputs change)
automated triggered output testing (outputs tested after a trigger condition is found)
automated pass/fail counting, and error reporting
internal DUT signals are treated identically to external ports, without the need for force/release
semantics
automated bus direction switching for bidirectional signals
automated clock timing and driving
static type checking on port and signal directions
optional static type checking that confirms that module signals are driven by, or compared
against, expressions of the correct bit size
Page 126/164
LRM 2.6 © 2008-2019 Maia EDA
9.2 Statement format
A drive-statement includes an optional list of input expressions on the left hand side, a separator,
an optional pipeline expression, and an optional list of output expressions on the right hand side. Each
input and output expression is matched to a port or a signal which is named in the corresponding drive
declaration (8.3).
There are three different forms of drive statement, since the input and output lists are optional.
Output-only drive statements are triggered drive statements. The other two forms (the form with inputs
only, and the form with both inputs and outputs) are sequential drive statements. Sequential drive
statements may additionally be pipelined.
Sequential drive statements may be used only in user functions (7.5); triggered drive statements may
be used only in trigger functions (7.7).
In the first clock cycle, D1 is driven to 0, and D2 is driven to 1, before driving the clock. The output is
then tested against 0. In the second clock cycle, D1 is driven to 1, and D2 is driven to 0, before driving
the clock. The output is then tested against 1.
Page 127/164
LRM 2.6 © 2008-2019 Maia EDA
9.2.3 Output-only drive statements
An output-only drive statement is a triggered drive statement, and may be used only in a trigger
function. The corresponding drive declaration must include a single input, which must be a declared
clock. The trigger function is implicitly driven from this clock:
DUT {
module test1(input D1, D2, CLK; output [15:0] Q);
create_clock CLK;
@tfunc [CLK] -> [Q]; // triggered drive declaration (8.3.3)
}
main()
int x;
...
trigger tfunc(x) when Q == 7;
}
@tfunc(int y) {
->[y+2]; // Q should be 'x+2' the cycle after it is 7
->[8]; // Q should be 8 two cycles after it is 7
}
Example 100
Output-only drive statements are unusual in that the drive declaration and the drive statement do not
match. The declaration must include a single clock input, but the statement itself omits the clock input,
since it is not responsible for driving the clock (in the example above, the clock might be driven from
main, but the resulting output is tested in tfunc).
The pipeline level may be specified as an integer constant, an identifier for a variable, or an expression
(which must be enclosed in parentheses to avoid ambiguity). However, the pipeline level must be
known before the drive statement is executed; the expression is sampled when the statement is
reached. If the pipeline level cannot be determined in advance, then a triggered drive statement should
be used, rather than a pipelined drive statement.
DUT { // 4-stage pipelined 8x8 multiplier
module test(
input [7:0] D1, D2;
input CLK;
output [15:0] Q);
create_clock CLK;
[D1, D2, CLK] -> [Q];
}
main() {
var8 i,j;
for all i
for all j
Page 128/164
LRM 2.6 © 2008-2019 Maia EDA
[i, j, .C] ->4 [i *$16 j];
}
Example 101
There is no simple way to test the pipeline output before the pipeline has filled (for the example above,
Q is not tested until the fourth clock edge). If necessary, a separate trigger function may be used to
check the outputs while the pipeline is filling.
When changing the required pipeline level, it is potentially possible to overwrite a given level in the
checker. A runtime error will be reported if existing (untested) data in the checker is overwritten with
new data. However, 'don't care' data may be written into the checker without affecting the previous
data at that level. Uninitialised levels in the checker are treated as don't care data (in other words, no
test is carried out when the uninitialised level emerges from the checker pipeline).
Page 129/164
LRM 2.6 © 2008-2019 Maia EDA
9.3 Drive directives
Drive statements may contain directives, rather than expressions. Directives are case-insensitive, and
are either shorthand for specific input and output conditions, or specify some action on or within the
DUT.
9.3.1 .C
This directive may appear only on the left-hand side of a drive statement, in a position which
corresponds to a single-bit port which has been declared as a clock (8.5). It may only appear once in a
drive statement; there is no mechanism to clock more than one input simultaneously. This directive
appears only in clocked drive statements; it cannot be used in combinatorial drive statements.
The clock directive instructs the simulator to advance one clock cycle, using the default clock waveform
or the waveform defined in the clock declaration. The testbench automatically times inputs and samples
outputs according to this clock waveform.
9.3.2 .X and .Z
When these directives appear on an input, the entire input is driven to X or Z; when they appear on an
output, the entire output is tested against X or Z.
9.3.3 .R
This directive specifies an internal 'release' condition within the DUT. It may only appear on the left-
hand side of a drive statement, in a position corresponding to an internal DUT signal (8.4); it may not
be specified for a DUT port.
Internal DUT signals may be driven in the same way as external DUT ports, but this is handled
internally by disabling the internal DUT driver that would otherwise have driven that signal. This
internal driver is automatically disabled whenever a drive statement applies an expression to an internal
signal; it remains disabled until the .R directive is issued.
This directive is applied with the same timing as any other expression applied to the specified internal
signal. If, for example, a timing declaration specifies that the internal signal has a setup of 2.1ns to a
declared external clock, then the .R directive will re-enable the internal driver 2.1ns before that clock.
The example below shows a 4-input 1-output clocked module. The Q output is driven from a D-type
register, and the input to the D-type is named Dint in the HDL code. Dint is declared as a signal in
the DUT declaration, and so may be driven from the testbench, to force the DUT output to a specific
value. Left to its own devices, this DUT would produce the output sequence 01010101 over eight clock
cycles; this code instead forces the output in cycles 3, 4, 5, and 6 to produce 01100001 instead.
DUT {
module top(input A, B, C, CLK; output Q)
signal (input top.mod1.Dint) // internal signal
create_clock CLK
[CLK, top.mod1.Dint] -> [Q] // force the output
}
// start with the internal driver enabled
[.C, -] -> [0] // the testbench is not driving Dint
Page 130/164
LRM 2.6 © 2008-2019 Maia EDA
[.C, -] -> [1]
[.C, 1] -> [1] // output should be 0, is forced to 1
[.C, 0] -> [0] // output should be 1, is forced to 0
[.C, -] -> [0] // output should be 0, is forced to 0
[.C,] -> [0] // output should be 1, is forced to 0 ('-' is optional)
[.C, .R] -> [0] // internal driver re-enabled, output takes on internal value
[.C, -] -> [1] // internal driver remains enabled
Example 103
Page 131/164
LRM 2.6 © 2008-2019 Maia EDA
10 SCHEDULING MODEL
10.1 Introduction
This section describes an idealised scheduling model which is independent of the back-end code
generator, and whether or not the generator relies on an existing third-party simulator. There may
potentially, however, be issues with specific Verilog simulators (14.7.6).
Maia uses a co-operative scheduling model, in which a given function remains in context until it
executes a wait, drive, trigger, or exec statement. These statements are defined as 'suspending'
statements, while all other statements are defined as non-suspending statements.
All maximally-sized blocks of non-suspending statements are guaranteed to execute atomically, in zero
simulation time, without interference from any other function. When a suspending statement is
executed the current function suspends and returns control to the scheduler, which may then schedule
future activity as a result of that statement.
The scheduler then advances to the next time at which an activity has been scheduled. The
corresponding function is then resumed, and it carries on execution until it executes a suspending
statement.
There may potentially be more than one function which is scheduled to be resumed at a given time. If
this is the case, the scheduler makes an arbitrary decision as to which of these functions to resume.
User code should not assume any given order of function execution when statement blocks in different
functions are scheduled to be executed at the same time; the statement blocks may have the desired
order of execution in one program run, but have a different order in a second run.
10.2 Threads
Maia programs are multi-threaded. The main function is entered at or before time 11, and executes in
thread 0 (the 'main' thread). New threads are created in one of two ways:
1. by execution of an exec statement. The exec statement returns immediately (in zero simulation
time), and the newly-created thread starts execution immediately. A function which is entered
by means of an exec statement is a 'Thread Function' (7.6), and may advance time as required
(10.4). Every Thread Function has a unique thread identifier (a 'thread ID').
2. trigger functions (7.6) are automatically entered when the corresponding trigger condition is
encountered. Trigger functions do not have a thread ID.
Statements which are scheduled to run at the same simulation time will execute in an arbitrary non-
deterministic order. The two report statements in Example 68, for example, both run at the same
time, in an unknown order.
1
main may be entered at time 1, rather than time 0, in order to avoid start-up races in the generated Verilog code. 'Time 1'
refers to the first step in the program's execution. If the time units are nanoseconds, and the precision is 100 ps, for example,
then 'time 1' is 0.1 ns.
Page 132/164
LRM 2.6 © 2008-2019 Maia EDA
10.3 Program termination
A Maia program will continue execution until an exit statement is encountered, or until all threads
have completed execution.
Execution of an exit statement (from any thread) will terminate the program cleanly, together with
any threads which are currently active. Program execution will otherwise continue until all threads
(including the main thread) have finished execution by "falling off the bottom". This second termination
mechanism is equivalent to requiring all threads to re-join main, and then terminating main.
The wait statement suspends execution of the calling thread for the specified time. The thread resumes
execution on completion of the wait.
Every drive statement has an associated drive declaration. The declaration itself has an associated cycle
time, which may be explicit, or which may be created by the compiler. The Operating Point, or OP, is
the time at the start of that cycle.
Consider, for example, the case where the user declares two clocks, where clock A has a cycle time of
6ns, and clock B has a cycle time of 8ns. Any drive statements which are associated with clock A will
then have OPs at 6n ns (0, 6, 12, and so on), while any drive statements associated with clock B will
have OPs at 8n ns (0, 8, 16, and so on).
When a drive statement is executed the calling thread is suspended, and resumes at a later time. The
total time suspended depends on whether the calling thread is already at the relevant OP:
If the calling thread is not already at the OP1, it suspends until the second following OP is
reached. If, for example, the drive statement is associated with clock A, and the current
simulation time is 20ns, then the statement suspends for a total of 10ns (4 ns to synchronise
with the correct OP, and then a further 6ns to advance a complete cycle time).
If the thread is already at the OP, it suspends until the next OP. If, for example, the drive
statement is associated with clock B, and the current simulation time is 24ns, then the
statement suspends for 8ns, to advance for a complete cycle time.
1
This will happen at (1) program start-up, or (2) when a wait statement has been executed, or (3) when a previous drive
statement with a different cycle time declaration has been executed.
Page 133/164
LRM 2.6 © 2008-2019 Maia EDA
within the function). This local storage is referred to as 'Thread-local storage', or TLS, and the function
itself is a 'Thread Function'. There may be multiple in-progress instances of any such Thread Function
at a given time.
A function which is not a Thread Function exists as a single instance at runtime, irrespective of the time
at which the function is called, or the thread from which it is called 1. Example 104 calls f3 from two
threads:
void main() {
int tid;
exec f1(tid);
wait 1.5;
exec f2(tid);
// f1(tid); /* ILLEGAL: f1 is a Thread Function, and must be exec'ed */
}
Note that, in this example, execution begins at 0.1 ns. This is 'time 1', because the default time units
(ns) are used, and because the wait of 1.5 ns sets the precision to 100 ps (see 8.8).
1
This is a limitation of the 2019.9 Verilog code generator. Recursive function calls are not supported in 2019.9 for the same
reason.
2
The compiler statically constructs a call graph to determine whether this is possible. mtv will output a dot-format call graph if
the –cg option is specified.
Page 134/164
LRM 2.6 © 2008-2019 Maia EDA
If a given thread is responsible for driving a clock signal (by executing drive statements), then that
thread will generate the clock waveform described by the relevant create_clock declaration. The
waveform is 'anchored' at time 0, and not at the time at which the first drive statement is executed.
This ensures that all clocks which are generated by the testbench have a known relationship to each
other, which can be determined solely from the relevant create_clock declarations.
DUT-output clocks are generated by the DUT itself. The create_clock declaration describing a DUT-
output clock must match the actual clock waveform generated by the HDL code, or signals generated
by the testbench will not have the correct timing relative to the clock edges.
If the DUT section contains setup constraints for DUT inputs which appear in the drive statement, then
those inputs will be automatically driven at the specified time, and not at the OP. In this case, the OP
simply defines the time at which user statements will be executed, rather than the time at which the
DUT inputs will be driven. However, the OP must be at, or before, the declared setup point. The
generated testbench will record the required values of the DUT inputs when the drive statement is
executed, but will defer driving these values until the declared setup points are reached.
Page 135/164
LRM 2.6 © 2008-2019 Maia EDA
10.7.2 Combinatorial drive statements
For a combinatorial drive statement, the compiler determines an equivalent cycle time from the
relevant combinatorial delays. This cycle time reflects the longest combinatorial path from the signals in
the drive inputs, through to the drive outputs. The OP is the time at the start of this cycle. The compiler
will again use a default cycle time if the relevant information is not supplied in the DUT declaration.
The runtime therefore ignores the OP when testing DUT outputs, and instead triggers a pipelined tester
on the appropriate clock edge (see 8.5.3.2).
A user function may use any defined combinatorial or clocked drives, which are all independent from
the point of view of advancing time. The main function, in this code, is an example of a user function
which uses multiple clocked and combinatorial drives:
DUT {
module TEST(
input CLK1, CLK2, CLK3, A, B, C, D, E, F;
output Q1, Q2, Q3, G);
Page 136/164
LRM 2.6 © 2008-2019 Maia EDA
main() {
var a, b, c, d, e, f;
... // these statements are executed at start-up
D1: [.C, a] -> [d]; // advance one CLK1 waveform, CLK2/CLK3 unaffected
... // these statements are executed at 8ns
D2: [.C, b] -> [e]; // advance one CLK2 waveform, CLK1/CLK3 unaffected
... // these statements are executed at 8+13 = 21ns
D3: [.C, c] -> [f]; // advance one CLK3 waveform, CLK1/CLK2 unaffected
... // these statements are executed at 21+18 = 39ns
[a, b, c] -> [d]; // advance 9ns, CLK1/CLK2/CLK3 unaffected
... // these statements are executed at 39+9 = 48ns
wait 5; // advance 5ns, CLK1/CLK2/CLK3 unaffected
... // these statements are executed at 48+5 = 53ns
The code below shows an example of a manual DUT test. This code uses a drive statement to generate
a clock waveform for a D-type F/F, but explicitly tests the Q output, rather than testing it as part of a
drive statement:
Page 137/164
LRM 2.6 © 2008-2019 Maia EDA
DUT {
module DType(input D, CLK, output Q);
[CLK]; // just drive the CLK input; don't test any outputs
...declare the timing parameters: CLK period and waveform, tSU, and tCO
}
main() {
// D, CLK, and Q are external variables, and may be read or written normally
D = 1; // will be correctly driven to meet any setup spec
[.C]; // advance one CLK period to the next operating point
if(Q == 1) // manual sample may or may not be correct: see (10.11.2)
report("passed\n");
else
report("failed\n");
}
Example 108
These are valid declarations, but the sum of the setup to the clock, and the output delay from the
clock, is 11.0ns, while the total clock period is only 10.0 ns:
0 2 4 6 8 10 12
CLK
This is not a problem for a drive statement, since the drive statement pipelines the output test.
However, when carrying out manual testing, the operating point occurs at 10.0 ns, which is before B is
guaranteed to be valid.
Page 138/164
LRM 2.6 © 2008-2019 Maia EDA
If a manual test of the DUT outputs is necessary in these circumstances, then a wait statement can be
executed at the operating point, to delay for 1 ns. However, this simply has the effect of stretching the
clock low time by 1 ns, and has the same effect as changing the clock definition to:
create_clock CLK period 11 waveform { 3.5 7 }
For all outputs which have synchronous output constraints, the specified tCO occurs before the
end of the defined clock waveform.
This condition is clearly not satisfied for the example in (10.11.2), since the output has a tCO of 7.5ns,
but the time available from the rising clock edge to the end of the waveform is only 6.5ns.
If this condition is not satisfied, then a drive statement will use a pipelined test to sample the output,
and to update _vectorCount, _passCount, and _failCount, after the operating point. The user
does not have the ability to do this by executing code at the operating point, and so will potentially
sample incorrect DUT data.
Page 139/164
LRM 2.6 © 2008-2019 Maia EDA
11 RUN-TIME ERROR CHECKING
There are a number of program errors which cannot be detected until run-time, and which are
described below. If a run-time error is detected, an error counter (_errorCount) is incremented, and
an error message is added to the logfile. The program will terminate when the run-time error count
reaches the value specified as the rte parameter to either mtv or rtv (14.5). This parameter defaults
to 1, so the default behaviour is to abort program execution when a run-time error is detected. Under
some circumstances, it may be desirable and possible to continue execution; if this is the case, rte
may be given a higher value.
Note that programmer-defined assertion errors are treated identically to run-time errors, and simply
increase the error counter; the program will not abort until the rte limit has been reached.
Run-time errors should not be confused with DUT errors. A run-time error is the result of a
programming error and is, as such, 'unexpected'; run-time errors should therefore normally result in a
program abort. DUT errors, on the other hand, are (potentially) expected errors.
Page 140/164
LRM 2.6 © 2008-2019 Maia EDA
11.6 Last value pipeline errors
When the history of an object is read with the 'last attribute (4.5.4.6) with a variable clock level, the
clock level must evaluate to an integer which is in the inclusive range [1,plevel], where plevel is the
declared pipeline level for that clock (8.5.4). If the clock level is outside this range an error will be
reported, and an all-X value will be returned.
Page 141/164
LRM 2.6 © 2008-2019 Maia EDA
12 PREPROCESSOR
12.1 Introduction
The translation of a source file is carried out in two distinct stages. In the first, a preprocessor carries
out a number of simple textual conversions on the source file. The preprocessor output is then used as
input to the second stage of translation. This second stage is conventionally known as "compilation".
Translation is split into two stages for compatibility with other C-like languages (the Maia preprocessor
is, for most intents and purposes, identical to the C preprocessor1). The primary purpose of
preprocessing is to allow a macro processor to be run as a separate stage before compilation. This
macro processor allows, among other things, the definition and expansion of macros, and the inclusion
of additional source files. However, the preprocessor also includes other functionality which is not
directly related to the macro processing functionality.
The macro processing language (MPL) has a syntax which is, with minor exceptions, unrelated to the
syntax of the language being compiled (in this case, Maia). This chapter documents the functionality of
the Maia preprocessor, and the MPL syntax.
The preprocessor is responsible for validating UTF-8 input, replacing trigraph and digraph character
sequences, removing escaped LF characters ("line splicing"), comment removal, and carrying out macro
operations in the MPL. The preprocessor stage is not required if all the following conditions are
satisfied:
1. The source character set is ASCII (in other words, the source contains no multi-byte UTF-8
characters)
The preprocessor has only minimal understanding of the lexical structure of a Maia program. It
understands the form of strings, comments, constants, and identifiers, but does not otherwise carry out
any tokenisation which is Maia-specific. It can therefore, in many situations, be used as a general-
purpose textual preprocessor. However, the preprocessor emits line directives (12.3.3) in its output, to
allow downstream tools to identify the current source file, and to keep track of the current line number
in that file. These tools must therefore be capable of either processing, or ignoring, these directives.
1
The primary differences are that the Maia preprocessor specifies UTF-8 as the input character set, and that there is no
specific 'tokenisation' phase.
Page 142/164
LRM 2.6 © 2008-2019 Maia EDA
12.2 Preprocessor translation phases
The preprocessor carries out textual translation of the source file. This translation is split into a number
of phases, which are carried out in the order defined by the paragraph headings below. The first 9 of
these phases primarily carry out a number of simple character substitutions, UTF-8 validation, line
concatenation, and comment removal.
The resulting source is then examined for operations in the MPL. These operations include directive
execution, which is carried out in phase 10, and macro expansion, which is carried out in phase 11.
Conceptually, each of these phases is carried out separately, over the entire source file, before the next
phase is started, with the single exception noted in step 2 of 12.3.1.1. However, the preprocessor may
carry out the translation in any way that preserves the ordering defined by the paragraphs below. In
particular, it is possible to carry out all preprocessing in a "line filter", operating only on the current line
of input. When operating in this way, the preprocessor reads a single logical line of input (one or more
physical lines separated by escaped newline characters), processes that line and then, if necessary,
outputs that line.
Three of the initial transformation phases are optional, and are disabled by default. The preprocessor
is, however, required to provide a mechanism to individually enable any, or all, of these three phases 1.
The optional phases are:
1
mtv 2019.9 carries out trigraph and digraph replacement, but does not compress whitespace. It does not currently provide a
mechanism to disable trigraph or digraph replacement, or to enable whitespace compression.
2
Trigraphs and digraphs may be required when, for example, a keyboard does not provide the equivalent character, or when a
text editor reserves an equivalent character.
Page 143/164
LRM 2.6 © 2008-2019 Maia EDA
When replacement is enabled, all trigraph sequences are replaced, irrespective of context; in particular,
trigraphs (and digraphs) within strings are also replaced. A trigraph within a string may be preserved by
replacing a question mark with an escaped question mark:
report("(???)"); // produces '(?]'
report("(??\?)"); // produces '(???)'
All these code points are converted into a \n character (LF, U+000A). The code point sequence
U+000D, U+000A is tested before testing for a single U+000D; both are converted into a single LF
character.
Any reference to a "line terminator" refers to the one or more code points which are used to terminate
a user input line either during, or prior to, this phase. Any reference to a "newline" or to "LF" after this
phase has completed refers to a single \n (LF, U+000A) character.
Page 144/164
LRM 2.6 © 2008-2019 Maia EDA
Two-byte code points
U+00A0
Three-byte code points
U+1680 U+180E U+2000 U+2001
U+2002 U+2003 U+2004 U+2005
U+2006 U+2007 U+2008 U+2009
U+200A U+202F U+205F U+3000
All these code points, with the exception of HT ("horizontal tab", U+0009), are converted into a SP
character (space, U+0020). Note that newline is not classified as whitespace. Any reference to
"whitespace" after this phase has completed refers only to one or more consecutive HT or SP
characters.
On completion of this phase, all line terminator and whitespace characters in the source will have been
replaced with either LF (U+000A), SP (U+0020), or HT (U+0009).
Line continuation is required only when it is necessary to split a preprocessor directive, or a string, over
multiple physical lines3.
1
Examples of invalid characters are characters which have an invalid byte count, or which have an overlong encoding, or
which code more than 21 bits, or which have an invalid continuation byte.
2
This exception is known as 'modified UTF-8'; it allows a NUL character to be placed into a string.
3
The compiler itself is "free-form" and never requires input to be split over multiple lines using an escaped LF. "Line splicing" is
relevant only to the preprocessor.
4
Strings may be continued over multiple lines either by inserting an escaped LF within the string itself, or by placing adjacent
strings on separate lines of input. In the former case, the preprocessor removes the escaped LF; in the latter, the compiler
concatenates the adjacent strings.
Page 145/164
LRM 2.6 © 2008-2019 Maia EDA
Note that the filename argument to the #include directive may be specified as a string (12.3.2). This
string is treated in the same way as any other string, and is protected from further transformation1.
12.2.8 Comments
Both block and line comments are replaced with a single SP character.
Phases 10 and 11 require partial tokenisation of the input in order to find identifiers, and to evaluate
any arithmetic expressions which control conditional inclusion. However, this tokenisation is not
required if there are no directives in the source, and is not treated as a separate phase.
If the next character is a LF, this line is ignored and is not copied to the output (in other words, it
is stripped from the input). This is a null directive;
If the next character is ( (U+0028), then the line is not considered to be a directive3 (and is
therefore subject to macro expansion in phase 11);
1
For the C preprocessor, the string argument is treated as a special case and may later be macro-expanded.
2
A directive must therefore appear on a single logical line of input. If it is necessary to split a directive over multiple physical
lines, it can be continued either by a block comment which extends past the end of the line, or by escaping the line terminators
with a \ character (12.2.6).
3
#( introduces a module parameter list in a module declaration; see 8.2.1.
Page 146/164
LRM 2.6 © 2008-2019 Maia EDA
If the next 6 characters are pragma, followed by whitespace, then the entire line is protected
from further transformation and is passed unmodified to the output1. This is a pragma directive;
see 12.7.
Syntax
pp-directive :
pp-cond-inclusion
pp-control \n
pp-control :
pp-include
pp-line
pp-warning
pp-error
pp-define
pp-undefine
1. for the #ifdef directive, the condition evaluates to true if pp-identifier is currently defined as a
macro name (in other words, a definition is currently in scope), and false otherwise. This
condition is equivalent to #if defined pp-identifier.
2. for the #ifndef directive, the condition evaluates to true if pp-identifier is not currently defined
as a macro name, and false otherwise. This condition is equivalent to #if !defined pp-
identifier.
3. for the #if and #elif directives, pp-condition is evaluated as an arithmetic constant
expression, using the procedure defined in 12.3.1.1. The condition evaluates to false if the
expression evaluates to 0, and true otherwise.
Each directive in a conditional inclusion directive (a pp-cond-inclusion) is checked in order; only the
block associated with the first condition that evaluates true is included. If none of the conditions
evaluates to true, and there is a #else branch, the block associated with the #else branch is
included. If there is no #else branch, none of the blocks associated with the pp-cond-inclusion is
included.
1
Macro expansion therefore does not occur inside a #pragma directive.
Page 147/164
LRM 2.6 © 2008-2019 Maia EDA
If pp-cond-block is excluded as a result of a condition evaluation, the preprocessor carries on analysing
the text in the excluded pp-cond-block until it finds the matching pp-elif-part, pp-else-part, or pp-endif-
part. The preprocessor is required to complete processing through to phase 10, and so will potentially
report any errors detected in these phases, despite the fact that the block has been excluded. However,
the preprocessor will not generate any output for these lines.
Syntax
pp-cond-inclusion :
pp-if-part pp-elif-partsopt pp-else-partopt pp-endif-part
pp-if-part :
# ifdef pp-identifier \n pp-cond-blockopt
# ifndef pp-identifier \n pp-cond-blockopt
# if pp-condition \n pp-cond-blockopt
pp-elif-parts :
pp-elif-parts pp-elif-part
pp-elif-part :
# elif pp-condition \n pp-cond-blockopt
pp-else-part :
# else \n pp-cond-blockopt
pp-endif-part :
# endif \n
pp-cond-block :
pp-cond-block pp-cond-block-part
pp-cond-block-part :
pp-cond-inclusion
pp-control \n
text-line \n
Page 148/164
LRM 2.6 © 2008-2019 Maia EDA
2. Any macro invocations in pp-condition are expanded, using the procedure defined in 12.4
below. This replacement occurs before phase 11, and is the only violation of the evaluation-
order rules defined in 12.21.
4. The resulting expression should contain only whitespace, parentheses ( and ), integer
constants in the form of a Cinteger (2.7.1), and the operators defined in Table 25. The
operators are listed in precedence order, with the highest precedence operators at the top of
the table, and operators of equal precedence on the same row of the table. These operators are
a subset of the full set of Maia operators, with the same precedence and associativity.
The expression is evaluated using 64-bit precision, and an error is raised if any Cinteger
constants cannot be represented in 64 bits.
On completion, the pp-condition evaluates to false if the expression evaluates to zero, and true
otherwise.
Operator Associativity
Unary ! ~ + - right to left
Multiplicative * / % left to right
Additive + - left to right
Shift << >> left to right
Comparison < <= > >= left to right
Equality == != left to right
Binary AND & left to right
Binary XOR ^ left to right
Binary OR | left to right
Logical AND && and left to right
Logical OR || or left to right
Table 25: MPL operators
The specified filename may be either a rooted absolute filename, or a relative filename. When the
"filename" syntax is used, relative filenames are searched for in a location relative to the current
source file. If the required file is not found in this location, it is searched for in the same directories
which are searched for the <filename> syntax.
1
The C preprocessor also allows the argument of a #include directive to be macro-expanded before source file inclusion is
carried out. This feature is not supported. Allowing this exception would not add any functionality that cannot easily be
achieved while keeping strict phase ordering.
Page 149/164
LRM 2.6 © 2008-2019 Maia EDA
The <filename> syntax is used when searching for system files. No system file directories are
specified for mtv 2019.9, and <filename> is currently treated identically to "filename".
Syntax
pp-include :
# include <filename>
# include "filename"
The line number is supplied as line-number, which should be a decimal integer. The preprocessor will
restart line numbering such that the next input line after this directive will be considered to have this
line number.
The filename is optional; if it is not provided, the current filename remains unchanged.
Syntax
pp-line :
# line line-number "filename"opt
line-number : pp-integer
Syntax
pp-warning :
# warning warning-textopt
pp-error :
# error error-textopt
12.3.5.1 Introduction
A macro definition associates the specified replacement text (or "macro body") with an identifier (the
"macro name"). There is a single namespace for macro names1. The identifiers defined, and, and or
may not be used as macro names.
1
It is therefore not possible to define an object-like and a function-like macro with the same name, or to define multiple
function-like macros with the same name, even if they have different numbers of arguments.
Page 150/164
LRM 2.6 © 2008-2019 Maia EDA
The scope of this association, or macro definition, lasts until a corresponding #undef directive for the
same macro name, or until preprocessing completes if no #undef directive is found. The #undef
directive need not occur in the same source file.
Within the scope of a macro definition, any valid invocation of the macro is replaced by the
corresponding replacement text during phase 11 (see 12.4 below). Whitespace surrounding the
replacement text is not significant, and is removed before the replacement occurs.
The replacement text may be empty; in this case, the macro invocation is simply removed from the
output during the replacement phase.
Syntax
pp-define :
object-like-macro-defn
function-like-macro-defn
object-like-macro-defn :
# define macro-name replacement-text
function-like-macro-defn :
# define macro-name-lparen formal-param-list ) replacement-text
macro-name : pp-identifier
formal-param-list :
formal-param
formal-param-list , formal-param
formal-param : pp-identifier
macro-name-lparen :: {pp-identifier}(
replacement-text :: .*
defines an object-like macro with name macro-name, and with associated replacement text
replacement-text. Subsequent occurrences of the macro name within the scope of the definition are
replaced with the associated replacement text during phase 11; see 12.4 below.
Page 151/164
LRM 2.6 © 2008-2019 Maia EDA
#define foo bar1 bar2 // object-like: 'foo' is replaced with 'bar1 bar2' in phase 11
Within the definition, an optional comma-separated list of identifiers (formal-param-list) names the
"formal parameters" to the macro. The scope of a formal parameter lasts from its introduction in the
parameter list to the end of the macro definition (in other words, to the newline which terminates the
current logical line). The formal parameter names must be unique within the macro definition.
Subsequent occurrences of the macro name, when within the scope of the definition and when followed
by a list of actual parameters enclosed in parentheses, are replaced with the associated replacement
text during phase 11; see 12.4 below.
Syntax
pp-undefine :
# undef pp-identifier
Page 152/164
LRM 2.6 © 2008-2019 Maia EDA
#define FOO BAR
#define BAR FOO
#define A(x, y) BAR
When replacement of the pp-identifier has completed, the scan process restarts at the first character of
the replacement text. The replacement text may therefore contain further complete or partial
invocations of object- or function-like macros, and these invocations are themselves replaced, until no
further replacements are possible.
#define FOO BAR(1, // 'FOO' expands to a partial invocation of 'BAR'
#define BAR(x, y) 2*x+y
FOO
3) // eventually expands to '2*1+3'
Example 112
The text between the outermost pair of matching parentheses following the macro name forms the
macro argument list (the "actual parameters"). It is an error if the source does not contain a closing
parenthesis after the argument list.
Individual parameters are separated by a comma character, unless that comma character is enclosed
within a pair of parentheses which are not the outer-most pair of parentheses2. The number of
arguments (including empty arguments) must match the number of formal parameters in the macro
definition3. Whitespace before or after an argument is not significant, and is not substituted into the
replacement text. If an argument is not present, or is composed entirely of whitespace, then it is
considered to be an "empty" argument, and the corresponding formal parameter is omitted from the
expanded replacement text.
1
Strings and comments are processed in phases 7 and 8, respectively. Macro expansion therefore does not occur in either
strings or comments.
2
An argument may therefore contain matched pairs of parentheses, but not unmatched parentheses.
3
2019.9 does not support variable argument lists.
Page 153/164
LRM 2.6 © 2008-2019 Maia EDA
Within the text forming an invocation of a function-like macro, any newline characters following the
macro name are treated as whitespace. An invocation of a function-like macro may therefore appear on
more than one logical line of source.
#define A(x) x+y
A(1) // expands to '1+y'
A // not a macro invocation; the preprocessor outputs 'A'
A() // an invocation of 'A' with one empty argument; preprocessor outputs '+y'
A(1,2) // an error: an invocation of 'A' requires exactly one argument
Otherwise, the actual argument is macro-expanded, and is then substituted into the replacement text in
place of the formal argument (this is known as argument prescan). This substitution is carried out
recursively, in the same way as for any other expansion; however, it is only the argument itself which is
expanded (the substitution process will not attempt to read any text beyond the comma character or
closing parenthesis which terminates the argument).
When argument substitution has completed, the entire replacement text is scanned for further
replacements. This process continues until no more expansion is possible.
#define E D
#define C(x,y) [x+y]
#define D C(1,2)
#define F(a,b) a+#b
1
The C preprocessor parses 1G as a "preprocessing number", and so does not recognise a macro in this case; it instead
outputs 1G(,)2
Page 154/164
LRM 2.6 © 2008-2019 Maia EDA
substitution into the replacement text. Leading and trailing whitespace around the actual argument is
ignored.
#define TEST(expr) \
do { \
if(!( expr )) \
report("test " #expr " failed\n"); \
} while(0)
12.5 Tokenisation
The preprocessor has no specific tokenisation phase. However, tokenisation is required during phases
10 and 11, for the following reasons:
3. The controlling expression of the #if and #elif directives (12.3.1) must be evaluated as a
constant expression.
In these contexts, the preprocessor classifies the remaining unprotected1 input into Vinteger tokens,
Cinteger tokens, pp-identifier tokens, MPL operator tokens, and punctuator tokens2, where:
ii. A Cinteger is a preprocessor C-style integer, and is defined identically to a Maia Cinteger
(2.7.1);
iii. A pp-identifier is a preprocessor identifier, and is defined identically to a Maia identifier (2.5);
1
Protected input includes strings and pragma directives.
2
The preprocessor does not identify floating constants (2.7.3) in 2019.9. Any part of a floating constant which has the same
form as an identifier is therefore subject to macro expansion.
Page 155/164
LRM 2.6 © 2008-2019 Maia EDA
iv. The MPL operators are the operators defined in Table 25, together with parentheses ( and );
Syntax
pp-identifier ::
[{ident-alpha}_][{ident_alpha}_0-9]*
ident-alpha ::
[U+0061-U+007A] | [U+0041-U+005A] |
[U+0080-U+0084] | [U+0086-U+2027] | [U+202A-U+10FFFF]
The "alphabetic characters" are defined as a through z, A through Z, and all multibyte UTF-8
characters, with the exception of the multibyte line terminators (12.2.3), and the multibyte whitespace
characters (12.2.4).
Page 156/164
LRM 2.6 © 2008-2019 Maia EDA
__UNIX__ Will be set to 1 if running on a Unix-like system, or undefined otherwise
__FILE__ The current source file name, as a string
__LINE__ The current source file line number, as a decimal integer
__DATE__ The compilation date, as a string in the format "mmm dd yyyy" (for
example, "Apr 22 2019")
__TIME__ The compilation time, as a string in the format "hh:mm:ss" (for example,
"17:20:56")
Table 26: predefined macro names
Page 157/164
LRM 2.6 © 2008-2019 Maia EDA
13 GLOSSARY
Aggregate object A compound object which is a collection of scalar objects. If the scalar objects
are all of the same type then the collection is homogeneous (an array);
sotherwise, the collection is heteregenous (a structure).
Arithmetic type A type which supports arithmetic operations: int, bit, and var. If
_StrictChecking is less than 2, bool is a synonym for bit1, and so is also
an arithmetic type.
Assignment Compatible Object lhs and rhs are assignment-compatible if the expression lhs=rhs is
allowable.
Bit A unit of data storage sufficient to hold an int1 or a var1 object. An int1
may take on one of the values 0 or 1; a var1 may take on one of the values 0,
1, X, or Z.
Constant A lexical element which represents a numeric or boolean value. A constant is not
an object. In some circumstances, however, the compiler can be considered to
create a temporary object which is initialised with the value of the constant.
Constant expression An arithmetic expression which can be evaluated during compilation; any
combination of constants and operators. With few exceptions, a constant
expression can be used wherever a constant is required in the syntax.
Data type A type which can be considered to hold 'data': the arithmetic types, and kmap.
Page 158/164
LRM 2.6 © 2008-2019 Maia EDA
Member An entity (member, or field) inside a structure or stream; see also tag
Object A region of data storage, which may be readable, writeable, or both. If the
object is readable, it yields a value when read. Every object has an associated
type, which determines the interpretation of the value stored in the object, and
the operations allowed on that object.
OP Operating point
Precedence Operator precedence determines the order in which the sub-expressions in a full
expression are evaluated. In the expression a=b+c*d, for example, * has a
higher precedence than +, and the expression is therefore evaluated as
a=b+(c*d). See also associativity.
Scope For an object which has an identifier, the scope of that identifier is the region of
the source code in which the identifier may be used to access that object
Tag The name associated with a structure or stream declaration; for example, this
declaration has the tag a, and has one member, b:
struct a { int b; }
void expression A void expression has no value (a call of a function which has been declared to
be of type void, for example). If an expression of any other type is evaluated as
a void expression, its result is discarded; in this case, the expression is evaluated
solely for its side-effects.
Page 159/164
LRM 2.6 © 2008-2019 Maia EDA
14 MTV
This chapter documents features and issues which are specific to mtv, or the current implementation of
mtv or a specific code generator, but which are not part of the language specification.
14.1 Preprocessor
A number of macro names are predefined, and are listed in Table 26 above. Macros may be defined or
cancelled on the mtv (or rtv) command line, with these switches:
The target language is set by mtv's –target option, or by the suffix of the output file; it is not
overridden by the __VHDL_TARGET__ and __VERILOG_TARGET__ macros, which should not normally
be changed.
mtv does not currently directly accept a –I switch to specify include file directories; these switches
should instead be specified in the CPP_OPTIONS environment variable.
rtv, the compiler driver, also requires a number of environment variables. These variables have no
default values; they must be set to valid values during installation. These variables are listed in Table
28.
If sizing does not complete within the default number of iterations it is likely that the user code
contains an erroneous loop involving a cycle of chained unconstrained objects.
Page 161/164
LRM 2.6 © 2008-2019 Maia EDA
assertion failures, but do not include DUT failures. The default value of n is 1; in other words, a
program will, by default, terminate when it encounters any assertion or run-time error.
Individual Verilog simulators also have widely differing support for the underlying $write system task,
so report statements with complex formatting requirements are likely to display differently on different
1
The code generator produces Verilog which conforms to IEEE1364-2005. This was the final LRM release for 'plain' Verilog.
No SystemVerilog code is generated.
Page 162/164
LRM 2.6 © 2008-2019 Maia EDA
simulators, or possibly not at all. No error or warning messages are generated if the output does not
conform to the report specification.
14.7.5 Recursion
The Verilog code generator does not support recursive function calls1 (in other words, a Maia function
may not directly or indirectly call itself). mtv's '-cg' switch produces a call graph, which may be viewed
with graphviz; the graph can be used to analyse illegal function call sequences.
14.7.6 Scheduling
Verilog's scheduling model is ambiguous, particularly with respect to the issue of the atomicity of
different 'processes'. The LRM doesn't explicitly state that processes should de-schedule only at defined
points, and leaves the option open for arbitrary process interleaving.
If the scheduler is implemented as defined, then it potentially has a number of undesirable effects (and
no benefits). It is unlikely, for these reasons, that any vendor actually uses an interleaved scheduling
model.
If a specific simulator does implement interleaved scheduling, then Maia is potentially affected if two or
more concurrent functions attempt to modify a shared variable at the same time (in other words, the
functions must have the same Operating Point). In this case, it is possible that the shared variable will
take on an incorrect value.
1
While Verilog-2005 does support 'auto' functions, this support is not sufficient to allow recursive function calls, except in
simple cases.
Page 163/164
LRM 2.6 © 2008-2019 Maia EDA
15 FLOATING-POINT ARITHMETIC EXAMPLE
The example program below uses the BBP formula to calculate to 15 decimal places, which is the
best that can be represented in IEC 60559 64-bit precision (a real2). The program executes two
report statements. The first simply outputs the '' variable (which is initialised from a constant which is
correct to 16 decimal places), while the second outputs the result of the BBP calculation.
#pragma _strictChecking 0
main() {
var64 = 3.1415926535897932;
real2 bbp[11];
report("%19.16f\n", );
report("%19.16f\n", bbp[10]);
} // main()
term(k) {
real2 t1 = 1.0;
for(i=0; i<k; i++)
t1 = 16.0 .F* t1;
t1 = 1.0 .F/ t1;
return
t1 .F* (
(4.0 .F/ ((8.0 .F* (real2)k) .F+ 1.0)) .F-
(2.0 .F/ ((8.0 .F* (real2)k) .F+ 4.0)) .F-
(1.0 .F/ ((8.0 .F* (real2)k) .F+ 5.0)) .F-
(1.0 .F/ ((8.0 .F* (real2)k) .F+ 6.0)));
}
Example 116
Page 164/164
LRM 2.6 © 2008-2019 Maia EDA