Application programming interface with inverted memory protocol for embedded software systems6968438Abstract A system and method is provided for enabling the reuse of algorithms in multiple application frameworks with no alterations required of the algorithm once it is developed. An inverted memory allocation mechanism enables various algorithm modules to be integrated into a single application without modifying the source code of the algorithm modules. An algorithm module is designed in a manner that renders the algorithm module reentrant within a preemptive environment. Each data access instruction of the algorithm module is coded in a manner that renders the algorithm module and all of the data access instructions relocatable. A memory interface is provided within the algorithm module that supports both design-time object instantiation and dynamic object instantiation. Claims 1. A method for creating an algorithm module that can be used without change in a plurality of frameworks, the method comprising the steps of: Description REFERENCE TO COMPUTER PROGRAM LISTING APPENDIX ON COMPACT DISC
Table 1 defines various terms that will be used herein.
FIG. 2 demonstrates the elements that should be considered in creating a component model for embedded systems. Level 1 201 contains programming guidelines that apply to all algorithms on all DSP architectures regardless of application area. Almost all recently developed algorithms follow these common sense guidelines but they are formalized in an embodiment of the present invention, as they must be followed for the embodiment to work successfully. Level 2 202 describes software techniques to enable all algorithms to operate harmoniously within a single system. The present invention is most concerned with this level, presenting solutions to technical problems that previously made the creation of algorithms usable across multiple frameworks impossible. Level 3 203 deals with guidelines for specific families of DSPs. Level 4 204 is concerned with various vertical markets. These levels are outside the scope of the present invention as they do not need to be addressed in order to implement the present invention. For algorithms to satisfy the minimum requirements of reentrancy, I/O peripheral independence, and debuggability, they must rely on a core set of services that is always present. Since most algorithms are still produced using assembly language, many of the services provided by this core must be accessible and appropriate for assembly language. In an embodiment of the present invention, the core set of services includes a subset of a DSP operating system together with some additions to support atomic modification of control/status registers. It also includes a subset of standard C language run-time support libraries; e.g., memcpy, strcpy, etc. The DSP operating system is a collection of twelve modules:
Of these modules, only SWI, PRD, and IDL are directly related to thread scheduling. Table 2 describes which operations are callable. Unless otherwise noted, any operation that does not appear in this table must not be called.
It is important to realize that none of the LOG, RTC, or STS operations has any semantic effect on the execution of an algorithm. In other words, it is possible to implement all of these operations as aliases to a function that simply returns and the operation of the algorithm will be unaffected. Table 3 summarizes the C language run-time support library functions that may be referenced in an embodiment of the present invention.
All algorithms must follow certain programming rules: The most basic software component is the module. In this embodiment, all algorithms are implemented as modules. A module is an implementation of one (or more) interfaces. An interface is simply a collection of related type definitions, functions, constants, and variables. In the C language, a header file typically specifies an interface. It is important to note that not all modules implement algorithms, but all algorithm implementations must be modules. All modules must follow the following conventions:
For example, consider FIG. 3. Suppose a module called FIR, which consists of functions that create and apply finite impulse response filters to a data stream, is created. Interface 301 to this module is declared in the single C header file fir.h. Any application that wants to use the functions provided by implementation 302 in the FIR module must include the header fir.h. Although Interface 301 is declared as a C header file, the module may be implemented entirely in assembly language (or a mix of both C and assembly). Since interfaces may build atop other interfaces, all header files are required to allow for the possibility that they might be included more than once by a framework. The general technique for insuring this behavior for C header files is illustrated in the code in Table 4.
A similar technique should be employed for assembly language headers, as illustrated in Table 5.
Since multiple algorithms and system control code are often integrated into a single executable, the only external identifiers defined by an algorithm implementation (i.e., symbols in the object code) should be those specified by the algorithm application program interface (API) definition. Unfortunately, due to limitations of traditional linkers, an identifier must sometimes have external scope even though it is not part of the e algorithm API. Thus, to avoid namespace collisions, vendor selected names must not conflict. All external identifiers defined by a module's implementation must be prefixed by "<module>—<vendor>—<name>", where <module> is the name of the module (containing only alphanumeric characters), <vendor> is the name of the vendor (containing only alphanumeric characters), and <name> is the unique name of the identifier (containing only alphanumeric characters). For example, for a vendor "TI", TI's implementation of the FIR module must only contain external identifiers of the form FIR—TI—<name>. If there are external identifiers that are common to all implementations, the <vendor> component may be eliminated. For example, if the FIR module interface defined a constant structure that is used by all implementations, its name would have the form FIR—<name>. In addition to the symbols defined by a module, the symbols referenced by all modules must be defined. All undefined references must refer either to the C run-time support library functions or to operations in the DSP operating system modules or to other modules that comply with the rules of this embodiment. All modules must follow the naming conventions summarized in Table 6. Note that the naming conventions only apply to external identifiers. Internal names and existing code need not change unless an identifier is externally visible to a framework.
In addition to these conventions, multi-word identifiers should never use the '—' character to separate the words. To improve readability, use title case; for example, LOG—getBuffer( ) should be used in lieu of LOG—get—buffer ( ). This avoids ambiguity when parsing module and vendor prefixes. Before a module can be used by an application, it must first be "initialized"; i.e., the module's init( ) method must be run. Similarly, when an application terminates, any module that was initialized must be "finalized" i.e., its exit( ) method must be executed. Initialization methods are often used to initialize global data used by the module that, due to the limitations of the C language, cannot be statically initialized. Finalization methods are often used to perform run-time debug assertions. For example, a finalization method might check for objects that were created but never deleted. The finalization method of a non-debug version of a module is often the empty function. Although some modules have no need for initialization or finalization, it is easier for frameworks to assume that all modules have them. Thus, frameworks can easily implement well-defined startup and shutdown sequences, for example. Modules optionally manage instance objects. In this embodiment of the present invention, all algorithm modules manage instance objects. Objects simply encapsulate the persistent state that is manipulated by the other functions (or methods) provided by the module. A module manages only one type of object. Thus, a module that manages objects roughly corresponds to a C++ class that follows a standard naming convention for its configuration parameters, interface header, and all external identifiers. FIG. 4 illustrates this concept. Object 401 is created by function 402 of module 403. Module 403 implements a finite impulse response filter. Many embedded systems are very static in nature. Memory, MIPS (millions of instructions per second), and I/O peripherals are statically partitioned among a fixed set of functions that operate continuously until power is removed. Static systems permit a number of performance optimizations that simply are not possible in dynamic systems. For example, a memory manager is not required in a static system and general data structures, such as linked lists, can often be replaced with much simpler and more efficient structures, such as fixed length arrays. These optimizations obviously reduce the system's code size requirements and, they may have a significant effect on the execution performance of the system. When designing a system that is very cost sensitive, must operate with limited power, and/or has limited MIPS, designers look for portions of the system that can be fixed at design time (i.e., made static). Even if the entire system cannot be static, often certain sub-systems can be fixed at design time. It is important, therefore, that all modules efficiently support static system designs. In practice, this simply means that all functions that are only required for run-time object creation be placed either in separate compilation units or separate COFF (Common Object File Format) output sections that can be manipulated by the linker. Ideally, every function should be in a separate compilation unit so the system integrator can eliminate run-time support that is unnecessary for a static system. An example that illustrates this "design-time" object creation for static systems is presented later in this specification. Modules may optionally support run-time object creation and deletion. In some applications, run-time creation and deletion is a requirement. Without the ability to remove unneeded objects and reuse memory, the physical constraints of the system make it impossible to create rich multi-functions applications. Run-time creation of objects is valuable even in systems that do not support or require run-time deletion of these objects. The precise nature of the objects, the number of objects, and even the type of objects created may be a function of parameters that are only available at run-time. For example, a programmer may want to create a single program that works in a variety of hardware platforms that differ in the amount of memory available and the amount is determinable at run-time. Note that the algorithms conforming to an embodiment of the present invention are modules of a special type, which are referred to as algorithm modules herein. How the algorithm modules support run-time object creation is another embodiment of the present invention described later in this specification. In an ideal world, a module that implements an API can be used in any system that requires the API. As a practical matter, however, every module implementation must make trade-offs among a variety of performance metrics such as program size, data size, MIPS, and a variety of application specific metrics such as recognition accuracy, perceived audio quality, and throughput. Thus, a single implementation of an API is unlikely to make the right set of tradeoffs for all applications. Therefore, any framework that wishes to incorporate modules following the specification herein must support multiple implementations of the same API. In addition, each module has one or more "global configuration" parameters that can be set at design time by the system integrator to adjust the behavior of the module to be optimal for its execution environment. Suppose, for example, that a module that implements digital filters exists. There are several special cases for digital filters that have significant performance differences such as all-pole, all-zero, and pole-zero filters. Moreover, for certain DSP architectures if one assumes that the filter's data buffers are aligned on certain boundaries, the implementation can take advantage of special data addressing modes and significantly reduce the time required to complete the computation. A filter module may include a global configuration parameter that specifies that the system will only use all-zero filters with aligned data. By making this a design-time global configuration parameter, systems that are willing to accept constraints in their use of the API are rewarded by faster operation of the module that implements the API. Modules that have one or more "global" configuration parameters should group them together in a C structure, called XYZ—Config, and declare this structure in the module's header. In addition, the module should declare a static constant structure named XYZ of type XYZ—Config that contain the module's current configuration parameters. FIG. 5 contains the code for a very simple module to illustrate the concept of modules and how they might be implemented in the C language. This module implements a simple FIR filter. As discussed above, the first two operations that must be supported by all modules are the init( ) and exit( ) functions. The init( ) function is called during system startup while the exit( ) function is called during system shutdown. These entry points, shown in code block 501, exist to allow the module to perform any run-time initialization necessary for the module as a whole. More often than not, these functions have nothing to do and are simply empty functions. The create entry point, shown in code block 502, creates and initializes an object; i.e., a C structure. The object encapsulates all the state necessary for the other functions to do their work. All of the other module entry points (functions) are passed a pointer to this object as their first argument. If the functions only reference data that is part of the object (or referenced within the object), the functions will naturally be reentrant. In this example, code block 502 would create and initialize the structure defined in code block 505 using the parameter defined in the same code block. The delete entry point, shown in code block 503, should release any resource held by the object being deleted and should gracefully handle the deletion of partially constructed objects. The delete entry point may be called by the create operation. In this example, the delete operation has nothing to do. Finally, the FIR module must provide a method for filtering a signal. This is accomplished via the apply operation shown in code block 504. In a real FIR module, the filter operation would be implemented in assembly language. However, because the state necessary to compute the algorithm is entirely contained in the object, this algorithm is reentrant. Thus, it is easy to use this module in multi-channel applications or in single channel applications that require more than one FIR filter. Modern component programming models support the ability of a single component to implement more than one interface. This allows a single component to be used concurrently by a variety of different applications. For example, in addition to a component's concrete interface (defined by its header), a component might also support a debug interface that allows debuggers to inquire about the existence and extent of the component's debug capabilities. If all debuggable components implement a common abstract debug interface, debuggers can be written that can uniformly debug arbitrary components. Support for multiple interfaces is generally incorporated into the development environment, the programming language itself, or both. Since this embodiment is intended to only require the C language, the ability of a module to support multiple interfaces is, at best, awkward. However, several significant benefits make this approach worthwhile:
As stated previously, header files define interfaces, and each header file defines a single interface. A module's header file defines a concrete interface. The functions defined in the header uniquely identify a specific (or concrete) implementation within a system. A special type of interface header is used to define abstract interfaces. Abstract interfaces define functions that are implemented by more than one module in a system. An abstract interface header is identical to a normal module interface header except that it declares a structure of function pointers named XYZ—fxns. A module ABC is said to implement an abstract interface XYZ if it declares and initializes a static structure of type XYZ—Fxns named ABC—XYZ. By convention, all abstract interface headers begin with the letter 'i'. This static structure, referred to subsequently as the v-table, is the mechanism through which a framework can manage an instance of the algorithm. As illustrated in FIG. 6, v-table 602 is a table of pointers to the functions implemented by algorithm 603 that framework 601 must use to execute the functions of algorithm 603. In this embodiment of the present invention, all algorithm modules must implement an abstract interface that is described later in this specification. This abstract interface is called IALG. Although all algorithm modules are required to implement the IALG interface, it is important to note that almost all of them must implement a more specific interface as well. They must implement functions specific to the algorithm. For example, a G.729 encoder algorithm module must not only implement IALG; it must also implement an "encode" function that is specific to the algorithm. In this common case, a new interface is defined that "derives from" or "inherits from" the IALG interface. Interface inheritance is implemented by simply defining the new interface's v-table or "Fxns" structure so that its first field is the v-table or "Fxns" structure from which the interface is inherited. Thus, any pointer to the new interface's "Fxns" structure can be treated as a pointer to the inherited interface's "Fxns" structure. In the case of the G.729 encoder implementation, this simply means that the first field of the G729E—Fxns structure is an IALG—Fxns structure. The previous paragraphs have described the structure shared by all modules. Table 7 summarizes the common design elements for a module named XYZ.
Table 8 summarizes the common elements of all modules that manage one or more instance objects.
In this embodiment of the present invention, algorithm modules are modules that implement the abstract interface IALG. IALG defines a framework independent interface for the creation of algorithm instance objects. The algorithm module must declare and initialize a structure of type IALG—Fxns, the structure must have global scope, and its name must be XYZ—IALG where XYZ is the unique module-vendor prefix as defined previously. The IALG interface allows algorithm modules to define their memory resource requirements (also called memory usage requirements) and thereby enable the efficient use of memory by frameworks. The IALG interface defines a "protocol" between the framework and the algorithm module that is used to create an algorithm instance object. This interface is designed to enable the algorithm module to be used by frameworks implementing virtually any execution environment, i.e., preemptive and non-preemptive, static and dynamic systems. Thus, it is important that algorithm modules never use any memory allocation routines (including those provided in the standard C run-time support libraries). The framework must perform all memory allocation in accordance with the inverted memory protocol. Since algorithm module implementations are modules that support object creation, and, all such modules should support design-time object creation, all algorithm modules support both run-time and design-time creation of algorithm objects. To ensure support for design-time object creation, all functions defined by the IALG interface must be independently relocatable. In practice, this means that each function should either be implemented in a separate file or placed in a separate COFF output section. By placing each of these functions in a separate file or output section, a linker can be used to eliminate those methods that are unnecessary for frameworks that do not require run-time object creation. In some cases, it is awkward to place each function in a separate file. Doing so may require making some identifiers globally visible or require significant changes to an existing code base. The C compiler supports a pragma directive that allows specified functions to be placed in distinct COFF sections. This pragma directive may be used in lieu of placing functions in separate files. Since the IALG interface does not define functions that can be used to actually run an algorithm, each abstract algorithm interface must extend or "derive" from the IALG interface. Thus, every algorithm module has considerable flexibility to define the methods that are appropriate for the algorithm. FIG. 7, which is divided into three parts 7A, 7B, and 7C, contains an example of the IALG algorithm instance interface. In an algorithm module implementation, it would be incorporated as a header file by means of the statement #include <ialg.h>. A module implements the IALG interface if it defines and initializes a global structure of type IALG—Fxns as shown in code block 701. For the most part, this means that every function defined in this structure must be implemented and assigned to the appropriate field in this structure. Note that the first field, code line 702, of the IALG—Fxns structure is a Void * pointer. This field must be initialized to a value that uniquely identifies the module implementation. This same value must be used in all interfaces implemented by the module. Since all algorithms must implement the IALG interface, it is sufficient for algorithm modules to set this field to the address of the module's declared IALG—Fxns structure. In some cases, an implementation of IALG does not require any processing for a particular method. Rather than require the module to implement functions that simply return to the caller, the function pointer may be set to NULL. This allows the framework to avoid unnecessarily calling functions that do nothing and avoids the code space overhead of these functions. The functions defined in IALG—Fxns fall into several categories. Removing memory allocation from the algorithm module complicates instance object creation. If an algorithm module is to be reusable in a variety of applications, the framework rather than the algorithm module must make decisions about memory overlays and preemption in accordance with the inverted memory protocol. Thus, it is important to give the framework as much control over memory management as possible. The functions algAlloc( ), algInit( ), and algFree( ) allow the algorithm module to communicate its memory usage requirements to the framework, let the algorithm module initialize the physical memory allocated by the framework, and allow the algorithm module to communicate the memory to be freed when an instance is no longer required, respectively. Note that these operations are not called in time critical sections of an application. Once an algorithm instance object is created, it can be used to process data in real-time. The sub-classes of IALG define other entry points to algorithmic processing supported by algorithm modules. Prior to invoking any of these functions, frameworks are required to activate the instance object via the algActivate( ) function. The algActivate( ) function provides a notification to the algorithm instance that one or more algorithm processing functions is about to be run zero or more times in succession. After the processing methods have been run, the framework calls the algDeactivate( ) function prior to reusing any of the instance's scratch memory. The algActivate( ) and algDeactivate( ) functions give the algorithm instance a chance to initialize and save scratch memory that is outside the main algorithm-processing loop defined by its extensions of the IALG interface. The final two functions defined by the IALG interface are algControl( ) and algMoved( ). The algControl( ) operation provides a standard way to control an algorithm instance and receive status in-formation from the algorithm instance in real-time. The algMoved( ) operation allows the framework to move an algorithm instance to physically different memory. Since the algorithm instance object may contain references to the internal buffer that may be moved by the framework, the framework is required to call the algMoved( ) function whenever the framework moves an object instance. FIG. 8 summarizes the only valid sequences of execution of the IALG—Fxns functions for a particular algorithm instance. For simplicity, the algControl( ) and algNumAlloc( ) operations are not shown. The algControl( ) function may be called at any time after algInit( ) and any time before algFree( ). The algNumAlloc( ) function may be called at any time. As FIG. 8 illustrates, when a framework wants to execute an algorithm, it must first put the algorithm into a "start state". This is accomplished by executing step 801 followed by step 802. In step 801, algAlloc( ) is called to query the algorithm module about its memory requirements. In step 802, algInit( ) is called to initialize the memory requested via the call to algAlloc( ) in step 801. The algorithm instance is now ready to execute. Execution of the algorithm instance is comprised of the four steps 803, 804, 805, and 806. In step 803, the framework calls algActivate( ) to notify the algorithm instance that its memory is "active" and the algorithm instance processing functions in step 804 may be called. The framework may then optionally (the option to skip or execute this step is denoted by arrows 808) execute step 804, calling algMoved( ) to allow the algorithm instance to update any internal references. This is only necessary when the framework has relocated the algorithm's instance object during system execution. Then, the framework may call any of the processing functions in the algorithm instance as denoted by step 805. The framework may execute step 805 repeatedly as necessary to accomplish the purpose of the algorithm. If the framework relocates the algorithm's instance object between executions of step 805, it must repeat step 804 before continuing. Once the framework has finished using the processing functions of step 805, it may proceed to step 806. In step 806, the algDeactivate( ) function is executed to notify the current algorithm instance that it is about to be deactivated. Once step 806 is completed, two options are available to the framework. It can choose to leave the algorithm instance in its "start state" pending reactivation or it can execute step 807. If the framework chooses to leave the algorithm instance in its "start state", it may reactivate the algorithm instance by executing the sequence described above starting with step 803. A framework will only execute step 807 if it wishes to reuse the memory that it has allocated to an algorithm instance. In step 807, the function algFree( ) is called to ask the algorithm instance to identify any memory assigned to it. The framework uses this information to free up that memory for use by other algorithm instances. Once step 807 is executed, the algorithm instance is no longer in its "start state". If the framework wishes to use the algorithm instance again, it must restart the process with step 802. Two other execution paths are available to the framework, as illustrated by arrows 809 and 810. Arrow 809 shows that once step 803 has been completed, the framework may choose to immediately go to step 806. Also, as per arrow 810, once step 802 is complete, the framework may choose to execute step 807. Note that there is no requirement that any of the steps shown in FIG. 8 must be performed in a single uninterrupted execution cycle by the framework. The order of execution shown must be followed but there is no requirement on how much time may elapse or what other things the framework might choose to do between performing the steps for a given algorithm instance. When algorithm instances are created, the framework can pass algorithm-specific parameters to the algAlloc( ) and the algInit( ) methods. To support implementation-specific extensions to standard abstract algorithm interfaces, every algorithm module's parameter structure must begin with the size field defined in the IALG—Params structure shown in FIG. 7B, code block 703. The framework sets this field to the size of the parameter structure (including the size field itself) that is being passed to the algorithm implementation. Thus, the implementation can "know" if the framework is passing just the standard parameter set or an extended parameter set. Conversely, the framework can elect to send just the "standard" parameters an implementation-specific set of parameters. Of course, if a framework uses an implementation-specific set, the framework cannot be used with a different implementation of the same algorithm. The code fragments shown in FIG. 9 illustrate how a framework may use the same style of parameter passing whether passing generic parameters or implementation-specific parameters. The implementation can also easily deal with either set of parameters. The only requirement is that the generic parameters always form a prefix of the implementation-specific parameters; i.e., any implementation-specific parameter structure must always include the standard parameters as its first fields. This same technique is used to extend the algorithm module's status structures. In this case, however, all algorithm status structures start with the IALG—Status fields (see FIG. 7B, code block 704). Modules that implement the IALG interface enable run-time instance creation using the generic create and delete functions shown in FIG. 10, code block 1001 and code block 1002 respectively. To implement the IALG interface, all algorithm objects must be defined with IALG—Obj (see FIG. 7B, code block 705) as their first field. This insures that all pointers to algorithm objects can be treated as pointers to IALG—Obj structures. The framework functions outlined above are just examples of how to use the IALG functions to create simple object create and delete functions. Other frameworks might create objects very differently. For example, one can imagine a framework that creates multiple objects at the same time by first invoking the algAlloc( ) function for all objects, optimally allocating memory for the entire collection of objects, and then completing the initialization of the objects. By considering the memory requirements of all objects prior to allocation, such a framework can more optimally assign memory to the required algorithms. Once an algorithm instance is created, it can be used to process data. However, if the algorithm module defines the algActivate( ) and algDeactivate( ) functions, they must bracket the execution of any of the algorithm modules processing functions. The function shown in FIG. 11 could be used, for example, to execute any implementation of the IFIR interface on a set of buffers. This implementation of FIR—apply( ) assumes that all persistent memory is not shared; thus, it does not restore this data prior to calling algActivate( ) and it does not save this memory after algDeactivate( ). If a framework shares persistent data among algorithm instances, it must insure that this data is properly restored prior to running any processing methods of the algorithm instances. If an algorithm instance's processing functions are always executed as shown in the FIR—apply( ) function in FIG. 11, there is no need for the algActivate( ) and algDeactivate( ) functions. To save the overhead of making two function calls, their functionality would be folded into the processing functions. The purpose of algActivate( ) and algDeactivate( ) is to enable the algorithm's processing functions to be called multiple times between calls to algActivate( ) and algDeactivate( ). This allows the algorithm writer the option of factoring data initialization functions, such as initialization of scratch memory, into the algActivate( ) function. The overhead of this data movement can then be amortized across multiple calls to processing functions. Each of the functions described above is presented in more detail below to further clarify an embodiment of the present invention. The functions are presented in alphabetic order. The descriptions include an example implementation along with detailed descriptions. Note that each of the example implementations assumes the presence of the IALG algorithm interface shown in FIG. 7. The function algActivate( ), an implementation of which is shown in FIG. 12 code segment 1201, initializes any of the instance's scratch buffers using the persistent memory that is part of the algorithm's instance object. The first (and only) argument to algActivate( ) is an algorithm instance handle. This handle is used by the algorithm to identify the various buffers that must be initialized prior to calling any of the algorithm's processing functions. The implementation of algActivate( ) is optional. The algActivate( ) method should only be implemented if a module wants to factor out initialization code that can be executed once prior to processing multiple consecutive frames of data. If a module does not implement this method, the algActivate field in the module's v-table must be set to NULL. This is equivalent to the implementation in Table 9.
The following conditions must be true prior to calling this method; otherwise, its operation is undefined.
The function algAlloc( ), an example implementation of which is shown in FIG. 13, returns a table of memory records that describe the size, alignment, type and memory space of all buffers required by an algorithm instance (including the instance object itself). If successful, this function returns a positive non-zero value indicating the number of records initialized. This function can never initialize more memory records than the number returned by the function algNumAlloc( ). If algNumAlloc( ) is not implemented, the maximum number of initialized memory records is IALG—DEFMEMRECS (see code line 706 in FIG. 7A). The first argument to algAlloc( ) is a pointer to the creation arguments for the instance of the algorithm object to be created. The creation arguments must be in a structure of type IALG—Params (see code block 703 in FIG. 7B). This pointer is algorithm-specific; i.e., it points to a structure that is defined by each particular algorithm. This pointer may be NULL, however. In this case, algAlloc( ), must assume default creation parameters and must not fail. The second argument to algAlloc( ) is an optional output parameter. algAlloc( ) may return a pointer to another set of IALG functions to the framework. This set of IALG functions must be in a structure of type IALG—Fxns (see code block 701 in FIG. 7C). If this output value is set to a non-NULL value, the framework creates an instance object using this set of IALG functions. The resulting instance object must then be passed to algInit( ). algAlloc( ) may be called at any time and it must be idempotent; i.e., it can be called repeatedly without any side effects and always returns the same result. The third argument to algAlloc( ) is an output parameter that is a structure of type IALG—MemRec (see code block 710 in FIG. 7A). This memory table contains the information about the size, type, alignment, etc. of the memory blocks required by the algorithm instance. The following conditions must be true prior to calling this method; otherwise, its operation is undefined.
The following conditions are true immediately after returning from this method.
If the operation succeeds, the return value of this operation is greater than or equal to one. Any other return value indicates that the parameters specified by params are invalid. The function algControl( ), an implementation of which is shown in FIG. 14, sends an algorithm specific command, cmd, and an input/output status buffer pointer to an algorithm module's instance object. The first argument to algControl( ) is an algorithm instance handle. algControl( ) must only be called after a successful call to algInit( ) but may be called prior to algActivate( ). algControl( ) must never be called after a call to algFree( ). The second and third parameters are algorithm (and possible implementation) specific values. Algorithm and implementation-specific cmd values are always less than IALG—SYSCMD (see FIG. 7A code line 711). Upon successful completion of the control operation, algControl( ) returns IALG—EOK (see FIG. 7A code line 712); otherwise it returns IALG—EFAIL (see FIG. 7A code line 713) or an algorithm-specific error return value. In preemptive execution environments, algControl( ) may preempt a module's other functions. The implementation of algControl( ) is optional. If a module does not implement this method, the algControl field in the module's static function table (of type IALG—Fxns) must be set to NULL. This is equivalent to the implementation in Table 10.
The following conditions must be true prior to calling this method; otherwise, its operation is undefined.
The following conditions are true immediately after returning from this method.
The function algDeactivate( ), an implementation of which is shown in FIG. 12 code block 1202, saves any persistent information to non-scratch buffers using the persistent memory that is part of the algorithm's instance object. The first (and only) argument to algDeactivate( ) is an algorithm instance handle. This handle is used by the algorithm instance to identify the various buffers that must be saved prior to the next cycle of algActivate( ) and processing. The implementation of algDeactivate( ) is optional. The algDeactivate( ) function is only implemented if a module wants to factor out initialization code that can be executed once prior to processing multiple consecutive frames of data. If a module does not implement this function, the algDeactivate field in the module's v-table must be set to NULL. This is equivalent to the implementation in Table 11.
The following conditions must be true prior to calling this function; otherwise, its operation is undefined.
The following conditions are true immediately after returning from this method.
The function algFree( ), an example implementation of which is shown in FIG. 15, returns a table of memory records that describe the base address, size, alignment, type, and memory space of all buffers previously allocated for the algorithm instance (including the algorithm instance object) specified by handle. This function always returns a positive non-zero value indicating the number of records initialized. This function can never initialize more memory records than the value returned by algNumAlloc( ). The following conditions must be true prior to calling this function; otherwise, its operation is undefined.
The following conditions are true immediately after returning from this function.
The function algInit( ), an example implementation of which is shown in FIG. 16, performs all initialization necessary to complete the run-time creation of an algorithm's instance object. After a successful return from algInit( ), the algorithm's instance object is ready to be used to process data. The first argument to algInit( ) is an algorithm instance handle. This handle is a pointer to an initialized IALG—Obj structure (see code block 707 in FIG. 7B). Its value is identical to the memTab[0].base. The second argument is a table of memory records of type IALG—MemRec, that describes the base address, size, alignment, type, and memory space of all buffers allocated for an algorithm instance (including the algorithm's instance object). The number of initialized records is identical to the number returned by a prior call to algAlloc( ). The third argument is a handle to another algorithm instance object. This parameter is often NULL indicating that no parent object exists. This parameter allows frameworks to create a shared algorithm instance object and pass it to other algorithm instances. For example, a parent instance object might contain global read-only tables that are used by several instances of a vocoder. The last argument is a pointer to algorithm-specific parameters that are necessary for the creation and initialization of the instance object. This pointer points to the same parameters passed to the algAlloc( ) operation. However, this pointer may be NULL. In this case, algInit( ), must assume default creation parameters. The framework is not required to satisfy the IALG—MemSpace (see code block 708 of FIG. 7A) attribute of the requested memory. Thus, the algorithm instance is, required to properly operate (although much less efficiently) even if it is not given memory in, say on-chip DARAM. The following conditions must be true prior to calling this function; otherwise, its operation is undefined.
The following condition is true immediately after returning from this method: all of the instance's persistent memory is initialized and the object is ready to be used (with the exception of any initialization performed by algActivate( )). The function algMoved( ), an example implementation of which is shown in code block 1701 in FIG. 17, performs any reinitialization necessary to insure that, if an algorithm's instance object has been moved by the framework, all internal data references are recomputed. The arguments to algMoved( ) are identical to the arguments passed to algInit( ). In fact, in many cases an algorithm module may use the same function defined for algInit( ) to implement algMoved( ). However, it is important to realize that algMoved( ) is called in real-time whereas algInit( ) is not. Much of the initialization required in algInit( ) does not need to occur in algMoved( ). The framework is responsible for copying the instance's state to the new location and only internal references need to be recomputed. Although the algorithm's parameters are passed to algMoved( ), with the exception of pointer values, their values must be identical to the parameters passed to algInit( ). The data referenced by any pointers in the params structure must also be identical to the data passed to algInit( ). The locations of the values may change but their values must not. The implementation of algMoved( ) is optional. However, it is highly recommended that this method be implemented. If a module does not implement this method, the algMoved( ) field in the module's v-table must be set to NULL. This is equivalent to asserting that the algorithm's instance objects cannot be moved. The following conditions must be true prior to calling this method; otherwise, its operation is undefined.
The following condition is true immediately after returning from this function. The instance object is functionally identical to the original instance object. It can be used immediately with any of the algorithm's functions. The function algNumAlloc( ), an implementation of which is presented in FIG. 18 code block 1801, returns the maximum number of memory allocation requests that the algAlloc( ) function requires. This operation allows frameworks to allocate sufficient space to call the algAlloc( ) function or fail because insufficient space exists to support the creation of the algorithm's instance object. algNumAlloc( ) may be called at any time and it must be idempotent; i.e., it can be called repeatedly without any side effects and always returns the same result. algNumAlloc( ) is optional; if it is not implemented, the maximum number of memory records for algAlloc( ) is assumed to be IALG—DEFMEMRECS (see FIG. 7A code line 706). This is equivalent to the implementation shown in Table 12.
If a module does not implement this method, the algNumAlloc field in the module's v-table must be set to NULL. The following condition is true immediately after returning from this function. The return value from algNumAlloc( ) is always greater than or equal to one and always equals or exceeds the value returned by algAlloc( ). Trace Control Interface To improve both the system integration efforts as well as enhance in-field diagnostics, all algorithms should implement a uniform trace and diagnostic interface. In an embodiment of the present invention, this capability is implemented as an alternate module interface as defined in subsequent paragraphs. The real-time trace control interface (IRTC) defines an interface that, when implemented, allows a module's various trace modes to be enabled, disabled, and controlled in real time. FIG. 19 shows an implementation of IRTC. It would be included in a module's definition with the following line of code: #include <irtc.h>. A module implements the IRTC interface if it defines and initializes a global structure of type IRTC—Fxns (see FIG. 19 code block 1901). For the most part, this means that every function defined in this structure must be implemented and assigned to the appropriate field in this structure. It is important to note that the first field of the IRTC—Fxns structure is a Void* pointer. This field must be initialized to a value that uniquely identifies the module implementation. This same value must be used in all interfaces implemented by the module. Since all algorithms must implement the IALG interface, it is sufficient for algorithm modules to set this field to the address of the module's declared IALG—Fxns structure. The subsequent paragraphs describe the functions that must be implemented in each module that includes this interface. The function rtcBind( ) sets the module's current output log. For any module that implements the IRTC interface, there is just one output log. All instances use a single output log. The first (and only) argument to rtcBind( ) is pointer to a LOG object. The following conditions must be true prior to calling this function; otherwise, its operation is undefined.
The following condition is true immediately after returning from this function. All subsequent output trace is redirected to the LOG object received as the argument. The code segment in Table 13 is an example implementation of rtcBind.
The function rtcGet( ) returns the current setting of the trace mask for a trace instance object. For any module that implements the IRTC interface, the module instance object is also the trace instance object. The only argument to rtcGet( ) is a trace instance handle. The following condition must be true prior to calling this function; otherwise, its operation is undefined: the argument must be a valid handle for the module's trace instance object. The following condition is true immediately after returning from this method: mask is the current trace mask setting for this instance. The code segment in Table 14 is an example implementation of rtcGet.
The function rtcSet( ) sets a new trace mask for a trace instance object. For any module that implements the IRTC interface, the module instance object is also the trace instance object. The first argument to rtcSet( ) is a trace instance handle and the second argument is the new setting for this instance. The following condition must be true prior to calling this function; otherwise, its operation is undefined: handle must be a valid handle for the module's trace instance object. The following conditions are true immediately after returning from this function.
The code segment in Table 15 is an example implementation of rtcSet.
Packaging Once a module is developed, it must be packaged in a uniform manner to enable delivery into any framework that conforms to the component model described herein. To ensure that a single component can be used in both UNIX and Windows environments, never create two files whose names only differ in case, and always treat file names as being case-sensitive. All of the object code files for a module should be archived into a library with the following name: <module><vers>—<vendor>.a<arch> where
In addition to the object code implementation of the algorithm, each module includes one or more interface headers. To ensure that no name conflicts occur, all header files must conform to the following naming conventions. C language headers should be named as follows:
A single vendor may produce more than one implementation of an algorithm module. For example, a "debug" version may include function parameter checking that incurs undesirable overhead in a "release" version. A vendor may even decide to provide multiple debug or release versions of a single algorithm. Each version may make different tradeoffs between time and space overhead, for example. To easily manage the common case of debug and release versions of the same algorithm, different versions of an algorithm from the same vendor must follow a uniform naming convention. If multiple versions of the same component are provided by a single vendor, the different versions must be in different libraries (as described above) and these libraries must be named as follows:
If there is only one release version of a component from a vendor, there is no need to add a variant suffix to the library name. Suppose, for example, that TI supplies one debug and one release version of the FIR module for a DSP that is referred to as a C62xx architecture. In this case, the library file names would be "fir—ti—debug.a62" and "fir—ti.a62". To avoid having to make changes to source code, only one header file must suffice for all variants supplied by a vendor. Since different algorithm implementations can be interchanged without re-compilation of applications, it should not be necessary to have different "debug" versus "release" definitions in a module's header. However, a vendor may elect to include vendor specific extensions that do require recompilation. In this case, the header must use the symbol —DEBUG to select the appropriate definitions; —DEBUG is defined for debug compilations and only for debug compilations. FIG. 23A is a block diagram of an example algorithm module that includes an IRTC interface. In this example, several instances 2310a-c of a G729 decoder algorithm are interfaced by a common interface. In an embodiment of the present invention, the framework that is integrated with algorithms implemented as described above must incorporate functionality that interfaces with the algorithms following the guidelines set out in the flowchart in FIG. 8. The next few paragraphs provide an example of this embodiment. The example is presented in the form of two modules, ALG and RTC, that conform to the guidelines for modules described above. The ALG module provides for the creation and management of algorithm instances. The RTC module provides for the enabling, disabling, and configuring of the trace modes of any algorithm module. The relationship of these interfaces to the abstract interfaces defined above is illustrated in FIG. 23A. Abstract interfaces 2302 correspond to API modules 2301. API modules 2301 provide conventional functional interfaces to any modules that implement the corresponding abstract interfaces 2302. The ALG module provides a generic interface used to create, delete, and invoke algorithms on data. The functions provided by this module use the IALG interface functions to dynamically create and delete algorithm objects. Any module that implements the IALG interface can be used by ALG. Note that there may actually be several different implementations of the ALG module available in the software development environment. Each implementation would follow a different memory management policy and optimally operate in a specified environment. For example, one implementation may never free memory and should only be used in applications that never need to delete algorithm objects. An implementation of ALG would be incorporated into a framework or test program as follows: #include <alg.h>. The interface to ALG is presented in Table 16.
ALG—activate( ) initializes any scratch buffers and shared persistent memory using the persistent memory that is part of the algorithm's instance object. In preemptive environments, it saves all shared data memory used by this instance to a shadow memory so that it can be restored by ALG-deactivate( ) when this instance is deactivated. The only argument to ALG—activate( ) is an algorithm instance handle. This handle is used by the algorithm instance to identify the various buffers that must be initialized prior to execution of any processing functions. It has no return value. ALG—create( ) implements a memory allocation policy and uses this policy to create an instance of the algorithm module specified in its first argument (of type IALG—Fxns). Its second argument is a pointer (of type IALG—Params) to an algorithm-specific set of instance parameters that are required by the algorithm module to create an instance. ALG—create( ) will return a handle to the instance if it is successful. If it is unsuccessful, it will return NULL. The code fragment in Table 17 shows an example use of ALG—create.
ALG—control( ) sends an algorithm specific command and a pointer to an I/O status buffer. The first argument to ALG—control( ) is an algorithm instance handle. The second two arguments, the command and the pointer to the I/O status buffer, are interpreted in an algorithm-specific manner by the implementation. The return value of ALG—control( ) indicates whether the control operation completed successfully. A return value of IALG—EOK indicates that the operation completed successfully; all other return values indicate failure. The code fragment in Table 18 contains an example of the use of ALG—control( ).
ALG—deactivate( ) saves any persistent information to non-scratch buffers using the persistent memory that is part of the algorithm's instance object. In preemptive environments, ALG—deactivate( ) also restores any data previously saved to shadow memory by ALG—activate( ). The only argument to ALG—deactivate( ) is an algorithm instance handle. This handle is used by the algorithm to identify the various buffers that must be saved prior to the next cycle of ALG—activate( ) and data processing calls. ALG—delete( ) deletes the dynamically created object indicated by its only argument. The handle denoted by this argument should be the return value from a previous call to ALG—create( ). If handle is NULL, ALG—delete( ) simply returns. It has no return value. Table 18 shows an example of the use of ALG—delete( ). ALG—init( ) is called during system startup to perform any run-time initialization necessary for the algorithm module as a whole. It has no parameters and returns nothing. ALG—exit( ) is called during system shutdown to perform any run-time finalization necessary for the algorithm module as a whole. It has no parameter and returns nothing. The RTC module provides a generic interface used to control the trace capabilities of an algorithm instance. The functions provided by this module use the IRTC interface functions to dynamically control the various trace levels supported by algorithm objects. Any module that implements the IRTC and IALG interfaces can be used by RTC. Table 19 contains an example of the RTC interface. This interface would be incorporated in the framework by the directive #include <rtc.h>.
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
