3.5.1.21.8 Example Usage of the Driver Functions
(Ask a Question)When using the SmartHLS™-generated API directly, as opposed to the transformed top-level function, users may have more flexibility to exchange data and control over the generated hardware modules. Let's use the following example code to demonstrate a few concepts when using the API:
- Use of the blocking top-level control driver function.
- Use of non-blocking calls.
- Manually write/read arguments.
- Control of multiple copies of the same SmartHLS-generated module. Note that this not the equivalent to having two different SmartHLS modules.
Below is the example for simple top-level function that adds two numbers and return the result. This function will be converted to Verilog and an HDL+ component for Libero® SmartDesign will be generated.
int add(int a, int b) { #pragma HLS function top #pragma HLS interface default type(axi_target) #pragma HLS interface argument(a) type(axi_target) num_elements(1) #pragma HLS interface argument(b) type(axi_target) num_elements(1) return (a + b); }
Once the module is generated, users can make a copy of the module to have two instances of the same module. Below is the code where the top-level driver functions are used to initialize, start and wait for the completion of both instances of the add(..)
module . While the top-level driver function (add_hls_driver(..., SUM0_BASE_ADDR
) is called, the processor has to wait for it to complete before starting the second instance. Note that the difference is the base address assigned to each module.
// use the top-level driver sums[0] = add_hls_driver(tv1[0], tv1[1], SUM0_BASE_ADDR); sums[1] = add_hls_driver(tv1[2], tv1[3], SUM1_BASE_ADDR);
To avoid the blocking behavior, a program can use the non-blocking API functions for each instance of the module. The following code shows that the inputs are initialized and the SmartHLS modules are started one after the other via <TopFunc>_write_input_and_start( )
and read the outputs via <TopFunc>_join_and_read_output( )
. The use of these two functions allow the program to perform other tasks between the calls to start and join while the HLS module are processing in parallel.
// use individual control drivers add_write_input_and_start(tv2[0], tv2[1], SUM0_BASE_ADDR); add_write_input_and_start(tv2[2], tv2[3], SUM1_BASE_ADDR); sums[0] = add_join_and_read_output(SUM0_BASE_ADDR); sums[1] = add_join_and_read_output(SUM1_BASE_ADDR);
Finally, the following example shows updates to individual input arguments via scalar input driver functions. In this case, the program writes to the arguments a
and b
prior to calling add_start( )
. It also shows that the values written can also be read back. This provides more flexibility when transferring data to and from the SmartHLS modules.
add_write_a(tv3[0], SUM0_BASE_ADDR); if(tv3[0] != add_read_a(SUM0_BASE_ADDR)){ PRINTF("Test 3: adder at %#x writing and reading a FAIL\r\n", SUM0_BASE_ADDR); err++; } add_write_b(tv3[1], SUM0_BASE_ADDR); if(tv3[1] != add_read_b(SUM0_BASE_ADDR)){ PRINTF("Test 3: adder at %#x writing and reading a FAIL\r\n", SUM0_BASE_ADDR); err++; } add_write_a(tv3[2], SUM1_BASE_ADDR); if(tv3[2] != add_read_a(SUM1_BASE_ADDR)){ PRINTF("Test 3: adder at %#x writing and reading a FAIL\r\n", SUM1_BASE_ADDR); err++; } add_write_b(tv3[3], SUM1_BASE_ADDR); if(tv3[3] != add_read_b(SUM1_BASE_ADDR)){ PRINTF("Test 3: adder at %#x writing and reading a FAIL\r\n", SUM1_BASE_ADDR); err++; } add_start(SUM0_BASE_ADDR); add_start(SUM1_BASE_ADDR); sums[0] = add_join(SUM0_BASE_ADDR); sums[1] = add_join(SUM1_BASE_ADDR);
add()
module are
instantiated and accessible by the CPU.