Skip to main content

sc_neurocore_engine/
fault.rs

1// SPDX-License-Identifier: AGPL-3.0-or-later | Commercial license available
2// © Concepts 1996–2026 Miroslav Šotek. All rights reserved.
3// © Code 2020–2026 Miroslav Šotek. All rights reserved.
4// ORCID: 0009-0009-3560-0851
5// Contact: www.anulum.li | protoscience@anulum.li
6// SC-NeuroCore — Hardware fault injection for robustness testing
7
8//! Hardware fault injection for robustness testing.
9
10use rand::{RngExt, SeedableRng};
11use rand_xoshiro::Xoshiro256PlusPlus;
12
13/// Flip random bits in packed u64 words with given probability per bit.
14pub fn inject_bitflips(data: &mut [u64], rate: f64, seed: u64) {
15    if rate <= 0.0 {
16        return;
17    }
18    let mut rng = Xoshiro256PlusPlus::seed_from_u64(seed);
19    for word in data.iter_mut() {
20        let mut flip_mask = 0u64;
21        for bit in 0..64 {
22            if rng.random::<f64>() < rate {
23                flip_mask |= 1u64 << bit;
24            }
25        }
26        *word ^= flip_mask;
27    }
28}
29
30/// Force bits to a fixed value with given probability per bit.
31pub fn inject_stuck_at(data: &mut [u64], rate: f64, value: bool, seed: u64) {
32    if rate <= 0.0 {
33        return;
34    }
35    let mut rng = Xoshiro256PlusPlus::seed_from_u64(seed);
36    for word in data.iter_mut() {
37        for bit in 0..64 {
38            if rng.random::<f64>() < rate {
39                if value {
40                    *word |= 1u64 << bit;
41                } else {
42                    *word &= !(1u64 << bit);
43                }
44            }
45        }
46    }
47}
48
49#[cfg(test)]
50mod tests {
51    use super::*;
52
53    #[test]
54    fn zero_rate_no_change() {
55        let mut data = vec![0xDEAD_BEEF_CAFE_BABEu64; 4];
56        let original = data.clone();
57        inject_bitflips(&mut data, 0.0, 42);
58        assert_eq!(data, original);
59    }
60
61    #[test]
62    fn full_rate_flips_all() {
63        let mut data = vec![0u64; 2];
64        inject_bitflips(&mut data, 1.0, 42);
65        assert_eq!(data, vec![u64::MAX; 2]);
66    }
67
68    #[test]
69    fn stuck_at_zero() {
70        let mut data = vec![u64::MAX; 2];
71        inject_stuck_at(&mut data, 1.0, false, 42);
72        assert_eq!(data, vec![0u64; 2]);
73    }
74
75    #[test]
76    fn stuck_at_one() {
77        let mut data = vec![0u64; 2];
78        inject_stuck_at(&mut data, 1.0, true, 42);
79        assert_eq!(data, vec![u64::MAX; 2]);
80    }
81
82    #[test]
83    fn partial_rate_changes_some() {
84        let mut data = vec![0u64; 8];
85        inject_bitflips(&mut data, 0.5, 99);
86        let total_set: u32 = data.iter().map(|w| w.count_ones()).sum();
87        // ~50% of 512 bits = ~256 ± reasonable margin
88        assert!(total_set > 100 && total_set < 400);
89    }
90}