Intel® High Level Synthesis Compiler Pro Edition: Reference Manual

ID 683349
Date 3/28/2022
Public

A newer version of this document is available. Customers should click here to go to the newest version.

Document Table of Contents

4.3.1. The pipe Class and Its Use

The pipe class exposes static methods for writing a data word to a pipe and reading a data word from a pipe. The reads and writes can be blocking or nonblocking, with the form chosen based on the overload resolution.

The pipe API is equivalent to the following class declaration:
template <class name,
          class dataT,
          size_t min_capacity = 0>
class pipe {
public:
  // Blocking
  static dataT read();
  static void write(dataT data);
  // Non-blocking
  static dataT read(bool &success);
  static void write(dataT data, bool &success);
}
Where the template parameters are defined as follows:
Table 12.   pipe API Template Parameters
Parameter Description
name The type that is used to create a unique identifier for the pipe.

It is typically a user-defined class, in a user namespace. Forward declaration of the type is enough, and the type need not be defined.

dataT The data type of the packet contained within a pipe.

This is the data type that is read during a successful pipe read() operation, or written during a successful pipe write() operation.

The type must have a standard layout and be trivially copyable.

min_capacity The minimum number of words (in units of dataT) that the pipe must be able to store without any being read out.

The compiler might create a pipe with a larger capacity due to performance considerations.

Example Using Pipes

The following code example shows a header file that you can use in a source code library or use in component or task functions.
#include "HLS/hls.h"

template<unsigned ID, class T, unsigned pipe_capacity> class TaskSystem {
private:
  template<unsigned SystemID> class InputPipeID {};
  template<unsigned SystemID> class TaskPipeID {};
  template<unsigned SystemID> class OutputPipeID {};

public:
  using input_pipe  = ihc::pipe<class InputPipeID<ID>, T, pipe_capacity>;
  using output_pipe = ihc::pipe<class OutputPipeID<ID>, T, pipe_capacity>;
  using task_pipe   = ihc::pipe<class TaskPipeID<ID>, T, pipe_capacity>;

  static void first_task(unsigned N) {
    T data;
    for(unsigned i=0; i<N; ++i) {
      data = input_pipe::read();
      task_pipe::write(data);
    }
  }

  static void second_task(unsigned N) {
    T data;
    for(unsigned i=0; i<N; ++i) {
      data = task_pipe::read();
      output_pipe::write(data);
    }
  }
};

With this header file, first_task and second_task can be called from separate task functions to achieve concurrency.

Assuming the header file in a file called task_system.h, you can create a system of tasks component like the following example:
#include "HLS/hls.h"
#include <iostream>

#include “task_system.h”

unsigned constexpr ID = 42; // can be any unique value
unsigned constexpr CAPACITY = 100;
using MySystem = TaskSystem<ID, int, CAPACITY>;

int main() {
  ihc::launch<MySystem::first_task>(CAPACITY);
  ihc::launch<MySystem::second_task>(CAPACITY);

  for(int i = 0; i < CAPACITY; ++i) {
    std::cout << "input: " << i << "\n";
    MySystem::input_pipe::write(i);
  }

  for(int i = 0; i < CAPACITY; ++i) {
    int data = MySystem::output_pipe::read();
    std::cout << "output: " << data << "\n";
  }

  return 0;
}

Example of an Array of Pipes

The following code example implements an array of pipes using templates, and includes functions to write to such an array.

#include "HLS/hls.h"

// PipeArray

template <class ArrayID, typename T, unsigned pipeCapacity, unsigned arraySize>
class PipeArray {
private:
  template <unsigned idx> struct StructIndex;
  template <unsigned idx> struct VerifyIndex {
    static_assert(idx < arraySize, "Index out of bounds");
    using VerifiedPipe = ihc::pipe<StructIndex<idx>, T, pipeCapacity>;
  };

public:
  template <unsigned idx>
  using pipe_at = typename VerifyIndex<idx>::VerifiedPipe;
  static void write_to_pipes(T *values);
};

// Write Unroller

template <class ArrayID, typename T, unsigned pipeCapacity, unsigned arraySize,
          unsigned idx>
struct WriteUnroller {
  using my_array = PipeArray<ArrayID, T, pipeCapacity, arraySize>;
  static void write_to_pipes_impl(T *values) {
    my_array::template pipe_at<idx>::write(values[idx]);
    WriteUnroller<ArrayID, T, pipeCapacity, arraySize,
                  idx + 1>::write_to_pipes_impl(values);
  }
};

template <class ArrayID, typename T, unsigned pipeCapacity, unsigned arraySize>
struct WriteUnroller<ArrayID, T, pipeCapacity, arraySize, arraySize> {
  static void write_to_pipes_impl(T *values) {}
};

// Write function

template <class ArrayID, typename T, unsigned pipeCapacity, unsigned arraySize>
void PipeArray<ArrayID, T, pipeCapacity, arraySize>::write_to_pipes(T *values) {
  WriteUnroller<ArrayID, T, pipeCapacity, arraySize, 0>::write_to_pipes_impl(
      values);
}

The function PipeArray::write_to_pipes takes an array of values to be written, and calls WriteUnroller::write_to_pipes_impl, which uses recursive templating to write to each pipe in the array. Reading from the array of pipes would have a similar implementation.