1
0
mirror of https://github.com/sendyne/cppreg.git synced 2025-05-09 15:14:05 +00:00

INITIAL COMMIT

This commit is contained in:
Nicolas Clauvelin 2018-02-20 15:38:59 -05:00
commit d7404ab946
18 changed files with 2079 additions and 0 deletions

35
.gitignore vendored Normal file
View File

@ -0,0 +1,35 @@
### Generic git ignore file ###
# Object files
*.o
*.ko
*.obj
*.elf
# Precompiled Headers
*.gch
*.pch
# Libraries
*.lib
*.a
*.la
*.lo
# Shared objects (inc. Windows DLLs)
*.dll
*.so
*.so.*
*.dylib
# Executables
*.exe
*.out
*.app
*.i*86
*.x86_64
*.hex
*.bin
# Mac specific
.DS_Store

288
API.md Normal file
View File

@ -0,0 +1,288 @@
# cppreg: API #
Copyright Sendyne Corp., 2010-2018. All rights reserved ([LICENSE](LICENSE)).
## Introduction ##
`cppreg` provides ways to define custom C++ data types to represent memory-mapped input/output (MMIO) registers and fields. In essence, `cppreg` does contain very little *executable* code, but it does provide a framework to efficiently manipulate MMIO registers and fields.
`cppreg` is primarily designed to be used in applications on *ARM Cortex-M*-like hardware, that is, MCUs with 32 bits registers and address space. It can easily be extended to support other types of architecture but this is not provided out-of-the-box.
The entire implementation is encapsulated in the `cppreg::` namespace.
## Overview ##
`cppreg` provides two template structures that can be customized:
* `Register`: used to define a MMIO register and its memory device,
* `Field`: used to define a field in a MMIO register.
The `Register` type itself is simply designed to keep track of the register address, size and other additional data (*i.e.*, reset value and shadow value setting). The `Field` type is the most interesting one as it is the type that provides access to part of the register memory device depending on the access policy.
## Data types ##
`cppreg` introduces type aliases in order to parameterize the set of data types used in the implementation. By default the following types are defined (see [cppreg_Defines.h](cppreg_Defines.h) for more details):
* `Address_t` is the data type used to hold addresses of registers and fields; it is equivalent to `std::uintptr_t`,
* `Width_t` and `Offset_t` are the data types to represent register and field sizes and offsets; both are equivalent to `std::uint8_t` (this effectively limits the maximal width and offset to 256).
The data type used to manipulate register and field content is derived from the register size. At the moment only 32-bits, 16-bits, and 8-bits registers are supported but additional register sizes can easily be added (see [Traits.h](register/Traits.h)).
## Register ##
The `Register` type implementation (see [Register.h](register/Register.h)) is designed to encapsulate details relevant to a particular MMIO register and provides access to the register memory. In `cppreg` the data type used to represent the memory register is always marked as `volatile`.
To implement a particular register the following information are required at compile time:
* the address of the register,
* the register size (required to be different from `0`),
* the reset value of the register (this is optional and is defaulted to zero),
* a optional boolean flag to indicate if shadow value should be used (see below; this is optional and not enabled by default).
For example, consider a 32-bits register `PeripheralRegister` mapped at `0x40004242`. The `Register` type can be derived from to create a `PeripheralRegister` C++ type:
```c++
// Register is defined as a struct so public inheritance is the default.
struct PeripheralRegister : Register<0x40004242, 32u> {};
```
If `PeripheralRegister` has a reset value of `0xF0220F00` it can be added to the type definition by adding a template parameter (this is only useful when enabling shadow value as explained later):
```c++
struct PeripheralRegister : Register<0x40004242, 32u, 0xF0220F00> { ... };
```
Note that, it is also possible to simply define a type alias:
```c++
using PeripheralRegister = Register<0x40004242, 32u, 0xF0220F00>;
```
As we shall see below, the derived type `PeripheralRegister` is not very useful by itself. The benefit comes from using it to define `Field`-based types.
## Field ##
The `Field` type provided by `cppreg` (see [Field.h](register/Field.h)) contains the added value of the library in terms of type safety, efficiency and expression of intent. It is defined as a template structure and in order to define a custom field type the following information are required at compile time:
* a `Register`-type describing the register in which the field memory resides,
* the width of the field (required to be different from `0`),
* the offset of the field in the register,
* the access policy of the field (*i.e.*, read-write, read-only, or write-only).
Assume that the register `PeripheralRegister` from the previous example contains a 6-bits field `Frequency ` with an offset of 12 bits (with respect to the register base address; that is, starting at the 13-th bits because the first bit is the 0-th bit). The corresponding custom `Field` type would be defined as:
```c++
using Frequency = Field<PeripheralRegister, 6u, 12u, read_write>;
```
It can also be nested with the definition of `PeripheralRegister`:
```c++
// Register definition with nested field definition.
struct PeripheralRegister : Register<0x40004242, 32u> {
using Frequency = Field<PeripheralRegister, 6u, 12u, read_write>;
};
// This is strictly equivalent to:
namespace PeripheralRegister {
using _REG = Register<0x40004242, 32u>;
using Frequency = Field<_REG, 6u, 12u, read_write>;
}
// Or even:
// (again, Field is defined as a struct so public inheritance is the default.
struct PeripheralRegister : Register<0x40004242, 32u> {
struct Frequency : Field<PeripheralRegister, 6u, 12u, read_write> {};
};
```
which then makes it possible to write expression like:
```c++
PeripheralRegister::Frequency::clear();
PeripheralRegister::Frequency::write(0x10u);
```
to clear the `Frequency` register and then write `0x10` to it.
As the last example suggests, any `Field`-based type must defines its access policy (the last template parameter). Depending on the access policy various static methods are available (or not) to perform read and write operations.
### Access policy ###
The last template parameter of a `Field`-based type describes the access policy of the field. Three access policies are available:
* `read_write` for readable and writable fields,
* `read_only` for read-only fields,
* `write_only` for write-only fields.
Depending on the access policy, the `Field`-based type will provide accessors and/or modifier to its data as described by the following table:
| Method | R/W | RO | WO | Description |
|:--------------|:---------:|:---------:|:---------:| :-----------------------------------------------------|
| `read()` | YES | YES | NO | return the content of the field |
| `write(value)`| YES | NO | YES | write `value` to the field |
| `set()` | YES | NO | NO | set all the bits of the field to `1` |
| `clear()` | YES | NO | NO | clear all the bits of the field (*i.e.*, set to `0`) |
| `toggle()` | YES | NO | NO | toggle all the bits of the field |
Any attempt at calling an access method which is not provided by a given policy will result in a compilation error. This is one of the mechanism used by `cppreg` to provide safety when accessing registers and fields.
For example using our previous `PeripheralRegister` example:
```c++
// Register definition with nested fields definitions.
struct PeripheralRegister : Register<0x40004242, 32u> {
using Frequency = Field<PeripheralRegister, 6u, 12u, read_write>;
using Mode = Field<PeripheralRegister, 4u, 18u, write_only>;
using State = Field<PeripheralRegister, 4u, 18u, read_only>;
};
// This would compile:
PeripheralRegister::Frequency::write(0x10);
const auto freq = PeripheralRegister::Frequency::read();
const auto state = PeripheralRegister::State::read();
// This would not compile:
PeripheralRegister::State::write(0x1);
const auto mode = PeripheralRegister::Mode::read();
// This would compile ...
// But read the section dedicated to write-only fields.
PeripheralRegister::Mode::write(0xA);
```
### 1-bit addons ###
`Field`-based type which are 1-bit wide and readable (`read_write` or `read_only`) have two additional methods:
* `is_set` returns `true` if the single bit is equal to `1` (false otherwise),
* `is_clear` returns `true` if the single bit is equal to `0` (false otherwise).
### Overflow check ###
For writable `Field`-based types overflow will be prevented at runtime: only the bits part of the `Field`-type will be written and any data that does not fit the region of the memory device assigned to the `Field`-type will not be modified. This prevents overflow at runtime. But `cppreg` can also prevent overflow at compile time.
If the value to be written is known at compile time (which is often the case when dealing with hardware registers) one can use a template version of the `write` method. The template version does perform a check at compile time to ensure that the value to be written does not overflow:
```c++
// Register definition with nested fields definitions.
struct PeripheralRegister : Register<0x40004242, 32u> {
using Frequency = Field<PeripheralRegister, 8u, 12u, read_write>;
};
// These two calls are strictly equivalent:
PeripheralRegister::Frequency::write(0xAB);
PeripheralRegister::Frequency::write<0xAB>();
// This call does not perform a compile-time check for overflow:
PeripheralRegister::Frequency::write(0x111); // But this will only write 0x11 to the memory device.
// This call does perform a compile-time check for overflow and will not compile:
PeripheralRegister::Frequency::write<0x111>();
```
It strongly recommended to use the template version when the value is known at compile time.
## Shadow value: a workaround for write-only fields ##
Write-only fields are somewhat special as extra-care has to be taken when manipulating them. The main difficulty resides in the fact that write-only field can be read but the value obtained by reading it is fixed (*e.g.*, it always reads as zero). `cppreg` assumes that write-only fields can actually be read from; if such an access on some given architecture would trigger an error (*à la FPGA*) then `cppreg` is not a good choice to deal with write-only fields on this particular architecture.
Consider the following situation:
```c++
struct Reg : Register <0x00000001, 8u> {
using f1 = Field<Reg, 1u, 0u, read_write>;
using f2 = Field<Reg, 1u, 1u, write_only>; // Always reads as zero.
}
```
Here is what will be happening (assuming the register is initially zeroed out):
```c++
Reg::f1::write<0x1>(); // reg = (... 0000) | (... 0001) = (... 0001)
Reg::f2::write<0x1>(); // reg = (... 0010), f1 got wiped out.
Reg::f1::write<0x1>(); // reg = (... 0000) | (... 0001) = (... 0001), f2 wiped out cause it reads as zero.
```
This shows two issues:
* the default `write` implementation for a write-only field will wipe out the register bits that are not part of the field,
* when writing to the read-write field it wipes out the write-only field because there is no way to retrieve the value that was previously written.
As a workaround, `cppreg` offers a shadow value implementation which mitigates the issue by tracking the register value. This implementation can be triggered when defining a register type by using an explicit reset value and a boolean flag:
```c++
struct Reg : Register<
0x40004242, // Register address
32u, // Register size
0x42u // Register reset value
true // Enable shadow value for the register
>
{
using f1 = Field<Reg, 1u, 0u, read_write>;
using f2 = Field<Reg, 1u, 1u, write_only>; // Always reads as zero.
};
```
The shadow value implementation for a write-only field works as follow:
* at static initialization time, the reset value of the register owning the field is used to initialize the shadow value (the shadow value is used for the entire register content),
* at each write access to any of the register fields, the shadow value will be updated and written to the entire register memory.
This mechanism ensures that the register value is always consistent. This comes at the price of additional memory (storage for the shadow value) and instructions (updating and copying the shadow value).
A few safety guidelines:
* the register shadow value can be accessed directly from the register type but this value should not be modified manually (it is intended to provide read access),
* if the shadow value implementation is used then it should be used everywhere the register is accessed, otherwise the shadow value will be out of sync,
* in case a shadow value register contains fields that can be modified directly by hardware, the user should implement a synchronization mechanism before performing writing operations.
## MergeWrite: writing to multiple fields at once ##
It is sometimes the case that multiple fields within a register needs to be written at the same time. For example, when setting the clock dividers in a MCU it is often recommended to write all their values to the corresponding register at the same time (to avoid overclocking part of the MCU).
Consider the following setup (not so artifical; it is inspired by a real flash memory controller peripheral):
```c++
struct FlashCtrl : Register<0xF0008282, 8u> {
// Command field.
// Can bet set to various values to trigger write, erase or check operations.
using Command = Field<FlashCtrl, 4u, 0u, read_write>;
// Set if a flash write/erase command was done to a protected section.
// To clear write 1 to it.
// If set this should be cleared prior to starting a new command.
using ProtectionError = Field<FlashCtrl, 1u, 4u, read_write>;
// Command complete field.
// Read behavior: 1 = command completed, 0 = command in progress
// Write behavior: 1 = Start command, 0 = no effect
using CommandComplete = Field<FlashCtrl, 1u, 7u, read_write>;
};
```
Now let's assume the following scenario:
1. The previous flash command failed because it attempted to write or erase in a protected section, at that point the content of the `FlashCtrl` register is `1001 XXXX` where `XXXX` is whatver value associated with the command that failed.
2. Before we can perform a new flash command we need to clear the `ProtectionError` by writing 1 to it (otherwise the new command will not be started); so one could think about doing:
```c++
FlashCtrl::ProtectionError::set(); // Write to ProtectionError to clear it.
```
however this write at `1000 XXXX | 0001 0000 = 1001 XXXX` at the register level and thus start the command that previously failed.
3. At this point one could try to set the value for the new command but that will fail as well (because `ProtectionError` was not cleared and it is required to be).
4. A possible alternative would be to fully zero out the `FlashCtrl` register but that would somewhat defeat the purpose of `cppreg`.
For this kind of situation a *merge write* mechanism was implemented in `cppreg` to merge multiple write operations into a single one. This makes it possible to write the following code to solve the flash controller issue:
```c++
// Write to both ProtectionError and CommandComplete.
FlashCtrl::merge_write<FlashCtrl::ProtectionError>(1).with<FlashCtrl::CommandComplete>(0);
// This will correspond to write with a mask set to 1001 0000,
// which boils down to write (at the register level):
// 0000 XXXX | 0001 0000 = 0001 XXXX ... CommandComplete is not set to 1 !
```
The `merge_write` method is only available in `Register`-based type that do not enable the shadow value mechanism. The `Field`-based types used in the chained call are required to *be from* the `Register` type used to call `merge_write`. In addition, the `Field`-types are also required to be writable.
Finally, it is possible to use a template form when writing merge write operations to perform overflow checking at compile time (similar to `write(value)/write<value>()` methods).

