The Butterfly and Bit Reversal
August 25, 20266 min readbeginner
Direct evaluation costs n^2. This note replaces it with a network of n2_2 n small operations, and runs the whole network by hand for n = 4.
Direct evaluation costs . This note replaces it with a network of small operations, and runs the whole network by hand for .
01.The butterfly
The recursive split from Evaluation and Interpolation bottoms out in one repeated operation. Two values go in, a constant multiplies one of them, and two values come out.
Given two coefficients and and a twiddle factor , the Cooley-Tukey butterfly computes
The name comes from the shape of the data-flow diagram: two lines in, two lines out, crossing in the middle.
One multiplication, one addition, one subtraction. That is the whole primitive, and it is what a hardware NTT engine is built out of. Everything else in an accelerator exists to feed butterflies with the right operands.
02.Counting them
The transform has levels, since each level halves the problem. Each level performs butterflies, since each butterfly consumes two of the values.
So the total is
butterflies, each with one multiplication.
At : butterflies. At : .
Compare with the of direct evaluation: at , and at . The first pair is a tie. The second is a factor of sixty-four.
The whole network for
Run it on the running example, , , , which The Negacyclic NTT transformed by direct evaluation to .
The twiddle factors are powers of , stored in a small table in bit-reversed order. For that table is
and where that ordering comes from is the last section of this note.
Level 1. Two butterflies, both with twiddle , pairing entries two apart.
First, and :
Second, and :
After level 1 the array holds .
Level 2. Two butterflies with different twiddles, now pairing adjacent entries.
First, and with :
Second, and with :
After level 2 the array holds
Four butterflies, four multiplications, done.
04.Where the output ordering went
Compare that with the direct evaluation result:
The two middle entries are swapped.
This is not an error. The fast algorithm produces its output in bit-reversed order, and the swap is exactly that.
Write each index in binary using bits, and reverse the bits:
| index | binary | reversed | value |
|---|---|---|---|
| 0 | 00 | 00 = 0 | 0 |
| 1 | 01 | 10 = 2 | 2 |
| 2 | 10 | 01 = 1 | 1 |
| 3 | 11 | 11 = 3 | 3 |
So position of the fast output holds what direct evaluation put at position , and vice versa. Positions and are fixed points and do not move. That is precisely the observed swap.
The reason is structural. The recursion repeatedly separates even-indexed from odd-indexed coefficients. After rounds of that separation, an element's final position is determined by its index bits read in the opposite order.
05.Why nobody fixes it
The natural instinct is to add a reordering pass and restore the natural order. Implementations deliberately do not.
The reason is that the ordering cancels. Pointwise multiplication does not care what order the values are in, as long as both operands use the same order, since it multiplies position by position. And the inverse transform can be written to consume bit-reversed input and produce natural-order output.
So the standard arrangement is a forward transform that takes natural order to bit-reversed order, pointwise multiplication in bit-reversed order, and an inverse transform that takes bit-reversed order back to natural. The permutation appears and disappears without a single element ever being moved.
That saves a full pass over memory in each direction, which on a memory-bound workload is a real saving rather than a micro-optimisation. It is also why reading NTT code for the first time is confusing: the arrays are permuted for most of the computation and nothing in the code says so.
06.In place
One more property, which matters most to hardware.
Look again at the butterfly. It reads and , and writes and . Once both outputs are computed, the inputs are never needed again, so the outputs can be written back over them.
The whole transform therefore runs in place, using the original array and a couple of temporaries. No second buffer of size is needed at any point.
For a hardware accelerator working with limited on-chip memory, that is close to decisive. A length- transform over -bit coefficients needs a single -bit memory, and the datapath is one butterfly unit reading two values, multiplying by a twiddle from a small ROM, and writing two values back. Everything else in the design is control logic deciding which addresses to read and which twiddle to use.
The next note gives the actual roots and dimensions that logic is built around.