5G NR: numerology, synchronisation block and reference signals#

NR keeps the OFDM of LTE and scales it: the subcarrier spacing is \(\Delta f = 2^{\mu} \cdot 15\) kHz, a 1 ms subframe holds \(2^{\mu}\) slots of 14 symbols, and the cyclic prefix shrinks as \(2^{-\mu}\) (X. Lin et al., “5G New Radio: Unveiling the Essentials of the Next Generation Wireless Access Technology”, IEEE Commun. Standards Mag. 3(3), 2019, Sec. II; \(\mu = 0\) is the numerology of LTE, D. Astély et al., IEEE Commun. Mag. 47(4), 2009). This page builds the pieces a receiver meets first: the numerology (comnum.nr_numerology(), comnum.nr_ofdm_modulate()), the synchronisation signal block that carries the cell identity (comnum.nr_ssb_grid(), comnum.nr_detect_cell_id()) and the demodulation reference signals of the data channel (comnum.nr_pdsch_dmrs()).

The sequences, the positions and the modulation follow TS 38.211 and agree with py3gpp, an open reimplementation of the 5G functions of MATLAB (tests/oracle/test_nr_oracle.py): bit for bit for the sequences and the positions, to 10⁻¹⁴ for the waveforms.

import matplotlib.pyplot as plt
import numpy as np
from matplotlib.colors import ListedColormap

import comnum
from comnum.plot import BLUE, CRIMSON, PAPER, SKY, TEAL

Numerology#

At the sampling rate \(N_{\mathrm{FFT}} \Delta f\), the first symbol of every half subframe has a longer prefix, so that the half subframe lasts exactly 0.5 ms.

print(" mu   Δf (kHz)  slot (µs)  prefix (µs)  long prefix (µs)")
for mu in range(5):
    info = comnum.nr_numerology(mu, n_fft=4096)
    fs, cp = info["sample_rate"], info["cp_lengths"]
    slot = 1e3 / info["slots_per_subframe"]
    print(
        f"{mu:3d} {info['subcarrier_spacing_hz'] / 1e3:9.0f} {slot:10.1f} {cp[1] / fs * 1e6:12.2f} {cp[0] / fs * 1e6:17.2f}"
    )
mu   Δf (kHz)  slot (µs)  prefix (µs)  long prefix (µs)
 0        15     1000.0         4.69              5.21
 1        30      500.0         2.34              2.86
 2        60      250.0         1.17              1.69
 3       120      125.0         0.59              1.11
 4       240       62.5         0.29              0.81

The synchronisation signal block#

4 symbols × 240 subcarriers: the PSS (an m-sequence of length 127, one of 3), the SSS (a Gold sequence of length 127, one of 336), the broadcast channel PBCH and its DMRS on one subcarrier in four, shifted by the cell identity (Lin et al., Sec. III-A and their Fig. 2). The 1008 identities are \(3 N_{\mathrm{ID}}^{(1)} + N_{\mathrm{ID}}^{(2)}\).

cell = 417
kinds = ["pss", "sss", "pbch", "pbch_dmrs"]
layout = np.zeros((240, 4), int)  # (subcarriers, symbols)
for i, kind in enumerate(kinds, start=1):
    kl = comnum.nr_ssb_indices(cell, kind)  # (k, l) pairs
    layout[kl[:, 0], kl[:, 1]] = i
cmap = ListedColormap([PAPER, CRIMSON, BLUE, SKY, TEAL])
fig, ax = plt.subplots(figsize=(6.4, 2.6))
ax.imshow(layout.T, aspect="auto", cmap=cmap, interpolation="nearest", vmin=0, vmax=4)
ax.set(xlabel="subcarrier", ylabel="OFDM symbol", yticks=range(4), title=f"SSB of cell {cell}")
for i, (name, c) in enumerate(
    zip(["PSS", "SSS", "PBCH", "PBCH DMRS"], cmap.colors[1:], strict=True)
):
    ax.text(1.01, 0.9 - 0.22 * i, name, color=c, transform=ax.transAxes, fontsize=8, weight="bold")
fig.tight_layout()
SSB of cell 417

Demodulation reference signals of the PDSCH#

Front-loaded (the first DMRS symbol is the third or fourth of the slot), with up to three additional symbols for mobility, on one subcarrier in two (configuration type 1) or two in six (type 2), QPSK of the Gold sequence of 3GPP (Lin et al., Sec. VI-A). pilot_table of the Rust module hands them to the channel estimators of comnum_waveform::ofdm::estimation.

fig, axes = plt.subplots(1, 2, figsize=(6.4, 3.0), sharey=True)
for ax, (ctype, add) in zip(axes, [(1, 2), (2, 3)], strict=True):
    d = comnum.nr_pdsch_dmrs(4, config_type=ctype, additional_position=add, n_id=cell)
    m = np.zeros((48, 14))  # (subcarriers, symbols)
    m[d["indices"][:, 0], d["indices"][:, 1]] = 1
    ax.imshow(
        m,
        aspect="auto",
        cmap=ListedColormap([PAPER, TEAL]),
        origin="lower",
    )
    ax.set(xlabel="OFDM symbol", title=f"type {ctype}, symbols {d['symbols'].tolist()}")
axes[0].set_ylabel("subcarrier (4 resource blocks)")
fig.tight_layout()
type 1, symbols [2, 7, 11], type 2, symbols [2, 5, 8, 11]

The waveform#

comnum.nr_ofdm_modulate() maps a grid (subcarriers × symbols) to samples with the prefixes of the numerology; here a slot at 30 kHz on 24 resource blocks: its spectrum occupies \(12 \times 24 \times 30\) kHz = 8.64 MHz.

mu, n_rb, n_fft = 1, 24, 512
k = 12 * n_rb
g = (np.sign(rng.gaussian(k * 14)) + 1j * np.sign(rng.gaussian(k * 14))).reshape(k, 14) / np.sqrt(2)
x = comnum.nr_ofdm_modulate(g, mu, n_fft)
fs = comnum.nr_numerology(mu, n_fft=n_fft)["sample_rate"]
spec = np.fft.fftshift(
    np.abs(np.fft.fft(x[: (x.size // n_fft) * n_fft].reshape(-1, n_fft), axis=1)) ** 2
).mean(0)
f = np.fft.fftshift(np.fft.fftfreq(n_fft, 1 / fs)) / 1e6
fig, ax = plt.subplots(figsize=(6.4, 3.0))
ax.plot(f, comnum.lin2db(spec / spec.max()), color=BLUE, linewidth=1)
ax.set(xlabel="frequency (MHz)", ylabel="PSD (dB)", ylim=(-40, 3))
ax.grid(alpha=0.3)
fig.tight_layout()
print(f"{x.size} samples at {fs / 1e6:.2f} MS/s: {x.size / fs * 1e3:.3f} ms")
plot 02 5g nr
7680 samples at 15.36 MS/s: 0.500 ms

Total running time of the script: (0 minutes 1.401 seconds)

Gallery generated by Sphinx-Gallery