Add funreg.hpp
parent
630b2e6a22
commit
30fd7bb79f
@ -1,3 +1,19 @@
|
||||
# funreg
|
||||
# funreg: Functional Memory-mapped Register I/O
|
||||
|
||||
*funreg* provides a functional approach to operating on memory-mapped registers
|
||||
with zero overhead. This library primarily targets embedded firmware, where
|
||||
these types of operations are frequently encountered.
|
||||
|
||||
What makes this library unique is its ability to carry out multiple register
|
||||
operations with a single function call, reducing this to a single register read
|
||||
and write. Further, registers can be organized into "groups": these groups can
|
||||
receive a list of operations for any of the contained registers, and will
|
||||
optimize down to a single read and write for each register.
|
||||
|
||||
A tutorial or guide will be added soon.
|
||||
|
||||
## Requirements
|
||||
|
||||
* C++20
|
||||
* GCC or Clang with some optimization enabled (O1, O2, O3, Os).
|
||||
|
||||
Functional memory-mapped register I/O using modern C++.
|
@ -0,0 +1,412 @@
|
||||
/**
|
||||
* funreg.hpp - Functional memory-mapped 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
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
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 Register
|
||||
* @brief Defines a memory-mapped register, given bit-size and address.
|
||||
* @tparam T The integer type that matches the size of the register.
|
||||
* @tparam Addr The memory address of the register.
|
||||
*
|
||||
* Defines a memory-mapped register that is usually accessed with a pointer of
|
||||
* type T*, and is located at address Addr.
|
||||
*
|
||||
* Use only as a type, e.g. "using GPIO_OUT = Register<uint32_t, 0xA0004120>"
|
||||
*/
|
||||
template<typename T, uintptr_t Addr>
|
||||
struct Register {
|
||||
/**
|
||||
* Gets a pointer to the register.
|
||||
*/
|
||||
constexpr static auto get() {
|
||||
return reinterpret_cast<volatile T*>(Addr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets register bits to '1' according to the given RegisterMasks.
|
||||
*/
|
||||
template<typename... Masks>
|
||||
static void set() {
|
||||
apply<Masks...>([](auto r, auto m) { *r = *r | m; });
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets register bits to '1' according to the given mask.
|
||||
*/
|
||||
static void set(const T& mask) {
|
||||
*get() = *get() | mask;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears register bits to '0' according to the given RegisterMasks.
|
||||
*/
|
||||
template<typename... Masks>
|
||||
static void clear() {
|
||||
apply<Masks...>([](auto r, auto m) { *r = *r & ~m; });
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears register bits to '0' according to the given mask.
|
||||
*/
|
||||
static void clear(const T& mask) {
|
||||
*get() = *get() & ~mask;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles bits in the register according to the given RegisterMasks.
|
||||
*/
|
||||
template<typename... Masks>
|
||||
static void toggle() {
|
||||
apply<Masks...>([](auto r, auto m) { *r = *r ^ m; });
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles bits in the register according to the given mask.
|
||||
*/
|
||||
static void toggle(const T& mask) {
|
||||
*get() = *get() ^ 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) {
|
||||
if (((Addr == Masks::reg::addr) | ...)) {
|
||||
auto mask =
|
||||
([] {
|
||||
return Addr == Masks::reg::addr ? Masks::mask : 0;
|
||||
}() | ...);
|
||||
return *get() & mask;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
return *get();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Overwrites the entire register with the given value.
|
||||
*/
|
||||
static void write(const T& value) {
|
||||
*get() = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
if (((Addr == Masks::reg::addr) | ...)) {
|
||||
auto mask =
|
||||
([] {
|
||||
return Addr == Masks::reg::addr ? Masks::mask : 0;
|
||||
}() | ...);
|
||||
return (*get() & mask) == mask;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
return *get() != 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 (((Addr == Ops::reg::addr) | ...)) {
|
||||
auto mask = *get();
|
||||
([&mask] {
|
||||
if (Addr == Ops::reg::addr)
|
||||
mask = Ops(mask);
|
||||
}(), ...);
|
||||
*get() = 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 =
|
||||
([] {
|
||||
return Addr == Masks::reg::addr ? Masks::mask : 0;
|
||||
}() | ...);
|
||||
if (mask)
|
||||
fn(get(), mask);
|
||||
} else {
|
||||
fn(get(), T(0) - 1);
|
||||
}
|
||||
}
|
||||
|
||||
Register() = delete;
|
||||
|
||||
using type = T;
|
||||
constexpr static auto addr = Addr;
|
||||
};
|
||||
|
||||
/**
|
||||
* @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::get() = *Reg::get() | 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::get() = *Reg::get() & ~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::get() = *Reg::get() ^ 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::get() & 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::get();
|
||||
r &= ~Mask;
|
||||
r |= value << BitOffset<Mask>;
|
||||
*Reg::get() = 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() & (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) { *r = *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) { *r = *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) { *r = *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 | ...);
|
||||
|
||||
} // namespace fr
|
||||
|
||||
#endif // FUNCTIONAL_REGISTER_IO_H
|
Loading…
Reference in New Issue