1use 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
90pub const SUBCARRIERS_PER_RB: usize = 12;
92pub const SSB_SUBCARRIERS: usize = 240;
94pub const SSB_SYMBOLS: usize = 4;
96pub const SYNC_LENGTH: usize = 127;
98pub const SYNC_FIRST_SUBCARRIER: usize = 56;
100
101#[derive(Clone, Copy, Debug, PartialEq, Eq)]
103pub struct Numerology {
104 pub mu: u32,
106 pub extended_cp: bool,
109}
110
111impl Numerology {
112 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 pub fn subcarrier_spacing_hz(&self) -> f64 {
126 15e3 * f64::from(1u32 << self.mu)
127 }
128
129 pub fn slots_per_subframe(&self) -> usize {
131 1 << self.mu
132 }
133
134 pub fn slots_per_frame(&self) -> usize {
136 10 * self.slots_per_subframe()
137 }
138
139 pub fn symbols_per_slot(&self) -> usize {
141 if self.extended_cp { 12 } else { 14 }
142 }
143
144 pub fn sample_rate(&self, n_fft: usize) -> f64 {
146 n_fft as f64 * self.subcarrier_spacing_hz()
147 }
148
149 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#[derive(Clone, Copy, Debug, PartialEq, Eq)]
190pub struct Carrier {
191 pub numerology: Numerology,
193 pub n_subcarriers: usize,
195 pub n_fft: usize,
197 pub first_symbol: usize,
199}
200
201impl Carrier {
202 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 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 fn prefix(cp: &[usize], first: usize, s: usize) -> usize {
224 cp[(first + s) % cp.len()]
225 }
226
227 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 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 pub fn sample_rate(&self) -> f64 {
277 self.numerology.sample_rate(self.n_fft)
278 }
279}
280
281fn bpsk(b: u8) -> f64 {
283 1.0 - 2.0 * f64::from(b)
284}
285
286pub 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
296pub 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
309fn 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
315pub 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#[derive(Clone, Copy, Debug, PartialEq, Eq)]
330pub enum SsbElement {
331 Empty,
333 Pss,
335 Sss,
337 Pbch,
339 PbchDmrs,
341}
342
343impl std::str::FromStr for SsbElement {
344 type Err = Error;
345
346 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
362pub 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
380pub 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
389pub 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
419pub 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 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#[derive(Clone, Copy, Debug, PartialEq, Eq)]
447pub struct PdschDmrs {
448 pub config_type: u8,
450 pub type_a_position: usize,
452 pub additional_position: usize,
454 pub n_id: u32,
456 pub slot: u32,
458}
459
460impl PdschDmrs {
461 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 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 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 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 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 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 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 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 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 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 assert!((0..cp[0]).all(|i| (x[i] - x[i + n_fft]).norm() < 1e-12));
598 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 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}