From e7088a6def91a4fff4c1afe7c8085bf24831d56c Mon Sep 17 00:00:00 2001 From: Alex Jones Date: Thu, 19 Dec 2024 14:56:55 +0000 Subject: [PATCH] Add `pinmux_pwm_test` to test runner This commit introduces a new `pinmux_pwm_test` that will run in CI, which tests that PWM appears to be working correctly when it is muxed over PMOD, and that it doesn't work when disabled via Pinmux. This re-uses the loopback testing code from the PWM tests that reads the PWM output via GPIO to determine the duty cycle and period, and use that to verify that PWM operation is as expected. There is some small refactoring to make that possible, including a fix in which the PWM loopback test was accidentally using hardcoded GPIO 0 instead of the specified pin constant (which was also 0, hence no previous problems). Requires an additional jumper cable to FPGAs to pass the test, which can be optionally disabled by the same `PINMUX_CABLE_CONNECTIONS_AVAILABLE` method as for tests where this was previously required. --- sw/cheri/common/sonata-devices.hh | 9 +++++ sw/cheri/tests/pinmux_tests.hh | 58 +++++++++++++++++++++++++++++++ sw/cheri/tests/pwm_tests.hh | 31 +++++++---------- 3 files changed, 80 insertions(+), 18 deletions(-) diff --git a/sw/cheri/common/sonata-devices.hh b/sw/cheri/common/sonata-devices.hh index caf3ddb0..b71f91aa 100644 --- a/sw/cheri/common/sonata-devices.hh +++ b/sw/cheri/common/sonata-devices.hh @@ -14,6 +14,7 @@ #include #include +#include #include #include #include @@ -22,6 +23,7 @@ typedef CHERI::Capability CapRoot; typedef volatile SonataGpioBoard *GpioPtr; +typedef volatile SonataPwm *PwmPtr; typedef volatile OpenTitanUart *UartPtr; typedef volatile OpenTitanUsbdev *UsbdevPtr; typedef volatile OpenTitanI2c *I2cPtr; @@ -40,6 +42,13 @@ using PinmuxPtrs = std::pair; return gpio; } +[[maybe_unused]] static PwmPtr pwm_ptr(CapRoot root) { + CHERI::Capability pwm = root.cast(); + pwm.address() = PWM_ADDRESS; + pwm.bounds() = PWM_BOUNDS * PWM_NUM; + return pwm; +} + [[maybe_unused]] static UartPtr uart_ptr(CapRoot root, uint32_t idx = 0) { CHERI::Capability uart = root.cast(); assert(idx < UART_NUM); diff --git a/sw/cheri/tests/pinmux_tests.hh b/sw/cheri/tests/pinmux_tests.hh index 151d95d6..ee7c67b6 100644 --- a/sw/cheri/tests/pinmux_tests.hh +++ b/sw/cheri/tests/pinmux_tests.hh @@ -12,6 +12,7 @@ #include "../common/block_tests.hh" #include "test_runner.hh" #include "i2c_tests.hh" +#include "pwm_tests.hh" #include #include #include @@ -32,6 +33,7 @@ using namespace SonataPinmux; * - mikroBus (P7) RX & TX * - Arduino Shield (P4) D0 & D1 * - Arduino Shield (P4) D8 & D9 + * - PMOD0 8 & 10 * This can be overriden via a compilation flag. */ #ifndef PINMUX_CABLE_CONNECTIONS_AVAILABLE @@ -310,6 +312,55 @@ static int pinmux_gpio_test(PinmuxPtrs sinks, SonataGpioFull *gpio) { return failures; } +static int pinmux_pwm_test(PinmuxPtrs sinks, PwmPtr pwm, SonataGpioFull *gpio, Log &log) { + constexpr uint8_t PmxPmod0_8ToPwmOut2 = 2; + constexpr uint8_t PmxPmod0Gpio7ToPmod0_10 = 1; + + constexpr GpioPin GpioPinInput = {GpioInstance::Pmod0, 7}; + constexpr uint8_t PwmInstance = 2; + + int failures = 0; + auto pmod0_8 = std::get(sinks)->get(PinSink::pmod0_8); + auto pmod0_gpio7 = std::get(sinks)->get(BlockSink::gpio_2_ios_7); + + constexpr size_t NumLoopbackTests = 3; + constexpr uint8_t Periods[NumLoopbackTests] = {255, 255, 128}; + constexpr uint8_t DutyCycles[NumLoopbackTests] = {213, 128, 40}; + + // Configure the PMOD 0 Pin 10 as GPIO input. + set_gpio_output_enable(gpio, GpioPinInput, false); + + // Ensure that the GPIO (PMOD 0 Pin 10) and PWM (PMOD 0 Pin 8) are enabled via Pinmux + failures += !pmod0_8.select(PmxPmod0_8ToPwmOut2); + failures += !pmod0_gpio7.select(PmxPmod0Gpio7ToPmod0_10); + + // Check that the PWM works as expected in loopback tests + for (uint8_t i = 0; i < NumLoopbackTests; i++) { + failures += pwm_loopback_test(gpio->pmod0, GpioPinInput.bit, pwm, PwmInstance, Periods[i], DutyCycles[i], + NumPwmCyclesObserved, AllowedCycleDeviation, log); + } + + // Disable the PWM via pinmux, and check that the test now fails: + pmod0_8.disable(); + for (uint8_t i = 0; i < NumLoopbackTests; i++) { + failures += !pwm_loopback_test(gpio->pmod0, GpioPinInput.bit, pwm, PwmInstance, Periods[i], DutyCycles[i], + NumPwmCyclesObserved, AllowedCycleDeviation, log); + } + + // Re-enable the PWM via pinmux, and check that the test now passes again + failures += !pmod0_8.select(PmxPmod0_8ToPwmOut2); + for (uint8_t i = 0; i < NumLoopbackTests; i++) { + failures += pwm_loopback_test(gpio->pmod0, GpioPinInput.bit, pwm, PwmInstance, Periods[i], DutyCycles[i], + NumPwmCyclesObserved, AllowedCycleDeviation, log); + } + + // Reset muxed pins to not interfere with future tests + pmod0_8.default_selection(); + pmod0_gpio7.default_selection(); + + return failures; +} + /** * Test the muxing capability of pinmux, by dynamically switching between using * (and testing) UART and pinmux on the same two pins - specifically the Arduino @@ -387,6 +438,8 @@ void pinmux_tests(CapRoot root, Log &log) { I2cPtr i2c0 = i2c_ptr(root, 0); I2cPtr i2c1 = i2c_ptr(root, 1); + PwmPtr pwm = pwm_ptr(root); + // Create bounded capabilities for the full range of GPIO SonataGpioFull gpio_full = get_full_gpio_ptrs(root); @@ -424,6 +477,11 @@ void pinmux_tests(CapRoot root, Log &log) { test_failed |= (failures > 0); write_test_result(log, failures); + log.print(" Running PWM Pinmux test... "); + failures = pinmux_pwm_test(sinks, pwm, &gpio_full, log); + test_failed |= (failures > 0); + write_test_result(log, failures); + log.print(" Running UART Pinmux test... "); failures = pinmux_uart_test(sinks, prng, uart1); test_failed |= (failures > 0); diff --git a/sw/cheri/tests/pwm_tests.hh b/sw/cheri/tests/pwm_tests.hh index 638c7496..5d265d27 100644 --- a/sw/cheri/tests/pwm_tests.hh +++ b/sw/cheri/tests/pwm_tests.hh @@ -9,7 +9,6 @@ #include "test_runner.hh" #include #include -#include using namespace CHERI; @@ -54,11 +53,9 @@ constexpr bool LogPwmTests = false; * * @returns The integer number of failures during the test */ -int pwm_loopback_test(Capability gpio_pmod0, Capability pwm, - uint8_t period, uint8_t duty_cycle, uint8_t cycles, uint8_t error_delta, Log &log) { - constexpr uint8_t PwmInstance = 0; - constexpr uint8_t InputPin = 0; - +int pwm_loopback_test(Capability gpio_pmod0, uint8_t input_pin, PwmPtr pwm, + uint8_t pwm_instance, uint8_t period, uint8_t duty_cycle, uint8_t cycles, uint8_t error_delta, + Log &log) { // Calculate error bounds & timeouts for use in testing const uint32_t period_lower = period - (error_delta > period ? period : error_delta); const uint32_t period_upper = period + error_delta; @@ -69,34 +66,34 @@ int pwm_loopback_test(Capability gpio_pmod0, Capabilit int failures = 0; // Configure GPIO, timer and PWM respectively. - gpio_pmod0->set_output_enable(InputPin, false); + gpio_pmod0->set_output_enable(input_pin, false); reset_mcycle(); - pwm->output_set(PwmInstance, period, duty_cycle); + pwm->output_set(pwm_instance, period, duty_cycle); uint32_t cycles_observed = 0; - const bool inputState = gpio_pmod0->read_input(0); + const bool inputState = gpio_pmod0->read_input(input_pin); while (cycles_observed < cycles) { // Wait for the next cycle to avoid missing measurements due to arithmetic/logging. uint32_t start_mcycle = get_mcycle(); uint32_t timeout_cycle = start_mcycle + timeout; - while (gpio_pmod0->read_input(0) == inputState && get_mcycle() < timeout_cycle) { + while (gpio_pmod0->read_input(input_pin) == inputState && get_mcycle() < timeout_cycle) { asm volatile(""); } start_mcycle = get_mcycle(); timeout_cycle = start_mcycle + timeout; - while (gpio_pmod0->read_input(0) != inputState && get_mcycle() < timeout_cycle) { + while (gpio_pmod0->read_input(input_pin) != inputState && get_mcycle() < timeout_cycle) { asm volatile(""); } // Measure PWM period & duty cycle times start_mcycle = get_mcycle(); timeout_cycle = start_mcycle + timeout; - while (gpio_pmod0->read_input(0) == inputState && get_mcycle() < timeout_cycle) { + while (gpio_pmod0->read_input(input_pin) == inputState && get_mcycle() < timeout_cycle) { asm volatile(""); } uint32_t changed_mcycle = get_mcycle(); timeout_cycle = changed_mcycle + timeout; - while (gpio_pmod0->read_input(0) != inputState && get_mcycle() < timeout_cycle) { + while (gpio_pmod0->read_input(input_pin) != inputState && get_mcycle() < timeout_cycle) { asm volatile(""); } uint32_t end_mcycle = get_mcycle(); @@ -204,9 +201,7 @@ void pwm_tests(CapRoot root, Log &log) { gpio_pmod0.bounds() = GPIO_BOUNDS; // Create bounded capability for PWM - Capability pwm = root.cast(); - pwm.address() = PWM_ADDRESS; - pwm.bounds() = PWM_BOUNDS; + PwmPtr pwm = pwm_ptr(root); // Execute the specified number of iterations of each test. for (size_t i = 0; i < PWM_TEST_ITERATIONS; i++) { @@ -226,8 +221,8 @@ void pwm_tests(CapRoot root, Log &log) { uint8_t period = periods[i]; uint8_t duty_cycle = duty_cycles[i]; log.print(" Running PWM Loopback ({}/{}) test... ", duty_cycle, period); - failures = - pwm_loopback_test(gpio_pmod0, pwm, period, duty_cycle, NumPwmCyclesObserved, AllowedCycleDeviation, log); + failures = pwm_loopback_test(gpio_pmod0, 0, pwm, 0, period, duty_cycle, NumPwmCyclesObserved, + AllowedCycleDeviation, log); test_failed |= (failures > 0); write_test_result(log, failures); }