FFT Accelerator
Overview
The FFT tile wraps a Chisel-generated, fully-parallel, single-precision floating-point FFT pipeline in a hand-written NoC decoder/encoder shim, so it can receive input samples and return transformed output over the MoSAIC NoC. The design has three layers:
graph TD
A["Tile_fft.sv (pass-through wrapper)"] --> B["Tile_fft_int.sv (AXI control + acc + tile_noc)"]
B --> C["acc_fft_sw16.sv (NoC protocol shim, selected width)"]
C --> D["FFT16w16.v (Chisel-generated datapath)"]
Source files:
src/Tile.HDL/fft_tile/Tile_fft.sv— top-level tile wrappersrc/Tile.HDL/fft_tile/Tile_fft_int.sv— internal glue (AXI control + accelerator + NoC switch)src/Tile.HDL/fft_tile/acc_fft_sw{2,4,8,16,32}.sv— NoC protocol shim, one per streaming widthsrc/Tile.HDL/fft_tile/FFT{N}w{W}.v— Chisel-generated pipelined FFT cores (multiple N/W combinations provided)- Testcase:
tools/generate/mosaic_fft.pl - Firmware:
tools/picorv_c/c_fft_acc/send_msg.c
Selecting an FFT Size and Streaming Width
Per src/Tile.HDL/fft_tile/README.md, two independent choices configure the accelerator:
- Streaming width is chosen in
Tile_fft_int.svby picking whichacc_fft_sw{2,4,8,16,32}module is instantiated. - FFT size (N) is chosen inside that
acc_fft_sw*.svfile, by picking which generatedFFT{N}w{W}module it instantiates internally.
Generated cores shipped in the repo (name format FFT{size}w{streaming width}):
| N \ W | 2 | 4 | 8 | 16 | 32 |
|---|---|---|---|---|---|
| 4 | FFT4w2 | FFT4w4 | - | - | - |
| 8 | FFT8w2 | FFT8w4 | FFT8w8 | - | - |
| 16 | FFT16w2 | FFT16w4 | FFT16w8 | FFT16w16 | - |
| 32 | FFT32w2 | FFT32w4 | FFT32w8 | FFT32w16 | FFT32w32 |
The shipped high-width configurations pair N=W (e.g. FFT16w16, FFT32w32), meaning all N samples arrive and transform in parallel in one shot — there’s no time-multiplexed folding at those widths. Lower-width variants (FFT16w2, FFT32w8, etc.) exist for cases where you want a narrower NoC-facing streaming interface than the full transform size.
The FFT core itself (e.g. FFT16w16) also supports inverse FFT via its io_in_inv input, though the current acc_fft_sw*.sv shims tie this permanently to 0 (forward transform only).
acc_fft_sw16 — NoC Protocol Shim (representative)
Each acc_fft_sw* module (all structurally identical, differing only in FFT_streamingwidth and the specific generated core instantiated) implements a NoC packet decoder -> FFT core -> NoC packet encoder pipeline:
sequenceDiagram
participant SW as Software (pico)
participant ACC as acc_fft_sw16
participant FFT as FFT16w16 core
SW->>ACC: qPut(fft_tile, 0x80000000) # enable command (short packet)
loop 16 frames
SW->>ACC: mPutH(header, size_code)
loop 32 words (16 complex samples)
SW->>ACC: mPutD(re, im)
end
ACC->>FFT: io_in_valid pulse (parallel Re/Im buses)
FFT-->>ACC: io_out_valid + 16 complex results
ACC-->>SW: streamed result packet (header + 32 words)
end
Input decode FSM (state_in):
- State 0: waits for a valid beat; distinguishes a short packet (
TDATA[28]==0, e.g. the0x80000000enable command) from a long packet (data payload, requiresin_fft_enalready set). - State 1: captures the second header word; decodes bits
[9:8]to decide return routing (01= forward viaMPUT,10= final send to the host pico viaQPUT, else defaultMPUTback to tile 0); builds the outgoing response header. - States 2-33: shift 32 sequential 32-bit NoC words into a
64*FFT_streamingwidth-bit register (16 complex Re/Im samples for width 16), placing each word into the exact bit range the generated FFT core expects. - State 33: asserts
in_validto fire the FFT core; loops back to state 0 onTLAST. - State 35: handles the short-packet enable command (
0x80000000), togglingin_fft_en.
Output encode FSM (state_out) drains a header FIFO and a result FIFO (both Xilinx xpm_fifo_sync), streaming the transformed samples back out word-by-word and asserting TLAST on the final word.
Local opcode constants match the software-side mq.h macros exactly: QPUT = 3'd3, MPUT = 3'd4.
FFT16w16 — Generated Datapath
The generated core (Chisel/FIRRTL output, ~10,700 lines) exposes a simple valid/ready handshake rather than full AXI-Stream:
module FFT16w16(
input clock,
input reset,
input io_in_inv, // 1 = inverse FFT, 0 = forward
input io_in_ready,
input [31:0] io_in_0_Re, io_in_0_Im, // ... through io_in_15_Re/Im
input io_in_valid,
output io_out_valid,
output [31:0] io_out_0_Re, io_out_0_Im, // ... through io_out_15_Re/Im
output io_out_ready
);
Internally it implements a radix-2, pipelined butterfly network using single-precision IEEE-754 floating-point adders/multipliers, with twiddle-factor ROMs specific to the chosen N. Because N and the streaming width are baked into the generated Verilog (not SystemVerilog parameters), changing FFT size means generating a different FFT{N}w{W}.v file and swapping the instantiated module name inside the corresponding acc_fft_sw*.sv file — it cannot be changed at synthesis/elaboration time within the existing files.
Testcase: mosaic_fft.pl
A 2x2 mesh:
$new_tile{'fft'} = 'Tile_fft';
@tile_array = (['pico', 'loop'],
['loop', 'fft']);
sim_loop = 8000, targeting a u250 board.
Software Example: send_msg.c (tools/picorv_c/c_fft_acc/)
#define N 16
qPut(9, 0x80000000); // enable the FFT accelerator (tile 9 = coordinate (1,1))
for (int j = 0; j < N; j++) {
mPutH(dest_tile, 6); // header: announce a long packet
for (int i = 0; i < 32; i++) {
mPutD(data2, data1); // 32 words = 16 complex (Re, Im) samples
data2 = data2 + 1;
}
}
This enables the accelerator with the magic 0x80000000 value, then streams 16 frames of 16-complex-sample test data (a simple incrementing ramp pattern), matching exactly the 32-word input sequence the decode FSM expects per frame.