/// Reciprocal PLL. /// /// Consumes noisy, quantized timestamps of a reference signal and reconstructs /// the phase and frequency of the update() invocations with respect to (and in units of /// 1 << 32 of) that reference. /// In other words, `update()` rate ralative to reference frequency, /// `u32::MAX` corresponding to both being equal. #[derive(Copy, Clone, Default)] pub struct RPLL { dt2: u8, // 1 << dt2 is the counter rate to update() rate ratio x: i32, // previous timestamp ff: u32, // current frequency estimate from frequency loop f: u32, // current frequency estimate from both frequency and phase loop y: i32, // current phase estimate } impl RPLL { /// Create a new RPLL instance. /// /// Args: /// * dt2: inverse update() rate. 1 << dt2 is the counter rate to update() rate ratio. /// /// Returns: /// Initialized RPLL instance. pub fn new(dt2: u8) -> Self { Self { dt2, ..Default::default() } } /// Advance the RPLL and optionally supply a new timestamp. /// /// Args: /// * input: Optional new timestamp (wrapping around at the i32 boundary). /// There can be at most one timestamp per `update()` cycle (1 << dt2 counter cycles). /// * shift_frequency: Frequency lock settling time. 1 << shift_frequency is /// frequency lock settling time in counter periods. The settling time must be larger /// than the signal period to lock to. /// * shift_phase: Phase lock settling time. Usually one less than /// `shift_frequency` (see there). /// /// Returns: /// A tuple containing the current phase (wrapping at the i32 boundary, pi) and /// frequency. pub fn update( &mut self, input: Option, shift_frequency: u8, shift_phase: u8, ) -> (i32, u32) { debug_assert!(shift_frequency >= self.dt2); debug_assert!(shift_phase >= self.dt2); // Advance phase self.y = self.y.wrapping_add(self.f as i32); if let Some(x) = input { // Reference period in counter cycles let dx = x.wrapping_sub(self.x); // Store timestamp for next time. self.x = x; // Phase using the current frequency estimate let p_sig_64 = self.ff as u64 * dx as u64; // Add half-up rounding bias and apply gain/attenuation let p_sig = ((p_sig_64 + (1u32 << (shift_frequency - 1)) as u64) >> shift_frequency) as u32; // Reference phase (1 << dt2 full turns) with gain/attenuation applied let p_ref = 1u32 << (32 + self.dt2 - shift_frequency); // Update frequency lock self.ff = self.ff.wrapping_add(p_ref.wrapping_sub(p_sig)); // Time in counter cycles between timestamp and "now" let dt = (x.wrapping_neg() & ((1 << self.dt2) - 1)) as u32; // Reference phase estimate "now" let y_ref = (self.f >> self.dt2).wrapping_mul(dt) as i32; // Phase error with gain let dy = y_ref.wrapping_sub(self.y) >> (shift_phase - self.dt2); // Current frequency estimate from frequency lock and phase error self.f = self.ff.wrapping_add(dy as u32); } (self.y, self.f) } } #[cfg(test)] mod test { use super::RPLL; use ndarray::prelude::*; use rand::{prelude::*, rngs::StdRng}; use std::vec::Vec; #[test] fn make() { let _ = RPLL::new(8); } struct Harness { rpll: RPLL, shift_frequency: u8, shift_phase: u8, noise: i32, period: i32, next: i32, next_noisy: i32, time: i32, rng: StdRng, } impl Harness { fn default() -> Self { Self { rpll: RPLL::new(8), shift_frequency: 9, shift_phase: 8, noise: 0, period: 333, next: 111, next_noisy: 111, time: 0, rng: StdRng::seed_from_u64(42), } } fn run(&mut self, n: usize) -> (Vec, Vec) { assert!(self.period >= 1 << self.rpll.dt2); assert!(self.period < 1 << self.shift_frequency); assert!(self.period < 1 << self.shift_phase + 1); let mut y = Vec::::new(); let mut f = Vec::::new(); for _ in 0..n { let timestamp = if self.time - self.next_noisy >= 0 { assert!(self.time - self.next_noisy < 1 << self.rpll.dt2); self.next = self.next.wrapping_add(self.period); let timestamp = self.next_noisy; let p_noise = self.rng.gen_range(-self.noise..=self.noise); self.next_noisy = self.next.wrapping_add(p_noise); Some(timestamp) } else { None }; let (yi, fi) = self.rpll.update( timestamp, self.shift_frequency, self.shift_phase, ); let y_ref = (self.time.wrapping_sub(self.next) as i64 * (1i64 << 32) / self.period as i64) as i32; // phase error y.push(yi.wrapping_sub(y_ref) as f32 / 2f32.powi(32)); let p_ref = 1 << 32 + self.rpll.dt2; let p_sig = fi as u64 * self.period as u64; // relative frequency error f.push( p_sig.wrapping_sub(p_ref) as i64 as f32 / 2f32.powi(32 + self.rpll.dt2 as i32), ); // advance time self.time = self.time.wrapping_add(1 << self.rpll.dt2); } (y, f) } fn measure(&mut self, n: usize, limits: [f32; 4]) { let t_settle = (1 << self.shift_frequency - self.rpll.dt2 + 4) + (1 << self.shift_phase - self.rpll.dt2 + 4); self.run(t_settle); let (y, f) = self.run(n); let y = Array::from(y); let f = Array::from(f); // println!("{:?} {:?}", f, y); let fm = f.mean().unwrap(); let fs = f.std_axis(Axis(0), 0.).into_scalar(); let ym = y.mean().unwrap(); let ys = y.std_axis(Axis(0), 0.).into_scalar(); println!("f: {:.2e}±{:.2e}; y: {:.2e}±{:.2e}", fm, fs, ym, ys); let m = [fm, fs, ym, ys]; print!("relative: "); for i in 0..m.len() { let rel = m[i].abs() / limits[i].abs(); print!("{:.2e} ", rel); assert!( rel <= 1., "idx {}, have |{:.2e}| > limit {:.2e}", i, m[i], limits[i] ); } println!(); } } #[test] fn default() { let mut h = Harness::default(); h.measure(1 << 16, [1e-11, 4e-8, 2e-8, 2e-8]); } #[test] fn noisy() { let mut h = Harness::default(); h.noise = 10; h.shift_frequency = 23; h.shift_phase = 22; h.measure(1 << 16, [3e-9, 3e-6, 4e-4, 2e-4]); } #[test] fn narrow_fast() { let mut h = Harness::default(); h.period = 990; h.next = 351; h.next_noisy = h.next; h.noise = 5; h.shift_frequency = 23; h.shift_phase = 22; h.measure(1 << 16, [2e-9, 2e-6, 1e-3, 1e-4]); } #[test] fn narrow_slow() { let mut h = Harness::default(); h.period = 1818181; h.next = 35281; h.next_noisy = h.next; h.noise = 1000; h.shift_frequency = 23; h.shift_phase = 22; h.measure(1 << 16, [2e-5, 6e-4, 2e-4, 2e-4]); } #[test] fn wide_fast() { let mut h = Harness::default(); h.period = 990; h.next = 351; h.next_noisy = h.next; h.noise = 5; h.shift_frequency = 10; h.shift_phase = 9; h.measure(1 << 16, [5e-7, 3e-2, 2e-5, 2e-2]); } #[test] fn wide_slow() { let mut h = Harness::default(); h.period = 1818181; h.next = 35281; h.next_noisy = h.next; h.noise = 1000; h.shift_frequency = 21; h.shift_phase = 20; h.measure(1 << 16, [2e-4, 6e-3, 2e-4, 2e-3]); } #[test] fn batch_fast_narrow() { let mut h = Harness::default(); h.rpll.dt2 = 8 + 3; h.period = 2431; h.next = 35281; h.next_noisy = h.next; h.noise = 100; h.shift_frequency = 23; h.shift_phase = 23; h.measure(1 << 16, [1e-8, 2e-5, 6e-4, 6e-4]); } }