Optimizing the HLS Project

After analysis, you will most likely need or want to optimize the performance of your function. Even if it is performing well there may be opportunities for improvement. This section discusses the mechanisms for applying optimizations to your project. Refer to Optimization Techniques in Vitis HLS for a discussion of the various types of optimizations you can perform.

You can add optimization directives directly into the source code as compiler pragmas, using various HLS PRAGMAS, or you can use Tcl set_directive commands to apply optimization directives in a Tcl script to be used by a solution.

In addition to optimization pragmas and directives, Vitis HLS provides a number of configuration settings to let you manage the default results of simulation and synthesis. These configuration settings are accessed using the Solution > Solution Settings... menu command, and clicking the Add command to add configuration settings. Refer to Configuration Commands for more information on applying specific configuration settings.

Creating Additional Solutions

The most typical use of Vitis HLS is to create an initial design, analyze the results, and then perform optimizations to meet the desired area and performance goals. This is often an iterative process, requiring multiple steps and multiple optimizations to achieve the desired results. Solutions offer a convenient way to configure the tool, add directives to your function to improve the results, and preserve those results to compare with other solutions.

To create an additional solution for your , use the Project > New Solution menu command, or the New Solution toolbar button . This opens the Solution Wizard as shown in the following figure.

Figure 1: Solution Wizard

The Solution Wizard has the same options as described in Creating a New Vitis HLS Project, with an additional option to let you Copy directives and constraints from solution. In the case where there are already multiple solutions, you can specify which solution to copy from. After the new solution has been created, optimization directives can be added (or modified if they were copied from the previous solution).

When your project has multiple solutions, the commands are generally directed at the current active solution. You can specify the active solution by right-clicking on a solution in the Explorer view, and use the Set Active Solution command. By default, synthesis and simulation commands build the active solution, directives are applied to the active solution, and reports are opened for the active solution. You want to ensure you are working in the correct solution when your project has multiple solutions.

TIP: The Explorer view shows which solution is active by applying a bold-italic font to the solution name.

Adding Pragmas and Directives

Vitis HLS pragmas and directives let you configure the synthesis results for your code.

  • HLS Pragmas are added to the source code to enable the optimization or change in the original source code. Every time the code is synthesized, it is implemented according to the specified pragmas.
  • Optimization Directives, or the set_directive commands, can be specified as Tcl commands that are associated with a specific solution, or set of solutions. Allowing you to customize the synthesis results for the same code base across different solutions.
IMPORTANT: In cases where pragmas or directives conflict with other pragmas or directives, the synthesis process returns an error until the conflict is resolved. For example, assigning ARRAY_PARTITION and ARRAY_RESHAPE pragmas to the same array variable will result in an error.

To add pragmas or directives to your project:

  1. In the Explorer view of the Vitis HLS IDE, double-click the code file under the Source folder to open the Code Editor dialog box, the Outline view, and the Directive view.
  2. Use the Directive view to add pragmas to your source code. This view helps you add and manage pragmas and directives for your project, and it ensures that the pragmas are correct and applied in the proper location. To use this view:
    1. With your source code open, select the Directive view tab to locate the function, loop, or feature of the code to add a pragma or directive to.

      Vitis HLS applies directives to the appropriate scope for the object currently selected in the Directive view.

    2. Right-click an object in the Directive view to use the Insert Directive command. The Vitis HLS Directive Editor opens, as shown in the following figure:

    3. Review the Vitis HLS Directive Editor dialog box. It includes the following sections:
      Directive
      Specifies the directive or pragma to apply. This is a drop-down menu that lets you choose from the list of available directives.
      Destination
      Specifies that a pragma should be added to the source file, or that a set_directive command should be added to a Tcl script, the directive file, associated with the active solution.
      TIP: If your project only has one solution then it is always active. However, if you have multiple solutions you will need to ensure the desired solution is active in the project. Right-click the solution in the Explorer view of the project and click the Set Active Solution command. Refer to Creating Additional Solutions for details on adding solutions.
      Options
      Lists various configurable options associated with the currently selected directive.
    4. Click OK to apply the pragma or directive.
    Note: To view information related to a selected directive, click Help.

