external-register #1
178
GUIDE.md
Normal file
178
GUIDE.md
Normal file
@ -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<uint8_t, 0x0021>;
|
||||||
|
```
|
||||||
|
|
||||||
|
`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<PORTA_OUT, (1 << 0)>; // bit zero
|
||||||
|
using LED_2 = fr::RegisterMask<PORTA_OUT, (1 << 2)>; // bit two
|
||||||
|
```
|
||||||
|
|
||||||
|
We can also give a name to both LEDs:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using LED_ALL = fr::RegisterMask<PORTA_OUT, (1 << 0) | (1 << 2)>;
|
||||||
|
```
|
||||||
|
|
||||||
|
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<LED_2>();
|
||||||
|
```
|
||||||
|
|
||||||
|
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<LED_1, LED_2>();
|
||||||
|
```
|
||||||
|
|
||||||
|
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<LED_1::set, LED_2::clear>(); // 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<uint8_t, 0x0041>;
|
||||||
|
|
||||||
|
using LED_3 = fr::RegisterMask<PORTB_OUT, (1 << 5)>;
|
||||||
|
|
||||||
|
// Group the output ports together:
|
||||||
|
using LEDS = fr::RegisterGroup<PORTA_OUT, PORTB_OUT>;
|
||||||
|
```
|
||||||
|
|
||||||
|
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<LED_1, LED_2, LED_3>();
|
||||||
|
|
||||||
|
LEDS::modify<LED_1::set,
|
||||||
|
LED_2::clear,
|
||||||
|
LED_3::toggle>(); // 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<CLOCK_CONTROL, 0x3C>;
|
||||||
|
```
|
||||||
|
|
||||||
|
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_DIV::write<0x03>, CLOCK_ENABLE::set>();
|
||||||
|
```
|
||||||
|
|
||||||
|
A `RegisterMaskValue` can also be defined to identify specific values:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using CLOCK_DIV4 = fr::RegisterMaskValue<CLOCK_DIV, 0x03>;
|
||||||
|
```
|
||||||
|
|
||||||
|
`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<typename T, uintptr_t Addr>
|
||||||
|
struct MemoryIO {
|
||||||
|
using type = T;
|
||||||
|
constexpr static auto addr = Addr;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the register's value.
|
||||||
|
*/
|
||||||
|
constexpr static T read() {
|
||||||
|
return *reinterpret_cast<volatile T*>(Addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overwrites the register's value.
|
||||||
|
*/
|
||||||
|
constexpr static void write(const T& value) {
|
||||||
|
*reinterpret_cast<volatile T*>(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<typename T, uintptr_t Addr>
|
||||||
|
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<PortIO, uint8_t, 0x60>;
|
||||||
|
```
|
||||||
|
|
38
README.md
38
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
|
*funreg* provides a functional approach to interacting with registers.
|
||||||
with zero overhead. This library primarily targets embedded firmware, where
|
The library includes support for memory-mapped registers; however, other types
|
||||||
these types of operations are frequently encountered.
|
of registers can be supported through creating a simple access interface.
|
||||||
|
|
||||||
What makes this library unique is its ability to carry out multiple register
|
A unique feature of this library is its ability to handle multiple register
|
||||||
operations with a single function call, reducing this to a single register read
|
operations with a single function call; these operations will be merged
|
||||||
and write. Further, registers can be organized into "groups": these groups can
|
together so that the register is only read and written once.
|
||||||
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.
|
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<LED1::set, LED2::clear, LED3::set>();
|
||||||
|
```
|
||||||
|
|
||||||
|
...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
|
## Requirements
|
||||||
|
|
||||||
|
228
funreg.hpp
228
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.
|
* Written by Clyne Sullivan.
|
||||||
* <https://github.com/tcsullivan/funreg>
|
* <https://github.com/tcsullivan/funreg>
|
||||||
*/
|
*/
|
||||||
@ -7,8 +7,19 @@
|
|||||||
#ifndef FUNCTIONAL_REGISTER_IO_H
|
#ifndef FUNCTIONAL_REGISTER_IO_H
|
||||||
#define 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 <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#ifdef FUNREG_ENABLE_EXTERNAL_IO
|
||||||
|
#include <type_traits>
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace fr {
|
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.
|
||||||
@ -21,23 +32,66 @@ constexpr auto BitOffset = []() constexpr {
|
|||||||
}();
|
}();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @struct Register
|
* @struct MemoryIO
|
||||||
* @brief Defines a memory-mapped register, given bit-size and address.
|
* @brief Specifies how to access a memory-mapped register.
|
||||||
* @tparam T The integer type that matches the size of the register.
|
* @tparam T The size of the register.
|
||||||
* @tparam Addr The memory address of the register.
|
* @tparam Addr The memory address of the register.
|
||||||
*
|
*
|
||||||
* Defines a memory-mapped register that is usually accessed with a pointer of
|
* To create an I/O access type for external register access, use this
|
||||||
* type T*, and is located at address Addr.
|
* structure as a template.
|
||||||
*
|
|
||||||
* Use only as a type, e.g. "using GPIO_OUT = Register<uint32_t, 0xA0004120>"
|
|
||||||
*/
|
*/
|
||||||
template<typename T, uintptr_t Addr>
|
template<typename T, uintptr_t Addr>
|
||||||
|
struct MemoryIO {
|
||||||
|
using type = T;
|
||||||
|
constexpr static auto addr = Addr;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the register's value.
|
||||||
|
*/
|
||||||
|
constexpr static T read() {
|
||||||
|
return *reinterpret_cast<volatile T*>(Addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overwrites the register's value.
|
||||||
|
*/
|
||||||
|
constexpr static void write(const T& value) {
|
||||||
|
*reinterpret_cast<volatile T*>(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<typename Access>
|
||||||
struct Register {
|
struct Register {
|
||||||
|
using access = Access;
|
||||||
|
using T = typename Access::type;
|
||||||
|
constexpr static auto Addr = Access::addr;
|
||||||
|
#else
|
||||||
|
template<typename T, uintptr_t Addr>
|
||||||
|
struct Register {
|
||||||
|
using RegAccess = MemoryIO<T, Addr>;
|
||||||
|
#endif // FUNREG_ENABLE_EXTERNAL_IO
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a pointer to the register.
|
* Gets a pointer to the register.
|
||||||
*/
|
*/
|
||||||
constexpr static auto get() {
|
constexpr static T read() {
|
||||||
return reinterpret_cast<volatile T*>(Addr);
|
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<typename... Masks>
|
template<typename... Masks>
|
||||||
static void set() {
|
static void set() {
|
||||||
apply<Masks...>([](auto r, auto m) { *r = *r | m; });
|
apply<Masks...>([](auto r, auto m) { return r | m; });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets register bits to '1' according to the given mask.
|
* Sets register bits to '1' according to the given mask.
|
||||||
*/
|
*/
|
||||||
static void set(const T& mask) {
|
static void set(const T& mask) {
|
||||||
*get() = *get() | mask;
|
write(read() | mask);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -60,14 +114,14 @@ struct Register {
|
|||||||
*/
|
*/
|
||||||
template<typename... Masks>
|
template<typename... Masks>
|
||||||
static void clear() {
|
static void clear() {
|
||||||
apply<Masks...>([](auto r, auto m) { *r = *r & ~m; });
|
apply<Masks...>([](auto r, auto m) { return r & ~m; });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clears register bits to '0' according to the given mask.
|
* Clears register bits to '0' according to the given mask.
|
||||||
*/
|
*/
|
||||||
static void clear(const T& mask) {
|
static void clear(const T& mask) {
|
||||||
*get() = *get() & ~mask;
|
write(read() & ~mask);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -75,14 +129,14 @@ struct Register {
|
|||||||
*/
|
*/
|
||||||
template<typename... Masks>
|
template<typename... Masks>
|
||||||
static void toggle() {
|
static void toggle() {
|
||||||
apply<Masks...>([](auto r, auto m) { *r = *r ^ m; });
|
apply<Masks...>([](auto r, auto m) { return r ^m; });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggles bits in the register according to the given mask.
|
* Toggles bits in the register according to the given mask.
|
||||||
*/
|
*/
|
||||||
static void toggle(const T& mask) {
|
static void toggle(const T& mask) {
|
||||||
*get() = *get() ^ mask;
|
write(read() ^ mask);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -92,26 +146,10 @@ struct Register {
|
|||||||
*/
|
*/
|
||||||
template<typename... Masks>
|
template<typename... Masks>
|
||||||
static auto read() {
|
static auto read() {
|
||||||
if constexpr (sizeof...(Masks) > 0) {
|
if constexpr (sizeof...(Masks) > 0)
|
||||||
if (((Addr == Masks::reg::addr) | ...)) {
|
return read() & mergeMasks<Masks...>();
|
||||||
auto mask =
|
else
|
||||||
([] {
|
return read();
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -121,17 +159,10 @@ struct Register {
|
|||||||
template<typename... Masks>
|
template<typename... Masks>
|
||||||
static bool test() {
|
static bool test() {
|
||||||
if constexpr (sizeof...(Masks) > 0) {
|
if constexpr (sizeof...(Masks) > 0) {
|
||||||
if (((Addr == Masks::reg::addr) | ...)) {
|
auto mask = mergeMasks<Masks...>();
|
||||||
auto mask =
|
return (read() & mask) == mask;
|
||||||
([] {
|
|
||||||
return Addr == Masks::reg::addr ? Masks::mask : 0;
|
|
||||||
}() | ...);
|
|
||||||
return (*get() & mask) == mask;
|
|
||||||
} else {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
return *get() != 0;
|
return read() != 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,13 +175,13 @@ struct Register {
|
|||||||
*/
|
*/
|
||||||
template<typename... Ops>
|
template<typename... Ops>
|
||||||
static void modify() {
|
static void modify() {
|
||||||
if (((Addr == Ops::reg::addr) | ...)) {
|
if constexpr ((isThis<typename Ops::reg> | ...)) {
|
||||||
auto mask = *get();
|
auto mask = read();
|
||||||
([&mask] {
|
([&mask] {
|
||||||
if (Addr == Ops::reg::addr)
|
if constexpr (isThis<typename Ops::reg>)
|
||||||
mask = Ops(mask);
|
mask = Ops(mask);
|
||||||
}(), ...);
|
}(), ...);
|
||||||
*get() = mask;
|
write(mask);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,21 +194,50 @@ struct Register {
|
|||||||
template<typename... Masks>
|
template<typename... Masks>
|
||||||
static void apply(auto fn) {
|
static void apply(auto fn) {
|
||||||
if constexpr (sizeof...(Masks) > 0) {
|
if constexpr (sizeof...(Masks) > 0) {
|
||||||
auto mask =
|
auto mask = mergeMasks<Masks...>();
|
||||||
([] {
|
if constexpr (mask)
|
||||||
return Addr == Masks::reg::addr ? Masks::mask : 0;
|
write(fn(read(), mask));
|
||||||
}() | ...);
|
|
||||||
if (mask)
|
|
||||||
fn(get(), mask);
|
|
||||||
} else {
|
} else {
|
||||||
fn(get(), T(0) - 1);
|
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<typename... Masks>
|
||||||
|
static auto mergeMasks() {
|
||||||
|
if constexpr (sizeof...(Masks) > 0) {
|
||||||
|
if constexpr ((isThis<typename Masks::reg> | ...)) {
|
||||||
|
auto mask =
|
||||||
|
([] {
|
||||||
|
return isThis<typename Masks::reg> ? 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<typename Reg>
|
||||||
|
constexpr static bool isThis = [] {
|
||||||
|
return std::is_same_v<typename Reg::access, access> && Addr == Reg::Addr;
|
||||||
|
}();
|
||||||
|
#else
|
||||||
|
// Determines if the given register matches this one.
|
||||||
|
template<typename Reg>
|
||||||
|
constexpr static bool isThis = [] {
|
||||||
|
return Addr == Reg::Addr;
|
||||||
|
}();
|
||||||
|
#endif // FUNREG_ENABLE_EXTERNAL_IO
|
||||||
|
|
||||||
Register() = delete;
|
Register() = delete;
|
||||||
|
|
||||||
using type = T;
|
using type = T;
|
||||||
constexpr static auto addr = Addr;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -203,7 +263,7 @@ struct RegisterMask
|
|||||||
*/
|
*/
|
||||||
struct set {
|
struct set {
|
||||||
constexpr set() {
|
constexpr set() {
|
||||||
*Reg::get() = *Reg::get() | Mask;
|
Reg::write(Reg::read() | Mask);
|
||||||
}
|
}
|
||||||
|
|
||||||
// For internal use.
|
// For internal use.
|
||||||
@ -219,7 +279,7 @@ struct RegisterMask
|
|||||||
*/
|
*/
|
||||||
struct clear {
|
struct clear {
|
||||||
constexpr clear() {
|
constexpr clear() {
|
||||||
*Reg::get() = *Reg::get() & ~Mask;
|
Reg::write(Reg::read() & ~Mask);
|
||||||
}
|
}
|
||||||
|
|
||||||
// For internal use.
|
// For internal use.
|
||||||
@ -235,7 +295,7 @@ struct RegisterMask
|
|||||||
*/
|
*/
|
||||||
struct toggle {
|
struct toggle {
|
||||||
constexpr toggle() {
|
constexpr toggle() {
|
||||||
*Reg::get() = *Reg::get() ^ Mask;
|
Reg::write(Reg::read() ^ Mask);
|
||||||
}
|
}
|
||||||
|
|
||||||
// For internal use.
|
// For internal use.
|
||||||
@ -249,7 +309,7 @@ struct RegisterMask
|
|||||||
* Reads from the paired register, applying the bit-mask.
|
* Reads from the paired register, applying the bit-mask.
|
||||||
*/
|
*/
|
||||||
static auto read() {
|
static auto read() {
|
||||||
return *Reg::get() & Mask;
|
return Reg::read() & Mask;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -272,10 +332,10 @@ struct RegisterMask
|
|||||||
template<T value>
|
template<T value>
|
||||||
struct write {
|
struct write {
|
||||||
constexpr write() {
|
constexpr write() {
|
||||||
auto r = *Reg::get();
|
auto r = Reg::read();
|
||||||
r &= ~Mask;
|
r &= ~Mask;
|
||||||
r |= value << BitOffset<Mask>;
|
r |= value << BitOffset<Mask>;
|
||||||
*Reg::get() = r;
|
Reg::write(r);
|
||||||
}
|
}
|
||||||
|
|
||||||
// For internal use.
|
// For internal use.
|
||||||
@ -332,7 +392,7 @@ struct RegisterMaskValue
|
|||||||
* Tests if this value is currently set in the register.
|
* Tests if this value is currently set in the register.
|
||||||
*/
|
*/
|
||||||
static bool test() {
|
static bool test() {
|
||||||
return Mask::read() & (value << BitOffset<Mask>);
|
return (Mask::read() & Mask::mask) == (value << BitOffset<Mask>);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -356,7 +416,7 @@ public:
|
|||||||
*/
|
*/
|
||||||
template<typename... Masks>
|
template<typename... Masks>
|
||||||
static void set() {
|
static void set() {
|
||||||
apply<Masks...>([](auto r, auto m) { *r = *r | m; });
|
apply<Masks...>([](auto r, auto m) { return r | m; });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -366,7 +426,7 @@ public:
|
|||||||
*/
|
*/
|
||||||
template<typename... Masks>
|
template<typename... Masks>
|
||||||
static void clear() {
|
static void clear() {
|
||||||
apply<Masks...>([](auto r, auto m) { *r = *r & ~m; });
|
apply<Masks...>([](auto r, auto m) { return r & ~m; });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -376,7 +436,7 @@ public:
|
|||||||
*/
|
*/
|
||||||
template<typename... Masks>
|
template<typename... Masks>
|
||||||
static void toggle() {
|
static void toggle() {
|
||||||
apply<Masks...>([](auto r, auto m) { *r = *r ^ m; });
|
apply<Masks...>([](auto r, auto m) { return r ^ m; });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -407,6 +467,38 @@ private:
|
|||||||
template<typename... RegMasks>
|
template<typename... RegMasks>
|
||||||
constexpr auto Masks = (RegMasks::mask | ...);
|
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<typename T, uintptr_t Addr>
|
||||||
|
using MemRegister = Register<MemoryIO<T, Addr>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<template<typename, uintptr_t> typename ExtIO, typename T, uintptr_t Addr>
|
||||||
|
using ExtRegister = Register<ExtIO<T, Addr>>;
|
||||||
|
#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<typename T, uintptr_t Addr>
|
||||||
|
using MemRegister = Register<T, Addr>;
|
||||||
|
#endif // FUNREG_ENABLE_EXTERNAL_IO
|
||||||
|
|
||||||
} // namespace fr
|
} // namespace fr
|
||||||
|
|
||||||
#endif // FUNCTIONAL_REGISTER_IO_H
|
#endif // FUNCTIONAL_REGISTER_IO_H
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user