/** * funreg.hpp - Functional register I/O using modern C++. * Written by Clyne Sullivan. * */ #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 #ifdef FUNREG_ENABLE_EXTERNAL_IO #include #endif 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 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 struct MemoryIO { using type = T; constexpr static auto addr = Addr; /** * Reads the register's value. */ constexpr static T read() { return *reinterpret_cast(Addr); } /** * Overwrites the register's value. */ constexpr static void write(const T& value) { *reinterpret_cast(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 struct Register { using access = Access; using T = typename Access::type; constexpr static auto Addr = Access::addr; #else template struct Register { using RegAccess = MemoryIO; #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 static void set() { apply([](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 static void clear() { apply([](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 static void toggle() { apply([](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 static auto read() { if constexpr (sizeof...(Masks) > 0) return read() & mergeMasks(); 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 static bool test() { if constexpr (sizeof...(Masks) > 0) { auto mask = mergeMasks(); 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 static void modify() { if constexpr ((isThis | ...)) { auto mask = read(); ([&mask] { if constexpr (isThis) 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 static void apply(auto fn) { if constexpr (sizeof...(Masks) > 0) { auto mask = mergeMasks(); 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 static auto mergeMasks() { if constexpr (sizeof...(Masks) > 0) { if constexpr ((isThis | ...)) { auto mask = ([] { return isThis ? 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 constexpr static bool isThis = [] { return std::is_same_v && Addr == Reg::Addr; }(); #else // Determines if the given register matches this one. template 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; */ 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::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 struct write { constexpr write() { auto r = Reg::read(); r &= ~Mask; r |= value << BitOffset; Reg::write(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() & Mask::mask) == (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) { 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 static void clear() { apply([](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 static void toggle() { apply([](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 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 | ...); #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 using MemRegister = Register>; /** * 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 typename ExtIO, typename T, uintptr_t Addr> using ExtRegister = Register>; #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 using MemRegister = Register; #endif // FUNREG_ENABLE_EXTERNAL_IO } // namespace fr #endif // FUNCTIONAL_REGISTER_IO_H