diff --git a/sw/host/opentitanlib/BUILD b/sw/host/opentitanlib/BUILD index 41548eae365304..289e3330187266 100644 --- a/sw/host/opentitanlib/BUILD +++ b/sw/host/opentitanlib/BUILD @@ -144,6 +144,7 @@ rust_library( "src/test_utils/bitbanging/i2c.rs", "src/test_utils/bitbanging/mod.rs", "src/test_utils/bitbanging/spi.rs", + "src/test_utils/bitbanging/pwm.rs", "src/test_utils/bootstrap.rs", "src/test_utils/e2e_command.rs", "src/test_utils/epmp.rs", diff --git a/sw/host/opentitanlib/src/test_utils/bitbanging/mod.rs b/sw/host/opentitanlib/src/test_utils/bitbanging/mod.rs index e3bc96702481db..90016c81ecd87c 100644 --- a/sw/host/opentitanlib/src/test_utils/bitbanging/mod.rs +++ b/sw/host/opentitanlib/src/test_utils/bitbanging/mod.rs @@ -3,6 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 pub mod i2c; +pub mod pwm; pub mod spi; #[repr(u8)] diff --git a/sw/host/opentitanlib/src/test_utils/bitbanging/pwm.rs b/sw/host/opentitanlib/src/test_utils/bitbanging/pwm.rs new file mode 100644 index 00000000000000..de679d0ebc3563 --- /dev/null +++ b/sw/host/opentitanlib/src/test_utils/bitbanging/pwm.rs @@ -0,0 +1,81 @@ +// Copyright lowRISC contributors (OpenTitan project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +use super::Bit; +use anyhow::Result; + +#[derive(Clone, Debug)] +pub struct PwmPeriod { + pub period: std::time::Duration, + pub duty_cycle: f32, +} + +pub mod decoder { + use super::*; + + #[derive(Clone, Debug)] + struct Sample { + raw: u8, + } + + impl Sample { + fn pin(&self) -> Bit { + ((self.raw >> PIN) & 0x01).into() + } + } + + pub struct Decoder { + pub active_level: Bit, + pub sampling_period: std::time::Duration, + } + + impl Decoder { + /// Loop sampling the pin until a rising edge is detected. + fn find_first_active_level(&self, samples: &mut I) -> Result<()> + where + I: Iterator>, + { + let _ = samples + .by_ref() + .find(|sample| sample.pin() == Bit::Low) + .expect("active edge not found"); + + let _ = samples + .by_ref() + .find(|sample| sample.pin() == self.active_level) + .expect("active edge not found"); + + Ok(()) + } + + fn decode_period(&self, samples: &mut I) -> Option + where + I: Iterator>, + { + let num_active_samples = + samples.position(|sample| sample.pin() != self.active_level)? + 1; + + let num_inactive_samples = + samples.position(|sample| sample.pin() == self.active_level)? + 1; + + let period = (num_active_samples + num_inactive_samples) as u32; + let duty_cycle = num_active_samples as f32 * 100.0 / period as f32; + + Some(PwmPeriod { + period: period * self.sampling_period, + duty_cycle, + }) + } + + pub fn run(&mut self, samples: Vec) -> Result> { + let mut samples = samples.into_iter().map(|raw| Sample:: { raw }); + self.find_first_active_level(&mut samples)?; + let mut pwms = Vec::new(); + while let Some(pwm) = self.decode_period(&mut samples) { + pwms.push(pwm); + } + Ok(pwms) + } + } +}