Fast Fourier transform algorithms [1]
compute the discrete Fourier transform of a sampled signal,
translating the original time-domain signal to its frequency-domain representation. The
complexity of computing the DFT is
The DFT transforms a sequence of
Direct computation of the above equation can be feasible for cases where
The best-known FFT algorithms originate from factorings of
The Decimation in Time (DIT) factorization splits the
The following code block is a MATLAB® code example of a “Radix-2” formulation of the Cooley-
Tukey DIT FFT algorithm. The algorithm structure contains three “for-loops.” The outer loop
processes a number
function [fft_o] = fft_cooleytukey_dit(fft_i)
N = numel(fft_i);
Nstage = log2(N);
buff_i = bitrevorder(fft_i);
for stage = Nstage : -1 : 1
Nhalf = N / 2^stage;
twid = exp(-1i*2*pi*[0:Nhalf-1]/(2*Nhalf));
stride = 2^(stage-1);
skip = N/stride;
for s = 0 : stride-1
q = s * skip;
for p = 0 : Nhalf-1
% Note: "in-place" addresses:
idx0_i = q + p + 0;
idx0_o = q + p + 0;
idx1_i = q + p + Nhalf;
idx1_o = q + p + Nhalf;
a = buff_i(1+idx0_i);
b = buff_i(1+idx1_i) * twid(1+p);
buff_i(1+idx0_o) = a + b;
buff_i(1+idx1_o) = a - b;
end
end
end
fft_o = buff_i;
end
The following code block provides a MATLAB code example of a “Radix-2” formulation of the Cooley-Tukey DIF FFT algorithm. The algorithm structure also contains three “for-loops,” as in the DIT case. The main difference is the input and output addresses are reversed for the DIT case. The input is applied naturally, whereas the output arrives in bit-reversed order. Also, the ordering of spans in each butterfly is reversed. This is also reflected in the diagrams shown earlier.
function [fft_o] = fft_cooleytukey_dif(fft_i)
N = numel(fft_i);
Nstage = log2(N);
buff_i = fft_i;
for stage = 1 : Nstage
Nhalf = N / 2^stage;
twid = exp(-1i*2*pi*[0:Nhalf-1]/(2*Nhalf));
stride = 2^(stage-1);
skip = N/stride;
for s = 0 : stride-1
q = s * skip;
for p = 0 : Nhalf-1
% Note: "in-place" addresses:
idx0_i = q + p + 0;
idx0_o = q + p + 0;
idx1_i = q + p + Nhalf;
idx1_o = q + p + Nhalf;
a = buff_i(1+idx0_i);
b = buff_i(1+idx1_i);
buff_i(1+idx0_o) = a + b;
buff_i(1+idx1_o) = (a - b) * twid(1+p);
end
end
end
fft_o = bitrevorder(buff_i);
end
One significant drawback of the Cooley-Tukey FFT factorizations is the need for the bit reversal operations required to establish natural sample order at either the input or output. These bit reversal permutations can be managed cheaply in traditional hardware (FPGA fabric) solutions but are expensive when implementing FFTs on coarse-grained CPU or GPU architectures due to incoherent memory access. Stockham FFT was developed to target vector computers whose specific factorization attempts to achieve maximal vector parallelism. For this reason, the Stockham FFT has undergone broad adoption as the method of choice for graphics processors (GPUs) [4]. This situation applies equally well to the Versal AI Engine and its SIMD vector pipeline architecture.
Unlike the Cooley-Tukey FFT, which implements all computations using the “in-place” approach identified earlier but requires costly bit-reversal operations, the Stockham FFT expends additional storage to achieve the natural ordering of I/O samples.
The following figure shows a signal flow graph for a Stockham Radix-2 DIF 8-point transform. Its structure is like earlier Cooley/Tukey formulations. The number of stages and butterflies is identical. Still, the I/O addressing of each butterfly differs – the computation is “out-of-place” and requires an extra storage buffer. The addressing permutation changes at each stage but resolves itself such that a natural ordering is maintained at both the input and output of the signal flow graph.
Unique variants of the Stockham algorithm for vector computing leverage additional tricks to improve performance, like refactoring the algorithm to replace “power-of-two” memory strides with unit strides in the access of twiddle factors and by precomputing twiddle factors in separate contiguous memory.
The following figure shows a signal flow graph for the Stockham Radix-2 DIT 8-point transform. Once again, its structure is like the earlier one but uses “out-of-place” butterflies with a reversed (as compared to the Stockham DIF version) permutation order to achieve a natural I/O ordering.
In practice, the Stockham FFT is often used using two buffers, X and Y, in a ping-pong fashion. This can be imagined in the context of the previous figures, wherein data is input into buffer X in the first stage, and the butterfly outputs are computed and stored in buffer Y. The output of the second stage is then computed from inputs in Y and stored back into X. Finally, the third stage outputs are computed from inputs in X and stored back into Y.
The following code block provides a MATLAB implementation of the Stockham DIT FFT
algorithm. Like the Cooley/Tukey formulation in FFT Code, the algorithm consists of a triple
nested “for-loop” with the outer loop running over the
function [fft_o] = fft_stockham_dit(fft_i)
N = numel(fft_i);
Nstage = log2(N);
buff_i = fft_i;
buff_t = zeros(size(buff_i));
for stage = Nstage : -1 : 1
Nhalf = N / 2^stage;
twid = exp(-1i*2*pi*[0:Nhalf-1]/(2*Nhalf));
stride = 2^(stage-1);
for p = 0 : Nhalf-1
for q = 0 : stride-1
% Note: address is not "in-place"
idx0_i = q + stride*(2*p+0);
idx1_i = q + stride*(2*p+1);
idx0_o = q + stride*(p+0 );
idx1_o = q + stride*(p+Nhalf);
a = buff_i(1+idx0_i);
b = buff_i(1+idx1_i) * twid(1+p);
buff_t(1+idx0_o) = a + b;
buff_t(1+idx1_o) = a - b;
end
end
buff_i = buff_t;
end
fft_o = buff_i;
end
The following code block provides a MATLAB implementation of the Stockham DIF FFT
algorithm. Like the Cooley/Tukey formulation in FFT Code, the algorithm consists of a triple
nested “for-loop” with the outer loop running over the
function [fft_o] = fft_stockham_dif(fft_i)
N = numel(fft_i);
Nstage = log2(N);
buff_i = fft_i;
buff_t = zeros(size(buff_i));
for stage = 1 : Nstage
Nhalf = N / 2^stage;
twid = exp(-1i*2*pi*[0:Nhalf-1]/(2*Nhalf));
stride = 2^(stage-1);
for p = 0 : Nhalf-1
for q = 0 : stride-1
% Note: address is not "in-place"
idx0_i = q + stride*(p+0 );
idx1_i = q + stride*(p+Nhalf);
idx0_o = q + stride*(2*p+0);
idx1_o = q + stride*(2*p+1);
a = buff_i(1+idx0_i);
b = buff_i(1+idx1_i);
buff_t(1+idx0_o) = a + b;
buff_t(1+idx1_o) = (a - b) * twid(1+p);
end
end
buff_i = buff_t;
end
fft_o = buff_i;
end
© Copyright 2024 Advanced Micro Devices, Inc.
© Copyright 2021 Xilinx Inc.