/** * 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