| // Copyright lowRISC contributors. | |
| // Licensed under the Apache License, Version 2.0, see LICENSE for details. | |
| // SPDX-License-Identifier: Apache-2.0 | |
| #include "sw/device/lib/dif/dif_rv_timer.h" | |
| #include <cstring> | |
| #include <limits> | |
| #include <ostream> | |
| #include <stdint.h> | |
| #include "gtest/gtest.h" | |
| #include "sw/device/lib/base/mmio.h" | |
| #include "sw/device/lib/base/mock_mmio.h" | |
| #include "sw/device/lib/dif/dif_test_base.h" | |
| #include "rv_timer_regs.h" // Generated. | |
| // We define global namespace == and << to make `dif_i2c_timing_params_t` work | |
| // nicely with EXPECT_EQ. | |
| bool operator==(dif_rv_timer_tick_params_t a, dif_rv_timer_tick_params_t b) { | |
| return a.prescale == b.prescale && a.tick_step == b.tick_step; | |
| } | |
| std::ostream &operator<<(std::ostream &os, | |
| const dif_rv_timer_tick_params_t ¶ms) { | |
| // Note that `tick_step` is actually a `char`, so it doesn't print correctly. | |
| auto step = static_cast<uint32_t>(params.tick_step); | |
| return os << "{ .prescale = " << params.prescale << ", .tick_step = " << step | |
| << " }"; | |
| } | |
| namespace dif_rv_timer_unittest { | |
| namespace { | |
| using ::mock_mmio::LeInt; | |
| using ::mock_mmio::MmioTest; | |
| using ::mock_mmio::MockDevice; | |
| constexpr uint32_t kFastClockSpeed = 2'000'000'000; // 2 GHz | |
| constexpr uint32_t kClockSpeed = 50'000'000; // 50 MHz | |
| constexpr uint32_t kSlowClockSpeed = 50; // 50 Hz | |
| constexpr uint32_t kSlowTimer = 1'000'000; // 1 MHz | |
| constexpr uint32_t kFastTimer = 120'000'000; // 120 MHz | |
| constexpr uint32_t kSluggishTimer = 3; // 3 Hz | |
| TEST(ApproximateParamsTest, Success) { | |
| // The timer frequency devices the clock speed, so their quotient minus 1 is | |
| // the prescale. | |
| dif_rv_timer_tick_params_t params, expected = { | |
| .prescale = 49, | |
| .tick_step = 1, | |
| }; | |
| EXPECT_DIF_OK( | |
| dif_rv_timer_approximate_tick_params(kClockSpeed, kSlowTimer, ¶ms)); | |
| EXPECT_EQ(params, expected); | |
| } | |
| TEST(ApproximateParamsTest, WithStep) { | |
| // 50 MHz / 5 is 10 MHz; multiplied by 12, we get 120 MHz. | |
| dif_rv_timer_tick_params_t params, expected = { | |
| .prescale = 4, | |
| .tick_step = 12, | |
| }; | |
| EXPECT_DIF_OK( | |
| dif_rv_timer_approximate_tick_params(kClockSpeed, kFastTimer, ¶ms)); | |
| EXPECT_EQ(params, expected); | |
| } | |
| TEST(ApproximateParamsTest, UnrepresenableTooSlow) { | |
| // This frequency is unrepresentable; the GCD of the clock and timer | |
| // frequencies is 1, so the prescale is the clock speed, which does not fit in | |
| // a u12. | |
| dif_rv_timer_tick_params_t params; | |
| EXPECT_DIF_BADARG(dif_rv_timer_approximate_tick_params( | |
| kFastClockSpeed, kSluggishTimer, ¶ms)); | |
| } | |
| TEST(ApproximateParamsTest, UnrepresenableTooFast) { | |
| // This freqncy is unrepresentable; the GCD is 50, meaning that the step must | |
| // be 2'400'000, which does not fit into a u8. | |
| dif_rv_timer_tick_params_t params; | |
| EXPECT_DIF_BADARG(dif_rv_timer_approximate_tick_params(kSlowClockSpeed, | |
| kFastTimer, ¶ms)); | |
| } | |
| TEST(ApproximateParamsTest, NullArgs) { | |
| EXPECT_DIF_BADARG(dif_rv_timer_approximate_tick_params(kSlowClockSpeed, | |
| kFastTimer, nullptr)); | |
| } | |
| class TimerTest : public testing::Test, public MmioTest { | |
| protected: | |
| dif_rv_timer_t rv_timer_ = {.base_addr = dev().region()}; | |
| }; | |
| // Copy of `reg_for_hart()` in the C translation unit under test. | |
| ptrdiff_t RegForHart(uint32_t hart, ptrdiff_t reg_offset) { | |
| return 0x100 * hart + reg_offset; | |
| } | |
| constexpr uint32_t kAllOnes = std::numeric_limits<uint32_t>::max(); | |
| class ResetTest : public TimerTest {}; | |
| TEST_F(ResetTest, Success) { | |
| EXPECT_WRITE32(RV_TIMER_CTRL_REG_OFFSET, 0x0); | |
| for (uint32_t hart_id = 0; hart_id < RV_TIMER_PARAM_N_HARTS; ++hart_id) { | |
| EXPECT_WRITE32(RegForHart(0, RV_TIMER_INTR_ENABLE0_REG_OFFSET), 0x0); | |
| EXPECT_WRITE32(RegForHart(0, RV_TIMER_INTR_STATE0_REG_OFFSET), kAllOnes); | |
| for (uint32_t comp_id = 0; comp_id < RV_TIMER_PARAM_N_TIMERS; ++comp_id) { | |
| EXPECT_WRITE32(RegForHart(0, RV_TIMER_COMPARE_UPPER0_0_REG_OFFSET), | |
| kAllOnes); | |
| EXPECT_WRITE32(RegForHart(0, RV_TIMER_COMPARE_LOWER0_0_REG_OFFSET), | |
| kAllOnes); | |
| EXPECT_WRITE32(RegForHart(0, RV_TIMER_COMPARE_UPPER0_0_REG_OFFSET), | |
| kAllOnes); | |
| } | |
| } | |
| EXPECT_WRITE32(RegForHart(0, RV_TIMER_TIMER_V_LOWER0_REG_OFFSET), 0x0); | |
| EXPECT_WRITE32(RegForHart(0, RV_TIMER_TIMER_V_UPPER0_REG_OFFSET), 0x0); | |
| EXPECT_DIF_OK(dif_rv_timer_reset(&rv_timer_)); | |
| } | |
| TEST_F(ResetTest, NullArgs) { EXPECT_DIF_BADARG(dif_rv_timer_reset(nullptr)); } | |
| class SetTickParamsTest : public TimerTest {}; | |
| TEST_F(SetTickParamsTest, Success) { | |
| EXPECT_WRITE32( | |
| RegForHart(0, RV_TIMER_CFG0_REG_OFFSET), | |
| {{RV_TIMER_CFG0_PRESCALE_OFFSET, 400}, {RV_TIMER_CFG0_STEP_OFFSET, 25}}); | |
| EXPECT_DIF_OK(dif_rv_timer_set_tick_params( | |
| &rv_timer_, 0, {.prescale = 400, .tick_step = 25})); | |
| } | |
| TEST_F(SetTickParamsTest, NullArgs) { | |
| EXPECT_DIF_BADARG(dif_rv_timer_set_tick_params( | |
| nullptr, 0, {.prescale = 400, .tick_step = 25})); | |
| } | |
| TEST_F(SetTickParamsTest, BadHartId) { | |
| EXPECT_DIF_BADARG(dif_rv_timer_set_tick_params( | |
| &rv_timer_, RV_TIMER_PARAM_N_HARTS, {.prescale = 400, .tick_step = 25})); | |
| } | |
| class CounterSetEnabledTest : public TimerTest {}; | |
| TEST_F(CounterSetEnabledTest, Success) { | |
| EXPECT_MASK32(RV_TIMER_CTRL_REG_OFFSET, | |
| {{/*offset=*/0, /*mask=*/1, /*value=*/1}}); | |
| EXPECT_DIF_OK( | |
| dif_rv_timer_counter_set_enabled(&rv_timer_, 0, kDifToggleEnabled)); | |
| } | |
| TEST_F(CounterSetEnabledTest, NullArgs) { | |
| EXPECT_DIF_BADARG( | |
| dif_rv_timer_counter_set_enabled(nullptr, 0, kDifToggleEnabled)); | |
| } | |
| TEST_F(CounterSetEnabledTest, BadHartId) { | |
| EXPECT_DIF_BADARG(dif_rv_timer_counter_set_enabled( | |
| &rv_timer_, RV_TIMER_PARAM_N_HARTS, kDifToggleEnabled)); | |
| } | |
| class CounterReadTest : public TimerTest {}; | |
| TEST_F(CounterReadTest, Success) { | |
| EXPECT_READ32(RegForHart(0, RV_TIMER_TIMER_V_UPPER0_REG_OFFSET), 0x0222'0222); | |
| EXPECT_READ32(RegForHart(0, RV_TIMER_TIMER_V_LOWER0_REG_OFFSET), 0x0333'0333); | |
| EXPECT_READ32(RegForHart(0, RV_TIMER_TIMER_V_UPPER0_REG_OFFSET), 0x0222'0222); | |
| uint64_t value; | |
| EXPECT_DIF_OK(dif_rv_timer_counter_read(&rv_timer_, 0, &value)); | |
| EXPECT_EQ(value, 0x0222'0222'0333'0333); | |
| } | |
| TEST_F(CounterReadTest, Overflow) { | |
| EXPECT_READ32(RegForHart(0, RV_TIMER_TIMER_V_UPPER0_REG_OFFSET), 0x0222'0222); | |
| EXPECT_READ32(RegForHart(0, RV_TIMER_TIMER_V_LOWER0_REG_OFFSET), 0x0333'0333); | |
| EXPECT_READ32(RegForHart(0, RV_TIMER_TIMER_V_UPPER0_REG_OFFSET), 0x0222'0223); | |
| EXPECT_READ32(RegForHart(0, RV_TIMER_TIMER_V_UPPER0_REG_OFFSET), 0x0222'0223); | |
| EXPECT_READ32(RegForHart(0, RV_TIMER_TIMER_V_LOWER0_REG_OFFSET), 0x0333'0444); | |
| EXPECT_READ32(RegForHart(0, RV_TIMER_TIMER_V_UPPER0_REG_OFFSET), 0x0222'0223); | |
| uint64_t value; | |
| EXPECT_DIF_OK(dif_rv_timer_counter_read(&rv_timer_, 0, &value)); | |
| EXPECT_EQ(value, 0x0222'0223'0333'0444); | |
| } | |
| TEST_F(CounterReadTest, NullArgs) { | |
| uint64_t value; | |
| EXPECT_DIF_BADARG(dif_rv_timer_counter_read(nullptr, 0, &value)); | |
| EXPECT_DIF_BADARG(dif_rv_timer_counter_read(&rv_timer_, 0, nullptr)); | |
| } | |
| TEST_F(CounterReadTest, BadHartId) { | |
| uint64_t value; | |
| EXPECT_DIF_BADARG( | |
| dif_rv_timer_counter_read(&rv_timer_, RV_TIMER_PARAM_N_HARTS, &value)); | |
| } | |
| class CounterWriteTest : public TimerTest {}; | |
| TEST_F(CounterWriteTest, Success) { | |
| EXPECT_READ32(RV_TIMER_CTRL_REG_OFFSET, 0x0000'0001); | |
| EXPECT_WRITE32(RV_TIMER_CTRL_REG_OFFSET, 0x0000'0000); | |
| EXPECT_WRITE32(RegForHart(0, RV_TIMER_TIMER_V_LOWER0_REG_OFFSET), | |
| 0xDEAD'BEEF); | |
| EXPECT_WRITE32(RegForHart(0, RV_TIMER_TIMER_V_UPPER0_REG_OFFSET), | |
| 0xCAFE'FEED); | |
| EXPECT_WRITE32(RV_TIMER_CTRL_REG_OFFSET, 0x0000'0001); | |
| uint64_t count = 0xCAFE'FEED'DEAD'BEEF; | |
| EXPECT_DIF_OK(dif_rv_timer_counter_write(&rv_timer_, 0, count)); | |
| } | |
| TEST_F(CounterWriteTest, NullArgs) { | |
| uint64_t count = 0xCAFE'FEED'DEAD'BEEF; | |
| EXPECT_DIF_BADARG(dif_rv_timer_counter_write(nullptr, 0, count)); | |
| } | |
| TEST_F(CounterWriteTest, BadHartId) { | |
| uint64_t count = 0xCAFE'FEED'DEAD'BEEF; | |
| EXPECT_DIF_BADARG( | |
| dif_rv_timer_counter_write(&rv_timer_, RV_TIMER_PARAM_N_HARTS, count)); | |
| } | |
| class ArmTest : public TimerTest {}; | |
| TEST_F(ArmTest, Success) { | |
| auto lower_reg = RegForHart(0, RV_TIMER_COMPARE_LOWER0_0_REG_OFFSET); | |
| auto upper_reg = RegForHart(0, RV_TIMER_COMPARE_UPPER0_0_REG_OFFSET); | |
| EXPECT_WRITE32(upper_reg, kAllOnes); | |
| EXPECT_WRITE32(lower_reg, 0x0444'0555); | |
| EXPECT_WRITE32(upper_reg, 0x0222'0333); | |
| EXPECT_DIF_OK(dif_rv_timer_arm(&rv_timer_, 0, 0, 0x0222'0333'0444'0555)); | |
| } | |
| TEST_F(ArmTest, NullArgs) { | |
| EXPECT_DIF_BADARG(dif_rv_timer_arm(nullptr, 0, 0, 0x0222'0333'0444'0555)); | |
| } | |
| TEST_F(ArmTest, BadHartIdBadCompId) { | |
| EXPECT_DIF_BADARG(dif_rv_timer_arm(&rv_timer_, RV_TIMER_PARAM_N_HARTS, 0, | |
| 0x0222'0333'0444'0555)); | |
| EXPECT_DIF_BADARG(dif_rv_timer_arm(&rv_timer_, 0, RV_TIMER_PARAM_N_TIMERS, | |
| 0x0222'0333'0444'0555)); | |
| EXPECT_DIF_BADARG(dif_rv_timer_arm(&rv_timer_, RV_TIMER_PARAM_N_HARTS, | |
| RV_TIMER_PARAM_N_TIMERS, | |
| 0x0222'0333'0444'0555)); | |
| } | |
| } // namespace | |
| } // namespace dif_rv_timer_unittest |