Circular Buffer
Understanding Circular Buffer
Real-time RF signal processing requires continuous data flow between hardware (ADC, DAC, DMA) and software (DSP algorithms, demodulators, decoders). Standard dynamic arrays and linked lists are unsuitable because memory allocation and deallocation introduce unpredictable latency (microseconds to milliseconds for malloc/free), which at sample rates of 10 to 100 Msps would cause buffer overruns and lost samples. The circular buffer solves this by pre-allocating a fixed memory block at initialization and using modular arithmetic on read/write indices to cycle through it indefinitely.
The implementation uses two pointers: a write (head) pointer where new samples are inserted, and a read (tail) pointer where samples are consumed. The available data is the difference (write - read) mod N, where N is the buffer size. For single-producer single-consumer (SPSC) scenarios common in RF pipelines, the buffer is inherently lock-free because the producer only modifies the write pointer and the consumer only modifies the read pointer. This guarantees deterministic sub-microsecond latency. Power-of-two buffer sizes (256, 1024, 4096, ...) are preferred because the modulo operation reduces to a bitwise AND (index & (N-1)), saving 10 to 30 CPU cycles per access compared to integer division. In FPGA-based RF systems, circular buffers are implemented as dual-port block RAM with binary counters, achieving single-clock-cycle read/write at ADC sampling rates up to 6.4 Gsps.
Circular Buffer Parameters
count = (write_ptr - read_ptr) mod N
Available Space:
free = N - 1 - count [one slot reserved to distinguish full from empty]
Fast Modulo (power-of-two N):
index = ptr & (N - 1) [bitwise AND, 1 clock cycle]
Where N = buffer size (power of two), ptr = read or write pointer. Required buffer size: N ≥ Tmax · fs where Tmax = max processing latency, fs = sample rate. For 100 Msps with 10 ms max latency: N = 1M samples.
Circular Buffer Sizing for RF Applications
| Application | Sample Rate | Max Latency | Buffer Size | Memory |
|---|---|---|---|---|
| SDR USB streaming | 2.4 Msps | 50 ms | 128K samples | 512 KB (I/Q 16-bit) |
| 5G NR baseband | 122.88 Msps | 1 ms | 128K samples | 512 KB |
| Radar pulse buffer | 500 Msps | 100 μs | 64K samples | 256 KB |
| Spectrum analyzer | 1 Gsps | 10 ms | 16M samples | 64 MB |
| FIR delay line (256 taps) | Any | N/A | 256 samples | 1 KB |
Frequently Asked Questions
How is a circular buffer used in SDR?
The ADC/DMA produces a continuous I/Q sample stream; a circular buffer sits between hardware and DSP threads. The DMA writes blocks at the hardware rate while the DSP reads at its processing rate. GNU Radio uses circular buffers (4K to 64K samples) between every connected block. Buffer size must accommodate max processing jitter to prevent data loss.
Why use power-of-two buffer sizes?
The modulo wrap operation becomes a bitwise AND (1 cycle vs 10 to 30 for division). At 100 Msps, this saves millions of cycles per second. Power-of-two sizes also align with FFT lengths, DMA transfer sizes, and cache lines, reducing pipeline stalls and memory access overhead.
What is a lock-free circular buffer?
With single producer and single consumer, atomic pointer updates replace mutexes. The producer only writes the write pointer (after data), the consumer only writes the read pointer (after reading). Each reads the other's pointer atomically. This eliminates lock latency and priority inversion, achieving consistent sub-microsecond latency critical for real-time RF processing.