1- use super :: { shift_round , Complex } ;
2- use core:: mem :: swap ;
1+ use super :: Complex ;
2+ use core:: f64 :: consts :: PI ;
33
4- const PHASE_BITS : i32 = 20 ;
5- const LUT_DEPTH : i32 = 8 ;
6- const LUT_SIZE : usize = 1 << LUT_DEPTH as usize ;
7- const OCTANT_BITS : i32 = 3 ;
8- const INTERPOLATION_BITS : i32 = PHASE_BITS - LUT_DEPTH - OCTANT_BITS ;
9- static COSSIN_TABLE : [ ( u16 , u16 ) ; LUT_SIZE ] = include ! ( "cossin_table.txt" ) ;
10-
11- // Approximate pi/4 with an integer multiplier and right bit
12- // shift. The numerator is designed to saturate the i32 range.
13- const PI_4_NUMERATOR : i32 = 50 ;
14- const PI_4_RIGHT_SHIFT : i32 = 6 ;
4+ include ! ( concat!( env!( "OUT_DIR" ) , "/cossin_table.rs" ) ) ;
155
166/// Compute the cosine and sine of an angle.
7+ /// This is ported from the MiSoC cossin core.
8+ /// (https://github.com/m-labs/misoc/blob/master/misoc/cores/cossin.py)
179///
1810/// # Arguments
19- ///
20- /// `phase` - 20-bit fixed-point phase value.
11+ /// * `phase` - 32-bit phase.
2112///
2213/// # Returns
23- ///
2414/// The cos and sin values of the provided phase as a `Complex<i32>`
25- /// value.
15+ /// value. With a 7-bit deep LUT there is 1e-5 max and 6e-8 RMS error
16+ /// in each quadrature over 20 bit phase.
2617pub fn cossin ( phase : i32 ) -> Complex < i32 > {
27- let mut phase = phase ;
28- let octant = (
29- ( phase & ( 1 << ( PHASE_BITS - 1 ) ) ) >> ( PHASE_BITS - 1 ) ,
30- ( phase & ( 1 << ( PHASE_BITS - 2 ) ) ) >> ( PHASE_BITS - 2 ) ,
31- ( phase & ( 1 << ( PHASE_BITS - 3 ) ) ) >> ( PHASE_BITS - 3 ) ,
32- ) ;
33-
34- // Mask off octant bits. This leaves the angle in the range [0,
35- // pi/4).
36- phase &= ( 1 << ( PHASE_BITS - OCTANT_BITS ) ) - 1 ;
37-
38- if octant. 2 == 1 {
18+ // Phase bits excluding the three highes MSB
19+ const OCTANT_BITS : usize = 32 - 3 ;
20+
21+ // This is a slightly more compact way to compute the four flags for
22+ // octant mapping/unmapping used below.
23+ let mut octant = ( phase as u32 ) >> OCTANT_BITS ;
24+ octant ^= octant << 1 ;
25+
26+ // Mask off octant bits. This leaves the angle in the range [0, pi/4).
27+ let mut phase = phase & ( ( 1 << OCTANT_BITS ) - 1 ) ;
28+
29+ if octant & 1 != 0 {
3930 // phase = pi/4 - phase
40- phase = ( 1 << ( INTERPOLATION_BITS + LUT_DEPTH ) ) - 1 - phase;
31+ phase = ( 1 << OCTANT_BITS ) - 1 - phase;
4132 }
4233
43- let interpolation: i32 = phase & ( ( 1 << INTERPOLATION_BITS ) - 1 ) ;
44-
45- phase >>= INTERPOLATION_BITS ;
46-
47- let ( mut cos, mut sin) = {
48- let lookup = COSSIN_TABLE [ phase as usize ] ;
49- (
50- // 1/2 < cos(0<=x<=pi/4) <= 1. So, to spread out the cos
51- // values and use the space more efficiently, we can
52- // subtract 1/2 and multiply by 2. Therefore, we add 1
53- // back in here. The sin values must be multiplied by 2 to
54- // have the same scale as the cos values.
55- lookup. 0 as i32 + u16:: MAX as i32 ,
56- ( lookup. 1 as i32 ) << 1 ,
57- )
58- } ;
59-
60- // The phase values used for the LUT are adjusted up by half the
61- // phase step. The interpolation must accurately reflect this. So,
62- // an interpolation phase offset less than half the maximum
63- // involves a negative phase offset. The rest us a non-negative
64- // phase offset.
65- let interpolation_factor =
66- ( interpolation - ( 1 << ( INTERPOLATION_BITS - 1 ) ) ) * PI_4_NUMERATOR ;
67- let dsin = shift_round (
68- cos * interpolation_factor,
69- LUT_DEPTH + INTERPOLATION_BITS + PI_4_RIGHT_SHIFT ,
70- ) ;
71- let dcos = shift_round (
72- -sin * interpolation_factor,
73- LUT_DEPTH + INTERPOLATION_BITS + PI_4_RIGHT_SHIFT ,
74- ) ;
75-
76- cos += dcos;
77- sin += dsin;
78-
79- if octant. 1 ^ octant. 2 == 1 {
80- swap ( & mut sin, & mut cos) ;
34+ let lookup = COSSIN [ ( phase >> ( OCTANT_BITS - COSSIN_DEPTH ) ) as usize ] ;
35+ // 1/2 < cos(0 <= x <= pi/4) <= 1: Shift the cos
36+ // values and scale the sine values as encoded in the LUT.
37+ let mut cos = lookup. 0 as i32 + u16:: MAX as i32 ;
38+ let mut sin = ( lookup. 1 as i32 ) << 1 ;
39+
40+ // 16 + 1 bits for cos/sin and 15 for dphi to saturate the i32 range.
41+ const ALIGN_MSB : usize = 32 - 16 - 1 ;
42+ phase >>= OCTANT_BITS - COSSIN_DEPTH - ALIGN_MSB ;
43+ phase &= ( 1 << ALIGN_MSB ) - 1 ;
44+ // The phase values used for the LUT are at midpoint for the truncated phase.
45+ // Interpolate relative to the LUT entry midpoint.
46+ phase -= ( 1 << ( ALIGN_MSB - 1 ) ) - ( octant & 1 ) as i32 ;
47+ // Fixed point pi/4.
48+ const PI4 : i32 = ( PI / 4. * ( 1 << ( 32 - ALIGN_MSB ) ) as f64 ) as i32 ;
49+ // No rounding bias necessary here since we keep enough low bits.
50+ let dphi = ( phase * PI4 ) >> ( 32 - ALIGN_MSB ) ;
51+
52+ // Make room for the sign bit.
53+ let dcos = ( sin * dphi) >> ( COSSIN_DEPTH + 1 ) ;
54+ let dsin = ( cos * dphi) >> ( COSSIN_DEPTH + 1 ) ;
55+
56+ cos = ( cos << ( ALIGN_MSB - 1 ) ) - dcos;
57+ sin = ( sin << ( ALIGN_MSB - 1 ) ) + dsin;
58+
59+ // Unmap using octant bits.
60+ if octant & 2 != 0 {
61+ core:: mem:: swap ( & mut sin, & mut cos) ;
8162 }
8263
83- if octant. 0 ^ octant . 1 == 1 {
64+ if octant & 4 != 0 {
8465 cos *= -1 ;
8566 }
8667
87- if octant. 0 == 1 {
68+ if octant & 8 != 0 {
8869 sin *= -1 ;
8970 }
9071
@@ -94,35 +75,71 @@ pub fn cossin(phase: i32) -> Complex<i32> {
9475#[ cfg( test) ]
9576mod tests {
9677 use super :: * ;
97- use core:: f64:: consts:: PI ;
98-
9978 #[ test]
10079 fn error_max_rms_all_phase ( ) {
101- let max_amplitude: f64 = ( ( 1 << 15 ) - 1 ) as f64 ;
80+ // Constant amplitude error due to LUT data range.
81+ const AMPLITUDE : f64 = ( ( 1i64 << 31 ) - ( 1i64 << 15 ) ) as f64 ;
82+ const MAX_PHASE : f64 = ( 1i64 << 32 ) as f64 ;
10283 let mut rms_err: Complex < f64 > = ( 0. , 0. ) ;
84+ let mut sum_err: Complex < f64 > = ( 0. , 0. ) ;
85+ let mut max_err: Complex < f64 > = ( 0. , 0. ) ;
86+ let mut sum: Complex < f64 > = ( 0. , 0. ) ;
87+ let mut demod: Complex < f64 > = ( 0. , 0. ) ;
88+
89+ // use std::{fs::File, io::prelude::*, path::Path};
90+ // let mut file = File::create(Path::new("data.csv")).unwrap();
91+
92+ const PHASE_DEPTH : usize = 20 ;
93+
94+ for phase in 0 ..( 1 << PHASE_DEPTH ) {
95+ let phase = ( phase << ( 32 - PHASE_DEPTH ) ) as i32 ;
96+ let have = cossin ( phase) ;
97+ // writeln!(file, " {},{}", have.0, have.1).unwrap();
98+
99+ let have = ( have. 0 as f64 / AMPLITUDE , have. 1 as f64 / AMPLITUDE ) ;
100+
101+ let radian_phase = 2. * PI * phase as f64 / MAX_PHASE ;
102+ let want = ( radian_phase. cos ( ) , radian_phase. sin ( ) ) ;
103103
104- for i in 0 ..( 1 << PHASE_BITS ) {
105- let phase = i as i32 ;
106- let radian_phase: f64 =
107- 2. * PI * ( phase as f64 + 0.5 ) / ( ( 1 << PHASE_BITS ) as f64 ) ;
108-
109- let actual: Complex < f64 > = (
110- max_amplitude * radian_phase. cos ( ) ,
111- max_amplitude * radian_phase. sin ( ) ,
112- ) ;
113- let computed = cossin ( phase) ;
114-
115- let err = (
116- computed. 0 as f64 / 4. - actual. 0 ,
117- computed. 1 as f64 / 4. - actual. 1 ,
118- ) ;
119- rms_err. 0 += err. 0 * err. 0 / ( 1 << PHASE_BITS ) as f64 ;
120- rms_err. 1 += err. 1 * err. 1 / ( 1 << PHASE_BITS ) as f64 ;
121-
122- assert ! ( err. 0 . abs( ) < 0.89 ) ;
123- assert ! ( err. 1 . abs( ) < 0.89 ) ;
104+ sum. 0 += have. 0 ;
105+ sum. 1 += have. 1 ;
106+
107+ demod. 0 += have. 0 * want. 0 - have. 1 * want. 1 ;
108+ demod. 1 += have. 1 * want. 0 + have. 0 * want. 1 ;
109+
110+ let err = ( have. 0 - want. 0 , have. 1 - want. 1 ) ;
111+
112+ sum_err. 0 += err. 0 ;
113+ sum_err. 1 += err. 1 ;
114+
115+ rms_err. 0 += err. 0 * err. 0 ;
116+ rms_err. 1 += err. 1 * err. 1 ;
117+
118+ max_err. 0 = max_err. 0 . max ( err. 0 . abs ( ) ) ;
119+ max_err. 1 = max_err. 1 . max ( err. 1 . abs ( ) ) ;
124120 }
125- assert ! ( rms_err. 0 . sqrt( ) < 0.41 ) ;
126- assert ! ( rms_err. 1 . sqrt( ) < 0.41 ) ;
121+ rms_err. 0 /= MAX_PHASE ;
122+ rms_err. 1 /= MAX_PHASE ;
123+
124+ println ! ( "sum: {:.2e} {:.2e}" , sum. 0 , sum. 1 ) ;
125+ println ! ( "demod: {:.2e} {:.2e}" , demod. 0 , demod. 1 ) ;
126+ println ! ( "sum_err: {:.2e} {:.2e}" , sum_err. 0 , sum_err. 1 ) ;
127+ println ! ( "rms: {:.2e} {:.2e}" , rms_err. 0 . sqrt( ) , rms_err. 1 . sqrt( ) ) ;
128+ println ! ( "max: {:.2e} {:.2e}" , max_err. 0 , max_err. 1 ) ;
129+
130+ assert ! ( sum. 0 . abs( ) < 4e-10 ) ;
131+ assert ! ( sum. 1 . abs( ) < 4e-10 ) ;
132+
133+ assert ! ( demod. 0 . abs( ) < 4e-10 ) ;
134+ assert ! ( demod. 1 . abs( ) < 4e-10 ) ;
135+
136+ assert ! ( sum_err. 0 . abs( ) < 4e-10 ) ;
137+ assert ! ( sum_err. 1 . abs( ) < 4e-10 ) ;
138+
139+ assert ! ( rms_err. 0 . sqrt( ) < 6e-8 ) ;
140+ assert ! ( rms_err. 1 . sqrt( ) < 6e-8 ) ;
141+
142+ assert ! ( max_err. 0 < 1.1e-5 ) ;
143+ assert ! ( max_err. 1 < 1.1e-5 ) ;
127144 }
128145}
0 commit comments