c++cpu-registersenum-flagsbitflags

How to store Processor Status flags' values with corresponding enum for 6502


I'm working on 6502 emulator in C++ as a part of my thesis. It has 6 registers, most of them just hold values but there's one special - Processor Status. It's 8 bit wide and each bit means a different flag. The best choice for me seemed to make it std::bitset<8> and create a corresponding enum class to map its values to the real bits as follow:

enum class PSFlags : uint8_t
{
    Carry = 0,
    Zero = 1,
    InterruptDisable = 2,
    Decimal = 3,
    Break = 4,
    Unknown = 5,
    Overflow = 6,
    Negative = 7
};

struct Registers
{
    int8_t A;
    int8_t X;
    int8_t Y;
    uint8_t SP;
    uint16_t PC;
    static constexpr uint8_t PSSize = 8;
    std::bitset<PSSize> PS;

    constexpr Registers() noexcept :
        A(0),
        X(0),
        Y(0),
        SP(0xFF),
        PS(0b00100100),
        PC(0)
    {
    }
};

And now, if I want to refer to one of three: size of PS, the flag number or the bitset itself I have:

Registers::PSSize;  // size
PSFlags::Carry;     // flag number
Registers r; r.PS;   // bitset itself

Where every call accesses the value in a very different way. I'd like to have it more consistent, e.g.

Registers::PS::value;        // for the bitset itself
Registers::PS::size;         // for the size
Registers::PS::flags::Carry; // for the name of flag

Do you have any good ideas on how to achieve such (or similar) consistency without creating some crazy or ugly constructs in the code?


Solution

  • What OP wants (or something acceptable similar) can be achieved using nested structs.

    Just for fun, I tried to model what OP intended:

    #include <bitset>
    
    struct Registers
    {
        int8_t A;
        int8_t X;
        int8_t Y;
        uint8_t SP;
        static constexpr uint8_t PSSize = 8;
    
        struct PS: std::bitset<PSSize> {
          enum Flags {
            Carry = 0,
            Zero = 1,
            InterruptDisable = 2,
            Decimal = 3,
            Break = 4,
            Unknown = 5,
            Overflow = 6,
            Negative = 7
          };
          static constexpr unsigned Size = PSSize;
    
          constexpr PS(std::uint8_t value):
            std::bitset<PSSize>((unsigned long long)value)
          { }
          std::uint8_t value() const { return (std::uint8_t)to_ulong(); }
        } PS;
    
        uint16_t PC;
    
        constexpr Registers() noexcept :
            A(0),
            X(0),
            Y(0),
            SP(0xFF),
            PS(0x24),//PS(0b00100100),
            PC(0)
        {
        }
    } r;
    

    A small test to show this in action:

    #include <iomanip>
    #include <iostream>
    
    #define DEBUG(...) std::cout << #__VA_ARGS__ << ";\n"; __VA_ARGS__ 
    
    int main()
    {
      std::cout << std::hex << std::setfill('0');
      DEBUG(std::cout << Registers::PS::Flags::Carry << '\n');
      DEBUG(std::cout << r.PS[Registers::PS::Flags::Carry] << '\n');
      DEBUG(std::cout << Registers::PS::Flags::InterruptDisable << '\n');
      DEBUG(std::cout << r.PS[Registers::PS::Flags::InterruptDisable] << '\n');
      DEBUG(std::cout << Registers::PS::Flags::Break << '\n');
      DEBUG(std::cout << r.PS[Registers::PS::Flags::Break] << '\n');
      DEBUG(std::cout << Registers::PS::Size << '\n');
      DEBUG(std::cout << "0x" << std::setw(2) << (unsigned)r.PS.value() << '\n');
      // done
      return 0;
    }
    

    Output:

    std::cout << Registers::PS::Flags::Carry << '\n';
    0
    std::cout << r.PS[Registers::PS::Flags::Carry] << '\n';
    0
    std::cout << Registers::PS::Flags::InterruptDisable << '\n';
    2
    std::cout << r.PS[Registers::PS::Flags::InterruptDisable] << '\n';
    1
    std::cout << Registers::PS::Flags::Break << '\n';
    4
    std::cout << r.PS[Registers::PS::Flags::Break] << '\n';
    0
    std::cout << Registers::PS::Size << '\n';
    8
    std::cout << "0x" << std::setw(2) << (unsigned)r.PS.value() << '\n';
    0x24
    

    Note:

    About naming the nested struct Registers::PS and the member Registers::PS with same name is I was expecting to work. Though, usually I use uppercase start character for type identifiers and lowercase start characters for variables. Hence, I don't have this issue usually.

    As being in doubt about this, I tested the struct Registers against various compilers (though I wouldn't count this as proof against the standard): Compiler Explorer

    Deriving from std::containers should be done with care (i.e. better not). Probably for performance reasons, none of the std::containers provides a virtual destructor with the respective consequences. In the above code, this shouldn't be a problem.


    6502 reminded me to the Commodore 64 where I made my first attempts on (although the C64 had the even more modern 6510 CPU). However, that's looong ago... ;-)