RISC-V: A Bare-metal Introduction using C++. Machine Mode Timer.

The Machine Level ISA Timer

The RISC-V machine level ISA defines a real-time counter. It is defined as two MMIO system registers mtime and mtimer .

Timekeeping in Modern C++

Modern C++ includes the std::chrono library, and std::chrono::literals that allow us to think in terms of real time, not machine time. For embedded systems, time is a first-order concern, a benefit of C++ is that it makes it a standard part of the language.

namespace driver {
struct default_timer_config {
static constexpr unsigned int MTIME_FREQ_HZ=32768;
};
template<class CONFIG=default_timer_config> class timer {
/** Duration of each timer tick */
using timer_ticks = std::chrono::duration<int,
std::ratio<1, CONFIG::MTIME_FREQ_HZ>>;
}
}
uint64_t value_from_mtime = ...;
auto value_in_ms =
std::chrono::duration_cast<std::chrono::microseconds>(
driver::timer::timer_ticks(value_from_mtime));
auto time_offset = std::chrono::microseconds(???);
uint64_t value_of_mtimecmp = std::chrono::duration_cast<timer_ticks>
(time_offset).count();

Reading/Writing MMIO Registers in C++

There is not much difference between accessing MMIO registers in C, and C++. One advantage C++ has is templates. As RISC-V’s timer registers are not at a fixed address (absolute or relative to each other), re-usable code should be parameterized. Here that is done via template parameters.

struct mtimer_address_spec {
static constexpr std::uintptr_t
MTIMECMP_ADDR = 0x2000000 + 0x4000;
static constexpr std::uintptr_t
MTIME_ADDR = 0x2000000 + 0xBFF8;
};
template<class ADDRESS_SPEC=mtimer_address_spec>
void set_raw_time_cmp(uint64_t clock_offset) {
// Single bus access
auto mtimecmp = reinterpret_cast<volatile std::uint64_t *>
(ADDRESS_SPEC::MTIMECMP_ADDR);
*mtimecmp = *mtimecmp + clock_offset;
}

Conclusion

The timer driver covers a few core topics in bare-metal programming and how C++ can provide an advantage.

  • MMIO access and static polymorphism.
  • Hardware real-time clocks.
  • Converting clock frequencies and periods to human-readable units.
  • Configuring drivers via templates and constexpr.

64 Bit Registers Access on a 32 Bit Bus

There is a small complication accessing timer registers, they are 64 bits wide and time tends to update constantly while our program is executing. On a 32 bit system, we can only access 1/2 of the register at a time.

  1. The mtime is 0x0000_0000_FFFF_FFFF.
  2. We read the top 32 bits, 0x0000_0000
  3. We save this into our register t0.
  4. The real time clock ticks.
  5. The mtime is 0x0000_0001_0000_0000.
  6. We read the bottom 32 bits, 0x0000_0000.
  7. We save this into our register t1.
  8. We check the time in t0:t1, it’s 0x0000_0000_0000_0000!

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Phil Mulholland

Phil Mulholland

Experienced in Distributed Systems, Event-Driven Systems, Firmware for SoC/MCU, Systems Simulation, Network Monitoring and Analysis, Automated Testing and RTL.