use std::num::NonZeroUsize;
use rand::{Rng, RngExt};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ShiftRegister {
bits: u16,
}
impl ShiftRegister {
#[must_use]
pub fn new() -> Self {
Self { bits: 0 }
}
#[must_use]
pub fn new_random(rng: &mut impl Rng) -> Self {
Self {
bits: rng.random::<u16>(),
}
}
pub fn clock(&mut self, new_bit: bool) {
self.bits = (self.bits << 1) | u16::from(new_bit);
}
#[must_use]
pub fn feedback_bit(&self, length: NonZeroUsize) -> bool {
(self.bits >> (length.get() - 1)) & 1 == 1
}
#[must_use]
pub fn bits(&self) -> u16 {
self.bits
}
#[must_use]
pub fn dac_byte(&self, length: NonZeroUsize) -> u8 {
((self.bits >> length.get().saturating_sub(8)) & 0xFF) as u8
}
#[must_use]
pub fn pulse_bit(&self, length: NonZeroUsize) -> bool {
(self.dac_byte(length) >> 7) & 1 == 1
}
#[must_use]
pub fn to_bools(&self) -> [bool; 16] {
let mut out = [false; 16];
for (i, slot) in out.iter_mut().enumerate() {
*slot = (self.bits >> (15 - i)) & 1 == 1;
}
out
}
pub fn reset(&mut self) {
self.bits = 0;
}
}
impl Default for ShiftRegister {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
fn nz(n: usize) -> NonZeroUsize {
NonZeroUsize::new(n).unwrap()
}
#[test]
fn test_new_returns_zeroed_register() {
let sr = ShiftRegister::new();
assert_eq!(sr.bits(), 0);
}
#[test]
fn test_default_delegates_to_new() {
assert_eq!(ShiftRegister::default(), ShiftRegister::new());
}
#[test]
fn test_new_random_produces_value() {
let mut rng = rand::rng();
let sr = ShiftRegister::new_random(&mut rng);
let _ = sr.bits();
}
#[test]
fn test_clock_shifts_left() {
let mut sr = ShiftRegister::new();
sr.clock(true);
assert_eq!(sr.bits(), 0b1);
sr.clock(false);
assert_eq!(sr.bits(), 0b10);
sr.clock(true);
assert_eq!(sr.bits(), 0b101);
}
#[test]
fn test_feedback_bit_respects_length() {
let mut sr = ShiftRegister::new();
sr.clock(true); sr.clock(false); sr.clock(true); sr.clock(false);
assert!(sr.feedback_bit(nz(4)));
assert!(!sr.feedback_bit(nz(3)));
assert!(sr.feedback_bit(nz(2)));
}
#[test]
fn test_dac_byte_length_16_is_upper_byte() {
let sr = ShiftRegister { bits: 0xAB_CD };
assert_eq!(sr.dac_byte(nz(16)), 0xAB);
}
#[test]
fn test_dac_byte_length_8_is_lower_byte() {
let sr = ShiftRegister { bits: 0xAB_CD };
assert_eq!(sr.dac_byte(nz(8)), 0xCD);
}
#[test]
fn test_dac_byte_length_less_than_8() {
let sr = ShiftRegister { bits: 0x00_3F };
assert_eq!(sr.dac_byte(nz(4)), 0x3F);
}
#[test]
fn test_pulse_bit_is_msb_of_dac_byte() {
let sr = ShiftRegister { bits: 0xFF_00 };
assert!(sr.pulse_bit(nz(16)));
assert!(!sr.pulse_bit(nz(8)));
}
#[test]
fn test_to_bools_order() {
let sr = ShiftRegister {
bits: 0b1000_0000_0000_0001,
};
let bools = sr.to_bools();
assert!(bools[0]);
assert!(bools[15]);
for (i, val) in bools.iter().enumerate().skip(1).take(14) {
assert!(!val, "expected false at index {i}");
}
}
#[test]
fn test_reset_clears_register() {
let mut sr = ShiftRegister { bits: 0xFFFF };
sr.reset();
assert_eq!(sr.bits(), 0);
}
#[test]
fn test_feedback_bit_length_one() {
let mut sr = ShiftRegister::new();
sr.clock(true); assert!(sr.feedback_bit(nz(1)));
sr.clock(false); assert!(!sr.feedback_bit(nz(1)));
}
#[test]
fn test_feedback_bit_maximum_length() {
let sr = ShiftRegister { bits: 0x8000 }; assert!(sr.feedback_bit(nz(16)));
let sr = ShiftRegister { bits: 0x7FFF }; assert!(!sr.feedback_bit(nz(16)));
}
#[test]
fn test_clock_overflow_discards_bit_16() {
let mut sr = ShiftRegister { bits: 0xFFFF };
sr.clock(false);
assert_eq!(sr.bits(), 0xFFFE);
}
}