0

IIT(BHU)'s First Silicon Tapeout

A 64-point Radix-2 SDF FFT engine taped out on SKY130 via Tiny Tapeout 10 — the first MPW silicon submission by undergraduates at IIT (BHU) Varanasi.

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

ParameterValue
Transform Size (N)64 points
ArchitectureRadix-2 SDF, Decimation-In-Frequency
Pipeline Stages6 (log₂ 64)
Data Format8-bit signed two's complement
ArithmeticFixed-point with convergent rounding
Pipeline Gain1/64 (arithmetic right-shift per stage)
Output OrderBit-reversed (inherent to Radix-2 DIF)
Clock Target10 MHz (100 ns period)
Process NodeSkyWater SKY130 (130 nm)
Tile Footprint6×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

PortWidthDirectionFunction
ui_in[7:0]8InputSigned real input sample xₙ
uio_in[7:0]8InputUnused — tied low internally
uo_out[7:0]8OutputSigned real FFT output Xₖ (real part)
uio_out[7:0]8OutputSigned imaginary FFT output Xₖ (imag part)
uio_oe[7:0]8OutputOutput-enable mask — hardwired 0xFF
ena1InputOutput gate — must be 1 to propagate data
clk1InputSystem clock
rst_n1InputActive-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  │
  └──────────────────────────────────────────────────────────────────────────┘
  1. Arming — The idle system watches ui_in for the trigger byte 0xAA. On detection, the internal running flag asserts on the next rising edge.
  2. Ingest Phase (Cycles 0–63) — 64 consecutive signed samples are read from ui_in and fed into the SDF pipeline.
  3. Sync Emission (Cycle 63) — A 2-stage slip buffer emits the synchronisation marker 0xFF on both uo_out and uio_out, signalling the host that results follow.
  4. Flush Phase (Cycles 64–127) — The hardware forces the input to 0x00 internally. The 64 FFT bins stream out on uo_out (real) and uio_out (imaginary) simultaneously, in bit-reversed order.
  5. Halt — At cycle 128, running deasserts and the machine awaits the next 0xAA trigger.

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} coefficients

Physical 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, and info.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:

StimulusDescription
DCConstant non-zero value across all 64 samples
Unit impulseSingle 1 at n=0, zeros elsewhere
Single-tone sineFull-cycle sinusoid at bin 1
Single-tone cosineFull-cycle cosinusoid at bin 1
NyquistAlternating +127 / −128
Pseudo-random noiseLFSR-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.