53
CMakeLists.txt Normal file
View File

@ -0,0 +1,53 @@
# -------------------------------------------------------------------------- #
# cppreg library CMake script
#
# Nicolas Clauvelin (nclauvelin@sendyne.com)
# Sendyne Corp., 2017
#
# -------------------------------------------------------------------------- #
# --- cppreg library files ---
# Header directories.
set(CPPREG_HEADERS_DIRS
.
policies/
register/)
# List of API headers.
set(CPPREG_API_HEADERS
cppreg.h
cppreg_Defines.h
cppreg_Includes.h
policies/AccessPolicy.h
register/Field.h
register/Mask.h
register/Overflow.h
register/Register.h
register/ShadowValue.h
register/Traits.h)
# Refactor headers directories.
set(BUILD_HEADERS_DIRS "")
foreach(dir IN ITEMS ${CPPREG_HEADERS_DIRS})
list(APPEND BUILD_HEADERS_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/${dir}")
endforeach()
# Header-only library.
add_library(cppreg INTERFACE)
target_include_directories(cppreg INTERFACE . register/ policies/)
# Include directories interfaces.
# This properly setup the API headers for the build and install phase.
set_property(TARGET cppreg
PROPERTY INTERFACE_INCLUDE_DIRECTORIES
$<BUILD_INTERFACE:${BUILD_HEADERS_DIRS}>
$<INSTALL_INTERFACE:include/cppreg>)
# --- Install directives ---
install(FILES ${CPPREG_API_HEADERS} DESTINATION include/cppreg)
install(TARGETS cppreg DESTINATION lib EXPORT cppreg-libs)
install(EXPORT cppreg-libs DESTINATION include/cppreg)

34
LICENSE Normal file
View File

@ -0,0 +1,34 @@
__
________ ____ ____/ /_ ______ ___ _________ _________
/ ___/ _ \/ __ \/ __ / / / / __ \/ _ \ / ___/ __ \/ ___/ __ \
(__ ) __/ / / / /_/ / /_/ / / / / __/ / /__/ /_/ / / / /_/ /
/____/\___/_/ /_/\__,_/\__, /_/ /_/\___/ \___/\____/_/ / .___(_).
/____/ /_/
Copyright 2010-2018 Sendyne Corporation.
All rights reserved.
Sendyne Corp., 250 West Broadway, New York, NY 10013, USA.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Sendyne Corp. nor the names of its contributors may
be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

275
QuickStart.md Normal file
View File

@ -0,0 +1,275 @@
# cppreg: quick start #
Copyright Sendyne Corp., 2010-2018. All rights reserved ([LICENSE](LICENSE)).
This document is a brief introduction to `cppreg` and how to use it. For more details about the implementation refers to the [API documentation](API.md).
## How to use the library ##
There are two recommended ways to use `cppreg` in a project:
* for CMake-based projects, it is sufficient to add the project directory (using CMake `add_subdirectory`) and then to link against the `cppreg` target:
```cmake
# Include cppreg library.
# The second argument is not necessarily required depending on the project setup.
# This will import the cppreg target.
# Because cppreg is a header-only library the cppreg target is actually an INTERFACE library target.
add_subdirectory(/path/to/cppreg ${PROJECT_BINARY_DIR)/cppreg)
...
# Some executable.
add_executable(firmware firmware.cpp)
target_link_libraries(firmware cppreg)
```
* for non-CMake projects, the easiest way is to use the [single header version of cppreg](single/cppreg-all.h) and import/copy it in your project.
### Recommended compiler settings ###
Although `cppreg` is entirely written in C++, there is little (if any) overhead in term of runtime performance if at least some level of optimization are enabled (mostly to take care of inlining). For GCC ARM the following settings are recommended (and similar settings should be used for other compilers):
* enable `-Og` for debug builds (this helps significantly with inlining),
* for optimized builds (`-O2`,`-O3` or `-Os`) the default settings will be sufficient,
* link time and dead code optimization (LTO and `--gc-section` with `-ffunction-sections` and `-fdata-sections`) can also help produce a more optimized version of the code.
## Example: peripheral setup ##
Consider an arbitrary peripheral with a setup register:
| Absolute address (hex) | Register | Width (bits) | Access |
|:----------------------:|:-----------------|:------------:|:------------------:|
| 0xA400 0000 | Peripheral setup register | 8 | R/W |
The setup register bits are mapped as:
* FREQ field bits [0:4] to control the peripheral frequency,
* MODE field bits [5:6] to setup the peripheral mode,
* EN field bits [7] to enable the peripheral.
The goal of `cppreg` is to facilitate the manipulation of such a register and the first step is to define custom types (`Register` and `Field`) that maps to the peripheral:
```c++
#include <cppreg.h>
using namespace cppreg;
// Peripheral register.
// The first template parameter is the register address.
// The second template parameter is the register width in bits.
struct Peripheral : Register<0xA4000000, 8u> {
// When defining a Field-based type:
// - the first template parameter is the owning register,
// - the second template parameter is the field width in bits,
// - the third template parameter is the offset in bits,
// - the last parameter is the access policy (readable and writable).
using Frequency = Field<Peripheral, 5u, 0u, read_write>; // FREQ
using Mode = Field<Peripheral, 2u, 5u, read_write>; // MODE
using Enable = Field<Peripheral, 1u, 7u, read_write>; // EN
// To enable the peripheral:
// write 1 to EN field; if the peripheral fails to start this will be reset to zero.
// To disable the peripheral:
// clear EN field; no effect if not enabled.
};
```
Now let's assume that we want to setup and enable the peripheral following the procedure:
1. we first set the mode, say with value `0x2`,
2. then the frequency, say with value `0x1A`,
3. then write `1` to the enable field to start the peripheral.
This translates to:
```c++
// Setup and enable.
Peripheral::Mode::write<0x2>();
Peripheral::Frequency::write<0x1A>();
Peripheral::Enable::set();
// Check if properly enabled.
if (!Peripheral::Enable::is_set()) {
// Peripheral failed to start ...
};
...
// Later on we want to disable the peripheral.
Peripheral::Enable::clear();
```
A few remarks:
* in the `write` calls we can also use `write(value)` instead of `write<value>()`; the latter only exists if `value` is `constexpr` (*i.e.*, known at compile time) but the benefits is that it will check for overflow at compile time,
* 1-bit wide `Field`-based type have `is_set` and `is_clear` defined to conveniently query their states.
The advantage of `cppreg` is that it limits the possibility of errors (see the [API documentation](API.md) for more details):
```c++
// Overflow checks.
Peripheral::Mode::write<0x4>(); // Would not compile because it overflows the MODE field.
Peripheral::Frequency::write<0xFF>(); // Idem. This overflows the FREQ field.
Peripheral::Enable::set();
// Instead if you write:
Peripheral::Mode::write(0x4); // Compile but writes 0x0 to the MODE field.
Peripheral::Frequency::write(0xFF); // Compile but writes 0x1F to the FREQ field
```
We can even add more expressive methods for our peripheral:
```c++
#include <cppreg.h>
using namespace cppreg;
// Peripheral register.
struct Peripheral : Register<0xA4000000, 8u> {
using Frequency = Field<Peripheral, 5u, 0u, read_write>; // FREQ
using Mode = Field<Peripheral, 2u, 5u, read_write>; // MODE
using Enable = Field<Peripheral, 1u, 7u, read_write>; // EN
// To enable the peripheral:
// write 1 to EN field; if the peripheral fails to start this will be reset to zero.
// To disable the peripheral:
// clear EN field; no effect if not enabled.
// Configuration with mode and frequency.
template <Mode::type mode, Frequency::type f>
inline static void configure() {
// Using template parameters we can check for overflow at compile time.
Mode::write<mode>();
Frequency::write<f>();
};
// Enable method.
inline static void enable() {
Enable::set();
};
// Disable method.
inline static void disable() {
Enable::clear();
};
};
Peripheral::configure<0x2, 01A>();
Peripheral::enable();
```
## Example: simple interface for GPIO pins ##
Let's assume we are dealing with a 32 pins GPIO peripheral on some MCU and that the following memory map and registers are available:
| Absolute address (hex) | Register | Width (bits) | Access |
|:----------------------:|:-------------------------|:--:|:------------------:|
| 0xF400 0000 | Port data output register | 32 | R/W |
| 0xF400 0004 | Port set output register | 32 | W (always reads 0) |
| 0xF400 0008 | Port clear output register | 32 | W (always reads 0) |
| 0xF400 000C | Port toggle output register | 32 | W (always reads 0) |
| 0xF400 0010 | Port data input output register | 32 | W (always reads 0) |
| 0xF400 0014 | Port data direction output register | 32 | R/W |
For each register, the 32 bits maps to the 32 pins of the GPIO peripheral (bit 0 maps to pin 0 and so on). In other words, each register is composed of 32 fields that maps to the GPIO pins.
Using `cppreg` we can define custom types for these registers and define an interface for the GPIO peripheral:
```c++
// GPIO peripheral namespace.
namespace gpio {
// To define a register type:
// using x = Register<register_address, register_width in bits>;
// Data output register (PDOR).
using pdor = Register<0xF4000000, 32u>;
// Set output register (PSOR).
using psor = Register<0xF4000004, 32u>;
// Clear output register (PCOR).
using pcor = Register<0xF4000008, 32u>;
// Toggle output register (PTOR).
using ptor = Register<0xF400000C, 32u>;
// Data input register.
using pdir = Register<0xF4000010, 32u>
// Data direction output register.
using pddr = Register<0xF4000014, 32u>
}
```
For the purpose of this example we further assume that we are only interested in two pins of the GPIO: we have a red LED on pin 14 and a blue LED on pin 16. We can now use the `Register` types defined above to map these pins to specific `Field`. Because the logic is independent of the pin number we can even define a generic LED interface:
```c++
// LEDs namespace.
namespace leds {
// LED interface template.
template <std::uint8_t Pin>
struct LED_Interface {
// Define the relevant fields.
// Some of these fields are write only (e.g., PSOR) but we define them
// as read write (it will always read zero but we will not read them).
using pin_direction = Field<gpio::pddr, 1u, Pin, AccessPolicy::rw>;
using pin_set = Field<gpio::psor, 1u, Pin, AccessPolicy::rw>;
using pin_clear = Field<gpio::pcor, 1u, Pin, AccessPolicy::rw>;
using pin_toggle = Field<gpio::ptor, 1u, Pin, AccessPolicy::rw>;
// We also define some constants.
constexpr static const gpio::pddr::type pin_output_dir = 1u;
// We can now define the static methods of the interface.
// The pin output direction is set in the init method.
inline static void on() {
pin_set::set(); // Set PSOR to 1.
};
inline static void off() {
pin_clear::set(); // Set PCOR to 1.
};
inline static void toggle() {
pin_toggle::set(); // Set PTOR to 1.
};
inline static void init() {
pin_direction::write(pin_output_dir);
off();
};
}
// Define the types for our two LEDs.
using red = LED_Interface<14>;
using blue = LED_Interface<16>;
}
```
At this point we have defined an interface to initialize and control the LEDs attached to two GPIO pins. Note that, at no moment we had to deal with masking or shifting operations. Furthermore, we only needed to deal with the register addresses when defining the related types. At compile time, `cppreg` also makes sure that the field actually fits within the register specifications (a `Field` type cannot overflow its `Register` type).
Using this interface it becomes easy to write very expressive code such as:
```c++
leds::red::init();
leds::blue::init();
// Turn on both LEDs.
leds::red::on();
leds::blue::on();
// Wait a bit.
for (std::size_t i = 0; i < 500000; ++i)
__NOP();
// Turn off the blue LED.
leds::blue::off();
```
A quick note: in this example some of the registers are write-only (set, clear and toggle); in genereal extra care has to be taken when dealing with write-only fields or registers but for this example the implementation still work fine due to the nature of the GPIO registers. Check the [API documentation](API.md) for more details.

46
README.md Normal file
View File

@ -0,0 +1,46 @@
# cppreg #
Copyright Sendyne Corp., 2010-2018. All rights reserved ([LICENSE](LICENSE)).
## Description ##
`cppreg`is a header-only C++11 library to facilitate the manipulation of MMIO registers (*i.e.*, memory-mapped I/O registers) in embedded devices. The idea is to make it possible to write expressive code and minimize the likelihood of ill-defined expressions when dealing with hardware registers on a MCU. The current features are:
* expressive syntax which shows the intent of the code when dealing with registers and fields,
* efficiency and performance on par with traditional C implementations (*e.g.*, CMSIS C code) when *at least some compiler optimizations* are enabled,
* field access policies (*e.g.*, read-only vs read-write) detect ill-defined access at compile-time,
* compile-time detection of overflow,
* easily extendable to support, for example, mock-up.
For a short introduction and how-to see the [quick start guide](QuickStart.md). A more complete and detailed documentation is available [here](API.md).
## Manifest ##
This project started when looking at this type of C code:
```c
// Now we enable the PLL as source for MCGCLKOUT.
MCG->C6 |= (1u << MCG_C6_PLLS_SHIFT);
// Wait for the MCG to use the PLL as source clock.
while ((MCG->S & MCG_S_PLLST_MASK) == 0)
__NOP();
```
This piece of code is part of the clock setup on a flavor of the K64F MCU. `MCG` is a peripheral and `MCG->C6` and `MCG->S` are registers containing some fields which are required to be set to specific values to properly clock the MCU. Some of the issues with such code are:
* the intent of the code is poorly expressed, and it requires at least the MCU data sheet or reference manual to be somewhat deciphered/understood,
* since the offsets and masks are known at compile time, it is error prone and somewhat tedious that the code has to manually implement shifting and masking operations,
* the code syntax itself is extremely error prone; for example, forget the `|` in `|=` and you are most likely going to spend some time debugging the code,
* there is no safety at all, that is, you might overflow the field, or you might try to write to a read-only field and no one will tell you (not the compiler, not the linker, and at runtime this could fail in various ways with little, if any, indication of where is the error coming from).
This does not have to be this way, and C++11 brings a lot of features and concepts that make it possible to achieve the same goal while clearly expressing the intent and being aware of any ill-formed instructions. Some will argue this will come at the cost of a massive performance hit, but this is actually not always the case (and more often than not, a C++ implementation can be very efficient; see [Ken Smith paper] and the example below).
This project has been inspired by the following previous works:
* [Ken Smith] work,
* [CMSIS SVD],
* this [blog post](http://blog.salkinium.com/typesafe-register-access-in-c++/).
[Ken Smith]: https://github.com/kensmith/cppmmio
[CMSIS SVD]: https://github.com/posborne/cmsis-svd

19
cppreg.h Normal file
View File

@ -0,0 +1,19 @@
//! cppreg library header.
/**
* @file cppreg.h
* @author Nicolas Clauvelin (nclauvelin@sendyne.com)
* @copyright Copyright 2010-2018 Sendyne Corp. All rights reserved.
*/
#ifndef CPPREG_CPPREG_H
#define CPPREG_CPPREG_H
#include "Register.h"
#include "Field.h"
#include "MergeWrite.h"
#include "Overflow.h"
#endif // CPPREG_CPPREG_H

53
cppreg_Defines.h Normal file
View File

@ -0,0 +1,53 @@
//! Project-wide definitions header.
/**
* @file cppreg_Defines.h
* @author Nicolas Clauvelin (nclauvelin@sendyne.com)
* @copyright Copyright 2010-2018 Sendyne Corp. All rights reserved.
*
* This header mostly defines data types used across the project. These data
* types have been defined with 32-bits address space hardware in mind. It
* can easily be extended to larger types if needed.
*/
#ifndef CPPREG_CPPREG_DEFINES_H
#define CPPREG_CPPREG_DEFINES_H
#include "cppreg_Includes.h"
//! cppreg namespace.
namespace cppreg {
//! Type alias for register and field addresses.
/**
* By design this type ensures that any address can be stored.
*/
using Address_t = std::uintptr_t;
//! Type alias for register and field widths.
/**
* This limit the implementation to a maximum size of 256 bits for any
* register or field.
* This corresponds to the number of bits in the a register or field.
*/
using Width_t = std::uint8_t;
//! Type alias for register and field offsets.
/**
* This limit the implementation to a maximum offset of 256 bits for any
* register or field.
* Given that we consider 32 bits address space and 32 bits register this
* should be enough.
*/
using Offset_t = std::uint8_t;
}
#endif // CPPREG_CPPREG_DEFINES_H

16
cppreg_Includes.h Normal file
View File

@ -0,0 +1,16 @@
//! Project-wide includes header.
/**
* @file cppreg_Defines.h
* @author Nicolas Clauvelin (nclauvelin@sendyne.com)
* @copyright Copyright 2010-2018 Sendyne Corp. All rights reserved.
*/
#ifndef CPPREG_CPPREG_INCLUDES_H
#define CPPREG_CPPREG_INCLUDES_H
#include <cstdint>
#endif // CPPREG_CPPREG_INCLUDES_H

128
policies/AccessPolicy.h Normal file
View File

@ -0,0 +1,128 @@
//! Access policy abstract implementation.
/**
* @file AccessPolicy.h
* @author Nicolas Clauvelin (nclauvelin@sendyne.com)
* @copyright Copyright 2010-2018 Sendyne Corp. All rights reserved.
*
* Access policies are used to describe if register fields are read-write,
* read-only or write-only.
*/
#ifndef CPPREG_ACCESSPOLICY_H
#define CPPREG_ACCESSPOLICY_H
#include "cppreg_Defines.h"
//! cppreg namespace.
namespace cppreg {
//! Read-only access policy.
struct read_only {
//! Read access implementation.
/**
* @tparam T Field data type.
* @param mmio_device Pointer to register mapped memory.
* @param mask Field mask.
* @param offset Field offset.
* @return The value at the field location.
*/
template <typename T>
inline static T read(const T* const mmio_device,
const T mask,
const Offset_t offset) noexcept {
return static_cast<T>((*mmio_device & mask) >> offset);
};
};
//! Read-write access policy.
struct read_write : read_only {
//! Write access implementation.
/**
* @tparam T Field data type.
* @param mmio_device Pointer to register mapped memory.
* @param mask Field mask.
* @param offset Field offset.
* @param value Value to be written at the field location.
*/
template <typename T>
inline static void write(T* const mmio_device,
const T mask,
const Offset_t offset,
const T value) noexcept {
*mmio_device = static_cast<T>((*mmio_device & ~mask) |
((value << offset) & mask));
};
//! Set field implementation.
/**
* @tparam T Field data type.
* @param mmio_device Pointer to register mapped memory.
* @param mask Field mask.
*/
template <typename T>
inline static void set(T* const mmio_device, const T mask) noexcept {
*mmio_device = static_cast<T>((*mmio_device) | mask);
};
//! Clear field implementation.
/**
* @tparam T Field data type.
* @param mmio_device Pointer to register mapped memory.
* @param mask Field mask.
*/
template <typename T>
inline static void clear(T* const mmio_device, const T mask) noexcept {
*mmio_device = static_cast<T>((*mmio_device) & ~mask);
};
//! Toggle field implementation.
/**
* @tparam T Field data type.
* @param mmio_device Pointer to register mapped memory.
* @param mask Field mask.
*/
template <typename T>
inline static void toggle(T* const mmio_device, const T mask) noexcept {
*mmio_device ^= mask;
};
};
//! Write-only access policy.
struct write_only {
//! Write access implementation.
/**
* @tparam T Field data type.
* @param mmio_device Pointer to register mapped memory.
* @param mask Field mask.
* @param offset Field offset
* @param value Value to be written at the field location.
*/
template <typename T>
inline static void write(T* const mmio_device,
const T mask,
const Offset_t offset,
const T value) noexcept {
// We cannot read the current value so we simply fully write to it.
*mmio_device = ((value << offset) & mask);
};
};
}
#endif // CPPREG_ACCESSPOLICY_H

270
register/Field.h Normal file
View File

@ -0,0 +1,270 @@
//! Register field type implementation.
/**
* @file Field.h
* @author Nicolas Clauvelin (nclauvelin@sendyne.com)
* @copyright Copyright 2010-2018 Sendyne Corp. All rights reserved.
*
* This header provides the definitions related to register field
* implementation.
*/
#ifndef CPPREG_REGISTERFIELD_H
#define CPPREG_REGISTERFIELD_H
#include "Register.h"
#include "Mask.h"
#include "AccessPolicy.h"
#include "ShadowValue.h"
//! cppreg namespace.
namespace cppreg {
//! Register field implementation.
/**
* @tparam BaseRegister Parent register.
* @tparam width Field width.
* @tparam offset Field offset.
* @tparam P Access policy type (rw, ro, wo).
*
* This data structure provides static methods to deal with field access
* (read, write, set, clear, and toggle). These methods availability depends
* on the access policy (e.g., a read-only field does not implement a
* write method).
* In practice, fields are implemented by deriving from this class to
* create custom types.
*/
template <
typename BaseRegister,
Width_t FieldWidth,
Offset_t FieldOffset,
typename AccessPolicy
>
struct Field {
//! Parent register for the field.
using parent_register = BaseRegister;
//! Field data type derived from register data type.
using type = typename parent_register::type;
//! MMIO type.
using MMIO_t = typename parent_register::MMIO_t;
//! Field width.
constexpr static const Width_t width = FieldWidth;
//! Field offset.
constexpr static const Offset_t offset = FieldOffset;
//! Field policy.
using policy = AccessPolicy;
//! Field mask.
/**
* The field mask is computed at compile time.
*/
constexpr static const type mask = make_shifted_mask<type>(width,
offset);
//! Customized overflow check implementation for Field types.
/**
* @tparam value Value to be checked for overflow.
* @return `true` if no overflow, `false` otherwise.
*/
template <type value>
struct check_overflow {
constexpr static const bool result =
internals::check_overflow<
parent_register::size,
value,
(mask >> offset)
>::result::value;
};
//! Field read method.
/**
* @return Field value.
*/
inline static type read() noexcept {
return
AccessPolicy
::template read<MMIO_t>(parent_register::ro_mem_pointer(),
mask,
offset);
};
//! Field write method (shadow value disabled).
/**
* @param value Value to be written to the field.
*
* We use SFINAE to discriminate for registers with shadow value
* enabled.
*
* This method does not perform any check on the input value. If the
* input value is too large for the field size it will not overflow
* but only the part that fits in the field will be written.
* For safe write see
*/
template <typename T = type>
inline static void
write(const typename std::enable_if<
!parent_register::shadow::use_shadow, T
>::type value) noexcept {
AccessPolicy
::template write<MMIO_t>(parent_register::rw_mem_pointer(),
mask,
offset,
value);
};
//! Field write method (shadow value enabled).
/**
* @param value Value to be written to the field.
*
* We use SFINAE to discriminate for registers with shadow value
* enabled.
*
* This method does not perform any check on the input value. If the
* input value is too large for the field size it will not overflow
* but only the part that fits in the field will be written.
* For safe write see
*/
template <typename T = type>
inline static void
write(const typename std::enable_if<
parent_register::shadow::use_shadow, T
>::type value) noexcept {
// Update shadow value.
// Fetch the whole register content.
// This assumes that reading a write-only fields return some value.
parent_register::shadow::value =
(parent_register::shadow::value & ~mask) |
((value << offset) & mask);
// Write as a block to the register, that is, we do not use the
// mask and offset.
AccessPolicy
::template write<MMIO_t>(parent_register::rw_mem_pointer(),
~(0u),
0u,
parent_register::shadow::value);
};
//! Field write method with compile-time check (shadow value disabled).
/**
* @tparam value Value to be written to the field
*
* We use SFINAE to discriminate for registers with shadow value
* enabled.
*
* This method performs a compile-time check to avoid overflowing the
* field.
*/
template <type value, typename T = void>
inline static
typename std::enable_if<
(!parent_register::shadow::use_shadow)
&&
check_overflow<value>::result,
T
>::type
write() noexcept {
write(value);
};
//! Field write method with compile-time check (shadow value enabled).
/**
* @tparam value Value to be written to the field
*
* We use SFINAE to discriminate for registers with shadow value
* enabled.
*
* This method performs a compile-time check to avoid overflowing the
* field.
*/
template <type value, typename T = void>
inline static
typename std::enable_if<
parent_register::shadow::use_shadow
&&
check_overflow<value>::result,
T
>::type
write() noexcept {
write(value);
};
//! Field set method.
/**
* This method will set all bits in the field.
*/
inline static void set() noexcept {
AccessPolicy
::template set<MMIO_t>(parent_register::rw_mem_pointer(), mask);
};
//! Field clear method.
/**
* This method will clear all bits in the field.
*/
inline static void clear() noexcept {
AccessPolicy
::template clear<MMIO_t>(parent_register::rw_mem_pointer(), mask);
};
//! Field toggle method.
/**
* This method will toggle all bits in the field.
*/
inline static void toggle() noexcept {
AccessPolicy
::template toggle<MMIO_t>(parent_register::rw_mem_pointer(), mask);
};
//! Is field set bool method.
/**
* @return `true` if the 1-bit field is set to 1, `false` otherwise.
*
* This is only available if the field is 1 bit wide.
*/
template <typename T = bool>
inline static typename std::enable_if<FieldWidth == 1, T>::type
is_set() noexcept {
return (Field::read() == 1u);
};
//! Is field clear bool method.
/**
* @return `true` if the 1-bit field is set to 0, `false` otherwise.
*
* This is only available if the field is 1 bit wide.
*/
template <typename T = bool>
inline static typename std::enable_if<FieldWidth == 1, T>::type
is_clear() noexcept {
return (Field::read() == 0u);
};
// Consistency checking.
// The width of the field cannot exceed the register size and the
// width added to the offset cannot exceed the register size.
static_assert(parent_register::size >= width,
"field width is larger than parent register size");
static_assert(parent_register::size >= width + offset,
"offset + width is larger than parent register size");
static_assert(FieldWidth != 0u,
"defining a Field type of width 0u is not allowed");
};
}
#endif // CPPREG_REGISTERFIELD_H

58
register/Mask.h Normal file
View File

@ -0,0 +1,58 @@
//! Bit mask implementation.
/**
* @file Mask.h
* @author Nicolas Clauvelin (nclauvelin@sendyne.com)
* @copyright Copyright 2010-2018 Sendyne Corp. All rights reserved.
*
* The implementation is designed to compute masks prior to runtime by
* relying on constexpr function. This will work as intended if the function
* argument is known at compile time, otherwise it will be evaluated at runtime.
*/
#ifndef CPPREG_MASK_H
#define CPPREG_MASK_H
#include "cppreg_Defines.h"
//! cppreg namespace.
namespace cppreg {
//! Mask constexpr function.
/**
* @tparam Mask_t Mask data type (will be derived from register).
* @param width Mask width.
* @return The mask value.
*/
template <typename Mask_t>
constexpr Mask_t make_mask(const Width_t width) noexcept {
return width == 0 ?
0u
:
static_cast<Mask_t>(
(make_mask<Mask_t>(Width_t(width - 1)) << 1) | 1
);
};
//! Shifted mask constexpr function.
/**
* @tparam Mask_t Mask data type (will be derived from register).
* @param width Mask width.
* @param offset Mask offset.
* @return The mask value.
*/
template <typename Mask_t>
constexpr Mask_t make_shifted_mask(const Width_t width,
const Offset_t offset) noexcept {
return static_cast<Mask_t>(make_mask<Mask_t>(width) << offset);
};
}
#endif // CPPREG_MASK_H

161
register/MergeWrite.h Normal file
View File

@ -0,0 +1,161 @@
//! Merge write implementation.
/**
* @file MergeWrite.h
* @author Nicolas Clauvelin (nclauvelin@sendyne.com)
* @copyright Copyright 2010-2018 Sendyne Corp. All rights reserved.
*
* The "merge write" implementation is designed to make it possible to merge
* write operations on different fields into a single one.
*/
#ifndef CPPREG_MERGEWRITE_H
#define CPPREG_MERGEWRITE_H
#include "Overflow.h"
#include "AccessPolicy.h"
#include <functional>
//! cppreg namespace.
namespace cppreg {
//! Write operation holding structure.
/**
* @tparam Register Underlying register for the final write operation.
*/
template <typename Register>
class MergeWrite {
public:
//! Type alias to register base type.
using base_type = typename Register::type;
//! Static instantiation method.
static MergeWrite create_instance(const base_type value,
const base_type mask) noexcept {
MergeWrite mw;
mw._accumulated_value = value;
mw._combined_mask = mask;
return mw;
};
//!@ Move constructor.
MergeWrite(MergeWrite&& mw) noexcept
: _accumulated_value(mw._accumulated_value),
_combined_mask(mw._combined_mask) {
};
//!@{ Non-copyable.
MergeWrite(const MergeWrite&) = delete;
MergeWrite& operator=(const MergeWrite&) = delete;
//!@}
//! Destructor.
/**
* This is where the write operation is performed.
*/
~MergeWrite() {
// Get memory pointer.
typename Register::MMIO_t* const mmio_device =
Register::rw_mem_pointer();
// Write to the whole register using the current accumulated value
// and combined mask.
// No offset needed because we write to the whole register.
*mmio_device = static_cast<base_type>(
(*mmio_device & ~_combined_mask) |
((_accumulated_value) & _combined_mask)
);
};
//! With method.
/**
* @tparam F Field type describing where to write in the register.
* @param value Value to write to the register.
* @return A reference to the current merge write data.
*
* This method is used to add another operation to the final merged
* write.
*/
template <typename F>
MergeWrite&& with(const base_type value) && noexcept {
// Check that the field belongs to the register.
static_assert(std::is_same<
typename F::parent_register,
Register
>::value,
"field is not from the same register in merge_write");
// Update accumulated value.
F::policy::write(&_accumulated_value,
F::mask,
F::offset,
value);
// Update combine mask.
_combined_mask = _combined_mask | F::mask;
return std::move(*this);
};
//! With method with compile-time check.
/**
* @tparam F Field type describing where to write in the register.
* @param value Value to write to the register.
* @return A reference to the current merge write data.
*
* This method is used to add another operation to the final merged
* write.
*
* This method performs a compile-time check to avoid overflowing the
* field.
*/
template <
typename F,
base_type value,
typename T = MergeWrite
>
typename std::enable_if<
(internals::check_overflow<
Register::size, value, (F::mask >> F::offset)
>::result::value),
T
>::type&&
with() && noexcept {
return std::move(*this).template with<F>(value);
};
private:
// Disabled for shadow value register.
static_assert(!Register::shadow::use_shadow,
"merge write is not available for shadow value register");
// Private default constructor.
MergeWrite() : _accumulated_value(0u),
_combined_mask(0u) {};
// Accumulated value.
base_type _accumulated_value;
// Combined mask.
base_type _combined_mask;
};
}
#endif // CPPREG_MERGEWRITE_H

47
register/Overflow.h Normal file
View File

@ -0,0 +1,47 @@
//! Overflow check implementation.
/**
* @file Overflow.h
* @author Nicolas Clauvelin (nclauvelin@sendyne.com)
* @copyright Copyright 2010-2018 Sendyne Corp. All rights reserved.
*/
#ifndef CPPREG_OVERFLOW_H
#define CPPREG_OVERFLOW_H
#include "Traits.h"
#include <type_traits>
//! cppreg::internals namespace.
namespace cppreg {
namespace internals {
//! Overflow check implementation.
/**
* @tparam W Width of the register or field type.
* @tparam value Value to check.
* @tparam limit Overflow limit value.
*
* This structure defines a type result set to std::true_type if there is
* no overflow and set to std::false_type if there is overflow.
* There is overflow if value if strictly larger than limit.
*/
template <
Width_t W,
typename RegisterType<W>::type value,
typename RegisterType<W>::type limit
>
struct check_overflow {
using result =
typename std::integral_constant<bool, value <= limit>::type;
};
}
}
#endif // CPPREG_OVERFLOW_H

130
register/Register.h Normal file
View File

@ -0,0 +1,130 @@
//! Register type implementation.
/**
* @file Register.h
* @author Nicolas Clauvelin (nclauvelin@sendyne.com)
* @copyright Copyright 2010-2018 Sendyne Corp. All rights reserved.
*
* This header provides the definitions related to register implementation.
*/
#ifndef CPPREG_REGISTER_H
#define CPPREG_REGISTER_H
#include "Traits.h"
#include "MergeWrite.h"
#include "ShadowValue.h"
//! cppreg namespace.
namespace cppreg {
//! Register data structure.
/**
* @tparam address Register address.
* @tparam width Register total width (i.e., size).
* @tparam reset Register reset value (0x0 if unknown).
* @tparam shadow Boolean flag to enable shadow value (enabled if `true`).
*
* This data structure will act as a container for fields and is
* therefore limited to a strict minimum. It only carries information
* about the register base address, size, and reset value.
* In practice, register are implemented by deriving from this class to
* create custom types.
*/
template <
Address_t RegAddress,
Width_t RegWidth,
typename RegisterType<RegWidth>::type ResetValue = 0x0,
bool UseShadow = false
>
struct Register {
//! Register base type.
using type = typename RegisterType<RegWidth>::type;
//! MMIO pointer type.
using MMIO_t = volatile type;
//! Register base address.
constexpr static const Address_t base_address = RegAddress;
//! Register total width.
constexpr static const Width_t size = RegWidth;
//! Register reset value.
constexpr static const type reset = ResetValue;
//! Boolean flag for shadow value management.
using shadow = Shadow<Register, UseShadow>;
//! Memory modifier.
/**
* @return A pointer to the writable register memory.
*/
static MMIO_t* rw_mem_pointer() {
return reinterpret_cast<MMIO_t* const>(base_address);
};
//! Memory accessor.
/**
* @return A pointer to the read-only register memory.
*/
static const MMIO_t* ro_mem_pointer() {
return reinterpret_cast<const MMIO_t* const>(base_address);
};
//! Merge write function.
/**
* @tparam F Field on which to perform the write operation.
* @param value Value to write to the field.
* @return A merge write data structure.
*/
template <typename F>
inline static MergeWrite<typename F::parent_register>
merge_write(const typename F::type value) noexcept {
return
MergeWrite<typename F::parent_register>
::create_instance(((value << F::offset) & F::mask), F::mask);
};
//! Merge write function.
/**
* @tparam F Field on which to perform the write operation.
* @param value Value to write to the field.
* @return A merge write data structure.
*/
template <
typename F,
type value,
typename T = MergeWrite<typename F::parent_register>
>
inline static
typename std::enable_if<
internals::check_overflow<
size, value, (F::mask >> F::offset)
>::result::value,
T
>::type
merge_write() noexcept {
return
MergeWrite<typename F::parent_register>
::create_instance(((value << F::offset) & F::mask), F::mask);
};
// Sanity check.
static_assert(RegWidth != 0u,
"defining a Register type of width 0u is not allowed");
};
}
#endif // CPPREG_REGISTER_H

52
register/ShadowValue.h Normal file
View File

@ -0,0 +1,52 @@
//! Simple shadow value implementation.
/**
* @file ShadowValue.h
* @author Nicolas Clauvelin (nclauvelin@sendyne.com)
* @copyright Copyright 2010-2018 Sendyne Corp. All rights reserved.
*/
#ifndef CPPREG_SHADOWVALUE_H
#define CPPREG_SHADOWVALUE_H
#include "cppreg_Defines.h"
//! cppreg namespace.
namespace cppreg {
//! Shadow value generic implementation.
/**
* @tparam Register Register type.
* @tparam UseShadow Boolean flag indicating if shadow value is required.
*
* This implementation is for register which do not require shadow value.
*/
template <typename Register, bool UseShadow>
struct Shadow {
constexpr static const bool use_shadow = false;
};
//! Shadow value implementation.
/**
* @tparam Register Register type.
*
* This implementation is for register which do require shadow value.
*/
template <typename Register>
struct Shadow<Register, true> {
static typename Register::type value;
constexpr static const bool use_shadow = true;
};
template <typename Register>
typename Register::type Shadow<Register, true>::value = Register::reset;
template <typename Register>
const bool Shadow<Register, true>::use_shadow;
}
#endif // CPPREG_SHADOWVALUE_H

41
register/Traits.h Normal file
View File

@ -0,0 +1,41 @@
//! Register traits implementation.
/**
* @file Traits.h
* @author Nicolas Clauvelin (nclauvelin@sendyne.com)
* @copyright Copyright 2010-2018 Sendyne Corp. All rights reserved.
*
* This header provides some traits for register type instantiation.
*/
#ifndef CPPREG_TRAITS_H
#define CPPREG_TRAITS_H
#include "cppreg_Defines.h"
//! cppreg namespace.
namespace cppreg {
//! Register data type default implementation.
/**
* @tparam Size Register size.
*
* This will fail to compile if the register size is not implemented.
*/
template <Width_t Size>
struct RegisterType;
//!@{ Specializations based on register size.
template <> struct RegisterType<8u> { using type = std::uint8_t; };
template <> struct RegisterType<16u> { using type = std::uint16_t; };
template <> struct RegisterType<32u> { using type = std::uint32_t; };
//!@}
}
#endif // CPPREG_TRAITS_H

373
single/cppreg-all.h Normal file
View File

@ -0,0 +1,373 @@
//! cppreg library.
/**
* @file cppreg-all.h
* @author Nicolas Clauvelin (nclauvelin@sendyne.com)
* @copyright Copyright 2010-2018 Sendyne Corp. All rights reserved.
*/
#include <cstdint>
#include <type_traits>
#include <functional>
// cppreg_Defines.h
#ifndef CPPREG_CPPREG_DEFINES_H
#define CPPREG_CPPREG_DEFINES_H
namespace cppreg {
using Address_t = std::uintptr_t;
using Width_t = std::uint8_t;
using Offset_t = std::uint8_t;
}
#endif
// AccessPolicy.h
#ifndef CPPREG_ACCESSPOLICY_H
#define CPPREG_ACCESSPOLICY_H
namespace cppreg {
struct read_only {
template <typename T>
inline static T read(const T* const mmio_device,
const T mask,
const Offset_t offset) noexcept {
return static_cast<T>((*mmio_device & mask) >> offset);
};
};
struct read_write : read_only {
template <typename T>
inline static void write(T* const mmio_device,
const T mask,
const Offset_t offset,
const T value) noexcept {
*mmio_device = static_cast<T>((*mmio_device & ~mask) |
((value << offset) & mask));
};
template <typename T>
inline static void set(T* const mmio_device, const T mask) noexcept {
*mmio_device = static_cast<T>((*mmio_device) | mask);
};
template <typename T>
inline static void clear(T* const mmio_device, const T mask) noexcept {
*mmio_device = static_cast<T>((*mmio_device) & ~mask);
};
template <typename T>
inline static void toggle(T* const mmio_device, const T mask) noexcept {
*mmio_device ^= mask;
};
};
struct write_only {
template <typename T>
inline static void write(T* const mmio_device,
const T mask,
const Offset_t offset,
const T value) noexcept {
*mmio_device = ((value << offset) & mask);
};
};
}
#endif
// Traits.h
#ifndef CPPREG_TRAITS_H
#define CPPREG_TRAITS_H
namespace cppreg {
template <Width_t Size>
struct RegisterType;
template <> struct RegisterType<8u> { using type = std::uint8_t; };
template <> struct RegisterType<16u> { using type = std::uint16_t; };
template <> struct RegisterType<32u> { using type = std::uint32_t; };
}
#endif
// Overflow.h
#ifndef CPPREG_OVERFLOW_H
#define CPPREG_OVERFLOW_H
namespace cppreg {
namespace internals {
template <
Width_t W,
typename RegisterType<W>::type value,
typename RegisterType<W>::type limit
>
struct check_overflow {
using result =
typename std::integral_constant<bool, value <= limit>::type;
};
}
}
#endif
// Mask.h
#ifndef CPPREG_MASK_H
#define CPPREG_MASK_H
namespace cppreg {
template <typename Mask_t>
constexpr Mask_t make_mask(const Width_t width) noexcept {
return width == 0 ?
0u
:
static_cast<Mask_t>(
(make_mask<Mask_t>(Width_t(width - 1)) << 1) | 1
);
};
template <typename Mask_t>
constexpr Mask_t make_shifted_mask(const Width_t width,
const Offset_t offset) noexcept {
return static_cast<Mask_t>(make_mask<Mask_t>(width) << offset);
};
}
#endif
// ShadowValue.h
#ifndef CPPREG_SHADOWVALUE_H
#define CPPREG_SHADOWVALUE_H
namespace cppreg {
template <typename Register, bool UseShadow>
struct Shadow {
constexpr static const bool use_shadow = false;
};
template <typename Register>
struct Shadow<Register, true> {
static typename Register::type value;
constexpr static const bool use_shadow = true;
};
template <typename Register>
typename Register::type Shadow<Register, true>::value = Register::reset;
template <typename Register>
const bool Shadow<Register, true>::use_shadow;
}
#endif
// MergeWrite.h
#ifndef CPPREG_MERGEWRITE_H
#define CPPREG_MERGEWRITE_H
namespace cppreg {
template <typename Register>
class MergeWrite {
public:
using base_type = typename Register::type;
static MergeWrite create_instance(const base_type value,
const base_type mask) noexcept {
MergeWrite mw;
mw._accumulated_value = value;
mw._combined_mask = mask;
return mw;
};
MergeWrite(MergeWrite&& mw) noexcept
: _accumulated_value(mw._accumulated_value),
_combined_mask(mw._combined_mask) {
};
MergeWrite(const MergeWrite&) = delete;
MergeWrite& operator=(const MergeWrite&) = delete;
~MergeWrite() {
typename Register::MMIO_t* const mmio_device =
Register::rw_mem_pointer();
*mmio_device = static_cast<base_type>(
(*mmio_device & ~_combined_mask) |
((_accumulated_value) & _combined_mask)
);
};
template <typename F>
MergeWrite&& with(const base_type value) && noexcept {
static_assert(std::is_same<
typename F::parent_register,
Register
>::value,
"field is not from the same register in merge_write");
F::policy::write(&_accumulated_value,
F::mask,
F::offset,
value);
_combined_mask = _combined_mask | F::mask;
return std::move(*this);
};
template <
typename F,
base_type value,
typename T = MergeWrite
>
typename std::enable_if<
(internals::check_overflow<
Register::size, value, (F::mask >> F::offset)
>::result::value),
T
>::type&&
with() && noexcept {
return std::move(*this).template with<F>(value);
};
private:
static_assert(!Register::shadow::use_shadow,
"merge write is not available for shadow value register");
MergeWrite() : _accumulated_value(0u),
_combined_mask(0u) {};
base_type _accumulated_value;
base_type _combined_mask;
};
}
#endif
// Register.h
#ifndef CPPREG_REGISTER_H
#define CPPREG_REGISTER_H
namespace cppreg {
template <
Address_t RegAddress,
Width_t RegWidth,
typename RegisterType<RegWidth>::type ResetValue = 0x0,
bool UseShadow = false
>
struct Register {
using type = typename RegisterType<RegWidth>::type;
using MMIO_t = volatile type;
constexpr static const Address_t base_address = RegAddress;
constexpr static const Width_t size = RegWidth;
constexpr static const type reset = ResetValue;
using shadow = Shadow<Register, UseShadow>;
static MMIO_t* rw_mem_pointer() {
return reinterpret_cast<MMIO_t* const>(base_address);
};
static const MMIO_t* ro_mem_pointer() {
return reinterpret_cast<const MMIO_t* const>(base_address);
};
template <typename F>
inline static MergeWrite<typename F::parent_register>
merge_write(const typename F::type value) noexcept {
return
MergeWrite<typename F::parent_register>
::create_instance(((value << F::offset) & F::mask), F::mask);
};
template <
typename F,
type value,
typename T = MergeWrite<typename F::parent_register>
>
inline static
typename std::enable_if<
internals::check_overflow<
size, value, (F::mask >> F::offset)
>::result::value,
T
>::type
merge_write() noexcept {
return
MergeWrite<typename F::parent_register>
::create_instance(((value << F::offset) & F::mask), F::mask);
};
static_assert(RegWidth != 0u,
"defining a Register type of width 0u is not allowed");
};
}
#endif
// Field.h
#ifndef CPPREG_REGISTERFIELD_H
#define CPPREG_REGISTERFIELD_H
namespace cppreg {
template <
typename BaseRegister,
Width_t FieldWidth,
Offset_t FieldOffset,
typename AccessPolicy
>
struct Field {
using parent_register = BaseRegister;
using type = typename parent_register::type;
using MMIO_t = typename parent_register::MMIO_t;
constexpr static const Width_t width = FieldWidth;
constexpr static const Offset_t offset = FieldOffset;
using policy = AccessPolicy;
constexpr static const type mask = make_shifted_mask<type>(width,
offset);
template <type value>
struct check_overflow {
constexpr static const bool result =
internals::check_overflow<
parent_register::size,
value,
(mask >> offset)
>::result::value;
};
inline static type read() noexcept {
return
AccessPolicy
::template read<MMIO_t>(parent_register::ro_mem_pointer(),
mask,
offset);
};
template <typename T = type>
inline static void
write(const typename std::enable_if<
!parent_register::shadow::use_shadow, T
>::type value) noexcept {
AccessPolicy
::template write<MMIO_t>(parent_register::rw_mem_pointer(),
mask,
offset,
value);
};
template <typename T = type>
inline static void
write(const typename std::enable_if<
parent_register::shadow::use_shadow, T
>::type value) noexcept {
parent_register::shadow::value =
(parent_register::shadow::value & ~mask) |
((value << offset) & mask);
AccessPolicy
::template write<MMIO_t>(parent_register::rw_mem_pointer(),
~(0u),
0u,
parent_register::shadow::value);
};
template <type value, typename T = void>
inline static
typename std::enable_if<
(!parent_register::shadow::use_shadow)
&&
check_overflow<value>::result,
T
>::type
write() noexcept {
write(value);
};
template <type value, typename T = void>
inline static
typename std::enable_if<
parent_register::shadow::use_shadow
&&
check_overflow<value>::result,
T
>::type
write() noexcept {
write(value);
};
inline static void set() noexcept {
AccessPolicy
::template set<MMIO_t>(parent_register::rw_mem_pointer(), mask);
};
inline static void clear() noexcept {
AccessPolicy
::template clear<MMIO_t>(parent_register::rw_mem_pointer(), mask);
};
inline static void toggle() noexcept {
AccessPolicy
::template toggle<MMIO_t>(parent_register::rw_mem_pointer(), mask);
};
template <typename T = bool>
inline static typename std::enable_if<FieldWidth == 1, T>::type
is_set() noexcept {
return (Field::read() == 1u);
};
template <typename T = bool>
inline static typename std::enable_if<FieldWidth == 1, T>::type
is_clear() noexcept {
return (Field::read() == 0u);
};
static_assert(parent_register::size >= width,
"field width is larger than parent register size");
static_assert(parent_register::size >= width + offset,
"offset + width is larger than parent register size");
static_assert(FieldWidth != 0u,
"defining a Field type of width 0u is not allowed");
};
}
#endif