diff --git a/README.md b/README.md index 5ea5a47..c46b295 100644 --- a/README.md +++ b/README.md @@ -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++. \ No newline at end of file diff --git a/funreg.hpp b/funreg.hpp new file mode 100644 index 0000000..156b6cf --- /dev/null +++ b/funreg.hpp @@ -0,0 +1,412 @@ +/** + * funreg.hpp - Functional memory-mapped register I/O using modern C++. + * Written by Clyne Sullivan. + * + */ + +#ifndef FUNCTIONAL_REGISTER_IO_H +#define FUNCTIONAL_REGISTER_IO_H + +#include + +namespace fr { + +// A utility to measure a bit-mask's offset from bit zero. +template +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" + */ +template +struct Register { + /** + * Gets a pointer to the register. + */ + constexpr static auto get() { + return reinterpret_cast(Addr); + } + + /** + * Sets register bits to '1' according to the given RegisterMasks. + */ + template + static void set() { + apply([](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 + static void clear() { + apply([](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 + static void toggle() { + apply([](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 + 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 + 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 + 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 + 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; + */ +template +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 + struct write { + constexpr write() { + auto r = *Reg::get(); + r &= ~Mask; + r |= value << BitOffset; + *Reg::get() = r; + } + + // For internal use. + using reg = Reg; + T modmask; + constexpr write(auto r): + modmask((r & ~Mask) | (value << BitOffset)) {} + 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 +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; + + /** + * 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); + } +}; + +/** + * @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 +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 + static void set() { + apply([](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 + static void clear() { + apply([](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 + static void toggle() { + apply([](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 + static void modify() { + (Registers::template modify(), ...); + } + +private: + template + static void apply(auto fn) { + (Registers::template apply(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 +constexpr auto Masks = (RegMasks::mask | ...); + +} // namespace fr + +#endif // FUNCTIONAL_REGISTER_IO_H