· tutorials · 4 min read

Bit Masking Made Easy with .NET Enum Flags

Recently, I’ve been developing a 6502 emulator in C#, an endeavor which involves a lot of bitwise operations. Fortunately however, by using C# enums in conjunction with the Flags attribute, we can handle bitfields an elegant fashion. Below are two practical examples of how I used them to make my code easier to reason about.

The 6502 Status Register

The 6502’s Status Register (also known as the P register) can be represented as a bitfield consisting of flag bits in the following order:

7  bit  0
---- ----
NV1B DIZC
|||| ||||
|||| |||+- Carry
|||| ||+-- Zero
|||| |+--- Interrupt Disable
|||| +---- Decimal
|||+------ (No CPU effect; see: the B flag)
||+------- (No CPU effect; always pushed as 1)
|+-------- Overflow
+--------- Negative

Since many of the 6502’s instructions modify these status bits, it can be cumbersome and difficult to keep track of which bit needs shifting during an operation, so to simplify this, let’s represent the flags as an enum marked with the [Flags] attribute:

[Flags]
public enum StatusRegisterFlags
{
    Carry = (1 << 0),
    Zero = (1 << 1),
    Irq = (1 << 2),
    Decimal = (1 << 3),
    Break = (1 << 4),
    Ignored = (1 << 5),
    Overflow = (1 << 6),
    Negative = (1 << 7),
}

Note how each flag is shifted left according to its order in the bitfield. Alternatively, you can define enum constants in powers of two (ie: 1, 2, 4, 8, … 128), but I prefer this way as I find it easier to keep track of which flag is where.

Now that we’ve defined our StatusRegisterFlags enum, let’s take a look at a practical example of how we can use them.

Bitwise Operations with Enum Flags

Setting & Clearing Individual Bits

The first thing that we can do using our enum is to write a function that sets individual Status Register flags, and I did this by first creating a BitOperation enum which has two values: Set and Clear. Next, I wrote the below function which takes a BitOperation and StatusRegisterFlag as arguments:

public void SetPFlag(BitOperation operation, StatusRegisterFlags flag)
{
    if (operation is BitOperation.Set)
    {
        P |= (byte)flag;
    }
        
    else if (operation is BitOperation.Clear)
    {
        P &= (byte)~flag;
    }
}

As you can see from the above code, we can easily set or clear a flag simply by calling the function like so:

SetPFlag(BitOperation.Set, StatusRegisterFlags.Carry)

This is both readable and handy!

The PLP Instruction: Loading Bits

In brief, the 6502’s PLP (Pull Processor Function) works by first incrementing the stack pointer and then loading the value at that stack position into the 6 status flags. The bit order is NVxxDIZC (high to low), and the B flag and extra bit are ignored.

Therefore, what we want to do is preserve the B flag and extra bit while setting the other 6 flags to what we read from memory. To do this, I implemented the following code:

// Since PLP addressing mode is implied, load the status from the address found at the start of the stack + SP location.
var processorStatus = Memory.Read((ushort)(0x100 + Registers.Sp));

// Build the Status Register as a series of bitwise OR operations in the form NVDIZC. This will position the bits in the proper order.
const StatusRegisterFlags statusRegisterFlags = StatusRegisterFlags.Carry | StatusRegisterFlags.Zero | StatusRegisterFlags.Irq | StatusRegisterFlags.Decimal | StatusRegisterFlags.Overflow | StatusRegisterFlags.Negative;

// Set the Status register by preserving the unset bits (AND NOT), and then OR that with only the flags being set (AND), resulting in the updated value.
Registers.P = (byte)((Registers.P & unchecked((byte)~statusRegisterFlags)) | (processorStatus & (byte)statusRegisterFlags));

Another thing to take note of in the above code is that because we’re declaring StatusRegisterFlags as a const, we need to use the unchecked keyword on this line:

Registers.P = (byte)((Registers.P & unchecked((byte)~statusRegisterFlags)) | (processorStatus & (byte)statusRegisterFlags));

This is because we need to tell the compiler that we’re aware that this operation is overflowing and that’s the intended behavior, as we’re effectively doing 0b1100_1111 (byte) to 0b1111_1111_1111_1111_1111_1111_0011_0000 (int) to 0b0011_0000 (byte). Note that if you use the var keyword instead (which is fine, I just wanted to use const to avoid accidental modification of the value) no warning will occur since the value of a var is evaluated at runtime and not compile time.

Conclusion

As the above example demonstrates, C#’s enums in conjunction with the Flags attribute can be a powerful tool when working with bitfields, helping to reduce cognitive load while also making it clear what bits in the mask are being changed.

Share:
Back to Blog