Kernels intended for the PL must use stream inputs and outputs rather than windows using the API defined in Programmable Logic Stream Operations. To use these data types, the standard header adf.h must be included before the function declaration. PL kernels modeled in C/C++ can be simulated using the AI Engine simulator. You cannot take this design to hardware because the C/C++ code is only a simulation construct.
Prepare the Kernels describes the header file for the AI Engine; this is the same concept for PL kernels with C++. The following code shows an example kernel.
#ifndef MAGNITUDE_MODULE_H
#define MAGNITUDE_MODULE_H
#include <adf.h>
void pladd(input_stream_int32 * in1, input_stream_int32 *in2, output_stream_int32 * out);
#endif
In this example, the module has two stream inputs carrying 32-bit integers, and produces a single stream output carrying 32-bit integers. The body of the kernel is specified in a separate file and must include adf.h. The following is an example kernel body.
#include <adf.h>
void pladd(input_stream_int32 * in1, input_stream_int32 *in2, output_stream_int32 * out) {
std::cerr << "Waiting for a value" << "\n";
int value1 = readincr(in1);
int value2 = readincr(in2);
int newvalue = value1 + value2;
std::cerr << "add " << value1 << "and " << value2 << " sent " << newvalue << "\n";
writeincr(out, newvalue);
};
There is no restriction on the C++ code used. For debugging, in addition to performing actual computation, the outputs can be printed to the console. To read from a stream, use readincr accessor functions for reading various data types (Reading and Advancing an Input Stream). To write to a stream, use writeincr accessor functions for writing various data types (Writing and Advancing an Output Stream). Both of these are blocking calls where the API function stalls when there is insufficient buffering between the source and destination.
You can specify that a kernel is intended to run in the programmable logic by attaching an attribute to the kernel instance.
fabric<pl>(adder);
The source used to implement the functionality of the kernel must also be specified in exactly the same way the kernels for the AI Engine are specified.
source(adder) = "module/add.cpp";
The connections from a PL kernel to an AI Engine kernel are similar to the connections from AI Engine to AI Engine kernels. The difference is the connection now specifies the stream and the window, rather than just a window. For example, the following connections are used to connect the adder.
connect<window<32>, stream>(prod.out[0], adder.in[0]);
connect<window<32>, stream>(prod.out[1], adder.in[1]);
connect<stream, window<32> >(adder.out[0], cons.in[0]);
All sources and destinations are supported as connections (AI Engine to PL, PL to AI Engine, and PL to PL). Run-time parameters (see Run-Time Graph Control API) can be specified for kernels mapped to the programmable logic. The input specification for the parameters is exactly the same as that for the kernels mapped to the AI Engine array. Parameters in the PL are synthesized to be registers or memories accessible via the memory-mapped AXI4 bus. Only scalar input parameters in the PL are supported and are modeled through a separate System C control thread.