Skip to content

Commit

Permalink
Add pinmux_pwm_test to test runner
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
AlexJones0 committed Dec 19, 2024
1 parent 1db4deb commit e7088a6
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 18 deletions.
9 changes: 9 additions & 0 deletions sw/cheri/common/sonata-devices.hh
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

#include <cheri.hh>
#include <platform-gpio.hh>
#include <platform-pwm.hh>
#include <platform-uart.hh>
#include <platform-i2c.hh>
#include <platform-spi.hh>
Expand All @@ -22,6 +23,7 @@

typedef CHERI::Capability<void> CapRoot;
typedef volatile SonataGpioBoard *GpioPtr;
typedef volatile SonataPwm *PwmPtr;
typedef volatile OpenTitanUart *UartPtr;
typedef volatile OpenTitanUsbdev *UsbdevPtr;
typedef volatile OpenTitanI2c *I2cPtr;
Expand All @@ -40,6 +42,13 @@ using PinmuxPtrs = std::pair<PinSinksPtr, BlockSinksPtr>;
return gpio;
}

[[maybe_unused]] static PwmPtr pwm_ptr(CapRoot root) {
CHERI::Capability<volatile SonataPwm> pwm = root.cast<volatile SonataPwm>();
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<volatile OpenTitanUart> uart = root.cast<volatile OpenTitanUart>();
assert(idx < UART_NUM);
Expand Down
58 changes: 58 additions & 0 deletions sw/cheri/tests/pinmux_tests.hh
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "../common/block_tests.hh"
#include "test_runner.hh"
#include "i2c_tests.hh"
#include "pwm_tests.hh"
#include <cheri.hh>
#include <platform-uart.hh>
#include <ds/xoroshiro.h>
Expand All @@ -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
Expand Down Expand Up @@ -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<PinSinksPtr>(sinks)->get(PinSink::pmod0_8);
auto pmod0_gpio7 = std::get<BlockSinksPtr>(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
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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);
Expand Down
31 changes: 13 additions & 18 deletions sw/cheri/tests/pwm_tests.hh
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
#include "test_runner.hh"
#include <cheri.hh>
#include <platform-gpio.hh>
#include <platform-pwm.hh>

using namespace CHERI;

Expand Down Expand Up @@ -54,11 +53,9 @@ constexpr bool LogPwmTests = false;
*
* @returns The integer number of failures during the test
*/
int pwm_loopback_test(Capability<volatile SonataGpioPmod0> gpio_pmod0, Capability<volatile SonataPwm> 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<volatile SonataGpioPmod0> 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;
Expand All @@ -69,34 +66,34 @@ int pwm_loopback_test(Capability<volatile SonataGpioPmod0> 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();
Expand Down Expand Up @@ -204,9 +201,7 @@ void pwm_tests(CapRoot root, Log &log) {
gpio_pmod0.bounds() = GPIO_BOUNDS;

// Create bounded capability for PWM
Capability<volatile SonataPwm> pwm = root.cast<volatile SonataPwm>();
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++) {
Expand All @@ -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);
}
Expand Down

0 comments on commit e7088a6

Please sign in to comment.