64-Point Radix-2 SDF FFT — SKY130 Silicon
The first successful MPW silicon tapeout by undergraduates at IIT (BHU) Varanasi, submitted to Tiny Tapeout 10 on the SkyWater 130 nm process node.
Overview
This project is a fully pipelined hardware implementation of a 64-point Fast Fourier Transform (FFT), fabricated on real silicon through Tiny Tapeout. The design uses a Radix-2 Single Delay Feedback (SDF) Decimation-In-Frequency (DIF) architecture and occupies a 6×2 tile footprint on the shared shuttle die (~167 µm × 108 µm per tile).
The entire RTL-to-GDS flow was executed using open-source EDA tooling: Yosys for synthesis, OpenROAD/LibreLane for place-and-route, and Magic for DRC sign-off against the SKY130 PDK. Simulation was validated with a CocoTB testbench against a NumPy floating-point FFT reference.
Architectural Specifications
| Parameter | Value |
|---|---|
| Transform Size (N) | 64 points |
| Architecture | Radix-2 SDF, Decimation-In-Frequency |
| Pipeline Stages | 6 (log₂ 64) |
| Data Format | 8-bit signed two's complement |
| Arithmetic | Fixed-point with convergent rounding |
| Pipeline Gain | 1/64 (arithmetic right-shift per stage) |
| Output Order | Bit-reversed (inherent to Radix-2 DIF) |
| Clock Target | 10 MHz (100 ns period) |
| Process Node | SkyWater SKY130 (130 nm) |
| Tile Footprint | 6×2 (~1002 µm × 216 µm) |
Each butterfly stage applies an arithmetic right-shift (÷2) to prevent overflow accumulation across the six pipeline stages. The total pipeline gain is therefore 1/64.
Pin Mapping
| Port | Width | Direction | Function |
|---|---|---|---|
ui_in[7:0] | 8 | Input | Signed real input sample xₙ |
uio_in[7:0] | 8 | Input | Unused — tied low internally |
uo_out[7:0] | 8 | Output | Signed real FFT output Xₖ (real part) |
uio_out[7:0] | 8 | Output | Signed imaginary FFT output Xₖ (imag part) |
uio_oe[7:0] | 8 | Output | Output-enable mask — hardwired 0xFF |
ena | 1 | Input | Output gate — must be 1 to propagate data |
clk | 1 | Input | System clock |
rst_n | 1 | Input | Active-low synchronous reset |
Execution Protocol
The pipeline is governed by a 128-cycle state machine that separates sample ingestion from result flushing, preventing cross-frame memory contamination.
┌──────────────────────────────────────────────────────────────────────────┐
│ IDLE → [0xAA detected] → INGEST (64 cy) → FLUSH (64 cy) → IDLE │
└──────────────────────────────────────────────────────────────────────────┘- Arming — The idle system watches
ui_infor the trigger byte0xAA. On detection, the internalrunningflag asserts on the next rising edge. - Ingest Phase (Cycles 0–63) — 64 consecutive signed samples are read from
ui_inand fed into the SDF pipeline. - Sync Emission (Cycle 63) — A 2-stage slip buffer emits the synchronisation marker
0xFFon bothuo_outanduio_out, signalling the host that results follow. - Flush Phase (Cycles 64–127) — The hardware forces the input to
0x00internally. The 64 FFT bins stream out onuo_out(real) anduio_out(imaginary) simultaneously, in bit-reversed order. - Halt — At cycle 128,
runningdeasserts and the machine awaits the next0xAAtrigger.
Module Hierarchy
tt_um_fft_adityaamehra (project.v)
│ 128-cycle state machine, zero-forcing, sync slip buffer
│
└── top.v
│ Iteratively generates 6 sdf_stage instances
│ Master control counter for stage synchronisation
│
├── sdf_stage.v ×6
│ │ Twiddle ROM, delay line, butterfly
│ │ Routes data through delay or butterfly per stage-bit
│ │
│ ├── delay_line.v Shift-register matrix (depths: 32, 16, 8, 4, 2, 1)
│ ├── Butterfly.v Radix-2 DIF arithmetic core
│ │ Computes A+B and (A−B)·W
│ │ 16-bit intermediate registers, +0.5 LSB convergent rounding
│ │ Final 8-bit truncation
│ └── twiddle_rom.v Async LUT — 32 pre-computed W₆₄⁰⁻³¹ phase factors
│ 5-bit address → 8-bit signed {Re, Im} coefficientsPhysical Implementation
The design was hardened using LibreLane (OpenROAD-based) with the following key configuration choices, arrived at through iterative GDS runs:
- Clock period: 100 ns (10 MHz) — consistent across
config.json,constraints.sdc, andinfo.yaml - Tile footprint: 6×2 — selected after synthesis cell-count estimation
- Placement density: 60% — leaving 40% routing headroom for a design of this combinational depth
- Hold slack margin: 0.3 ns post-placement, 0.1 ns post-global-routing
- Post-placement repair: enabled with 30% slew/capacitance margins
- CTS max capacitance: 0.2 pF per buffer node (conservative for SKY130 at 10 MHz)
- Input delays: 20 ns max (accounts for FPGA I/O buffer propagation)
- False paths: explicitly not set on reset/enable — both feed synchronous flip-flops and must be fully timed
DRC sign-off was performed by Magic (the authoritative checker for SKY130). LVS confirmed layout-netlist equivalence. Final STA showed zero setup and hold violations.
CocoTB Testbench Metrics
The test.py suite validates hardware output against an unquantised numpy.fft.fft floating-point reference across six input classes:
| Stimulus | Description |
|---|---|
| DC | Constant non-zero value across all 64 samples |
| Unit impulse | Single 1 at n=0, zeros elsewhere |
| Single-tone sine | Full-cycle sinusoid at bin 1 |
| Single-tone cosine | Full-cycle cosinusoid at bin 1 |
| Nyquist | Alternating +127 / −128 |
| Pseudo-random noise | LFSR-generated sequence |
Verification logic: The bit-reversed hardware output array is extracted, remapped to natural order, and compared point-by-point against the reference.
Tolerance: ≤ 4 LSBs per bin. Typical observed maximum is ~2.0 LSBs, consistent with cascaded truncation across six butterfly stages.
How to Use
Triggering a Transform
# 1. Send trigger byte
send_byte(0xAA)
# 2. Stream 64 signed 8-bit samples (back-to-back, no gaps)
for sample in input_frame:
send_byte(sample & 0xFF)
# 3. Wait for sync marker (0xFF on both uo_out and uio_out) — arrives at cycle 63
# 4. Read 64 output pairs {real, imag} — bit-reversed FFT bins
results = []
for _ in range(64):
real = read_byte(uo_out)
imag = read_byte(uio_out)
results.append(complex(real, imag))
# 5. Reorder from bit-reversed to natural frequency order
output = bit_reverse_reorder(results)Reset Sequence
Assert rst_n = 0 for at least one clock cycle before normal operation. Hold ena = 1 throughout. The reset is synchronous — apply it on a rising clock edge.
External Hardware
No hardware beyond the standard Tiny Tapeout demo board is required to exercise the chip. An FPGA or microcontroller driving the input pins at 10 MHz can be used to feed real signal data and capture FFT output in real time.