Modulation and bit error rate cheat sheet
Bits per symbol, AWGN and Rayleigh BER formulas for BPSK, QPSK and square QAM, the Eb/N0 each needs for a target BER, and a simulation that checks the formulas.
This sheet is for anyone sizing a link or checking a modem against theory. It gives the uncoded bit error rate (BER) formulas you will use most, the Eb/N0 they need, and a short simulation that confirms them. The background is in the digital modulation lesson and the fading channels lesson.
Notation
| Symbol | Meaning |
|---|---|
| M | Constellation size |
| k = log2 M | Bits per symbol |
| Es/N0 | Symbol energy over noise density. Equal to SNR when the noise bandwidth equals the symbol rate |
| Eb/N0 | Bit energy over noise density, Es/N0 = k Eb/N0, or in dB, Es/N0 = Eb/N0 + 10 log10 k |
| Q(x) | Gaussian tail probability, Q(x) = 0.5 erfc(x / √2) |
The schemes
| Scheme | Bits per symbol | Where you meet it |
|---|---|---|
| BPSK | 1 | Low-rate links that need the most margin per bit |
| π/2-BPSK | 1 | NR PUSCH with transform precoding enabled (TS 38.211) |
| QPSK | 2 | The lowest-rate entries of the NR CQI tables in TS 38.214, many satellite links |
| 16-QAM | 4 | Mid-range LTE, NR and Wi-Fi rates |
| 64-QAM | 6 | Good channels |
| 256-QAM | 8 | Short, clean links. In the NR CQI Table 2 of TS 38.214 |
| 1024-QAM | 10 | NR FR1 downlink from Release 17, per the TS 38.214 change history |
3GPP TS 38.211 (NR physical channels and modulation) defines the bit-to-symbol mapping for π/2-BPSK, BPSK, QPSK and 16, 64, 256 and 1024 QAM, each scaled to unit average energy. See the 3GPP spec map for where it sits.
BER over AWGN
Gray coding is assumed throughout, so a symbol error to a neighbour costs one bit.
| Scheme | BER in terms of Eb/N0 | Status |
|---|---|---|
| BPSK | Q(√(2 Eb/N0)) | Exact |
| QPSK | Q(√(2 Eb/N0)) | Exact. I and Q are two independent BPSK channels |
| Square M-QAM | (4/k)(1 − 1/√M) Q(√(3k Eb/N0 / (M − 1))) | Nearest-neighbour approximation. Exact for M = 4, tight below BER 10⁻² |
| 16-QAM | (3/4) Q(√(4 Eb/N0 / 5)) | From the line above |
| 64-QAM | (7/12) Q(√(2 Eb/N0 / 7)) | From the line above |
Tse and Viswanath give the BPSK and QPSK forms in chapter 3 of Fundamentals of Wireless Communication. The M-QAM line follows from treating each axis as √M-level PAM with Gray labels. The simulation below agrees with it to within Monte Carlo noise.
Eb/N0 needed over AWGN
Solved from the formulas above by the code at the end of this page.
| Scheme | BER 10⁻³ | BER 10⁻⁵ | BER 10⁻⁶ |
|---|---|---|---|
| BPSK and QPSK | 6.8 dB | 9.6 dB | 10.5 dB |
| 16-QAM | 10.5 dB | 13.4 dB | 14.4 dB |
| 64-QAM | 14.8 dB | 17.8 dB | 18.8 dB |
| 256-QAM | 19.4 dB | 22.5 dB | 23.5 dB |
Each step from 16 to 64 to 256 points costs about 4 to 5 dB in Eb/N0 and gives 2 more bits per symbol. In Es/N0 (add 10 log10 k), 256-QAM at 10⁻⁶ needs about 32.5 dB, which few real transmitters reach because of phase noise and PA distortion.
BER over Rayleigh fading
With coherent detection and perfect channel knowledge, and SNR the average received SNR per symbol, Tse and Viswanath (eqs. 3.19 and 3.21) give for BPSK:
BPSK, Rayleigh: p_e = 0.5 (1 - sqrt(SNR / (1 + SNR))) ~ 1 / (4 SNR) at high SNR
Their Table 3.1 lists the high-SNR BER for other schemes:
| Scheme | High-SNR BER, Rayleigh | Bits per symbol |
|---|---|---|
| Coherent BPSK | 1/(4 SNR) | 1 |
| Coherent QPSK | 1/(2 SNR) | 2 |
| Coherent 16-QAM | 5/(2 SNR) | 4 |
| Differential BPSK | 1/(2 SNR) | 1 |
| Differential QPSK | 1/SNR | 2 |
The error rate now falls only tenfold per 10 dB. At BER 10⁻³, Tse and Viswanath note a 17 dB gap between BPSK over AWGN and BPSK over Rayleigh fading. Diversity and coding close that gap, not extra power. With L independent branches the error rate falls as SNR to the power −L.
Code: check the formulas by simulation
The block simulates Gray-coded square QAM over AWGN and BPSK over Rayleigh fading, with complex128 baseband samples at one sample per symbol. It then solves for the Eb/N0 each scheme needs. It runs in well under a second.
import numpy as np
from scipy.special import erfc
from scipy.optimize import brentq
def qfunc(x):
return 0.5 * erfc(x / np.sqrt(2))
def ber_mqam_theory(ebn0_db: float, m: int) -> float:
"""Gray-coded square M-QAM, nearest-neighbour approximation (exact for M = 4)."""
k = np.log2(m)
ebn0 = 10 ** (ebn0_db / 10)
return (4 / k) * (1 - 1 / np.sqrt(m)) * qfunc(np.sqrt(3 * k * ebn0 / (m - 1)))
def gray_pam_levels(bits_per_axis: int) -> tuple[np.ndarray, np.ndarray]:
"""Return PAM amplitudes and the Gray label of each amplitude index."""
n = 2 ** bits_per_axis
levels = np.arange(-(n - 1), n, 2, dtype=float)
gray = np.arange(n) ^ (np.arange(n) >> 1)
return levels, gray
def simulate_mqam(ebn0_db: float, m: int, n_sym: int, rng) -> float:
k = int(np.log2(m))
kb = k // 2
levels, gray = gray_pam_levels(kb)
idx_i = rng.integers(0, 2 ** kb, n_sym)
idx_q = rng.integers(0, 2 ** kb, n_sym)
s = levels[idx_i] + 1j * levels[idx_q]
es = np.mean(np.abs(levels[:, None] + 1j * levels[None, :]) ** 2)
n0 = es / (k * 10 ** (ebn0_db / 10))
r = s + np.sqrt(n0 / 2) * (rng.standard_normal(n_sym) + 1j * rng.standard_normal(n_sym))
def slice_axis(x):
return np.clip(np.round((x + (2 ** kb - 1)) / 2), 0, 2 ** kb - 1).astype(int)
det_i, det_q = slice_axis(r.real), slice_axis(r.imag)
errs = 0
for tx, rx in [(idx_i, det_i), (idx_q, det_q)]:
diff = gray[tx] ^ gray[rx]
errs += sum(np.count_nonzero(diff & (1 << b)) for b in range(kb))
return errs / (n_sym * k)
rng = np.random.default_rng(1)
n_sym = 400_000
print("Eb/N0 M theory simulated")
for m, ebn0_db in [(4, 6), (16, 10), (64, 14)]:
print(f"{ebn0_db:4d} dB {m:3d} {ber_mqam_theory(ebn0_db, m):.3e} {simulate_mqam(ebn0_db, m, n_sym, rng):.3e}")
# BPSK over Rayleigh fading with coherent detection (Tse and Viswanath eq. 3.19)
snr = 10 ** (20 / 10)
h = (rng.standard_normal(n_sym) + 1j * rng.standard_normal(n_sym)) / np.sqrt(2)
b = rng.integers(0, 2, n_sym)
x = 2.0 * b - 1
y = h * x + np.sqrt(1 / (2 * snr)) * (rng.standard_normal(n_sym) + 1j * rng.standard_normal(n_sym))
ber_sim = np.mean(((np.conj(h) * y).real > 0) != (b == 1))
ber_th = 0.5 * (1 - np.sqrt(snr / (1 + snr)))
print(f"Rayleigh BPSK at 20 dB: theory {ber_th:.3e}, simulated {ber_sim:.3e}, 1/(4 SNR) = {1/(4*snr):.3e}")
# Eb/N0 needed for a target BER over AWGN
print("Required Eb/N0 (dB) for BER 1e-3, 1e-5, 1e-6")
for m in [4, 16, 64, 256]:
req = [brentq(lambda e: np.log10(ber_mqam_theory(e, m)) - np.log10(t), -5, 30) for t in (1e-3, 1e-5, 1e-6)]
print(f"M = {m:3d}: " + ", ".join(f"{v:.1f}" for v in req))
Expected output:
Eb/N0 M theory simulated
6 dB 4 2.388e-03 2.376e-03
10 dB 16 1.754e-03 1.752e-03
14 dB 64 2.154e-03 2.165e-03
Rayleigh BPSK at 20 dB: theory 2.481e-03, simulated 2.480e-03, 1/(4 SNR) = 2.500e-03
Required Eb/N0 (dB) for BER 1e-3, 1e-5, 1e-6
M = 4: 6.8, 9.6, 10.5
M = 16: 10.5, 13.4, 14.4
M = 64: 14.8, 17.8, 18.8
M = 256: 19.4, 22.5, 23.5
The chart values come from this block:
import numpy as np
from scipy.special import erfc
def qfunc(x):
return 0.5 * erfc(x / np.sqrt(2))
ebn0_db = np.arange(0, 22, 2)
g = 10 ** (ebn0_db / 10)
curves = {
"BPSK and QPSK, AWGN": qfunc(np.sqrt(2 * g)),
"16-QAM, AWGN": 0.75 * qfunc(np.sqrt(4 * g / 5)),
"64-QAM, AWGN": (7 / 12) * qfunc(np.sqrt(18 * g / 63)),
"BPSK, Rayleigh": 0.5 * (1 - np.sqrt(g / (1 + g))),
}
for name, ber in curves.items():
pts = [[int(e), float(f"{b:.3g}")] for e, b in zip(ebn0_db, ber) if b > 1e-9]
print(name, pts)
Pitfalls
- Mixing Es/N0, Eb/N0 and SNR. State which one every axis uses. A 64-QAM curve plotted against Es/N0 sits 7.8 dB to the right of the same curve against Eb/N0.
- Measuring SNR in a bandwidth wider than the signal. Noise measured through a wide channel filter is not the noise the slicer sees after the matched filter.
- Natural (binary) labelling instead of Gray. Neighbouring errors then cost more than one bit.
- Comparing an uncoded curve with a coded system’s specification. LDPC or polar coding moves the waterfall by several dB.
- Simulating too few bits. To trust a BER of 10⁻⁵, count at least 100 errors, which needs at least 10⁷ bits.
Sources
- D. Tse and P. Viswanath, Fundamentals of Wireless Communication, Cambridge University Press, 2005, doi:10.1017/CBO9780511807213. Chapter 3 is free from the author.
- 3GPP TS 38.211, NR physical channels and modulation, for the modulation mappers.
- 3GPP TS 38.214, NR physical layer procedures for data, for the CQI and MCS tables.
- Glossary: bit error rate, Eb/N0, constellation diagram, Rayleigh fading.