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