内に含ま
その他のドキュメント
サポート リソース
| PDF 文書ファイルをダウンロードする
More on Writing Device Handlers
2
- This chapter provides basic information on writing device handlers that use the XIL library.
-
What Does the XIL Library Provide?
- The XIL library provides software implementations of all atomic functions and default implementations of some molecules. In addition, it supports four device handler types. Through creation of one or more device handlers, you can provide alternate implementations for any of the XIL-provided atomic functions or molecules, or any additional molecules that you may define.
- An atomic function is a basic XIL function. A molecule is composed of more than one atomic function.
- You can port functions to provide hardware acceleration or access to your particular device. Porting is discussed in the sections that follow.
What Kinds of Ports Are Possible in the XIL Library?
- The mechanism for porting in the XIL library allows you to decide which functions would provide the maximum benefit for your customers. If an add-on card is only good at geometric operators, only those functions need to be ported; the memory versions of the remaining functions are called automatically. If the device is a general-purpose imaging accelerator, you may find it reasonable to provide a compute handler for most or all of the possible XIL atomic functions.
- If only a compute handler is written, the XIL library expects that an image ends up residing in the CPU memory after each operation. If an accelerator has its own memory, it is often an advantage to allow the image data to reside on the device between operations. This avoids the overhead of having to copy the data back to the CPU after each operation. The XIL library has the concept of a storage handler, which is a set of functions which implements a copy to and from the specific device. If a storage handler is written, the XIL core code allows the image to reside in accelerator memory until another function requests that it be moved somewhere else. Writing a storage handler can greatly speed up a port for certain types of accelerator devices.
- Additional molecules may be implemented by combining atomic functions in ways that accelerate specific application areas. Faster implementations of atomic functions can be used in place of the default implementation. While not properly a device port, molecules can greatly improve the performance of groups of operations.
- For devices that act as either a source or destination image in an operation, the XIL library has the concept of an I/O handler. Once the handler is written, the application programmer can use the I/O device as a source or destination through the device image mechanism. The I/O device handler may also provide image processing for the source or destination image.
- A single device may be represented by more than one handler. For example, an input frame grabber that has integrated processing support can be described by an I/O handler and an associated compute handler. If it appears as though multiple processing operations will be done often on the grabbed images, a storage handler can be written for the frame-grabber board as well.
- Compression devices must implement the compression but may be associated with other compute, storage, or I/O handlers as well.
- A chapter in this guide describes the details of each type of handler.
What Kinds of Ports Are Not Possible in the XIL Library?
- The major constraint on porting in the XIL library is that the set of atomic functions may not be extended by the user. All molecules, including those going to I/O hardware, must be made up of groups of the atomic functions that the XIL library defines and implements. The list of available atomic functions is given in Appendix B, "XIL Atomic Functions."
- In addition, the IHV should not change the meaning of existing atomic functions; a new implementation should do exactly what the original version does. The correctness of a new function can be tested using the XIL Test Suite.
- Porting of functions not defined by the XIL library must be performed using the mechanism defined by xil_export().
The Development Environment
- The porting interface for the XIL library is written in C++. Because C++ compilers lack a stable binary interface, it is important that you write device handler code with the same compiler as the interface part of the library. Two compilers are supported: SPARCompiler(TM) C++ 4.2 and ProCompiler(TM) C++ 4.2.
- A compiler flag selects the C++ Application Binary Interface (ABI) used by the XIL library. The flag is added to the compile line and is:
-
-
-Qoption ccfe -abi=1:4.2:1
- The XIL library contains the XIL Test Suite. It enables you to perform regression tests against proven reference signatures. The XIL Test suite is described in XIL Test Suite User's Guide, which is part of this software release.
- The environment variable XIL_DEBUG can be useful in development situations. The options for XIL_DEBUG are described in Table 2-1.
-
Table 2-1 XIL_DEBUG
| XIL_DEBUG Option | Definition |
| linkxx | Add the two characters following the option link to the base name of the loadable handlers. This option is especially useful when you want to load a debug version of a handler. For example, link_g causes xilioxlib_g.so.1 to be loaded. If this version does not exist, the handler with the standard name (xilioxlib.so.1) is loaded. |
| show_action | Print XIL_ACTION, the name of the device (such as XilDeviceComputeMemory), and the name of the function being called to execute each XIL operation (such as setvalue8()), for example: XIL_ACTION[XilDeviceComputeMemory]:setvalue8 () |
| set_synchronize | Disable deferred execution. |
| provide_warnings | Have the default error handler also output warnings. |
| use_stripping | Make stripping the tiling mode. |
| txsize=* | Set the default X tile size (0 = default tiling). |
| tysize=* | Set the default Y tile size (0 = default tiling). |
| threads=* | Override the number of threads to create for this machine. This value normally is dependent on the number of processors in the machine. Setting the value to 1 turns off threading. |
| split_threashold=* | Override the minimum Y tile size for thread splitting. |
- Multiple variables may be set at once. For example, you could set XIL_DEBUG to show_action:set_synchronize.
Installing XIL Device Handlers
- XIL picks up the software pipelines provided by Sun from
-
-
/usr/openwin/lib/xil/devhandlers
- Any machine specific libraries, such as those provided by third-party driver developers, should be installed in:
-
-
/etc/openwin/lib/xil/devhandlers
-
Note - The environment variable XILHOME no longer exists and will not be read by XIL. If the device handlers are not in /etc/openwin/lib/xil/devhandlers, they will not be found.
- Compute devices are identified in the OWconfig file. See Chapter 4, "Compute Devices," for more information.
-
Note - The file xil.compute is no longer used as a configuration file for handlers and their dependencies. Instead, compute devices are identified in the OWconfig file.
-
Note - Be sure not to overwrite any existing files when you write your device handlers to the devhandlers directory.
Error Reporting for XIL Device Handlers
- All the possible error messages in the XIL library are listed in Appendix B, "XIL Error Messages," in the XIL Programming Guide.
- Where possible, you should make use of the currently existing error messages. When you need to use device-specific error messages that are to be included in the standard XIL release, you should create a new error file. The XIL Programming Guide contains the device-independent error message IDs. These IDs are numbered and prefixed with the string di- (for example, di-312).
- For device-dependent errors, the prefix for the error ID should be the device name for the handler. For example, for a handler with the device name XXXCamera (where XXX is the company name), the error IDs should have the form XXXCamera-123. In this example, the XIL library looks for the device-specific error message number 123 in the directory
-
-
/usr/openwin/lib/xil/locale/current_locale\
/LC_MESSAGES/XXXCamera.mo
- The XIL library is internationalized; that is, it uses functions to extract error messages for a given locale. For information on localization of error messages, see the document Developer's Guide to Internationalization (available in AnswerBook).
Version Control for XIL Handlers
- The XIL core contains the global function:
-
-
xilVersionPtr* XilGetVersion()
- This function returns a pointer to a structure that contains 16-bit unsigned integers containing the major and minor release numbers of the current XIL library. The structure looks like this:
-
typedef struct {
Xil_unsigned16 majorVersion;
Xil_unsigned16 minorVersion;
} *xilVersionPtr;
|
- The rules for loading handlers are fairly simple:
-
- The library will not load a module with a majorVersion greater than its own. An attempt to load a module greater than the current library version results in an error.
- Currently, the allowable (earlier) module versions that are supported are versions 1.1 and 1.2. Thus, majorVersion can only equal 1, and minorVersion can equal either 1 or 2.
- The library loads and executes any module with the same majorVersion number.
- Similar version control rules exist for all of the OGI foundation libraries, including the port for the OpenWindows software.
- These rules have implications for writers of XIL device handlers. You should write your handler with the earliest version of the Solaris operating system that you wish to support. Upgrading to a new operating system version by the end user will, in general, not require a new release of XIL device handlers. If you wish to write a handler that requires functionality only available after a specific library release, you must check the majorVersion and minorVersion numbers to make sure the handler has been loaded by an appropriate version of the library.
- For the XIL library to properly load handlers, the name of the handler must contain its major version number as a suffix. For example, the standard XIL I/O handler for X11 support is called xilioxlib.so.1. For the 1.x release of the XIL library, it is sufficient to ensure that each handler name includes the suffix .1.
How XIL Device Handlers Work
- Each type of device in the XIL library handles a different aspect of imaging device dependence. The inner workings of each type of device are detailed in Chapter 3, "I/O Devices," through Chapter 6, "Storage Devices," in this guide, along with pointers to examples of each device handler. However, the overall concept behind providing a device handler is similar among different kinds of devices.
-
Note - If you are writing a device handler, be aware that not all XIL application programs call xil_close() before exiting. Therefore you should make sure, if possible, that your device handler releases any persistent system resources if an application dies abnormally.
- To implement a specific device, you must define a derived class from the appropriate XilDeviceManager class that represents the device. Only one derived class can exist for each device, and therefore only one for each handler. The purpose of the derived class is to:
-
- Initialize the device.
- Create the derived XilDevice class.
- As an example, consider the case of an I/O device called XXXCamera, which represents the combination of a frame grabber and camera (as shown in Figure 2-1).
-
Note - The XXX in the device name represents the name of the company creating the device. For details on I/O device naming, see "Adding an I/O Device" on page 91.
- This example demonstrates the flow of creating an XIL handler, as follows:
-
- The subclass XilDeviceManagerIOXXXCamera is created by a call to the XilDeviceManager member function create(), which must exist in the loadable library that contains the handler.
- The XilDeviceManagerIOXXXCamera subclass initializes the frame grabber, and holds all the global information that is shared among different instances of the actual device. The create() function is called when the handler is loaded. In this example of an I/O device, this happens the first XXXCamera handler name as the device-name parameter.
- After initializing, the XIL core code calls code in XilDeviceManagerIOXXXCamera that creates an instance of the derived class XilDeviceIOXXXCamera. This class contains all the code needed to perform the image acquisition. The second time the application calls xil_create_from_device() with the same device name, the second instance of XilDeviceIOXXXCamera is created. To the application, this appears as a second device image. The two device images can exist in sequence or simultaneously.

