I've been investigating the performance of the arm q31 fixed point FFT CMSIS routines, in particular measuring
the performance of taking a waveform, pushing it through the FFT and then the relevant inverse FFT and comparing
to the original signal. This is clearly of interest for fast convolution code for two reasons - some processors have
slower floating point than fixed point, and 32 bit fixed point promises a better signal/noise ratio than single floats
for the same memory footprint.
It turns out that the arm routines for both forward and inverse FFT divide by N (where N is the size of the FFT).
For the forward direction this is understandable, in order to prevent overflow in intermediate and final results
for fixed point - this allows an arbitrary input waveform in the range -1 to +1 (Q31 is fixed point 1.31 format),
yet yields spectral components with a maximum magnitude of 1.
However for the inverse mapping this is usually a mistake, and it throws away P bits of precision where N=2^P.
And in particular IFFT(FFT(x)) = x/N
Normally standard semantics have IFFT(FFT(x)) = Nx, and you have to chose when to divide out the N, but
the q31 routines divide by N in both directions without giving you the option to prevent it.
For spectral analysis you'd divide by N in the forward direction. For any mapping like convolution where you
apply the inverse FFT to a spectrum it doesn't really matter when you divide by N, so long as you do it once!
So here's a quick summary of the performance of doing FFT->IFFT for the arm_rfft_q31, arm_cfft_radix2_q31,
arm_cfft_radix4_q31, together with my FFT coded to do divide-by-N only in the forward direction, and some
hybrid schemes using my invertse FFT after one of the arm forward FFTs:
The solid lines represent a random signal, the dashed lines a single sinusoid, and the dash-dot lines are a two-tone
sinusoidal signal. The variation between them is interesting, I've not thought about why, but it gives some idea of
the variation in the data.
The two magenta lines represent the SNR of 24 and 16 bits. The 6dB drop as the FFT size doubles clearly shows
for the arm routines, together with a smaller rate of drop for my routine and the hybrid cases. Also of note is the
oscillating behaviour of the SNR for the rfft_q31 version - the real fft as I understand it uses the standard trick
of packing 2N samples into N complex numbers allowing the actual FFT to be of half the size it would otherwise be,
I suspect its choosing the radix4 fft when it can (faster), the radix2 otherwise, leading to the oscillating SNR performance.
A conclusion is that despite the 32 bit fixed point representation the q31 CMSIS routines aren't giving much/any better
precision than the single float routines for this style of FFT -> IFFT processing, and that this is fixable by using an
implementation of inverse FFT which doesn't throw away bits.
Another conclusion is that the situation for the q15 inverse FFT is _much_ worse, and its basically unfit for most use-cases,
as for instance a 1024 point q15 inverse FFT is effectively somewhat less than 6 bit precision!
I am thinking of creating a little FFT library to provide better fixed-point FFT support to address these issues.
If anyone wants a preview of the code let me know...