Auto-Pipe Software Block Interface v2 From Auto-Pipe Wiki Contents 1 Interface 1.1 Auto-Pipe Types 1.2 AutoPipe Functions 1.3 Block Structure 1.4 Block Functions 2 Signaling 2.1 Stop Signal (type 0) 2.2 Credit Signal (type 1) 3 Examples 3.1 Simple Example 3.2 More Complex Example 4 Changes Since Version 1 Interface Each block exports certain functions to the Auto-Pipe runtime system. These functions are declared in a header file with the name "blockname.h" where blockname is the name of the block. To access the Auto-Pipe types and related functions and macros, the block header file should include "X.h". The functions declared in the block header file are defined in a file with the name "blockname.c". Auto-Pipe Types The following types are available: UNSIGNED8 SIGNED8 UNSIGNED16 SIGNED16 UNSIGNED32 SIGNED32 UNSIGNED64 SIGNED64 FLOAT32 FLOAT96 STRING - NULL-terminated character array. 1/7
AutoPipe Functions The following functions are exposed to the Auto-Pipe block. The first argument to each of these is a pointer to instance-specific data for the block. int ap_get_max_send(struct ap_blockname_data *block, int out_port); int ap_get_free(struct ap_blockname_data *block, int out_port); void *ap_allocate(struct ap_blockname_data *block, int out_port, int count); void ap_send(struct ap_blockname_data *block, int out_port, int count); void ap_release(struct ap_blockname_data *block, int in_port, int count); void ap_send_signal(struct ap_block block, int out_port, int type, int value); int ap_get_input_count(struct ap_blockname_data *block, int in_port); void *ap_get_input_data(struct ap_blockname_data *block, int in_port); int ap_check_inputs_upto(struct ap_blockname_data *block, int in_port); ap_get_max_send - Determine the maximum "safe" send count for out_port. Note that the value returned will not change from call to call. For SHM and intra-process edges, this will be the half of the queue size between two blocks. This is because the queues are implemented using a ring buffer. Since the "read" pointer is controlled by the consumer, even if the buffer is empty, a write can't cross the read pointer. Thus, if the read pointer is in the middle of the buffer, we can write to the first half or the last half, but not the whole buffer. ap_get_free - Determine how many items can be allocated on out_port without blocking. Note that it is possible for this value to be larger than the value returned from "ap_get_max_send" since this value indicates how much can be allocated now whereas "ap_get_max_send" returns a lower bound on how much can be allocated at a time. The value returned from "ap_get_free" can be zero. "ap_get_max_send" is faster than "ap_get_free", but "ap_get_free" allows one to avoid blocking, which can be useful for a block that is trying to balance work sent downstream. ap_allocate - Allocate memory for count data elements on port out_port, which must be a valid output port number (port numbers start at 0). Note that this will allocate count*sizeof(porttype) bytes of memory where porttype is the type of the port as specified in the X block description. NULL is returned if the memory could not be allocated. If multiple items are to be allocated, use "ap_get_max_send" or "ap_get_free" to determine how many items can be allocated at a time. This will block until enough space is available. ap_send - Send count items on port out_port. Note that this uses the buffer from the ap_allocate call. If the count is less than the allocated space, the block can either call ap_allocate again to disgard the remaining items or ap_send again to send the remaining items. ap_release - Release count data elements from port in_port. This call is used to tell the Auto-Pipe runtime that the block is finished with data from a "push" call. If all of the data isn't consumed, Auto-Pipe will call the block back with an updated pointer and data count for the port. If release isn't called on a push, Auto-Pipe will assume that the block is saving the data for later and needs more data from a different port before continuing. ap_send_signal - Send a signal of type type on output port out_port with value value. ap_get_input_count - Get the number of items available on port in_port. ap_get_input_data - Get a pointer to the data available on port in_port. ap_check_inputs_upto - Return 1 if input ports up to and including in_port have data available, otherwise returns 0. Most of these "functions" are implemented as macros in X.h. Block Structure Each block must define a structure named "ap_blockname_data" in its header file. Items of this type are used to hold block instance-specific data which includes configuration options and any local variables required by a block. Configuration options must have the same name and type as specified in the X description. Configuration options 2/7
are available to the block before the block's "init" function is called. Here is an example of the structure for a block with an UNSIGNED32 configuration option named "config_option" and no other per-instance variables: struct ap_example_data UNSIGNED32 config_option; ; Block Functions Each block must declare the following functions in the header file. These functions must be implemented in the block's source file. Note that it is also possible to define one or more of these functions as macros in the header file. In this case the function does not need to be present in the ".c" file. void ap_blockname_init(struct ap_blockname_data *block); void ap_blockname_destroy(struct ap_blockname_data *block); void ap_blockname_push(struct ap_blockname_data *block, int port, void *ptr, int count); int ap_blockname_go(struct ap_blockname_data *block); void ap_blockname_push_signal(struct ap_blockname_data *block, int port, int type, int value); A description of these functions follows. ap_blockname_init - Perform block initialization, such as opening files and initializing tables. This is called once per block instance when the application is started. Note that it is not safe to send data or signals at this point. ap_blockname_destroy - Perform rundown, such as closing files and deallocating memory. This is called once per block instance when the application shuts down. ap_blockname_push - Auto-Pipe calls this function when data becomes available on one of the block's input ports. port is the input port number (starting at 0), ptr is a pointer to the data on the port which the push was called, and count is the number of new elements available. Note that once data for one port is available, this function will not be called for data on the same port until all of the data for the port is released by calling the release function. ap_blockname_go - Auto-Pipe calls this function after start up once it is safe to start sending data. The return value of this function determines whether Auto-Pipe should call the function again. If the return value is 0, Auto-Pipe will call it again. ap_blockname_push_signal - Auto-Pipe calls this function when a signal is received. The port parameter indicates the input port on which the signal received, the type parameter is the signal type, and the value parameter is the signal value. Signaling Stop Signal (type 0) Stop signals allow Auto-Pipe applications to shut down cleanly. A stop signal is delivered on a port when a stop signal is received for that port and the credit for the port is zero. Blocks without input ports will not receive stop signals. 3/7
Stop signals are automatically generated on all output ports of a block when all of the following are true: 1. A non-zero value has been returned from the block's "go" function and 2. A stop signal has been received on all input ports and the credit for all input ports is zero. RDC: we also need for the block to indicate that it is done sending. Is the return from sighandler() sufficient for this? JGW: I was thinking that a return from sighandler would be sufficient since the block won't receive any more calls into it from "push" after this happens and we could enforce that "go" wouldn't be called any more either. The credit field for a stop signal contains a value that is zero when added to the credit for the input port of the next block. By doing this and waiting for the credit to be zero, we ensure that the next block will have consumed all of its input before the stop signals are delivered to the block and propagated. Note that we don't need to queue up the signals since the stop signal will be the last signal, however, we may need to hold on to the stop signal until more data arrives. An Auto-Pipe process will exit when all of the following are true: 1. All input ports for blocks mapped to the process have received a stop signal and 2. All output ports for blocks mapped to the process have sent a stop signal. Since the Auto-Pipe infrastructure manages stop signals, blocks are prohibited from sending them. However, blocks can respond to stop signals in the push_signal call, which will occur before the stop signal is propagated. Credit Signal (type 1) Credit signals prevent Auto-Pipe applications from deadlocking. Each input and output port has an associated credit. This credit is initialized to zero when Auto-Pipe starts. For input ports: 1. When a signal arrives on an input port, the credit field of the signal is added to the input port's credit. 2. When data arrives on an input port, the credit is decremented by the number of data elements. 3. Additional data is not delivered to the block unless the credit for the port is greater than zero (this may not be necessary, but we need to track this credit for the stop signal to work properly). For output ports: 1. When data is sent on an output port, the credit for that output port is decremented by the number of data elements sent. 2. If the credit for an output port goes negative, a credit signal is sent on all output ports to bring the credit back up to the buffer capacity. Examples Simple Example Here's a simple example of a version 2 block to add two numbers that only adds one pair at a time. 4/7
add_2u32.h: #ifndef ADD_2U32_H_ #define ADD_2U32_H_ #include "X.h" struct ap_add_2u32_data /* No instance-specific data */ ; void ap_add_2u32_init(struct ap_add_2u32_data *block); void ap_add_2u32_destroy(struct ap_add_2u32_data *block); void ap_add_2u32_push(struct ap_add_2u32_data *block, int port, void *ptr, int count); int ap_add_2u32_go(struct ap_add_2u32_data *block); void ap_add_2u32_push_signal(struct ap_add_2u32_data *block, int port, int type, int value); #endif add_2u32.c #include "add_2u32.h" void ap_add_2u32_init(struct ap_add_2u32_data *block) void ap_add_2u32_destroy(struct ap_add_2u32_data *block) void ap_add_2u32_push(struct ap_add_2u32_data *block, int port, void *ptr, int count) UNSIGNED32 *a = (UNSIGNED32*)ap_get_input_data(block, 0); UNSIGNED32 *b = (UNSIGNED32*)ap_get_input_data(block, 1); if(a!= NULL && b!= NULL) UNSIGNED32 *out = (UNSIGNED32*)ap_allocate(block, 0, 1); *out = *a + *b; ap_send(block, 0, 1); ap_release(block, 0, 1); ap_release(block, 1, 1); int ap_add_2u32_go(struct ap_add_2u32_data *block) return 1; // No need to call this again. void ap_add_2u32_push_signal(struct ap_add_2u32_data *block, int port, int type, int value) // We're not doing anything with signals. More Complex Example Here is a more complex example of a version 2 block to add two numbers. This one can add multiple pairs of numbers per call. add_2u32.h: #ifndef ADD_2U32_H_ #define ADD_2U32_H_ #include "X.h" 5/7
struct ap_add_2u32_data /* No instance-specific data. */ ; void ap_add_2u32_init(struct ap_add_2u32_data *block); void ap_add_2u32_destroy(struct ap_add_2u32_data *block); void ap_add_2u32_push(struct ap_add_2u32_data *block, int port, void *ptr, int count); int ap_add_2u32_go(struct ap_add_2u32_data *block); void ap_add_2u32_push_signal(struct ap_add_2u32_data *block, int port, int type, int value); #endif add_2u32.c #include "add_2u32.h" void ap_add_2u32_init(struct ap_add_2u32_data *block) void ap_add_2u32_destroy(struct ap_add_2u32_data *block) void ap_add_2u32_push(struct ap_add_2u32_data *block, int port, void *ptr, int count) const int count_a = ap_get_input_count(block, 0); const int count_b = ap_get_input_count(block, 1); if(count_a > 0 && count_b > 0) int send_count = ap_get_max_send(block, 0); if(send_count > count_a) send_count = count_a; if(send_count > count_b) send_count = count_b; UNSIGNED32 *a = (UNSIGNED32*)ap_get_input_data(block, 0); UNSIGNED32 *b = (UNSIGNED32*)ap_get_input_data(block, 1); UNSIGNED32 *out = (UNSIGNED32*)ap_allocate(block, 0, send_count); // Compute the sums. int i; for(i = 0; i < send_count; i++) out[i] = a[i] + b[i]; // Send the results on port 0. ap_send(block, 0, send_count); // Release the data we consumed. ap_release(block, 0, send_count); ap_release(block, 1, send_count); int ap_add_2u32_go(struct ap_add_2u32_data *block) return 1; // No need to call this again. void ap_add_2u32_push_signal(struct ap_add_2u32_data *block, int port, int type, int value) // We're not doing anything with signals. 6/7
Changes Since Version 1 Support for signals has been added (ap_blockname_signal_handler and ap_send_signal). Blocks must now call "ap_allocate" to obtain memory for sends. A "count" parameter is available allowing the runtime system to batch up sends and receives. The "release" function no longer takes a parameter to determine if it should free the memory. From the block's perspective, the memory is always freed and cannot be used for sends. Everything except configuration parameters has been removed from the block structure. The functions are now global (ap_send, for example) and the input/output ports are available via other means. The header file for the block no longer has the "X." prefix. Retrieved from "http://" This page was last modified on 19 August 2011, at 15:35. 7/7