![]() |
|
ADSP 21xx
Have you found this site useful? Did we save you time? Did we cure your head-ache? Is your hair growing back now? Please make a donation to help with maintenance. |
Objective Real-Time Software on the ADSP21XXProcedures and the ADSP 21XXHigh Level Language CompilersYou may have noticed that we have begun to regularly use, or even reserve certain registers for certain functions. The ADSP 21XX has a large (and somewhat confusing) register file, but one needs to be careful not to get interactions or side effects between unrelated pieces of code. This can easily happen if we would just carry on allocating registers willy nilly. Therefore, some planning is required before you start coding a project. If you want to interface to a high level language compiler, the problem becomes even worse, since you then have to follow the compiler allocations, whether it makes good sense or not. We shall assume that you are not going to use a compiler for real-time code, since there is not a good one available anyway. The compilers that we know of, are all best avoided, since they are both slow and buggy, a bad combination of unwanted features. The reason why the code produced by most compilers is slow, is because they are tuned to the execution of large programs, with big procedures and variable length parameter lists, together with any depth of call nesting. These features are all essential if you are writing a large business application, but they simply do not make sense in a digital signal processing application. For instance, the 21XX DSP manages its own Program Counter (PC) stack. This limits call nesting to a depth of sixteen calls. I have never encountered a call stack crash due to exceeding this limit in a DSP application, but one needs to be aware of it. A typical compiler allows unlimited call nesting, by saving the PC in RAM. It has to jump through a lot of hoops to do that, which wastes processing time. In a typical DSP application, it is seldom required to pass a large number of parameters to a procedure. The data is usually already somewhere in a buffer, put there by DMA action or the serial port. Therefore, all you need to do, is to pass a pointer, or even just the object number. Therefore, the one or two parameters needed, can be passed in the processor registers. Consequently, the only thing the data stack is used for, is to save some registers. Clearly, the fewer registers that need to be saved upon procedure entry and exit, the more speed one gains over a traditional compiler. Register UseSuggested register allocations are shown in example 1 below. Note that it is a good idea to identify registers that are free game, which need not be saved by procedures, as well as the critical registers that may never be altered or at least need to be handled with care.
Expand this table for your own requirements and document it properly in the project to allow other people to modify the code without causing strange side effects. The scratch registers need not be preserved by procedures. The register sets i1 and i4 are used for table lookups. The i7 register set is the stack pointer and should be handled with care. Typically, everything concerning the accumulator and barrel shifter is considered to be scratchable, while the multiplier is not. The aim is to reduce the number of registers that specifically need to be preserved, without hampering the creation of filter algorithms. You have to add declarations for DAG registers used for circular data buffers and the like. These buffers are usually acted upon by interrupt handlers and therefore should join the critical register list. All registers not expressly declared as scratch registers, should be preserved by procedure calls, on the data stack. Data Stack ManagementWhen you execute a procedure call, the Program Counter (PC) is automatically saved on a special on chip stack. This PC stack can grow to a maximum depth of sixteen. If you would nest procedures deeper than that, the context will be lost and your program will not be able to return properly. Interrupts have a special shadow register mechanism and do not consume PC stack space. When a procedure executes, it needs to preserve the registers that it needs to use for calculations and restore them upon procedure return, to prevent clobbering something that the calling party was using. This context save is performed with a data memory stack. The stack has to be managed using one of the DAG register sets. You can run the stack as a push down, pop up stack, or the other way around if you are a pervert - the choice is yours. Example 2 below, shows the stack definition in the System States module (STA), part of the start up code.
Once we have a working stack, we can call a procedure, save and restore the processor context. Procedure CallsThese beasts go by many different names. They used to be known as subroutines, until Professor Nicklaus Wirth did his doctorate and invented the word procedure, as part of the Pascal language. This stayed the voque until the US government sponsored the development of Ada (a kind of concurrent object Pascal), when the word method was born. Take your pick, it is exactly the same thing, only the spelling differs. When the processor executes a procedure call, the program counter is automatically saved on the PC stack. If the procedure doesn't do anything, then this is sufficient to perform a return back to the next instruction in the execution thread. However, most procedures are created with some functionality in mind, which usually requires the use of a handful of registers to execute computations. These registers, need to be saved somewhere before we use them and should be restored before the procedure returns, as a courtesy to the code that called it. The data stack is used to save these registers and can also provide temporary work space to the procedure. Example 3 shows the simple case, where a procedure does not have temporary variables on the stack (Also known as automatic variables. Who exactly invented that confusing name, needs to be smothered in a pile of shredded computer paper.).
The simple stack frame example above, is adequate for the majority of procedures and can be used as shown in example 4. The scratch registers defined in example 1, register allocation, need never be saved or restored.
Note that it would be possible to add the rts to the exit macro. It is just visually good to see an rts statement as proof of the end of the procedure. It also allows us to combine the simple macros with the more complicated case, which requires temporary work space on the stack, as shown in example 5. In this example, we save a copy of the stack pointer, called the frame pointer, in m6. Then we move the stack pointer down, to leave a big gap for local stack based variables. Finally, we save a copy of the old stack pointer, followed by the usual MAC_ENTER macro, to save the common registers. The frame pointer in m6, can be used to access the temporary variables on the stack, possibly by using the table read macros MAC_RD_DM and MAC_WR_DM. Note that if we want to use a DAG register to save another DAG register, we first have to put the register contents somewhere else, since there is no path between DAGs. We do this by moving the i7 register contents through the si register.
The procedure can be constructed the same way as in example 4. Simply use the new macros and provide the number of words you need on the stack for temporary storage space as a parameter. The temporary variables can be saved somewhere below the address in m6. For really fast real-time code, one should avoid parameter passing on the stack. This is the important speed advantage that we want to maintain over a generic compiler. If you need large procedures with limitless numbers of parameters, write your code in C, don't bother with assembler and don't be surprised if it runs slowly. However, the MAC_VAR_ENTER and MAC_VAR_EXIT macros provide almost everything one needs for parameter passing. Procedure Parameter PassingThere are essentially two styles for parameter passing. One is commonly known as Pascal style and the other as C style. (The use of subroutines dates back to Charles Babage's difference engine, constructed in the previous century, I don't know what style he and Dame Ada used, but they surely would not have called it C or Pascal style...) In both cases, parameters are pushed onto the stack before the procedure is called. These parameters can then be accessed using the frame pointer and they will be situated somewhere above the address in m6 if you use the MAC_VAR_ENTER macro to construct the procedure stack frame. The difference lies in the way the stack is cleaned up when the procedure returns. In Pascal, the stack is cleaned up before the return, by the procedure itself, while in C, the stack is cleaned up by the calling party, after the procedure returned. It is pretty much six of one and half a dozen of the other. The difference is that with C style, one can pass a variable number of parameters to a procedure. In real-time code however, everything should always be deterministic; things being hard enough as they are, without the introduction of more uncertainty. Therefore, passing variable length parameter lists is not a bright idea, as it is bound to catch you a few microseconds sooner or later. If you want to use C style, the macros are fine as they are, but you will need to add something after every call, to pop the stack back to where it was before you pushed the parameters. If you want to use Pascal style, you need a new exit macro. The Pascal style exit macro in example 6, cleans up a number of parameters from the stack, before returning to the caller.
Using this macro is clearly less trouble than having to clean the stack with yet another macro after every call to a procedure. It also reduces the risk of forgetting to clean up, which will cause the stack to gradually (that is, almost immediately!) grow down, until it overwrites some important data structure. We would therefore call a procedure with parameters, as shown in example 7.
To be quite honest, I've never used the parameter passing macros for any real application. The ADSP processors have enough registers to enable the use of complex functions, without the need to push parameters onto the stack. The technique would however be required for interfacing to a high level compiler. To do that, one would need to rework all the macros, to conform with the calling convention used by the compiler, which depends upon the artistic freedom of the compiler designers. The temporary stack variable macros discussed in the next section, are however quite useful in real-time code. The Frame PointerSo far, we have refrained from indicating exactly where the parameters are on the stack and how to access them. This is fortunately not difficult to figure out, since the PC is saved on its own stack, so we only need to worry about the things we put on the stack ourselves. The matter is however, complicated by the auto incrementing or decrementing of the stack pointer, which invariably leaves us one position off from where we would expect things to be! In practice, parameter passing should be checked carefully on the simulator, until you are quite sure about what you are doing.
The positions of the parameters can be calculated as follows. We save the stack pointer in m6 the moment we enter the procedure. This is the frame pointer. Remember though that the stack pointer was decremented after the last parameter was pushed. Therefore, the last parameter is at (m6 + 1), the second last at (m6 + 2) etc. The behaviour of the call stack is best explained with an example. See Example 8, Procedure Parameter Passing on the Stack, for a set of macros to push parameters onto the stack and to retrieve them from within a procedure.
The stack pointer is decremented further, to leave a gap for temporary variables, before we save any registers. The first temporary variable can be saved at m6 (since the stack pointer was already decremented before we saved it in m6), the next variable can be saved at (m6 - 1), followed by (m6 - 2) etc. This is explained in Example 9, which contains a set of macros for the manipulation of temporary variables in the stack frame.
|
|
Copyright © 1996-2008, Aerospace Software Ltd., GPL. |