1

My bitfield below represents the 7 status flags for the 6502 CPU. I am trying to emulate the instruction php, which pushes a copy of the status flags onto the stack.

struct Flags {
    uint8_t C: 1;
    uint8_t Z: 1;
    uint8_t I: 1;
    uint8_t D: 1;
    uint8_t V: 1;
    uint8_t N: 1;
    uint8_t B: 2;
};

I need a way to pack each field into a single-byte datatype like uint8_t so that I can push it to the stack. However, the code below gives this error: operand of type 'struct Flags' where arithmetic or pointer type is required. How do I resolve this problem?

int main() {
    struct Flags f = {0, 0, 0, 0, 1, 0, 0};
    uint8_t f_as_byte = (uint8_t) f;
}
Caspian Ahlberg
  • 934
  • 10
  • 19

3 Answers3

2

The problem with bitfields is that it is implementation-defined in what order the bits are laid out. This could be rather unacceptable for a 6502 emulator. The PHP command must push the status word in the exact desired format, i.e. something like

uint8_t as_state = f.N << 7 | f.V << 6 | f.B << 4 | f.D << 3 | f.I << 2 | f.Z << 1 | f.C;

Your layout is wrong, the B member is in wrong position. All in all considering the complex code like one above, maybe it would be easier to just consider the flags as a single uint8_t and have access macros for it, something like

#define FLAG_N 0x80U
...
#define FLAG_C 0x1U

#define SET_FLAG(flag_var, flag) ((flag_var) |= (flag))
#define CLR_FLAG(flag_var, flag) ((flag_var) &= ~(flag))
#define GET_FLAG(flag_var, flag) ((_Bool)((flag_var) & (flag)))

uint8_t flags = 0;
SET_FLAG(flags, FLAG_C);

if (GET_FLAG(flags, FLAG_N)) { ... }

That way the PHP instruction can be coded to just push flags as is...

Except that the B flag that is pushed is not a flag from the status register...

1

You can do it with a union:

typedef unsigned char uint8_t;

struct Flags {
    uint8_t C:1;
    uint8_t Z:1;
    uint8_t I:1;
    uint8_t D:1;
    uint8_t V:1;
    uint8_t N:1;
    uint8_t B:2;
};

union uFlags {
    uint8_t B;
    struct Flags F;
};

int
main()
{
    struct Flags f = { 0, 0, 0, 0, 1, 0, 0 };
    union uFlags u;

    u.F = f;
    uint8_t f_as_byte = u.B;
}

With a designated initializer, you may be able to initialize the union directly without a separate f:

typedef unsigned char uint8_t;

struct Flags {
    uint8_t C:1;
    uint8_t Z:1;
    uint8_t I:1;
    uint8_t D:1;
    uint8_t V:1;
    uint8_t N:1;
    uint8_t B:2;
};

union uFlags {
    uint8_t B;
    struct Flags F;
};

int
main()
{
    union uFlags u = {
        .F = { .V = 1 }
    };

    uint8_t f_as_byte = u.B;
}
Craig Estey
  • 30,627
  • 4
  • 24
  • 48
1

Instead of casting the structure as a (uint8_t), you should take its address and cast that as a (uint8_t *):

#include <stdio.h>

struct Flags {
    uint8_t C: 1;
    uint8_t Z: 1;
    uint8_t I: 1;
    uint8_t D: 1;
    uint8_t V: 1;
    uint8_t N: 1;
    uint8_t B: 2;
};

int main() {
    struct Flags f = {0, 0, 0, 0, 1, 0, 0};
    uint8_t f_as_byte = *(uint8_t *)&f;
    printf("flags: %.2X\n", f_as_byte);
    return 0;
}

Note however that the actual bit values used to represent the bit-field members are implementation defined. The C Standard does not even guarantee that the Flags structure fit in a single byte. This make bit-fields a risky choice to represent hardware registers or instruction opcodes as a would make the emulator non-portable. Little-endian and big-endian systems typically use a different layout for bit-fields members.

chqrlie
  • 131,814
  • 10
  • 121
  • 189