std_rs/snl/femto.rs
1//! Femto amplifier gain control — native Rust port of `femto.st`.
2//!
3//! This implements the gain selection state machine for Femto low-noise
4//! current amplifiers. The amplifier gain is controlled by 4 digital bits
5//! (G1, G2, G3, NO) which map to a gain index via a lookup table.
6//!
7//! # Gain Lookup Table
8//!
9//! | Index | Bits (NO:G3:G2:G1) | Gain (V/A) |
10//! |-------|-------------------|------------|
11//! | 0 | 0:0:0:0 | 10^5 |
12//! | 1 | 0:0:0:1 | 10^6 |
13//! | 2 | 0:0:1:0 | 10^7 |
14//! | 3 | 0:0:1:1 | 10^8 |
15//! | 4 | 0:1:0:0 | 10^9 |
16//! | 5 | 0:1:0:1 | 10^10 |
17//! | 6 | 0:1:1:0 | 10^11 |
18//! | 7 | 0:1:1:1 | (unused) |
19//! | 8 | 1:0:0:0 | 10^3 |
20//! | 9 | 1:0:0:1 | 10^4 |
21//! | 10 | 1:0:1:0 | 10^5 |
22//! | 11 | 1:0:1:1 | 10^6 |
23//! | 12 | 1:1:0:0 | 10^7 |
24//! | 13 | 1:1:0:1 | 10^8 |
25//! | 14 | 1:1:1:0 | 10^9 |
26//! | 15 | 1:1:1:1 | (unused) |
27
28/// Gain power lookup: `gain = 10^POWERS[gainidx]`.
29/// Index 7 and 15 are unused (mapped to power 0).
30pub const POWERS: [u32; 16] = [5, 6, 7, 8, 9, 10, 11, 0, 3, 4, 5, 6, 7, 8, 9, 0];
31
32pub const MIN_GAIN: i32 = 0;
33pub const MAX_GAIN: i32 = 15;
34pub const UNUSED_GAIN: i32 = 7;
35
36/// Decode 4 gain bits into a gain index (0–15).
37pub fn bits_to_gain_index(g1: bool, g2: bool, g3: bool, no: bool) -> i32 {
38 let t0 = g1 as i32;
39 let t1 = g2 as i32;
40 let t2 = g3 as i32;
41 let tx = no as i32;
42 (tx << 3) | (t2 << 2) | (t1 << 1) | t0
43}
44
45/// Encode a gain index into 4 gain bits (g1, g2, g3, no).
46pub fn gain_index_to_bits(idx: i32) -> (bool, bool, bool, bool) {
47 let g1 = (idx & 1) != 0;
48 let g2 = (idx & 2) != 0;
49 let g3 = (idx & 4) != 0;
50 let no = (idx & 8) != 0;
51 (g1, g2, g3, no)
52}
53
54/// Validate a gain index. Returns `true` if valid.
55pub fn is_valid_gain_index(idx: i32) -> bool {
56 (MIN_GAIN..MAX_GAIN).contains(&idx) && idx != UNUSED_GAIN
57}
58
59/// Compute the gain value for a given index.
60pub fn gain_for_index(idx: i32) -> f64 {
61 if !(0..16).contains(&idx) {
62 return 0.0;
63 }
64 10.0_f64.powi(POWERS[idx as usize] as i32)
65}
66
67/// State of the femto amplifier state machine.
68#[derive(Debug, Clone, Copy, PartialEq, Eq)]
69pub enum FemtoState {
70 Init,
71 Idle,
72 ChangeGain,
73 UpdateGain,
74}
75
76/// Femto amplifier gain controller.
77///
78/// Port of the `femto.st` SNL program as a pure Rust state machine.
79/// Call `step()` to advance the state machine when events occur.
80pub struct FemtoController {
81 pub state: FemtoState,
82 pub gain_index: i32,
83 pub current_gain: i32,
84 pub g1: bool,
85 pub g2: bool,
86 pub g3: bool,
87 pub no: bool,
88 pub gain: f64,
89}
90
91impl Default for FemtoController {
92 fn default() -> Self {
93 Self {
94 state: FemtoState::Init,
95 gain_index: 0,
96 current_gain: -1,
97 g1: false,
98 g2: false,
99 g3: false,
100 no: false,
101 gain: 0.0,
102 }
103 }
104}
105
106/// Events that drive the femto state machine.
107#[derive(Debug, Clone, Copy)]
108pub enum FemtoEvent {
109 /// Gain bits changed from hardware.
110 BitsChanged {
111 g1: bool,
112 g2: bool,
113 g3: bool,
114 no: bool,
115 },
116 /// User requested a specific gain index.
117 GainIndexChanged(i32),
118}
119
120impl FemtoController {
121 /// Advance the state machine by one step given an event.
122 /// Returns the new state.
123 pub fn step(&mut self, event: Option<FemtoEvent>) -> FemtoState {
124 match self.state {
125 FemtoState::Init => {
126 // Initialize from current bit state
127 if let Some(FemtoEvent::BitsChanged { g1, g2, g3, no }) = event {
128 self.g1 = g1;
129 self.g2 = g2;
130 self.g3 = g3;
131 self.no = no;
132 }
133
134 let idx = bits_to_gain_index(self.g1, self.g2, self.g3, self.no);
135 self.gain_index = if !self.g1 && !self.g2 && !self.g3 && !self.no {
136 8 // Default to 1e3 when all bits are off
137 } else if !is_valid_gain_index(idx) {
138 6 // Default to 1e11
139 } else {
140 idx
141 };
142
143 self.current_gain = -1;
144 self.gain = gain_for_index(self.gain_index);
145 self.state = FemtoState::ChangeGain;
146 }
147
148 FemtoState::Idle => match event {
149 Some(FemtoEvent::BitsChanged { g1, g2, g3, no }) => {
150 self.g1 = g1;
151 self.g2 = g2;
152 self.g3 = g3;
153 self.no = no;
154 self.state = FemtoState::UpdateGain;
155 }
156 Some(FemtoEvent::GainIndexChanged(idx)) => {
157 self.gain_index = idx;
158 self.state = FemtoState::ChangeGain;
159 }
160 None => {}
161 },
162
163 FemtoState::ChangeGain => {
164 // Validate requested gain
165 if self.current_gain == self.gain_index || !is_valid_gain_index(self.gain_index) {
166 // Invalid or no change: revert to current gain
167 if self.current_gain >= 0 && self.current_gain != self.gain_index {
168 self.gain_index = self.current_gain;
169 self.gain = gain_for_index(self.current_gain);
170 }
171 self.state = FemtoState::Idle;
172 } else {
173 // Apply gain: set bits
174 let (g1, g2, g3, no) = gain_index_to_bits(self.gain_index);
175 self.g1 = g1;
176 self.g2 = g2;
177 self.g3 = g3;
178 self.no = no;
179 self.current_gain = self.gain_index;
180 self.gain = gain_for_index(self.gain_index);
181 self.state = FemtoState::Idle;
182 }
183 }
184
185 FemtoState::UpdateGain => {
186 // Bits changed externally: recompute gain index
187 let idx = bits_to_gain_index(self.g1, self.g2, self.g3, self.no);
188 self.gain_index = if !is_valid_gain_index(idx) { 6 } else { idx };
189 self.current_gain = self.gain_index;
190 self.gain = gain_for_index(self.gain_index);
191 self.state = FemtoState::Idle;
192 }
193 }
194
195 self.state
196 }
197}