18
\$\begingroup\$

I m working on a DSP project(IIR filtering) on an Analog Devices digital signal processor(BF706) with the compiler suite coming with it, CrossCore Studio. It has some examples for simple DSP stuff like FIR and IIR filters and library functions for it. The processor manual describes the assembly instruction set and doesn't comment on C.

MY question arises from this particular application, but I thought there is a best practice that DSP developers follow. So I will frame it in a general way:

What I have realized by the examples coming with this DSP, is that if I want to use the circuits designed for DSP applications I need to program in assembly to directly run those instructions.(like multiply and add, etc. ) My question is if I just program in C, wouldn't the compiler(which also comes from the DSP chip company) optimize it for that DSP and use its capabilities? Or do I really need to write DSP routines directly in assembly?

\$\endgroup\$

closed as too broad by Ale..chenski, jonk, laptop2d, PeterJ, Daniel Grillo Aug 3 '17 at 12:18

Please edit the question to limit it to a specific problem with enough detail to identify an adequate answer. Avoid asking multiple distinct questions at once. See the How to Ask page for help clarifying this question. If this question can be reworded to fit the rules in the help center, please edit the question.

  • 17
    \$\begingroup\$ I spent many years writing assembly for the ADSP-21xx (and assembly and C for the Blackfin, later.) You don't disclose what you are using, so any answer will be more a guess and opinion than anything else. But AD's DSP processors are darned good stuff and it is very hard for C compiler writers to properly fill the pipe, so to speak. I have two decades experience in this area (including some very modest experience writing a C compiler) and up until the time I stopped writing code (a few years ago) the C compilers could not come close to hand-coding. But what you do depends on your goals. \$\endgroup\$ – jonk Aug 1 '17 at 4:42
  • 1
    \$\begingroup\$ @jonk hope you're going to write an answer to this one - I only ever did one hardcore DSP Blackfin project, but I have fond memories of some of the performance hacks it needed :) \$\endgroup\$ – pericynthion Aug 1 '17 at 4:51
  • 6
    \$\begingroup\$ @pericynthion No, I can't imagine writing an answer to it unless the OP talks a LOT more about the particular DSP and the project goals. Otherwise, it would be vague, unguided opinions that could be very right or very wrong depending on what the OP then wrote about it. So I'll just wait. \$\endgroup\$ – jonk Aug 1 '17 at 4:55
  • 1
    \$\begingroup\$ If you want it to run the fastest, you hand optimize it in assembly. That is a time\money tradeoff. If you know how to write good C you can get most of the way there. \$\endgroup\$ – laptop2d Aug 1 '17 at 5:33
  • 2
    \$\begingroup\$ I'm not sure about DSP but for most microprocessors you can use intrinsics which is halfway between writing assembler and C code. \$\endgroup\$ – Maciej Piechotka Aug 1 '17 at 7:43
20
\$\begingroup\$

It's always better to have your algorithm implemented in a higher-level language (which C is compared to assembly), even if you plan to implement everything in assembly in the end.

  • chances are, you won't even need assembly. If the code generated by your compiler meets your design goals, your job is done.

  • if not, you won't be starting your assembly coding from scratch. Let the compiler generate the initial code for you, and use that as a base for your optimized assembly version.

  • later, when you'll need to test your optimized assembly code, you'll be glad to have the C version. Instead of manually calculating the correct output for your test input data, you can just feed that input data to your unoptimized C implementation, then check that the assembly produces exactly the same output after the optimizations you have made.

If, after a few years a new developer will need to make modifications to your algorithm and all they have at hand is a highly optimized assembly code, there's a high chance they'll have to start from scratch.

\$\endgroup\$
23
\$\begingroup\$

If the compiler writers put some effort into optimizing it for that target, it will at least make some use of the special DSP instructions / architecture. But for ultimate performance it will never be as good as hand-tuned assembly. It might be plenty good enough, though - depends on your application.

Other alternatives include:

  1. Write the majority of your program in C, and just the most critical numerical portion in assembly.
  2. Write the program in C and use libraries supplied by the manufacturer or third parties - if you're doing common DSP tasks such as FFTs, FIR / IIR filters etc somebody has probably already written the hand-tuned machine code to do it, so you can use that (you may have to pay for it) and link it to your application.
