HLS Stream Library

Streaming data is a type of data transfer in which data samples are sent in sequential order starting from the first sample. Streaming requires no address management.

Modeling designs that use streaming data can be difficult in C. The approach of using pointers to perform multiple read and/or write accesses can introduce issues, because there are implications for the type qualifier and how the test bench is constructed.

Vitis HLS provides a C++ template class hls::stream<> for modeling streaming data structures. The streams implemented with the hls::stream<> class have the following attributes.

  • In the C code, an hls::stream<> behaves like a FIFO of infinite depth. There is no requirement to define the size of an hls::stream<>.
  • They are read from and written to sequentially. That is, after data is read from an hls::stream<>, it cannot be read again.
  • An hls::stream<> on the top-level interface is by default implemented with an ap_fifo interface.
  • There are two possible stream declarations:
    • hls::stream<Type>: specify the data type for the stream.

      An hls::stream<> internal to the design is implemented as a FIFO with a default depth of 2. The STREAM pragma or directive can be used to change the depth.

    • hls::stream<Type, Depth> : specify the data type for the stream, and the FIFO depth.

      Set the depth to prevent stalls. If any task in the design can produce or consume samples at a greater rate than the specified depth, the FIFOs might become empty (or full) resulting in stalls, because it is unable to read (or write).

This section shows how the hls::stream<> class can more easily model designs with streaming data. The topics in this section provide:

  • An overview of modeling with streams and the RTL implementation of streams.
  • Rules for global stream variables.
  • How to use streams.
  • Blocking reads and writes.
  • Non-Blocking Reads and writes.
  • Controlling the FIFO depth.
Note: The hls::stream class should always be passed between functions as a C++ reference argument. For example, &my_stream.
IMPORTANT: The hls::stream class is only used in C++ designs. Array of streams is not supported.

C Modeling and RTL Implementation

Streams are modeled as an infinite queue in software (and in the test bench during RTL co-simulation). There is no need to specify any depth to simulate streams in C++. Streams can be used inside functions and on the interface to functions. Internal streams may be passed as function parameters.

Streams can be used only in C++ based designs. Each hls::stream<> object must be written by a single process and read by a single process.

If an hls::stream is used on the top-level interface, it is by default implemented in the RTL as a FIFO interface (ap_fifo) but may be optionally implemented as a handshake interface (ap_hs) or an AXI4-Stream interface (axis).

If an hls::stream is used inside the design function and synthesized into hardware, it is implemented as a FIFO with a default depth of 2. In some cases, such as when interpolation is used, the depth of the FIFO might have to be increased to ensure the FIFO can hold all the elements produced by the hardware. Failure to ensure the FIFO is large enough to hold all the data samples generated by the hardware can result in a stall in the design (seen in C/RTL co-simulation and in the hardware implementation). The depth of the FIFO can be adjusted using the STREAM directive with the depth option. An example of this is provided in the example design hls_stream.

IMPORTANT: Ensure hls::stream variables are correctly sized when used in the default non-DATAFLOW regions.

If an hls::stream is used to transfer data between tasks (sub-functions or loops), you should immediately consider implementing the tasks in a DATAFLOW region where data streams from one task to the next. The default (non-DATAFLOW) behavior is to complete each task before starting the next task, in which case the FIFOs used to implement the hls::stream variables must be sized to ensure they are large enough to hold all the data samples generated by the producer task. Failure to increase the size of the hls::stream variables results in the error below:

ERROR: [XFORM 203-733] An internal stream xxxx.xxxx.V.user.V' with default size is 
used in a non-dataflow region, which may result in deadlock. Please consider to 
resize the stream using the directive 'set_directive_stream' or the 'HLS stream' 

This error informs you that in a non-DATAFLOW region (the default FIFOs depth is 2) may not be large enough to hold all the data samples written to the FIFO by the producer task.

Global and Local Streams

Streams may be defined either locally or globally. Local streams are always implemented as internal FIFOs. Global streams can be implemented as internal FIFOs or ports:

  • Globally-defined streams that are only read from, or only written to, are inferred as external ports of the top-level RTL block.
  • Globally-defined streams that are both read from and written to (in the hierarchy below the top-level function) are implemented as internal FIFOs.

Streams defined in the global scope follow the same rules as any other global variables.

Using HLS Streams

