1
0
mirror of https://github.com/sendyne/cppreg.git synced 2025-05-11 05:33:52 +00:00

API REVISED FOR BETTER READ/WRITE PERFORMANCE

* For trivial read/write operations (over whole register) simpler implementations are now used to avoid unnecessary load/store instructions.
* Merge write implementation has been revised to provide better performance when manipulating constant values.
* `is_set` and `is_clear` are now available for all Field-based types.

This closes #8.
This commit is contained in:
Nicolas Clauvelin 2018-03-09 10:32:40 -05:00 committed by Nicolas Clauvelin
parent 1c27f907a5
commit eb9226fe5d
8 changed files with 732 additions and 303 deletions

View File

@ -47,6 +47,18 @@ namespace cppreg {
using Offset_t = std::uint8_t; using Offset_t = std::uint8_t;
//! Shorthand for max value as a mask.
/**
* @tparam T Data type.
*
* This is used to define register masks.
*/
template <typename T>
struct type_mask {
constexpr static const T value = std::numeric_limits<T>::max();
};
} }

View File

@ -11,6 +11,7 @@
#include <cstdint> #include <cstdint>
#include <limits>
#endif // CPPREG_CPPREG_INCLUDES_H #endif // CPPREG_CPPREG_INCLUDES_H

View File