\$\endgroup\$
  • \$\begingroup\$ Usually, the DSP vendors will supply source code for the common functions. If their code is "good enough", you can drop it right in. If it isn't quite right, you have to tweak it. I had to do an FFT layer some years ago, to get a frequency-only real FFT. There's a trick that lets you do a 2N-point real FFT as an N-point complex FFT, but then you have to do a final pass over the complex output to recover the real frequency data. Analog Devices didn't have that particular case in their example code. \$\endgroup\$ – John R. Strohm Aug 2 '17 at 19:30
21
\$\begingroup\$

Premature optimization is the root of all evil. - Donald Knuth

When you find that you don't get enough performance from your code, profile your program first, find the bottlenecks, analyze your performance requirements, and only then start doing optimizations. Writing assembly code is last resort.

My question is if I just program in C, wouldn't the compiler(which also comes from the DSP chip company) optimize it for that DSP and use its capabilities?

Yes, C compiler can do a fair amount of optimization. But this depends on the quality of the compiler. Frequently, a human can write faster assembly code than the compiled C code. At great expense of human pain and suffering, that is.

Or do I really need to write DSP routines directly in assembly?

First write in C, then profile, then decide if you need to write in assembly. Hopefully, you would not need the assembly.

\$\endgroup\$
  • 20
    \$\begingroup\$ In general programming this is certainly good advice, but DSP is a little different - if the OP truly wants to make efficient use of a DSP there will probably need to be some handwritten code somewhere along the line. And in fact with DSP projects sometimes you even want to start off by writing that core numerical kernel, to validate that the processor is going to be suitable for the task at hand. \$\endgroup\$ – pericynthion Aug 1 '17 at 4:45
  • 11
    \$\begingroup\$ Your concluding statement is good general advice. But it's kind of pale when considering the specific details of the AD DSP ALUs. I don't suppose you've ever examined them. \$\endgroup\$ – jonk Aug 1 '17 at 4:45
18
\$\begingroup\$

Your DSP will be advertised with a maximum sustained MACs, assuming all the pipes are filled. That is obviously an upper limit to what can be achieved. You know how many MACs your filters and other processing will take, from your analysis. Aim to have the first at least twice the second, as you will not be able to keep the DSP core running at maximum. Just as you would not try to fill an FPGA above 70% resource (PAR gets very slow above that), development could get very slow trying to squeeze the last few theoretical MACs out of a DSP.

You will code your whole application in C. It's impractical to write all the extra, necessary stuff in assembler, test injection and visibility, housekeeping etc. Write a C version of the test filter. Write an assembler version of the same filter, to verify you can in fact write assembler for this beast.

Now do some timings. Use an RTOS approved by the supplier. Compare the run time of your test assembler module to a C version. If they're within a few percent, move on. If it's triple, then read the documentation, quiz the vendor, and find out why the compiler isn't tuning it. You may need to learn to write its flavour of C as much as to set the correct compiler flags, it will be quicker to find out how to drive the compiler properly than to rewrite everything in assembler.

You've done all this before committing to a DSP, to a tool chain.

Once you have a toolchain you can work with, a compiler you can tune to get reasonably close to maximum, a DSP with some timing headroom left, then you can be reasonably confident that very few parts of your code suite will need to be put into assembler to finish the job.

\$\endgroup\$
7
\$\begingroup\$

Even though I answered this question already, I will add another answer to illustrate a different viewpoint:

Write in C, read in assembly!

So, instead of writing in assembly, you will write the logic in C, carefully making sure that the assembler output of the C code is optimal. You can often do certain tricks on the C code to affect the assembler output. Use static inline functions when it makes sense. If you need to use some special instructions the DSP supports, make a static inline function abstraction of the special instruction, and call the special instruction using the abstraction.

Although I have to say I have never programmed DSPs, this approach of writing the C code while carefully observing the compiled assembly has worked for me on x86 machines extremely well. So well, in fact, that I have never had to write anything in assembly to obtain the best possible performance. I will instead of optimizing the assembly code modify the C code in such manner that the assembly is optimal.

