]> code.bitgloo.com Git - clyne/funreg.git/commitdiff
Add funreg.hpp
authorClyne Sullivan <clyne@bitgloo.com>
Sun, 7 Aug 2022 01:57:56 +0000 (21:57 -0400)
committerClyne Sullivan <clyne@bitgloo.com>
Sun, 7 Aug 2022 01:57:56 +0000 (21:57 -0400)
README.md
funreg.hpp [new file with mode: 0644]

index 5ea5a4769d2c2a5a3df50802620aaf422427c867..c46b2957c2d56d8831c0c0331a95b035fd1b3e20 100644 (file)
--- 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 (file)
index 0000000..156b6cf
--- /dev/null
@@ -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