Forward
This is a continuation of my work in progress and the last iteration that I posted can be found here. I have designed a compact class template that uses SFINAE with constructor delegation to reduce the amount of code duplication, and to keep this as generic and portable as possible.
I even added in a few functions that works on these registers.
One is an internal method that will retain the same internal value, but will adjust the bitset
from one endian to another: Note: I'm only currently supporting Little Endian
and Big Endian
representations. Currently I do not have any internal members to act as a flag to tell what the current state of the endian is in but I may add this in later and I may add it by either an enumeration, by a bool flag, or by both(one to indicate which it is and the other to indicate if there was a change).
I also have a function that will reverse the order of all of the bits, for example: 0x00110101
after being reversed will become: 0x10101100
and the value will change in this case. Some cases the bit's are perfect mirror representations so the value will stay the same as there is no effect on the bit pattern as in: 0x01011010
. This will be the same in either version, but the endian view may change. My reverse function has a second bool parameter which is false by default and will modify the contents internally, if true is passed it will create a copy and return by that copy.
I am able to construct Any of the Four Register types from Any of the Four basic unsigned integral types, or from any of the other Four Register types. All of the types are assumed to be a multiple of bytes such that:
u8 = 8bits = 1 byte
u16 = 16bits = 2 bytes
u32 = 32bits = 4bytes
u64 = 64bits = 8bytes
Design
There are three cases or ways my constructors work and they follow this set of design rules:
Overall structure of class:
In the first case it is constructing a smaller size from a larger size and can extract a byte, word, or dword from a word, dword or qword by the index value. If the index value is out of range, then assert.
In the second case it is constructing a larger size from a smaller size and can set a byte word or dword into word, dword or qword at that index location. If the index value is out of range, then assert.
In the third case (default) case it is a 1 to 1 mapping so no calculations nor assertions need to be performed, just save the contents, and the index parameter if passed will have no effect.
Implementation
Here is my class declaration:
#pragma once
#include <algorithm>
#include <assert.h>
#include <bitset>
#include <cstdint>
#include <iostream>
#include <iomanip>
#include <limits>
#include <string>
#include <type_traits>
namespace vpc {
using u8 = std::uint8_t;
using u16 = std::uint16_t;
using u32 = std::uint32_t;
using u64 = std::uint64_t;
template<typename T>
struct Register {
T data;
T value;
std::bitset<sizeof(T)* CHAR_BIT> bits;
Register() : data{ 0 }, value{ 0 }, bits{ 0 } {}
template<typename P, std::enable_if_t<(sizeof(P) > sizeof(T))>* = nullptr>
explicit Register(const P val, const u8 idx = 0) :
data{ static_cast<T>((val >> std::size(bits) * idx) &
std::numeric_limits<std::make_unsigned_t<T>>::max()) },
value{ data },
bits{ data }
{
constexpr u16 sizeT = sizeof(T);
constexpr u16 sizeP = sizeof(P);
assert((idx >= 0) && (idx <= ((sizeP / sizeT) - 1)) );
}
template<typename P, std::enable_if_t<(sizeof(P) < sizeof(T))>* = nullptr>
explicit Register(const P val, const u8 idx = 0) :
data{ static_cast<T>((static_cast<T>(val) << sizeof(P)*CHAR_BIT*idx) &
std::numeric_limits<std::make_unsigned_t<T>>::max()) },
value{ data },
bits{ data }
{
constexpr u16 sizeT = sizeof(T);
constexpr u16 sizeP = sizeof(P);
assert((idx >= 0) && (idx <= ((sizeT / sizeP) - 1)) );
}
template<typename P, std::enable_if_t<(sizeof(P) == sizeof(T))>* = nullptr>
explicit Register(const P val, const u8 idx = 0) :
data{ static_cast<T>( val ) }, value{ data }, bits{ data }
{}
template<typename P>
explicit Register(const Register<P>& reg, const u8 idx = 0) : Register(reg.data, idx) {}
void changeEndian() {
T tmp = data;
char* const p = reinterpret_cast<char*>(&tmp);
for (size_t i = 0; i < sizeof(T) / 2; ++i)
std::swap(p[i], p[sizeof(T) - i - 1]);
bits = tmp;
}
};
using Reg8 = Register<u8>;
using Reg16 = Register<u16>;
using Reg32 = Register<u32>;
using Reg64 = Register<u64>;
template<typename T>
std::ostream& operator<<(std::ostream& os, const Register<T>& r) {
return os << "Reg" << std::size(r.bits) << '(' << +r.data << ")\nhex: 0x"
<< std::uppercase << std::setfill('0') << std::setw(sizeof(T) * 2) << std::hex
<< +r.bits.to_ullong() << std::dec << "\nbin: "
<< r.bits << "\n\n";
}
// this is a universal template class to change the endian on any value
template<typename T>
T changeEndian(T in) {
char* const p = reinterpret_cast<char*>(&in);
for (size_t i = 0; i < sizeof(T) / 2; ++i)
std::swap(p[i], p[sizeof(T) - i - 1]);
return in;
}
template<typename T>
Register<T> reverseBitOrder(Register<T>& reg, bool copy = false) {
static constexpr u16 BitCount = sizeof(T) * CHAR_BIT;
auto str = reg.bits.to_string();
std::reverse(str.begin(), str.end());
if (copy) { // return a copy
Register<T> cpy;
cpy.bits = std::bitset<BitCount>(str);
cpy.data = static_cast<T>(cpy.bits.to_ullong());
return cpy;
}
else {
reg.bits = std::bitset<BitCount>(str);
reg.data = static_cast<T>(reg.bits.to_ullong());
return {};
}
}
} // namespace vpc
Example
And here is an example of it in use:
#include "Register.h"
int main() {
using namespace vpc;
Reg8 r8{ 0xEF };
Reg16 r16{ 0xABCD };
Reg32 r32{ 0x23456789 };
Reg64 r64{ 0x0123456789ABCDEF };
std::cout << "Default Constructors by value\n";
std::cout << r8 << r16 << r32 << r64 << '\n';
std::cout << "Showing opposite endian of original values\n";
r8.changeEndian();
r16.changeEndian();
r32.changeEndian();
r64.changeEndian();
std::cout << r8 << r16 << r32 << r64;
std::cout << "Reversing the bit representation\n";
reverseBitOrder( r8 );
reverseBitOrder( r16 );
reverseBitOrder( r32 );
reverseBitOrder( r64 );
std::cout << r8 << r16 << r32 << r64 << '\n';
std::cout << "Showing opposite endian of the reversed bit order\n";
r8.changeEndian();
r16.changeEndian();
r32.changeEndian();
r64.changeEndian();
std::cout << r8 << r16 << r32 << r64;
// I'm only going to show a couple for demonstration instead of
// showing every possible combination
std::cout << "Constructing from larger types:\n";
Reg8 r8a0{ r32, 0 }; // sets r8 to what is in r32 at 0
Reg8 r8a1{ r32, 1 }; // sets r8 to what is in r32 at 1
Reg8 r8a2{ r32, 2 }; // sets r8 to what is in r32 at 2
Reg8 r8a3{ r32, 3 }; // sets r8 to what is in r32 at 3
// Reg8 r8a4{ r32, 4 }; // uncomment -> assertion failure index out of range
std::cout << r8a0 << r8a1 << r8a1 << r8a2 << '\n';
// This also works not just by Register<T> but also from types:
Reg8 r8b0{ u32(0x01234567), 0 }; // r8a0 = 0x67
Reg8 r8b1{ u32(0x01234567), 1 }; // r8a1 = 0x45
std::cout << r8b0 << r8b1 << '\n';
Reg64 r64a0{ r32, 0 };
Reg64 r64a1{ r32, 1 };
// Reg64 r64a2{ r32, 2 }; // uncomment -> assertion failure index out of range
std::cout << r64a0 << r64a1 << '\n';
// Just for fun:
r64a0.changeEndian();
r64a1.changeEndian();
std::cout << r64a0 << r64a1 << '\n';
reverseBitOrder( r64a0 );
reverseBitOrder( r64a1 );
std::cout << r64a0 << r64a1 << '\n';
r64a0.changeEndian();
r64a1.changeEndian();
std::cout << r64a0 << r64a1 << '\n';
std::cout << "Constructing from smaller types\n";
Reg32 r32a { r8, 0 };
Reg32 r32b { r8, 1 };
Reg32 r32c { r8, 2 };
Reg32 r32d { r8, 3 };
// Reg32 r32e{ r8, 4 }; // uncomment -> assertion failure index out of range
std::cout << r32a << r32b << r32c << r32d << '\n';
// This also works not just by Register<T> but also from types:
Reg32 r32b0{ u16(0x4567), 0 }; // r32a0 = 0x00004567
Reg32 r32b1{ u16(0x4567), 1 }; // r32a1 = 0x45670000
std::cout << r32b0 << r32b1 << '\n';
// Third case constructor
Reg8 r8x(r8);
Reg8 r8x2(r8,2); // 2 has no effect due to same size
std::cout << r8x << r8x2 << '\n';
Reg16 r16x(r16);
Reg16 r16x3( u16(0xABCD), 3 ); // 3 has no effect due to same size
std::cout << r16x << r16x3 << '\n';
return EXIT_SUCCESS;
}
Output
And here is the output: it matches expected values!
Default Constructors by value
Reg8(239)
hex: 0xEF
bin: 11101111
Reg16(43981)
hex: 0xABCD
bin: 1010101111001101
Reg32(591751049)
hex: 0x23456789
bin: 00100011010001010110011110001001
Reg64(81985529216486895)
hex: 0x0123456789ABCDEF
bin: 0000000100100011010001010110011110001001101010111100110111101111
Showing opposite endian of original values
Reg8(239)
hex: 0xEF
bin: 11101111
Reg16(43981)
hex: 0xCDAB
bin: 1100110110101011
Reg32(591751049)
hex: 0x89674523
bin: 10001001011001110100010100100011
Reg64(81985529216486895)
hex: 0xEFCDAB8967452301
bin: 1110111111001101101010111000100101100111010001010010001100000001
Reversing the bit representation
Reg8(247)
hex: 0xF7
bin: 11110111
Reg16(54707)
hex: 0xD5B3
bin: 1101010110110011
Reg32(3299010193)
hex: 0xC4A2E691
bin: 11000100101000101110011010010001
Reg64(9278720243462943735)
hex: 0x80C4A2E691D5B3F7
bin: 1000000011000100101000101110011010010001110101011011001111110111
Showing opposite endian of the reversed bit order
Reg8(247)
hex: 0xF7
bin: 11110111
Reg16(54707)
hex: 0xB3D5
bin: 1011001111010101
Reg32(3299010193)
hex: 0x91E6A2C4
bin: 10010001111001101010001011000100
Reg64(9278720243462943735)
hex: 0xF7B3D591E6A2C480
bin: 1111011110110011110101011001000111100110101000101100010010000000
Constructing from larger types:
Reg8(145)
hex: 0x91
bin: 10010001
Reg8(230)
hex: 0xE6
bin: 11100110
Reg8(230)
hex: 0xE6
bin: 11100110
Reg8(162)
hex: 0xA2
bin: 10100010
Reg8(103)
hex: 0x67
bin: 01100111
Reg8(69)
hex: 0x45
bin: 01000101
Reg64(3299010193)
hex: 0x00000000C4A2E691
bin: 0000000000000000000000000000000011000100101000101110011010010001
Reg64(14169140888105648128)
hex: 0xC4A2E69100000000
bin: 1100010010100010111001101001000100000000000000000000000000000000
Reg64(3299010193)
hex: 0x91E6A2C400000000
bin: 1001000111100110101000101100010000000000000000000000000000000000
Reg64(14169140888105648128)
hex: 0x0000000091E6A2C4
bin: 0000000000000000000000000000000010010001111001101010001011000100
Reg64(591751049)
hex: 0x0000000023456789
bin: 0000000000000000000000000000000000100011010001010110011110001001
Reg64(2541551402828693504)
hex: 0x2345678900000000
bin: 0010001101000101011001111000100100000000000000000000000000000000
Reg64(591751049)
hex: 0x8967452300000000
bin: 1000100101100111010001010010001100000000000000000000000000000000
Reg64(2541551402828693504)
hex: 0x0000000089674523
bin: 0000000000000000000000000000000010001001011001110100010100100011
Constructing from smaller types
Reg32(247)
hex: 0x000000F7
bin: 00000000000000000000000011110111
Reg32(63232)
hex: 0x0000F700
bin: 00000000000000001111011100000000
Reg32(16187392)
hex: 0x00F70000
bin: 00000000111101110000000000000000
Reg32(4143972352)
hex: 0xF7000000
bin: 11110111000000000000000000000000
Reg32(17767)
hex: 0x00004567
bin: 00000000000000000100010101100111
Reg32(1164378112)
hex: 0x45670000
bin: 01000101011001110000000000000000
Reg8(247)
hex: 0xF7
bin: 11110111
Reg8(247)
hex: 0xF7
bin: 11110111
Reg16(54707)
hex: 0xB3D5
bin: 1011001111010101
Reg16(43981)
hex: 0xABCD
bin: 1010101111001101
Objectives
My class above is not complete yet as this is just a public interface. Eventually I'm thinking about turning it into a class and encapsulating the internal members. There are a few more constructors that I would like to add and that is constructing a larger Register<T>
from several smaller Register<T>
objects.
Pseudo Example:
Reg8 r8a{ 0xAB };
Reg8 r8b{ 0xFE };
Reg16 r16{ r8a, r8b }; // would yield 0xFEAB
// remember that first index 0 is to the right
// so our first passed in parameter would be
// set to index 0 and the next would be index 1
Possible future additions:
If I do plan on encapsulating the members by making then private and adding accessing and modifying functions, then I'll need to add them. Other than that this is the bulk of my class design.
I may even add
operator<<=()
andoperator>>=()
that would push and pop information from one register to another, streaming the bits. The same thing as you would see in assembly languages when you shift the registers.Eventually I'll add in the arithmetic and comparison operators that you would commonly see done in assembly language on registers. Addition, Subtraction, Negation, Comparison etc. I'll even include the
operator[]
to get the individual bit at a specific index through the use ofbitset
.I'll even have a function to get a byte, word, or dword from a word, dword or qword by index similar to the constructors, and one to extract them as well.
Maybe a few house keeping functions
As of now I have a
data
andvalue
variables and thevalue
isn't being used. I plan on changingdata
tovalue
and renamevalue
topreviousValue
, then if the value of the internal register changes, both its value and itsbitset
bit pattern changes and it will retain a history of the last value. It'll do this by savingvalue
intopreviousValue
before applying the changes.
- I think this would be a nice feature so that when working with the CPU class and you are moving in and out of registers or adding in place by immediate values, etc. you may not have to construct another temp
Register<T>
object since you can do the operation on its history, then the calculation on that register can be done in place.
Objectives I'd like to fulfill:
- Keep the bulk of the work done during compile time and let the compiler optimize away all it needs to.
- Allowing it to be fast and efficient.
- Making it to be generic, portable and reusable.
- Making it readable and expressive enough.
- Not sure if this is thread safe or not: it is something I'd like to keep in mind.
- Let's say one builds a virtual CPU that is a quad core. They may want to have each core in its own thread. Then all of the registers belong to each of the individual cores of the CPU would have to be thread safe.
- Even in a single core environment, one might want to construct their CPU that has multithreading capabilities and the registers again should be thread safe.
- Finally, the one thing I like about it the most is a combination of its ease of use, and its dynamic ability to be created from the four common basic unsigned types as well as the four templates of Register types. I also like the fact that you can construct a smaller register from a larger and vise versa and that you can index into which byte, word or dword you want to save or write to.
Summary
I like to consider this a very versatile multipurpose register class with a good amount of flexibility, code reuse, easy to use, and being generic and portable with great readability while keeping thread safety in mind.
Conclusion
Other things I'd like to commit to this project in the future after finishing with the operators and methods, would be to extend this to signed
types as well as floating
types, but that's tomorrows challenge. Then I plan to use it in my emulator projects where I plan on emulating an 8080 and a 6502 CPUs. I plan on using the 8080 to emulate arcade games that ran on the 8080 such as Space Invaders, and the 6502 such as an NES emulator.
Feedback: Questions & Concerns
Remember these will be used to create a virtual CPU or virtual machine so think of these as you would an actual register within a CPU. Let me know what you think of my universal multipurpose virtual Register class. I want to hear all different kinds of feedback; pros and cons, where improvements can be made, any corner cases I may have missed, etc.