Vitis HLS Process Overview

Vitis HLS is project based and can contain multiple variations called "solutions" to drive synthesis and simulation. Each solution can target either the Vivado IP flow, or the Vitis Kernel flow. Based on the target flow, each solution will specify different constraints and optimization directives, as described in Enabling the Vivado IP Flow and Enabling the Vitis Kernel Flow. Refer to Default Settings of Vivado/Vitis Flows for a clear list of differences between the two flows.

The following are the synthesis, analysis, and optimization steps in the typical design flow:

  1. Create a new Vitis HLS project.
  2. Verify the source code with C simulation.
  3. Run high-level synthesis to generate RTL files.
  4. Analyze the results by examining latency, initiation interval (II), throughput, and resource utilization.
  5. Optimize and repeat as needed.
  6. Verify the results using C/RTL Co-simulation.

Vitis HLS implements the solution based on the target flow, default tool configuration, design constraints, and any optimization pragmas or directives you specify. You can use optimization directives to modify and control the implementation of the internal logic and I/O ports, overriding the default behaviors of the tool.

The C/C++ code is synthesized as follows:

  • Top-level function arguments synthesize into RTL I/O port interfaces automatically by Vitis HLS. As described in Managing Interface Synthesis, the default interfaces that the tool creates depends on the target flow, the data type and direction of the function argument, the default interface mode, and any user-specified INTERFACE pragmas or directives that manually define the interface.
  • Sub-functions of the top-level C/C++ function synthesize into blocks in the hierarchy of the RTL design.
    • The final RTL design includes a hierarchy of modules or entities that correspond with the original top-level C function hierarchy.
    • Vitis HLS automatically inlines sub-functions into higher level functions, or the top-level function as needed to improve performance.
    • You can disable automatic inlining by specifying the INLINE pragma to a sub-function, or using set_directive_inline, and setting it to OFF in your solution.
    • By default, each call of the C sub-function uses the same instance of the RTL module. However, you can implement multiple instances of the RTL module to improve performance by specifying the ALLOCATION pragma, or using the set_directive_allocation in your solution.
  • Loops in the C functions are kept rolled and are pipelined by default to improve performance.
    • The Vitis HLS tool will not unroll loops unless it improves the performance of the solution, like unrolling nested loops to pipeline the top-level loop. When loops are rolled, synthesis creates the logic for one iteration of the loop, and the RTL design executes this logic for each iteration of the loop in sequence. Unrolled loops let some or all iterations of the loop occur in parallel, but also consume more device resources.
    • You can manually unroll loops using the UNROLL pragma, or the set_directive_unroll command.
    • Loops can also be pipelined, either with a finite-state machine fine-grain implementation (loop pipelining) or with a more coarse-grain handshake-based implementation (dataflow).
  • Arrays in the code are synthesized into block RAM (BRAM), LUT RAM, or UltraRAM in the final FPGA design.
    • If the array is on the top-level function interface, high-level synthesis implements the array as ports with access to a block RAM outside the design.
    • You can reconfigure the type of memory used, or reconfigure read/write memory transfers using the ARRAY_PARTITION or ARRAY_RESHAPE pragmas, or the associated set_directive_array commands to change the default assignments.
IMPORTANT: In Vitis HLS, if you specify a pragma or directive in a particular scope (function/loop/region), then the default behavior of the tool as described above will be overridden by your pragma. In that case, for example, default features like auto-pipelining of loops with low iterations counts cannot be applied if you have specified pragmas or configurations in the current scope.

After synthesis, you can analyze the results in the various reports produced to determine the quality of your results. After analyzing the results, you can create additional solutions for your project specifying different constraints and optimization directives, and synthesize and analyze those results. You can compare results between different solutions to see what has worked and what has not. You can repeat this process until the design has the desired performance characteristics. Using multiple solutions allows you to proceed with development while retaining prior solutions.

Enabling the Vivado IP Flow

When you select the Vivado IP Flow Target on the Solution Settings dialog box, as discussed in Creating a New Vitis HLS Project, you are configuring Vitis HLS to generate RTL IP files for use in the Vivado Design Suite, for inclusion in the IP catalog, and for use in block designs of the IP integrator tool. HLS synthesis transforms your C or C++ code into register transfer level (RTL) code that you can synthesize and implement into the programmable logic region of a Xilinx device.

