idsp/
rpll.rs

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