Since starting this blog back in 2013, I get emailed questions from time to time. I definitely try to answer as many as I can and recently received an interesting one that I thought would make for a great example blog discussing what is actually occurring when we work with High-Level Synthesis.
The questioner was working with Vivado HLS 2020.1 and Vitis HLS 2020.1 and struggling to achieve the same operation in both tools. The code itself is simple and leverages the ap_wait_n() command to delay for several clock cycles.
When the code is co-simulated in Vivado HLS, the pointer raw_line is toggled to one and then back to zero. When the same code is run within Vitis HLS, the raw_line does not toggle and stays at zero.
The use of the volatile qualifier tells the compiler that the signal may change at any time, without dependency on the code around it. Typically in embedded systems this is used to define memory mapped registers, global variables which are changed by interrupt sub routine or multi-threaded tasks. In HLS we use it in a similar way to indicate to the HLS compiler to make no assumptions about the pointer accesses.
This got me thinking about how we can track issues through our design if we come across them in either HLS tool. One of the most useful views available in both Vivado HLS and Vitis HLS is the analysis view. This view is most used to determine where optimization pragmas can be deployed in the source code to improve throughput and latency.
Using the analysis view, we can also see how the synthesis tool has mapped our C/C++ code to hardware operations. Additionally, we can cross probe between the instructions implemented and our source code.
When we cross probe between the code in Vitis HLS and Vivado HLS, we quickly notice differences in the implemented operation / control step.
Examining the Vivado HLS analysis view, we can see the line of code where the single output is set to one, and maps to a write instruction.
This gives the performance as expected in co-simulation, where the raw_line signal toggles between 0 and 1 as desired.
Observing the same in the Vitis HLS analysis view shows there is no corresponding write the first time raw_line is set.
This results in the raw_line do not change as desire during co-simulation
Wanting to understand this difference in behavior, I examined the Xilinx HLS Example Library pointer_stream_good example which shows how multiple access can be made to pointers. To achieve this, it uses the volatile command on int types. Examining the implementation of this example, I could see that the multiple writes to the pointers were performed as expected with the source code.
This pointed to the issue being the ap_int type and its interpretation being different between Vivado HLS and Vitis HLS. As the questioner wanted, a single bit output switching the type to a bool enabled a single bit output to be created which toggles between 0 and 1.
I updated the header file to include a typedef for the output and include the necessary header file.
Re-running the code with this change resulted in the desired behavior. Following re-synthesis and by looking at the analysis view again, you can see the multiple writes taking place to the desired output.
The co-simulation shows the desired result as well.
Now that we get the same performance as first intended, I have replied to the original email with the solution.
If you want to understand a little more about the changes between Vivado HLS and Vitis HLS, check out Xilinx User Guide UG1391.