From 0a3a9188e70ca435f600dc1cceee614895468572 Mon Sep 17 00:00:00 2001 From: Clyne Sullivan Date: Sun, 7 Aug 2022 19:23:07 -0400 Subject: [PATCH 1/4] draft; make reg access generic --- funreg.hpp | 152 +++++++++++++++++++++++++++++++++-------------------- 1 file changed, 95 insertions(+), 57 deletions(-) diff --git a/funreg.hpp b/funreg.hpp index 156b6cf..24a34fe 100644 --- a/funreg.hpp +++ b/funreg.hpp @@ -11,7 +11,7 @@ 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) @@ -20,6 +20,40 @@ constexpr auto BitOffset = []() constexpr { return BitOffset<(Mask >> 1), (N + 1)>; }(); +template +struct MemoryIO { + constexpr static auto addr = Addr; + + /** + * Gets a pointer to the register. + */ + constexpr static T read() { + return *reinterpret_cast(Addr); + } + + /** + * Overwrites the register's value. + */ + constexpr static void write(const T& value) { + *reinterpret_cast(Addr) = value; + } +}; + +template +struct ExternalIO { + constexpr static auto addr = Addr; + + /** + * Gets a pointer to the register. + */ + static T (*read)(); + + /** + * Overwrites the register's value. + */ + static void (*write)(const T& value); +}; + /** * @struct Register * @brief Defines a memory-mapped register, given bit-size and address. @@ -31,13 +65,22 @@ constexpr auto BitOffset = []() constexpr { * * Use only as a type, e.g. "using GPIO_OUT = Register" */ -template +template struct Register { + constexpr static auto Addr = RegAccess::addr; + /** * Gets a pointer to the register. */ - constexpr static auto get() { - return reinterpret_cast(Addr); + constexpr static T read() { + return RegAccess::read(); + } + + /** + * Overwrites the register's value. + */ + constexpr static void write(const T& value) { + RegAccess::write(value); } /** @@ -45,14 +88,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 +103,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 +118,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 +135,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 +148,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; } } @@ -145,16 +165,16 @@ struct Register { template static void modify() { if (((Addr == Ops::reg::addr) | ...)) { - auto mask = *get(); + auto mask = read(); ([&mask] { if (Addr == Ops::reg::addr) 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,14 +183,28 @@ struct Register { template static void apply(auto fn) { if constexpr (sizeof...(Masks) > 0) { - auto mask = + auto mask = mergeMasks(); + if (mask) + write(fn(read(), mask)); + } else { + write(fn(read(), T(0) - 1)); + } + } + + template + static auto mergeMasks() { + if constexpr (sizeof...(Masks) > 0) { + if (((Addr == Masks::reg::addr) | ...)) { + auto mask = ([] { return Addr == Masks::reg::addr ? Masks::mask : 0; }() | ...); - if (mask) - fn(get(), mask); + return mask; + } else { + return 0; + } } else { - fn(get(), T(0) - 1); + return 0; } } @@ -203,7 +237,7 @@ struct RegisterMask */ struct set { constexpr set() { - *Reg::get() = *Reg::get() | Mask; + Reg::write(Reg::read() | Mask); } // For internal use. @@ -219,7 +253,7 @@ struct RegisterMask */ struct clear { constexpr clear() { - *Reg::get() = *Reg::get() & ~Mask; + Reg::write(Reg::read() & ~Mask); } // For internal use. @@ -235,7 +269,7 @@ struct RegisterMask */ struct toggle { constexpr toggle() { - *Reg::get() = *Reg::get() ^ Mask; + Reg::write(Reg::read() ^ Mask); } // For internal use. @@ -249,7 +283,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 +306,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. @@ -352,11 +386,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 +400,7 @@ public: */ template static void clear() { - apply([](auto r, auto m) { *r = *r & ~m; }); + apply([](auto r, auto m) { return r & ~m; }); } /** @@ -376,7 +410,7 @@ public: */ template static void toggle() { - apply([](auto r, auto m) { *r = *r ^ m; }); + apply([](auto r, auto m) { return r ^ m; }); } /** @@ -407,6 +441,10 @@ private: template constexpr auto Masks = (RegMasks::mask | ...); +template +using MemRegister = Register>; + } // namespace fr #endif // FUNCTIONAL_REGISTER_IO_H + -- 2.39.2 From c0ca2d3a373e66f474d21ce34c25b38d953a66c7 Mon Sep 17 00:00:00 2001 From: Clyne Sullivan Date: Mon, 8 Aug 2022 08:42:21 -0400 Subject: [PATCH 2/4] external i/o implemented --- funreg.hpp | 122 ++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 88 insertions(+), 34 deletions(-) diff --git a/funreg.hpp b/funreg.hpp index 24a34fe..6ca44ed 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,8 +7,19 @@ #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. @@ -20,8 +31,18 @@ constexpr auto BitOffset = []() constexpr { 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; /** @@ -39,48 +60,38 @@ struct MemoryIO { } }; -template -struct ExternalIO { - constexpr static auto addr = Addr; - - /** - * Gets a pointer to the register. - */ - static T (*read)(); - - /** - * Overwrites the register's value. - */ - static void (*write)(const T& value); -}; - /** * @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. + * @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. */ -template +#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 { - constexpr static auto Addr = RegAccess::addr; + using RegAccess = MemoryIO; +#endif // FUNREG_ENABLE_EXTERNAL_IO /** * Gets a pointer to the register. */ constexpr static T read() { - return RegAccess::read(); + return Access::read(); } /** * Overwrites the register's value. */ constexpr static void write(const T& value) { - RegAccess::write(value); + Access::write(value); } /** @@ -164,10 +175,10 @@ struct Register { */ template static void modify() { - if (((Addr == Ops::reg::addr) | ...)) { + if constexpr ((isThis | ...)) { auto mask = read(); ([&mask] { - if (Addr == Ops::reg::addr) + if constexpr (isThis) mask = Ops(mask); }(), ...); write(mask); @@ -184,20 +195,22 @@ struct Register { static void apply(auto fn) { if constexpr (sizeof...(Masks) > 0) { auto mask = mergeMasks(); - if (mask) + 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 (((Addr == Masks::reg::addr) | ...)) { + if constexpr ((isThis | ...)) { auto mask = ([] { - return Addr == Masks::reg::addr ? Masks::mask : 0; + return isThis ? Masks::mask : 0; }() | ...); return mask; } else { @@ -208,10 +221,23 @@ struct Register { } } +#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; }; /** @@ -441,8 +467,36 @@ 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>; +using MemRegister = Register; +#endif // FUNREG_ENABLE_EXTERNAL_IO } // namespace fr -- 2.39.2 From 86c4b3b5ceaa1bd927a443fa254cdab76b44a480 Mon Sep 17 00:00:00 2001 From: Clyne Sullivan Date: Mon, 8 Aug 2022 21:03:52 -0400 Subject: [PATCH 3/4] add guide; couple small fixes --- GUIDE.md | 178 +++++++++++++++++++++++++++++++++++++++++++++++++++++ funreg.hpp | 4 +- 2 files changed, 180 insertions(+), 2 deletions(-) create mode 100644 GUIDE.md diff --git a/GUIDE.md b/GUIDE.md new file mode 100644 index 0000000..6d3b702 --- /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; +``` + +`fr::MemRegister` is an `fr::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 +`fr::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: + +```cpp +PORTA_OUT::toggle(); +``` + +They also support a `modify` function, which takes a list of mask operations as +shown below. Through `modify`, the register is only read and written once, +minimizing I/O. + +```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 what you could do: + +```cpp +using PORTB_OUT = fr::MemRegister; + +using LED_3 = fr::RegisterMask; + +// Group the output ports together: +using LEDS = fr::RegisterGroup; +``` + +By defining a `RegisterGroup`, we can make the same calls to modify the LEDs as +we would with the single register. `RegisterGroup` will direct masks to their +appropriate registers, while merging operations on the same register to +maintain that 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, bitwise-OR the value `0x03` to the mask's location, then write the new +value to the register. + +`write` can also be included in `modify` chains: + +```cpp +CLOCK_CONTROL::modify, CLOCK_ENABLE::set>(); +``` + +You may also define a `RegisterMaskValue` to name a specific value: + +```cpp +using CLOCK_DIV4 = fr::RegisterMaskValue; +``` + +Three functions are supported for `RegisterMaskValue`: `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 +memory-mapped or 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/funreg.hpp b/funreg.hpp index 6ca44ed..00cd3a9 100644 --- a/funreg.hpp +++ b/funreg.hpp @@ -46,7 +46,7 @@ struct MemoryIO { constexpr static auto addr = Addr; /** - * Gets a pointer to the register. + * Reads the register's value. */ constexpr static T read() { return *reinterpret_cast(Addr); @@ -392,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); } }; -- 2.39.2 From a35ded8145691d7f616a498da432dcbced8bb0ef Mon Sep 17 00:00:00 2001 From: Clyne Sullivan Date: Tue, 9 Aug 2022 22:06:06 -0400 Subject: [PATCH 4/4] update documentation --- GUIDE.md | 32 ++++++++++++++++---------------- README.md | 38 ++++++++++++++++++++++++++++---------- 2 files changed, 44 insertions(+), 26 deletions(-) diff --git a/GUIDE.md b/GUIDE.md index 6d3b702..80abfb6 100644 --- a/GUIDE.md +++ b/GUIDE.md @@ -10,12 +10,12 @@ For an 8-bit register at memory address `0x0021`, you would write: using PORTA_OUT = fr::MemRegister; ``` -`fr::MemRegister` is an `fr::Register` that uses `MemoryIO` access. Registers +`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 -`fr::RegisterMask` lets us name one or more bits within a register. +`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: @@ -40,15 +40,16 @@ These calls can also be made through the register using template parameters: PORTA_OUT::set(); ``` -Registers can take multiple masks at once: +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(); ``` -They also support a `modify` function, which takes a list of mask operations as -shown below. Through `modify`, the register is only read and written once, -minimizing I/O. +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 @@ -57,7 +58,7 @@ 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 what you could do: +This is where `RegisterGroup` comes in handy: ```cpp using PORTB_OUT = fr::MemRegister; @@ -68,10 +69,9 @@ using LED_3 = fr::RegisterMask; using LEDS = fr::RegisterGroup; ``` -By defining a `RegisterGroup`, we can make the same calls to modify the LEDs as -we would with the single register. `RegisterGroup` will direct masks to their -appropriate registers, while merging operations on the same register to -maintain that minimal I/O: +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(); @@ -98,8 +98,8 @@ CLOCK_DIV::write<0x03>(); ``` This will read the register's current value, clear all bits selected by the -mask, bitwise-OR the value `0x03` to the mask's location, then write the new -value to the register. +mask, set the new value `0x03` in the mask's location, then update the +register. `write` can also be included in `modify` chains: @@ -107,13 +107,13 @@ value to the register. CLOCK_CONTROL::modify, CLOCK_ENABLE::set>(); ``` -You may also define a `RegisterMaskValue` to name a specific value: +A `RegisterMaskValue` can also be defined to identify specific values: ```cpp using CLOCK_DIV4 = fr::RegisterMaskValue; ``` -Three functions are supported for `RegisterMaskValue`: `set`, which would call +`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. @@ -122,7 +122,7 @@ bits' location. "External" registers are registers that are not memory-mapped. These are also supported in *funreg*, and can even be placed in `RegisterGroup`s with -memory-mapped or other register types. +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: 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 -- 2.39.2