Using FFT Function with Streaming Interface - 2023.2 English

Vitis High-Level Synthesis User Guide (UG1399)

Document ID
UG1399
Release Date
2023-12-18
Version
2023.2 English

The FFT function with streaming interfaces is defined in the HLS namespace similarly to this:

  template <typename PARAM_T>
  void fft(hls::stream<complex<float or ap_fixed>> &xn_s,
           hls::stream<complex<float or ap_fixed >> &xk_s,
           hls::stream<ip_fft::status_t<CONFIG_T>> &status_s,
           hls::stream<ip_fft::config_t<CONFIG_T>> &config_s);

and can be called as follows:

hls::fft<STATIC_PARAM> (
INPUT_DATA_STREAM,
OUTPUT_DATA_STREAM,
OUTPUT_STATUS_STREAM,
INPUT_RUN_TIME_CONFIGURATION_STREAM);

The STATIC_PARAM is the static parameterization struct that defines the static parameters for the FFT.

All input and outputs are supplied to the function as a hls::stream<>. In the final implementation, the ports on the FFT RTL block will be implemented as AXI4-Stream ports.

Important: You are required to use the streaming FFT function in a dataflow region using set_directive_dataflow or #pragma HLS dataflow. The FFT cannot be used in a region which is pipelined. If high-performance operation is required, in particular by avoiding clock cycles in which the FFT is stalled without processing data, then:
  • The entire design hierarchy including and above the FFT must be either dataflow regions or functions containing only dataflow-in-loop.
  • The depth of all FIFOs must be enough to avoid blocking in cosimulation, as described in Cosim Deadlock Detection)
  • All dataflow processes throughout the hierarchy must contain only:
    • Pipelines with the rewind option
    • Or pipelined loops with the rewind option

The data types for input data and output data streams can be float or ap_fixed.

void fft_top(
    bool direction,
    complex<data_in_t> in[FFT_LENGTH],
    complex<data_out_t> out[FFT_LENGTH],
    bool &ovflo)
{
    #pragma HLS dataflow
    hls::stream<complex<data_in_t>> in_fft; 
    hls::stream<complex<data_out_t>> out_fft; 
    hls::stream<config_t> fft_config;
    hls::stream<status_t> fft_status;

    // convert inputs to hls::stream<> and generates fft_config stream based on input arguments   
    proc_fe(direction, fft_config, in, in_fft);
    // FFT IP
    hls::fft<config1>(in_fft, out_fft, fft_status, fft_config);
    // convert fft result to outputs data type and sets output ovflo according to fft_status stream
    proc_be(fft_status, ovflo, out_fft, out);
}

To use fixed-point data types, the Vitis HLS arbitrary precision type ap_fixed should be used.

#include "ap_fixed.h"
typedef ap_fixed<FFT_INPUT_WIDTH,1> data_in_t;
typedef ap_fixed<FFT_OUTPUT_WIDTH,FFT_OUTPUT_WIDTH-FFT_INPUT_WIDTH+1>
data_out_t;
#include <complex>
typedef complex<data_in_t> cmpxData;
typedef complex<data_out_t> cmpxDataOut;

In both cases, the FFT should be parameterized with the same correct data sizes. In the case of floating point data, the data widths will always be 32-bit and any other specified size will be considered invalid.

Comparing Stream and Array

The following table shows the array example and stream example usage of the FFT IP. In both cases the dataflow hierarchy extends all the way to the top, without any sequential code (code that is not a dataflow region or a dataflow-in-loop function) and that copy-in and copy-out loops are pipelined with rewind.

Tip: The top functions in each example are using array interfaces for consistency, but could be changed to use hls::stream<> or use the STREAM pragma or directive to the same effect.
Table 1. FFT Arguments
FFT with Array Arguments FFT with hls::stream<> Arguments
#include "fft_top.h"
 
void proc_fe(
 bool direction,
 config_t* config, 
 cmpxDataIn in[FFT_LENGTH], 
 cmpxDataIn out[FFT_LENGTH])
{
 int i; 
 config->setDir(direction);
 config->setSch(0x2AB);
 for (i=0; i< FFT_LENGTH; i++)
 #pragma HLS pipeline II=1 rewind
 out[i] = in[i];
}
 
void proc_be(
 status_t* status_in, 
 bool* ovflo,
 cmpxDataOut in[FFT_LENGTH], 
 cmpxDataOut out[FFT_LENGTH])
{
 int i; 
 for (i=0; i< FFT_LENGTH; i++)
 #pragma HLS pipeline II=1 rewind
 out[i] = in[i];
 *ovflo = status_in->getOvflo() & 0x1;
}
 
// TOP function
void fft_top(
 bool direction,
 complex<data_in_t> in[FFT_LENGTH],
 complex<data_out_t> out[FFT_LENGTH],
 bool* ovflo)
{
#pragma HLS interface ap_hs port=direction
#pragma HLS interface ap_fifo depth=1 port=ovflo
#pragma HLS interface ap_fifo depth=FFT_LENGTH port=in,out
#pragma HLS data_pack variable=in
#pragma HLS data_pack variable=out
#pragma HLS dataflow
 complex<data_in_t> xn[FFT_LENGTH];
 complex<data_out_t> xk[FFT_LENGTH];
 config_t fft_config;
 status_t fft_status;
 
 // convert inputs to arrays and generates fft_config 
 // based on input arguments 
 proc_fe(direction, &fft_config, in, xn);
 // FFT IP
 hls::fft<config1>(xn, xk, &fft_status, &fft_config);
 // convert fft results to outputs data type and 
 // sets output ovflo according to fft_status
 proc_be(&fft_status, ovflo, xk, out);
}
#include "fft_top.h"
 
void proc_fe(
 bool direction,
 hls::stream<config_t> &config_s, 
 cmpxDataIn in[FFT_LENGTH],
 hls::stream<cmpxDataIn> &out_s)
{
 int i; 
 config_t config;
 config.setDir(direction);
 config.setSch(0x2AB);
 config_s.write(config);
 for (i=0; i< FFT_LENGTH; i++) {
 #pragma HLS pipeline II=1 rewind
 out_s.write(in[i]);
 }
}
 
void proc_be(
 hls::stream<status_t> &status_in_s, 
 bool &ovflo,
 hls::stream<cmpxDataOut> &in_s,
 cmpxDataOut out[FFT_LENGTH])
{
 int i; 
 for (i=0; i< FFT_LENGTH; i++) {
 #pragma HLS pipeline II=1 rewind
 out[i] = in_s.read();
 }
 status_t status_in = status_in_s.read();
 ovflo = status_in.getOvflo() & 0x1;
}
 
// TOP function
void fft_top(
 bool direction,
 complex<data_in_t> in[FFT_LENGTH],
 complex<data_out_t> out[FFT_LENGTH],
 bool &ovflo)
{
#pragma HLS dataflow
 hls::stream<complex<data_in_t>> xn;
 hls::stream<complex<data_out_t>> xk;
 hls::stream<config_t> fft_config;
 hls::stream<status_t> fft_status;
 // convert inputs to hls::stream<> 
 // and generates fft_config stream based on input arguments 
 proc_fe(direction, fft_config, in, xn);
 // FFT IP
 hls::fft<config1>(xn, xk, fft_status, fft_config);
 // convert fft result to outputs data type 
 // and sets output ovflo according to fft_status stream
 proc_be(fft_status, ovflo, xk, out);
}