aboutsummaryrefslogtreecommitdiffstats
path: root/funreg.hpp
diff options
context:
space:
mode:
authorClyne Sullivan <clyne@bitgloo.com>2022-08-06 21:57:56 -0400
committerClyne Sullivan <clyne@bitgloo.com>2022-08-06 21:57:56 -0400
commit30fd7bb79fe88aad8740543dd29a56b8f7b55a01 (patch)
tree58349c5de711d3bde75eb0802dc275945cca57e3 /funreg.hpp
parent630b2e6a22b9ecea8980db6e30f1851353022f71 (diff)
Add funreg.hpp
Diffstat (limited to 'funreg.hpp')
-rw-r--r--funreg.hpp412
1 files changed, 412 insertions, 0 deletions
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.
+ * <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