Skip to content

Commit

Permalink
✨ hal::bit_value: multi-bit set/clear/xor
Browse files Browse the repository at this point in the history
- 📝 Add documentation to all class and APIs in bit.hpp
  • Loading branch information
kammce committed Oct 4, 2024
1 parent 5cb1629 commit b06bf73
Showing 1 changed file with 206 additions and 10 deletions.
216 changes: 206 additions & 10 deletions include/libhal-util/bit.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

#pragma once

#include <climits>
#include <concepts>
#include <cstddef>
#include <cstdint>
Expand Down Expand Up @@ -223,6 +224,8 @@ constexpr hal::bit_mask byte_m = byte_mask<ByteIndex>::value;
* @ingroup Bit
* @brief Helper for generating nibble position masks
*
* A nibble is considered 4 bits or half a byte's width in bits.
*
* @tparam NibbleIndex - the nibble position to make a mask for
*/
template<size_t NibbleIndex>
Expand All @@ -240,6 +243,28 @@ struct nibble_mask
template<size_t NibbleIndex>
constexpr hal::bit_mask nibble_m = nibble_mask<NibbleIndex>::value;

/**
* @ingroup Bit
* @brief Extracts a specific field from an unsigned integral value using a
* compile time (template) bit mask.
*
* Prefer to use this function over the non-template function if the bit mask
* can be known at compile time. This will allow the compiler to optimize this
* bit extract even further.
*
* USAGE:
*
* static constexpr auto peripheral_state = hal::bit_mask::from<4, 9>();
* auto state = hal::bit_extract<peripheral_state>(reg->status);
* // Proceed to use `state` for something
*
* @tparam field - The bit mask defining the position and width of the field to
* be extracted.
* @param p_value The unsigned integral value from which the field will be
* extracted.
* @return A value representing only the specified field, with all other bits
* set to zero.
*/
template<bit_mask field>
constexpr auto bit_extract(std::unsigned_integral auto p_value)
{
Expand All @@ -251,7 +276,28 @@ constexpr auto bit_extract(std::unsigned_integral auto p_value)
// Leaving only the desired bits
return static_cast<T>(masked);
}