The flow selection is enabled with the open_solution -flow_target vivado command.

The Vivado IP flow is more flexible and less structured than the Vitis Kernel flow. Vivado IP can support a wide variety of interface specifications and data transfer protocols, and does not naturally support the Xilinx runtime (XRT) requirements of the Vitis system. The Vivado IP flow provides much greater discretion in your design choices, however, leaves the integration and management of the IP up to you as well.

Unlike the Vitis Kernel flow, the Vivado IP flow has no default interface assigned to function arguments, or ports on the IP. You can manually assign the interface specification for your function argument, using the INTERFACE pragma or set_directive_interface command, to meet the needs of your Vivado design.

Enabling the Vitis Kernel Flow

When you select the Vitis Kernel Flow Target on the Solution Settings dialog box, as discussed in Creating a New Vitis HLS Project, you are configuring Vitis HLS to generate the compiled kernel object (.xo) for the Vitis application acceleration flow. The Vitis Kernel flow is more restrictive than the Vivado IP flow, and the kernels produced by the HLS tool must meet the specific requirements of the platforms and Xilinx runtime (XRT), as described in Kernel Properties in the Vitis Unified Software Platform Documentation (UG1416).

The flow selection is enabled with the open_solution -flow_target vitis command.

When specifying open_solution -flow_target vitis, or enabling the Vitis Kernel Flow in the IDE, Vitis HLS implements interface ports using the AXI standard as described in AXI Adapter Interface Protocols.

The solution is updated to include two new configuration commands:

config_rtl -register_reset_num=3


config_interface -default_slave_interface=s_axilite -m_axi_latency=64 \
-m_axi_alignment_byte_size=64 -m_axi_max_widen_bitwidth=512

The config_rtl command defines characteristics of the RTL code generated by Vitis HLS, specifically defining characteristics of the reset required by the Vitis application acceleration development flow.

The config_interface command sets characteristics of the default interface protocols the tool assigns. If there are no existing Interface pragmas in the code, then the following interface protocols will be applied.

  • AXI4-Lite interfaces (s_axilite) are assigned to scalar arguments, global variables, control signals for arrays, and the return value of the software function.
  • AXI4 Master interfaces (m_axi) are assigned to pointer arguments of the C/C++ function.
  • Vitis HLS automatically tries to infer BURST transactions whenever possible to aggregate memory accesses to maximize the throughput bandwidth and/or minimize the latency.
  • Defining a software function argument using an hls::stream data type implies an AXI4-Stream (axis) port.

You can manually assign the interface specification for your function argument, using the INTERFACE pragma or set_directive_interface command. You can use this technique to change the settings of the default interfaces, such as -bundle to group function arguments into AXI interfaces, and -max_read/write_burst_length to manage burst transactions.

Default Settings of Vivado/Vitis Flows

The open_solution target will configure the compiler for either the Vivado IP flow or the Vitis Kernel flow. This will change the default behavior of the tool according to the flow specified. The following table shows the default settings of both flows so that you can quickly determine the differences in the default configuration.

TIP: Beyond the default configuration, there are additional features of the Vitis HLS tool that support one flow, but not the other, or are configured differently between the two flows. Those differences are highlighted throughout this document.
Table 1. Default Configuration
Configuration Vivado Vitis
set_clock_uncertainty 27% 27%
config_compile -pipeline_loops 64 64
config_compile -name_max_length 255 255
config_export -vivado_optimization_level 0 0
config_export -vivado_phys_opt None None
config_rtl -module_auto_prefix TRUE TRUE
config_rtl -register_reset_num 0 3
config_schedule -enable_dsp_full_reg TRUE TRUE
INTERFACE pragma defaults Has no default interface Defaults to m_axi interface
config_interface -m_axi_addr64 TRUE TRUE
config_interface -m_axi_latency 0 64
config_interface -m_axi_alignment_byte_size 0 64
config_interface -m_axi_max_widen_bitwidth 0 512
config_interface -default_slave_interface None Slave