Writing a Test Bench - 2023.2 English

Vitis High-Level Synthesis User Guide (UG1399)

Document ID
UG1399
Release Date
2023-12-18
Version
2023.2 English

When using the HLS component flow, it can be time consuming to synthesize an improperly coded C/C++ function and then analyze the implementation details to determine why the function does not perform as expected. Therefore, the first step in high-level synthesis should be to validate that the C function is correct, before generating RTL code, by performing simulation using a well written test bench. Writing a good test bench can greatly increase your productivity because C functions execute in orders of magnitude faster than RTL simulations. Using C/C++ to develop and validate the algorithm before synthesis is much faster than developing and debugging RTL code.

The test bench includes the main() function, as well as any needed sub-functions that are not in the hierarchy of the top-level function designated for synthesis. The main function verifies that the top-level function for synthesis is correct by providing stimuli and calling the function from the test bench, and by consuming and validating its output.

Important: The test bench can accept input arguments that can be provided when C simulation is launched, as described in Running C Simulation. However, the test bench must not require interactive user inputs during execution. The HLS tool can not interact with the simulator and therefore cannot accept user inputs while the test bench executes.

The following code shows the important features of a self-checking test bench, as an example:

int main () { 
  //Establish an initial return value. 0 = success
  int ret=0;

  // Call any preliminary functions required to prepare input for the test.
  …
  …// Call the top-level function multiple times, passing input stimuli as needed.
  for(i=0; i<NUM_TRANS; i++){
     top_func(input, output);
  }

  // Capture the output results of the function, write to a file
  …
  // Compare the results of the function against expected results
  ret = system("diff --brief  -w output.dat output.golden.dat");
  
  if (ret != 0) {
        printf("Test failed  !!!\n"); 
        ret=1;
  } else {
        printf("Test passed !\n"); 
  }
  …
  return ret;
}

The test bench should execute the top-level function for multiple transactions, allowing many different data values to be applied and verified. The test bench is only as good as the variety of tests it performs. In addition, your test bench must provide repeated calls to the synthesized function if you want to calculate II during RTL simulation as described in Running C/RTL Co-Simulation.

The self-checking test bench above compares the results of the function, output.dat, against known good results in output.golden.dat. This is one example of a self-checking test bench. There are many ways to validate your top-level function, and you must code your test bench as appropriate to your code.

In the HLS component flow, the return value of function main() indicates the following:

  • Zero: Results are correct.
  • Non-zero value: Results are incorrect.

The test bench can return any non-zero value. A complex test bench can return different values depending on the type of failure. If the test bench returns a non-zero value after C simulation or C/RTL co-simulation, the tool reports an error and simulation fails.

Tip: Because the system environment (for example, Linux, Windows, or Tcl) interprets the return value of the main() function, it is recommended that you constrain the return value to an 8-bit range for portability and safety.

Of course, the results of simulation are only as good as the test bench you provide. You are responsible for ensuring that the test bench returns the correct result. If the test bench returns zero, the tool indicates that the simulation has passed, regardless of what occurred during simulation.