Intel® High Level Synthesis Compiler Pro Edition: Reference Manual

ID 683349
Date 6/02/2023

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

Document Table of Contents

4.3. Pipes

Using external memory to move data in and out of static-object libraries or task functions can constrain the performance of your design. Pipes provide a mechanism for passing data with high efficiency and low latency by using on-device FIFO buffers to communicate.

Pipes are similar to streams, but are simpler. You can use pipes in a component or to allow tasks to communicate with each other, but pipes support only ready/valid and data signals, while streams support additional sideband signals such as startofpacket and endofpacket.

Unlike streams, you can create an array of pipes using templates and you can iterate over the array to write or read many pipes in a design.

Like streams, data written to a pipe remains in the pipe until it is read or until the component is reset.

The memory model of pipes allows you to use them to send and receive data from task functions in running functions in a static-object library or a running task function.

Pipe Properties

Pipes have the following key properties:
FIFO ordering
Data is accessible (readable) only in FIFO order. There is no concept of a memory address or pointer in a pipe, which means random data access is not possible with pipes.
Pipes have a capacity. That is, a fixed amount of data can be written to an initially empty pipe before needing to read anything from it to make room for more data.

A full pipe applies back pressure to the write site.

Pipe Accessors

Data is written to a pipe through an API that commits a single word of the pipe data type, and that word is later returned by an API that reads data from the pipe.

The API accessing the pipe can be a blocking or a nonblocking type:
Blocking API calls
Blocking write API calls wait until the pipe has enough capacity to commit data.

Blocking read API calls wait until the pipe contains data to be read.

Nonblocking API calls
Nonblocking API calls execute immediately and return a status that indicates whether the operation was successful.

You can mix blocking and nonblocking accesses to the same pipe. For example you can write data to a pipe with a blocking pipe write() call and read it from the other end using a non-blocking read() call, and vice versa.

Data Persistence in Pipes

Data written to a pipe by a component remains in the pipe until it is read or until the component is reset.

Data written to a pipe by a task remains in the pipe until another task reads from the pipe or the component containing the tasks is reset.

The sequence of data in a pipe always follows FIFO ordering.

Data in pipes does not persist across FPGA device resets or reprogramming.

Restrictions on using Pipes

Using pipes comes with the following restrictions:
Multiple pipe call sites
A component or task function can read from the same pipe multiple times, but multiple component or task functions cannot read from the same pipe. Similarly, a component or task function can write to the same pipe multiple times, but multiple component or task functions cannot write to the same pipe.
Feedback and feed-forward pipes
A component or task function should use separate pipes for pipe reads and pipe writes. Writing to and reading from the same pipe within the same function can lead to poor performance.
Pipe accesses in loops
Do not use nonblocking pipes if you use a loop structure that waits for data from the pipe. That is, avoid the following code pattern for nonblocking pipe accesses:
bool success = false;
while (!success) {
  my_pipe::write(rd_src_buf[i], success); 
  // can also be a nonblocking read
Use a blocking access with this code pattern instead because a blocking access in this code pattern is more efficient in hardware than a nonblocking access in this code pattern.