Of course, this depends on good C compilers being available. For x86 such compilers are available (you often have to specify a higher optimization level than the default). For DSPs, I frankly don't know if the compilers are as good.

The benefit of this approach is that you have a single portable codebase, optimized to result in optimal assembly for a given DSP, but it works also if the DSP is changed to something else. Of course you may have to slightly adjust the C code to obtain best possible performance on the new DSP.

\$\endgroup\$
  • \$\begingroup\$ I have a question about this : I work on STM32F4 Cortex-M4 processors and I use the CMSIS/Cube libraries. I also use the compiler's -O3 flag, because it proved way for efficient than anything I could produce. The problem is that the compiled assembly is always way too chaotic for proper analysis. Do you always compile without compiler optimisation? Or do you manage to understant the assembly eve, if it's all over the place? \$\endgroup\$ – Florent Aug 2 '17 at 5:59
  • 2
    \$\begingroup\$ @FlorentEcochard: If the compiler's assembler can not be understood by a programmer, probably it is better than the assembler this programmer can write. As direct answer to your question: use maximum optimization and manual analysis of the assembler, difficult parts could be educative. \$\endgroup\$ – pasaba por aqui Aug 2 '17 at 9:37
4
\$\begingroup\$

In general, it is not necessary to write assembler sources if:

  • you optimize C in the critical sections: a good usage of "register" keyword, inline functions, ...
  • could be some functions of the C program using asm blocks

That means review manually the assembler generated by the C compiler (for the critical parts) and modify the source until enough level of optimization.

\$\endgroup\$
  • \$\begingroup\$ Virtually all modern compilers ignore the "register" keyword, regardless of platform. Using it is very unlikely to result in better code. \$\endgroup\$ – Kef Schecter Aug 2 '17 at 4:59
  • \$\begingroup\$ @KefSchecter: not only they take into account the register hint, nowadays they even allows to select the register to be used: gcc.gnu.org/onlinedocs/gcc-6.1.0/gcc/… \$\endgroup\$ – pasaba por aqui Aug 2 '17 at 9:14
  • 1
    \$\begingroup\$ @KefSchecter : except for compilers written for embedded devices, where it is a very important keyword if you are programming on bare metal. \$\endgroup\$ – vsz Aug 2 '17 at 9:17
  • \$\begingroup\$ @pasabaporaqui: I forgot about that bit of syntax. But if you don't specify a register name -- in other words, if you use it in the ISO standard way -- I'll bet GCC will ignore it. \$\endgroup\$ – Kef Schecter Aug 2 '17 at 18:03
3
\$\begingroup\$

I would say here that if you do FIR / IIR filters, it is far more important which algorithm you use (the trivial algorithm versus fast Fourier transform (FFT)) than which language you use (C versus assembly).

Would I write FFT in assembly? Probably not.

Would I write FFT myself? The answer to this is also probably not, as FFT has been implemented many times already. So chances are you will find some library that has FFT already implemented. Considering that C is a portable language whereas assembly is not, you will be far more likely to find existing libraries already implemented in C.

If you want the most extreme possible performance, you could obviously hand-tune a FFT algorithm to work as quickly as possible in assembly language. But I don't really believe it makes sense to do that except in very exceptional circumstances.

\$\endgroup\$
2
\$\begingroup\$

My own view FWIW is that any time you want maximum speed/efficiency/throughput/whatever, assembler is your friend, so long as you are proficient. A compiler is dumb; it "knows" only what its author thought to program into it, and its author did not know your application at all.

I must admit, I have loved assembler since early 80s 8 bit micros (not dissimilar at all to modern MCUs in many respects) where learning "machine code" was a prerequisite for getting any useful performance out of them, but I think its role remains as the way to program for maximum efficiency. Plus, it is highly rewarding as you can throw in all kinds of optimising shortcuts that a compiler won't think of, because a compiler can't think at all.

C is okay I guess. But if you really know what you want your machine to do at the hardware level, go assembler.

\$\endgroup\$

Not the answer you're looking for? Browse other questions tagged or ask your own question.