Skip to main content

comnum_spec/blocks/
systems.rs

1//! Blocks of the standard-compliant chains of `comnum_systems` (ADR 0010).
2//!
3//! | Block | Does |
4//! |---|---|
5//! | `dvbs2_tx` | DVB-S2: draws the data of `frames` BBFRAMEs (generic continuous stream), encodes them (BCH, LDPC), interleaves, maps and frames them (PLHEADER, pilots, scrambling): the PLFRAME symbols at 1 sample per symbol |
6//! | `dvbs2_rx` | DVB-S2: descrambles the frame-aligned PLFRAMEs, removes the headers and the pilots, demaps with the noise variance of the channel, decodes, checks the BBHEADER |
7//!
8//! Every symbol of a PLFRAME has unit energy: `{type: awgn, snr_db: …, reference: unit}`
9//! sets $E_{s}/N_{0}$, the abscissa of Table 13 of the standard. The receiver records the
10//! MODCOD errors of the PLHEADER decoder (`dvbs2_rx.plheader_errors`) and the CRC-8
11//! failures of the BBHEADERs (`dvbs2_rx.crc_failures`).
12//!
13//! # Example
14//!
15//! ```yaml
16//! link:
17//!   - {type: dvbs2_tx, modulation: 8PSK, rate: 3/5, frame: short}
18//!   - {type: awgn, snr_db: $esn0_db, reference: unit}
19//!   - {type: dvbs2_rx, modulation: 8PSK, rate: 3/5, frame: short}
20//! metrics: {report: [coded_ber, bler]}
21//! ```
22//!
23//! # References
24//!
25//! 1. ETSI EN 302 307-1 V1.4.1, *DVB-S2*, 2014 (see `comnum_systems::dvbs2`).
26
27use super::{Block, Coding, Extension, Frame};
28use comnum_core::rng::Rng;
29use comnum_core::{Error, Result, ensure};
30use comnum_math::Jacobian;
31use comnum_systems::dvbs2::{Dvbs2, FrameSize, Modcod, PLHEADER_LEN, bbframe, bbframe_data, decode_plheader};
32
33/// The blocks of this module: their documentation and their builders.
34pub(super) fn blocks() -> Vec<Extension> {
35    vec![
36        builtin!(
37            start "dvbs2_tx",
38            "DVB-S2 transmitter: BBFRAME, BCH and LDPC, bit interleaving, mapping, PL framing.",
39            Dvbs2Spec,
40            needs [],
41            gives [Samples, Payload],
42            |q, _, _| Ok(Box::new(Dvbs2Tx(Dvbs2Link::new(&q)?))),
43        ),
44        builtin!(
45            "dvbs2_rx",
46            "DVB-S2 receiver of frame-aligned PLFRAMEs: descrambling, demapping, decoding.",
47            Dvbs2Spec,
48            needs [Samples, Noise],
49            gives [Decoded],
50            |q, label, _| Ok(Box::new(Dvbs2Rx { link: Dvbs2Link::new(&q)?, label })),
51        ),
52    ]
53}
54
55block_params! {
56    struct Dvbs2Spec {
57        modulation: String ["QPSK"] one_of ["QPSK", "8PSK", "16APSK", "32APSK"], "modulation";
58        rate: String ["1/2"]
59            one_of ["1/4", "1/3", "2/5", "1/2", "3/5", "2/3", "3/4", "4/5", "5/6", "8/9", "9/10"],
60            "code rate (Table 12)";
61        frame: String ["normal"] one_of ["normal", "short", "64800", "16200"],
62            "FECFRAME of 64 800 bits (normal) or 16 200 bits (short)";
63        pilots: bool [true], "pilot blocks of 36 symbols every 16 slots";
64        scrambling_code: usize [0] <= 262_141, "number n of the Gold scrambling sequence";
65        frames: usize [1] >= 1, "PLFRAMEs per trial (dvbs2_tx; the receiver reads it from the frame)";
66        symbol_rate: f64 [1.0] "Bd" > 0, "symbol rate";
67        iterations: usize [50] >= 1, "LDPC iterations (sum-product, layered)";
68        max_log: bool [false], "max-log demapping instead of the exact LLRs";
69    }
70}
71
72struct Dvbs2Link {
73    link: Dvbs2,
74    frames: usize,
75    symbol_rate: f64,
76    max_log: bool,
77}
78
79impl Dvbs2Link {
80    fn new(p: &Dvbs2Spec) -> Result<Dvbs2Link> {
81        let frame: FrameSize = p.frame.parse().map_err(|e: Error| e.context("dvbs2"))?;
82        let mut link = Dvbs2::new(Modcod::from_name(&p.modulation, &p.rate)?, frame, p.pilots, p.scrambling_code)?;
83        link.fec_mut().ldpc_mut().decoder.max_iterations = p.iterations;
84        Ok(Dvbs2Link { link, frames: p.frames, symbol_rate: p.symbol_rate, max_log: p.max_log })
85    }
86}
87
88struct Dvbs2Tx(Dvbs2Link);
89
90impl Block for Dvbs2Tx {
91    fn apply(&self, s: &mut Frame, rng: &mut Rng) -> Result<()> {
92        let l = &self.0;
93        let k = l.link.k();
94        let data: Vec<u8> = (0..(k - 80) * l.frames).map(|_| rng.bit()).collect();
95        let mut samples = Vec::with_capacity(l.link.plframe_len() * l.frames);
96        for chunk in data.chunks(k - 80) {
97            samples.extend(l.link.modulate(&bbframe(chunk, k, 0)?)?);
98        }
99        let bits_per_symbol = l.link.modcod().modulation().bits_per_symbol();
100        *s = Frame {
101            trial: s.trial,
102            coding: Coding {
103                code_rate: Some(data.len() as f64 / (samples.len() * bits_per_symbol) as f64),
104                payload: data,
105                n_blocks: l.frames,
106                ..Coding::default()
107            },
108            sps: 1.0,
109            symbol_rate: l.symbol_rate,
110            ..Frame::default()
111        };
112        s.samples = samples;
113        Ok(())
114    }
115}
116
117struct Dvbs2Rx {
118    link: Dvbs2Link,
119    label: String,
120}
121
122impl Block for Dvbs2Rx {
123    fn apply(&self, s: &mut Frame, _: &mut Rng) -> Result<()> {
124        let l = &self.link;
125        // the variance the receiver declares, the channel's, or the mean of the
126        // per-symbol variances of a detector
127        let per_symbol = &s.receiver.symbol_noise;
128        let var = s
129            .receiver
130            .noise_var
131            .or(s.channel.noise_var)
132            .or((!per_symbol.is_empty()).then(|| per_symbol.iter().sum::<f64>() / per_symbol.len() as f64))
133            .ok_or_else(|| {
134                Error::Config("dvbs2_rx needs the noise variance: add a noise block (awgn) before it".into())
135            })?;
136        let len = l.link.plframe_len();
137        let frames = s.coding.n_blocks.max(1);
138        ensure(s.samples.len() >= len * frames, || {
139            format!("dvbs2_rx: {} samples for {frames} PLFRAMEs of {len} symbols", s.samples.len())
140        })?;
141        let number = l.link.modcod().number();
142        let (mut decoded, mut header_errors, mut crc_failures) = (Vec::new(), 0usize, 0usize);
143        for f in 0..frames {
144            let rx = &s.samples[f * len..(f + 1) * len];
145            if decode_plheader(&rx[..PLHEADER_LEN])?.modcod != number {
146                header_errors += 1;
147            }
148            let bb = l.link.fec().decode(&l.link.llr(rx, var, Jacobian::from_max_log(l.max_log))?)?;
149            let (data, crc_ok) = bbframe_data(&bb)?;
150            crc_failures += usize::from(!crc_ok);
151            decoded.extend(data);
152        }
153        s.record(&self.label, "plheader_errors", header_errors as f64);
154        s.record(&self.label, "crc_failures", crc_failures as f64);
155        s.coding.decoded = decoded;
156        Ok(())
157    }
158}
159
160#[cfg(test)]
161mod tests {
162    use super::*;
163    use crate::blocks::build;
164    use serde_json::json;
165
166    #[test]
167    fn a_clean_channel_decodes_every_frame() {
168        let ctx = crate::blocks::BuildContext::default();
169        let p = json!({"modulation": "16APSK", "rate": "3/4", "frame": "short", "frames": 2});
170        let mut q = p.clone();
171        q["type"] = json!("dvbs2_tx");
172        let tx = build(&q, &ctx).unwrap();
173        q["type"] = json!("dvbs2_rx");
174        let rx = build(&q, &ctx).unwrap();
175        let mut s = Frame::default();
176        let mut rng = Rng::new(1);
177        tx.apply(&mut s, &mut rng).unwrap();
178        s.channel.noise_var = Some(1e-3);
179        rx.apply(&mut s, &mut rng).unwrap();
180        assert_eq!(s.coding.decoded, s.coding.payload);
181        assert_eq!(s.coding.payload.len(), 2 * (11712 - 80));
182    }
183}