Visible to Intel only — GUID: GUID-8EAA57D0-08FB-4A2B-B2AD-5737A6B9F117
Intel oneAPI DPC++/C++ Compiler Handbook for FPGAs Overview
Introduction To FPGA Design Concepts
Intel oneAPI FPGA Development
Getting Started with the Intel oneAPI DPC++/C++ Compiler for Intel FPGA Development
Defining a Kernel for FPGAs
Debugging and Verifying Your Design
Analyzing Your Design
Optimizing Your Kernel
Optimizing Your Host Application
Integrating Your Kernel into DSP Builder for Intel FPGAs
Integrating Your RTL IP Core Into a System
RTL IP Core Kernel Interfaces
Loops
Pipes
Data Types and Arithmetic Operations
Parallelism
Memories and Memory Operations
Libraries
Additional FPGA Acceleration Flow Considerations
FPGA Optimization Flags, Attributes, Pragmas, and Extensions
Quick Reference
Additional Information
Document Revision History for the Intel oneAPI DPC++/C++ Compiler Handbook for Intel FPGAs
Notices and Disclaimers
Set the Environment Variables and Launch Visual Studio* Code
Create an FPGA Visual Studio* Code Project
Enable Code Completion in a Visual Studio* Code Project
Configure Running and Debugging in a Visual Studio* Code Project
Debugging Your Kernel in Visual Studio* Code with a Native Debugger
Generate and View the FPGA Optimization Report
Build and Run the FPGA Hardware Image
Throughput
Resource Use
System-level Profiling Using the Intercept Layer for OpenCL™ Applications
Multithreaded Host Application
Utilizing Hardware Kernel Invocation Queue
Double Buffering Host Utilizing Kernel Invocation Queue
N-Way Buffering to Overlap Kernel Execution
Prepinning Memory
Simple Host-Device Streaming
Buffered Host-Device Streaming
Refactor the Loop-Carried Data Dependency
Relax Loop-Carried Dependency
Transfer Loop-Carried Dependency to Local Memory
Minimize the Memory Dependencies for Loop Pipelining
Unroll Loops
Fuse Loops to Reduce Overhead and Improve Performance
Optimize Loops With Loop Speculation
Remove Loop Bottlenecks
Improve fMAX/II with Shannonization
Optimize Inner Loop Throughput
Improve Loop Performance by Caching Data in On-Chip Memory
Global Memory Bandwidth Use Calculation
Manual Partition of Global Memory
Partitioning Buffers Across Different Memory Types (Heterogeneous Memory)
Partitioning Buffers Across Memory Channels of the Same Memory Type
Ignoring Dependencies Between Accessor Arguments
Contiguous Memory Accesses
Static Memory Coalescing
Specify Schedule fMAX Target for Kernels (-Xsclock=<clock target>)
Create a 2xclock Interface (-Xsuse-2xclock)
Disable Burst-Interleaving of Global Memory (-Xsno-interleaving)
Force Ring Interconnect for Global Memory (-Xsglobal-ring)
Force a Single Store Ring to Reduce Area (-Xsforce-single-store-ring)
Force Fewer Read Data Reorder Units to Reduce Area (-Xsnum-reorder)
Disable Hardware Kernel Invocation Queue (-Xsno-hardware-kernel-invocation-queue)
Modify the Handshaking Protocol Between Clusters (-Xshyper-optimized-handshaking)
Disable Automatic Fusion of Loops (-Xsdisable-auto-loop-fusion)
Fuse Adjacent Loops With Unequal Trip Counts (-Xsenable-unequal-tc-fusion)
Pipeline Loops in Non-task Kernels (-Xsauto-pipeline)
Control Semantics of Floating-Point Operations (-fp-model=<value>)
Modify the Rounding Mode of Floating-point Operations (-Xsrounding=<rounding_type>)
Global Control of Exit FIFO Latency of Stall-free Clusters (-Xssfc-exit-fifo-type=<value>)
Enable the Read-Only Cache for Read-Only Accessors (-Xsread-only-cache-size=<N>)
Control Hardware Implementation of the Supported Data Types and Math Operations (-Xsdsp-mode=<option>)
Generate Register Map Wrapper (-Xsregister-map-wrapper-type)
Allow Wide Memory Initialization (-Xsallow-wide-device-globals)
Specify Schedule fMAX Target for Kernels (scheduler_target_fmax_mhz)
Specify a Workgroup Size (max_work_group_size/reqd_work_group_size)
Specify Number of SIMD Work Items (num_simd_work_items)
Omit Hardware that Generates and Dispatches Kernel IDs (max_global_work_dim)
Omit Hardware that Supports Global Work Offsets (no_global_work_offset)
Reduce Kernel Area and Latency (use_stall_enable_clusters)
Visible to Intel only — GUID: GUID-8EAA57D0-08FB-4A2B-B2AD-5737A6B9F117
Suggested Kernel Coding Styles
For creating your kernel, use one of the following recommended general coding styles:
- Functor Coding Style Example: With the functor coding style, you can write your kernel code out-of-line from the host code. The style is typically preferred for the SYCL* HLS flow. In the SYCL* HLS flow, your host code becomes the testbench for your IP core.
The functor coding style clearly communicates what the inputs to your kernel are and you can easily annotate kernel arguments.
Lambda Coding Style Example: The lambda coding style is typically used for kernels in the FPGA acceleration flow.
In the following examples, the kernel code and the code for its enqueueing is highlighted.
Functor Coding Style Example
#include <iostream>
// oneAPI headers
#include <sycl/ext/intel/fpga_extensions.hpp>
#include <sycl/sycl.hpp>
// Forward declare the kernel name in the global scope. This is an FPGA best
// practice that reduces name mangling in the optimization reports.
class VectorAddID;
struct VectorAdd {
int *const vec_a_in;
int *const vec_b_in;
int *const vec_c_out;
int len;
void operator()() const {
for (int idx = 0; idx < len; idx++) {
int a_val = vec_a_in[idx];
int b_val = vec_b_in[idx];
int sum = a_val + b_val;
vec_c_out[idx] = sum;
}
}
};
constexpr int kVectSize = 256;
int main() {
bool passed = true;
try {
// Use compile-time macros to select either:
// - the FPGA emulator device (CPU emulation of the FPGA)
// - the FPGA device (a real FPGA)
// - the simulator device
#if FPGA_SIMULATOR
auto selector = sycl::ext::intel::fpga_simulator_selector_v;
#elif FPGA_HARDWARE
auto selector = sycl::ext::intel::fpga_selector_v;
#else // #if FPGA_EMULATOR
auto selector = sycl::ext::intel::fpga_emulator_selector_v;
#endif
// create the device queue
sycl::queue q(selector);
// make sure the device supports USM host allocations
auto device = q.get_device();
std::cout << "Running on device: "
<< device.get_info<sycl::info::device::name>().c_str()
<< std::endl;
if (!device.has(sycl::aspect::usm_host_allocations)) {
std::terminate();
}
// declare arrays and fill them
// allocate in shared memory so the kernel can see them
int *vec_a = sycl::malloc_shared<int>(kVectSize, q);
int *vec_b = sycl::malloc_shared<int>(kVectSize, q);
int *vec_c = sycl::malloc_shared<int>(kVectSize, q);
assert(vec_a);
assert(vec_b);
assert(vec_c);
for (int i = 0; i < kVectSize; i++) {
vec_a[i] = i;
vec_b[i] = (kVectSize - i);
}
std::cout << "add two vectors of size " << kVectSize << std::endl;
q.single_task<VectorAddID>(VectorAdd{vec_a, vec_b, vec_c, kVectSize})
.wait();
// verify that vec_c is correct
for (int i = 0; i < kVectSize; i++) {
int expected = vec_a[i] + vec_b[i];
if (vec_c[i] != expected) {
std::cout << "idx=" << i << ": result " << vec_c[i] << ", expected ("
<< expected << ") A=" << vec_a[i] << " + B=" << vec_b[i]
<< std::endl;
passed = false;
}
}
std::cout << (passed ? "PASSED" : "FAILED") << std::endl;
sycl::free(vec_a, q);
sycl::free(vec_b, q);
sycl::free(vec_c, q);
} catch (sycl::exception const &e) {
// Catches exceptions in the host code.
std::cerr << "Caught a SYCL host exception:\n" << e.what() << "\n";
// Most likely the runtime couldn't find FPGA hardware!
if (e.code().value() == CL_DEVICE_NOT_FOUND) {
std::cerr << "If you are targeting an FPGA, please ensure that your "
"system has a correctly configured FPGA board.\n";
std::cerr << "Run sys_check in the oneAPI root directory to verify.\n";
std::cerr << "If you are targeting the FPGA emulator, compile with "
"-DFPGA_EMULATOR.\n";
}
std::terminate();
}
return passed ? EXIT_SUCCESS : EXIT_FAILURE;
}
Lambda Coding Style Example
#include <iostream>
#include <vector>
// oneAPI headers
#include <sycl/sycl.hpp>
#include <sycl/ext/intel/fpga_extensions.hpp>
using namespace sycl;
// Forward declare the kernel name in the global scope. This is an FPGA best
// practice that reduces name mangling in the optimization reports.
class VectorAddID;
void VectorAdd(const int *vec_a_in, const int *vec_b_in, int *vec_c_out,
int len) {
for (int idx = 0; idx < len; idx++) {
int a_val = vec_a_in[idx];
int b_val = vec_b_in[idx];
int sum = a_val + b_val;
vec_c_out[idx] = sum;
}
}
constexpr int kVectSize = 256;
int main() {
bool passed = true;
try {
// Use compile-time macros to select either:
// - the FPGA emulator device (CPU emulation of the FPGA)
// - the FPGA device (a real FPGA)
// - the simulator device
#if FPGA_SIMULATOR
auto selector = sycl::ext::intel::fpga_simulator_selector_v;
#elif FPGA_HARDWARE
auto selector = sycl::ext::intel::fpga_selector_v;
#else // #if FPGA_EMULATOR
auto selector = sycl::ext::intel::fpga_emulator_selector_v;
#endif
// create the device queue
sycl::queue q(selector);
// make sure the device supports USM host allocations
auto device = q.get_device();
std::cout << "Running on device: "
<< device.get_info<sycl::info::device::name>().c_str()
<< std::endl;
if (!device.has(sycl::aspect::usm_host_allocations)) {
std::terminate();
}
// declare arrays and fill them
// allocate in shared memory so the kernel can see them
int *vec_a = malloc_shared<int>(kVectSize, q);
int *vec_b = malloc_shared<int>(kVectSize, q);
int *vec_c = malloc_shared<int>(kVectSize, q);
assert(vec_a);
assert(vec_b);
assert(vec_c);
for (int i = 0; i < kVectSize; i++) {
vec_a[i] = i;
vec_b[i] = (kVectSize - i);
}
std::cout << "add two vectors of size " << kVectSize << std::endl;
q.single_task<VectorAddID>([=]() {
VectorAdd(vec_a, vec_b, vec_c, kVectSize);
})
.wait();
// verify that vec_c is correct
for (int i = 0; i < kVectSize; i++) {
int expected = vec_a[i] + vec_b[i];
if (vec_c[i] != expected) {
std::cout << "idx=" << i << ": result " << vec_c[i] << ", expected ("
<< expected << ") A=" << vec_a[i] << " + B=" << vec_b[i]
<< std::endl;
passed = false;
}
}
std::cout << (passed ? "PASSED" : "FAILED") << std::endl;
free(vec_a, q);
free(vec_b, q);
free(vec_c, q);
} catch (sycl::exception const &e) {
// Catches exceptions in the host code.
std::cerr << "Caught a SYCL host exception:\n" << e.what() << "\n";
// Most likely the runtime couldn't find FPGA hardware!
if (e.code().value() == CL_DEVICE_NOT_FOUND) {
std::cerr << "If you are targeting an FPGA, please ensure that your "
"system has a correctly configured FPGA board.\n";
std::cerr << "Run sys_check in the oneAPI root directory to verify.\n";
std::cerr << "If you are targeting the FPGA emulator, compile with "
"-DFPGA_EMULATOR.\n";
}
std::terminate();
}
return passed ? EXIT_SUCCESS : EXIT_FAILURE;
}