Figure 2-1
- The flow of creating a device handler is essentially the same for I/O, storage, and compression handlers (Figure 2-2), but is not identical for compute devices.

Figure 2-2
- Compute devices have only a single instantiation, which is controlled by the XIL core code. Thus, there is no derived class called XilDeviceCompute; only the XilDeviceManagerCompute class exists. Like the other device classes, the XilDeviceManagerCompute class must be subclassed, this time to represent the XIL functions that are being accelerated. The mechanism for allowing the XIL core code to instantiate the compute class is described in detail in Chapter 4, "Compute Devices."
Implementing an XIL Operation
- To create a handler for a compute, I/O, or compression device, you must implement an XIL operation.
- An XIL operation is a member of one of the following classes:
-
-
XilDeviceManagerCompute
-
XilDeviceManagerIO
-
XilDeviceManagerCompression
-
Note - This version of the XIL Graphics Porting Interface (GPI) is different from earlier versions: you may now implement XIL operations as part of an I/O, compression, or compute device.
- While each type of handler has its own unique features and capabilities, implementing an XIL operation as a member of a compute, I/O, or compression class is essentially the same.
Operation Prototype: Atomic Function
- This section specifically looks at code that implements a compute routine for the byte case of the add atomic function. However, you can generally apply the basic structure it describes to atomic functions for I/O and compression devices.
- For a list of all the atomic functions that you can implement, see Appendix B, "XIL Atomic Functions."
- The prototype for the add operation is shown below.
-
int
XilDeviceManagerComputeBYTE::Add(
XilOp* op, // Pointer into the DAG
int op_count, // Number of combined ops to be done
XilRoi* roi, // Region of interest used in the op
XilBoxList* bl) // List of boxes to be processed
|
- As you recall, the XilOp class holds the information required to store an XIL operation in a Directed Acyclic Graph (DAG). The operation parameters are op, op_count, roi, and bl.
- For an XIL atomic function, the op parameter is a pointer to the XilOp object that represents the specific atomic function in the DAG. The source and destination images must be accessible to the function. These images are stored in the XilOp object. The parameters of the atomic function also are stored in the XilOp object. The XilOp class contains member functions that enable you to extract the image and parameter information for the atomic function. Appendix A, "XilOp Object," identifies the images and parameters supported by each XIL atomic function and explains how to extract them.
- The op_count parameter is the number of operations (also called ops) combined in the operation. For an atomic function, this number is 1. (For a molecule, the value is a number greater than 1.)
- The roi parameter is a pointer to the XilRoi object, which stores the intersected region of interest (ROI) for the destination image.
-
Note - In this version of XIL, the compute routine is not required to calculate the intersected ROI while processing. The core has already done this.
- The intersected ROI represents the overlapping regions of all source images and the destination image with image origins aligned. The intersected ROI represents that portion of the destination to be written.
- The bl parameter is a box list. The box list is a list of destination areas and their corresponding source areas to be processed by the operation.
- A box list may have more than one entry. Each entry contains one box representing a portion of the destination image to be processed in an operation. In addition, the entry contains a box corresponding to each source image. The boxes are used to acquire the storage for each image. Before processing, the ROI is intersected with the box to produce the rectangles relative to the box that need to be processed.
Basic Structure: Atomic Function
- This section looks at the code used to implement a compute routine for the byte case of the add atomic function.
- The complete source code for the XilDeviceManagerComputeBYTE::Add() atomic function example can be found in the TBD directory.
- The basic structure consists of the following steps:
-
-
Split boxes in the box list on tile boundaries in the sources.
-
Obtain the necessary images and XilOp object parameters.
-
Loop over the boxes to account for all boxes in the box list.
-
Acquire storage for each box.
-
Process the data.
-
Note - Although this section describes a compute routine, all compute, I/O, and compression routines have this same basic structure. This was not true for previous versions of XIL.
Step 1: Splitting Boxes on Tile Boundaries
-
Note - Generally the boxes passed in to an operation are already split for tiles in the destination image so the image does not span tiles. (Some exceptions are noted in specific sections of Chapter 4, "Compute Devices.") Boxes have not yet been split for tiles in the source image.
- The first step of the basic structure has the operation take the box list and split boxes on tile boundaries for the source images, as shown below.
-
XilStatus
XilDeviceManagerComputeBYTE::Add(XilOp* op,
unsigned int ,
XilRoi* roi,
XilBoxList* bl)
{
if(op->splitOnTileBoundaries(bl) == XIL_FAILURE) {
return XIL_FAILURE;
}
|
- This step guarantees that the storage you later acquire for the boxes (see "Step 4: Acquiring Storage") lies within a tile. Because this step involves an operation-specific call, you should see Chapter 4, "Compute Devices," for potential specific information.
Obtaining Necessary Images and XilOp Object Parameters
- The example code below shows how to get the images for the operation and the number of bands in the image. See Appendix A, "XilOp Object," for a summary of the parameters available for each operation and a description of the XilOp member functions you need to call to obtain the parameter information.
-
XilImage* src1 = op->getSrcImage(1);
XilImage* src2 = op->getSrcImage(2);
XilImage* dest = op->getDstImage(1);
unsigned int nbands = dest->getNumBands();
|
Step 3: Looping Over Boxes
- The compute routine iterates over the box list until no there are no more storage boxes to be processed. Each box in the list describes the area required to perform the image operation. While it is possible to retrieve the coordinates of the image area from the box, most operations simply pass the boxes retrieved from the list as arguments to other functions.
- Each entry in a box list contains a box for each image required by that operation. As an example, a box list entry for the add operation consists of three boxes: one for each of the two source images and one for the destination image.
- In the example below, XilBox::getNext() returns FALSE when all boxes have been processed.
-
XilBox* src1_box;
XilBox* src2_box;
XilBox* dest_box;
while(bl->getNext(&src1_box, &src2_box, &dest_box)) {
|
Step 4: Acquiring Storage
- The next step of the basic structure acquires storage for each box. Before describing this procedure, you should understand what XilStorage objects are and how to construct them.
-
XilStorage Object The XilStorage object represents the description of a contiguous region of memory associated with a given image. At construction, they contain no information but are filled in by subsequent calls. For improved performance and to ensure that they are destroyed automatically at the end of the loop, XilStorage objects should be constructed on the stack as shown below.
-
XilStorage src1_storage(src1);
XilStorage src2_storage(src2);
XilStorage dest_storage(dest);
|
-
Filling in the XilStorage Object You fill in the XilStorage objects for the given boxes by calling XilImage::getStorage() for the appropriate images, as shown below.
-
if((src1->getStorage(&src1_storage, op, src1_box, "XilMemory",
XIL_READ_ONLY) == XIL_FAILURE) ||
(src2->getStorage(&src2_storage, op, src2_box, "XilMemory",
XIL_READ_ONLY) == XIL_FAILURE) ||
(dest->getStorage(&dest_storage, op, dest_box, "XilMemory",
XIL_WRITE_ONLY) == XIL_FAILURE)) {
//
// Mark this box entry as having failed. If marking the box
// returns XIL_FAILURE, then we return XIL_FAILURE.
//
if(bl->markAsFailed() == XIL_FAILURE) {
return XIL_FAILURE;
} else {
continue;
}
}
|
- The first parameter to getStorage() is the address of the XilStorage object to be filled.
- The second parameter is the op that was passed in to this function.
-
Note - The op passed to the getStorage() call is different for molecules. See "Operation Prototype: Molecule" on page 62 for more information.
- The third parameter is the name of the storage that is being requested. The default is XilMemory. If, however, you have your own storage device, this is where you would specify its name.
- The fourth parameter is the XilStorageAccess type. Source images should always be accessed as XIL_READ_ONLY. Destination images are accessed as either XIL_WRITE_ONLY or XIL_READ_WRITE depending on whether the destination can be read from as well as written to (as is the case with the xil_copy_with_planemask()function, or for some optimizations).
-
Note - It is very important that you request the storage with the correct XilStorageAccessType. Not doing so may cause the XIL core to incorrectly prepare the storage and may generate incorrect results.
- Two additional parameters need not be specified as they are default parameters. These are an XilStorageType and a void*.
- The fifth parameter, an XilStorageType, has the default value of XIL_STORAGE_TYPE_UNDEFINED, which means that any of XIL's supported storage types are acceptable and can be processed. If the operation wants to restrict its processing to another type such as XIL_PIXEL_SEQUENTIAL, it specifies that as an argument.
-
Note - It is strongly recommended that you use the default value for XilStorageType. Unless you know exactly what you are doing, specifying a different value could significantly impede performance.
- The sixth parameter, a void*, allows storage-device specific attributes to be passed in by the compute routine. This parameter would only be used if you wrote your own storage device that needed additional information. The XilMemory storage device in the example shown takes no additional attributes. However, if you write your own storage device, you may need to pass in additional information.
-
Note - Failure can be indicated on a per-box basis. If one box can't be acquired or processed, the operation simply marks it as failed and continues. The XIL memory code picks up that portion of the operation that remains undone.
-
Determining Storage Formats of the Images Once storage has been acquired from the images, the compute routine can then determine the storage format and set up its processing loop accordingly. XIL supports three storage layouts:
-
-
XIL_PIXEL_SEQUENTIAL
-
XIL_BAND_SEQUENTIAL
-
XIL_GENERAL
- Although the memory compute routines handle all three storage formats, the compute routine may want to restrict its processing to certain types or to write a different image processing loop for each format. For more information on storage formats, see Chapter 4, "XIL Storage," in the XIL Programmer's Guide.
-
if((src1_storage.isType(XIL_PIXEL_SEQUENTIAL)) &&
(src2_storage.isType(XIL_PIXEL_SEQUENTIAL)) &&
(dst_storage.isType(XIL_PIXEL_SEQUENTIAL)) ) {
|
-
Getting Storage Information To obtain information from a storage object such as the starting address of the image data or the pixel stride, the XilStorage::getStorageInfo() is called. These examples are two overloaded methods for obtaining this data.
-
// Pixel Sequential
unsigned int src1_pixel_stride;
unsigned int src1_scanline_stride;
Xil_unsigned8* src1_data;
src1_storage.getStorageInfo(&src1_pixel_stride,
&src1_scanline_stride,
NULL, NULL,
(void**)&src1_data);
// Band Sequential, General
for(unsigned int band=0; band<nbands; band++) {
unsigned int src1_pixel_stride;
unsigned int src1_scanline_stride;
Xil_unsigned8* src1_data;
src1_storage.getStorageInfo(band,
&src1_pixel_stride,
&src1_scanline_stride,
NULL,
(void**)&src1_data);
|
- For details on storage formats, see Chapter 4, "XIL Storage," in the XIL Programmer's Guide.
Step 5: Processing the Data
- At this point the basic compute routine is ready to process the data.
-
Obtaining Intersected ROIs To account for any ROIs that may be set on the images, the routine creates an XilRectList object using the roi passed in and the destination box. The XilRectList constructor gets a list of rectangles to be processed on the destination. Passing in the box ensures that the rectangles lie within the box area and causes the rectangles to be relative to the starting x and starting y of the box.
- The ROI passed in to the compute routine represents the intersected ROI of the operation. (Any deviations from this are noted in the specific compute device sections in Chapter 4, "Compute Devices.") The XIL core has correctly mapped and intersected the source and destination ROIs as appropriate for the operation.
- The code fragment shows the XilRectList object being created on the stack. This enhances performance, as it eliminates the overhead of using new() and delete() functions.
-
XilRectList rl(roi, dest_box);
int x;
int y;
unsigned int xsize;
unsigned int ysize;
while(rl.getNext(&x, &y, &xsize, &ysize)) {
...
|
-
Note - The compute routine can construct an XilConvexRegionList instead of an XilRectList. An XilConvexRegionList is used primarily in affine() and rotate() geometric operations. For details, see Chapter 4, "Compute Devices."
-
Using the Appropriate X an d Y Values With the XilRectList object created, the compute routine then moves to the location of the data to start processing using the x and y values, and it processes data until all the pixels in the rectangles have been dealt with.
-
Note - The x and y locations are relative to the current box being processed (that is, they are not image coordinates). The data pointer returned from the storage object also is relative to the current box.
- The following code shows how to calculate the appropriate data starting point for an XIL_PIXEL_SEQUENTIAL byte image.
-
XilRectList rl(roi, dest_box);
int x;
int y;
unsigned int xsize;
unsigned int ysize;
while (rl.getNext(&x, &y, &xsize, &ysize)) {
Xil_unsigned8* src1_scanline = src1_data +
(y*src1_scanline_stride + (x*src2_pixel_stride);
Xil_unsigned8* src2_scanline = src2_data +
(y*src2_scanline_stride + (x*src2_pixel_stride);
Xil_unsigned8* dest_scanline = dest_data +
(y*dest_scanline_stride + (x*dest_pixel_stride);
|
Handling Failure and Return Values
- As mentioned in "Step 4: Acquiring Storage" on page 56 and "Step 5: Processing the Data" on page 59, failure can be indicated on a per-box basis. This allows the operation to continue looping through the box list, processing those boxes that it can. If any of the boxes has been marked as failed, it is best to return XIL_FAILURE at the completion of the whole operation. Although the XIL core catches failed boxes (even if the routine returns XIL_SUCCESS), it is more efficient to indicate the failure. If all boxes are processed successfully, the operation returns XIL_SUCCESS upon completion of the box list loop.
-
Note - Because the XilStorage object and XilRectList are created on the stack, they are destroyed automatically at the end of each loop through the box list. Since the information in them is specific to the boxes being processed
- during any given loop, there is no reason to make them persistent. If you do not create them on the stack, you must explicitly destroy them upon completion of looping through the box list.
Operation Prototype: Molecule
- The prototype for a molecule looks exactly like that for an atomic function. (Compare the prototype in "Operation Prototype: Atomic Function" on page 53 to the molecule prototype shown here.)
-
int
XilDeviceManagerComputeBYTE::ThresholdThreshold(
XilOp* op, // Pointer into the DAG
int op_count; // Number of combined ops to be done
XilRoi* roi; // Region of interest used in the op
XilBoxList* bl) // List of boxes to be processed
|
- As in an atomic function, the XilOp class holds all the information required to store an XIL operation in the DAG. In the case of a molecule, however, the op parameter is a pointer to the XilOp object that represents the last in a sequence of XIL operations in the DAG. The op_count indicates the number of XIL operations combined in the molecule.
- Any of the previous operations in the molecule's operation sequence are accessible from the passed-in op parameter. The source, destination, and parameters for each individual op in the sequence are available to you from the appropriate XilOp object.
- As in an atomic function, the roi parameter is a pointer to the XilRoi object, which stores the intersected ROI for the destination image. In the case of a molecule, the intersected ROI represents the intersected ROI that would be generated by doing the individual operations in sequence from the start of the molecule to the final destination.
-
Note - There are restrictions on which operations are deferrable in XIL1.3. For more information, see "Some Considerations" on page 32.
- The bl parameter is the box list. The box list is a list of destination areas and their corresponding source areas to be processed by the operation. In the case of a molecule, the source areas referenced are those for the sources to the first XilOp object in the operation sequence. The destination areas refer to the destination of the last XilOp object in the operation sequence. Secondary sources on intermediate ops would follow in order between the initial sources and the final destination.
Basic Structure: Molecule
- This section looks at the code used to implement a compute routine for the byte case of the threshold-threshold molecule.
- The complete source code for the XilDeviceManagerComputeBYTE::ThresholdThreshold() molecule example can be found in the TBD directory.
- The structure of a molecule is very similar to that for an atomic function. The basic structure consists of the following steps:
-
-
Optionally verify which molecule is being handled.
-
Obtain the individual XilOp objects from the op parameter.
-
Split boxes in the box list on tile boundaries in the source.
-
Obtain the images and XilOp object parameters.
-
Loop over the boxes to account for all boxes in the box list.
-
Acquire storage for the boxes using the appropriate XilOp objects.
-
Process the data.
- This basic structure differs from the atomic function basic structure (see "Basic Structure: Atomic Function" on page 54) in the following ways:
-
- The first two (additional) steps apply to molecules only.
- Step 3 (splitting boxes on tile boundaries) and step 6 (acquiring storage) include some modifications for molecules.
Step 1: (Optional) Verifying the Passed-In Molecule
- The XIL core ensures that a molecule does not get called unless it meets the criteria defined when the molecule is registered. It is not necessary for the molecule to check. Checking the op_count can be valuable, however, when the function implements more than one molecule. In such cases, the op_count indicates which molecule is being called. Because only one molecule is implemented in this example, op_count does not require verification.
-
XilStatus
XilDeviceManagerComputeBYTE::ThresholdThreshold(XilOp* op,
unsigned op_count,
XilRoi* roi,
XilBoxList* bl)
{
|
- A common example of a function that would handle molecules of different lengths is a decompressed-display molecule that has an optional extra copy.
Step 2: Obtaining the XilOp Objects and Their Parameters
- The op parameter contains a pointer to the list of the deferred operations in depth-first order (that is, the first XilOp object in the list is the bottom op in the operation sequence). The compute routine views all the individual ops in a molecule as one operation. As such, it is only interested in the destination image from the final XilOp object in the op sequence.

Figure 2-3
- As shown in the code example below, the op parameter is the same as the 0 entry in the op list (the first position in the list). The first XilOp object in the sequence of operations is always in the position (op_count -1) in the op list.
-
//
// This molecule only has two XilOp objects. The top op
// provides the source image. The bottom op provides the
// destination image.
//
XilOp* src_op = op->getOpList()[1];
XilOp* dst_op = op;
if(dst_op->splitOnTileBoundaries(bl) == XIL_FAILURE) {
return XIL_FAILURE;
}
|
Step 3: Splitting Boxes on Tile Boundaries
- This next step has the operation take the box list and split boxes on tile boundaries for the source images, as shown below. Although this code could be exactly the same as the atomic function code for this step, it is important to realize that the XilOp::splitOnTileBoundaries() function is being called on the destination op.
-
{
if(dst_op->splitOnTileBoundaries(bl) == XIL_FAILURE) {
return XIL_FAILURE;
}
|
Step 4: Obtaining Images and XilOp Object Parameters
- The molecule function needs to get the source images from the first op in the operation sequence, the destination image from the last op in the operation sequence, as well as any additional source images needed by intermediate ops in the sequence. (See Figure 2-3 on page 65.) Once the XilOp objects are obtained (see "Step 2: Obtaining the XilOp Objects and Their Parameters" on page 64), you use the same functions to obtain the parameters from each
- individual op as you would for an atomic function, as shown in the code example below. For more information on these functions, see Appendix A, "XilOp Object."
-
XilImage* src1_image = src_op->getSrcImage(1);
XilImage* dest_image = dst_op->getDstImage(1);
//
// First Threshold
//
Xil_unsigned8* op1_low;
src_op->getParam(1, (void**)&op1_low);
Xil_unsigned8* op1_high;
src_op->getParam(2, (void**)&op1_high);
Xil_unsigned8* op1_map;
src_op->getParam(3, (void**)&op1_map);
//
// Second Threshold
//
Xil_unsigned8* op2_low;
dst_op->getParam(1, (void**)&op2_low);
Xil_unsigned8* op2_high;
dst_op->getParam(2, (void**)&op2_high);
Xil_unsigned8* op2_map;
dst_op->getParam(3, (void**)&op2_map);
//
// Store away the number of bands for this operation.
//
unsigned int num_bands = dest_image->getNumBands();
|
Step 5: Looping Over Boxes
- The compute routine now iterates over the box list until no there are no more storage boxes to be processed. This step is exactly the same as if this were an atomic function.
-
XilBox* src1_box;
XilBox* dest_box;
while(bl->getNext(&src1_box, &dest_box)) {
|
Step 6: Acquiring Storage
- The next step of the basic molecule acquires storage for the boxes. This differs from an atomic function in that the op parameter passed in to the XilImage::getStorage() call for a given image must match the XilOp object with which the image is associated. As shown below, the source image comes from the top operation (src_op), and the destination image comes from the bottom operation (dst_op).
-
//
// Aquire our storage from the images. The storage returned is valid
// for the box given. Thus, any origins or child offsets have been
// taken into account.
//
XilStorage src1_storage(src1_image);
XilStorage dest_storage(dest_image);
if((src1_image->getStorage(&src1_storage,src_op, src1_box,
"XilMemory", XIL_READ_ONLY) == XIL_FAILURE) ||
(dest_image->getStorage(&dest_storage, dst_op, dest_box,
"XilMemory", XIL_WRITE_ONLY) == XIL_FAILURE)) {
//
// Mark this box entry as having failed. If marking the box
// returns XIL_FAILURE, then we return XIL_FAILURE.
//
if(bl->markAsFailed() == XIL_FAILURE) {
return XIL_FAILURE;
} else {
continue;
}
}
|
- All other parameters to the XilImage::getStorage() call are the same as those for an atomic function.
Step 7: Processing the Data
- Once storage has been acquired from the images, the compute routine can then determine the storage format and set up its processing loop accordingly.
- The first part of this routine shown here is specialized for XIL_PIXEL_SEQUENTIAL data.
-
//
// Test to see if all of our storage is of type XIL_PIXEL_SEQUENTIAL.
// If so, implement a loop optimized fro pixel-sequential storage.
//
if((src1_storage.isType(XIL_PIXEL_SEQUENTIAL)) &&
(dest_storage.isType(XIL_PIXEL_SEQUENTIAL))) {
unsigned int src1_pstride;
unsigned int src1_sstride;
Xil_unsigned8* src1_data;
src1_storage.getStorageInfo(&src1_pstride, &src1_sstride,
NULL, NULL,(void**)&src1_data);
unsigned int dest_pstride;
unsigned int dest_sstride;
Xil_unsigned8* dest_data;
dest_storage.getStorageInfo(&dest_pstride, &dest_sstride,
NULL, NULL,(void**)&dest_data);
//
// Create a list of rectangles to loop over. The resulting list
// of rectangles is the area left by intersecting the ROI with
// the destination box.
//
XilRectList rl(roi, dest_box);
int x1;
int y1;
unsigned int xsize;
unsigned int ysize;
while(rl.getNext(&x1, &y1, &xsize, &ysize)) {
|
- The latter part of the above routine is shown here. It handles any general storage layout.
-
} else {
//
// For XIL_GENERAL and XIL_BAND_SEQUENTIAL images
//
XilRectList rl(roi, dest_box);
int x1;
int y1;
unsigned int xsize;
unsigned int ysize;
while(rl.getNext(&x1, &y1, &xsize, &ysize)) {
//
// Each Band...
//
for(unsigned int band=0; band<num_bands; band++) {
unsigned int src1_pstride;
unsigned int src1_sstride;
Xil_unsigned8* src1_data;
src1_storage.getStorageInfo(band,
&src1_pstride,
&src1_sstride,
NULL,
(void**)&src1_data);
unsigned int dest_pstride;
unsigned int dest_sstride;
Xil_unsigned8* dest_data;
dest_storage.getStorageInfo(band,
&dest_pstride,
&dest_sstride,
NULL,
(void**)&dest_data);
|
Supporting Re-entrancy
- It is critical and expected that routines be fully re-entrant. This is because any operation can be processing any number of operations simultaneously. Furthermore, multiple portions of the same operation can be processed simultaneously.
- XIL classes and objects are not multi-thread safe (MT-safe). As such, only acquiring information is allowed. Global or static variables cause the operation to fail.
Pre-Process and Post-Process Methods
- Some operations such as lookup may need to pre-calculate information for use by each of multiple threads executing that operation at the same time, and then perform a single cleanup function. To facilitate this, compute, I/O, and compression routines can have optional pre-process and post-process methods. These methods are guaranteed to be called once per operation, but many threads may be calling them at the same time.
Pre-Process Method
- The pre-process routine is guaranteed to be called prior to any threads calling the main function. (The post-process routine is not called until all the threads have completed the main function.)
-
Pre-Process Example In this example, the pre-process routine fills in a pointer compute_data to the data to be shared between compute routines.
-
XilStatus
XilDeviceManagerComputeBYTE::Lookup8Preprocess(XilOp* op,
unsigned ,
XilRoi* ,
void** compute_data
unsigned int* )
{
XilImage* dst = op->getDstImage(1);
//
// Create a lookup structure no matter what
//
LookupData* lud = new LookupData;
if(lud == NULL) {
XIL_ERROR(dst->getSystemState(), XIL_ERROR_RESOURCE, "di-1", TRUE);
return XIL_FAILURE;
}
...
|
-
XilStatus
// Code deleted for brevity
...
*compute_data = lud;
return XIL_SUCCESS;
}
|
- In the example, the unsigned int parameter to Lookup8Preprocess() is an optional parameter. By default its value is 0. You can use this parameter, for example, if you implement multiple versions of the same function in a given compute routine. You assign a unique value to it for each function so the XIL core can keep track of which version is being associated with a particular pre-process routine.
- If the pre-process routine returns XIL_FAILURE, the function is never called for this operation. This can be helpful for detecting operation-specific conditions (such as only processing one-banded images) without requiring the main function to test the image format each time it is called.
-
Accessing the Pre-process Data The compute routine gets at the pre-process data using a method on the op XilOp::getPreprocessData() as shown here.
-
LookupData* lud = (LookupData*)op->getPreprocessData(this,id);
|
- The id parameter is optional. It is a unique value passed in to identify this particular version of a function.
Post-Process Method
- The post-process method is called after all the compute (I/O or compression) operations have been called. Like the pre-process method, the post-process method is called once per operation but may be called by multiple threads. This method typically would be used to deallocate the pre-process data. The following is an example.
-
XilStatus
XilDeviceManagerComputeBYTE::Lookup8Postprocess(XilOp* ,
void* compute_data)
{
LookupData* lud = (LookupData*)compute_data;
if(lud->allocated != 0) {
delete lud;
}
return XIL_SUCCESS;
}
|
Registering Operations With the XIL Library
- When a compute, I/O, or compression device handler is loaded into the XIL library, it calls the device manager's describeMembers() pure virtual function. Your device handler is required to implement this function to describe its capabilities to the XIL library. If describeMembers() returns XIL_SUCCESS, the library assumes all your device manager functions have been successfully described, and it considers your device handler to have been loaded. If, however, describeMembers() returns XIL_FAILURE, the library does not consider your device handler loaded and will not use functions from the device.
Generating describeMembers()
- To simplify generating the describeMembers() function--since the GPI to describe functions to the XIL library (particularly molecules) is often a place where mistakes can be made--SunSoft provides the Perl script xilcompdesc.pl. You can use xilcompdesc.pl to generate a describeMembers.cc file from your source files. Then compile describeMembers.cc into your device handler.
- You invoke the script with three arguments, using the syntax shown here.
-
xilcompdesc.pl <className> <classType> <files>
|
- <className> is the name of your derived class.
- <classType> is the type of class (that is, compute, I/O, or compression).
- <files> is a space-delimited list of files from which to extract XILCONFIG information.
- This example invokes xilcompdesc.pl for a compute handler. XILCONFIG information is extracted from three files: Add.cc, Lookup.cc, and Multiply.cc.
-
xilcompdesc.pl XilDeviceManagerComputeMyDevice Compute Add.cc Lookup.cc Multiply.cc
|
- Usually, you would incorporate an xilcompdesc.pl script into your Makefile to generate describeMembers.cc and to compile describeMembers.cc into your device handler.
XilConfig Syntax Describing an Operation
- The xilcompdesc.pl script looks for a well-defined XILCONFIG line and an adjoining block describing the member functions implementing an operation in the given source files. The XILCONFIG syntax is shown here.
-
//
// XILCONFIG: <Member Function> {
// OP=<XIL Operation Name>
// PRE=<Preprocess Member Function>
// POST=<Postprocess Member Function>
// }
//
|
- <Member Function> is the name of the member function in your derived class that implements the functionality described in the adjoining block.
- <XIL Operation Name> is the GPI name of the XIL operation you're implementing. See Appendix A, "XilOp Object," for a list of the available atomic function names.
- <Preprocess Member Function> and <Postprocess Member Function> are the names of the member functions in your derived class that act as pre-processor and post-processor routines, respectively, for the primary member function. See "Pre-Process Method" on page 71 and "Post-Process Method" on page 73 for more information on these routines.
- In the above syntax, do not precede or follow the equal sign (=) of a description assignment with a space character delimiter. PRE and POST assignments are optional, but OP is required.
Example of Generating describeMembers()
- Say, for example, an atomic function such as lookup() looks up XIL_BYTE data and outputs XIL_BIT data. As another example, a molecule implements an add() followed by a multiply() function. The XILCONFIG lines to describe each of these operations would look something like this.
-
//
// XILCONFIG: Lookup {
// OP=lookup;8->1
// PRE=LookupPreprocess
// POST=LookupPostprocess
// }
//
// XILCONFIG: AddMul {
// OP=add;8
// OP=multiply;8
// }
//
|
- For the previous two XILCONFIG lines, the xilcompdesc.pl script generates this describeMembers() function.
-
XilStatus
XilDeviceManagerComputeMyDevice::describeMembers()
{
XilFunctionInfo* fi;
fi = XilfunctionInfo::create();
fi->describeOp(XIL_STEP, 1, "lookup;8->1");
fi->setFunction((XilComputeFunctionPtr)
XilDeviceManagerComputeMyDevice::Lookup,
"lookup;8->1()");
fi->setPreprocessFunction((XilComputePreprocessFunctionPtr)
XilDeviceManagerComputeMyDevice::LookupPreprocess)
fi->setPostprocessFunction((XilComputePostprocessFunctionPtr)
XilDeviceManagerComputeMyDevice::LookupPostprocess)
this->addFunction(fi);
fi->destroy();
fi = XilfunctionInfo::create();
fi->describeOp(XIL_STEP, 1, "multiply;8");
fi->describeOp(XIL_STEP, 1, "add;8");
fi->setFunction((XilComputeFunctionPtr)
XilDeviceManagerComputeMyDevice::AddMul,
"multiply;8(add;8())");
this->addFunction(fi);
fi->destroy();
return XIL_SUCCESS;}
|
|
|