sc_neurocore_engine/neuron/
fixed_point_lif.rs1#[inline]
11pub fn mask(value: i32, width: u32) -> i16 {
12 assert!(
13 width > 0 && width <= 32,
14 "mask width must be 1..=32, got {width}"
15 );
16 let mask = (1_i64 << width) - 1;
17 let value = (value as i64) & mask;
18 let shift = 64 - width;
19 ((value << shift) >> shift) as i16
20}
21
22#[derive(Clone, Debug)]
24pub struct FixedPointLif {
25 pub v: i16,
26 pub refractory_counter: i32,
27 pub data_width: u32,
28 pub fraction: u32,
29 pub v_rest: i16,
30 pub v_reset: i16,
31 pub v_threshold: i16,
32 pub refractory_period: i32,
33}
34
35impl FixedPointLif {
36 pub fn new(
37 data_width: u32,
38 fraction: u32,
39 v_rest: i16,
40 v_reset: i16,
41 v_threshold: i16,
42 refractory_period: i32,
43 ) -> Self {
44 Self {
45 v: v_rest,
46 refractory_counter: 0,
47 data_width,
48 fraction,
49 v_rest,
50 v_reset,
51 v_threshold,
52 refractory_period,
53 }
54 }
55
56 #[allow(non_snake_case)]
57 pub fn step(&mut self, leak_k: i16, gain_k: i16, i_t: i16, noise_in: i16) -> (i32, i16) {
58 let width = self.data_width;
59 if self.refractory_counter > 0 {
60 self.refractory_counter -= 1;
61 self.v = self.v_rest;
62 return (0, mask(self.v_rest as i32, width));
63 }
64
65 let diff = mask((self.v_rest as i32) - (self.v as i32), 2 * width) as i32;
66 let dv_leak = mask((diff * (leak_k as i32)) >> self.fraction, self.data_width);
67 let dv_in = mask(
68 ((i_t as i32) * (gain_k as i32)) >> self.fraction,
69 self.data_width,
70 );
71 let v_next = mask(
72 (self.v as i32) + (dv_leak as i32) + (dv_in as i32) + (noise_in as i32),
73 self.data_width,
74 );
75
76 if v_next >= self.v_threshold {
77 self.v = self.v_reset;
78 self.refractory_counter = self.refractory_period;
79 (1, mask(self.v_reset as i32, width))
80 } else {
81 self.v = v_next;
82 (0, mask(v_next as i32, width))
83 }
84 }
85
86 pub fn reset(&mut self) {
87 self.v = self.v_rest;
88 self.refractory_counter = 0;
89 }
90}
91
92#[cfg(test)]
93mod tests {
94 use super::{mask, FixedPointLif};
95
96 #[test]
97 fn branchless_mask_matches_signed_reference() {
98 for &width in &[16_u32, 32] {
99 for value in [
100 -32768_i32,
101 -1,
102 0,
103 1,
104 32767,
105 65535,
106 -65536,
107 i16::MAX as i32,
108 i16::MIN as i32,
109 ] {
110 let result = mask(value, width);
111 let bit_mask = (1_i64 << width) - 1;
112 let mut expected = (value as i64) & bit_mask;
113 if expected >= (1_i64 << (width - 1)) {
114 expected -= 1_i64 << width;
115 }
116 let expected = if width >= 32 {
117 expected as i32 as i16
118 } else {
119 expected as i16
120 };
121 assert_eq!(result, expected, "value={value}, width={width}");
122 }
123 }
124 }
125
126 #[test]
127 fn refractory_period_enforces_two_silent_steps() {
128 let mut neuron = FixedPointLif::new(16, 8, 0, 0, 256, 2);
129 let spikes: Vec<_> = (0..30).map(|_| neuron.step(1, 256, 50, 0).0).collect();
130 assert!(spikes.iter().sum::<i32>() > 0);
131 for (index, &spike) in spikes.iter().enumerate() {
132 if spike == 1 && index + 2 < spikes.len() {
133 assert_eq!(spikes[index + 1], 0);
134 assert_eq!(spikes[index + 2], 0);
135 }
136 }
137 }
138
139 #[test]
140 fn zero_refractory_period_allows_repeated_firing() {
141 let mut neuron = FixedPointLif::new(16, 8, 0, 0, 256, 0);
142 let spikes: i32 = (0..20).map(|_| neuron.step(1, 256, 50, 0).0).sum();
143 assert!(spikes > 0);
144 }
145}