diff --git a/GUIDE.md b/GUIDE.md new file mode 100644 index 0000000..80abfb6 --- /dev/null +++ b/GUIDE.md @@ -0,0 +1,178 @@ +## Guided Overview + +Let's say you have a couple LEDs that are controlled by memory-mapped +registers, a common case for embedded microcontrollers. To begin, you'll need +to define the register used to control the LEDs. + +For an 8-bit register at memory address `0x0021`, you would write: + +```cpp +using PORTA_OUT = fr::MemRegister; +``` + +`MemRegister` is an `Register` that uses `MemoryIO` access. Registers +have static functions for interacting with their contents; for example, we +could now do `PORTA_OUT::write(0x10)` or `auto state = PORTA::read()`. + +A lot more can be done with registers once we define some register masks. A +`RegisterMask` lets us name one or more bits within a register. + +To name our two LEDs, which are only controlled by single bits, we write: + +```cpp +using LED_1 = fr::RegisterMask; // bit zero +using LED_2 = fr::RegisterMask; // bit two +``` + +We can also give a name to both LEDs: + +```cpp +using LED_ALL = fr::RegisterMask; +``` + +Now, we can control the LEDs either directly or through the register. Direct +calls would be like `LED_1::set()`; there is also `clear`, `toggle`, `read`, +`write`, and `test`. + +These calls can also be made through the register using template parameters: + +```cpp +PORTA_OUT::set(); +``` + +Registers can take multiple masks at once too. The masks will be merged so that +the register is only read and written once: + +```cpp +PORTA_OUT::toggle(); +``` + +A `modify` function is also supported, which takes a list of mask operations as +shown below. This allows the different operations to be carried out together, +still keeping to a single register read and write: + +```cpp +PORTA_OUT::modify(); // Only have LED_1 turned on +``` + +What if we need to add a third LED? And what if that LED is on a different +register, PORTB? + +This is where `RegisterGroup` comes in handy: + +```cpp +using PORTB_OUT = fr::MemRegister; + +using LED_3 = fr::RegisterMask; + +// Group the output ports together: +using LEDS = fr::RegisterGroup; +``` + +By grouping the two registers, we can carry out our modification calls without +worrying about which mask is for what register. The `RegisterGroup` will take +of that, while still merging operations when possible to maintain minimal I/O: + +```cpp +LEDS::clear(); + +LEDS::modify(); // You get the idea... +``` + +## Other features + +### Multi-bit masks + +Say bits two through five in a register select a clock's prescaler: + +```cpp +using CLOCK_DIV = fr::RegisterMask; +``` + +The mask's `write` function will let you write values to the field: + +```cpp +CLOCK_DIV::write<0x03>(); +``` + +This will read the register's current value, clear all bits selected by the +mask, set the new value `0x03` in the mask's location, then update the +register. + +`write` can also be included in `modify` chains: + +```cpp +CLOCK_CONTROL::modify, CLOCK_ENABLE::set>(); +``` + +A `RegisterMaskValue` can also be defined to identify specific values: + +```cpp +using CLOCK_DIV4 = fr::RegisterMaskValue; +``` + +`RegisterMaskValue` supports three functions: `set`, which would call +`CLOCK_DIV::write<0x03>()`; `clear`, which clears the masked bits; and `test`, +which would confirm that the register contains the value `0x03` in the masked +bits' location. + +### External registers + +"External" registers are registers that are not memory-mapped. These are also +supported in *funreg*, and can even be placed in `RegisterGroup`s with +other register types. + +An "access type" must be defined to specify how the register is accessed. Here +is the definition of `MemoryIO`, which is used for memory-mapped registers: + +```cpp +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; + } +}; +``` + +Custom access types should use `MemoryIO` as a template. For example, here is +an access type for ports on x86 processors: + +```cpp +template +struct PortIO { + using type = T; + constexpr static auto addr = Addr; + + static T read() { + T ret; + asm volatile("in %0, %1" : "=r" (ret) : "r" (Addr)); + return ret; + } + + static void write(const T& value) { + asm volatile("out %0, %1" :: "r" (value), "r" (Addr)); + } +}; +``` + +Now, just define your register(s) using `ExtRegister`: + +```cpp +using KEYBOARD = fr::ExtRegister; +``` + diff --git a/README.md b/README.md index c46b295..7ca829c 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,34 @@ -# funreg: Functional Memory-mapped Register I/O +# funreg: Functional Register I/O using modern C++ -*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. +*funreg* provides a functional approach to interacting with registers. +The library includes support for memory-mapped registers; however, other types +of registers can be supported through creating a simple access interface. -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 unique feature of this library is its ability to handle multiple register +operations with a single function call; these operations will be merged +together so that the register is only read and written once. -A tutorial or guide will be added soon. +Registers may also be organized into groups. These groups can similarly receive +a list of operations, which will be directed the to the appropriate registers +for the same single-read-single-write process. + +For example, LEDs can be controlled by a microcontroller with a single call: + +```cpp +LEDS::modify(); +``` + +...no matter if the LEDs use different registers, or if any of them are +controlled by an external circuit rather than a built-in IO peripheral. + +See `GUIDE.md` for a walk-through of the available functionality. + +## Feature overview + +* Define registers of any size, at any address, with optional custom access interface +* Define register masks to name the bits of registers +* Define register groups so ease programming (e.g. define an `RTC` group to work with all real-time clock registers at once) +* Make modifications through groups, masks, or the registers directly ## Requirements diff --git a/funreg.hpp b/funreg.hpp index 156b6cf..00cd3a9 100644 --- a/funreg.hpp +++ b/funreg.hpp @@ -1,5 +1,5 @@ /** - * funreg.hpp - Functional memory-mapped register I/O using modern C++. + * funreg.hpp - Functional register I/O using modern C++. * Written by Clyne Sullivan. * */ @@ -7,11 +7,22 @@ #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. +// A utility to measure a bit-mask's offset from bit zero. template constexpr auto BitOffset = []() constexpr { if constexpr (Mask & 1) @@ -21,23 +32,66 @@ constexpr auto BitOffset = []() constexpr { }(); /** - * @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. + * @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. * - * Defines a memory-mapped register that is usually accessed with a pointer of - * type T*, and is located at address Addr. + * 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. * - * Use only as a type, e.g. "using GPIO_OUT = Register" + * 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 auto get() { - return reinterpret_cast(Addr); + constexpr static T read() { + return Access::read(); + } + + /** + * Overwrites the register's value. + */ + constexpr static void write(const T& value) { + Access::write(value); } /** @@ -45,14 +99,14 @@ struct Register { */ template static void set() { - apply([](auto r, auto m) { *r = *r | m; }); + apply([](auto r, auto m) { return r | m; }); } /** * Sets register bits to '1' according to the given mask. */ static void set(const T& mask) { - *get() = *get() | mask; + write(read() | mask); } /** @@ -60,14 +114,14 @@ struct Register { */ template static void clear() { - apply([](auto r, auto m) { *r = *r & ~m; }); + apply([](auto r, auto m) { return r & ~m; }); } /** * Clears register bits to '0' according to the given mask. */ static void clear(const T& mask) { - *get() = *get() & ~mask; + write(read() & ~mask); } /** @@ -75,14 +129,14 @@ struct Register { */ template static void toggle() { - apply([](auto r, auto m) { *r = *r ^ m; }); + apply([](auto r, auto m) { return r ^m; }); } /** * Toggles bits in the register according to the given mask. */ static void toggle(const T& mask) { - *get() = *get() ^ mask; + write(read() ^ mask); } /** @@ -92,26 +146,10 @@ struct Register { */ 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; + if constexpr (sizeof...(Masks) > 0) + return read() & mergeMasks(); + else + return read(); } /** @@ -121,17 +159,10 @@ struct Register { 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; - } + auto mask = mergeMasks(); + return (read() & mask) == mask; } else { - return *get() != 0; + return read() != 0; } } @@ -144,17 +175,17 @@ struct Register { */ template static void modify() { - if (((Addr == Ops::reg::addr) | ...)) { - auto mask = *get(); + if constexpr ((isThis | ...)) { + auto mask = read(); ([&mask] { - if (Addr == Ops::reg::addr) + if constexpr (isThis) mask = Ops(mask); }(), ...); - *get() = mask; + write(mask); } } - // Below is meant for internal use only. + // 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 @@ -163,21 +194,50 @@ struct Register { template static void apply(auto fn) { if constexpr (sizeof...(Masks) > 0) { - auto mask = + 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 Addr == Masks::reg::addr ? Masks::mask : 0; + return isThis ? Masks::mask : 0; }() | ...); - if (mask) - fn(get(), mask); + return mask; + } else { + return 0; + } } else { - fn(get(), T(0) - 1); + 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; - constexpr static auto addr = Addr; }; /** @@ -203,7 +263,7 @@ struct RegisterMask */ struct set { constexpr set() { - *Reg::get() = *Reg::get() | Mask; + Reg::write(Reg::read() | Mask); } // For internal use. @@ -219,7 +279,7 @@ struct RegisterMask */ struct clear { constexpr clear() { - *Reg::get() = *Reg::get() & ~Mask; + Reg::write(Reg::read() & ~Mask); } // For internal use. @@ -235,7 +295,7 @@ struct RegisterMask */ struct toggle { constexpr toggle() { - *Reg::get() = *Reg::get() ^ Mask; + Reg::write(Reg::read() ^ Mask); } // For internal use. @@ -249,7 +309,7 @@ struct RegisterMask * Reads from the paired register, applying the bit-mask. */ static auto read() { - return *Reg::get() & Mask; + return Reg::read() & Mask; } /** @@ -272,10 +332,10 @@ struct RegisterMask template struct write { constexpr write() { - auto r = *Reg::get(); + auto r = Reg::read(); r &= ~Mask; r |= value << BitOffset; - *Reg::get() = r; + Reg::write(r); } // For internal use. @@ -332,7 +392,7 @@ struct RegisterMaskValue * Tests if this value is currently set in the register. */ static bool test() { - return Mask::read() & (value << BitOffset); + return (Mask::read() & Mask::mask) == (value << BitOffset); } }; @@ -352,11 +412,11 @@ 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. + * only written once. */ template static void set() { - apply([](auto r, auto m) { *r = *r | m; }); + apply([](auto r, auto m) { return r | m; }); } /** @@ -366,7 +426,7 @@ public: */ template static void clear() { - apply([](auto r, auto m) { *r = *r & ~m; }); + apply([](auto r, auto m) { return r & ~m; }); } /** @@ -376,7 +436,7 @@ public: */ template static void toggle() { - apply([](auto r, auto m) { *r = *r ^ m; }); + apply([](auto r, auto m) { return r ^ m; }); } /** @@ -407,6 +467,38 @@ private: 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 +