Using Directives in Scripts vs. Pragmas in Code

In the Vitis HLS Directive Editor dialog box, you can specify either of the following Destination settings:

Directive File
Vitis HLS inserts the directive as a Tcl command into the file directives.tcl in the solution directory.
Source File
Vitis HLS inserts the directive directly into the C source file as a pragma.

The following table describes the advantages and disadvantages of both approaches.

Table 1. Tcl Directives Versus Pragmas
Directive Format Advantages Disadvantages
Directives file (Tcl Script)

Each solution has independent directives. This approach is ideal for design exploration.

If any solution is re-synthesized, only the directives specified in that solution are applied.

If the C source files are transferred to a third-party or archived, the directives.tcl file must be included.

The directives.tcl file is required if the results are to be re-created.

Source Code (Pragma)

The optimization directives are embedded into the C source code.

Ideal when the C sources files are shipped to a third-party as C IP. No other files are required to recreate the same results.

Useful approach for directives that are unlikely to change, such as TRIPCOUNT and INTERFACE.

If the optimization directives are embedded in the code, they are automatically applied to every solution when re-synthesized.
TIP: When using the Vitis core development kit to define hardware acceleration of your C/C++ code, you should use pragmas in your source code, rather than trying to work with directives in a Tcl file. In the Vitis HLS bottom-up flow (or the Vitis kernel flow) you can use directives to develop different solutions, but should convert your final directives to pragmas in the finished project.

When specifying values for pragma arguments, you can use literal values (for example, 1, 55, 3.14), or pass a macro using #define. The following example shows a pragma with literal values:

#pragma HLS ARRAY_PARTITION variable=k_matrix_val  type=cyclic factor=5

This example uses defined macros:

#define E 5
#pragma HLS ARRAY_PARTITION variable=k_matrix_val  type=cyclic factor=E

Applying Directives to the Proper Scope

Although the Vitis HLS GUI lets you apply directives to specific code objects, the directives are added to the scope that contains the object. For example, you can apply the INTERFACE pragma to an interface object in the Vitis HLS GUI, but the directive is applied to the top-level function (scope). The interface port (object) is identified in the directive.

You can apply optimization directives to the following objects and scopes:

Functions
When you apply directives to functions, Vitis HLS applies the directive to all objects within the scope of that function. The effect of any directive stops at the next level of the function hierarchy, and does not apply to sub-functions.
TIP: Directives that include a recursive option, such as the PIPELINE directive, can be applied recursively through the hierarchy.
Interfaces
Vitis HLS applies the directive to the top-level function, which is the scope that contains the interface.
Loops
Directives apply to all objects within the scope of the loop.

For example, if you apply the LOOP_MERGE directive to a loop, Vitis HLS applies the directive to any sub-loops within the loop, but not to the loop itself. The loop to which the directive is applied is not merged with siblings at the same level of hierarchy.

Arrays
Directives are applied to the scope that contains the array.

Applying Optimization Directives to Global Variables

Directives can only be applied to scopes, or to objects within a scope. As such, they cannot be directly applied to global variables which are declared outside the scope of any function. Therefore, to apply a directive to a global variable you must manually assign it using the following process:
  1. With the code open in the Code Editor, select the scope (function, loop or region) where the global variable is used in the Directive view.
  2. Right-click and use the Insert Directive command to open the Vitis HLS Directives Editor.
  3. Select and configure the required directive, and click OK to add it.
  4. Locate the added directive in the Directive view, and manually edit the variable name to assign it to the global variable.

Applying Optimization Directives to Class Objects

Optimization directives can be also applied to objects or scopes defined in a class. The difference is typically that classes are defined in a header file. Use one of the following actions to open the header file:

  • From the Explorer view in the Vitis HLS GUI, open the Includes folder, double-click the header file to open it in the Code Editor.
  • From within an open source code file, place the cursor over the #include statement for the header file, hold down the Ctrl key, and click the header file to open it in the Code Editor.