/**
* @ingroup Bit
* @brief Extracts a specific field from an unsigned integral value using a bit
* mask.
*
* If the bit mask is known and constant at compile time, use the
* `hal::bit_extract<mask>` function as it provides more information to the
* compiler that it can use to optimize the bit extraction operation.
*
* USAGE:
*
* auto const peripheral_state = hal::bit_mask::from(4, 9);
* auto state = hal::bit_extract(peripheral_state, reg->status);
* // Proceed to use `state` for something
*
* @param p_field - The bit mask defining the position and width of the field to
* be extracted.
* @param p_value The unsigned integral value from which the field will be
* extracted.
* @return A value representing only the specified field, with all other bits
* set to zero.
*/
constexpr auto bit_extract(bit_mask p_field,
std::unsigned_integral auto p_value)
{
Expand All @@ -264,83 +310,166 @@ constexpr auto bit_extract(bit_mask p_field,
return static_cast<T>(masked);
}

/**
* @ingroup Bit
* @brief A class for representing a value as a sequence of bits.
*
* This class provides a flexible way to manipulate and work with bit-level
* values, allowing for efficient and expressive operations on large integers.
*
* This class provides template and non-template options for manipulating bits.
* If the bitmask is KNOWN at COMPILE TIME, OPT to use the template versions of
* the APIs as the compiler will have enough information to deduce the bit mask
* and reduce the set of operations needed to perform the bit manipulation.
*
* USAGE:
*
* static constexpr auto pre_scalar = hal::bit_mask::from<3, 18>();
* static constexpr auto enable = hal::bit_mask::from<19>();
* hal::bit_value my_value;
* my_value.insert<pre_scalar>(120UL)
* .set<enable>();
* reg->control = my_value.get();
*
* @tparam T - defaults to `std::uint32_t`, but can be any unsigned integral
* type.
*/
template<std::unsigned_integral T = std::uint32_t>
class bit_value
{
public:
static constexpr std::uint32_t width = sizeof(T) * 8;
/**
* @brief The total number of bits in the represented value.
*
* This is calculated as the product of the size of `T` and the number of bits
* per byte (`CHAR_BIT`).
*/
static constexpr std::uint32_t width = sizeof(T) * CHAR_BIT;

/**
* @brief Constructs a new bit_value instance with an initial value.
*
* @param p_initial_value The initial value to use. Defaults to 0.
*/
constexpr bit_value(T p_initial_value = 0)
: m_value(p_initial_value)
{
}

/**
* @brief Sets (sets to a 1) multiple bits in the represented value.
*
* @tparam field A bit_mask type, which represents the position and size of
* the bit to set.
* @return A reference to this instance for method chaining.
*/
template<bit_mask field>
constexpr auto& set()
{
static_assert(field.position < width,
"Bit position exceeds register width");
constexpr auto mask = static_cast<T>(1U << field.position);
constexpr auto mask = field.value<T>();

m_value = m_value | mask;

return *this;
}

/**
* @brief Sets (sets to a 1) multiple bits in the represented value.
*
* @param p_field A bit_mask instance, which represents the position and size
* of the bit to set.
* @return A reference to this instance for chaining.
*/
constexpr auto& set(bit_mask p_field)
{
auto const mask = static_cast<T>(1U << p_field.position);
auto const mask = p_field.value<T>();

m_value = m_value | mask;

return *this;
}

/**
* @brief Clears (sets to 0) multiple bits in the represented value.
*
* @tparam field A bit_mask type, which represents the position and size of
* the bit to clear.
* @return A reference to this instance for chaining.
*/
template<bit_mask field>
constexpr auto& clear()
{
static_assert(field.position < width,
"Bit position exceeds register width");
constexpr auto mask = static_cast<T>(1U << field.position);
constexpr auto inverted_mask = ~mask;
constexpr auto inverted_mask = ~field.value<T>();

m_value = m_value & inverted_mask;

return *this;
}

/**
* @brief Clears (sets to 0) multiple bits in the represented value.
*
* @param p_field A bit_mask instance, which represents the position and size
* of the bit to clear.
* @return A reference to this instance for chaining.
*/
constexpr auto& clear(bit_mask p_field)
{
auto const mask = static_cast<T>(1U << p_field.position);
auto const inverted_mask = ~mask;
auto const inverted_mask = ~p_field.value<T>();

m_value = m_value & inverted_mask;

return *this;
}

/**
* @brief Toggles multiple bits in the represented value.
*
* @tparam field A bit_mask type, which represents the position and size of
* the bit to toggle.
* @return A reference to this instance for chaining.
*/
template<bit_mask field>
constexpr auto& toggle()
{
static_assert(field.position < width,
"Bit position exceeds register width");

constexpr auto mask = static_cast<T>(1U << field.position);
constexpr auto mask = field.value<T>();

m_value = m_value ^ mask;

return *this;
}

/**
* @brief Toggles a single bit in the represented value.
*
* @param p_field A bit_mask instance, which represents the position and size
* of the bit to toggle.
* @return A reference to this instance for chaining.
*/
constexpr auto& toggle(bit_mask p_field)
{
auto const mask = static_cast<T>(1U << p_field.position);
auto const mask = p_field.value<T>();

m_value = m_value ^ mask;

return *this;
}

/**
* @brief Inserts a new value into the represented bit sequence.
*
* @tparam field A bit_mask type, which represents the position and size of
* the bit to insert.
* @param p_value The new value to insert.
* @return A reference to this instance for chaining.
*/
template<bit_mask field>
constexpr auto& insert(std::unsigned_integral auto p_value)
{
Expand All @@ -358,6 +487,14 @@ class bit_value
return *this;
}

/**
* @brief Inserts a new value into the represented bit sequence.
*
* @param p_field A bit_mask instance, which represents the position and size
* of the bit to insert.
* @param p_value The new value to insert.
* @return A reference to this instance for chaining.
*/
constexpr auto& insert(bit_mask p_field, std::unsigned_integral auto p_value)
{
// AND value with mask to remove any bits beyond the specified width.
Expand All @@ -373,6 +510,14 @@ class bit_value
return *this;
}

/**
* @brief Inserts a new value into the represented bit sequence.
*
* @tparam p_field A bit_mask instance, which represents the position and size
* of the bit to insert.
* @tparam p_value The new value to insert.
* @return A reference to this instance for chaining.
*/
template<bit_mask p_field, std::unsigned_integral auto p_value>
constexpr auto& insert()
{
Expand All @@ -389,12 +534,24 @@ class bit_value
return *this;
}

/**
* @brief Returns the represented value as an instance of U
*
* Performs truncating static cast if the sizeof(T) > sizeof(U)
*
* @return The represented value as type U.
*/
template<std::integral U>
[[nodiscard]] constexpr auto to()
{
return static_cast<U>(m_value);
}

/**
* @brief Returns the represented value as a T instance.
*
* @return The represented value.
*/
[[nodiscard]] constexpr T get()
{
return m_value;
Expand All @@ -404,22 +561,61 @@ class bit_value
T m_value;
};

/**
* @ingroup Bit
* @brief A class for modifying a register value by manipulating its bits.
*
* This class provides a convenient and expressive way to perform bitwise
* operations on a register value, allowing for efficient and safe modifications
* without compromising performance.
*
* USAGE:
*
* static constexpr auto pre_scalar = hal::bit_mask::from<3, 18>();
* static constexpr auto enable = hal::bit_mask::from<19>();
*
* hal::bit_modify(reg->control)
* .insert<pre_scalar>(120UL)
* .set<enable>();
* // At this line, reg->control will be updated
*
* @tparam T - defaults to `std::uint32_t`, but can be any unsigned integral
* type.
*/
template<std::unsigned_integral T>
class bit_modify : public bit_value<T>
{
public:
/**
* @brief Constructs a new bit_modify instance with an initial value and a
* pointer to the register.
*
* @param p_register_reference A reference to the register whose bits will be
* modified.
*/
constexpr bit_modify(T volatile& p_register_reference)
: bit_value<T>(p_register_reference)
, m_pointer(&p_register_reference)
{
}

/**
* @brief On destruction, update the original register's value with the final
* modified bits.
*
*/
~bit_modify()
{
*m_pointer = this->m_value;
}

private:
/**
* @brief A pointer to the original register value being modified.
*
* This allows for safe and efficient updates of the underlying register with
* the final modified bits.
*/
T volatile* m_pointer;
};
} // namespace hal

0 comments on commit b06bf73

Please sign in to comment.