mirror of
https://github.com/sendyne/cppreg.git
synced 2026-01-20 05:12:09 +00:00
Compare commits
42 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2e0d502674 | ||
|
|
e59bc356c8 | ||
|
|
e763c2bfa8 | ||
| 4b85835035 | |||
| cd4448b330 | |||
| 435e038315 | |||
| 5a2a351791 | |||
|
|
c48108be64 | ||
|
|
203443c1f8 | ||
|
|
819a004588 | ||
|
|
c6c2c5055a | ||
|
|
8d2cdbf25f | ||
|
|
5925c223c8 | ||
|
|
6085f47115 | ||
|
|
53b74b2b5c | ||
|
|
8cdc89336f | ||
|
|
23a5580d2b | ||
|
|
95288090d4 | ||
|
|
c217f34e0c | ||
|
|
1590e7c0e3 | ||
|
|
6a99ea5de6 | ||
|
|
7b9a843e2d | ||
|
|
8ce22c666d | ||
|
|
ee48bbd261 | ||
|
|
49d19f1b20 | ||
|
|
bbdf513156 | ||
|
|
ae6da9b7f5 | ||
|
|
984d4ceb58 | ||
|
|
327e230847 | ||
|
|
4d575c176f | ||
|
|
154b29bef3 | ||
|
|
e4ae5641ec | ||
|
|
f1d98d6bd5 | ||
|
|
058ccbf13d | ||
|
|
eb9226fe5d | ||
|
|
1c27f907a5 | ||
|
|
816c579d7d | ||
|
|
11734b0bf4 | ||
|
|
926eb71df2 | ||
|
|
93763cd6ee | ||
|
|
5cf8a851d8 | ||
|
|
a31d4a2875 |
377
API.md
377
API.md
@@ -5,105 +5,247 @@ 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. All the code examples assume this namespace is accessible (*i.e.*, `using namespace cppreg;` is implicit).
|
||||
|
||||
The entire implementation is encapsulated in the `cppreg::` namespace.
|
||||
|
||||
## Overview ##
|
||||
`cppreg` provides two template structures that can be customized:
|
||||
`cppreg` API makes it possible to:
|
||||
|
||||
* `Register`: used to define a MMIO register and its memory device,
|
||||
* `Field`: used to define a field in a MMIO register.
|
||||
* define a pack of registers: a register pack is simply a group of registers contiguous in memory (this is often the case when dealing with registers associated with a peripheral),
|
||||
* define single register at a specific memory address: this is provided as a fallback when the register pack implementation cannot be used,
|
||||
* define fields within registers (packed or not): a field corresponds to a group of bits within a register and comes with a specific access policy which control read and write access.
|
||||
|
||||
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.
|
||||
The API was designed such that `cppreg`-based code is safer and more expressive than traditional low-level code while providing the same level of performance.
|
||||
|
||||
As explained below, when using `cppreg`, registers and fields are defined as C++ types specializing pre-defined template types. This can be done by explicitly deriving from the specialized template type or by using the `using` keyword (both approaches are strictly equivalent). With the exception of the merged write mechanism discussed below, all methods provided by the `cppreg` types are static methods.
|
||||
|
||||
|
||||
## 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).
|
||||
* register sizes are represented by the enumeration type `RegBitSize`,
|
||||
* `FieldWidth_t` and `FieldOffset_t` are the data types to represent field sizes and offsets; both are equivalent to `std::uint8_t`.
|
||||
|
||||
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 size ###
|
||||
The `RegBitSize` enumeration type represents the register sizes supported in `cppreg` and the values are:
|
||||
|
||||
* `RegBitSize::b8` for 8-bit registers,
|
||||
* `RegBitSize::b16` for 16-bit registers,
|
||||
* `RegBitSize::b32` for 32-bit registers,
|
||||
* `RegBitSize::b64` for 64-bit registers.
|
||||
|
||||
The register size is used to define the C++ data type that represent the register content in [Traits.h](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`.
|
||||
## Register interface ##
|
||||
In `cppreg`, registers are represented as memory locations that contain fields, and do not provide any methods by themselves. There are two possible ways to define registers:
|
||||
|
||||
To implement a particular register the following information are required at compile time:
|
||||
* for registers which are part of a pack (*i.e.*, group) the `RegisterPack` and `PackedRegister` types should be used,
|
||||
* for standalone register, the `Register` type is available.
|
||||
|
||||
* 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).
|
||||
Most of the times registers are part of groups related to peripherals or specific functionalities within a MCU. It is therefore recommended to use the register pack implementation rather than the standalone one. This ensures that the assembly generated from `cppreg`-based code will be optimal. In other words, the difference between packed registers and standalone registers is only a matter of performance in the generated assembly: the packed register interface relies on mapping an array on the pack memory region, which provides to the compiler the ability to use offset between the various registers versus reloading their absolute addresses.
|
||||
|
||||
For example, consider a 32-bits register `PeripheralRegister` mapped at `0x40004242`. The `Register` type can be derived from to create a `PeripheralRegister` C++ type:
|
||||
Moreover, the same level of functionality is provided by both implementations (`RegisterPack` is simply deriving from `Register` and redefining accessor and modifier methods). That is, a packed register type can be replaced by a standalone register type (and *vice versa*).
|
||||
|
||||
### Register pack interface ###
|
||||
To define a pack of registers:
|
||||
|
||||
1. define a `RegisterPack` type with the base address of the pack and the number of bytes,
|
||||
2. define `PackedRegister` types for all the registers in the pack.
|
||||
|
||||
The interface is (see [RegisterPack.h](register/RegisterPack.h)):
|
||||
|
||||
* `struct RegisterPack<pack_base_address, pack_size_in_bytes>`:
|
||||
|
||||
| parameter | description |
|
||||
|:---------------------|:-------------------------------------------|
|
||||
| `pack_base_address` | starting address of the pack memory region |
|
||||
| `pack_size_in_bytes` | size in bytes of the pack memory region |
|
||||
|
||||
* `struct PackedRegister<pack_type, RegBitSize_value, offset_in_bits, reset_value, use_shadow_value>`:
|
||||
|
||||
| parameter | description |
|
||||
|:---------------------|:-------------------------------------------|
|
||||
| `pack_type` | starting address of the pack memory region |
|
||||
| `RegBitSize_value` | size in bytes of the pack memory region |
|
||||
| `offset_in_bits` | offset in bits wrt pack base address |
|
||||
| `reset_value` | register reset value (defaulted to zero) |
|
||||
| `use_shadow_value` | enable shadow value if `true` (see below) |
|
||||
|
||||
Note that, the reset value is only useful when a shadow value is used.
|
||||
|
||||
The following example defines a 4 bytes register pack starting at address 0xA4000000 and containing: two 8-bit register and a 16-bit register. The `cppreg` implementation is:
|
||||
|
||||
```c++
|
||||
// Register is defined as a struct so public inheritance is the default.
|
||||
struct PeripheralRegister : Register<0x40004242, 32u> {};
|
||||
```
|
||||
struct SomePeripheral {
|
||||
|
||||
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):
|
||||
// Define a register pack:
|
||||
// - starting at address 0xA4000000,
|
||||
// - with a size of 4 bytes.
|
||||
using SomePack = RegisterPack<0xA4000000, 4>;
|
||||
|
||||
// Strictly equivalent formlation:
|
||||
// struct SomePack : RegisterPack<0xA4000000, 4> {};
|
||||
|
||||
```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>;
|
||||
// Define the first 8-bit register:
|
||||
using FirstRegister = PackRegister<SomePack, RegBitSize::b8, 8 * 0>;
|
||||
|
||||
// Define the second 8-bit register:
|
||||
// (the last template parameter indicates the offset in bits)
|
||||
using SecondRegister = PackRegister<SomePack, RegBitSize::b8, 8 * 1>;
|
||||
|
||||
// Define the 16-bit register:
|
||||
// (the last template parameter indicates the offset in bits)
|
||||
using ThirdRegister = PackRegister<SomePack, RegBitSize::b16, 8 * 2>;
|
||||
|
||||
// Strictly equivalent formulation:
|
||||
// struct FirstRegister : PackRegister<SomePack, RegBitSize::b8, 0> {};
|
||||
// ...
|
||||
|
||||
}
|
||||
|
||||
// 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:
|
||||
There are a few requirements for when defining packed registers:
|
||||
|
||||
* for a register of size N bits, the pack base address has to be aligned on a N bits boundary,
|
||||
* for a register of size N bits, the pack base address plus the offset has to be aligned on a N bits boundary,
|
||||
* the offset plus the size of register should be less or equal to the size of the pack.
|
||||
|
||||
These requirements are enforced at compile time and errors will be generated if there are not met.
|
||||
|
||||
### Standalone register interface ###
|
||||
The interface for standalone register is (see [Register.h](register/Register.h)):
|
||||
|
||||
`struct Register<register_address, RegBitSize_value, reset_value, use_shadow_value>`:
|
||||
|
||||
| parameter | description |
|
||||
|:---------------------|:-------------------------------------------|
|
||||
| `register_address` | register absolute address |
|
||||
| `RegBitSize_value` | size in bytes of the pack memory region |
|
||||
| `reset_value` | register reset value (defaulted to zero) |
|
||||
| `use_shadow_value` | enable shadow value if `true` (see below) |
|
||||
|
||||
Note that, the reset value is only useful when a shadow value is used.
|
||||
|
||||
For example, consider a 32-bit register `SomeRegister` mapped at `0x40004242`. The `Register` type is created using:
|
||||
|
||||
```c++
|
||||
PeripheralRegister::Frequency::clear();
|
||||
PeripheralRegister::Frequency::write(0x10u);
|
||||
using SomeRegister = Register<0x40004242, RegBitSize::b32>;
|
||||
|
||||
// Strictly equivalent formulation:
|
||||
// struct PeripheralRegister : Register<0x40004242, RegBitSize::b32> {};
|
||||
```
|
||||
|
||||
to clear the `Frequency` register and then write `0x10` to it.
|
||||
Similarly to the packed register interface, for a N bits register the address is required to be aligned on a N bits boundary. This requirement is enforced at compile time and an error will be generated if not met.
|
||||
|
||||
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.
|
||||
### Register pack goodies ###
|
||||
`cppreg` provides a few additional goodies to simplify register packs usage. Let's consider the following peripheral with four registers, each containing a single read/write `Data`field:
|
||||
|
||||
```c++
|
||||
struct Peripheral {
|
||||
|
||||
// Define a register pack:
|
||||
// - starting at address 0xA4000000,
|
||||
// - with a size of 4 bytes.
|
||||
using Pack = RegisterPack<0xA4000000, 4>;
|
||||
|
||||
// Registers and fields:
|
||||
struct Channel0 : PackedRegister<Pack, RegBistSize::b8, 8 * 0> {
|
||||
using Data = Field<Channel0, 8u, 0u, read_write>;
|
||||
};
|
||||
struct Channel1 : PackedRegister<Pack, RegBistSize::b8, 8 * 1> {
|
||||
using Data = Field<Channel0, 8u, 0u, read_write>;
|
||||
};
|
||||
struct Channel2 : PackedRegister<Pack, RegBistSize::b8, 8 * 2> {
|
||||
using Data = Field<Channel0, 8u, 0u, read_write>;
|
||||
};
|
||||
struct Channel3 : PackedRegister<Pack, RegBistSize::b8, 8 * 3> {
|
||||
using Data = Field<Channel0, 8u, 0u, read_write>;
|
||||
};
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
`PackIndexing` can be used to easily access field in packed registers using indexes:
|
||||
|
||||
```c++
|
||||
// Map indexes.
|
||||
using Channels = PackIndexing<
|
||||
Peripheral::Channel0::Data,
|
||||
Peripheral::Channel1::Data,
|
||||
Peripheral::Channel2::Data,
|
||||
Peripheral::Channel3::Data
|
||||
>;
|
||||
|
||||
// Read some data.
|
||||
const auto x0 = Channels::elem<0>::read();
|
||||
const auto x1 = Channels::elem<1>::read();
|
||||
const auto x2 = Channels::elem<2>::read();
|
||||
const auto x3 = Channels::elem<3>::read();
|
||||
```
|
||||
|
||||
`cppreg` also provides a template-based for loop implementation to simplify such iterations:
|
||||
|
||||
```c++
|
||||
// Map indexes.
|
||||
using Channels = PackIndexing<
|
||||
Peripheral::Channel0::Data,
|
||||
Peripheral::Channel1::Data,
|
||||
Peripheral::Channel2::Data,
|
||||
Peripheral::Channel3::Data
|
||||
>;
|
||||
|
||||
// Define a functor structure to collect data.
|
||||
static std::array<std::uint8_t, Channels::n_elems> some_buffer = {};
|
||||
struct ChannelsCollector {
|
||||
template <std::size_t index>
|
||||
void operator()() {
|
||||
some_buffer[index] = Channels::elem<index>::read();
|
||||
};
|
||||
};
|
||||
|
||||
// Iterate over the pack.
|
||||
pack_loop<Channels>::apply<ChannelsCollector>();
|
||||
```
|
||||
|
||||
If C++14 is used, another version of the loop is also available that makes it possible to use polymorphic lambdas:
|
||||
|
||||
```c++
|
||||
// Map indexes.
|
||||
using Channels = PackIndexing<
|
||||
Peripheral::Channel0::Data,
|
||||
Peripheral::Channel1::Data,
|
||||
Peripheral::Channel2::Data,
|
||||
Peripheral::Channel3::Data
|
||||
>;
|
||||
|
||||
// Define a buffer to collect data.
|
||||
static std::array<std::uint8_t, Channels::n_elems> some_buffer = {};
|
||||
|
||||
// Iterate over the pack and use a lambda.
|
||||
// Note the "auto index" ... this is required because the loop will
|
||||
// use std::integral_constant to pass the index while iterating.
|
||||
pack_loop<Channels>::apply<ChannelsCollector>([](auto index) {
|
||||
some_buffer[index] = Channels::elem<index>::read();
|
||||
Channels::elem<index>::template write<index>();
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
## Field interface ##
|
||||
The `Field` template 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. The interface is:
|
||||
|
||||
`struct Field<register, width_in_bits, offset_in_bits, access_policy>`:
|
||||
|
||||
| parameter | description |
|
||||
|:---------------------|:-------------------------------------------|
|
||||
| `register` | register type owning the field |
|
||||
| `width_in_bits` | width in bits (*i.e.*, size) |
|
||||
| `offset_in_bits` | offset in bits wrt to register address |
|
||||
| `access_policy` | access policy type (see below) |
|
||||
|
||||
Compile-time errors will be generated if the field width and offset are not consistent with the parent register size.
|
||||
|
||||
### Access policy ###
|
||||
The last template parameter of a `Field`-based type describes the access policy of the field. Three access policies are available:
|
||||
@@ -114,78 +256,88 @@ The last template parameter of a `Field`-based type describes the access policy
|
||||
|
||||
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 |
|
||||
| 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 |
|
||||
| `is_set()` | YES | NO | NO | `true` is all bits set to 1 |
|
||||
| `is_clear()` | YES | NO | NO | `true` is all bits set to 0 |
|
||||
|
||||
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:
|
||||
### Example ###
|
||||
Consider a 32-bit register located at 0x40004242 containing (among other things): a R/W FREQ field over bits [12:17], a WO MODE field over bits [18:21], and a RO STATE field one over bits [28:31]. The `cppreg` implementation is:
|
||||
|
||||
```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>;
|
||||
struct SomeRegister : Register<0x40004242, RegBitSize::b32> {
|
||||
using Frequency = Field<SomeRegister, 6u, 12u, read_write>;
|
||||
using Mode = Field<SomeRegister, 4u, 18u, write_only>;
|
||||
using State = Field<SomeRegister, 4u, 28u, read_only>;
|
||||
|
||||
// Strictly equivalent formulation:
|
||||
// struct Frequency : Field<SomeRegister, 6u, 12u, read_write> {};
|
||||
// ...
|
||||
};
|
||||
|
||||
// This would compile:
|
||||
PeripheralRegister::Frequency::write(0x10);
|
||||
const auto freq = PeripheralRegister::Frequency::read();
|
||||
const auto state = PeripheralRegister::State::read();
|
||||
SomeRegister::Frequency::write<0x10>();
|
||||
const auto freq = SomeRegister::Frequency::read();
|
||||
const auto state = SomeRegister::State::read();
|
||||
|
||||
// This would not compile:
|
||||
PeripheralRegister::State::write(0x1);
|
||||
const auto mode = PeripheralRegister::Mode::read();
|
||||
// This would not compile (State is read-only):
|
||||
SomeRegister::State::write<0x1>();
|
||||
const auto mode = SomeRegister::Mode::read();
|
||||
|
||||
// This would compile ...
|
||||
// But read the section dedicated to write-only fields.
|
||||
PeripheralRegister::Mode::write(0xA);
|
||||
// But read the section dedicated to write-only fields !!!
|
||||
SomeRegister::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:
|
||||
### Constant value and overflow check ###
|
||||
When performing write operations for any `Field`-based type, `cppreg` distinguishes between constant values (known at compile time) and non-constant values:
|
||||
|
||||
* `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).
|
||||
```c++
|
||||
SomeField::write<0xAB>(); // Template version for constant value write.
|
||||
SomeField::write(0xAB); // Function argument version.
|
||||
```
|
||||
|
||||
### 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.
|
||||
The advantages of using the constant value version are:
|
||||
|
||||
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:
|
||||
* `cppreg` will most of the time use a faster implementation for the write operation (this is particularly true if the field spans an entire register),
|
||||
* a compile-time error will occur if the value overflow the field.
|
||||
|
||||
Note that, even when using the non-constant value version overflow will not occur: only the bits part of the `Field`-type will be written and any data that does not fit the region of the memory assigned to the `Field`-type will not be modified. For example:
|
||||
|
||||
```c++
|
||||
// Register definition with nested fields definitions.
|
||||
struct PeripheralRegister : Register<0x40004242, 32u> {
|
||||
using Frequency = Field<PeripheralRegister, 8u, 12u, read_write>;
|
||||
struct SomeRegister : SomeRegister <0x40004242, RegBitSize::b32> {
|
||||
using Frequency = Field<SomeRegister, 8u, 12u, read_write>;
|
||||
};
|
||||
|
||||
// These two calls are strictly equivalent:
|
||||
PeripheralRegister::Frequency::write(0xAB);
|
||||
PeripheralRegister::Frequency::write<0xAB>();
|
||||
SomeRegister::Frequency::write(0xAB);
|
||||
SomeRegister::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.
|
||||
// But this will only write 0x11 to the memory device.
|
||||
SomeRegister::Frequency::write(0x111);
|
||||
|
||||
// This call does perform a compile-time check for overflow and will not compile:
|
||||
PeripheralRegister::Frequency::write<0x111>();
|
||||
SomeRegister::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.
|
||||
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> {
|
||||
struct Reg : Register <0x00000001, RegBitSize::b8> {
|
||||
using f1 = Field<Reg, 1u, 0u, read_write>;
|
||||
using f2 = Field<Reg, 1u, 1u, write_only>; // Always reads as zero.
|
||||
}
|
||||
@@ -194,9 +346,9 @@ struct Reg : Register <0x00000001, 8u> {
|
||||
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.
|
||||
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:
|
||||
@@ -204,12 +356,14 @@ 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.
|
||||
|
||||
On the other hand, if we were considering an example where a single write-only field extend over an entire register there will be no issue.
|
||||
|
||||
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
|
||||
RegBitSize::b32, // Register size
|
||||
0x42u // Register reset value
|
||||
true // Enable shadow value for the register
|
||||
>
|
||||
@@ -239,7 +393,7 @@ It is sometimes the case that multiple fields within a register needs to be writ
|
||||
Consider the following setup (not so artifical; it is inspired by a real flash memory controller peripheral):
|
||||
|
||||
```c++
|
||||
struct FlashCtrl : Register<0xF0008282, 8u> {
|
||||
struct FlashCtrl : Register<0xF0008282, RegBitSize::b8> {
|
||||
|
||||
// Command field.
|
||||
// Can bet set to various values to trigger write, erase or check operations.
|
||||
@@ -271,18 +425,21 @@ Now let's assume the following scenario:
|
||||
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:
|
||||
For this kind of situation a *merged 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);
|
||||
FlashCtrl::merge_write<FlashCtrl::ProtectionError, 0x1>().with<FlashCtrl::CommandComplete, 0x0>().done();
|
||||
|
||||
// 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.
|
||||
The `merge_write` method is only available in register type (`PackedRegister` or `Register`) 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. By design, the successive write operations have to be chained, that is, it is not possible to capture a `merge_write` context and add other write operations to it; it always has to be of the form: `register::merge_write<field1, xxx>().with<field2, xxx>(). ... .done()`.
|
||||
|
||||
**Warning:** if`done()` is not called at the end of the successive write operations no write at all will be performed.
|
||||
|
||||
Similarly to regular write operations it is recommended to use the template version (as shown in the example) if possible: this will enable overflow checking and possibly use faster write implementations. If not possible the values to be written are passed as arguments to the various calls.
|
||||
|
||||
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).
|
||||
|
||||
BIN
Assembly_Comparison.png
Normal file
BIN
Assembly_Comparison.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 162 KiB |
@@ -25,6 +25,7 @@ set(CPPREG_API_HEADERS
|
||||
register/Mask.h
|
||||
register/Overflow.h
|
||||
register/Register.h
|
||||
register/RegisterPack.h
|
||||
register/ShadowValue.h
|
||||
register/Traits.h)
|
||||
|
||||
|
||||
46
CODE_OF_CONDUCT.md
Normal file
46
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at nclauvelin+github@sendyne.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
||||
173
Performance.md
Normal file
173
Performance.md
Normal file
File diff suppressed because one or more lines are too long
199
QuickStart.md
199
QuickStart.md
@@ -28,19 +28,25 @@ There are two recommended ways to use `cppreg` in a 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):
|
||||
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/Clang 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:
|
||||
## Prologue ##
|
||||
When developing firmware code for embedded MCUs it is customary that peripherals and devices are configured and operated through MMIO registers (for example, a UART or GPIO peripheral). For a given peripheral the related registers are often grouped together at a specific location in memory. `cppreg` makes it possible to *map* C++ constructs to such peripherals registers and fields in order to get safer and more expressive code. The following two examples illustrate how to define such constructs using `cppreg`.
|
||||
|
||||
| Absolute address (hex) | Register | Width (bits) | Access |
|
||||
|:----------------------:|:-----------------|:------------:|:------------------:|
|
||||
| 0xA400 0000 | Peripheral setup register | 8 | R/W |
|
||||
|
||||
## Example: peripheral setup ##
|
||||
Consider an arbitrary peripheral with the following registers:
|
||||
|
||||
| Address (hex) | Register | Width (bits) | Access |
|
||||
|:-------------:|:-----------------|:------------:|:------------------:|
|
||||
| 0xA400 0000 | Setup register | 8 | R/W |
|
||||
| 0xA400 0001 | RX data register | 8 | R |
|
||||
| 0xA400 0002 | TX data register | 8 | W |
|
||||
|
||||
The setup register bits are mapped as:
|
||||
|
||||
@@ -48,94 +54,127 @@ The setup register bits are mapped as:
|
||||
* 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:
|
||||
The RX and TX data registers both contain a single DATA field occupying the whole register. The DATA field is read-only for the RX register and write-only for the TX register.
|
||||
|
||||
The goal of `cppreg` is to facilitate the manipulation of such a peripheral. This can be done as follow:
|
||||
|
||||
```c++
|
||||
#include <cppreg.h>
|
||||
#include <cppreg.h> // use cppreg-all.h instead if you are using the single header.
|
||||
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> {
|
||||
// Peripheral structure.
|
||||
// This will contain all the peripheral registers definitions.
|
||||
struct Peripheral {
|
||||
|
||||
// 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
|
||||
// Define a register pack type.
|
||||
// This is used to indicate that the register are packed together in memory.
|
||||
using periph_pack = RegisterPack<
|
||||
0xA400 0000, // Base address of the pack (i.e., peripheral).
|
||||
3 // Number of bytes for all peripheral registers.
|
||||
>;
|
||||
|
||||
// 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.
|
||||
// Define the setup register and the fields.
|
||||
struct Setup : PackedRegister<
|
||||
periph_pack, // Pack to which the register belongs to.
|
||||
RegBitSize::b8, // Register size.
|
||||
0 // Offset in bits from the base.
|
||||
> {
|
||||
|
||||
// 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.
|
||||
using Frequency = Field<Setup, 5u, 0u, read_write>; // FREQ
|
||||
using Mode = Field<Setup, 2u, 5u, read_write>; // MODE
|
||||
using Enable = Field<Setup, 1u, 7u, read_write>; // EN
|
||||
|
||||
};
|
||||
|
||||
// Define the RX data register.
|
||||
struct RX : PackedRegister<
|
||||
periph_pack, // Pack to which the register belongs to.
|
||||
RegBitSize::b8, // Register width in bits
|
||||
8 // Offset in bits from the base.
|
||||
> {
|
||||
using Data = Field<RX, 8u, 0u, read_only>;
|
||||
};
|
||||
|
||||
// Define the RX data register.
|
||||
struct TX : PackedRegister<
|
||||
periph_pack, // Pack to which the register belongs to.
|
||||
RegBitSize::b8, // Register width in bits
|
||||
8 * 2 // Offset in bits from the base.
|
||||
> {
|
||||
using Data = Field<TX, 8u, 0u, write_only>;
|
||||
};
|
||||
|
||||
};
|
||||
```
|
||||
|
||||
For more details about the various types (`RegisterPack`, `PackedRegister`, and `Field` see the [API documentation](API.md)).
|
||||
|
||||
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.
|
||||
3. then write `1` to the enable field to start the peripheral,
|
||||
4. if the peripheral fails to start or stop this will be set to zero.
|
||||
|
||||
Once enabled we also want to implement a echo loop that will simply read data from the RX register and put them in the TX register so that the peripheral will echo whatever it receives.
|
||||
|
||||
This translates to:
|
||||
|
||||
```c++
|
||||
// Setup and enable.
|
||||
Peripheral::Mode::write<0x2>();
|
||||
Peripheral::Frequency::write<0x1A>();
|
||||
Peripheral::Enable::set();
|
||||
Peripheral::Setup::Mode::write<0x2>();
|
||||
Peripheral::Setup::Frequency::write<0x1A>();
|
||||
Peripheral::Setup::Enable::set();
|
||||
|
||||
// Echo loopback.
|
||||
while (true) {
|
||||
|
||||
// Check if sill enabled.
|
||||
if (!Peripheral::Setup::Enable::is_set()) {
|
||||
break;
|
||||
};
|
||||
|
||||
// Read data.
|
||||
const auto incoming_data = Peripheral::RX::Data::read();
|
||||
|
||||
// Echo the data.
|
||||
Peripheral::TX::Data::write(incoming_data);
|
||||
|
||||
// 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 `write` calls for the `Setup` register pass the data as template arguments, while the write call for the `TX` register pass it as a function argument: if the value to be written is known at compile time it is recommended to use the template form; the template form will detect overflow (see below) and will also make it possible to use a faster write implementation in some cases,
|
||||
* `Field`-based types 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):
|
||||
In this example, we can already see how `cppreg` 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();
|
||||
Peripheral::Setup::Mode::write<0x4>(); // Would not compile because it overflows the MODE field.
|
||||
Peripheral::Setup::Frequency::write<0xFF>(); // Idem. This overflows the FREQ field.
|
||||
Peripheral::Setup::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
|
||||
Peripheral::Setup::Mode::write(0x4); // Compile but writes 0x0 to the MODE field.
|
||||
Peripheral::Setup::Frequency::write(0xFF); // Compile but writes 0x1F to the FREQ field
|
||||
|
||||
// Access policies.
|
||||
Peripheral::RX::Data::write<0x1>(); // Would not compile because read-only.
|
||||
Peripheral::TX::Data::read(); // Would not compile because write-only.
|
||||
```
|
||||
|
||||
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.
|
||||
struct PeripheralInterface : Peripheral {
|
||||
|
||||
// Configuration with mode and frequency.
|
||||
template <Mode::type mode, Frequency::type f>
|
||||
@@ -150,15 +189,22 @@ struct Peripheral : Register<0xA4000000, 8u> {
|
||||
Enable::set();
|
||||
};
|
||||
|
||||
// Is enabled method.
|
||||
inline static bool is_enabled() {
|
||||
return Enable::is_set();
|
||||
};
|
||||
|
||||
// Disable method.
|
||||
inline static void disable() {
|
||||
Enable::clear();
|
||||
};
|
||||
|
||||
// ...
|
||||
|
||||
};
|
||||
|
||||
Peripheral::configure<0x2, 01A>();
|
||||
Peripheral::enable();
|
||||
PeripheralInterface::configure<0x2, 01A>();
|
||||
PeripheralInterface::enable();
|
||||
```
|
||||
|
||||
|
||||
@@ -182,31 +228,32 @@ Using `cppreg` we can define custom types for these registers and define an inte
|
||||
// GPIO peripheral namespace.
|
||||
namespace gpio {
|
||||
|
||||
// To define a register type:
|
||||
// using x = Register<register_address, register_width in bits>;
|
||||
// Register pack.
|
||||
// 6 x 32-bits = 6 x 4 bytes.
|
||||
using gpio_pack = RegisterPack<0xF4000000, 6 * 8>;
|
||||
|
||||
// Data output register (PDOR).
|
||||
using pdor = Register<0xF4000000, 32u>;
|
||||
using pdor = PackedRegister<gpio_pack, RegBitSize::b32, 0 * 32>;
|
||||
|
||||
// Set output register (PSOR).
|
||||
using psor = Register<0xF4000004, 32u>;
|
||||
using psor = PackedRegister<gpio_pack, RegBitSize::b32, 1 * 32>;
|
||||
|
||||
// Clear output register (PCOR).
|
||||
using pcor = Register<0xF4000008, 32u>;
|
||||
using pcor = PackedRegister<gpio_pack, RegBitSize::b32, 2 * 32>;
|
||||
|
||||
// Toggle output register (PTOR).
|
||||
using ptor = Register<0xF400000C, 32u>;
|
||||
using ptor = PackedRegister<gpio_pack, RegBitSize::b32, 3 * 32>;
|
||||
|
||||
// Data input register.
|
||||
using pdir = Register<0xF4000010, 32u>
|
||||
using pdir = PackedRegister<gpio_pack, RegBitSize::b32, 4 * 32>;
|
||||
|
||||
// Data direction output register.
|
||||
using pddr = Register<0xF4000014, 32u>
|
||||
using pddr = PackedRegister<gpio_pack, RegBitSize::b32, 5 * 32>;
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
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:
|
||||
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 `PackedRegister` 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.
|
||||
@@ -219,10 +266,10 @@ namespace leds {
|
||||
// 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>;
|
||||
using pin_direction = Field<gpio::pddr, 1u, Pin, read_write>;
|
||||
using pin_set = Field<gpio::psor, 1u, Pin, read_write>;
|
||||
using pin_clear = Field<gpio::pcor, 1u, Pin, read_write>;
|
||||
using pin_toggle = Field<gpio::ptor, 1u, Pin, read_write>;
|
||||
|
||||
// We also define some constants.
|
||||
constexpr static const gpio::pddr::type pin_output_dir = 1u;
|
||||
@@ -239,8 +286,8 @@ namespace leds {
|
||||
pin_toggle::set(); // Set PTOR to 1.
|
||||
};
|
||||
inline static void init() {
|
||||
pin_direction::write(pin_output_dir);
|
||||
off();
|
||||
pin_direction::write(pin_output_dir);
|
||||
};
|
||||
|
||||
}
|
||||
@@ -252,7 +299,7 @@ namespace leds {
|
||||
}
|
||||
```
|
||||
|
||||
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).
|
||||
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 mapping. At compile time `cppreg` also makes sure that the field actually fits within the register specifications (a `Field`-based type cannot overflow the register in which it is defined). Similarly, `cppreg` also checks that packed registers are properly aligned and fit within the pack.
|
||||
|
||||
Using this interface it becomes easy to write very expressive code such as:
|
||||
|
||||
@@ -266,10 +313,10 @@ leds::blue::on();
|
||||
|
||||
// Wait a bit.
|
||||
for (std::size_t i = 0; i < 500000; ++i)
|
||||
__NOP();
|
||||
asm("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.
|
||||
A quick note: in this example some of the fields are write-only (set, clear and toggle); in general extra care has to be taken when dealing with write-only fields 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.
|
||||
|
||||
14
README.md
14
README.md
@@ -7,11 +7,23 @@ Copyright Sendyne Corp., 2010-2018. All rights reserved ([LICENSE](LICENSE)).
|
||||
|
||||
* 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,
|
||||
* [huge emphasis](Performance.md) on ensuring the assembly is the same if not better than CMSIS versions,
|
||||
* 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).
|
||||
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).
|
||||
|
||||
The features provided by `cppreg` come with no overhead or performance penalty compared to traditional low-level C approaches. We give [here](Performance.md) an example comparing the assembly generated by a CMSIS-like implementation versus a `cppreg` one.
|
||||
|
||||
|
||||
## Requirements ##
|
||||
`cppreg` is designed to be usable on virtually any hardware that statisfies the following requirements:
|
||||
|
||||
* MMIO register sizes are integral numbers of bytes (*e.g.*, 8 bits, 16 bits, ...),
|
||||
* registers are properly aligned: a N-bit register is aligned on a N-bit boundary,
|
||||
|
||||
GCC (4.8 and above) and Clang (3.3 and above) are supported and it is expected that any other C++11-compliant compiler should work (see the [quick start guide](QuickStart.md) for recommended compiler settings).
|
||||
|
||||
|
||||
## Manifest ##
|
||||
|
||||
1
_config.yml
Normal file
1
_config.yml
Normal file
@@ -0,0 +1 @@
|
||||
theme: jekyll-theme-cayman
|
||||
7
cppreg.h
7
cppreg.h
@@ -10,10 +10,11 @@
|
||||
#define CPPREG_CPPREG_H
|
||||
|
||||
|
||||
#include "Register.h"
|
||||
#include "Field.h"
|
||||
#include "Internals.h"
|
||||
#include "MergeWrite.h"
|
||||
#include "Overflow.h"
|
||||
#include "Register.h"
|
||||
#include "RegisterPack.h"
|
||||
#include "Field.h"
|
||||
|
||||
|
||||
#endif // CPPREG_CPPREG_H
|
||||
|
||||
@@ -28,23 +28,36 @@ namespace cppreg {
|
||||
using Address_t = std::uintptr_t;
|
||||
|
||||
|
||||
//! Type alias for register and field widths.
|
||||
//! Enumeration type for register size in bits.
|
||||
/**
|
||||
* 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.
|
||||
* This is used to enforce the supported register sizes.
|
||||
*/
|
||||
using Width_t = std::uint8_t;
|
||||
enum class RegBitSize {
|
||||
b8, //!< 8-bit register.
|
||||
b16, //!< 16-bit register.
|
||||
b32, //!< 32-bit register.
|
||||
b64 //!< 64-bit register.
|
||||
};
|
||||
|
||||
|
||||
//! Type alias field width.
|
||||
using FieldWidth_t = std::uint8_t;
|
||||
|
||||
|
||||
//! Type alias for register and field offsets.
|
||||
//! Type alias for field offset.
|
||||
using FieldOffset_t = std::uint8_t;
|
||||
|
||||
|
||||
//! Shorthand for max value as a mask.
|
||||
/**
|
||||
* 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.
|
||||
* @tparam T Data type.
|
||||
*
|
||||
* This is used to define register masks.
|
||||
*/
|
||||
using Offset_t = std::uint8_t;
|
||||
template <typename T>
|
||||
struct type_mask {
|
||||
constexpr static const T value = std::numeric_limits<T>::max();
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
|
||||
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <tuple>
|
||||
|
||||
|
||||
#endif // CPPREG_CPPREG_INCLUDES_H
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Access policy abstract implementation.
|
||||
//! Access policy implementation.
|
||||
/**
|
||||
* @file AccessPolicy.h
|
||||
* @author Nicolas Clauvelin (nclauvelin@sendyne.com)
|
||||
@@ -6,6 +6,17 @@
|
||||
*
|
||||
* Access policies are used to describe if register fields are read-write,
|
||||
* read-only or write-only.
|
||||
*
|
||||
* - The read and write implementations distinguish between trivial and
|
||||
* non-trivial operations. A trivial operation corresponds to a read or
|
||||
* write over a whole register; in such a case no masking of shifting is
|
||||
* necessary. On the other hand, if the operation is only over a region of
|
||||
* the register proper masking and shifting is required. The switch between
|
||||
* trivial and non-trivial implementations is done automatically.
|
||||
* - The write implementation also distinguishes between writing constant
|
||||
* values (i.e., known at compile time) and non-constant value. This was
|
||||
* intended to make it possible to simplify operations at compile time and
|
||||
* minimize overhead.
|
||||
*/
|
||||
|
||||
|
||||
@@ -20,22 +31,184 @@
|
||||
namespace cppreg {
|
||||
|
||||
|
||||
//! Register read implementation.
|
||||
/**
|
||||
* @tparam MMIO_t Memory device type.
|
||||
* @tparam T Register data type.
|
||||
* @tparam mask Mask for the read operation.
|
||||
* @tparam offset Offset for the read operation.
|
||||
*
|
||||
* The mask and offset are used to define a specific field within the
|
||||
* register.
|
||||
*/
|
||||
template <typename MMIO_t, typename T, T mask, FieldOffset_t offset>
|
||||
struct RegisterRead {
|
||||
|
||||
//! Boolean flag for trivial implementation.
|
||||
/**
|
||||
* The trivial corresponds to reading the whole register, that is,
|
||||
* the mask is identity and the offset is zero.
|
||||
*/
|
||||
constexpr static const bool is_trivial =
|
||||
(mask == type_mask<T>::value) && (offset == 0u);
|
||||
|
||||
//! Non-trivial read implementation.
|
||||
/**
|
||||
* @param mmio_device Pointer to the register memory device.
|
||||
* @return The content of the register field.
|
||||
*/
|
||||
template <typename U = void>
|
||||
static T read(
|
||||
const MMIO_t& mmio_device,
|
||||
typename std::enable_if<!is_trivial, U*>::type = nullptr
|
||||
) noexcept {
|
||||
return static_cast<T>((mmio_device & mask) >> offset);
|
||||
};
|
||||
|
||||
//! Trivial read implementation.
|
||||
/**
|
||||
* @param mmio_device Pointer to the register memory device.
|
||||
* @return The content of the register field.
|
||||
*/
|
||||
template <typename U = void>
|
||||
static T read(
|
||||
const MMIO_t& mmio_device,
|
||||
typename std::enable_if<is_trivial, U*>::type = nullptr
|
||||
) noexcept {
|
||||
return static_cast<T>(mmio_device);
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
||||
//! Register write implementation.
|
||||
/**
|
||||
* @tparam MMIO_t Memory device type.
|
||||
* @tparam T Register data type.
|
||||
* @tparam mask Mask for the read operation.
|
||||
* @tparam offset Offset for the read operation.
|
||||
*
|
||||
* The mask and offset are used to define a specific field within the
|
||||
* register.
|
||||
*
|
||||
* This write implementation is used only when the value to be written is
|
||||
* not a constant expression.
|
||||
*/
|
||||
template <typename MMIO_t, typename T, T mask, FieldOffset_t offset>
|
||||
struct RegisterWrite {
|
||||
|
||||
//! Boolean flag for trivial implementation.
|
||||
/**
|
||||
* The trivial corresponds to writing to the whole register, that is,
|
||||
* the mask is identity and the offset is zero.
|
||||
*/
|
||||
constexpr static const bool is_trivial =
|
||||
(mask == type_mask<T>::value) && (offset == 0u);
|
||||
|
||||
//! Non-trivial write implementation.
|
||||
/**
|
||||
* @param mmio_device Pointer to the register memory device.
|
||||
* @param value Value to be written to the register field.
|
||||
*/
|
||||
template <typename U = void>
|
||||
static void write(
|
||||
MMIO_t& mmio_device,
|
||||
T value,
|
||||
typename std::enable_if<!is_trivial, U*>::type = nullptr
|
||||
) noexcept {
|
||||
mmio_device = static_cast<T>(
|
||||
(mmio_device & ~mask) | ((value << offset) & mask)
|
||||
);
|
||||
};
|
||||
|
||||
//! Trivial write implementation.
|
||||
/**
|
||||
* @param mmio_device Pointer to the register memory device.
|
||||
* @param value Value to be written to the register field.
|
||||
*/
|
||||
template <typename U = void>
|
||||
static void write(
|
||||
MMIO_t& mmio_device,
|
||||
T value,
|
||||
typename std::enable_if<is_trivial, U*>::type = nullptr
|
||||
) noexcept {
|
||||
mmio_device = value;
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
||||
//! Register write constant implementation.
|
||||
/**
|
||||
* @tparam MMIO_t Memory device type.
|
||||
* @tparam T Register data type.
|
||||
* @tparam mask Mask for the read operation.
|
||||
* @tparam offset Offset for the read operation.
|
||||
* @tparam value Value to be written to the register field.
|
||||
*
|
||||
* The mask and offset are used to define a specific field within the
|
||||
* register.
|
||||
*
|
||||
* This write implementation is used only when the value to be written is
|
||||
* a constant expression.
|
||||
*/
|
||||
template <
|
||||
typename MMIO_t, typename T, T mask, FieldOffset_t offset, T value
|
||||
>
|
||||
struct RegisterWriteConstant {
|
||||
|
||||
//! Boolean flag for trivial implementation.
|
||||
/**
|
||||
* The trivial corresponds to writing to the whole register, that is,
|
||||
* the mask is identity and the offset is zero.
|
||||
*/
|
||||
constexpr static const bool is_trivial =
|
||||
(mask == type_mask<T>::value) && (offset == 0u);
|
||||
|
||||
//! Non-trivial write implementation.
|
||||
/**
|
||||
* @param mmio_device Pointer to the register memory device.
|
||||
*/
|
||||
template <typename U = void>
|
||||
static void write(
|
||||
MMIO_t& mmio_device,
|
||||
typename std::enable_if<!is_trivial, U*>::type = nullptr
|
||||
) noexcept {
|
||||
mmio_device = static_cast<T>(
|
||||
(mmio_device & ~mask) | ((value << offset) & mask)
|
||||
);
|
||||
};
|
||||
|
||||
//! Trivial write implementation.
|
||||
/**
|
||||
* @param mmio_device Pointer to the register memory device.
|
||||
*/
|
||||
template <typename U = void>
|
||||
static void write(
|
||||
MMIO_t& mmio_device,
|
||||
typename std::enable_if<is_trivial, U*>::type = nullptr
|
||||
) noexcept {
|
||||
mmio_device = value;
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
||||
//! Read-only access policy.
|
||||
struct read_only {
|
||||
|
||||
//! Read access implementation.
|
||||
/**
|
||||
* @tparam MMIO_t Register memory device type.
|
||||
* @tparam T Field data type.
|
||||
* @tparam mask Field mask.
|
||||
* @tparam offset Field offset.
|
||||
* @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);
|
||||
template <typename MMIO_t, typename T, T mask, FieldOffset_t offset>
|
||||
static T read(const MMIO_t& mmio_device) noexcept {
|
||||
return RegisterRead<MMIO_t, T, mask, offset>::read(mmio_device);
|
||||
};
|
||||
|
||||
};
|
||||
@@ -46,52 +219,74 @@ namespace cppreg {
|
||||
|
||||
//! Write access implementation.
|
||||
/**
|
||||
* @tparam MMIO_t Register memory device type.
|
||||
* @tparam T Field data type.
|
||||
* @tparam mask Field mask.
|
||||
* @tparam offset Field offset.
|
||||
* @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,
|
||||
template <typename MMIO_t, typename T, T mask, FieldOffset_t offset>
|
||||
static void write(MMIO_t& mmio_device,
|
||||
const T value) noexcept {
|
||||
*mmio_device = static_cast<T>((*mmio_device & ~mask) |
|
||||
((value << offset) & mask));
|
||||
RegisterWrite<MMIO_t, T, mask, offset>::write(mmio_device, value);
|
||||
};
|
||||
|
||||
//! Write access implementation for constant value.
|
||||
/**
|
||||
* @tparam MMIO_t Register memory device type.
|
||||
* @tparam T Field data type.
|
||||
* @tparam mask Field mask.
|
||||
* @tparam offset Field offset.
|
||||
* @tparam value Value to be written at the field location.
|
||||
* @param mmio_device Pointer to register mapped memory.
|
||||
*/
|
||||
template <
|
||||
typename MMIO_t, typename T, T mask, FieldOffset_t offset, T value
|
||||
>
|
||||
static void write(MMIO_t& mmio_device) noexcept {
|
||||
RegisterWriteConstant<MMIO_t, T, mask, offset, value>
|
||||
::write(mmio_device);
|
||||
};
|
||||
|
||||
//! Set field implementation.
|
||||
/**
|
||||
* @tparam T Field data type.
|
||||
* @tparam mask Field mask.
|
||||
* @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);
|
||||
template <typename MMIO_t, typename T, T mask>
|
||||
static void set(MMIO_t& mmio_device)
|
||||
noexcept {
|
||||
RegisterWriteConstant<MMIO_t, T, mask, 0u, mask>
|
||||
::write(mmio_device);
|
||||
};
|
||||
|
||||
//! Clear field implementation.
|
||||
/**
|
||||
* @tparam MMIO_t Register memory device type.
|
||||
* @tparam T Field data type.
|
||||
* @tparam mask Field mask.
|
||||
* @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);
|
||||
template <typename MMIO_t, typename T, T mask>
|
||||
static void clear(MMIO_t& mmio_device)
|
||||
noexcept {
|
||||
RegisterWriteConstant<MMIO_t, T, mask, 0u, ~mask>
|
||||
::write(mmio_device);
|
||||
};
|
||||
|
||||
//! Toggle field implementation.
|
||||
/**
|
||||
* @tparam MMIO_t Register memory device type.
|
||||
* @tparam T Field data type.
|
||||
* @tparam mask Field mask.
|
||||
* @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;
|
||||
template <typename MMIO_t, typename T, T mask>
|
||||
static void toggle(MMIO_t& mmio_device)
|
||||
noexcept {
|
||||
mmio_device = static_cast<T>((mmio_device) ^ mask);
|
||||
};
|
||||
|
||||
};
|
||||
@@ -102,20 +297,41 @@ namespace cppreg {
|
||||
|
||||
//! Write access implementation.
|
||||
/**
|
||||
* @tparam MMIO_t Register memory device type.
|
||||
* @tparam T Field data type.
|
||||
* @tparam mask Field mask.
|
||||
* @tparam offset Field offset
|
||||
* @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 {
|
||||
template <typename MMIO_t, typename T, T mask, FieldOffset_t offset>
|
||||
static void write(MMIO_t& mmio_device, const T value) noexcept {
|
||||
|
||||
// We cannot read the current value so we simply fully write to it.
|
||||
*mmio_device = ((value << offset) & mask);
|
||||
// For write-only fields we can only write to the whole register.
|
||||
RegisterWrite<MMIO_t, T, type_mask<T>::value, 0u>::write(
|
||||
mmio_device, ((value << offset) & mask)
|
||||
);
|
||||
};
|
||||
|
||||
//! Write access implementation for constant value.
|
||||
/**
|
||||
* @tparam MMIO_t Register memory device type.
|
||||
* @tparam T Field data type.
|
||||
* @tparam mask Field mask.
|
||||
* @tparam offset Field offset
|
||||
* @tparam value Value to be written at the field location.
|
||||
* @param mmio_device Pointer to register mapped memory.
|
||||
*/
|
||||
template <
|
||||
typename MMIO_t, typename T, T mask, FieldOffset_t offset, T value
|
||||
>
|
||||
static void write(MMIO_t& mmio_device) noexcept {
|
||||
|
||||
// For write-only fields we can only write to the whole register.
|
||||
RegisterWriteConstant<
|
||||
MMIO_t, T, type_mask<T>::value, 0u, ((value << offset) & mask)
|
||||
>
|
||||
::write(mmio_device);
|
||||
|
||||
};
|
||||
|
||||
|
||||
206
register/Field.h
206
register/Field.h
@@ -6,6 +6,7 @@
|
||||
*
|
||||
* This header provides the definitions related to register field
|
||||
* implementation.
|
||||
* Strictly speaking a field is defined as a region of a register.
|
||||
*/
|
||||
|
||||
|
||||
@@ -27,7 +28,7 @@ namespace cppreg {
|
||||
* @tparam BaseRegister Parent register.
|
||||
* @tparam width Field width.
|
||||
* @tparam offset Field offset.
|
||||
* @tparam P Access policy type (rw, ro, wo).
|
||||
* @tparam P Access policy type.
|
||||
*
|
||||
* This data structure provides static methods to deal with field access
|
||||
* (read, write, set, clear, and toggle). These methods availability depends
|
||||
@@ -38,8 +39,8 @@ namespace cppreg {
|
||||
*/
|
||||
template <
|
||||
typename BaseRegister,
|
||||
Width_t FieldWidth,
|
||||
Offset_t FieldOffset,
|
||||
FieldWidth_t field_width,
|
||||
FieldOffset_t field_offset,
|
||||
typename AccessPolicy
|
||||
>
|
||||
struct Field {
|
||||
@@ -53,201 +54,168 @@ namespace cppreg {
|
||||
//! 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 width.
|
||||
constexpr static const FieldWidth_t width = field_width;
|
||||
|
||||
//! Field offset.
|
||||
constexpr static const FieldOffset_t offset = field_offset;
|
||||
|
||||
//! Field mask.
|
||||
/**
|
||||
* The field mask is computed at compile time.
|
||||
*/
|
||||
constexpr static const type mask = make_shifted_mask<type>(width,
|
||||
offset);
|
||||
|
||||
//! Boolean flag indicating if a shadow value is used.
|
||||
constexpr static const bool has_shadow =
|
||||
parent_register::shadow::value;
|
||||
|
||||
//! Customized overflow check implementation for Field types.
|
||||
/**
|
||||
* @tparam value Value to be checked for overflow.
|
||||
* @return `true` if no overflow, `false` otherwise.
|
||||
*
|
||||
* This is only used for the template form of the write method.
|
||||
*/
|
||||
template <type value>
|
||||
struct check_overflow {
|
||||
constexpr static const bool result =
|
||||
internals::check_overflow<
|
||||
parent_register::size,
|
||||
value,
|
||||
(mask >> offset)
|
||||
>::result::value;
|
||||
};
|
||||
struct check_overflow : internals::check_overflow<
|
||||
type,
|
||||
value,
|
||||
(mask >> offset)
|
||||
> {};
|
||||
|
||||
//! Field read method.
|
||||
//!@ Field read method.
|
||||
/**
|
||||
* @return Field value.
|
||||
*/
|
||||
inline static type read() noexcept {
|
||||
return
|
||||
AccessPolicy
|
||||
::template read<MMIO_t>(parent_register::ro_mem_pointer(),
|
||||
mask,
|
||||
offset);
|
||||
static type read() noexcept {
|
||||
return policy::template read<MMIO_t, type, mask, offset>(
|
||||
parent_register::ro_mem_device()
|
||||
);
|
||||
};
|
||||
|
||||
//! Field write method (shadow value disabled).
|
||||
//! Field write method (no shadow value).
|
||||
/**
|
||||
* @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);
|
||||
static void
|
||||
write(const typename std::enable_if<!has_shadow, T>::type value)
|
||||
noexcept {
|
||||
policy::template write<MMIO_t, type, mask, offset>(
|
||||
parent_register::rw_mem_device(),
|
||||
value
|
||||
);
|
||||
};
|
||||
|
||||
//! Field write method (shadow value enabled).
|
||||
//! Field write method (w/ shadow value).
|
||||
/**
|
||||
* @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 {
|
||||
static void
|
||||
write(const typename std::enable_if<has_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);
|
||||
RegisterWrite<type, type, mask, offset>
|
||||
::write(parent_register::shadow::shadow_value, value);
|
||||
|
||||
// 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);
|
||||
policy::template write<MMIO_t, type, type_mask<type>::value, 0u>(
|
||||
parent_register::rw_mem_device(),
|
||||
parent_register::shadow::shadow_value
|
||||
);
|
||||
|
||||
};
|
||||
|
||||
//! Field write method with compile-time check (shadow value disabled).
|
||||
//! Field write method with overflow check (no shadow value).
|
||||
/**
|
||||
* @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.
|
||||
* field and uses the constant write implementation.
|
||||
*/
|
||||
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);
|
||||
static void write(
|
||||
typename std::enable_if<
|
||||
!has_shadow
|
||||
&&
|
||||
check_overflow<value>::value,
|
||||
T
|
||||
>::type* = nullptr
|
||||
) noexcept {
|
||||
policy::template write<MMIO_t, type, mask, offset, value>(
|
||||
parent_register::rw_mem_device()
|
||||
);
|
||||
};
|
||||
|
||||
//! Field write method with compile-time check (shadow value enabled).
|
||||
//! Field write method with overflow check (w/ shadow value).
|
||||
/**
|
||||
* @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.
|
||||
* field and uses the constant write implementation.
|
||||
*/
|
||||
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);
|
||||
};
|
||||
static void write(
|
||||
typename std::enable_if<
|
||||
has_shadow
|
||||
&&
|
||||
check_overflow<value>::value,
|
||||
T
|
||||
>::type* = nullptr
|
||||
) noexcept {
|
||||
|
||||
// For this particular we simply forward to the non-constant
|
||||
// implementation because the shadow value needs to be updated.
|
||||
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);
|
||||
static void set() noexcept {
|
||||
policy::template
|
||||
set<MMIO_t, type, mask>(parent_register::rw_mem_device());
|
||||
};
|
||||
|
||||
//! 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);
|
||||
static void clear() noexcept {
|
||||
policy::template
|
||||
clear<MMIO_t, type, mask>(parent_register::rw_mem_device());
|
||||
};
|
||||
|
||||
//! 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);
|
||||
static void toggle() noexcept {
|
||||
policy::template
|
||||
toggle<MMIO_t, type, mask>(parent_register::rw_mem_device());
|
||||
};
|
||||
|
||||
//! 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.
|
||||
* @return `true` if all the bits are set to 1, `false` otherwise.
|
||||
*/
|
||||
template <typename T = bool>
|
||||
inline static typename std::enable_if<FieldWidth == 1, T>::type
|
||||
is_set() noexcept {
|
||||
return (Field::read() == 1u);
|
||||
static bool is_set() noexcept {
|
||||
return (Field::read() == (mask >> offset));
|
||||
};
|
||||
|
||||
//! 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.
|
||||
* @return `true` if all the bits are set to 0, `false` otherwise.
|
||||
*/
|
||||
template <typename T = bool>
|
||||
inline static typename std::enable_if<FieldWidth == 1, T>::type
|
||||
is_clear() noexcept {
|
||||
static bool is_clear() noexcept {
|
||||
return (Field::read() == 0u);
|
||||
};
|
||||
|
||||
@@ -258,7 +226,7 @@ namespace cppreg {
|
||||
"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,
|
||||
static_assert(width != 0u,
|
||||
"defining a Field type of width 0u is not allowed");
|
||||
|
||||
};
|
||||
|
||||
102
register/Internals.h
Normal file
102
register/Internals.h
Normal file
@@ -0,0 +1,102 @@
|
||||
//! Internals implementation.
|
||||
/**
|
||||
* @file Internals.h
|
||||
* @author Nicolas Clauvelin (nclauvelin@sendyne.com)
|
||||
* @copyright Copyright 2010-2018 Sendyne Corp. All rights reserved.
|
||||
*
|
||||
* This header collects various implementations which are required for cppreg
|
||||
* implementation but not intended to be fully exposed to the user.
|
||||
*/
|
||||
|
||||
|
||||
#include "cppreg_Defines.h"
|
||||
#include "Traits.h"
|
||||
|
||||
|
||||
#ifndef CPPREG_INTERNALS_H
|
||||
#define CPPREG_INTERNALS_H
|
||||
|
||||
|
||||
//! cppreg::internals namespace.
|
||||
namespace cppreg {
|
||||
namespace internals {
|
||||
|
||||
|
||||
//! Overflow check implementation.
|
||||
/**
|
||||
* @tparam T Data 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 <
|
||||
typename T,
|
||||
T value,
|
||||
T limit
|
||||
>
|
||||
struct check_overflow : std::integral_constant<bool, value <= limit> {};
|
||||
|
||||
|
||||
//! is_aligned implementation.
|
||||
/**
|
||||
* @tparam address Address to be checked for alignment.
|
||||
* @tparam alignment Alignment boundary in bytes.
|
||||
*
|
||||
* This will only derived from std::true_type if the address is aligned.
|
||||
*/
|
||||
template <Address_t address, std::size_t alignment>
|
||||
struct is_aligned : std::integral_constant<
|
||||
bool,
|
||||
(address & (alignment - 1)) == 0
|
||||
> {
|
||||
};
|
||||
|
||||
|
||||
//! Memory map implementation for packed registers.
|
||||
/**
|
||||
* @tparam address Memory region base address.
|
||||
* @tparam n Memory size in bytes.
|
||||
* @tparam reg_size Register bit size enum value.
|
||||
*
|
||||
* This structure is used to map an array structure onto a memory region.
|
||||
* The size of the array elements is defined by the register size.
|
||||
*/
|
||||
template <Address_t address, std::uint32_t n, RegBitSize reg_size>
|
||||
struct memory_map {
|
||||
|
||||
//! Array type.
|
||||
using mem_array_t = std::array<
|
||||
volatile typename TypeTraits<reg_size>::type,
|
||||
n / sizeof(typename TypeTraits<reg_size>::type)
|
||||
>;
|
||||
|
||||
//! Static reference to memory array.
|
||||
static mem_array_t& array;
|
||||
|
||||
// Alignment check.
|
||||
static_assert(
|
||||
is_aligned<address, TypeTraits<reg_size>::byte_size>::value,
|
||||
"memory_map: base address is mis-aligned for register type"
|
||||
);
|
||||
|
||||
};
|
||||
|
||||
//! Memory array reference definition.
|
||||
template <Address_t address, std::uint32_t n, RegBitSize reg_size>
|
||||
typename memory_map<address, n, reg_size>::mem_array_t&
|
||||
memory_map<address, n, reg_size>::array = *(
|
||||
reinterpret_cast<typename memory_map<address, n, reg_size>
|
||||
::mem_array_t*>(
|
||||
address
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif // CPPREG_INTERNALS_H
|
||||
@@ -28,12 +28,12 @@ namespace cppreg {
|
||||
* @return The mask value.
|
||||
*/
|
||||
template <typename Mask_t>
|
||||
constexpr Mask_t make_mask(const Width_t width) noexcept {
|
||||
constexpr Mask_t make_mask(const FieldWidth_t width) noexcept {
|
||||
return width == 0 ?
|
||||
0u
|
||||
:
|
||||
static_cast<Mask_t>(
|
||||
(make_mask<Mask_t>(Width_t(width - 1)) << 1) | 1
|
||||
(make_mask<Mask_t>(FieldWidth_t(width - 1)) << 1) | 1
|
||||
);
|
||||
};
|
||||
|
||||
@@ -46,8 +46,8 @@ namespace cppreg {
|
||||
* @return The mask value.
|
||||
*/
|
||||
template <typename Mask_t>
|
||||
constexpr Mask_t make_shifted_mask(const Width_t width,
|
||||
const Offset_t offset) noexcept {
|
||||
constexpr Mask_t make_shifted_mask(const FieldWidth_t width,
|
||||
const FieldOffset_t offset) noexcept {
|
||||
return static_cast<Mask_t>(make_mask<Mask_t>(width) << offset);
|
||||
};
|
||||
|
||||
|
||||
@@ -6,6 +6,11 @@
|
||||
*
|
||||
* The "merge write" implementation is designed to make it possible to merge
|
||||
* write operations on different fields into a single one.
|
||||
* The implementation distinguishes between values known at compile time and
|
||||
* value known at run time.
|
||||
*
|
||||
* By design the merge write implementation forces the caller to chain and
|
||||
* finalize all write operations in a single pass.
|
||||
*/
|
||||
|
||||
|
||||
@@ -13,7 +18,7 @@
|
||||
#define CPPREG_MERGEWRITE_H
|
||||
|
||||
|
||||
#include "Overflow.h"
|
||||
#include "Internals.h"
|
||||
#include "AccessPolicy.h"
|
||||
#include <functional>
|
||||
|
||||
@@ -22,11 +27,138 @@
|
||||
namespace cppreg {
|
||||
|
||||
|
||||
//! Write operation holding structure.
|
||||
//! Merge write constant implementation.
|
||||
/**
|
||||
* @tparam Register Underlying register for the final write operation.
|
||||
* @tparam Register Register on which the merged write will be performed.
|
||||
* @tparam mask Initial mask.
|
||||
* @tparam offset Initial offset.
|
||||
* @tparam value Initial value.
|
||||
*
|
||||
* The initial data will come from the field on which the merge write
|
||||
* will be initiated.
|
||||
*
|
||||
* This implementation is designed for operations in which all data is
|
||||
* available at compile time. This makes it possible to leverage a
|
||||
* template implementation and simplify most of the operations (based on
|
||||
* the access policies implementation that detects trivial operations). In
|
||||
* addition, this will also perform overflow checking.
|
||||
*/
|
||||
template <typename Register>
|
||||
template <
|
||||
typename Register,
|
||||
typename Register::type mask,
|
||||
FieldOffset_t offset,
|
||||
typename Register::type value
|
||||
> class MergeWrite_tmpl {
|
||||
|
||||
|
||||
private:
|
||||
|
||||
// Type alias to register base type.
|
||||
using base_type = typename Register::type;
|
||||
|
||||
// Disabled for shadow value register.
|
||||
static_assert(!Register::shadow::value,
|
||||
"merge write is not available for shadow value register");
|
||||
|
||||
// Accumulated value.
|
||||
constexpr static const base_type _accumulated_value =
|
||||
((value << offset) & mask);
|
||||
|
||||
// Combined mask.
|
||||
constexpr static const base_type _combined_mask = mask;
|
||||
|
||||
// Default constructor.
|
||||
MergeWrite_tmpl() = default;
|
||||
|
||||
|
||||
public:
|
||||
|
||||
//! Instantiation method.
|
||||
static MergeWrite_tmpl make() noexcept { return {}; };
|
||||
|
||||
//!@{ Non-copyable and non-moveable.
|
||||
MergeWrite_tmpl(const MergeWrite_tmpl&) = delete;
|
||||
MergeWrite_tmpl& operator=(const MergeWrite_tmpl&) = delete;
|
||||
MergeWrite_tmpl& operator=(MergeWrite_tmpl&&) = delete;
|
||||
MergeWrite_tmpl operator=(MergeWrite_tmpl) = delete;
|
||||
MergeWrite_tmpl(MergeWrite_tmpl&&) = delete;
|
||||
//!@}
|
||||
|
||||
//! Closure method.
|
||||
/**
|
||||
* This is where the write happens.
|
||||
*/
|
||||
void done() const && noexcept {
|
||||
|
||||
// Get memory pointer.
|
||||
typename Register::MMIO_t& mmio_device =
|
||||
Register::rw_mem_device();
|
||||
|
||||
// Write to the whole register using the current accumulated value
|
||||
// and combined mask.
|
||||
// No offset needed because we write to the whole register.
|
||||
RegisterWriteConstant<
|
||||
typename Register::MMIO_t,
|
||||
typename Register::type,
|
||||
_combined_mask,
|
||||
0u,
|
||||
_accumulated_value
|
||||
>::write(mmio_device);
|
||||
|
||||
};
|
||||
|
||||
//! With method for constant value.
|
||||
/**
|
||||
* @tparam F Field to be written
|
||||
* @tparam new_value Value to write to the field.
|
||||
* @return A new instance for chaining other write operations.
|
||||
*/
|
||||
template <
|
||||
typename F,
|
||||
base_type new_value,
|
||||
typename T = MergeWrite_tmpl<
|
||||
Register,
|
||||
(_combined_mask | F::mask),
|
||||
0u,
|
||||
(_accumulated_value & ~F::mask) | ((new_value << F::offset) &
|
||||
F::mask)
|
||||
>
|
||||
>
|
||||
typename std::enable_if<
|
||||
(internals::check_overflow<
|
||||
typename Register::type, new_value, (F::mask >> F::offset)
|
||||
>::value),
|
||||
T
|
||||
>::type&&
|
||||
with() const && 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");
|
||||
|
||||
return std::move(T::make());
|
||||
|
||||
};
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
//! Merge write implementation.
|
||||
/**
|
||||
* @tparam Register Register on which the merged write will be performed.
|
||||
* @tparam mask Initial mask.
|
||||
*
|
||||
* The initial mask will come from the field on which the merge write
|
||||
* will be initiated.
|
||||
*/
|
||||
template <
|
||||
typename Register,
|
||||
typename Register::type mask
|
||||
>
|
||||
class MergeWrite {
|
||||
|
||||
|
||||
@@ -35,43 +167,49 @@ namespace cppreg {
|
||||
//! Type alias to register base type.
|
||||
using base_type = typename Register::type;
|
||||
|
||||
|
||||
private:
|
||||
|
||||
// Combined mask.
|
||||
constexpr static const base_type _combined_mask = mask;
|
||||
|
||||
|
||||
public:
|
||||
|
||||
//! 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;
|
||||
constexpr static MergeWrite make(const base_type value) noexcept {
|
||||
return MergeWrite(value);
|
||||
};
|
||||
|
||||
//!@ Move constructor.
|
||||
MergeWrite(MergeWrite&& mw) noexcept
|
||||
: _accumulated_value(mw._accumulated_value),
|
||||
_combined_mask(mw._combined_mask) {
|
||||
};
|
||||
: _accumulated_value(mw._accumulated_value) {};
|
||||
|
||||
//!@{ Non-copyable.
|
||||
MergeWrite(const MergeWrite&) = delete;
|
||||
MergeWrite& operator=(const MergeWrite&) = delete;
|
||||
MergeWrite& operator=(MergeWrite&&) = delete;
|
||||
//!@}
|
||||
|
||||
//! Destructor.
|
||||
//! Closure method.
|
||||
/**
|
||||
* This is where the write operation is performed.
|
||||
* This is where the write happens.
|
||||
*/
|
||||
~MergeWrite() {
|
||||
void done() const && noexcept {
|
||||
|
||||
// Get memory pointer.
|
||||
typename Register::MMIO_t* const mmio_device =
|
||||
Register::rw_mem_pointer();
|
||||
typename Register::MMIO_t& mmio_device =
|
||||
Register::rw_mem_device();
|
||||
|
||||
// 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)
|
||||
);
|
||||
RegisterWrite<
|
||||
typename Register::MMIO_t,
|
||||
base_type,
|
||||
_combined_mask,
|
||||
0u
|
||||
>::write(mmio_device, _accumulated_value);
|
||||
|
||||
};
|
||||
|
||||
@@ -80,12 +218,10 @@ namespace cppreg {
|
||||
* @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 {
|
||||
MergeWrite<Register, _combined_mask | F::mask>
|
||||
with(const base_type value) && noexcept {
|
||||
|
||||
// Check that the field belongs to the register.
|
||||
static_assert(std::is_same<
|
||||
@@ -95,62 +231,32 @@ namespace cppreg {
|
||||
"field is not from the same register in merge_write");
|
||||
|
||||
// Update accumulated value.
|
||||
F::policy::write(&_accumulated_value,
|
||||
F::mask,
|
||||
F::offset,
|
||||
value);
|
||||
_accumulated_value = (_accumulated_value & ~F::mask)
|
||||
| ((value << F::offset) & F::mask);
|
||||
|
||||
// Update combine mask.
|
||||
_combined_mask = _combined_mask | F::mask;
|
||||
return
|
||||
std::move(
|
||||
MergeWrite<Register, (_combined_mask | F::mask)>
|
||||
::make(_accumulated_value)
|
||||
);
|
||||
|
||||
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,
|
||||
static_assert(!Register::shadow::value,
|
||||
"merge write is not available for shadow value register");
|
||||
|
||||
// Private default constructor.
|
||||
MergeWrite() : _accumulated_value(0u),
|
||||
_combined_mask(0u) {};
|
||||
constexpr MergeWrite() : _accumulated_value(0u) {};
|
||||
constexpr explicit MergeWrite(const base_type v) :
|
||||
_accumulated_value(v) {};
|
||||
|
||||
// Accumulated value.
|
||||
base_type _accumulated_value;
|
||||
|
||||
// Combined mask.
|
||||
base_type _combined_mask;
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
//! 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
|
||||
@@ -23,10 +23,10 @@ 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`).
|
||||
* @tparam reg_address Register address.
|
||||
* @tparam reg_size Register size enum value.
|
||||
* @tparam reset_value Register reset value (0x0 if unknown).
|
||||
* @tparam use_shadow shadow Boolean flag to enable shadow value.
|
||||
*
|
||||
* This data structure will act as a container for fields and is
|
||||
* therefore limited to a strict minimum. It only carries information
|
||||
@@ -35,88 +35,99 @@ namespace cppreg {
|
||||
* create custom types.
|
||||
*/
|
||||
template <
|
||||
Address_t RegAddress,
|
||||
Width_t RegWidth,
|
||||
typename RegisterType<RegWidth>::type ResetValue = 0x0,
|
||||
bool UseShadow = false
|
||||
Address_t reg_address,
|
||||
RegBitSize reg_size,
|
||||
typename TypeTraits<reg_size>::type reset_value = 0x0,
|
||||
bool use_shadow = false
|
||||
>
|
||||
struct Register {
|
||||
|
||||
//! Register base type.
|
||||
using type = typename RegisterType<RegWidth>::type;
|
||||
using type = typename TypeTraits<reg_size>::type;
|
||||
|
||||
//! MMIO pointer type.
|
||||
using MMIO_t = volatile type;
|
||||
|
||||
//! Register base address.
|
||||
constexpr static const Address_t base_address = RegAddress;
|
||||
//! Boolean flag for shadow value management.
|
||||
using shadow = Shadow<Register, use_shadow>;
|
||||
|
||||
//! Register total width.
|
||||
constexpr static const Width_t size = RegWidth;
|
||||
//! Register base address.
|
||||
constexpr static const Address_t base_address = reg_address;
|
||||
|
||||
//! Register size in bits.
|
||||
constexpr static const std::uint8_t size =
|
||||
TypeTraits<reg_size>::bit_size;
|
||||
|
||||
//! Register reset value.
|
||||
constexpr static const type reset = ResetValue;
|
||||
|
||||
//! Boolean flag for shadow value management.
|
||||
using shadow = Shadow<Register, UseShadow>;
|
||||
constexpr static const type reset = reset_value;
|
||||
|
||||
//! Memory modifier.
|
||||
/**
|
||||
* @return A pointer to the writable register memory.
|
||||
* @return A reference to the writable register memory.
|
||||
*/
|
||||
static MMIO_t* rw_mem_pointer() {
|
||||
return reinterpret_cast<MMIO_t* const>(base_address);
|
||||
static MMIO_t& rw_mem_device() {
|
||||
return *(reinterpret_cast<MMIO_t* const>(base_address));
|
||||
};
|
||||
|
||||
//! Memory accessor.
|
||||
/**
|
||||
* @return A pointer to the read-only register memory.
|
||||
* @return A reference to the read-only register memory.
|
||||
*/
|
||||
static const MMIO_t* ro_mem_pointer() {
|
||||
return reinterpret_cast<const MMIO_t* const>(base_address);
|
||||
static const MMIO_t& ro_mem_device() {
|
||||
return *(reinterpret_cast<const MMIO_t* const>(base_address));
|
||||
};
|
||||
|
||||
//! Merge write function.
|
||||
//! Merge write start 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.
|
||||
* @tparam F Field on which to perform the first write operation.
|
||||
* @param value Value to be written to the field.
|
||||
* @return A merge write data structure to chain further writes.
|
||||
*/
|
||||
template <typename F>
|
||||
inline static MergeWrite<typename F::parent_register>
|
||||
static MergeWrite<typename F::parent_register, F::mask>
|
||||
merge_write(const typename F::type value) noexcept {
|
||||
return
|
||||
MergeWrite<typename F::parent_register>
|
||||
::create_instance(((value << F::offset) & F::mask), F::mask);
|
||||
MergeWrite<typename F::parent_register, F::mask>
|
||||
::make(((value << F::offset) & F::mask));
|
||||
};
|
||||
|
||||
//! Merge write function.
|
||||
//! Merge write start function for constant value.
|
||||
/**
|
||||
* @tparam F Field on which to perform the write operation.
|
||||
* @param value Value to write to the field.
|
||||
* @return A merge write data structure.
|
||||
* @tparam F Field on which to perform the first write operation.
|
||||
* @tparam value Value to be written to the field.
|
||||
* @return A merge write data structure to chain further writes.
|
||||
*/
|
||||
template <
|
||||
typename F,
|
||||
type value,
|
||||
typename T = MergeWrite<typename F::parent_register>
|
||||
typename T = MergeWrite_tmpl<
|
||||
typename F::parent_register,
|
||||
F::mask,
|
||||
F::offset,
|
||||
value
|
||||
>
|
||||
>
|
||||
inline static
|
||||
static
|
||||
typename std::enable_if<
|
||||
internals::check_overflow<
|
||||
size, value, (F::mask >> F::offset)
|
||||
>::result::value,
|
||||
type, value, (F::mask >> F::offset)
|
||||
>::value,
|
||||
T
|
||||
>::type
|
||||
>::type&&
|
||||
merge_write() noexcept {
|
||||
return
|
||||
MergeWrite<typename F::parent_register>
|
||||
::create_instance(((value << F::offset) & F::mask), F::mask);
|
||||
return std::move(T::make());
|
||||
};
|
||||
|
||||
// Sanity check.
|
||||
static_assert(RegWidth != 0u,
|
||||
"defining a Register type of width 0u is not allowed");
|
||||
static_assert(size != 0u,
|
||||
"Register: register definition with zero size");
|
||||
|
||||
// Enforce alignment.
|
||||
static_assert(
|
||||
internals::is_aligned<reg_address, TypeTraits<reg_size>::byte_size>
|
||||
::value,
|
||||
"Register: address is mis-aligned for register type"
|
||||
);
|
||||
|
||||
};
|
||||
|
||||
@@ -125,6 +136,3 @@ namespace cppreg {
|
||||
|
||||
|
||||
#endif // CPPREG_REGISTER_H
|
||||
|
||||
|
||||
|
||||
|
||||
218
register/RegisterPack.h
Normal file
218
register/RegisterPack.h
Normal file
@@ -0,0 +1,218 @@
|
||||
//! Register pack implementation.
|
||||
/**
|
||||
* @file RegisterPack.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.
|
||||
*/
|
||||
|
||||
|
||||
#include "Register.h"
|
||||
#include "Internals.h"
|
||||
|
||||
|
||||
#ifndef CPPREG_REGISTERPACK_H
|
||||
#define CPPREG_REGISTERPACK_H
|
||||
|
||||
|
||||
//! cppreg namespace.
|
||||
namespace cppreg {
|
||||
|
||||
|
||||
//! Register pack base implementation.
|
||||
/**
|
||||
* @tparam base_address Pack base address.
|
||||
* @tparam pack_byte_size Pack size in bytes.
|
||||
*/
|
||||
template <
|
||||
Address_t base_address,
|
||||
std::uint32_t pack_byte_size
|
||||
> struct RegisterPack {
|
||||
|
||||
//! Base address.
|
||||
constexpr static const Address_t pack_base = base_address;
|
||||
|
||||
//! Pack size in bytes.
|
||||
constexpr static const std::uint32_t size_in_bytes = pack_byte_size;
|
||||
|
||||
};
|
||||
|
||||
|
||||
//! Packed register implementation.
|
||||
/**
|
||||
* @tparam RegisterPack Pack to which the register belongs.
|
||||
* @tparam reg_size Register size enum value.
|
||||
* @tparam bit_offset Offset in bits for the register with respect to base.
|
||||
* @tparam reset_value Register reset value (0x0 if unknown).
|
||||
* @tparam use_shadow Boolean flag to enable shadow value.
|
||||
*
|
||||
* This implementation is intended to be used when defining a register
|
||||
* that belongs to a peripheral group.
|
||||
*/
|
||||
template <
|
||||
typename RegisterPack,
|
||||
RegBitSize reg_size,
|
||||
std::uint32_t bit_offset,
|
||||
typename TypeTraits<reg_size>::type reset_value = 0x0,
|
||||
bool use_shadow = false
|
||||
>
|
||||
struct PackedRegister : Register<
|
||||
RegisterPack::pack_base + (bit_offset / 8u),
|
||||
reg_size,
|
||||
reset_value,
|
||||
use_shadow
|
||||
> {
|
||||
|
||||
//! Register type.
|
||||
using base_reg = Register<
|
||||
RegisterPack::pack_base + (bit_offset / 8u),
|
||||
reg_size,
|
||||
reset_value,
|
||||
use_shadow
|
||||
>;
|
||||
|
||||
//! Memory map type.
|
||||
using mem_map_t = internals::memory_map<
|
||||
RegisterPack::pack_base,
|
||||
RegisterPack::size_in_bytes,
|
||||
reg_size
|
||||
>;
|
||||
|
||||
//! Memory modifier.
|
||||
/**
|
||||
* @return A reference to the writable register memory.
|
||||
*/
|
||||
static typename base_reg::MMIO_t& rw_mem_device() noexcept {
|
||||
return mem_map_t::array[bit_offset
|
||||
/ TypeTraits<reg_size>::bit_size];
|
||||
};
|
||||
|
||||
//! Memory accessor.
|
||||
/**
|
||||
* @return A reference to the read-only register memory.
|
||||
*/
|
||||
static const typename base_reg::MMIO_t& ro_mem_device() noexcept {
|
||||
return mem_map_t::array[bit_offset
|
||||
/ TypeTraits<reg_size>::bit_size];
|
||||
};
|
||||
|
||||
// Safety check to detect if are overflowing the pack.
|
||||
static_assert(
|
||||
TypeTraits<reg_size>::byte_size + (bit_offset / 8u) <=
|
||||
RegisterPack::size_in_bytes,
|
||||
"PackRegister: packed register is overflowing the pack"
|
||||
);
|
||||
|
||||
// A packed register of width N bits requires:
|
||||
// - the pack address to be N-bits aligned (N/8 aligned),
|
||||
// - the pack address with offset to be N-bits aligned (N/8 aligned).
|
||||
static_assert(
|
||||
internals::is_aligned<
|
||||
RegisterPack::pack_base,
|
||||
TypeTraits<reg_size>::byte_size
|
||||
>::value,
|
||||
"PackedRegister: pack base address is mis-aligned for register type"
|
||||
);
|
||||
static_assert(
|
||||
internals::is_aligned<
|
||||
RegisterPack::pack_base + (bit_offset / 8u),
|
||||
TypeTraits<reg_size>::byte_size
|
||||
>::value,
|
||||
"PackedRegister: offset address is mis-aligned for register type"
|
||||
);
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
//! Pack indexing structure.
|
||||
/**
|
||||
* @tparam T List of types (registers or fields) to index.
|
||||
*
|
||||
* This can be used to conveniently map indices over packed registers.
|
||||
* The order in the variadic parameter pack will define the indexing
|
||||
* (starting at zero).
|
||||
*/
|
||||
template <typename... T>
|
||||
struct PackIndexing {
|
||||
|
||||
//! Tuple type.
|
||||
using tuple_t = typename std::tuple<T...>;
|
||||
|
||||
//! Number of elements.
|
||||
constexpr static const std::size_t n_elems =
|
||||
std::tuple_size<tuple_t>::value;
|
||||
|
||||
//! Element accessor.
|
||||
template <std::size_t N>
|
||||
using elem = typename std::tuple_element<N, tuple_t>::type;
|
||||
|
||||
};
|
||||
|
||||
|
||||
//! Template for loop implementation.
|
||||
/**
|
||||
* @tparam start Start index value.
|
||||
* @tparam end End index value.
|
||||
*/
|
||||
template <std::size_t start, std::size_t end>
|
||||
struct for_loop {
|
||||
|
||||
//! Loop method.
|
||||
/**
|
||||
* @tparam Func Function to be called at each iteration.
|
||||
*
|
||||
* This will call Op for the range [start, end).
|
||||
*/
|
||||
template <typename Func>
|
||||
static void apply() noexcept {
|
||||
Func().template operator()<start>();
|
||||
if (start < end)
|
||||
for_loop<start + 1ul, end>::template apply<Func>();
|
||||
};
|
||||
|
||||
#if __cplusplus >= 201402L
|
||||
//! Apply method.
|
||||
/**
|
||||
* @tparam Op Operator type to be called.
|
||||
*
|
||||
* This is only available with C++14 and up as this requires polymorphic
|
||||
* lambdas to be used in a somewhat useful manner.
|
||||
*
|
||||
* Typical example:
|
||||
* use lambda [](auto index) { index.value will be the loop index};
|
||||
*/
|
||||
template <typename Op>
|
||||
static void apply(Op&& f) noexcept {
|
||||
if (start < end) {
|
||||
f(std::integral_constant<std::size_t, start>{});
|
||||
for_loop<start + 1ul, end>::apply(std::forward<Op>(f));
|
||||
};
|
||||
};
|
||||
#endif // __cplusplus 201402L
|
||||
|
||||
};
|
||||
template <std::size_t end>
|
||||
struct for_loop<end, end> {
|
||||
template <typename Func>
|
||||
static void apply() noexcept {};
|
||||
#if __cplusplus >= 201402L
|
||||
template <typename Op>
|
||||
static void apply(Op&& f) noexcept {};
|
||||
#endif // __cplusplus 201402L
|
||||
};
|
||||
|
||||
|
||||
//! Template range loop implementation.
|
||||
/**
|
||||
* @tparam IndexedPack Indexed pack type.
|
||||
*/
|
||||
template <typename IndexedPack>
|
||||
struct pack_loop : for_loop<0, IndexedPack::n_elems> {};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // CPPREG_REGISTERPACK_H
|
||||
@@ -20,14 +20,12 @@ namespace cppreg {
|
||||
//! Shadow value generic implementation.
|
||||
/**
|
||||
* @tparam Register Register type.
|
||||
* @tparam UseShadow Boolean flag indicating if shadow value is required.
|
||||
* @tparam use_shadow 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;
|
||||
};
|
||||
template <typename Register, bool use_shadow>
|
||||
struct Shadow : std::false_type {};
|
||||
|
||||
|
||||
//! Shadow value implementation.
|
||||
@@ -35,16 +33,17 @@ namespace cppreg {
|
||||
* @tparam Register Register type.
|
||||
*
|
||||
* This implementation is for register which do require shadow value.
|
||||
*
|
||||
* See
|
||||
*/
|
||||
template <typename Register>
|
||||
struct Shadow<Register, true> {
|
||||
static typename Register::type value;
|
||||
constexpr static const bool use_shadow = true;
|
||||
struct Shadow<Register, true> : std::true_type {
|
||||
static typename Register::type shadow_value;
|
||||
};
|
||||
template <typename Register>
|
||||
typename Register::type Shadow<Register, true>::value = Register::reset;
|
||||
template <typename Register>
|
||||
const bool Shadow<Register, true>::use_shadow;
|
||||
typename Register::type Shadow<Register, true>::shadow_value =
|
||||
Register::reset;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -19,19 +19,39 @@
|
||||
namespace cppreg {
|
||||
|
||||
|
||||
//! Register data type default implementation.
|
||||
//! Register type traits based on size.
|
||||
/**
|
||||
* @tparam Size Register size.
|
||||
*
|
||||
* This will fail to compile if the register size is not implemented.
|
||||
* @tparam S Register size in bits.
|
||||
*/
|
||||
template <Width_t Size>
|
||||
struct RegisterType;
|
||||
template <RegBitSize S>
|
||||
struct TypeTraits;
|
||||
|
||||
//!@{ 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; };
|
||||
|
||||
//!@{ TypeTraits specializations.
|
||||
//! 8-bit specialization.
|
||||
template <> struct TypeTraits<RegBitSize::b8> {
|
||||
using type = std::uint8_t;
|
||||
constexpr static const std::uint8_t bit_size = 8u;
|
||||
constexpr static const std::uint8_t byte_size = bit_size / 8u;
|
||||
};
|
||||
//! 16-bit specialization.
|
||||
template <> struct TypeTraits<RegBitSize::b16> {
|
||||
using type = std::uint16_t;
|
||||
constexpr static const std::uint8_t bit_size = 16u;
|
||||
constexpr static const std::uint8_t byte_size = bit_size / 8u;
|
||||
};
|
||||
//! 32-bit specialization.
|
||||
template <> struct TypeTraits<RegBitSize::b32> {
|
||||
using type = std::uint32_t;
|
||||
constexpr static const std::uint8_t bit_size = 32u;
|
||||
constexpr static const std::uint8_t byte_size = bit_size / 8u;
|
||||
};
|
||||
//! 64-bit specialization.
|
||||
template <> struct TypeTraits<RegBitSize::b64> {
|
||||
using type = std::uint64_t;
|
||||
constexpr static const std::uint8_t bit_size = 64u;
|
||||
constexpr static const std::uint8_t byte_size = bit_size / 8u;
|
||||
};
|
||||
//!@}
|
||||
|
||||
|
||||
|
||||
@@ -8,59 +8,24 @@
|
||||
#include <cstdint>
|
||||
#include <type_traits>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
|
||||
// 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);
|
||||
};
|
||||
enum class RegBitSize {
|
||||
b8,
|
||||
b16,
|
||||
b32,
|
||||
b64
|
||||
};
|
||||
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);
|
||||
};
|
||||
using FieldWidth_t = std::uint8_t;
|
||||
using FieldOffset_t = std::uint8_t;
|
||||
template <typename T>
|
||||
struct type_mask {
|
||||
constexpr static const T value = std::numeric_limits<T>::max();
|
||||
};
|
||||
}
|
||||
#endif
|
||||
@@ -69,48 +34,214 @@ namespace cppreg {
|
||||
#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; };
|
||||
template <RegBitSize S>
|
||||
struct TypeTraits;
|
||||
template <> struct TypeTraits<RegBitSize::b8> {
|
||||
using type = std::uint8_t;
|
||||
constexpr static const std::uint8_t bit_size = 8u;
|
||||
constexpr static const std::uint8_t byte_size = bit_size / 8u;
|
||||
};
|
||||
template <> struct TypeTraits<RegBitSize::b16> {
|
||||
using type = std::uint16_t;
|
||||
constexpr static const std::uint8_t bit_size = 16u;
|
||||
constexpr static const std::uint8_t byte_size = bit_size / 8u;
|
||||
};
|
||||
template <> struct TypeTraits<RegBitSize::b32> {
|
||||
using type = std::uint32_t;
|
||||
constexpr static const std::uint8_t bit_size = 32u;
|
||||
constexpr static const std::uint8_t byte_size = bit_size / 8u;
|
||||
};
|
||||
template <> struct TypeTraits<RegBitSize::b64> {
|
||||
using type = std::uint64_t;
|
||||
constexpr static const std::uint8_t bit_size = 64u;
|
||||
constexpr static const std::uint8_t byte_size = bit_size / 8u;
|
||||
};
|
||||
}
|
||||
#endif
|
||||
|
||||
// Overflow.h
|
||||
#ifndef CPPREG_OVERFLOW_H
|
||||
#define CPPREG_OVERFLOW_H
|
||||
// Internals.h
|
||||
#ifndef CPPREG_INTERNALS_H
|
||||
#define CPPREG_INTERNALS_H
|
||||
namespace cppreg {
|
||||
namespace internals {
|
||||
template <
|
||||
Width_t W,
|
||||
typename RegisterType<W>::type value,
|
||||
typename RegisterType<W>::type limit
|
||||
typename T,
|
||||
T value,
|
||||
T limit
|
||||
>
|
||||
struct check_overflow {
|
||||
using result =
|
||||
typename std::integral_constant<bool, value <= limit>::type;
|
||||
struct check_overflow : std::integral_constant<bool, value <= limit> {};
|
||||
template <Address_t address, std::size_t alignment>
|
||||
struct is_aligned : std::integral_constant<
|
||||
bool,
|
||||
(address & (alignment - 1)) == 0
|
||||
> {
|
||||
};
|
||||
template <Address_t address, std::uint32_t n, RegBitSize reg_size>
|
||||
struct memory_map {
|
||||
using mem_array_t = std::array<
|
||||
volatile typename TypeTraits<reg_size>::type,
|
||||
n / sizeof(typename TypeTraits<reg_size>::type)
|
||||
>;
|
||||
static mem_array_t& array;
|
||||
static_assert(
|
||||
is_aligned<address, TypeTraits<reg_size>::byte_size>::value,
|
||||
"memory_map: base address is mis-aligned for register type"
|
||||
);
|
||||
};
|
||||
template <Address_t address, std::uint32_t n, RegBitSize reg_size>
|
||||
typename memory_map<address, n, reg_size>::mem_array_t&
|
||||
memory_map<address, n, reg_size>::array = *(
|
||||
reinterpret_cast<typename memory_map<address, n, reg_size>
|
||||
::mem_array_t*>(
|
||||
address
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// AccessPolicy.h
|
||||
#ifndef CPPREG_ACCESSPOLICY_H
|
||||
#define CPPREG_ACCESSPOLICY_H
|
||||
namespace cppreg {
|
||||
template <typename MMIO_t, typename T, T mask, FieldOffset_t offset>
|
||||
struct RegisterRead {
|
||||
constexpr static const bool is_trivial =
|
||||
(mask == type_mask<T>::value) && (offset == 0u);
|
||||
template <typename U = void>
|
||||
static T read(
|
||||
const MMIO_t& mmio_device,
|
||||
typename std::enable_if<!is_trivial, U*>::type = nullptr
|
||||
) noexcept {
|
||||
return static_cast<T>((mmio_device & mask) >> offset);
|
||||
};
|
||||
template <typename U = void>
|
||||
static T read(
|
||||
const MMIO_t& mmio_device,
|
||||
typename std::enable_if<is_trivial, U*>::type = nullptr
|
||||
) noexcept {
|
||||
return static_cast<T>(mmio_device);
|
||||
};
|
||||
};
|
||||
template <typename MMIO_t, typename T, T mask, FieldOffset_t offset>
|
||||
struct RegisterWrite {
|
||||
constexpr static const bool is_trivial =
|
||||
(mask == type_mask<T>::value) && (offset == 0u);
|
||||
template <typename U = void>
|
||||
static void write(
|
||||
MMIO_t& mmio_device,
|
||||
T value,
|
||||
typename std::enable_if<!is_trivial, U*>::type = nullptr
|
||||
) noexcept {
|
||||
mmio_device = static_cast<T>(
|
||||
(mmio_device & ~mask) | ((value << offset) & mask)
|
||||
);
|
||||
};
|
||||
template <typename U = void>
|
||||
static void write(
|
||||
MMIO_t& mmio_device,
|
||||
T value,
|
||||
typename std::enable_if<is_trivial, U*>::type = nullptr
|
||||
) noexcept {
|
||||
mmio_device = value;
|
||||
};
|
||||
};
|
||||
template <
|
||||
typename MMIO_t, typename T, T mask, FieldOffset_t offset, T value
|
||||
>
|
||||
struct RegisterWriteConstant {
|
||||
constexpr static const bool is_trivial =
|
||||
(mask == type_mask<T>::value) && (offset == 0u);
|
||||
template <typename U = void>
|
||||
static void write(
|
||||
MMIO_t& mmio_device,
|
||||
typename std::enable_if<!is_trivial, U*>::type = nullptr
|
||||
) noexcept {
|
||||
mmio_device = static_cast<T>(
|
||||
(mmio_device & ~mask) | ((value << offset) & mask)
|
||||
);
|
||||
};
|
||||
template <typename U = void>
|
||||
static void write(
|
||||
MMIO_t& mmio_device,
|
||||
typename std::enable_if<is_trivial, U*>::type = nullptr
|
||||
) noexcept {
|
||||
mmio_device = value;
|
||||
};
|
||||
};
|
||||
struct read_only {
|
||||
template <typename MMIO_t, typename T, T mask, FieldOffset_t offset>
|
||||
static T read(const MMIO_t& mmio_device) noexcept {
|
||||
return RegisterRead<MMIO_t, T, mask, offset>::read(mmio_device);
|
||||
};
|
||||
};
|
||||
struct read_write : read_only {
|
||||
template <typename MMIO_t, typename T, T mask, FieldOffset_t offset>
|
||||
static void write(MMIO_t& mmio_device,
|
||||
const T value) noexcept {
|
||||
RegisterWrite<MMIO_t, T, mask, offset>::write(mmio_device, value);
|
||||
};
|
||||
template <
|
||||
typename MMIO_t, typename T, T mask, FieldOffset_t offset, T value
|
||||
>
|
||||
static void write(MMIO_t& mmio_device) noexcept {
|
||||
RegisterWriteConstant<MMIO_t, T, mask, offset, value>
|
||||
::write(mmio_device);
|
||||
};
|
||||
template <typename MMIO_t, typename T, T mask>
|
||||
static void set(MMIO_t& mmio_device)
|
||||
noexcept {
|
||||
RegisterWriteConstant<MMIO_t, T, mask, 0u, mask>
|
||||
::write(mmio_device);
|
||||
};
|
||||
template <typename MMIO_t, typename T, T mask>
|
||||
static void clear(MMIO_t& mmio_device)
|
||||
noexcept {
|
||||
RegisterWriteConstant<MMIO_t, T, mask, 0u, ~mask>
|
||||
::write(mmio_device);
|
||||
};
|
||||
template <typename MMIO_t, typename T, T mask>
|
||||
static void toggle(MMIO_t& mmio_device)
|
||||
noexcept {
|
||||
mmio_device = static_cast<T>((mmio_device) ^ mask);
|
||||
};
|
||||
};
|
||||
struct write_only {
|
||||
template <typename MMIO_t, typename T, T mask, FieldOffset_t offset>
|
||||
static void write(MMIO_t& mmio_device, const T value) noexcept {
|
||||
RegisterWrite<MMIO_t, T, type_mask<T>::value, 0u>::write(
|
||||
mmio_device, ((value << offset) & mask)
|
||||
);
|
||||
};
|
||||
template <
|
||||
typename MMIO_t, typename T, T mask, FieldOffset_t offset, T value
|
||||
>
|
||||
static void write(MMIO_t& mmio_device) noexcept {
|
||||
RegisterWriteConstant<
|
||||
MMIO_t, T, type_mask<T>::value, 0u, ((value << offset) & mask)
|
||||
>
|
||||
::write(mmio_device);
|
||||
};
|
||||
};
|
||||
}
|
||||
#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 {
|
||||
constexpr Mask_t make_mask(const FieldWidth_t width) noexcept {
|
||||
return width == 0 ?
|
||||
0u
|
||||
:
|
||||
static_cast<Mask_t>(
|
||||
(make_mask<Mask_t>(Width_t(width - 1)) << 1) | 1
|
||||
(make_mask<Mask_t>(FieldWidth_t(width - 1)) << 1) | 1
|
||||
);
|
||||
};
|
||||
template <typename Mask_t>
|
||||
constexpr Mask_t make_shifted_mask(const Width_t width,
|
||||
const Offset_t offset) noexcept {
|
||||
constexpr Mask_t make_shifted_mask(const FieldWidth_t width,
|
||||
const FieldOffset_t offset) noexcept {
|
||||
return static_cast<Mask_t>(make_mask<Mask_t>(width) << offset);
|
||||
};
|
||||
}
|
||||
@@ -120,19 +251,15 @@ namespace cppreg {
|
||||
#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, bool use_shadow>
|
||||
struct Shadow : std::false_type {};
|
||||
template <typename Register>
|
||||
struct Shadow<Register, true> : std::true_type {
|
||||
static typename Register::type 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;
|
||||
typename Register::type Shadow<Register, true>::shadow_value =
|
||||
Register::reset;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -140,66 +267,115 @@ namespace cppreg {
|
||||
#ifndef CPPREG_MERGEWRITE_H
|
||||
#define CPPREG_MERGEWRITE_H
|
||||
namespace cppreg {
|
||||
template <typename Register>
|
||||
class MergeWrite {
|
||||
public:
|
||||
template <
|
||||
typename Register,
|
||||
typename Register::type mask,
|
||||
FieldOffset_t offset,
|
||||
typename Register::type value
|
||||
> class MergeWrite_tmpl {
|
||||
private:
|
||||
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;
|
||||
static_assert(!Register::shadow::value,
|
||||
"merge write is not available for shadow value register");
|
||||
constexpr static const base_type _accumulated_value =
|
||||
((value << offset) & mask);
|
||||
constexpr static const base_type _combined_mask = mask;
|
||||
MergeWrite_tmpl() = default;
|
||||
public:
|
||||
static MergeWrite_tmpl make() noexcept { return {}; };
|
||||
MergeWrite_tmpl(const MergeWrite_tmpl&) = delete;
|
||||
MergeWrite_tmpl& operator=(const MergeWrite_tmpl&) = delete;
|
||||
MergeWrite_tmpl& operator=(MergeWrite_tmpl&&) = delete;
|
||||
MergeWrite_tmpl operator=(MergeWrite_tmpl) = delete;
|
||||
MergeWrite_tmpl(MergeWrite_tmpl&&) = delete;
|
||||
void done() const && noexcept {
|
||||
typename Register::MMIO_t& mmio_device =
|
||||
Register::rw_mem_device();
|
||||
RegisterWriteConstant<
|
||||
typename Register::MMIO_t,
|
||||
typename Register::type,
|
||||
_combined_mask,
|
||||
0u,
|
||||
_accumulated_value
|
||||
>::write(mmio_device);
|
||||
};
|
||||
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 {
|
||||
template <
|
||||
typename F,
|
||||
base_type new_value,
|
||||
typename T = MergeWrite_tmpl<
|
||||
Register,
|
||||
(_combined_mask | F::mask),
|
||||
0u,
|
||||
(_accumulated_value & ~F::mask) | ((new_value << F::offset) &
|
||||
F::mask)
|
||||
>
|
||||
>
|
||||
typename std::enable_if<
|
||||
(internals::check_overflow<
|
||||
typename Register::type, new_value, (F::mask >> F::offset)
|
||||
>::value),
|
||||
T
|
||||
>::type&&
|
||||
with() const && 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);
|
||||
return std::move(T::make());
|
||||
};
|
||||
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);
|
||||
};
|
||||
template <
|
||||
typename Register,
|
||||
typename Register::type mask
|
||||
>
|
||||
class MergeWrite {
|
||||
public:
|
||||
using base_type = typename Register::type;
|
||||
private:
|
||||
constexpr static const base_type _combined_mask = mask;
|
||||
public:
|
||||
constexpr static MergeWrite make(const base_type value) noexcept {
|
||||
return MergeWrite(value);
|
||||
};
|
||||
MergeWrite(MergeWrite&& mw) noexcept
|
||||
: _accumulated_value(mw._accumulated_value) {};
|
||||
MergeWrite(const MergeWrite&) = delete;
|
||||
MergeWrite& operator=(const MergeWrite&) = delete;
|
||||
MergeWrite& operator=(MergeWrite&&) = delete;
|
||||
void done() const && noexcept {
|
||||
typename Register::MMIO_t& mmio_device =
|
||||
Register::rw_mem_device();
|
||||
RegisterWrite<
|
||||
typename Register::MMIO_t,
|
||||
base_type,
|
||||
_combined_mask,
|
||||
0u
|
||||
>::write(mmio_device, _accumulated_value);
|
||||
};
|
||||
template <typename F>
|
||||
MergeWrite<Register, _combined_mask | F::mask>
|
||||
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");
|
||||
_accumulated_value = (_accumulated_value & ~F::mask)
|
||||
| ((value << F::offset) & F::mask);
|
||||
return
|
||||
std::move(
|
||||
MergeWrite<Register, (_combined_mask | F::mask)>
|
||||
::make(_accumulated_value)
|
||||
);
|
||||
};
|
||||
private:
|
||||
static_assert(!Register::shadow::use_shadow,
|
||||
static_assert(!Register::shadow::value,
|
||||
"merge write is not available for shadow value register");
|
||||
MergeWrite() : _accumulated_value(0u),
|
||||
_combined_mask(0u) {};
|
||||
constexpr MergeWrite() : _accumulated_value(0u) {};
|
||||
constexpr explicit MergeWrite(const base_type v) :
|
||||
_accumulated_value(v) {};
|
||||
base_type _accumulated_value;
|
||||
base_type _combined_mask;
|
||||
};
|
||||
}
|
||||
#endif
|
||||
@@ -209,163 +385,265 @@ namespace cppreg {
|
||||
#define CPPREG_REGISTER_H
|
||||
namespace cppreg {
|
||||
template <
|
||||
Address_t RegAddress,
|
||||
Width_t RegWidth,
|
||||
typename RegisterType<RegWidth>::type ResetValue = 0x0,
|
||||
bool UseShadow = false
|
||||
Address_t reg_address,
|
||||
RegBitSize reg_size,
|
||||
typename TypeTraits<reg_size>::type reset_value = 0x0,
|
||||
bool use_shadow = false
|
||||
>
|
||||
struct Register {
|
||||
using type = typename RegisterType<RegWidth>::type;
|
||||
using type = typename TypeTraits<reg_size>::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);
|
||||
using shadow = Shadow<Register, use_shadow>;
|
||||
constexpr static const Address_t base_address = reg_address;
|
||||
constexpr static const std::uint8_t size =
|
||||
TypeTraits<reg_size>::bit_size;
|
||||
constexpr static const type reset = reset_value;
|
||||
static MMIO_t& rw_mem_device() {
|
||||
return *(reinterpret_cast<MMIO_t* const>(base_address));
|
||||
};
|
||||
static const MMIO_t* ro_mem_pointer() {
|
||||
return reinterpret_cast<const MMIO_t* const>(base_address);
|
||||
static const MMIO_t& ro_mem_device() {
|
||||
return *(reinterpret_cast<const MMIO_t* const>(base_address));
|
||||
};
|
||||
template <typename F>
|
||||
inline static MergeWrite<typename F::parent_register>
|
||||
static MergeWrite<typename F::parent_register, F::mask>
|
||||
merge_write(const typename F::type value) noexcept {
|
||||
return
|
||||
MergeWrite<typename F::parent_register>
|
||||
::create_instance(((value << F::offset) & F::mask), F::mask);
|
||||
MergeWrite<typename F::parent_register, F::mask>
|
||||
::make(((value << F::offset) & F::mask));
|
||||
};
|
||||
template <
|
||||
typename F,
|
||||
type value,
|
||||
typename T = MergeWrite<typename F::parent_register>
|
||||
typename T = MergeWrite_tmpl<
|
||||
typename F::parent_register,
|
||||
F::mask,
|
||||
F::offset,
|
||||
value
|
||||
>
|
||||
>
|
||||
inline static
|
||||
static
|
||||
typename std::enable_if<
|
||||
internals::check_overflow<
|
||||
size, value, (F::mask >> F::offset)
|
||||
>::result::value,
|
||||
type, value, (F::mask >> F::offset)
|
||||
>::value,
|
||||
T
|
||||
>::type
|
||||
>::type&&
|
||||
merge_write() noexcept {
|
||||
return
|
||||
MergeWrite<typename F::parent_register>
|
||||
::create_instance(((value << F::offset) & F::mask), F::mask);
|
||||
return std::move(T::make());
|
||||
};
|
||||
static_assert(RegWidth != 0u,
|
||||
"defining a Register type of width 0u is not allowed");
|
||||
static_assert(size != 0u,
|
||||
"Register: register definition with zero size");
|
||||
static_assert(
|
||||
internals::is_aligned<reg_address, TypeTraits<reg_size>::byte_size>
|
||||
::value,
|
||||
"Register: address is mis-aligned for register type"
|
||||
);
|
||||
};
|
||||
}
|
||||
#endif
|
||||
|
||||
// RegisterPack.h
|
||||
#ifndef CPPREG_REGISTERPACK_H
|
||||
#define CPPREG_REGISTERPACK_H
|
||||
namespace cppreg {
|
||||
template <
|
||||
Address_t base_address,
|
||||
std::uint32_t pack_byte_size
|
||||
> struct RegisterPack {
|
||||
constexpr static const Address_t pack_base = base_address;
|
||||
constexpr static const std::uint32_t size_in_bytes = pack_byte_size;
|
||||
};
|
||||
template <
|
||||
typename RegisterPack,
|
||||
RegBitSize reg_size,
|
||||
std::uint32_t bit_offset,
|
||||
typename TypeTraits<reg_size>::type reset_value = 0x0,
|
||||
bool use_shadow = false
|
||||
>
|
||||
struct PackedRegister : Register<
|
||||
RegisterPack::pack_base + (bit_offset / 8u),
|
||||
reg_size,
|
||||
reset_value,
|
||||
use_shadow
|
||||
> {
|
||||
using base_reg = Register<
|
||||
RegisterPack::pack_base + (bit_offset / 8u),
|
||||
reg_size,
|
||||
reset_value,
|
||||
use_shadow
|
||||
>;
|
||||
using mem_map_t = internals::memory_map<
|
||||
RegisterPack::pack_base,
|
||||
RegisterPack::size_in_bytes,
|
||||
reg_size
|
||||
>;
|
||||
static typename base_reg::MMIO_t& rw_mem_device() noexcept {
|
||||
return mem_map_t::array[bit_offset
|
||||
/ TypeTraits<reg_size>::bit_size];
|
||||
};
|
||||
static const typename base_reg::MMIO_t& ro_mem_device() noexcept {
|
||||
return mem_map_t::array[bit_offset
|
||||
/ TypeTraits<reg_size>::bit_size];
|
||||
};
|
||||
static_assert(
|
||||
TypeTraits<reg_size>::byte_size + (bit_offset / 8u) <=
|
||||
RegisterPack::size_in_bytes,
|
||||
"PackRegister: packed register is overflowing the pack"
|
||||
);
|
||||
static_assert(
|
||||
internals::is_aligned<
|
||||
RegisterPack::pack_base,
|
||||
TypeTraits<reg_size>::byte_size
|
||||
>::value,
|
||||
"PackedRegister: pack base address is mis-aligned for register type"
|
||||
);
|
||||
static_assert(
|
||||
internals::is_aligned<
|
||||
RegisterPack::pack_base + (bit_offset / 8u),
|
||||
TypeTraits<reg_size>::byte_size
|
||||
>::value,
|
||||
"PackedRegister: offset address is mis-aligned for register type"
|
||||
);
|
||||
};
|
||||
template <typename... T>
|
||||
struct PackIndexing {
|
||||
using tuple_t = typename std::tuple<T...>;
|
||||
constexpr static const std::size_t n_elems =
|
||||
std::tuple_size<tuple_t>::value;
|
||||
template <std::size_t N>
|
||||
using elem = typename std::tuple_element<N, tuple_t>::type;
|
||||
};
|
||||
template <std::size_t start, std::size_t end>
|
||||
struct for_loop {
|
||||
template <typename Func>
|
||||
static void apply() noexcept {
|
||||
Func().template operator()<start>();
|
||||
if (start < end)
|
||||
for_loop<start + 1ul, end>::template apply<Func>();
|
||||
};
|
||||
#if __cplusplus >= 201402L
|
||||
template <typename Op>
|
||||
static void apply(Op&& f) noexcept {
|
||||
if (start < end) {
|
||||
f(std::integral_constant<std::size_t, start>{});
|
||||
for_loop<start + 1ul, end>::apply(std::forward<Op>(f));
|
||||
};
|
||||
};
|
||||
#endif
|
||||
};
|
||||
template <std::size_t end>
|
||||
struct for_loop<end, end> {
|
||||
template <typename Func>
|
||||
static void apply() noexcept {};
|
||||
#if __cplusplus >= 201402L
|
||||
template <typename Op>
|
||||
static void apply(Op&& f) noexcept {};
|
||||
#endif
|
||||
};
|
||||
template <typename IndexedPack>
|
||||
struct pack_loop : for_loop<0, IndexedPack::n_elems> {};
|
||||
}
|
||||
#endif
|
||||
|
||||
// Field.h
|
||||
#ifndef CPPREG_REGISTERFIELD_H
|
||||
#define CPPREG_REGISTERFIELD_H
|
||||
namespace cppreg {
|
||||
template <
|
||||
typename BaseRegister,
|
||||
Width_t FieldWidth,
|
||||
Offset_t FieldOffset,
|
||||
FieldWidth_t field_width,
|
||||
FieldOffset_t field_offset,
|
||||
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 FieldWidth_t width = field_width;
|
||||
constexpr static const FieldOffset_t offset = field_offset;
|
||||
constexpr static const type mask = make_shifted_mask<type>(width,
|
||||
offset);
|
||||
constexpr static const bool has_shadow =
|
||||
parent_register::shadow::value;
|
||||
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);
|
||||
struct check_overflow : internals::check_overflow<
|
||||
type,
|
||||
value,
|
||||
(mask >> offset)
|
||||
> {};
|
||||
static type read() noexcept {
|
||||
return policy::template read<MMIO_t, type, mask, offset>(
|
||||
parent_register::ro_mem_device()
|
||||
);
|
||||
};
|
||||
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);
|
||||
static void
|
||||
write(const typename std::enable_if<!has_shadow, T>::type value)
|
||||
noexcept {
|
||||
policy::template write<MMIO_t, type, mask, offset>(
|
||||
parent_register::rw_mem_device(),
|
||||
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);
|
||||
static void
|
||||
write(const typename std::enable_if<has_shadow, T>::type value)
|
||||
noexcept {
|
||||
RegisterWrite<type, type, mask, offset>
|
||||
::write(parent_register::shadow::shadow_value, value);
|
||||
policy::template write<MMIO_t, type, type_mask<type>::value, 0u>(
|
||||
parent_register::rw_mem_device(),
|
||||
parent_register::shadow::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);
|
||||
static void write(
|
||||
typename std::enable_if<
|
||||
!has_shadow
|
||||
&&
|
||||
check_overflow<value>::value,
|
||||
T
|
||||
>::type* = nullptr
|
||||
) noexcept {
|
||||
policy::template write<MMIO_t, type, mask, offset, value>(
|
||||
parent_register::rw_mem_device()
|
||||
);
|
||||
};
|
||||
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 {
|
||||
static void write(
|
||||
typename std::enable_if<
|
||||
has_shadow
|
||||
&&
|
||||
check_overflow<value>::value,
|
||||
T
|
||||
>::type* = nullptr
|
||||
) noexcept {
|
||||
write(value);
|
||||
};
|
||||
inline static void set() noexcept {
|
||||
AccessPolicy
|
||||
::template set<MMIO_t>(parent_register::rw_mem_pointer(), mask);
|
||||
static void set() noexcept {
|
||||
policy::template
|
||||
set<MMIO_t, type, mask>(parent_register::rw_mem_device());
|
||||
};
|
||||
inline static void clear() noexcept {
|
||||
AccessPolicy
|
||||
::template clear<MMIO_t>(parent_register::rw_mem_pointer(), mask);
|
||||
static void clear() noexcept {
|
||||
policy::template
|
||||
clear<MMIO_t, type, mask>(parent_register::rw_mem_device());
|
||||
};
|
||||
inline static void toggle() noexcept {
|
||||
AccessPolicy
|
||||
::template toggle<MMIO_t>(parent_register::rw_mem_pointer(), mask);
|
||||
static void toggle() noexcept {
|
||||
policy::template
|
||||
toggle<MMIO_t, type, mask>(parent_register::rw_mem_device());
|
||||
};
|
||||
template <typename T = bool>
|
||||
inline static typename std::enable_if<FieldWidth == 1, T>::type
|
||||
is_set() noexcept {
|
||||
return (Field::read() == 1u);
|
||||
static bool is_set() noexcept {
|
||||
return (Field::read() == (mask >> offset));
|
||||
};
|
||||
template <typename T = bool>
|
||||
inline static typename std::enable_if<FieldWidth == 1, T>::type
|
||||
is_clear() noexcept {
|
||||
static bool 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,
|
||||
static_assert(width != 0u,
|
||||
"defining a Field type of width 0u is not allowed");
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user