Understanding Advanced Processor Features Promotes Efficient Coding
By David Katz, Tomasz Lukasiak and Rick Gentile, Blackfin Applications Group,
Analog Devices, Inc.
Today's digital signal processors (DSPs) have achieved such an attractive mix ofperformance, peripheral mix, power dissipation and pricing that many system designers
are eager to explore their benefits over the processors with which they've traditionallydesigned. One potential hurdle to this is the large amount of legacy C/C++ code they've
developed for their application space. Clearly, these engineers would like to leveragetheir existing high-level code base on a DSP platform while taking advantage of DSP
architectural features that enable performance unattainable on their former platform.
Moreover, they require a familiar, intuitive development environment, as well as astraightforward way to implement assembly language routines selectively for increasedperformance. This article discusses programming strategies and techniques for DSPs intoday’s development environment.
HLL vs Assembly – A Combination May Be BestOne mandatory task when undertaking a DSP-based project is deciding what kind ofprogramming methodology to use. The choice is usually between assembly language
and a high-level language (HLL) like C or C++. The decision revolves around manyfactors, so it’s important to understand the benefits and drawbacks each approachentails.
Benefits of C/C++ include modularity, portability and reusability. Not only do themajority of embedded programmers have experience with one of these high-level
languages, but also there exists a huge code base that can be ported from an existing
microcontroller or DSP domain to a new DSP platform in a relatively straightforwardmanner. Because assembly language is architecture-specific, reuse is typically restricted
to devices in the same processor family. Also, within a development team it is often
desirable to have various teams coding different system modules, and a HLL allows thesecross-functional teams to be processor-agnostic.
Traditional assembly languages have long been maligned for their arcane syntax andstrange acronyms. Today, however, these factors are much less of an issue inarchitectures where so-called “algebraic syntax” is used. Figure 1 shows an example of atypical DSP instruction in the conventional style versus that of the algebraic format. It isclear that the latter’s structure is much more intuitive.

One reason assembly has been difficult to program is its focus on data flow between theDSP’s actual register sets, computational units and memories. In C/C++, thismanipulation typically occurs at a much more abstract level through the use of variables
and function/procedure calls, making the code easier to follow.
Today’s C/C++ compilers are quite resourceful, and many can do an admirable job ofcompiling the HLL code into tight assembly code. In fact, it’s often best to just let thecompiler optimizer do its job. However, the fact remains that compiler performance istuned to a specific set of features that the tool developer considered most important.
Therefore, it cannot exceed handcrafted assembly code performance in all situations.
The bottom line is that developers use assembly language only when it is necessary to
optimize important processing-intensive code blocks for efficient execution on the DSP.
HLL compiler optimization switches can do an admirable job, but nothing beats
thoughtful, direct control of DSP data flow and computation. This is why designers often
use a combination of C/C++ and assembly. The HLL is fine for the control and basic
data manipulation, but the assembly shines for efficient numeric computation.
Architectural Features for Efficient Programming
In order for the assembly programmer to do an effective job, it is imperative tounderstand the types of structures that can differentiate DSPs from processors notoptimized for super-fast number crunching. These features include:.
.
.
.
.
.

• Specialized addressing modes• Hardware Loop Constructs• Cacheable memories• Multiple operations per cycle• Interlocked pipeline• Flexible data register file

These features can make an enormous difference in computational efficiency. Let’sdiscuss each one in turn.
Specialized Addressing Modes
Allowing the processor to access multiple data words in a single cycle requires complete
flexibility in address generation. In addition to the more DSP-centric access sizes along
16 and 32-bit boundaries, byte addressing is required for the most efficient processing.
This is important because some common applications, including many video-basedsystems, operate on 8-bit data. When memory accesses are restricted to a single
boundary, extra cycles may be required for the processor to mask off relevant bits.
Another beneficial addressing capability is “circular buffering”. This feature must be
supported directly by the processor with no special software management overhead.
Circular buffering allows a programmer to define buffers in memory and stride throughthem automatically. Once the buffer is set up, no special software interaction is required
to navigate through the data. The address generator handles non-unity strides and, more
importantly, handles the “wrap-around” feature illustrated in Figure 2. Without thisautomated address generation, the programmer would have to manually keep track of thebuffer, thus wasting valuable processing cycles.

An essential addressing mode for efficient signal processing operations such as the FFTand DCT is bit reversal. Just as the name implies, “bit reversal” involves reversing thebits in a binary address. That is, the least significant bits are swapped in position with themost significant bits. The data ordering required by a radix-2 butterfly is in "bit-reversed"
order, so bit-reversed indices are used to combine FFT stages. It is possible to calculatethese bit-reversed indices in software, but this is very inefficient. An example of bit
reversal address flow is shown in Figure 3.