The Directives tab is populated with the objects in the header file, and directives can be applied.

CAUTION: Care should be taken when applying directives as pragmas to a header file. The file might be used by other people or used in other projects. Any directives added as a pragma are applied each time the header file is included in a design.

Applying Optimization Directives to Templates

To apply optimization directives manually on templates when using Tcl commands, specify the template arguments and class when referring to class methods. For example, given the following C++ code:

template <uint32 SIZE, uint32 RATE>
void DES10<SIZE,RATE>::calcRUN() {…}

The following Tcl command is used to specify the INLINE directive on the function:

set_directive_inline DES10<SIZE,RATE>::calcRUN

Using #define with Pragma Directives

Pragma directives support the use of values specified by #define statements.

For example, the following code seeks to specify the depth of a stream using the #define statement:

#include <hls_stream.h>
using namespace hls;

#define STREAM_IN_DEPTH 8

void foo (stream<int> &InStream, stream<int> &OutStream) {

// pragma using #DEFINE 
#pragma HLS stream depth=STREAM_IN_DEPTH variable=InStream

// pragma using value 
#pragma HLS stream depth=8 variable=OutStream

}

You can use a constant such as const int, or constexpr. For example:

const int MY_DEPTH=1024; 
#pragma HLS stream variable=my_var depth=MY_DEPTH

You can also use macros in the C code to implement this functionality. The key to using macros is to use a level of hierarchy in the macro. This allows the expansion to be correctly performed. The code can be made to compile as follows:

#include <hls_stream.h>
using namespace hls;

#define PRAGMA_SUB(x) _Pragma (#x)
#define PRAGMA_HLS(x) PRAGMA_SUB(x)
#define STREAM_IN_DEPTH 8

void foo (stream<int> &InStream, stream<int> &OutStream) {

// Legal pragmas 
PRAGMA_HLS(HLS stream depth=STREAM_IN_DEPTH variable=InStream)
#pragma HLS stream depth=8 variable=OutStream

}

Failure to Satisfy Optimization Directives

When optimization directives are applied, Vitis HLS outputs information to the console (and log file) detailing the progress. In the following example the PIPELINE directives was applied to the C function with an II=1 (iteration interval of 1) but synthesis failed to satisfy this objective.


INFO: [SCHED 11] Starting scheduling ...
INFO: [SCHED 61] Pipelining function 'array_RAM'.
WARNING: [SCHED 63] Unable to schedule the whole 2 cycles 'load' operation 
('d_i_load', array_RAM.c:98) on array 'd_i' within the first cycle (II = 1).
WARNING: [SCHED 63] Please consider increasing the target initiation interval of the 
pipeline.
WARNING: [SCHED 69] Unable to schedule 'load' operation ('idx_load_2', 
array_RAM.c:98) on array 'idx' due to limited memory ports.
INFO: [SCHED 61] Pipelining result: Target II: 1, Final II: 4, Depth: 6.
INFO: [SCHED 11] Finished scheduling.

IMPORTANT: If Vitis HLS fails to satisfy an optimization directive, it automatically relaxes the optimization target and seeks to create a design with a lower performance target. If it cannot relax the target, it will halt with an error.

By seeking to create a design which satisfies a lower optimization target, Vitis HLS is able to provide three important types of information:

  • What target performance can be achieved with the current C code and optimization directives.
  • A list of the reasons why it was unable to satisfy the higher performance target.
  • A design which can be analyzed to provide more insight and help understand the reason for the failure.

In message SCHED-69, the reason given for failing to reach the target II is due to limited ports. The design must access a block RAM, and a block RAM only has a maximum of two ports.

The next step after a failure such as this is to analyze what the issue is. In this example, analyze line 52 of the code and/or use the Analysis perspective to determine the bottleneck and if the requirement for more than two ports can be reduced or determine how the number of ports can be increased.

After the design is optimized and the desired performance achieved, the RTL can be verified and the results of synthesis packaged as IP.