OXYGENHUB
◆ main openmsx / src / utils / BitField.hh
2.5 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
e8555c89 Andy McCall 1
#ifndef BITFIELD_HH
2
#define BITFIELD_HH
3
4
#include <concepts>
5
#include <cstddef>
6
#include <limits>
7
8
// "Portable" bitfield class
9
//
10
// This is inspired by:
11
//   https://blog.codef00.com/2014/12/06/portable-bitfields-using-c11
12
// (and stripped down to the bare minimum).
13
//
14
// In theory C++ bitfields are not suited(*1) to replicate an exact
15
// bit-layout (e.g. of some hardware register or of some binary file-format).
16
// The C++ standard leaves many details of bitfield layout up to the
17
// implementation, including things like:
18
//   * layout from LSB to MSB, or the other way around.
19
//   * whether fields can cross byte or word boundaries (how large
20
//     is a single word, whether padding bits are inserted, ...).
21
//   * ...
22
// Fortunately in C++, with a relatively simple utility like this, we can take
23
// full control. Use it like this:
24
//
25
// template<size_t POS, size_t WIDTH = 1, typename T2 = bool>
26
// using BF8 = BitField<uint8_t, POS, WIDTH, T2>;
27
//
28
// union MyRegister { // (*2)
29
//     uint8_t raw = 0;           // the full raw 8 bit value
30
//     BF8<0> bit0;               // a single bit at bit0
31
//     BF8<1, 2, uint8_t> bit12;  // a 2-bit field covering bits 1 and 2
32
//     BF8<3, 5, uint8_t> bit37;  // a 6-bit field covering bits 3 through 7
33
//     BF8<0, 3, uint8_t> bit012; // a 3-bit field, overlapping with 'bit0' and 'bit12'
34
// };
35
// ...
36
// MyRegister reg;
37
// ...
38
// reg.bit0 = true;
39
// reg.bit12 = 2;
40
// int a = reg.bit37;
41
//
42
// (*1) If you stick to a specific compiler like only gcc, then you can often
43
//      make it work. But this relies on implementation-specific behavior, and
44
//      is not guaranteed by the C++ standard.
45
//
46
// (*2) Using a union, and then reading/writing fields in arbitrary order may
47
//      also seem like undefined behavior, normally you're only allowed to read
48
//      the 'active' member (=last written field). Though here it's fine: all
49
//      members are trivially layout-compatible and begin with the same
50
//      underlying type (e.g., uint8_t), satisfying the "common initial
51
//      sequence" rule.
52
53
template<std::unsigned_integral T, size_t POS, size_t WIDTH, typename T2 = T>
54
	requires (WIDTH > 0 && POS + WIDTH <= std::numeric_limits<T>::digits)
55
class BitField {
56
	static constexpr T Mask = (T(1) << WIDTH) - T(1);
57
58
public:
59
	BitField &operator=(T2 newVal) {
60
		value = T((value & ~(Mask << POS)) | ((newVal & Mask) << POS));
61
		return *this;
62
	}
63
64
	explicit(false) operator T2() const {
65
		return (value >> POS) & Mask;
66
	}
67
68
private:
69
	T value;
70
};
71
72
#endif