Self-checking code

The examples shown above contain some simple drive statements which are used to drive DUT inputs, and to test DUT outputs. The left-hand-side inputs (the drive values) and the right-hand-side outputs (the test values) may be arbitrary expressions. These statements, for example, drive various DUT inputs and test the resulting outputs at some later time:

[0, .C, 2*x, y, z] -> [foo(), 0x64, 2*out2()];  // test clocked/sequential outputs
[a, b, c] -> [d, e];                            // test combinatorial outputs

The .C is a directive which instructs the simulator to advance one clock cycle, using the clock waveform associated with this particular drive statement. The generated testbench automatically times inputs and samples outputs according to this clock waveform. There is no clock, of course, for the combinatorial test. In this case, the compiler instead generates its own "cycle time", based on your declared timing parameters.

Drive statements have a number of other features:

  • Other directives can be used to drive or test against all-x or all-z values, or to 'release' an internal DUT signal after a preload (a 'force')
  • an expression can be omitted entirely to leave the drive value unmodified, or to ignore the output in that cycle
  • the -> token can be followed by a trailing integer expression which specifies a pipeline delay for the test (->3, for example, is used for testing a 3-stage pipeline in Example #2)

The use of drive statements creates self-checking code. If a DUT output is correct, an internal pass counter (_passCount) is incremented; if an output is not correct, an internal fail counter (_failCount) is incremented. In the case of a failure, a message is also recorded giving the current time, the Maia source code line number, and the actual and expected DUT output values. The simulation will stop after a specified number of failures. On completion of the simulation, the pass and fail counts are reported.