Hardware Loop Constructs
Looping is a critical feature in communications processing algorithms. There are two key
looping-related features that can improve performance on a wide variety of algorithms.
The first is referred to as a “zero-overhead hardware loop”. As with the addressing
capabilities, the looping constructs are implemented in hardware. Again, while thisfunction could be accomplished in software, the associated overhead would cut into thereal-time processing budget. Zero overhead loops allow programmers to initialize loops
by setting up a count value and defining the loop bounds. The processor will continue to
execute this loop until the count has been reached.
Zero-overhead loops are part of most processors, but “hardware loop buffers” can really
add increased performance in looping constructs. They act as a type of cache forinstructions being executed in the loop. For example, after the first time through a loop,
the instructions can be kept in the loop buffer, eliminating the need to “re-fetch” the same
instructions over and over again each time through the loop. This can produce asignificant savings in cycles by keeping the loop instructions in a buffer where they canbe accessed in a single cycle. This feature requires no additional setup by the programmer
but it is important to know the size of this buffer so that loop sizes can be selected
intelligently.

Cacheable Memories
Typical DSPs usually have a small amount of fast, on-chip memory. Microcontrollers
usually have access to large external memories. A hierarchical memory architecture
combines the best of both approaches, providing several levels of memory with different
performance levels. For applications that require the most determinism, on-chip SRAM
can be accessed in a single core clock cycle. For systems with larger code sizes, large,
higher-latency on-chip and off-chip memory is available.
By itself, this hierarchy is only moderately useful, since today’s high-speed processors
would effectively run at much slower speeds because larger applications would only fit inslower external memory. Additionally, programmers would be forced to manually move
key code in and out of internal SRAM. However, by adding data and instruction caches
into the architecture, external memory becomes much more manageable. The cache
reduces the manual movement of instructions and data into the processor core. This
greatly simplifies the programming model by eliminating the need to worry about
managing the flow of data and instructions into the core.
Figure 4 demonstrates a typical memory configuration where instructions are brought infrom external memory as they are needed. Instruction cache usually operates with some
type of Least Recently Used (LRU) algorithm, insuring that instructions that run more
often get replaced less often. The figure also illustrates that having the ability toconfigure some on-chip data memory as cache and some as SRAM can optimize
performance. DMA controllers can feed the core directly, while data from tables can bebrought in to the data cache as they are needed.

Multiple Operations per Cycle
Processors are often benchmarked by how many millions of instructions they can executeper second (MIPS). However, for modern processors this can be misleading because of
the confusion surrounding what actually constitutes an instruction. For example, multiissue instructions, which were once reserved for use in higher-cost parallel processors,
are now also available in low-cost, fixed-point processors. In addition to performing
multiple ALU/MAC operations each core processor cycle, additional data loads andstores can also be completed in the same cycle. The memory is typically portioned into
sub-banks that can be dual-accessed by the core and optionally by a DMA controller.
Factoring in the hardware-based address calculations described above, it is apparent thata lot can happen in a single cycle.
An example of a multi-operation instruction is shown in Figure 5. As shown, in
addition to 2 separate MAC operations, a data fetch and data store can also be
accomplished in the same processor clock cycle.

Interlocked Pipeline
As processors increase in speed, it is necessary for the processing pipeline to become
deeper in terms of overall stages. This is important to understand because whenassembly programming is required, the pipeline can make programming morechallenging. Some processors, however, have an “interlocked” pipeline. This means thatwhen assembly programming is performed, the programmer does not have to manuallyschedule or keep track of data and instructions moving through the pipe. The processor
automatically handles stalls and bubbles.
Flexible Data Register File
Finally, another complementary feature is a versatile data register set. In traditional
fixed-point DSPs, word sizes are usually fixed. However, there is an advantage to having
data registers that can be treated as either a 32-bit word (e.g., R0) or two 16-bit words(R0.L and R0.H, for the low and high halves, respectively). In a dual-MAC system, thisallows operation on four pieces of 16-bit data in a single cycle.

