2026-09-10
A ping-pong buffer (also called a double buffer) is two identical memory banks with a single-bit toggle that swaps which one the producer writes and which the consumer reads. While the producer fills buffer A, the consumer drains buffer B. When both finish, the toggle flips: producer moves to B, consumer moves to A. The net effect is that a slow producer and a slow consumer run in parallel, hiding each other's latency behind the toggle.
The alternative — a single shared buffer — forces serialization: the producer must finish writing before the consumer can start reading, and vice versa. Total time is T_produce + T_consume. With ping-pong, total time is max(T_produce, T_consume) after the first fill. That's a 2× throughput improvement when the two rates are balanced, at the cost of 2× memory and one cycle of extra latency for the initial fill.
The hardware is deceptively simple: two SRAM banks, a 1-bit toggle flip-flop, and two 2:1 muxes on the address/data paths. The producer's address bus routes to bank A or B based on the toggle; the consumer's address bus routes to the opposite bank. When both sides assert their done signal, the toggle flips on the next clock edge. The tricky part is the handshake — both sides must complete before the swap, or you'll corrupt data mid-flight.
Real-world example: JPEG decoders in every phone camera. The Huffman decoder writes one 8×8 pixel block into buffer A while the inverse-DCT unit reads the previous block from buffer B. Each block takes ~100 cycles to decode and ~80 cycles to transform. Serialized, that's 180 cycles per block. Ping-ponged, it's 100 cycles — a 44% speedup on real video decode workloads. Video codecs like H.264 extend this to triple buffering (three banks) when the producer and consumer rates fluctuate, so neither ever stalls waiting for the other.
Rule of thumb: Ping-pong pays off when |T_produce − T_consume| / max(T_produce, T_consume) < 0.3. If one side is more than ~3× faster than the other, you're wasting silicon on a buffer that sits idle most of the time — use a FIFO instead, which handles rate mismatch gracefully with a smaller area cost.
The classic bug: forgetting to sync the toggle across clock domains. If the producer and consumer run on different clocks, the toggle must go through a synchronizer (typically Gray-coded, since it's only one bit here — no coding needed, just a two-flop synchronizer). Skip that, and metastability will flip the toggle mid-access, corrupting whichever buffer the consumer thought it owned.
