use super::bpsk::BpskDecider;
use super::qam::{Qam16Decider, Qam64Decider, Qam256Decider, QamDecider};
use super::qpsk::QpskDecider;
use crate::core::{Block, WorkReport};
use crate::modulate::qam::{axis_scale, build_axis_table};
use crate::modulate::{ConstellationOrder, OfdmConfig};
use crate::multicarrier::{CarrierGrid, GridExtract, SymbolFft};
use crate::sync::ofdm_sync::training_symbol_freq_pattern;
use num_complex::Complex32 as C32;
pub struct OfdmDemod {
samples_per_symbol: usize,
num_data_carriers: usize,
gain: f32,
symbol_fft: SymbolFft,
grid_extract: GridExtract,
}
impl OfdmDemod {
pub fn new(cfg: &OfdmConfig) -> Self {
let grid = CarrierGrid::from_plan(&cfg.carrier_plan);
let n_fft = cfg.carrier_plan.n_fft();
let cp_len = cfg.carrier_plan.cp_len();
let num_data_carriers = grid.num_data_carriers();
Self {
samples_per_symbol: cfg.samples_per_ofdm_symbol(),
num_data_carriers,
gain: 1.0,
symbol_fft: SymbolFft::new(n_fft, cp_len).with_window_backoff(cfg.rx_window_backoff),
grid_extract: GridExtract::new(grid),
}
}
pub fn set_gain(&mut self, g: f32) {
self.gain = g;
}
pub fn num_data_carriers(&self) -> usize {
self.num_data_carriers
}
pub fn samples_per_symbol(&self) -> usize {
self.samples_per_symbol
}
}
impl Block for OfdmDemod {
type In = C32;
type Out = C32;
fn process(&mut self, input: &[C32], output: &mut [C32]) -> WorkReport {
if input.len() < self.samples_per_symbol || output.len() < self.num_data_carriers {
return WorkReport::default();
}
let freq = match self
.symbol_fft
.demod_symbol(&input[..self.samples_per_symbol])
{
Some(f) => f,
None => return WorkReport::default(),
};
let grid_wr = self.grid_extract.process(freq, output);
debug_assert_eq!(grid_wr.out_written, self.num_data_carriers);
let g = self.gain;
if (g - 1.0).abs() > f32::EPSILON {
for s in output[..self.num_data_carriers].iter_mut() {
*s = C32::new(g * s.re, g * s.im);
}
}
WorkReport {
in_read: self.samples_per_symbol,
out_written: self.num_data_carriers,
}
}
}
enum DeciderKind {
Bpsk(BpskDecider),
Qpsk(QpskDecider),
Qam16(Qam16Decider),
Qam64(Qam64Decider),
Qam256(Qam256Decider),
}
impl DeciderKind {
fn new(order: ConstellationOrder) -> Self {
match order {
ConstellationOrder::Bpsk => DeciderKind::Bpsk(BpskDecider::new()),
ConstellationOrder::Qpsk => DeciderKind::Qpsk(QpskDecider::new()),
ConstellationOrder::Qam16 => DeciderKind::Qam16(QamDecider::new()),
ConstellationOrder::Qam64 => DeciderKind::Qam64(QamDecider::new()),
ConstellationOrder::Qam256 => DeciderKind::Qam256(QamDecider::new()),
}
}
#[inline(always)]
fn process(&mut self, input: &[C32], output: &mut [u8]) -> WorkReport {
match self {
DeciderKind::Bpsk(d) => d.process(input, output),
DeciderKind::Qpsk(d) => d.process(input, output),
DeciderKind::Qam16(d) => d.process(input, output),
DeciderKind::Qam64(d) => d.process(input, output),
DeciderKind::Qam256(d) => d.process(input, output),
}
}
}
pub struct OfdmDecider {
num_data_carriers: usize,
bits_per_ofdm_symbol: usize,
decider: DeciderKind,
}
impl OfdmDecider {
pub fn new(cfg: &OfdmConfig) -> Self {
Self {
num_data_carriers: cfg.carrier_plan.data_carriers().len(),
bits_per_ofdm_symbol: cfg.bits_per_ofdm_symbol(),
decider: DeciderKind::new(cfg.constellation),
}
}
}
impl Block for OfdmDecider {
type In = C32;
type Out = u8;
fn process(&mut self, input: &[C32], output: &mut [u8]) -> WorkReport {
if input.len() < self.num_data_carriers || output.len() < self.bits_per_ofdm_symbol {
return WorkReport::default();
}
self.decider.process(
&input[..self.num_data_carriers],
&mut output[..self.bits_per_ofdm_symbol],
)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct OfdmRxFrame {
pub bits: Vec<u8>,
pub num_symbols: usize,
pub evm_db: Option<f32>,
pub cfo_hz: Option<f32>,
pub timing_offset_samples: Option<i32>,
pub channel_mse: Option<f32>,
pub sync_score: Option<f32>,
pub channel_estimate: Option<Vec<C32>>,
pub inner_fec_ok: Option<bool>,
pub outer_fec_ok: Option<bool>,
pub channel_ber: Option<f32>,
pub inner_ber: Option<f32>,
}
pub fn build_ofdm_rx_frame(cfg: &OfdmConfig, soft_symbols: &[C32], bits: Vec<u8>) -> OfdmRxFrame {
let num_data_carriers = cfg.carrier_plan.data_carriers().len();
let num_symbols = soft_symbols
.len()
.checked_div(num_data_carriers)
.unwrap_or(0);
let evm_db = evm_db(cfg, soft_symbols, &bits, num_symbols);
OfdmRxFrame {
bits,
num_symbols,
evm_db,
cfo_hz: None,
timing_offset_samples: None,
channel_mse: None,
sync_score: None,
channel_estimate: None,
inner_fec_ok: None,
outer_fec_ok: None,
channel_ber: None,
inner_ber: None,
}
}
pub(crate) fn evm_db(
cfg: &OfdmConfig,
soft_symbols: &[C32],
bits: &[u8],
num_symbols: usize,
) -> Option<f32> {
if num_symbols == 0 || soft_symbols.is_empty() {
return None;
}
let mut mapper = crate::modulate::ofdm::ideal_symbol_mapper(cfg.constellation);
let mut ideal = vec![C32::default(); soft_symbols.len()];
let wr = mapper.process(bits, &mut ideal);
if wr.out_written != soft_symbols.len() {
return None;
}
let mut err_energy = 0.0f64;
let mut ref_energy = 0.0f64;
for (s, r) in soft_symbols.iter().zip(ideal.iter()) {
let e = s - r;
err_energy += (e.re * e.re + e.im * e.im) as f64;
ref_energy += (r.re * r.re + r.im * r.im) as f64;
}
if ref_energy <= 0.0 {
return None;
}
Some((10.0 * (err_energy / ref_energy).log10()) as f32)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum EqualizerMethod {
#[default]
TrainingSymbolHold,
PerSymbolPilotInterp,
}
pub struct OfdmEqualizer {
method: EqualizerMethod,
n_fft: usize,
estimate: Vec<C32>,
pilot_bins: Vec<(usize, C32)>,
data_bins: Vec<usize>,
pilot_ratios: Vec<(usize, C32)>,
}
const EQUALIZER_FLOOR: f32 = 1e-6;
impl OfdmEqualizer {
pub fn new(cfg: &OfdmConfig, method: EqualizerMethod) -> Self {
let grid = CarrierGrid::from_plan(&cfg.carrier_plan);
let n_fft = cfg.carrier_plan.n_fft();
let mut pilot_bins = grid.pilot_bins().to_vec();
pilot_bins.sort_by_key(|&(bin, _)| bin);
let n_pilots = pilot_bins.len();
Self {
method,
n_fft,
estimate: vec![C32::new(1.0, 0.0); n_fft],
pilot_bins,
data_bins: grid.data_bins().to_vec(),
pilot_ratios: Vec::with_capacity(n_pilots),
}
}
pub fn method(&self) -> EqualizerMethod {
self.method
}
pub fn set_pilot_bins(&mut self, pilots: &[(usize, C32)], data_bins: &[usize]) {
self.pilot_bins.clear();
self.pilot_bins.extend_from_slice(pilots);
self.pilot_bins.sort_by_key(|&(bin, _)| bin);
self.data_bins.clear();
self.data_bins.extend_from_slice(data_bins);
}
pub fn estimate_from_training_symbol(&mut self, received_freq: &[C32]) {
if self.method != EqualizerMethod::TrainingSymbolHold || received_freq.len() < self.n_fft {
return;
}
let known = training_symbol_freq_pattern(self.n_fft);
for bin in 0..self.n_fft {
self.estimate[bin] = received_freq[bin] / known[bin];
}
}
fn interpolate_from_pilots(&mut self, received_freq: &[C32]) {
if self.pilot_bins.is_empty() {
return;
}
self.pilot_ratios.clear();
self.pilot_ratios.extend(
self.pilot_bins
.iter()
.map(|&(bin, known)| (bin, received_freq[bin] / known)),
);
for &bin in &self.data_bins {
self.estimate[bin] = interpolate_at(&self.pilot_ratios, bin);
}
for &(bin, ratio) in &self.pilot_ratios {
self.estimate[bin] = ratio;
}
}
}
fn interpolate_at(pilots: &[(usize, C32)], bin: usize) -> C32 {
if pilots.len() == 1 {
return pilots[0].1;
}
let hi = pilots.partition_point(|&(pbin, _)| pbin < bin);
if hi == 0 {
return pilots[0].1;
}
if hi == pilots.len() {
return pilots[pilots.len() - 1].1;
}
let (ub, ur) = pilots[hi];
if ub == bin {
return ur;
}
let (lb, lr) = pilots[hi - 1];
let t = (bin - lb) as f32 / (ub - lb) as f32;
lr + (ur - lr) * t
}
impl Block for OfdmEqualizer {
type In = C32;
type Out = C32;
fn process(&mut self, input: &[C32], output: &mut [C32]) -> WorkReport {
if input.len() < self.n_fft || output.len() < self.n_fft {
return WorkReport::default();
}
if self.method == EqualizerMethod::PerSymbolPilotInterp {
self.interpolate_from_pilots(&input[..self.n_fft]);
}
for bin in 0..self.n_fft {
let h = self.estimate[bin];
let mag_sq = h.norm_sqr().max(EQUALIZER_FLOOR);
output[bin] = input[bin] * h.conj() / mag_sq;
}
WorkReport {
in_read: self.n_fft,
out_written: self.n_fft,
}
}
}
#[inline]
pub fn bpsk_soft_llr(v: C32) -> f32 {
4.0 * v.re
}
#[inline]
pub fn qpsk_soft_llr(v: C32) -> [f32; 2] {
let scale = 4.0 * std::f32::consts::SQRT_2;
[scale * v.re, scale * v.im]
}
pub fn qam_axis_soft_llr<const BITS: usize>(v: f32, out: &mut [f32]) {
let k = BITS / 2;
let m = 1usize << k;
let table = build_axis_table(BITS, axis_scale(BITS));
for (b, slot) in out.iter_mut().enumerate().take(k) {
let bit_shift = k - 1 - b;
let mut d0_sq = f32::INFINITY;
let mut d1_sq = f32::INFINITY;
for (gray, &level) in table.iter().enumerate().take(m) {
let d_sq = (v - level) * (v - level);
if (gray >> bit_shift) & 1 == 0 {
d0_sq = d0_sq.min(d_sq);
} else {
d1_sq = d1_sq.min(d_sq);
}
}
*slot = d1_sq - d0_sq;
}
}
pub fn qam_soft_llr<const BITS: usize>(v: C32) -> [f32; 8] {
let k = BITS / 2;
let mut out = [0.0f32; 8];
qam_axis_soft_llr::<BITS>(v.re, &mut out[..k]);
qam_axis_soft_llr::<BITS>(v.im, &mut out[k..2 * k]);
out
}
enum SoftKind {
Bpsk,
Qpsk,
Qam16,
Qam64,
Qam256,
}
impl SoftKind {
fn new(order: ConstellationOrder) -> Self {
match order {
ConstellationOrder::Bpsk => SoftKind::Bpsk,
ConstellationOrder::Qpsk => SoftKind::Qpsk,
ConstellationOrder::Qam16 => SoftKind::Qam16,
ConstellationOrder::Qam64 => SoftKind::Qam64,
ConstellationOrder::Qam256 => SoftKind::Qam256,
}
}
#[inline]
fn llrs_per_symbol(&self) -> usize {
match self {
SoftKind::Bpsk => 1,
SoftKind::Qpsk => 2,
SoftKind::Qam16 => 4,
SoftKind::Qam64 => 6,
SoftKind::Qam256 => 8,
}
}
#[inline]
fn extract(&self, v: C32, out: &mut [f32]) {
match self {
SoftKind::Bpsk => out[0] = bpsk_soft_llr(v),
SoftKind::Qpsk => out[..2].copy_from_slice(&qpsk_soft_llr(v)),
SoftKind::Qam16 => out[..4].copy_from_slice(&qam_soft_llr::<4>(v)[..4]),
SoftKind::Qam64 => out[..6].copy_from_slice(&qam_soft_llr::<6>(v)[..6]),
SoftKind::Qam256 => out[..8].copy_from_slice(&qam_soft_llr::<8>(v)[..8]),
}
}
}
pub struct OfdmSoftDemod {
num_data_carriers: usize,
bits_per_ofdm_symbol: usize,
kind: SoftKind,
}
impl OfdmSoftDemod {
pub fn new(cfg: &OfdmConfig) -> Self {
Self {
num_data_carriers: cfg.carrier_plan.data_carriers().len(),
bits_per_ofdm_symbol: cfg.bits_per_ofdm_symbol(),
kind: SoftKind::new(cfg.constellation),
}
}
}
impl Block for OfdmSoftDemod {
type In = C32;
type Out = f32;
fn process(&mut self, input: &[C32], output: &mut [f32]) -> WorkReport {
if input.len() < self.num_data_carriers || output.len() < self.bits_per_ofdm_symbol {
return WorkReport::default();
}
let llrs_per_symbol = self.kind.llrs_per_symbol();
for (k, &v) in input[..self.num_data_carriers].iter().enumerate() {
self.kind.extract(
v,
&mut output[k * llrs_per_symbol..(k + 1) * llrs_per_symbol],
);
}
WorkReport {
in_read: self.num_data_carriers,
out_written: self.bits_per_ofdm_symbol,
}
}
}