Code Comparison and Analysis
The architectural framework described above is the foundation of efficient DSP
programming. Many ubiquitous number-crunching algorithms can be executed
extremely fast if the programmer utilizes the full potential of the processor's features.
Below is a selection of a few common algorithms with a description of how they should
be executed on a DSP. Note that, while the code efficiency needs to be examined at theassembly level, modern optimizing DSP compilers are designed to utilize many of thesame rules that are at the disposal of an assembly programmer. For illustration, Blackfin
processor assembly language is used in the examples.
Dot Product The dot product, or scalar product, is an operation useful in measuringorthogonality of two vectors. Most C programmers would be familiar with the following
implementation of a dot product:
short dot(short a[], short b[], int size) { int i; int output = 0;
for(i=0; i<size; i++) {output += (a[i] * b[i]);}return output;

Below is the main portion of the assembly code://P0=loop count, I0 & P1 are address registersA1 = A0 = 0; // A0 &A1 are accumulatorsLSETUP (loop1,loop1) LC0 = P0 ; // Set up hardware loop starting at label loop1:loop1: A1 +=R1.H * R0.H , A0 += R1.L * R0.L || R1 = [ P1 ++ ] || R0 = [ I0 ++ ] ;The following points illustrate DSP architectural features that facilitate this tight coding.
Hardware loop buffers and loop counters eliminate the need for jump instructions at theend of each iteration. Since a dot product is a summation of products, it is implemented
in a loop. Many RISC microcontrollers use a jump instruction at the end of each iterationin order to process the next iteration of the loop. The assembly program shows theLSETUP instruction, which is the only instruction needed to implement a loop.
Multi-issue instructions allow the execution of instructions and two data accesses in thesame cycle. In each iteration, the values a[i] and b[i] must be read, then multiplied, andfinally written back to the running summation in the variable output. On many
microcontroller platforms, this effectively amounts to four instructions. The last line of
the assembly code shows that all of these operations can be executed in one cycle.
Parallel ALU operations allow two 16-bit instructions to be executed simultaneously.
The assembly code shows two accumulator units (A0 and A1) being used in each
iteration. This reduces the number of iterations by 50%, effectively halving theoriginal execution time.FIR The finite impulse response filter is a very common filter structure equivalent to theconvolution operation. A straightforward C implementation looks very similar to the dot
product:// sample the signal into a circular bufferx[cur] = sampling_function();cur = (cur+1)%TAPS; // advance the cur pointer in a circular fashion// perform the multiplyadditiony = 0;for (k=0; k<TAPS; k++){y += h[k] *x[(cur+k)%TAPS];}

The essential part of an FIR kernel written in assembly shows a format similar to that ofthe dotproduct. In fact, the same DSP features were used to deliver maximum performance tothealgorithm’s execution. In this specific example, the samples are stored in the R0 register,while the coefficients are stored in the R1 register.
// P0 holds # of filter taps R0=[I0++] || R1=[I1++]; // set initialvalues for R0 and R1 A1=A0=0; // zero the accumulators LSETUP (loop1, loop1) LC0 = P0; // configure inner
looploop1: A1+=R0.L*R1.L, A0+=R0.H*R1.H || R0 = [I0++] || R1 = [I1++]; // compute

Besides the features described for the dot product, the FIR algorithm shown above
exploits circular buffering.
Circular buffers eliminate the need for explicit modulus arithmetic. In the C codesnippet, the % (modulus) operator provides a mechanism for circular buffering. As
shown in the assembly kernel, this modulus operator does not get translated into an
additional instruction inside the loop. Instead, the Data Address Generator registers I0
and I1 are configured outside the loop to automatically wrap around to the beginningupon hitting the coefficient buffer boundary.
FFT A Fast Fourier Transform is an integral part of many signal-processing algorithms.
One of its peculiarities is that the input vector is in sequential time order, but the output
comes out in bit-reversed order. Most traditional general-purpose processors require theprogrammer to implement a separate routine to unscramble the bit-reversed output. On aDSP platform, bit reversal is designed into the addressing engine.

Bit-reversed addressing eliminates the need for a separate bit-reversing procedure inan FFT implementation. Allowing the hardware to automatically bit-reverse the output
of an FFT algorithm relieves the programmer from writing additional utilities, and thus
improves performance.
In addition to the instruction constructs shown above, some processors also include anadditional set of dedicated instructions to support a wide range of applications. The
purpose of these instructions is to further extend the processing capabilities to algorithms
such as Viterbi, Huffman coding and many other bit manipulation routines.
Clearly, there is much to consider when defining a programming strategy for a DSPbased application. Using C or C++ with a strong compiler/optimizer can produce robust
results much of the time, but handcrafted assembly is often the best way to gain extra
performance out of a processor. However, this effort must be undertaken only aftergaining a thorough understanding of the architectural blocks that promote efficientcoding.