Maia was designed specifically for the testing of HDL modules coded in Verilog or VHDL. The compiler generates a Verilog testbench. You will therefore need a Verilog simulator to test a Verilog DUT, or a mixed-language simulator to test a VHDL DUT.
A Maia program has two parts: a 'DUT section', and a testbench for that DUT. The DUT section tells the compiler how to interact with your DUT. It contains a declaration of the DUT itself, and any signals inside the DUT which the testbench needs to access, as well as any clocks or tristate control signals. If you need a timing simulation, you can add timing constraints. The DUT section also includes declarations of any 'drive statements' that you will be using in your testbench. These drive statements automate the driving of DUT inputs, and the testing of DUT outputs.
The DUT section is optional. The test code itself can be completely self-contained; it might, for example, just execute a given algorithm. However, the output produced by the Maia compiler will still need to be run on a simulator, even if there is no DUT.
The 'testbench' part of the program can be entered in one of two forms. The first form is simply a list of drive statements (or 'test vectors'), and nothing else. The first tutorial example (tut1.tv) is a 'test vector' program. This form of test is only suitable in simple cases, because:
- the test data must be known in advance (the data in the test vectors must be constant)
- the code can't include any control constructs (loops, branches, and so on), and can't call functions
- the code runs in a single thread. This means that you can't simultaneously write to and read from a FIFO, for example: you have to execute the reads and writes sequentially
There are a number of examples of test vector programs in the tutorials, and in the HDL Coding Styles article, but they're not discussed any further here.
In the second form, the testbench is coded in a function named 'main', which may itself call other functions. The code must be entered in a single source file, but can include other source files by using the #include preprocessor directive. In this second form, drive statements must appear inside functions, and are executed within the normal program flow. The DUT can be accessed in one of two ways:
- directly, by assigning to or reading any ports or signals named in the DUT section
- indirectly, by using drive statements.
A Maia program runs as a collection of independent concurrent threads. On startup, 'main' is run in thread 0, and any functions called within the normal program flow are also executed in thread 0. Most of the time, this is all you'll need to do. If you need a new thread, however, you can initiate it with the exec statement, which runs a function in the new thread. Program execution terminates either when any thread calls exit, or when all threads have terminated normally (by returning or 'falling off the bottom'). DUT ports and internal signals can be driven by at most one thread: the compiler will detect and report an error if a given port or signal is driven by more than one thread.
Within a given thread, the simulation time is advanced only by executing wait or drive statements (these are the 'time consuming' statements). All other statements execute in zero simulation time. In normal use, statements within a given thread execute at the 'Operating Point' (the 'OP'), and time is advanced to the next OP by a drive statement. This is explained in more detail here. The OP is the time at which the compiler can test device outputs, or drive device inputs. In practice, threads are normally used to handle different clocks in a DUT, or to respond to groups of DUT outputs. The tutorial FIFO test, for example, has one thread to write to the FIFO, and one to read from the FIFO, using independent write and read clocks.
The features described above allow Maia programs to access the DUT, to run within concurrent threads, and to advance simulation time. These are the features which distinguish Maia (or, in general, any hardware description or verification language) from conventional programming languages. The rest of the language is made up of more-or-less conventional statements which handle sequential program flow, control constructs, data structures, and so on. This part of the language looks, and feels, very much like C, since C is the lingua franca of the computing world.
Plain C is of course not suitable for the verification of HDL designs, for lots of reasons. It doesn't handle multi-value logic, or arbitrary-length arithmetic, for example. It is difficult to handle bitfields within words, or words which don't have a size of 8, 16, 32, or 64 bits. Maia adds these features, and more, but also removes some other features: pointers, unions, and so on. The rest of this section describes the base language. It will be helpful if you already have some understanding of C, or a similar language, but this shouldn't be necessary.