@ -1,4 +1,4 @@
//! Access policy abstract implementation. //! Access policy implementation.
/** /**
* @file AccessPolicy.h * @file AccessPolicy.h
* @author Nicolas Clauvelin (nclauvelin@sendyne.com) * @author Nicolas Clauvelin (nclauvelin@sendyne.com)
@ -6,6 +6,17 @@
* *
* Access policies are used to describe if register fields are read-write, * Access policies are used to describe if register fields are read-write,
* read-only or write-only. * 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,6 +31,167 @@
namespace cppreg { 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, Offset_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>
inline static T read(
const MMIO_t* const 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>
inline static T read(
const MMIO_t* const 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, Offset_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>
inline static void write(
MMIO_t* const 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>
inline static void write(
MMIO_t* const 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, Offset_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>
inline static void write(
MMIO_t* const 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>
inline static void write(
MMIO_t* const mmio_device,
typename std::enable_if<is_trivial, U*>::type = nullptr
) noexcept {
*mmio_device = value;
};
};
//! Read-only access policy. //! Read-only access policy.
struct read_only { struct read_only {
@ -27,16 +199,14 @@ namespace cppreg {
/** /**
* @tparam MMIO_t Register memory device type. * @tparam MMIO_t Register memory device type.
* @tparam T Field data type. * @tparam T Field data type.
* @tparam mask Field mask.
* @tparam offset Field offset.
* @param mmio_device Pointer to register mapped memory. * @param mmio_device Pointer to register mapped memory.
* @param mask Field mask.
* @param offset Field offset.
* @return The value at the field location. * @return The value at the field location.
*/ */
template <typename MMIO_t, typename T> template <typename MMIO_t, typename T, T mask, Offset_t offset>
inline static T read(const MMIO_t* const mmio_device, inline static T read(const MMIO_t* const mmio_device) noexcept {
const T mask, return RegisterRead<MMIO_t, T, mask, offset>::read(mmio_device);
const Offset_t offset) noexcept {
return static_cast<T>((*mmio_device & mask) >> offset);
}; };
}; };
@ -49,54 +219,68 @@ namespace cppreg {
/** /**
* @tparam MMIO_t Register memory device type. * @tparam MMIO_t Register memory device type.
* @tparam T Field data type. * @tparam T Field data type.
* @tparam mask Field mask.
* @tparam offset Field offset.
* @param mmio_device Pointer to register mapped memory. * @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. * @param value Value to be written at the field location.
*/ */
template <typename MMIO_t, typename T> template <typename MMIO_t, typename T, T mask, Offset_t offset>
inline static void write(MMIO_t* const mmio_device, inline static void write(MMIO_t* const mmio_device,
const T mask,
const Offset_t offset,
const T value) noexcept { const T value) noexcept {
*mmio_device = static_cast<T>((*mmio_device & ~mask) | RegisterWrite<MMIO_t, T, mask, offset>::write(mmio_device, value);
((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, Offset_t offset, T value>
inline static void write(MMIO_t* const mmio_device) noexcept {
RegisterWriteConstant<MMIO_t, T, mask, offset, value>
::write(mmio_device);
}; };
//! Set field implementation. //! Set field implementation.
/** /**
* @tparam T Field data type. * @tparam T Field data type.
* @tparam mask Field mask.
* @param mmio_device Pointer to register mapped memory. * @param mmio_device Pointer to register mapped memory.
* @param mask Field mask.
*/ */
template <typename MMIO_t, typename T> template <typename MMIO_t, typename T, T mask>
inline static void set(MMIO_t* const mmio_device, const T mask) inline static void set(MMIO_t* const mmio_device)
noexcept { noexcept {
*mmio_device = static_cast<T>((*mmio_device) | mask); RegisterWriteConstant<MMIO_t, T, mask, 0u, mask>
::write(mmio_device);
}; };
//! Clear field implementation. //! Clear field implementation.
/** /**
* @tparam MMIO_t Register memory device type. * @tparam MMIO_t Register memory device type.
* @tparam T Field data type. * @tparam T Field data type.
* @tparam mask Field mask.
* @param mmio_device Pointer to register mapped memory. * @param mmio_device Pointer to register mapped memory.
* @param mask Field mask.
*/ */
template <typename MMIO_t, typename T> template <typename MMIO_t, typename T, T mask>
inline static void clear(MMIO_t* const mmio_device, const T mask) inline static void clear(MMIO_t* const mmio_device)
noexcept { noexcept {
*mmio_device = static_cast<T>((*mmio_device) & ~mask); RegisterWriteConstant<MMIO_t, T, mask, 0u, ~mask>
::write(mmio_device);
}; };
//! Toggle field implementation. //! Toggle field implementation.
/** /**
* @tparam MMIO_t Register memory device type. * @tparam MMIO_t Register memory device type.
* @tparam T Field data type. * @tparam T Field data type.
* @tparam mask Field mask.
* @param mmio_device Pointer to register mapped memory. * @param mmio_device Pointer to register mapped memory.
* @param mask Field mask.
*/ */
template <typename MMIO_t, typename T> template <typename MMIO_t, typename T, T mask>
inline static void toggle(MMIO_t* const mmio_device, const T mask) inline static void toggle(MMIO_t* const mmio_device)
noexcept { noexcept {
*mmio_device = static_cast<T>((*mmio_device) ^ mask); *mmio_device = static_cast<T>((*mmio_device) ^ mask);
}; };
@ -111,19 +295,38 @@ namespace cppreg {
/** /**
* @tparam MMIO_t Register memory device type. * @tparam MMIO_t Register memory device type.
* @tparam T Field data type. * @tparam T Field data type.
* @tparam mask Field mask.
* @tparam offset Field offset
* @param mmio_device Pointer to register mapped memory. * @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. * @param value Value to be written at the field location.
*/ */
template <typename MMIO_t, typename T> template <typename MMIO_t, typename T, T mask, Offset_t offset>
inline static void write(MMIO_t* const mmio_device, inline static void write(MMIO_t* const mmio_device,
const T mask,
const Offset_t offset,
const T value) noexcept { const T value) noexcept {
// We cannot read the current value so we simply fully write to it. // For write-only fields we can only write to the whole register.
*mmio_device = ((value << offset) & mask); 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, Offset_t offset, T value>
inline static void write(MMIO_t* const 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);
}; };

View File

@ -6,6 +6,7 @@
* *
* This header provides the definitions related to register field * This header provides the definitions related to register field
* implementation. * implementation.
* Strictly speaking a field is defined as a region of a register.
*/ */
@ -27,7 +28,7 @@ namespace cppreg {
* @tparam BaseRegister Parent register. * @tparam BaseRegister Parent register.
* @tparam width Field width. * @tparam width Field width.
* @tparam offset Field offset. * @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 * This data structure provides static methods to deal with field access
* (read, write, set, clear, and toggle). These methods availability depends * (read, write, set, clear, and toggle). These methods availability depends
@ -53,26 +54,29 @@ namespace cppreg {
//! MMIO type. //! MMIO type.
using MMIO_t = typename parent_register::MMIO_t; using MMIO_t = typename parent_register::MMIO_t;
//! Field policy.
using policy = AccessPolicy;
//! Field width. //! Field width.
constexpr static const Width_t width = FieldWidth; constexpr static const Width_t width = FieldWidth;
//! Field offset. //! Field offset.
constexpr static const Offset_t offset = FieldOffset; constexpr static const Offset_t offset = FieldOffset;
//! Field policy.
using policy = AccessPolicy;
//! Field mask. //! Field mask.
/**
* The field mask is computed at compile time.
*/
constexpr static const type mask = make_shifted_mask<type>(width, constexpr static const type mask = make_shifted_mask<type>(width,
offset); offset);
//! Boolean flag indicating if a shadow value is used.
constexpr static const bool has_shadow =
parent_register::shadow::use_shadow;
//! Customized overflow check implementation for Field types. //! Customized overflow check implementation for Field types.
/** /**
* @tparam value Value to be checked for overflow. * @tparam value Value to be checked for overflow.
* @return `true` if no overflow, `false` otherwise. * @return `true` if no overflow, `false` otherwise.
*
* This is only used for the template form of the write method.
*/ */
template <type value> template <type value>
struct check_overflow { struct check_overflow {
@ -84,125 +88,104 @@ namespace cppreg {
>::result::value; >::result::value;
}; };
//! Field read method. //!@ Field read method.
/** /**
* @return Field value. * @return Field value.
*/ */
inline static type read() noexcept { inline static type read() noexcept {
return return policy::template read<MMIO_t, type, mask, offset>(
AccessPolicy::read(parent_register::ro_mem_pointer(), parent_register::ro_mem_pointer()
mask, );
offset);
}; };
//! Field write method (shadow value disabled). //! Field write method (no shadow value).
/** /**
* @param value Value to be written to the field. * @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> template <typename T = type>
inline static void inline static void
write(const typename std::enable_if< write(const typename std::enable_if<!has_shadow, T>::type value)
!parent_register::shadow::use_shadow, T noexcept {
>::type value) noexcept { policy::template write<MMIO_t, type, mask, offset>(
AccessPolicy::write(parent_register::rw_mem_pointer(), parent_register::rw_mem_pointer(),
mask, value
offset, );
value);
}; };
//! Field write method (shadow value enabled). //! Field write method (w/ shadow value).
/** /**
* @param value Value to be written to the field. * @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> template <typename T = type>
inline static void inline static void
write(const typename std::enable_if< write(const typename std::enable_if<has_shadow, T>::type value)
parent_register::shadow::use_shadow, T noexcept {
>::type value) noexcept {
// Update shadow value. // Update shadow value.
// Fetch the whole register content.
// This assumes that reading a write-only fields return some value. // This assumes that reading a write-only fields return some value.
parent_register::shadow::value = RegisterWrite<type, type, mask, offset>
(parent_register::shadow::value & ~mask) | ::write(&parent_register::shadow::value, value);
((value << offset) & mask);
// Write as a block to the register, that is, we do not use the // Write as a block to the register, that is, we do not use the
// mask and offset. // mask and offset.
AccessPolicy::write(parent_register::rw_mem_pointer(), policy::template write<MMIO_t, type, type_mask<type>::value, 0u>(
~(0u), parent_register::rw_mem_pointer(),
0u, parent_register::shadow::value
parent_register::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 * @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 * 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> template <type value, typename T = void>
inline static inline static
typename std::enable_if< typename std::enable_if<
(!parent_register::shadow::use_shadow) !has_shadow
&& &&
check_overflow<value>::result, check_overflow<value>::result,
T T
>::type >::type
write() noexcept { write() noexcept {
write(value); policy::template write<MMIO_t, type, mask, offset, value>(
parent_register::rw_mem_pointer()
);
}; };
//! 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 * @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 * 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> template <type value, typename T = void>
inline static inline static
typename std::enable_if< typename std::enable_if<
parent_register::shadow::use_shadow has_shadow
&& &&
check_overflow<value>::result, check_overflow<value>::result,
T T
>::type >::type
write() noexcept { write() noexcept {
write(value);
};
// For this particular we can simply forward to the non-constant
// implementation.
write(value);
};
//! Field set method. //! Field set method.
/** /**
* This method will set all bits in the field. * This method will set all bits in the field.
*/ */
inline static void set() noexcept { inline static void set() noexcept {
AccessPolicy::set(parent_register::rw_mem_pointer(), mask); policy::template
set<MMIO_t, type, mask>(parent_register::rw_mem_pointer());
}; };
//! Field clear method. //! Field clear method.
@ -210,7 +193,8 @@ namespace cppreg {
* This method will clear all bits in the field. * This method will clear all bits in the field.
*/ */
inline static void clear() noexcept { inline static void clear() noexcept {
AccessPolicy::clear(parent_register::rw_mem_pointer(), mask); policy::template
clear<MMIO_t, type, mask>(parent_register::rw_mem_pointer());
}; };
//! Field toggle method. //! Field toggle method.
@ -218,30 +202,23 @@ namespace cppreg {
* This method will toggle all bits in the field. * This method will toggle all bits in the field.
*/ */
inline static void toggle() noexcept { inline static void toggle() noexcept {
AccessPolicy::toggle(parent_register::rw_mem_pointer(), mask); policy::template
toggle<MMIO_t, type, mask>(parent_register::rw_mem_pointer());
}; };
//! Is field set bool method. //! Is field set bool method.
/** /**
* @return `true` if the 1-bit field is set to 1, `false` otherwise. * @return `true` if all the bits are set to 1, `false` otherwise.
*
* This is only available if the field is 1 bit wide.
*/ */
template <typename T = bool> inline static bool is_set() noexcept {
inline static typename std::enable_if<FieldWidth == 1, T>::type return (Field::read() == (mask >> offset));
is_set() noexcept {
return (Field::read() == 1u);
}; };
//! Is field clear bool method. //! Is field clear bool method.
/** /**
* @return `true` if the 1-bit field is set to 0, `false` otherwise. * @return `true` if all the bits are set to 0, `false` otherwise.
*
* This is only available if the field is 1 bit wide.
*/ */
template <typename T = bool> inline static bool is_clear() noexcept {
inline static typename std::enable_if<FieldWidth == 1, T>::type
is_clear() noexcept {
return (Field::read() == 0u); return (Field::read() == 0u);
}; };

View File

@ -6,6 +6,11 @@
* *
* The "merge write" implementation is designed to make it possible to merge * The "merge write" implementation is designed to make it possible to merge
* write operations on different fields into a single one. * 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.
*/ */
@ -22,12 +27,28 @@
namespace cppreg { 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 <
class MergeWrite { typename Register,
typename Register::type mask,
Offset_t offset,
typename Register::type value
> class MergeWrite_tmpl {
public: public:
@ -35,31 +56,42 @@ namespace cppreg {
//! Type alias to register base type. //! Type alias to register base type.
using base_type = typename Register::type; using base_type = typename Register::type;
//! Static instantiation method.
static MergeWrite create_instance(const base_type value,
const base_type mask) noexcept {
MergeWrite mw;
mw._accumulated_value = value;
mw._combined_mask = mask;
return mw;
};
//!@ Move constructor. private:
MergeWrite(MergeWrite&& mw) noexcept
: _accumulated_value(mw._accumulated_value),
_combined_mask(mw._combined_mask) {
};
//!@{ Non-copyable. // Disabled for shadow value register.
MergeWrite(const MergeWrite&) = delete; static_assert(!Register::shadow::use_shadow,
MergeWrite& operator=(const MergeWrite&) = delete; "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() {};
public:
//! Instantiation method.
inline 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;
//!@} //!@}
//! Destructor. //! Closure method.
/** /**
* This is where the write operation is performed. * This is where the write happens.
*/ */
~MergeWrite() { inline void done() const && noexcept {
// Get memory pointer. // Get memory pointer.
typename Register::MMIO_t* const mmio_device = typename Register::MMIO_t* const mmio_device =
@ -68,10 +100,111 @@ namespace cppreg {
// Write to the whole register using the current accumulated value // Write to the whole register using the current accumulated value
// and combined mask. // and combined mask.
// No offset needed because we write to the whole register. // No offset needed because we write to the whole register.
*mmio_device = static_cast<base_type>( RegisterWriteConstant<
(*mmio_device & ~_combined_mask) | typename Register::MMIO_t,
((_accumulated_value) & _combined_mask) 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)
>
>
inline
typename std::enable_if<
(internals::check_overflow<
Register::size, new_value, (F::mask >> F::offset)
>::result::value),
T
>::type&&
with() const && noexcept {
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 {
public:
//! 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.
constexpr static MergeWrite make(const base_type value) noexcept {
return MergeWrite(value);
};
//!@ Move constructor.
MergeWrite(MergeWrite&& mw) noexcept
: _accumulated_value(mw._accumulated_value) {};
//!@{ Non-copyable.
MergeWrite(const MergeWrite&) = delete;
MergeWrite& operator=(const MergeWrite&) = delete;
MergeWrite& operator=(MergeWrite&&) = delete;
//!@}
//! Closure method.
/**
* This is where the write happens.
*/
inline void done() const && noexcept {
// Get memory pointer.
typename Register::MMIO_t* const mmio_device =
Register::rw_mem_pointer();
// Write to the whole register using the current accumulated value
// and combined mask.
// No offset needed because we write to the whole register.
RegisterWrite<
typename Register::MMIO_t,
base_type,
_combined_mask,
0u
>::write(mmio_device, _accumulated_value);
}; };
@ -80,12 +213,10 @@ namespace cppreg {
* @tparam F Field type describing where to write in the register. * @tparam F Field type describing where to write in the register.
* @param value Value to write to the register. * @param value Value to write to the register.
* @return A reference to the current merge write data. * @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> template <typename F>
MergeWrite&& with(const base_type value) && noexcept { inline MergeWrite<Register, _combined_mask | F::mask> with
(const base_type value) && noexcept {
// Check that the field belongs to the register. // Check that the field belongs to the register.
static_assert(std::is_same< static_assert(std::is_same<
@ -95,43 +226,19 @@ namespace cppreg {
"field is not from the same register in merge_write"); "field is not from the same register in merge_write");
// Update accumulated value. // Update accumulated value.
F::policy::write(&_accumulated_value, F::policy::template write<
F::mask, base_type,
F::offset, base_type,
value); F::mask,
F::offset
>(&_accumulated_value, value);
// Update combine mask. return
_combined_mask = _combined_mask | F::mask; 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);
}; };
@ -142,15 +249,12 @@ namespace cppreg {
"merge write is not available for shadow value register"); "merge write is not available for shadow value register");
// Private default constructor. // Private default constructor.
MergeWrite() : _accumulated_value(0u), constexpr MergeWrite() : _accumulated_value(0u) {};
_combined_mask(0u) {}; constexpr MergeWrite(const base_type v) : _accumulated_value(v) {};
// Accumulated value. // Accumulated value.
base_type _accumulated_value; base_type _accumulated_value;
// Combined mask.
base_type _combined_mask;
}; };

View File

@ -76,30 +76,35 @@ namespace cppreg {
return reinterpret_cast<const MMIO_t* const>(base_address); 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. * @tparam F Field on which to perform the first write operation.
* @param value Value to write to the field. * @param value Value to be written to the field.
* @return A merge write data structure. * @return A merge write data structure to chain further writes.
*/ */
template <typename F> template <typename F>
inline static MergeWrite<typename F::parent_register> inline static MergeWrite<typename F::parent_register, F::mask>
merge_write(const typename F::type value) noexcept { merge_write(const typename F::type value) noexcept {
return return
MergeWrite<typename F::parent_register> MergeWrite<typename F::parent_register, F::mask>
::create_instance(((value << F::offset) & F::mask), 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. * @tparam F Field on which to perform the first write operation.
* @param value Value to write to the field. * @tparam value Value to be written to the field.
* @return A merge write data structure. * @return A merge write data structure to chain further writes.
*/ */
template < template <
typename F, typename F,
type value, type value,
typename T = MergeWrite<typename F::parent_register> typename T = MergeWrite_tmpl<
typename F::parent_register,
F::mask,
F::offset,
value
>
> >
inline static inline static
typename std::enable_if< typename std::enable_if<
@ -107,11 +112,9 @@ namespace cppreg {
size, value, (F::mask >> F::offset) size, value, (F::mask >> F::offset)
>::result::value, >::result::value,
T T
>::type >::type&&
merge_write() noexcept { merge_write() noexcept {
return return std::move(T::make());
MergeWrite<typename F::parent_register>
::create_instance(((value << F::offset) & F::mask), F::mask);
}; };
// Sanity check. // Sanity check.

View File

@ -49,6 +49,7 @@ namespace cppreg {
template <typename Register> template <typename Register>
constexpr const bool Shadow<Register, true>::use_shadow; constexpr const bool Shadow<Register, true>::use_shadow;
} }

View File

@ -8,6 +8,7 @@
#include <cstdint> #include <cstdint>
#include <type_traits> #include <type_traits>
#include <functional> #include <functional>
#include <limits>
// cppreg_Defines.h // cppreg_Defines.h
#ifndef CPPREG_CPPREG_DEFINES_H #ifndef CPPREG_CPPREG_DEFINES_H
@ -16,6 +17,10 @@ namespace cppreg {
using Address_t = std::uintptr_t; using Address_t = std::uintptr_t;
using Width_t = std::uint8_t; using Width_t = std::uint8_t;
using Offset_t = std::uint8_t; using Offset_t = std::uint8_t;
template <typename T>
struct type_mask {
constexpr static const T value = std::numeric_limits<T>::max();
};
} }
#endif #endif
@ -23,46 +28,118 @@ namespace cppreg {
#ifndef CPPREG_ACCESSPOLICY_H #ifndef CPPREG_ACCESSPOLICY_H
#define CPPREG_ACCESSPOLICY_H #define CPPREG_ACCESSPOLICY_H
namespace cppreg { namespace cppreg {
struct read_only { template <typename MMIO_t, typename T, T mask, Offset_t offset>
template <typename MMIO_t, typename T> struct RegisterRead {
inline static T read(const MMIO_t* const mmio_device, constexpr static const bool is_trivial =
const T mask, (mask == type_mask<T>::value) && (offset == 0u);
const Offset_t offset) noexcept { template <typename U = void>
inline static T read(
const MMIO_t* const mmio_device,
typename std::enable_if<!is_trivial, U*>::type = nullptr
) noexcept {
return static_cast<T>((*mmio_device & mask) >> offset); return static_cast<T>((*mmio_device & mask) >> offset);
}; };
template <typename U = void>
inline static T read(
const MMIO_t* const 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, Offset_t offset>
struct RegisterWrite {
constexpr static const bool is_trivial =
(mask == type_mask<T>::value) && (offset == 0u);
template <typename U = void>
inline static void write(
MMIO_t* const 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>
inline static void write(
MMIO_t* const 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, Offset_t offset, T value>
struct RegisterWriteConstant {
constexpr static const bool is_trivial =
(mask == type_mask<T>::value) && (offset == 0u);
template <typename U = void>
inline static void write(
MMIO_t* const 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>
inline static void write(
MMIO_t* const 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, Offset_t offset>
inline static T read(const MMIO_t* const mmio_device) noexcept {
return RegisterRead<MMIO_t, T, mask, offset>::read(mmio_device);
};
}; };
struct read_write : read_only { struct read_write : read_only {
template <typename MMIO_t, typename T> template <typename MMIO_t, typename T, T mask, Offset_t offset>
inline static void write(MMIO_t* const mmio_device, inline static void write(MMIO_t* const mmio_device,
const T mask,
const Offset_t offset,
const T value) noexcept { const T value) noexcept {
*mmio_device = static_cast<T>((*mmio_device & ~mask) | RegisterWrite<MMIO_t, T, mask, offset>::write(mmio_device, value);
((value << offset) & mask));
}; };
template <typename MMIO_t, typename T> template <typename MMIO_t, typename T, T mask, Offset_t offset, T value>
inline static void set(MMIO_t* const mmio_device, const T mask) inline static void write(MMIO_t* const mmio_device) noexcept {
RegisterWriteConstant<MMIO_t, T, mask, offset, value>
::write(mmio_device);
};
template <typename MMIO_t, typename T, T mask>
inline static void set(MMIO_t* const mmio_device)
noexcept { noexcept {
*mmio_device = static_cast<T>((*mmio_device) | mask); RegisterWriteConstant<MMIO_t, T, mask, 0u, mask>
::write(mmio_device);
}; };
template <typename MMIO_t, typename T> template <typename MMIO_t, typename T, T mask>
inline static void clear(MMIO_t* const mmio_device, const T mask) inline static void clear(MMIO_t* const mmio_device)
noexcept { noexcept {
*mmio_device = static_cast<T>((*mmio_device) & ~mask); RegisterWriteConstant<MMIO_t, T, mask, 0u, ~mask>
::write(mmio_device);
}; };
template <typename MMIO_t, typename T> template <typename MMIO_t, typename T, T mask>
inline static void toggle(MMIO_t* const mmio_device, const T mask) inline static void toggle(MMIO_t* const mmio_device)
noexcept { noexcept {
*mmio_device = static_cast<T>((*mmio_device) ^ mask); *mmio_device = static_cast<T>((*mmio_device) ^ mask);
}; };
}; };
struct write_only { struct write_only {
template <typename MMIO_t, typename T> template <typename MMIO_t, typename T, T mask, Offset_t offset>
inline static void write(MMIO_t* const mmio_device, inline static void write(MMIO_t* const mmio_device,
const T mask,
const Offset_t offset,
const T value) noexcept { const T value) noexcept {
*mmio_device = ((value << offset) & mask); RegisterWrite<MMIO_t, T, type_mask<T>::value, 0u>::write(
mmio_device, ((value << offset) & mask)
);
};
template <typename MMIO_t, typename T, T mask, Offset_t offset, T value>
inline static void write(MMIO_t* const mmio_device) noexcept {
RegisterWriteConstant<
MMIO_t, T, type_mask<T>::value, 0u, ((value << offset) & mask)
>
::write(mmio_device);
}; };
}; };
} }
@ -144,66 +221,115 @@ namespace cppreg {
#ifndef CPPREG_MERGEWRITE_H #ifndef CPPREG_MERGEWRITE_H
#define CPPREG_MERGEWRITE_H #define CPPREG_MERGEWRITE_H
namespace cppreg { namespace cppreg {
template <typename Register> template <
typename Register,
typename Register::type mask,
Offset_t offset,
typename Register::type value
> class MergeWrite_tmpl {
public:
using base_type = typename Register::type;
private:
static_assert(!Register::shadow::use_shadow,
"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() {};
public:
inline 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;
inline void done() const && noexcept {
typename Register::MMIO_t* const mmio_device =
Register::rw_mem_pointer();
RegisterWriteConstant<
typename Register::MMIO_t,
typename Register::type,
_combined_mask,
0u,
_accumulated_value
>::write(mmio_device);
};
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)
>
>
inline
typename std::enable_if<
(internals::check_overflow<
Register::size, new_value, (F::mask >> F::offset)
>::result::value),
T
>::type&&
with() const && noexcept {
return std::move(T::make());
};
};
template <
typename Register,
typename Register::type mask
>
class MergeWrite { class MergeWrite {
public: public:
using base_type = typename Register::type; using base_type = typename Register::type;
static MergeWrite create_instance(const base_type value, private:
const base_type mask) noexcept { constexpr static const base_type _combined_mask = mask;
MergeWrite mw; public:
mw._accumulated_value = value; constexpr static MergeWrite make(const base_type value) noexcept {
mw._combined_mask = mask; return MergeWrite(value);
return mw;
}; };
MergeWrite(MergeWrite&& mw) noexcept MergeWrite(MergeWrite&& mw) noexcept
: _accumulated_value(mw._accumulated_value), : _accumulated_value(mw._accumulated_value) {};
_combined_mask(mw._combined_mask) {
};
MergeWrite(const MergeWrite&) = delete; MergeWrite(const MergeWrite&) = delete;
MergeWrite& operator=(const MergeWrite&) = delete; MergeWrite& operator=(const MergeWrite&) = delete;
~MergeWrite() { MergeWrite& operator=(MergeWrite&&) = delete;
inline void done() const && noexcept {
typename Register::MMIO_t* const mmio_device = typename Register::MMIO_t* const mmio_device =
Register::rw_mem_pointer(); Register::rw_mem_pointer();
*mmio_device = static_cast<base_type>( RegisterWrite<
(*mmio_device & ~_combined_mask) | typename Register::MMIO_t,
((_accumulated_value) & _combined_mask) base_type,
); _combined_mask,
0u
>::write(mmio_device, _accumulated_value);
}; };
template <typename F> template <typename F>
MergeWrite&& with(const base_type value) && noexcept { inline MergeWrite<Register, _combined_mask | F::mask> with
(const base_type value) && noexcept {
static_assert(std::is_same< static_assert(std::is_same<
typename F::parent_register, typename F::parent_register,
Register Register
>::value, >::value,
"field is not from the same register in merge_write"); "field is not from the same register in merge_write");
F::policy::write(&_accumulated_value, F::policy::template write<
F::mask, base_type,
F::offset, base_type,
value); F::mask,
_combined_mask = _combined_mask | F::mask; F::offset
return std::move(*this); >(&_accumulated_value, value);
}; return
template < std::move(
typename F, MergeWrite<Register, (_combined_mask | F::mask)>
base_type value, ::make(_accumulated_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: private:
static_assert(!Register::shadow::use_shadow, static_assert(!Register::shadow::use_shadow,
"merge write is not available for shadow value register"); "merge write is not available for shadow value register");
MergeWrite() : _accumulated_value(0u), constexpr MergeWrite() : _accumulated_value(0u) {};
_combined_mask(0u) {}; constexpr MergeWrite(const base_type v) : _accumulated_value(v) {};
base_type _accumulated_value; base_type _accumulated_value;
base_type _combined_mask;
}; };
} }
#endif #endif
@ -232,16 +358,21 @@ namespace cppreg {
return reinterpret_cast<const MMIO_t* const>(base_address); return reinterpret_cast<const MMIO_t* const>(base_address);
}; };
template <typename F> template <typename F>
inline static MergeWrite<typename F::parent_register> inline static MergeWrite<typename F::parent_register, F::mask>
merge_write(const typename F::type value) noexcept { merge_write(const typename F::type value) noexcept {
return return
MergeWrite<typename F::parent_register> MergeWrite<typename F::parent_register, F::mask>
::create_instance(((value << F::offset) & F::mask), F::mask); ::make(((value << F::offset) & F::mask));
}; };
template < template <
typename F, typename F,
type value, type value,
typename T = MergeWrite<typename F::parent_register> typename T = MergeWrite_tmpl<
typename F::parent_register,
F::mask,
F::offset,
value
>
> >
inline static inline static
typename std::enable_if< typename std::enable_if<
@ -249,11 +380,9 @@ namespace cppreg {
size, value, (F::mask >> F::offset) size, value, (F::mask >> F::offset)
>::result::value, >::result::value,
T T
>::type >::type&&
merge_write() noexcept { merge_write() noexcept {
return return std::move(T::make());
MergeWrite<typename F::parent_register>
::create_instance(((value << F::offset) & F::mask), F::mask);
}; };
static_assert(RegWidth != 0u, static_assert(RegWidth != 0u,
"defining a Register type of width 0u is not allowed"); "defining a Register type of width 0u is not allowed");
@ -275,11 +404,13 @@ namespace cppreg {
using parent_register = BaseRegister; using parent_register = BaseRegister;
using type = typename parent_register::type; using type = typename parent_register::type;
using MMIO_t = typename parent_register::MMIO_t; using MMIO_t = typename parent_register::MMIO_t;
using policy = AccessPolicy;
constexpr static const Width_t width = FieldWidth; constexpr static const Width_t width = FieldWidth;
constexpr static const Offset_t offset = FieldOffset; constexpr static const Offset_t offset = FieldOffset;
using policy = AccessPolicy;
constexpr static const type mask = make_shifted_mask<type>(width, constexpr static const type mask = make_shifted_mask<type>(width,
offset); offset);
constexpr static const bool has_shadow =
parent_register::shadow::use_shadow;
template <type value> template <type value>
struct check_overflow { struct check_overflow {
constexpr static const bool result = constexpr static const bool result =
@ -290,49 +421,47 @@ namespace cppreg {
>::result::value; >::result::value;
}; };
inline static type read() noexcept { inline static type read() noexcept {
return return policy::template read<MMIO_t, type, mask, offset>(
AccessPolicy::read(parent_register::ro_mem_pointer(), parent_register::ro_mem_pointer()
mask, );
offset);
}; };
template <typename T = type> template <typename T = type>
inline static void inline static void
write(const typename std::enable_if< write(const typename std::enable_if<!has_shadow, T>::type value)
!parent_register::shadow::use_shadow, T noexcept {
>::type value) noexcept { policy::template write<MMIO_t, type, mask, offset>(
AccessPolicy::write(parent_register::rw_mem_pointer(), parent_register::rw_mem_pointer(),
mask, value
offset, );
value);
}; };
template <typename T = type> template <typename T = type>
inline static void inline static void
write(const typename std::enable_if< write(const typename std::enable_if<has_shadow, T>::type value)
parent_register::shadow::use_shadow, T noexcept {
>::type value) noexcept { RegisterWrite<type, type, mask, offset>
parent_register::shadow::value = ::write(&parent_register::shadow::value, value);
(parent_register::shadow::value & ~mask) | policy::template write<MMIO_t, type, type_mask<type>::value, 0u>(
((value << offset) & mask); parent_register::rw_mem_pointer(),
AccessPolicy::write(parent_register::rw_mem_pointer(), parent_register::shadow::value
~(0u), );
0u,
parent_register::shadow::value);
}; };
template <type value, typename T = void> template <type value, typename T = void>
inline static inline static
typename std::enable_if< typename std::enable_if<
(!parent_register::shadow::use_shadow) !has_shadow
&& &&
check_overflow<value>::result, check_overflow<value>::result,
T T
>::type >::type
write() noexcept { write() noexcept {
write(value); policy::template write<MMIO_t, type, mask, offset, value>(
parent_register::rw_mem_pointer()
);
}; };
template <type value, typename T = void> template <type value, typename T = void>
inline static inline static
typename std::enable_if< typename std::enable_if<
parent_register::shadow::use_shadow has_shadow
&& &&
check_overflow<value>::result, check_overflow<value>::result,
T T
@ -341,22 +470,21 @@ namespace cppreg {
write(value); write(value);
}; };
inline static void set() noexcept { inline static void set() noexcept {
AccessPolicy::set(parent_register::rw_mem_pointer(), mask); policy::template
set<MMIO_t, type, mask>(parent_register::rw_mem_pointer());
}; };
inline static void clear() noexcept { inline static void clear() noexcept {
AccessPolicy::clear(parent_register::rw_mem_pointer(), mask); policy::template
clear<MMIO_t, type, mask>(parent_register::rw_mem_pointer());
}; };
inline static void toggle() noexcept { inline static void toggle() noexcept {
AccessPolicy::toggle(parent_register::rw_mem_pointer(), mask); policy::template
toggle<MMIO_t, type, mask>(parent_register::rw_mem_pointer());
}; };
template <typename T = bool> inline static bool is_set() noexcept {
inline static typename std::enable_if<FieldWidth == 1, T>::type return (Field::read() == (mask >> offset));
is_set() noexcept {
return (Field::read() == 1u);
}; };
template <typename T = bool> inline static bool is_clear() noexcept {
inline static typename std::enable_if<FieldWidth == 1, T>::type
is_clear() noexcept {
return (Field::read() == 0u); return (Field::read() == 0u);
}; };
static_assert(parent_register::size >= width, static_assert(parent_register::size >= width,