Section 4.7. Understanding Streams

4.7. Understanding Streams

Streams are unidirectional communication channels that connect multiple processes, whether hardware or software. The co_stream_create function creates a stream, defines its data width and its buffer size, and makes the stream available for use in subsequent co_process_create calls. The following is an example of a stream being created with co_stream_create:

     strm_image_value = co_stream_create("IMG_VAL", INT_TYPE(16),BUFSIZE); 

There are three arguments to the co_stream_create function. The first argument is an optional name that may be assigned to the stream for debugging, external monitoring, and post-processing purposes. This name has no semantic meaning in the application but may be useful for certain downstream synthesis or simulation tools.

If application monitoring will be used (as indicated by any use of cosim_ monitoring functions, which are described in a later chapter), stream names are required, and the chosen stream names must be unique across the entire application.

How are streams implemented in hardware?

The details of streams (their control signals and external protocols) are the subject of a later chapter, but for now it's useful to know that streams are implemented as first- in-first-out (FIFO) buffers. You define the characteristics of a given stream when you create the stream using the co_stream_create function. For example, the following stream definition:

 co_stream pixelvalues; pixelvalues = co_stream_create("pixval", UINT_TYPE(24), 2); 

results in a stream of name pixval being generated in hardware that will carry 24-bit-wide data values on a buffer with a depth of two, for a total of 48 bits of data memory plus control signals. Note that the actual depth of the generated hardware FIFO must be a power of two, so the compiler rounds up the specified buffer size to the closest power of two.

Buffer widths (specified in the second argument to the co_stream_create function) reflect the nature of the data being transferred (whether a single character or a 32-bit word). The depth of a stream and its corresponding FIFO (specified as the third argument to co_stream_create) can be a more complex decision, however. Although there is no performance penalty for using deep FIFOs (no extra stream delay is introduced into the system), using large buffers can have a significant impact on the size of the generated hardware. Using too small a buffer, on the other hand, can result in deadlock conditions for processes that must read multiple packets of data prior to performing some computation, or for connected processes that operate at different rates (such as communicating hardware and software processes). We will cover the subject of stream optimization in later chapters.

The second argument specifies the type and size of the stream's data element. Macros are provided for defining specific stream types, including INT_TYPE, UINT_TYPE, and CHAR_TYPE.

The third and final argument to co_stream_create is the buffer size. This buffer size directly relates to the size of the FIFO buffer that will be created between two processes that are connected with a stream. A buffer size of 1 indicates that the stream is essentially unbuffered; the receiving process will block until the sending process has completed and moved data onto the stream. In contrast, a larger buffer size will result in additional hardware resources (registers and corresponding interconnect resources) being generated, but may result in more efficient process synchronization. As an application designer, you will choose buffer sizes that best meet the requirements of your particular application.

When choosing buffer sizes, keep in mind that the width and depth of a stream (as specified in the call to co_stream_create) will have a significant impact on the amount of hardware required to implement the process. You should therefore choose a stream buffer size that is as small as practical for the given process. In the following example a buffer size of four has been selected for all three streams, as indicated in the definition of BUFSIZE:

 #define BUFSIZE 4 void my_config_function(void *arg) {   co_stream host2controller,controller2pe,pe2host;   co_process host1, host2;   co_process controller;   co_process pe;   host2cntrlr=co_stream_create("host2cntrlr", INT_TYPE(32), BUFSIZE);   cntrlr2pe=co_stream_create("cntrlr2pe", INT_TYPE(32), BUFSIZE);   pe2host=co_stream_create("pe2host", INT_TYPE(32),BUFSIZE);   host2=co_process_create("host2", (co_function)host2_run, 1, pe2host);   pe=co_process_create("pe", (co_function)pe1_proc_run, 2, cntrlr2pe, pe2host);   cntrlr=co_process_create("cntrlr", (co_function) cntrlr_run, 2, host2cntrlr, cntrlr2pe);   host1=co_process_create("host1",(co_function)host1_run, 2, host2cntrlr, iterations);   co_process_config(cntrlr, co_loc, "PE0");   co_process_config(pe, co_loc, "PE0"); } 

Stream I/O

Reading and writing of processes is performed within the process run functions that form an application. Each process run function in a dataflow- oriented Impulse C application iteratively reads one or more input streams and performs the necessary processing when data is available. If no data is available on the stream being read, the process blocks until such time as data is made available by the upstream process.

Other, non-dataflow processing models are supported, including message-based process synchronization. These alternate models are described in subsequent sections.

At the start of a process run function, the co_stream_open function resets a stream's internal state, making it available for either reading or writing. The co_stream_open function must be used to open all streams (input and output) prior to their being read from or written to. An example of a stream being opened is as follows:

     err = co_stream_open(input_stream, O_RDONLY, INT_TYPE(32)); 

The co_stream_open function accepts three arguments: the stream (which has previously been declared as a process argument of type co_stream), the type of stream (which may be O_RDONLY or O_WRONLY), and the data type and size, as expressed using either INT_TYPE, UINT_TYPE, or CHAR_TYPE.

Streams are point-to-point and unidirectional, so each stream should be opened by exactly two processes, one for reading and the other for writing. If a stream being opened with co_stream_open has already been opened, the co_stream_open function returns the error code co_err_already_open.

When the stream is no longer needed, it may be closed using the co_stream_close function:

 err = co_stream_close(input_stream); 

The co_stream_close function writes an end-of-stream (EOS) token to the output stream, which can then be detected by the downstream process when the stream is read using co_stream_read. The co_stream_read function returns an error when the EOS token is received, indicating that the stream is closed. If a stream being closed is not open (or has already been closed), the co_stream_close function returns the error code co_err_not_open.

Keep in mind that all streams must be properly closed by the reading and writing process. In particular, note that the reading process blocks when it calls co_stream_close until the EOS has been received, indicating that the upstream proess has also closed the stream. Once closed, a process can open a stream again for multiple sessions.


Stream connections between processes must be one-to-one; broadcast patterns are not supported, and many-to-one connections are not supported. It is possible to create such data distribution patterns in an application, however, by creating intermediate stream collector and distributor processes.

    Practical FPGA Programming in C
    Practical FPGA Programming in C
    ISBN: 0131543180
    EAN: 2147483647
    Year: 2005
    Pages: 208 © 2008-2017.
    If you may any questions please contact us: