FFT Spectrum Analyser

Signals & Audio

A 32-point radix-2 DIT FFT runs every frame on a Super Nintendo, transforming a synthesised sine tone into 16 frequency bars. The tone sweeps from bin 1 to 15 — the tallest bar follows it. Each of 5 butterfly stages runs 16 complex multiplies: 320 __mulsi3 calls per frame. Compiled with the llvm-mos-based 65816 toolchain (+mos-a16).

loading core…

Self-running — the tone sweeps through 15 frequency bins automatically. (Boot holds while the gate CRC computes.)

Click the screen to focus, then watch. Tab away and it pauses.

What it is

Each frame the demo generates 32 samples of a sine tone, runs the full DIT FFT, computes 16 magnitude bars (Manhattan approximation: |re| + |im|), and repaints the canvas. The canvas clears and redraws every frame; the dirty-tile DMA streams the update across ~4 v-blanks, giving smooth progressive animation.

// DIT butterfly — 4× __mulsi3 per butterfly:
int32_t t_re = ((int32_t)TW_RE[j*w] * xr[bot]
              - (int32_t)TW_IM[j*w] * xi[bot]) >> 8;
int32_t t_im = ((int32_t)TW_RE[j*w] * xi[bot]
              + (int32_t)TW_IM[j*w] * xr[bot]) >> 8;
xr[bot] = xr[top] - t_re;  xi[bot] = xi[top] - t_im;
xr[top] += t_re;            xi[top] += t_im;

Compiler stress-test #25 — FFT butterfly + bit-reversal

A Round 2 demo covering two new corners: the bit-reversal permutation (index shuffling never seen in prior demos) and the butterfly complex multiply — the same __mulsi3 corner as Round 1, but in a structurally different context (staged butterfly vs per-pixel iteration) that exercises different register-allocation pressure.

CornerWhat it exercises
Butterfly twiddle multiply4× __mulsi3 per butterfly: t_re = (TW_RE[j*w] * xr[bot] - TW_IM[j*w] * xi[bot]) >> 8 and the imaginary counterpart. Each 16×16→32-bit product is one __mulsi3 libcall. 5 stages × 16 butterflies × 4 __mulsi3 = 320 multiply calls per FFT frame. The disasm gate confirmed __mulsi3=4 (folded by -Os loop optimisation — the hot multiply is the dominant operation).
Bit-reversal permutationBefore the butterfly passes, samples are rearranged in bit-reversed index order: sample at index 3 (binary 00011) moves to index 24 (binary 11000). The precomputed FFT_BITREV[32] table drives this swap — a pure bit-manipulation corner distinct from all prior demos.
Q8.8 fixed-point complex multiplyTwiddle factors W_32^k = cos(-2πk/32) × 256 + i·sin(-2πk/32) × 256 are stored as int16_t pairs. The butterfly multiplies them by the data words (also int16_t Q8.8), right-shifts 8 to re-normalise. The product range is 256 × 256 = 65536, which fits in int32_t — the multiplication MUST be 32-bit or the result overflows, making this a genuine __mulsi3 workload.
Sweeping tone demonstrationThe signal is a pure sine at frequency bin cur_bin, generated from the SPIRO_SIN_LUT. cur_bin steps from 1 to 15 every ~1 second. The tallest bar should be at cur_bin; neighbouring bars show FFT windowing leakage. Correct butterfly arithmetic → one tall bar; a miscompile → wrong bar heights or the wrong bin is tallest.

Written in C with the llvm-mos 65816 toolchain. Hit Verify fidelity to reproduce the build gate's WRAM assert (gate CRC 0x6D7A) live in this tab. Disasm gate: __mulsi3=4, rep/sep=63. No compiler bug found — FFT butterfly codegen correct across all modes.