You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
funreg/funreg.hpp

505 lines
14 KiB
C++

/**
* funreg.hpp - Functional register I/O using modern C++.
* Written by Clyne Sullivan.
* <https://github.com/tcsullivan/funreg>
*/
#ifndef FUNCTIONAL_REGISTER_IO_H
#define FUNCTIONAL_REGISTER_IO_H
/**
* Comment to disable external/custom register access.
* When disabled, only memory-mapped register access is supported.
* fr::Register can then also be used instead of fr::MemRegister.
*/
#define FUNREG_ENABLE_EXTERNAL_IO
#include <stdint.h>
#ifdef FUNREG_ENABLE_EXTERNAL_IO
#include <type_traits>
#endif
namespace fr {
// A utility to measure a bit-mask's offset from bit zero.
template<auto Mask, unsigned int N = 0>
constexpr auto BitOffset = []() constexpr {
if constexpr (Mask & 1)
return N;
else
return BitOffset<(Mask >> 1), (N + 1)>;
}();
/**
* @struct MemoryIO
* @brief Specifies how to access a memory-mapped register.
* @tparam T The size of the register.
* @tparam Addr The memory address of the register.
*
* To create an I/O access type for external register access, use this
* structure as a template.
*/
template<typename T, uintptr_t Addr>
struct MemoryIO {
using type = T;
constexpr static auto addr = Addr;
/**
* Reads the register's value.
*/
constexpr static T read() {
return *reinterpret_cast<volatile T*>(Addr);
}
/**
* Overwrites the register's value.
*/
constexpr static void write(const T& value) {
*reinterpret_cast<volatile T*>(Addr) = value;
}
};
/**
* @struct Register
* @brief Defines a register, given how to access it.
* @tparam Access Specifies register access. See MemoryIO for an example.
*
* When FUNREG_ENABLE_EXTERNAL_IO is not defined, Register assumes MemoryIO
* access. The template parameters become that of MemoryIO.
*/
#ifdef FUNREG_ENABLE_EXTERNAL_IO
template<typename Access>
struct Register {
using access = Access;
using T = typename Access::type;
constexpr static auto Addr = Access::addr;
#else
template<typename T, uintptr_t Addr>
struct Register {
using RegAccess = MemoryIO<T, Addr>;
#endif // FUNREG_ENABLE_EXTERNAL_IO
/**
* Gets a pointer to the register.
*/
constexpr static T read() {
return Access::read();
}
/**
* Overwrites the register's value.
*/
constexpr static void write(const T& value) {
Access::write(value);
}
/**
* Sets register bits to '1' according to the given RegisterMasks.
*/
template<typename... Masks>
static void set() {
apply<Masks...>([](auto r, auto m) { return r | m; });
}
/**
* Sets register bits to '1' according to the given mask.
*/
static void set(const T& mask) {
write(read() | mask);
}
/**
* Clears register bits to '0' according to the given RegisterMasks.
*/
template<typename... Masks>
static void clear() {
apply<Masks...>([](auto r, auto m) { return r & ~m; });
}
/**
* Clears register bits to '0' according to the given mask.
*/
static void clear(const T& mask) {
write(read() & ~mask);
}
/**
* Toggles bits in the register according to the given RegisterMasks.
*/
template<typename... Masks>
static void toggle() {
apply<Masks...>([](auto r, auto m) { return r ^m; });
}
/**
* Toggles bits in the register according to the given mask.
*/
static void toggle(const T& mask) {
write(read() ^ mask);
}
/**
* Reads the current value stored in the register, masking bits according to
* the given RegisterMasks.
* If no masks are given, all register bits are returned.
*/
template<typename... Masks>
static auto read() {
if constexpr (sizeof...(Masks) > 0)
return read() & mergeMasks<Masks...>();
else
return read();
}
/**
* Reads the register, and tests if all of the given bits are set.
* If no masks are given, tests if the register has a non-zero value.
*/
template<typename... Masks>
static bool test() {
if constexpr (sizeof...(Masks) > 0) {
auto mask = mergeMasks<Masks...>();
return (read() & mask) == mask;
} else {
return read() != 0;
}
}
/**
* Modifies the register's contents according to the given operations.
* The register will only be read and written once.
* Possible operations include RegisterMask::set, RegisterMask::clear,
* RegisterMask::toggle, RegisterMask::write<>, RegisterMaskValue::set,
* and RegisterMaskValue::clear.
*/
template<typename... Ops>
static void modify() {
if constexpr ((isThis<typename Ops::reg> | ...)) {
auto mask = read();
([&mask] {
if constexpr (isThis<typename Ops::reg>)
mask = Ops(mask);
}(), ...);
write(mask);
}
}
// Below is meant for internal use only.
// Applies bit-masks to the register through the provided function.
// The provided function receives a pointer to the register's data and a
// bit-mask created by merging all provided bit-masks.
// If no masks are given, a mask selecting all bits is used.
template<typename... Masks>
static void apply(auto fn) {
if constexpr (sizeof...(Masks) > 0) {
auto mask = mergeMasks<Masks...>();
if constexpr (mask)
write(fn(read(), mask));
} else {
write(fn(read(), T(0) - 1));
}
}
// Takes a list of bit-masks, and returns a merged mask of those which are
// meant for this register.
template<typename... Masks>
static auto mergeMasks() {
if constexpr (sizeof...(Masks) > 0) {
if constexpr ((isThis<typename Masks::reg> | ...)) {
auto mask =
([] {
return isThis<typename Masks::reg> ? Masks::mask : 0;
}() | ...);
return mask;
} else {
return 0;
}
} else {
return 0;
}
}
#ifdef FUNREG_ENABLE_EXTERNAL_IO
// Determines if the given register matches this one.
template<typename Reg>
constexpr static bool isThis = [] {
return std::is_same_v<typename Reg::access, access> && Addr == Reg::Addr;
}();
#else
// Determines if the given register matches this one.
template<typename Reg>
constexpr static bool isThis = [] {
return Addr == Reg::Addr;
}();
#endif // FUNREG_ENABLE_EXTERNAL_IO
Register() = delete;
using type = T;
};
/**
* @struct RegisterMask
* @brief Defines a bit mask that can be used with the specified register.
* @tparam Reg The Register that this mask belongs to.
* @tparam Mask A mask selecting the bits that the RegisterMask can modify.
*
* Pairs together a bit mask and the register the mask is meant for.
* For example, a single LED is controlled by bit 2 on the GPIO_OUT register:
* using LED_RED = RegisterMask<GPIO_OUT, (1 << 2)>;
*/
template<typename Reg, typename Reg::type Mask>
struct RegisterMask
{
using T = typename Reg::type;
/**
* Sets all bits in the bit-mask to "1".
* Call with no arguments (LED_REG::set()) to affect the paired register.
* Calling with an argument sets bits in the argument as if it were a
* register, returning the resulting value.
*/
struct set {
constexpr set() {
Reg::write(Reg::read() | Mask);
}
// For internal use.
using reg = Reg;
T modmask;
constexpr set(auto r): modmask(r | Mask) {}
constexpr operator T() const { return modmask; }
};
/**
* Clears all bits in the bit-mask to "0".
* See RegisterMask::set for calling conventions.
*/
struct clear {
constexpr clear() {
Reg::write(Reg::read() & ~Mask);
}
// For internal use.
using reg = Reg;
T modmask;
constexpr clear(auto r): modmask(r & ~Mask) {}
constexpr operator T() const { return modmask; }
};
/**
* Toggles all bits in the bit-mask.
* See RegisterMask::set for calling conventions.
*/
struct toggle {
constexpr toggle() {
Reg::write(Reg::read() ^ Mask);
}
// For internal use.
using reg = Reg;
T modmask;
constexpr toggle(auto r): modmask(r ^ Mask) {}
constexpr operator T() const { return modmask; }
};
/**
* Reads from the paired register, applying the bit-mask.
*/
static auto read() {
return Reg::read() & Mask;
}
/**
* Applies the bit-mask to the given register value, returning the result.
* This is useful in case the register's value has already been read; or, if
* the mask needs to be applied to a different value or register.
* @see Mask<>
*/
static auto read(const T& regval) {
return regval & Mask;
}
/**
* Writes the given value to the register.
* Writing is accomplished by clearing the bit-mask, then OR-ing the value
* to the bit-mask's offset.
* See RegisterMask::set for calling conventions, but note the additional
* template parameter "value".
*/
template<T value>
struct write {
constexpr write() {
auto r = Reg::read();
r &= ~Mask;
r |= value << BitOffset<Mask>;
Reg::write(r);
}
// For internal use.
using reg = Reg;
T modmask;
constexpr write(auto r):
modmask((r & ~Mask) | (value << BitOffset<Mask>)) {}
constexpr operator T() const { return modmask; }
};
/**
* Tests if all masked bits are set in the register.
*/
static bool test() {
return read() == Mask;
}
/**
* Tests if all masked bits are set in the given register value.
*/
static bool test(const T& regval) {
return read(regval) == Mask;
}
RegisterMask() = delete;
using reg = Reg;
constexpr static auto mask = Mask;
};
/**
* @struct RegisterMaskValue
* @brief Used to name the possible values of a multi-bit bit-mask.
* @tparam Mask The RegisterMask this value is associated with.
* @tparam value The value to be used for the given Mask.
*/
template<typename Mask, Mask::T value>
struct RegisterMaskValue
{
/**
* Call this directly to write the value into the register.
* Can also be used in modify() chains.
* @see RegisterMask::write()
* @see Register::modify()
*/
using set = typename Mask::write<value>;
/**
* Call this to clear the value from the register.
*/
using clear = typename Mask::clear;
/**
* Tests if this value is currently set in the register.
*/
static bool test() {
return (Mask::read() & Mask::mask) == (value << BitOffset<Mask>);
}
};
/**
* @class RegisterGroup
* @brief Groups registers together for unified operations.
* @tparam Registers The registers to be included in this group.
*
* Allows for single operations to be carried out on multiple registers.
* Masks for the same register are merged, resulting in single load/stores for
* each register.
*/
template<typename... Registers>
class RegisterGroup
{
public:
/**
* Sets bits throughout this group's registers according to the given masks.
* Bit-masks for the same register will be merged so that each register is
* only written once.
*/
template<typename... Masks>
static void set() {
apply<Masks...>([](auto r, auto m) { return r | m; });
}
/**
* Clears bits throughout this group's registers according to the given
* masks.
* Only reads and writes each register once; see set().
*/
template<typename... Masks>
static void clear() {
apply<Masks...>([](auto r, auto m) { return r & ~m; });
}
/**
* Toggles bits throughout this group's registers according to the given
* masks.
* Only reads and writes each register once; see set().
*/
template<typename... Masks>
static void toggle() {
apply<Masks...>([](auto r, auto m) { return r ^ m; });
}
/**
* Modifies registers in this group according to the given operations.
* Each register will only be read and written once.
* Possible operations include RegisterMask::set, RegisterMask::clear,
* RegisterMask::toggle, RegisterMask::write<>, RegisterMaskValue::set,
* and RegisterMaskValue::clear.
*/
template<typename... Ops>
static void modify() {
(Registers::template modify<Ops...>(), ...);
}
private:
template<typename... Masks>
static void apply(auto fn) {
(Registers::template apply<Masks...>(fn), ...);
}
};
/**
* Merges the bit-masks of the given RegisterMasks, ignoring register
* assignment.
* Useful if the bit-masks are needed for something besides the assigned
* register.
*/
template<typename... RegMasks>
constexpr auto Masks = (RegMasks::mask | ...);
#ifdef FUNREG_ENABLE_EXTERNAL_IO
/**
* Defines a register that is accessed through memory, i.e. memory-mapped.
* @tparam T The variable type used to access the register (e.g. uint32_t).
* @tparam Addr The memory address of the register.
*/
template<typename T, uintptr_t Addr>
using MemRegister = Register<MemoryIO<T, Addr>>;
/**
* Defines a register that is accessed through external or custom means.
* @tparam ExtIO A type that provides access functionality (e.g. MemoryIO).
* @tparam T The variable type used to access the register (e.g. uint32_t).
* @tparam Addr The memory address of the register.
*
* Custom access types should be defined using MemoryIO as a template.
*/
template<template<typename, uintptr_t> typename ExtIO, typename T, uintptr_t Addr>
using ExtRegister = Register<ExtIO<T, Addr>>;
#else
/**
* Defines a register that is accessed through memory, i.e. memory-mapped.
* @tparam T The variable type used to access the register (e.g. uint32_t).
* @tparam Addr The memory address of the register.
*
* With external I/O disabled, the Register type may be used directly instead.
*/
template<typename T, uintptr_t Addr>
using MemRegister = Register<T, Addr>;
#endif // FUNREG_ENABLE_EXTERNAL_IO
} // namespace fr
#endif // FUNCTIONAL_REGISTER_IO_H