idsp/
rpll.rs

1use crate::Accu;
2use core::num::Wrapping as W;
3use dsp_process::SplitProcess;
4
5/// Reciprocal PLL.
6///
7/// Consumes noisy, quantized timestamps of a reference signal and reconstructs
8/// the phase and frequency of the update() invocations with respect to (and in units of
9/// 1 << 32 of) that reference.
10/// In other words, `update()` rate relative to reference frequency,
11/// `u32::MAX + 1` corresponding to both being equal.
12#[derive(Copy, Clone, Default)]
13pub struct RPLL {
14    x: W<i32>,  // previous timestamp
15    ff: W<u32>, // current frequency estimate from frequency loop
16    f: W<u32>,  // current frequency estimate from both frequency and phase loop
17    y: W<i32>,  // current phase estimate
18}
19
20/// RPLL configuration
21#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
22pub struct RPLLConfig {
23    /// 1 << dt2 is the counter rate to update() rate ratio
24    pub dt2: u8,
25    /// Frequency lock settling time.
26    ///
27    /// `1 << shift_frequency` is
28    /// frequency lock settling time in counter periods. The settling time must be larger
29    /// than the signal period to lock to.
30    pub shift_frequency: u8,
31    /// Phase lock settling time.
32    ///
33    /// Usually one less than `shift_frequency` (see there).
34    pub shift_phase: u8,
35}
36
37impl SplitProcess<Option<W<i32>>, Accu<W<i32>>, RPLL> for RPLLConfig {
38    /// Advance the RPLL and optionally supply a new timestamp.
39    ///
40    /// * input: Optional new timestamp.
41    ///   There can be at most one timestamp per `update()` cycle (1 << dt2 counter cycles).
42    ///
43    /// Returns:
44    /// A tuple containing the current phase (wrapping at the i32 boundary, pi) and
45    /// frequency.
46    fn process(&self, state: &mut RPLL, x: Option<W<i32>>) -> Accu<W<i32>> {
47        debug_assert!(self.shift_frequency >= self.dt2);
48        debug_assert!(self.shift_phase >= self.dt2);
49        // Advance phase
50        state.y += W(state.f.0 as i32);
51        if let Some(x) = x {
52            // Reference period in counter cycles
53            let dx = x - state.x;
54            // Store timestamp for next time.
55            state.x = x;
56            // Phase using the current frequency estimate
57            let p_sig_64 = W(state.ff.0 as u64 * dx.0 as u64);
58            // Add half-up rounding bias and apply gain/attenuation
59            let p_sig = W(((p_sig_64 + W((1u32 << (self.shift_frequency - 1)) as u64))
60                >> self.shift_frequency as usize)
61                .0 as _);
62            // Reference phase (1 << dt2 full turns) with gain/attenuation applied
63            let p_ref = W(1u32 << (32 + self.dt2 - self.shift_frequency));
64            // Update frequency lock
65            state.ff += p_ref - p_sig;
66            // Time in counter cycles between timestamp and "now"
67            let dt = W((-x & W((1 << self.dt2) - 1)).0 as _);
68            // Reference phase estimate "now"
69            let y_ref = W(((state.f >> self.dt2 as usize) * dt).0 as _);
70            // Phase error with gain
71            let dy = (y_ref - state.y) >> (self.shift_phase - self.dt2) as usize;
72            // Current frequency estimate from frequency lock and phase error
73            state.f = state.ff + W(dy.0 as u32);
74        }
75        Accu::new(state.y, W(state.f.0 as _))
76    }
77}
78
79impl RPLL {
80    /// Return the current phase estimate
81    pub fn phase(&self) -> W<i32> {
82        self.y
83    }
84
85    /// Return the current frequency estimate
86    pub fn frequency(&self) -> W<u32> {
87        self.f
88    }
89}
90
91#[cfg(test)]
92mod test {
93    use super::{RPLL, RPLLConfig};
94    use core::num::Wrapping as W;
95    use dsp_process::SplitProcess;
96    use rand::{prelude::*, rngs::StdRng};
97    use std::vec::Vec;
98
99    #[test]
100    fn make() {
101        let _ = RPLL::default();
102    }
103
104    struct Harness {
105        rpll: RPLL,
106        rpll_config: RPLLConfig,
107        noise: i32,
108        period: i32,
109        next: W<i32>,
110        next_noisy: W<i32>,
111        time: W<i32>,
112        rng: StdRng,
113    }
114
115    impl Harness {
116        fn default() -> Self {
117            Self {
118                rpll: RPLL::default(),
119                rpll_config: RPLLConfig {
120                    dt2: 8,
121                    shift_frequency: 9,
122                    shift_phase: 8,
123                },
124                noise: 0,
125                period: 333,
126                next: W(111),
127                next_noisy: W(111),
128                time: W(0),
129                rng: StdRng::seed_from_u64(42),
130            }
131        }
132
133        fn run(&mut self, n: usize) -> (Vec<f32>, Vec<f32>) {
134            assert!(self.period >= 1 << self.rpll_config.dt2);
135            assert!(self.period < 1 << self.rpll_config.shift_frequency);
136            assert!(self.period < 1 << self.rpll_config.shift_phase + 1);
137
138            let mut y = Vec::<f32>::new();
139            let mut f = Vec::<f32>::new();
140            for _ in 0..n {
141                let timestamp = if self.time - self.next_noisy >= W(0) {
142                    assert!(self.time - self.next_noisy < W(1 << self.rpll_config.dt2));
143                    self.next += self.period;
144                    let timestamp = self.next_noisy;
145                    let p_noise = self.rng.random_range(-self.noise..=self.noise);
146                    self.next_noisy = self.next + W(p_noise);
147                    Some(timestamp)
148                } else {
149                    None
150                };
151                let _accu = self.rpll_config.process(&mut self.rpll, timestamp);
152                let (yi, fi) = (self.rpll.phase(), self.rpll.frequency());
153
154                let y_ref = W(
155                    ((self.time - self.next).0 as i64 * (1i64 << 32) / self.period as i64) as i32,
156                );
157                // phase error
158                y.push((yi - y_ref).0 as f32 / 2f32.powi(32));
159
160                let p_ref = 1 << 32 + self.rpll_config.dt2;
161                let p_sig = fi.0 as u64 * self.period as u64;
162                // relative frequency error
163                f.push(
164                    p_sig.wrapping_sub(p_ref) as i64 as f32
165                        / 2f32.powi(32 + self.rpll_config.dt2 as i32),
166                );
167
168                // advance time
169                self.time += W(1 << self.rpll_config.dt2);
170            }
171            (y, f)
172        }
173
174        fn measure(&mut self, n: usize, limits: [f32; 4]) {
175            let t_settle = (1 << self.rpll_config.shift_frequency - self.rpll_config.dt2 + 4)
176                + (1 << self.rpll_config.shift_phase - self.rpll_config.dt2 + 4);
177            self.run(t_settle);
178
179            let (y, f) = self.run(n);
180            // println!("{:?} {:?}", f, y);
181
182            let fm = f.iter().copied().sum::<f32>() / f.len() as f32;
183            let fs = f.iter().map(|f| (*f - fm).powi(2)).sum::<f32>().sqrt() / f.len() as f32;
184            let ym = y.iter().copied().sum::<f32>() / y.len() as f32;
185            let ys = y.iter().map(|y| (*y - ym).powi(2)).sum::<f32>().sqrt() / y.len() as f32;
186
187            println!("f: {:.2e}±{:.2e}; y: {:.2e}±{:.2e}", fm, fs, ym, ys);
188
189            let m = [fm, fs, ym, ys];
190
191            print!("relative: ");
192            for i in 0..m.len() {
193                let rel = m[i].abs() / limits[i].abs();
194                print!("{:.2e} ", rel);
195                assert!(
196                    rel <= 1.,
197                    "idx {}, have |{:.2e}| > limit {:.2e}",
198                    i,
199                    m[i],
200                    limits[i]
201                );
202            }
203            println!();
204        }
205    }
206
207    #[test]
208    fn default() {
209        let mut h = Harness::default();
210
211        h.measure(1 << 16, [1e-11, 4e-8, 2e-8, 2e-8]);
212    }
213
214    #[test]
215    fn noisy() {
216        let mut h = Harness::default();
217        h.noise = 10;
218        h.rpll_config.shift_frequency = 23;
219        h.rpll_config.shift_phase = 22;
220
221        h.measure(1 << 16, [3e-9, 3e-6, 5e-4, 2e-4]);
222    }
223
224    #[test]
225    fn narrow_fast() {
226        let mut h = Harness::default();
227        h.period = 990;
228        h.next = W(351);
229        h.next_noisy = h.next;
230        h.noise = 5;
231        h.rpll_config.shift_frequency = 23;
232        h.rpll_config.shift_phase = 22;
233
234        h.measure(1 << 16, [2e-9, 2e-6, 1e-3, 1e-4]);
235    }
236
237    #[test]
238    fn narrow_slow() {
239        let mut h = Harness::default();
240        h.period = 1818181;
241        h.next = W(35281);
242        h.next_noisy = h.next;
243        h.noise = 1000;
244        h.rpll_config.shift_frequency = 23;
245        h.rpll_config.shift_phase = 22;
246
247        h.measure(1 << 16, [2e-5, 6e-4, 2e-4, 2e-4]);
248    }
249
250    #[test]
251    fn wide_fast() {
252        let mut h = Harness::default();
253        h.period = 990;
254        h.next = W(351);
255        h.next_noisy = h.next;
256        h.noise = 5;
257        h.rpll_config.shift_frequency = 10;
258        h.rpll_config.shift_phase = 9;
259
260        h.measure(1 << 16, [2e-6, 3e-2, 2e-5, 2e-2]);
261    }
262
263    #[test]
264    fn wide_slow() {
265        let mut h = Harness::default();
266        h.period = 1818181;
267        h.next = W(35281);
268        h.next_noisy = h.next;
269        h.noise = 1000;
270        h.rpll_config.shift_frequency = 21;
271        h.rpll_config.shift_phase = 20;
272
273        h.measure(1 << 16, [2e-4, 6e-3, 2e-4, 2e-3]);
274    }
275
276    #[test]
277    fn batch_fast_narrow() {
278        let mut h = Harness::default();
279        h.rpll_config.dt2 = 8 + 3;
280        h.period = 2431;
281        h.next = W(35281);
282        h.next_noisy = h.next;
283        h.noise = 100;
284        h.rpll_config.shift_frequency = 23;
285        h.rpll_config.shift_phase = 23;
286
287        h.measure(1 << 16, [1e-8, 2e-5, 6e-4, 6e-4]);
288    }
289}