To use hls::stream<> objects, include the header file hls_stream.h. Streaming data objects are defined by specifying the type and variable name. In this example, a 128-bit unsigned integer type is defined and used to create a stream variable called my_wide_stream.

#include "ap_int.h"
#include "hls_stream.h"

typedef ap_uint<128> uint128_t;  // 128-bit user defined type
hls::stream<uint128_t> my_wide_stream;  // A stream declaration

Streams must use scoped naming. Xilinx recommends using the scoped hls:: naming shown in the example above. However, if you want to use the hls namespace, you can rewrite the preceding example as:

#include <ap_int.h>
#include <hls_stream.h>
using namespace hls;

typedef ap_uint<128> uint128_t;  // 128-bit user defined type
stream<uint128_t> my_wide_stream;  // hls:: no longer required

Given a stream specified as hls::stream<T>, the type T can be:

  • Any C++ native data type
  • A Vitis HLS arbitrary precision type (for example, ap_int<>, ap_ufixed<>)
  • A user-defined struct containing either of the above types
Note: General user-defined classes (or structures) that contain methods (member functions) should not be used as the type (T) for a stream variable.

A stream can also be specified as hls::stream<Type, Depth>, where Depth indicates the depth of the FIFO needed in the verification adapter that the HLS tool creates for RTL co-simulation.

Streams can be optionally named. Providing a name for the stream allows the name to be used in reporting. For example, Vitis HLS automatically checks to ensure all elements from an input stream are read during simulation. Given the following two streams:

stream<uint8_t> bytestr_in1;
stream<uint8_t> bytestr_in2("input_stream2");
WARNING: Hls::stream 'hls::stream<unsigned char>.1' contains leftover data, which 
may result in RTL simulation hanging.
WARNING: Hls::stream 'input_stream2' contains leftover data, which may result in RTL 
simulation hanging.
Any warning on elements left in the streams are reported as follows, where
         it is clear that the bytetr_in2 must be bytestr_in2:

When streams are passed into and out of functions, they must be passed-by-reference as in the following example:

