3.5.1.7 Simulate HLS Hardware (SW/HW Co-Simulation)

The circuit generated by SmartHLS should be functionally equivalent to the input software. Users should not modify the generated Verilog, as it is overwritten every time SmartHLS is recompiled.

SW/HW co-simulation can be used to verify that the generated hardware produces the same outputs for the same inputs as software. With SW/HW co-simulation, user does not have to write their own RTL testbench, as it is automatically generated. If user already has their own custom RTL testbench, one can optionally choose their custom RTL testbench (3.5.1.20 Specifying a Custom Test Bench) and not use SW/HW co-simulation.

To use SW/HW co-simulation, the input software program will be composed of two parts,
  • A top-level function (and its descendant functions) to be synthesized to hardware by SmartHLS,
  • A C/C++ testbench (the parent functions of the top-level function, typically main()) that invokes the top-level function with test inputs and verifies outputs.

SW/HW co-simulation consists of the following automated steps:

  1. SmartHLS runs your software program and saves all the inputs passed to the top-level function.
  2. SmartHLS automatically creates an RTL testbench that reads in the inputs from step 1 and passes them into the SmartHLS-generated hardware module.
  3. ModelSim simulates the testbench and saves the SmartHLS-generated module outputs.
  4. SmartHLS runs your software program again, but uses the simulation outputs as the output of your top-level function.

You should write your C/C++ test bench such that the main() function returns a 0 when all outputs from the top-level function are as expected and otherwise return a nonzero value up to 255. We use this return value to determine whether the SW/HW co-simulation has passed.

In Step 1, we verify that the program returns 0.

In Step 4, we run the program using the outputs from simulation and if the SmartHLS-generated circuit matches the C program then main() should still return 0.

If the C/C++ program matches the RTL simulation then you should see: SW/HW co-simulation: PASS

Warning: The return value of the main() function must be within the range of 0 to 255. If the return value is greater than 255, only the lower 8 bits will be used as the return code. For example, a return value of 257 or 1025 will be considered as failed since their lower 8 bits equal to 1, but values such as 256, 512 and 1024 will be considered as passed since their lower 8 bits equal to 0.

For any values that are shared between software test bench and hardware functions (top-level and descendants), you can either pass in as arguments into the top-level function, or if it is a global variable, it can be directly accessed without being passed in as an argument. Any variables that are accessed by both software test bench and hardware functions will create an interface at the top-level module. For example, if there is an array that is initialized in the software test bench and is used as an input to the hardware function, you may pass the array as an argument into the top-level function, which will create a memory interface for the array in the hardware core generated by SmartHLS. Arguments into the top-level function can be constants, pointers, arrays, and FIFO data types. The top-level function can also have a return value.

For more information,see SmartHLS IDE, C++ Canny Edge Detection (SW/HW Co-Simulation), as a reference.

If a top-level argument is coming from a dynamically allocated array (for example, malloc), the size of the array (in bytes) must be specified with our interface pragma (for example,#pragma HLS interface argument(<arg_name>) depth(<int>)). For more information, see 3.6.1.14 Memory Interface for Pointer Argument/Global Variable . The sizes of arrays that are statically allocated do not need to be specified with the pragma, as SmartHLS will automatically determine them.

For debugging purposes, SmartHLS converts any C printf statements into Verilog $write statements so that values printed during software execution will also be printed during hardware simulation. This allows easy verification of the correctness of the hardware circuit. Verilog $write statements are unsynthesizable and will not affect the final FPGA hardware.

To specify the arguments to be passed to the software test bench (for example, int main(int argc, char *argv[])), a Makefile argument PROGRAM_ARGUMENTS can be defined in a makefile.user file (you need to create the file in the SmartHLS project folder). For example, if a software test bench takes in two arguments, an input BMP file and a golden output BMP file, you would specify the following in the makefile.user file.

PROGRAM_ARGUMENTS = input_file.bmp golden_output_file.bmp
Important: Co-simulating multiple top-level modules:
  • Co-simulation supports verifying multiple top-level modules simultaneously. Each top-level module is verified solely based on the corresponding top-level function's input and expected output gathered from the software test bench. However the Co-simulation test bench will simulate all top-level modules simultaneously with the same clock.
  • If the user wants to verify a single top-level module, the top pragma should be only added for the desired function in the source code.
Important: Limitations:
  • When function pipelining is used, the top-level function cannot have array interfaces (array arguments or global arrays that are accessed from both SW test bench and HW functions).
  • When multi-threading is used (For more information,see 3.5.1.11 Multi-threading with SmartHLS Threads), Co-Simulation can only support the case when all threads are joined in the functions where the threads are forked. Free-running threads (that are continuously running and never joined) are not supported by SW/HW Co-Simulation.