Skip to main content

comnum_waveform/ofdm/
nr.rs

1//! The numerology, the synchronisation signal block and the demodulation reference
2//! signals of 5G NR (and the numerology of LTE, its case $\mu = 0$).
3//!
4//! # Numerology
5//!
6//! NR scales the 15 kHz subcarrier spacing of LTE: $\Delta f = 2^{\mu} \cdot 15$ kHz,
7//! $\mu = 0, \dots, 4$; a 10 ms frame holds ten 1 ms subframes of $2^{\mu}$ slots of 14
8//! OFDM symbols (12 with the extended cyclic prefix, $\mu = 2$ only); a resource block
9//! is 12 subcarriers (Lin et al. 2019, Sec. II; Astély et al. 2009 for LTE). At the
10//! sampling rate $N_{\mathrm{FFT}}\Delta f$, the cyclic prefix of a symbol is
11//! $144 N_{\mathrm{FFT}}/2048$ samples, and $(144 + 16 \cdot 2^{\mu}) N_{\mathrm{FFT}}/2048$
12//! for the first symbol of every half subframe ($l = 0$ and $l = 7 \cdot 2^{\mu}$), so
13//! that a half subframe lasts exactly 0.5 ms; the extended prefix is $512
14//! N_{\mathrm{FFT}}/2048$ (TS 38.211, Sec. 5.3.1): 4.7 µs and 16.7 µs at $\mu = 0$, the
15//! prefixes of LTE. [`Carrier::modulate`] maps a grid of $K = 12 N_{\mathrm{RB}}$
16//! subcarriers, the subcarrier $k$ at the frequency $(k - K/2)\Delta f$, and prepends the
17//! prefixes; [`Carrier::demodulate`] removes them and gives the grid back.
18//!
19//! # Synchronisation signal block
20//!
21//! The SSB occupies 4 OFDM symbols and 240 subcarriers (20 resource blocks): the
22//! primary synchronisation signal (PSS) on symbol 0, subcarriers 56 to 182; the
23//! secondary (SSS) on symbol 2, same subcarriers; the broadcast channel (PBCH) on
24//! symbols 1 and 3 and on subcarriers 0–47 and 192–239 of symbol 2, with its
25//! demodulation reference signal (DMRS) on one subcarrier in four, offset by
26//! $\nu = N_{\mathrm{ID}}^{\mathrm{cell}} \bmod 4$. The 1008 cell identities are
27//! $N_{\mathrm{ID}}^{\mathrm{cell}} = 3 N_{\mathrm{ID}}^{(1)} + N_{\mathrm{ID}}^{(2)}$,
28//! $N_{\mathrm{ID}}^{(1)} < 336$, $N_{\mathrm{ID}}^{(2)} < 3$ (TS 38.211, Sec. 7.4.2–7.4.3).
29//!
30//! - PSS: the BPSK m-sequence $d(n) = 1 - 2x((n + 43 N_{\mathrm{ID}}^{(2)}) \bmod 127)$,
31//!   $x(i+7) = (x(i+4) + x(i)) \bmod 2$, $[x(6) \dots x(0)] = [1\,1\,1\,0\,1\,1\,0]$;
32//! - SSS: the BPSK Gold sequence $d(n) = [1 - 2x_{0}((n + m_{0}) \bmod 127)][1 -
33//!   2x_{1}((n + m_{1}) \bmod 127)]$, $x_{0}(i+7) = (x_{0}(i+4) + x_{0}(i)) \bmod 2$,
34//!   $x_{1}(i+7) = (x_{1}(i+1) + x_{1}(i)) \bmod 2$, both started at $[0 \dots 0\,1]$,
35//!   $m_{0} = 15\lfloor N_{\mathrm{ID}}^{(1)}/112 \rfloor + 5 N_{\mathrm{ID}}^{(2)}$,
36//!   $m_{1} = N_{\mathrm{ID}}^{(1)} \bmod 112$;
37//! - PBCH DMRS: the QPSK $r(m) = [(1 - 2c(2m)) + j(1 - 2c(2m+1))]/\sqrt{2}$, $m < 144$,
38//!   of the Gold sequence of 3GPP ([`comnum_math::sequences::gold_3gpp`]) with
39//!   $c_{\mathrm{init}} = 2^{11}(\bar{\imath}_{\mathrm{SSB}} + 1)(\lfloor N_{\mathrm{ID}}^{\mathrm{cell}}/4
40//!   \rfloor + 1) + 2^{6}(\bar{\imath}_{\mathrm{SSB}} + 1) + (N_{\mathrm{ID}}^{\mathrm{cell}} \bmod 4)$,
41//!   mapped frequency first.
42//!
43//! [`detect_cell_id`] finds $N_{\mathrm{ID}}^{(2)}$ by the largest correlation of the
44//! PSS subcarriers with the three PSS, then $N_{\mathrm{ID}}^{(1)}$ by the largest real
45//! part of $\hat{h}^{*} \langle y_{\mathrm{SSS}}, d_{\mathrm{SSS}} \rangle$ over the 336
46//! SSS, with the channel $\hat{h}$ estimated on the PSS (assumed flat over the 127
47//! subcarriers and the three symbols).
48//!
49//! # DMRS of the PDSCH
50//!
51//! [`PdschDmrs`] gives the positions and values of the DMRS of a PDSCH of mapping type A
52//! over a whole slot (14 symbols, normal prefix), single-symbol, port 1000: the first
53//! DMRS symbol $l_{0} = 2$ or 3 (`dmrs-TypeA-Position`) and the additional positions
54//! $\{l_{0}\}$, $\{l_{0}, 11\}$, $\{l_{0}, 7, 11\}$, $\{l_{0}, 5, 8, 11\}$; the subcarriers
55//! $k = 4n + 2k'$ (configuration type 1, one in two) or $k = 6n + k'$ (type 2, two in
56//! six), $k' \in \{0, 1\}$, with the QPSK value $r(2n + k')$ of the Gold sequence of
57//! $c_{\mathrm{init}} = (2^{17}(14 n_{s} + l + 1)(2N_{\mathrm{ID}} + 1) + 2N_{\mathrm{ID}})
58//! \bmod 2^{31}$ (TS 38.211, Sec. 7.4.1.1). It gives a [`PilotTable`] for the channel
59//! estimators of [`super::estimation`]. Shorter allocations, two-symbol DMRS and the
60//! other ports need the tables of TS 38.211 (status needed in `references/README.md`).
61//!
62//! # Examples
63//! ```
64//! use comnum_waveform::ofdm::nr::{Numerology, pss, detect_cell_id, ssb_grid};
65//! let nr = Numerology::new(1, false)?;                       // 30 kHz
66//! assert_eq!((nr.subcarrier_spacing_hz(), nr.slots_per_frame()), (30e3, 20));
67//! assert_eq!(nr.cp_lengths(4096)?[..2], [352, 288]);         // (144 + 16·2)·2, 144·2
68//! assert_eq!(pss(0)?.len(), 127);
69//! let grid = ssb_grid(417, 0, None)?;                        // 4 × 240, no PBCH data
70//! assert_eq!(detect_cell_id(&grid)?.0, 417);
71//! # Ok::<(), comnum_core::Error>(())
72//! ```
73//!
74//! # References
75//!
76//! 1. X. Lin et al., "5G New Radio: Unveiling the essentials of the next generation
77//!    wireless access technology," *IEEE Commun. Standards Mag.* 3(3), 30–37, 2019
78//!    (arXiv:1806.06898).
79//! 2. D. Astély et al., "LTE: the evolution of mobile broadband," *IEEE Commun. Mag.*
80//!    47(4), 44–51, 2009.
81//! 3. 3GPP TS 38.211, *NR; Physical channels and modulation*, Sec. 5.3, 7.4.
82
83use super::Ofdm;
84use super::grid::PilotTable;
85use comnum_core::{Error, Result, ensure, ensure_shape};
86use comnum_math::C64;
87use comnum_math::sequences::{gold_3gpp, lfsr};
88use std::f64::consts::FRAC_1_SQRT_2;
89
90/// Subcarriers of a resource block.
91pub const SUBCARRIERS_PER_RB: usize = 12;
92/// Subcarriers of the SSB.
93pub const SSB_SUBCARRIERS: usize = 240;
94/// OFDM symbols of the SSB.
95pub const SSB_SYMBOLS: usize = 4;
96/// Length of the PSS and of the SSS.
97pub const SYNC_LENGTH: usize = 127;
98/// First subcarrier of the PSS and of the SSS in the SSB.
99pub const SYNC_FIRST_SUBCARRIER: usize = 56;
100
101/// A numerology of NR: $\Delta f = 2^{\mu} \cdot 15$ kHz (see the module).
102#[derive(Clone, Copy, Debug, PartialEq, Eq)]
103pub struct Numerology {
104    /// $\mu$, 0 to 4.
105    pub mu: u32,
106    /// The extended cyclic prefix (12 symbols per slot; $\mu = 2$ only in NR, and the
107    /// extended prefix of LTE at $\mu = 0$).
108    pub extended_cp: bool,
109}
110
111impl Numerology {
112    /// The numerology $\mu$.
113    ///
114    /// # Errors
115    /// [`comnum_core::Error::Config`] for $\mu > 4$, or an extended prefix with $\mu \notin \{0, 2\}$.
116    pub fn new(mu: u32, extended_cp: bool) -> Result<Numerology> {
117        ensure(mu <= 4, || format!("NR numerologies are mu = 0 to 4, got {mu}"))?;
118        ensure(!extended_cp || mu == 0 || mu == 2, || {
119            format!("the extended cyclic prefix exists for mu = 2 (and LTE, mu = 0), got {mu}")
120        })?;
121        Ok(Numerology { mu, extended_cp })
122    }
123
124    /// The subcarrier spacing $2^{\mu} \cdot 15$ kHz (Hz).
125    pub fn subcarrier_spacing_hz(&self) -> f64 {
126        15e3 * f64::from(1u32 << self.mu)
127    }
128
129    /// Slots per 1 ms subframe, $2^{\mu}$.
130    pub fn slots_per_subframe(&self) -> usize {
131        1 << self.mu
132    }
133
134    /// Slots per 10 ms frame.
135    pub fn slots_per_frame(&self) -> usize {
136        10 * self.slots_per_subframe()
137    }
138
139    /// OFDM symbols per slot: 14, or 12 with the extended prefix.
140    pub fn symbols_per_slot(&self) -> usize {
141        if self.extended_cp { 12 } else { 14 }
142    }
143
144    /// The sampling rate $N_{\mathrm{FFT}}\Delta f$ (Hz).
145    pub fn sample_rate(&self, n_fft: usize) -> f64 {
146        n_fft as f64 * self.subcarrier_spacing_hz()
147    }
148
149    /// The cyclic prefixes (samples) of the symbols of one subframe at the FFT size
150    /// `n_fft` (see the module).
151    ///
152    /// # Errors
153    /// [`comnum_core::Error::Config`] when `n_fft` is not a multiple of 128 (the prefixes would not be
154    /// whole samples).
155    pub fn cp_lengths(&self, n_fft: usize) -> Result<Vec<usize>> {
156        ensure(n_fft >= 128 && n_fft % 128 == 0, || {
157            format!("the FFT size must be a multiple of 128 for whole prefixes, got {n_fft}")
158        })?;
159        let n = self.symbols_per_slot() * self.slots_per_subframe();
160        if self.extended_cp {
161            return Ok(vec![512 * n_fft / 2048; n]);
162        }
163        let half = 7 * self.slots_per_subframe();
164        let long = (144 + 16 * self.slots_per_subframe()) * n_fft / 2048;
165        Ok((0..n).map(|l| if l % half == 0 { long } else { 144 * n_fft / 2048 }).collect())
166    }
167}
168
169/// An NR carrier: a grid of `n_subcarriers` $K$ subcarriers of the numerology
170/// `numerology`, at the FFT size `n_fft`, whose first OFDM symbol is the symbol
171/// `first_symbol` of a subframe (see the module).
172///
173/// Every symbol is the block of [`Ofdm::to_time`] of $K$ subcarriers out of
174/// $N_{\mathrm{FFT}}$ without oversampling, scaled by $1/\sqrt{K}$ (a grid of unit-energy
175/// symbols gives a unit mean power), and takes the prefix of its position in the
176/// subframe.
177///
178/// # Examples
179/// ```
180/// use comnum_waveform::ofdm::nr::{Carrier, Numerology};
181/// use comnum_math::C64;
182/// let carrier = Carrier { numerology: Numerology::new(1, false)?, n_subcarriers: 48, n_fft: 128, first_symbol: 0 };
183/// let grid: Vec<C64> = (0..3 * 48).map(|i| C64::new((i % 3) as f64 - 1.0, 1.0)).collect();
184/// let x = carrier.modulate(&grid)?;
185/// assert_eq!(x.len(), 3 * 128 + 11 + 9 + 9);              // (144 + 32)/16, then 144/16
186/// assert!(carrier.demodulate(&x)?.iter().zip(&grid).all(|(a, b)| (a - b).norm() < 1e-12));
187/// # Ok::<(), comnum_core::Error>(())
188/// ```
189#[derive(Clone, Copy, Debug, PartialEq, Eq)]
190pub struct Carrier {
191    /// The numerology.
192    pub numerology: Numerology,
193    /// Subcarriers $K$ of the grid (even, at most $N_{\mathrm{FFT}}$).
194    pub n_subcarriers: usize,
195    /// FFT size $N_{\mathrm{FFT}}$ (a multiple of 128).
196    pub n_fft: usize,
197    /// The position in the subframe of the first OFDM symbol of the grid.
198    pub first_symbol: usize,
199}
200
201impl Carrier {
202    /// Validate the carrier.
203    ///
204    /// # Errors
205    /// [`comnum_core::Error::Config`] for an odd or too large $K$, or an FFT size that
206    /// [`Numerology::cp_lengths`] refuses.
207    pub fn check(&self) -> Result<()> {
208        let k = self.n_subcarriers;
209        ensure(k > 0 && k % 2 == 0 && k <= self.n_fft, || {
210            format!("the grid needs an even K ≤ N_FFT subcarriers, got {k}")
211        })?;
212        self.numerology.cp_lengths(self.n_fft).map(|_| ())
213    }
214
215    /// The modulator of one symbol and the prefixes of a subframe.
216    fn modem(&self) -> Result<(Ofdm, Vec<usize>)> {
217        self.check()?;
218        let cp = self.numerology.cp_lengths(self.n_fft)?;
219        Ok((Ofdm { oversampling: 1, ..Ofdm::new(self.n_fft, self.n_subcarriers)? }, cp))
220    }
221
222    /// The prefix of the symbol `s` of the grid.
223    fn prefix(cp: &[usize], first: usize, s: usize) -> usize {
224        cp[(first + s) % cp.len()]
225    }
226
227    /// OFDM-modulate the symbols of a grid (`grid[l * K + i]` for subcarrier `i` of symbol
228    /// `l`), every one with the prefix of its position in the subframe.
229    ///
230    /// # Errors
231    /// The errors of [`Carrier::check`]; [`comnum_core::Error::Shape`] for a grid that is
232    /// not a whole number of symbols.
233    pub fn modulate(&self, grid: &[C64]) -> Result<Vec<C64>> {
234        let (ofdm, cp) = self.modem()?;
235        let k = self.n_subcarriers;
236        ensure_shape(grid.len() % k == 0, || format!("{} values is not a whole number of symbols of {k}", grid.len()))?;
237        let mut out = Vec::new();
238        for (s, sym) in grid.chunks_exact(k).enumerate() {
239            comnum_dsp::signal::extend_with_prefix(
240                &mut out,
241                &ofdm.to_time(sym)?,
242                Carrier::prefix(&cp, self.first_symbol, s),
243            )?;
244        }
245        Ok(out)
246    }
247
248    /// The grid of the samples of whole OFDM symbols, every prefix of its position in the
249    /// subframe removed: the inverse of [`Carrier::modulate`].
250    ///
251    /// # Errors
252    /// The errors of [`Carrier::check`]; [`comnum_core::Error::Shape`] if the samples do not
253    /// end with a whole symbol.
254    pub fn demodulate(&self, samples: &[C64]) -> Result<Vec<C64>> {
255        let (ofdm, cp) = self.modem()?;
256        let mut out = Vec::new();
257        let (mut at, mut s) = (0, 0);
258        while at < samples.len() {
259            let len = Carrier::prefix(&cp, self.first_symbol, s) + self.n_fft;
260            ensure_shape(at + len <= samples.len(), || {
261                format!(
262                    "{} samples do not end with a whole OFDM symbol (symbol {s} ends at {})",
263                    samples.len(),
264                    at + len
265                )
266            })?;
267            let block = comnum_dsp::signal::strip_prefix(&samples[at..at + len], len - self.n_fft)?;
268            out.extend(ofdm.to_active(block)?);
269            at += len;
270            s += 1;
271        }
272        Ok(out)
273    }
274
275    /// The sampling rate $N_{\mathrm{FFT}}\Delta f$ of the carrier (Hz).
276    pub fn sample_rate(&self) -> f64 {
277        self.numerology.sample_rate(self.n_fft)
278    }
279}
280
281/// $1 - 2x$ as $\pm 1$.
282fn bpsk(b: u8) -> f64 {
283    1.0 - 2.0 * f64::from(b)
284}
285
286/// The PSS of $N_{\mathrm{ID}}^{(2)}$ (127 values $\pm 1$).
287///
288/// # Errors
289/// [`comnum_core::Error::Config`] for $N_{\mathrm{ID}}^{(2)} > 2$.
290pub fn pss(n_id2: u32) -> Result<Vec<f64>> {
291    ensure(n_id2 < 3, || format!("N_ID2 is 0, 1 or 2, got {n_id2}"))?;
292    let x = lfsr(&[0, 4], 7, &[0, 1, 1, 0, 1, 1, 1], SYNC_LENGTH);
293    Ok((0..SYNC_LENGTH).map(|n| bpsk(x[(n + 43 * n_id2 as usize) % SYNC_LENGTH])).collect())
294}
295
296/// The SSS of $(N_{\mathrm{ID}}^{(1)}, N_{\mathrm{ID}}^{(2)})$ (127 values $\pm 1$).
297///
298/// # Errors
299/// [`comnum_core::Error::Config`] for $N_{\mathrm{ID}}^{(1)} > 335$ or $N_{\mathrm{ID}}^{(2)} > 2$.
300pub fn sss(n_id1: u32, n_id2: u32) -> Result<Vec<f64>> {
301    ensure(n_id1 < 336 && n_id2 < 3, || format!("N_ID1 < 336 and N_ID2 < 3, got ({n_id1}, {n_id2})"))?;
302    let x0 = lfsr(&[0, 4], 7, &[1], SYNC_LENGTH);
303    let x1 = lfsr(&[0, 1], 7, &[1], SYNC_LENGTH);
304    let m0 = (15 * (n_id1 / 112) + 5 * n_id2) as usize;
305    let m1 = (n_id1 % 112) as usize;
306    Ok((0..SYNC_LENGTH).map(|n| bpsk(x0[(n + m0) % SYNC_LENGTH]) * bpsk(x1[(n + m1) % SYNC_LENGTH])).collect())
307}
308
309/// The QPSK of the pairs of a Gold sequence: $[(1 - 2c(2m)) + j(1 - 2c(2m+1))]/\sqrt{2}$.
310fn qpsk_of_gold(c_init: u32, n: usize) -> Vec<C64> {
311    let c = gold_3gpp(c_init, 2 * n);
312    c.chunks_exact(2).map(|p| C64::new(bpsk(p[0]), bpsk(p[1])) * FRAC_1_SQRT_2).collect()
313}
314
315/// The 144 values of the PBCH DMRS of the cell `n_cell_id` and the SSB index
316/// $\bar{\imath}_{\mathrm{SSB}}$ (0 to 7; see the module).
317///
318/// # Errors
319/// [`comnum_core::Error::Config`] for a cell identity above 1007 or $\bar{\imath}_{\mathrm{SSB}} > 7$.
320pub fn pbch_dmrs(n_cell_id: u32, i_ssb_bar: u32) -> Result<Vec<C64>> {
321    ensure(n_cell_id < 1008 && i_ssb_bar < 8, || {
322        format!("the cell identity is below 1008 and the SSB index below 8, got ({n_cell_id}, {i_ssb_bar})")
323    })?;
324    let c_init = (1 << 11) * (i_ssb_bar + 1) * (n_cell_id / 4 + 1) + (1 << 6) * (i_ssb_bar + 1) + n_cell_id % 4;
325    Ok(qpsk_of_gold(c_init, 144))
326}
327
328/// What a resource element of the SSB carries.
329#[derive(Clone, Copy, Debug, PartialEq, Eq)]
330pub enum SsbElement {
331    /// Nothing (the guard subcarriers around the PSS and the SSS).
332    Empty,
333    /// The PSS.
334    Pss,
335    /// The SSS.
336    Sss,
337    /// The PBCH data.
338    Pbch,
339    /// The PBCH DMRS.
340    PbchDmrs,
341}
342
343impl std::str::FromStr for SsbElement {
344    type Err = Error;
345
346    /// `empty`, `pss`, `sss`, `pbch` or `pbch_dmrs` (case-insensitive).
347    ///
348    /// # Errors
349    /// [`Error::Config`] for another name.
350    fn from_str(s: &str) -> Result<SsbElement> {
351        match s.to_ascii_lowercase().as_str() {
352            "empty" => Ok(SsbElement::Empty),
353            "pss" => Ok(SsbElement::Pss),
354            "sss" => Ok(SsbElement::Sss),
355            "pbch" => Ok(SsbElement::Pbch),
356            "pbch_dmrs" => Ok(SsbElement::PbchDmrs),
357            _ => Err(Error::Config(format!("unknown SSB element {s:?}: empty, pss, sss, pbch or pbch_dmrs"))),
358        }
359    }
360}
361
362/// What the subcarrier `k` (0 to 239) of the symbol `l` (0 to 3) of the SSB of the cell
363/// `n_cell_id` carries (see the module).
364pub fn ssb_element(n_cell_id: u32, k: usize, l: usize) -> SsbElement {
365    let sync = (SYNC_FIRST_SUBCARRIER..SYNC_FIRST_SUBCARRIER + SYNC_LENGTH).contains(&k);
366    let pbch = match l {
367        1 | 3 => true,
368        2 => !(48..192).contains(&k),
369        _ => false,
370    };
371    match (l, sync, pbch) {
372        (0, true, _) => SsbElement::Pss,
373        (2, true, _) => SsbElement::Sss,
374        (_, _, true) if k % 4 == (n_cell_id % 4) as usize => SsbElement::PbchDmrs,
375        (_, _, true) => SsbElement::Pbch,
376        _ => SsbElement::Empty,
377    }
378}
379
380/// The resource elements `(k, l)` of one kind in the SSB, frequency first (the order
381/// of the mapping).
382pub fn ssb_indices(n_cell_id: u32, kind: SsbElement) -> Vec<(usize, usize)> {
383    (0..SSB_SYMBOLS)
384        .flat_map(|l| (0..SSB_SUBCARRIERS).map(move |k| (k, l)))
385        .filter(|&(k, l)| ssb_element(n_cell_id, k, l) == kind)
386        .collect()
387}
388
389/// The SSB of the cell `n_cell_id` with the SSB index $\bar{\imath}_{\mathrm{SSB}}$:
390/// `grid[l * 240 + k]`, the PSS and the SSS of unit amplitude, the PBCH DMRS, and the
391/// 432 PBCH symbols `pbch` (zeros when `None`).
392///
393/// # Errors
394/// [`comnum_core::Error::Config`] for a cell identity above 1007 or an SSB index above 7;
395/// [`comnum_core::Error::Shape`] for a PBCH of another length than 432.
396pub fn ssb_grid(n_cell_id: u32, i_ssb_bar: u32, pbch: Option<&[C64]>) -> Result<Vec<C64>> {
397    let mut g = vec![C64::new(0.0, 0.0); SSB_SYMBOLS * SSB_SUBCARRIERS];
398    let (n_id1, n_id2) = (n_cell_id / 3, n_cell_id % 3);
399    let dmrs = pbch_dmrs(n_cell_id, i_ssb_bar)?;
400    for (i, v) in pss(n_id2)?.into_iter().enumerate() {
401        g[SYNC_FIRST_SUBCARRIER + i] = C64::new(v, 0.0);
402    }
403    for (i, v) in sss(n_id1, n_id2)?.into_iter().enumerate() {
404        g[2 * SSB_SUBCARRIERS + SYNC_FIRST_SUBCARRIER + i] = C64::new(v, 0.0);
405    }
406    for ((k, l), v) in ssb_indices(n_cell_id, SsbElement::PbchDmrs).into_iter().zip(dmrs) {
407        g[l * SSB_SUBCARRIERS + k] = v;
408    }
409    if let Some(d) = pbch {
410        let idx = ssb_indices(n_cell_id, SsbElement::Pbch);
411        ensure_shape(d.len() == idx.len(), || format!("the PBCH carries {} symbols, got {}", idx.len(), d.len()))?;
412        for ((k, l), v) in idx.into_iter().zip(d) {
413            g[l * SSB_SUBCARRIERS + k] = *v;
414        }
415    }
416    Ok(g)
417}
418
419/// The cell identity of a received SSB grid (`grid[l * 240 + k]`, 960 values), by the
420/// PSS then the SSS, coherently with the channel of the PSS (see the module); returns
421/// the identity and the normalised correlations $|\langle y, d \rangle| / (\|y\| \|d\|)$ of the PSS and of the SSS.
422///
423/// # Errors
424/// [`comnum_core::Error::Shape`] for a grid of another size.
425pub fn detect_cell_id(grid: &[C64]) -> Result<(u32, f64, f64)> {
426    ensure_shape(grid.len() == SSB_SYMBOLS * SSB_SUBCARRIERS, || {
427        format!("an SSB grid has {} values, got {}", SSB_SYMBOLS * SSB_SUBCARRIERS, grid.len())
428    })?;
429    let at = |l: usize| &grid[l * SSB_SUBCARRIERS + SYNC_FIRST_SUBCARRIER..][..SYNC_LENGTH];
430    let (yp, ys) = (at(0), at(2));
431    let energy = |y: &[C64]| y.iter().map(C64::norm_sqr).sum::<f64>().sqrt().max(1e-300);
432    let norm = (SYNC_LENGTH as f64).sqrt();
433    let corr = |y: &[C64], d: &[f64]| y.iter().zip(d).map(|(a, b)| a * *b).sum::<C64>();
434    let c2 = (0..3).map(|n2| Ok(corr(yp, &pss(n2)?))).collect::<Result<Vec<C64>>>()?;
435    let n2 = comnum_math::argmax(c2.iter().map(|c| c.norm())).map_or(0, |(i, _)| i);
436    // the flat channel estimated on the PSS, then a coherent correlation of the SSS
437    let h = c2[n2] / SYNC_LENGTH as f64;
438    let n2 = n2 as u32;
439    let c1 = (0..336).map(|n1| Ok((h.conj() * corr(ys, &sss(n1, n2)?)).re)).collect::<Result<Vec<f64>>>()?;
440    let n1 = comnum_math::argmax(c1.iter().copied()).map_or(0, |(i, _)| i) as u32;
441    let rs = corr(ys, &sss(n1, n2)?).norm() / (energy(ys) * norm);
442    Ok((3 * n1 + n2, c2[n2 as usize].norm() / (energy(yp) * norm), rs))
443}
444
445/// The DMRS of a PDSCH of mapping type A over a slot of 14 symbols (see the module).
446#[derive(Clone, Copy, Debug, PartialEq, Eq)]
447pub struct PdschDmrs {
448    /// Configuration type 1 (one subcarrier in two) or 2 (two in six).
449    pub config_type: u8,
450    /// `dmrs-TypeA-Position`: the first DMRS symbol, 2 or 3.
451    pub type_a_position: usize,
452    /// `dmrs-AdditionalPosition`, 0 to 3.
453    pub additional_position: usize,
454    /// The scrambling identity $N_{\mathrm{ID}}$ (the cell identity by default).
455    pub n_id: u32,
456    /// The slot $n_{s}$ in the frame.
457    pub slot: u32,
458}
459
460impl PdschDmrs {
461    /// The DMRS symbols of the slot.
462    ///
463    /// # Errors
464    /// [`comnum_core::Error::Config`] for a configuration type other than 1 or 2, a first symbol
465    /// other than 2 or 3, an additional position above 3, or $l_{0} = 3$ with three
466    /// additional positions (not allowed).
467    pub fn symbols(&self) -> Result<Vec<usize>> {
468        ensure(self.config_type == 1 || self.config_type == 2, || {
469            format!("the DMRS configuration type is 1 or 2, got {}", self.config_type)
470        })?;
471        ensure(self.type_a_position == 2 || self.type_a_position == 3, || {
472            format!("dmrs-TypeA-Position is 2 or 3, got {}", self.type_a_position)
473        })?;
474        ensure(self.additional_position <= 3, || {
475            format!("dmrs-AdditionalPosition is 0 to 3, got {}", self.additional_position)
476        })?;
477        ensure(!(self.additional_position == 3 && self.type_a_position == 3), || {
478            "dmrs-AdditionalPosition 3 needs dmrs-TypeA-Position 2".to_string()
479        })?;
480        let l0 = self.type_a_position;
481        Ok(match self.additional_position {
482            0 => vec![l0],
483            1 => vec![l0, 11],
484            2 => vec![l0, 7, 11],
485            _ => vec![l0, 5, 8, 11],
486        })
487    }
488
489    /// The DMRS subcarriers of a grid of `n_rb` resource blocks.
490    pub fn subcarriers(&self, n_rb: usize) -> Vec<usize> {
491        let k = SUBCARRIERS_PER_RB * n_rb;
492        if self.config_type == 1 { (0..k).step_by(2).collect() } else { (0..k).filter(|i| i % 6 < 2).collect() }
493    }
494
495    /// The DMRS values of the symbol `l` on [`PdschDmrs::subcarriers`] (the grid starts
496    /// at common resource block 0).
497    pub fn values(&self, n_rb: usize, l: usize) -> Vec<C64> {
498        let n = self.subcarriers(n_rb).len();
499        let a = (1u64 << 17) * (14 * u64::from(self.slot) + l as u64 + 1) * (2 * u64::from(self.n_id) + 1);
500        let c_init = ((a + 2 * u64::from(self.n_id)) % (1u64 << 31)) as u32;
501        qpsk_of_gold(c_init, n)
502    }
503
504    /// The DMRS of the slot as a [`PilotTable`] of `12 n_rb` subcarriers and 14 symbols.
505    ///
506    /// # Errors
507    /// As [`PdschDmrs::symbols`].
508    pub fn pilot_table(&self, n_rb: usize) -> Result<PilotTable> {
509        let ks = self.subcarriers(n_rb);
510        let mut pilots = Vec::new();
511        for l in self.symbols()? {
512            for (k, v) in ks.iter().zip(self.values(n_rb, l)) {
513                pilots.push(((*k, l), v));
514            }
515        }
516        PilotTable::new(SUBCARRIERS_PER_RB * n_rb, 14, pilots)
517    }
518}
519
520#[cfg(test)]
521mod tests {
522    use super::*;
523
524    #[test]
525    fn the_ssb_elements_are_read_by_name() {
526        for e in [SsbElement::Empty, SsbElement::Pss, SsbElement::Sss, SsbElement::Pbch, SsbElement::PbchDmrs] {
527            let name = format!("{e:?}").replace("PbchDmrs", "pbch_dmrs");
528            assert_eq!(name.parse::<SsbElement>().unwrap(), e);
529        }
530        assert!("dmrs".parse::<SsbElement>().is_err());
531    }
532
533    #[test]
534    fn the_numerologies_of_lin_et_al_and_of_lte() {
535        // Lin et al. 2019, Sec. II: 2^mu 15 kHz, 2^mu slots of 14 symbols per subframe
536        for mu in 0..=4 {
537            let nr = Numerology::new(mu, false).unwrap();
538            assert_eq!(nr.subcarrier_spacing_hz(), 15e3 * f64::from(1u32 << mu));
539            assert_eq!((nr.slots_per_subframe(), nr.symbols_per_slot()), (1 << mu, 14));
540            // a subframe lasts 1 ms at every FFT size
541            for n_fft in [256, 2048, 4096] {
542                let total: usize = nr.cp_lengths(n_fft).unwrap().iter().map(|c| c + n_fft).sum();
543                assert!((total as f64 / nr.sample_rate(n_fft) - 1e-3).abs() < 1e-15);
544            }
545        }
546        // LTE (Astély et al. 2009): 15 kHz, 4.7 µs and 16.7 µs prefixes, 14 or 12 symbols
547        let lte = Numerology::new(0, false).unwrap();
548        let fs = lte.sample_rate(2048);
549        assert_eq!(fs, 30.72e6);
550        let cp = lte.cp_lengths(2048).unwrap();
551        assert_eq!((cp[0], cp[1], cp[7]), (160, 144, 160));
552        assert!((cp[1] as f64 / fs - 4.7e-6).abs() < 0.05e-6);
553        let ext = Numerology::new(0, true).unwrap();
554        assert_eq!(ext.symbols_per_slot(), 12);
555        assert!((ext.cp_lengths(2048).unwrap()[0] as f64 / fs - 16.7e-6).abs() < 0.05e-6);
556        assert!(Numerology::new(5, false).is_err() && Numerology::new(1, true).is_err());
557        assert!(lte.cp_lengths(100).is_err());
558    }
559
560    #[test]
561    fn the_ssb_of_lin_et_al() {
562        // 4 symbols, 240 subcarriers; PSS and SSS of length 127, BPSK; 1008 identities
563        let id = 421;
564        let count = |k| ssb_indices(id, k).len();
565        assert_eq!((count(SsbElement::Pss), count(SsbElement::Sss)), (127, 127));
566        assert_eq!((count(SsbElement::PbchDmrs), count(SsbElement::Pbch)), (144, 432));
567        // the m-sequence of the PSS: 64 ones and 63 minus ones; the three PSS nearly
568        // orthogonal
569        let p: Vec<Vec<f64>> = (0..3).map(|n| pss(n).unwrap()).collect();
570        assert_eq!(p[0].iter().filter(|v| **v < 0.0).count(), 64);
571        for (a, b) in [(0, 1), (0, 2), (1, 2)] {
572            let c: f64 = p[a].iter().zip(&p[b]).map(|(x, y)| x * y).sum();
573            assert_eq!(c, -1.0);
574        }
575        // 1008 distinct SSS-PSS pairs
576        let mut seen = std::collections::HashSet::new();
577        for n1 in 0..336 {
578            for n2 in 0..3 {
579                let s: Vec<i8> = sss(n1, n2).unwrap().iter().map(|v| *v as i8).collect();
580                assert!(seen.insert((s, n2)));
581            }
582        }
583        assert!(pss(3).is_err() && sss(336, 0).is_err() && pbch_dmrs(1008, 0).is_err());
584    }
585
586    #[test]
587    fn a_slot_modulates_and_the_dmrs_table_fits_it() {
588        let nr = Numerology::new(1, false).unwrap();
589        let (n_rb, n_fft) = (24, 512);
590        let k = SUBCARRIERS_PER_RB * n_rb;
591        let grid: Vec<C64> = (0..14 * k).map(|i| C64::new(((i * 7) % 3) as f64 - 1.0, 1.0) * FRAC_1_SQRT_2).collect();
592        let carrier = Carrier { numerology: nr, n_subcarriers: k, n_fft, first_symbol: 0 };
593        let x = carrier.modulate(&grid).unwrap();
594        let cp = nr.cp_lengths(n_fft).unwrap();
595        assert_eq!(x.len(), 14 * n_fft + cp[..14].iter().sum::<usize>());
596        // the prefix repeats the end of its symbol
597        assert!((0..cp[0]).all(|i| (x[i] - x[i + n_fft]).norm() < 1e-12));
598        // the demodulator gives the grid back, and refuses a partial symbol
599        let back = carrier.demodulate(&x).unwrap();
600        assert!(back.iter().zip(&grid).all(|(a, b)| (a - b).norm() < 1e-12));
601        assert!(carrier.demodulate(&x[..x.len() - 1]).is_err());
602        assert!(matches!(carrier.modulate(&grid[1..]), Err(comnum_core::Error::Shape(_))));
603        let dmrs = PdschDmrs { config_type: 1, type_a_position: 2, additional_position: 2, n_id: 1, slot: 3 };
604        assert_eq!(dmrs.symbols().unwrap(), vec![2, 7, 11]);
605        let t = dmrs.pilot_table(n_rb).unwrap();
606        assert_eq!(t.len(), 3 * k / 2);
607        let t2 = PdschDmrs { config_type: 2, ..dmrs }.pilot_table(n_rb).unwrap();
608        assert_eq!(t2.len(), 3 * k / 3);
609        assert!(PdschDmrs { type_a_position: 3, additional_position: 3, ..dmrs }.symbols().is_err());
610    }
611
612    #[test]
613    fn the_symbols_are_those_of_the_direct_ifft() {
614        // the subcarrier i at the bin (i - K/2) mod N, the IFFT scaled by N/√K, then the
615        // prefix: the samples of Ofdm::to_time to the last bit
616        let nr = Numerology::new(2, false).unwrap();
617        let (k, n_fft, first) = (36, 128, 5);
618        let grid: Vec<C64> = (0..6 * k).map(|i| C64::new(((i * 7) % 13) as f64 - 6.0, ((i * 3) % 5) as f64)).collect();
619        let cp = nr.cp_lengths(n_fft).unwrap();
620        let mut expected = Vec::new();
621        for (s, sym) in grid.chunks_exact(k).enumerate() {
622            let mut bins = vec![C64::new(0.0, 0.0); n_fft];
623            for (i, v) in sym.iter().enumerate() {
624                bins[(i + n_fft - k / 2) % n_fft] = *v;
625            }
626            let scale = (n_fft as f64) / (k as f64).sqrt();
627            let t: Vec<C64> = comnum_math::fft::ifft(&bins).into_iter().map(|v| v * scale).collect();
628            comnum_dsp::signal::extend_with_prefix(&mut expected, &t, cp[(first + s) % cp.len()]).unwrap();
629        }
630        let carrier = Carrier { numerology: nr, n_subcarriers: k, n_fft, first_symbol: first };
631        assert_eq!(carrier.modulate(&grid).unwrap(), expected);
632        let odd = Carrier { n_subcarriers: 35, ..carrier };
633        assert!(odd.modulate(&grid).is_err() && Carrier { n_subcarriers: 256, ..carrier }.modulate(&grid).is_err());
634        assert!(matches!(detect_cell_id(&grid[..10]), Err(comnum_core::Error::Shape(_))));
635    }
636}