From 6ef588766bd6b1d0d32c80f0c233b8517020717c Mon Sep 17 00:00:00 2001 From: Douglas Reis Date: Mon, 25 Nov 2024 13:56:10 +0000 Subject: [PATCH] [SiVal, pwm] Add a harness for the pwm_smoketest Signed-off-by: Douglas Reis --- sw/host/tests/chip/pwm_smoketest/BUILD | 22 +++ sw/host/tests/chip/pwm_smoketest/src/main.rs | 143 +++++++++++++++++++ 2 files changed, 165 insertions(+) create mode 100644 sw/host/tests/chip/pwm_smoketest/BUILD create mode 100644 sw/host/tests/chip/pwm_smoketest/src/main.rs diff --git a/sw/host/tests/chip/pwm_smoketest/BUILD b/sw/host/tests/chip/pwm_smoketest/BUILD new file mode 100644 index 0000000000000..2b17dd16fa037 --- /dev/null +++ b/sw/host/tests/chip/pwm_smoketest/BUILD @@ -0,0 +1,22 @@ +# Copyright lowRISC contributors (OpenTitan project). +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 + +load("@rules_rust//rust:defs.bzl", "rust_binary") + +package(default_visibility = ["//visibility:public"]) + +rust_binary( + name = "pwm_smoketest", + srcs = [ + "src/main.rs", + ], + deps = [ + "//sw/host/opentitanlib", + "@crate_index//:anyhow", + "@crate_index//:clap", + "@crate_index//:humantime", + "@crate_index//:log", + "@crate_index//:object", + ], +) diff --git a/sw/host/tests/chip/pwm_smoketest/src/main.rs b/sw/host/tests/chip/pwm_smoketest/src/main.rs new file mode 100644 index 0000000000000..6aa657de93ce5 --- /dev/null +++ b/sw/host/tests/chip/pwm_smoketest/src/main.rs @@ -0,0 +1,143 @@ +// Copyright lowRISC contributors (OpenTitan project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +use anyhow::Result; +use clap::Parser; +use std::time::Duration; + +use opentitanlib::app::TransportWrapper; +use opentitanlib::execute_test; +use opentitanlib::io::gpio::BitbangEntry; +use opentitanlib::io::gpio::GpioPin; +use opentitanlib::io::gpio::PinMode; +use opentitanlib::test_utils; +use opentitanlib::test_utils::init::InitializeTest; +use std::borrow::Borrow; +use std::fs; +use std::path::PathBuf; +use std::rc::Rc; + +#[derive(Debug, Parser)] +struct Opts { + #[command(flatten)] + init: InitializeTest, + + /// Console receive timeout. + #[arg(long, value_parser = humantime::parse_duration, default_value = "600s")] + timeout: Duration, + + /// Path to the firmware's ELF file, for querying symbol addresses. + #[arg(value_name = "FIRMWARE_ELF")] + firmware_elf: PathBuf, +} + +fn test_clock_and_duty_cycle( + transport: &TransportWrapper, + gpio_pins: &[Rc], + period: Duration, + duty_cycle: f64, +) -> Result<()> { + let gpio_bitbanging = transport.gpio_bitbanging()?; + + const SAMPLES: usize = 100_000; + let mut samples = vec![0x00; SAMPLES]; + let mut output = [0x01; SAMPLES]; + output[1] = 0x03; + output[output.len() - 1] = 0x03; + let waveform = Box::new([BitbangEntry::Both(&output, &mut samples)]); + + let sampling_period = Duration::from_micros(10); + + gpio_bitbanging.run( + &gpio_pins + .iter() + .map(Rc::borrow) + .collect::>(), + sampling_period, + waveform, + )?; + + let mut pwm_bitbang_decoder = test_utils::bitbanging::pwm::decoder::Decoder::<0> { + active_level: test_utils::bitbanging::Bit::High, + sampling_period: sampling_period, + }; + let mut decoded = pwm_bitbang_decoder.run(samples)?; + // Discard the last sample because it may be distorted. + decoded.pop(); + let count = decoded.len() as f64; + let (sum_period, sum_duty) = decoded.iter().fold((0.0, 0.0), |(period, duty), elem| { + ( + period + elem.period.as_micros() as f64, + duty + elem.duty_cycle as f64, + ) + }); + + let average_period = sum_period / count; + let average_duty = sum_duty / count; + let period_error = + (average_period - period.as_micros() as f64).abs() / period.as_micros() as f64; + println!( + "Pwm average period: {} micros, expected: {:?}, err: {}%", + average_period, + period, + period_error * 100.0 + ); + + let duty_error = (average_duty - duty_cycle).abs() / duty_cycle; + println!( + "Pwm average duty: {}%, expected: {}, err: {}%", + average_duty, + duty_cycle, + duty_error * 100.0 + ); + + // Theres a trade off on the precision of the clock and the precision of the + // dutycycle, the higher the `kDutyCycleResulution` the more precise is the + // the dutycycle and less precise is the pwm frequency. + // By experimentation kDutyCycleResulution would generate an error of 2.5% + // on the frequency and 6% on the dutycycle. + assert!(period_error < 0.03); + assert!(duty_error < 0.07); + + Ok(()) +} + +fn main() -> Result<()> { + let opts = Opts::parse(); + opts.init.init_logging(); + let transport = opts.init.init_target()?; + transport.pin_strapping("RESET")?.apply()?; + let uart = transport.uart("console")?; + uart.set_flow_control(true)?; + uart.clear_rx_buffer()?; + transport.pin_strapping("RESET")?.remove()?; + + /* Load the ELF binary and get the expect data.*/ + let elf_binary = fs::read(&opts.firmware_elf)?; + let object = object::File::parse(&*elf_binary)?; + + let clocks = test_utils::object::symbol_data(&object, "kClocksHz")?; + let duty_cycles = test_utils::object::symbol_data(&object, "kDutyCycles")?; + + let gpio_pins = transport.gpio_pins(&["IOA8", "IOA7"].map(str::to_owned))?; + for pin in &gpio_pins { + pin.set_mode(PinMode::OpenDrain)?; + } + + for clock in &clocks { + for duty_cycle in &duty_cycles { + execute_test!( + test_clock_and_duty_cycle, + &transport, + &gpio_pins, + Duration::from_micros(1_000_000u64 / *clock as u64), + *duty_cycle as f64, + ); + } + } + + transport.apply_default_configuration(None)?; + + Ok(()) +}