void stream_function (
      hls::stream<uint8_t> &strm_out,
      hls::stream<uint8_t> &strm_in,
     uint16_t strm_len

Vitis HLS supports both blocking and non-blocking access methods.

A complete design example using streams is provided in the Vitis HLS examples. Refer to the hls_stream example in the design examples available from the Vitis IDE welcome screen.

Blocking Reads and Writes

The basic accesses to an hls::stream<> object are blocking reads and writes. These are accomplished using class methods. These methods stall (block) execution if a read is attempted on an empty stream FIFO, a write is attempted to a full stream FIFO, or until a full handshake is accomplished for a stream mapped to an ap_hs interface protocol.

A stall can be observed in C/RTL co-simulation as the continued execution of the simulator without any progress in the transactions. The following shows a classic example of a stall situation, where the RTL simulation time keeps increasing, but there is no progress in the inter or intra transactions:

// RTL Simulation : "Inter-Transaction Progress" ["Intra-Transaction Progress"] @ 
"Simulation Time"
// RTL Simulation : 0 / 1 [0.00%] @ "110000"
// RTL Simulation : 0 / 1 [0.00%] @ "202000"
// RTL Simulation : 0 / 1 [0.00%] @ "404000"

Blocking Write Methods

In this example, the value of variable src_var is pushed into the stream.

// Usage of void write(const T & wdata)

hls::stream<int> my_stream;
int src_var = 42;


The << operator is overloaded such that it may be used in a similar fashion to the stream insertion operators for C++ stream (for example, iostreams and filestreams). The hls::stream<> object to be written to is supplied as the left-hand side argument and the value to be written as the right-hand side.

// Usage of void operator << (T & wdata)

hls::stream<int> my_stream;
int src_var = 42;

my_stream << src_var;

Blocking Read Methods

This method reads from the head of the stream and assigns the values to the variable dst_var.

// Usage of void read(T &rdata)

hls::stream<int> my_stream;
int dst_var;


Alternatively, the next object in the stream can be read by assigning (using for example =, +=) the stream to an object on the left-hand side:

// Usage of T read(void)

hls::stream<int> my_stream;

int dst_var = my_stream.read();

The >> operator is overloaded to allow use similar to the stream extraction operator for C++ stream (for example, iostreams and filestreams). The hls::stream is supplied as the LHS argument and the destination variable the RHS.

// Usage of void operator >> (T & rdata)

hls::stream<int> my_stream;
int dst_var;

my_stream >> dst_var;

Non-Blocking Reads and Writes

Non-blocking write and read methods are also provided. These allow execution to continue even when a read is attempted on an empty stream or a write to a full stream.

These methods return a Boolean value indicating the status of the access (true if successful, false otherwise). Additional methods are included for testing the status of an hls::stream<> stream.

IMPORTANT: Non-blocking behavior is only supported on interfaces using the ap_fifo protocol. More specifically, the AXI-Stream standard and the Xilinx ap_hs IO protocol do not support non-blocking accesses.

During C simulation, streams have an infinite size. It is therefore not possible to validate with C simulation if the stream is full. These methods can be verified only during RTL simulation when the FIFO sizes are defined (either the default size of 1, or an arbitrary size defined with the STREAM directive).

IMPORTANT: If the design is specified to use the block-level I/O protocol ap_ctrl_none and the design contains any hls::stream variables that employ non-blocking behavior, C/RTL co-simulation is not guaranteed to complete.

Non-Blocking Writes

This method attempts to push variable src_var into the stream my_stream, returning a boolean true if successful. Otherwise, false is returned and the queue is unaffected.

// Usage of void write_nb(const T & wdata)

hls::stream<int> my_stream;
int src_var = 42;

if (my_stream.write_nb(src_var)) {
 // Perform standard operations
} else {
 // Write did not occur

Fullness Test

bool full(void)

Returns true, if and only if the hls::stream<> object is full.

// Usage of bool full(void)

hls::stream<int> my_stream;
int src_var = 42;
bool stream_full;

stream_full = my_stream.full();

Non-Blocking Read

bool read_nb(T & rdata)

This method attempts to read a value from the stream, returning true if successful. Otherwise, false is returned and the queue is unaffected.

// Usage of void read_nb(const T & wdata)

hls::stream<int> my_stream;
int dst_var;

if (my_stream.read_nb(dst_var)) {
 // Perform standard operations
} else {
 // Read did not occur

Emptiness Test

bool empty(void)

Returns true if the hls::stream<> is empty.

// Usage of bool empty(void)

hls::stream<int> my_stream;
int dst_var;
bool stream_empty;

stream_empty = my_stream.empty();

The following example shows how a combination of non-blocking accesses and full/empty tests can provide error handling functionality when the RTL FIFOs are full or empty:

#include "hls_stream.h"
using namespace hls;

typedef struct {
   short    data;
   bool     valid;
   bool     invert;
} input_interface;

bool invert(stream<input_interface>& in_data_1,
            stream<input_interface>& in_data_2,
            stream<short>& output
  ) {
  input_interface in;
  bool full_n;

// Read an input value or return
  if (!in_data_1.read_nb(in))
      if (!in_data_2.read_nb(in))
          return false;

// If the valid data is written, return not-full (full_n) as true
  if (in.valid) {
    if (in.invert)
      full_n = output.write_nb(~in.data);
      full_n = output.write_nb(in.data);
  return full_n;

Controlling the RTL FIFO Depth

For most designs using streaming data, the default RTL FIFO depth of 2 is sufficient. Streaming data is generally processed one sample at a time.

For multirate designs in which the implementation requires a FIFO with a depth greater than 2, you must determine (and set using the STREAM directive) the depth necessary for the RTL simulation to complete. If the FIFO depth is insufficient, RTL co-simulation stalls.

Because stream objects cannot be viewed in the GUI directives pane, the STREAM directive cannot be applied directly in that pane.

Right-click the function in which an hls::stream<> object is declared (or is used, or exists in the argument list) to:

  • Select the STREAM directive.
  • Populate the variable field manually with name of the stream variable.

Alternatively, you can:

  • Specify the STREAM directive manually in the directives.tcl file, or
  • Add it as a pragma in source.

C/RTL Co-Simulation Support

The Vitis HLS C/RTL co-simulation feature does not support structures or classes containing hls::stream<> members in the top-level interface. Vitis HLS supports these structures or classes for synthesis.

typedef struct {
   hls::stream<uint8_t> a;
   hls::stream<uint16_t> b;
} strm_strct_t;

void dut_top(strm_strct_t indata, strm_strct_t outdata) { … }

These restrictions apply to both top-level function arguments and globally declared objects. If structs of streams are used for synthesis, the design must be verified using an external RTL simulator and user-created HDL test bench. There are no such restrictions on hls::stream<> objects with strictly internal linkage.