The first important thing is to realize what would the CPU need. You can forget to put some stuff as you can add them later, but the sooner you realize what you need, the better you understand the console.
So let's see what has the GB CPU:
- 8 Registers of 1 byte which could be used as 4 2 bytes Registers.
- The Stack Pointer(SP) Register.
- The Program Counter(PC) Register.
- The Interruption Master Enabled(IME) Flag.
When I started the cpu design I didn't put the IME flag because I didn't understand too well how it worked, and if it was part of the CPU or a part of the memory. Then when getting to handle the interruptions I realized it was part of the CPU so I added it.
Let's talk about the Registers. We have the A, F, B, C, D, E, H and L registers. Each of them could be handled by an unsigned char type. But we have the problem that they can be paired to be 2 bytes Registers. A solution could be to replicate the data(i.e. AF = A << 8 F)
typedef union
{
UINT16 w;
struct
{
UINT8 l;
UINT8 h;
}b;
}cpuReg;
cpuReg AF;
The UINT8 is just a #DEFINE UINT8 unsigned char.
The UINT16 is just a #DEFINE UINT16 unsigned short.
So here to access AF we would use AF.w and to access A or F it would be AF.b.h and AF.b.l respectively. The great advantage with the union solution is that they share the same memory so changing one value changes the other.
Another solution could be done with pointers to make them work in a similar way as union does.
The SP and PC Registers are just UINT16 types.
In my case I'm using IME as a bool as it just says if interruptions are enabled or not.
The Register F is special one. It doesn't let you change it directly, because is a register that stores the flag status. It has the flags
Flag Bit Name Description
Z 7 Zero Set when the last operation yielded 0 as result.
N 6 Sub Set if last operation was a substraction.
H 5 Half Carry Set when there's a carry from the 3 bit to the 4 bit of the last operation.
C 4 Carry Set when there's a carry on the 7 bit.
Besides the "official" meaning, the flags gets set or reset in other situations depending on the command and the parameters. You have to obtain that information from somewhere. Usually the information that developers use to create the games will have the correct behaviour of the different commands.
The general cpu structure will be something like:
Pretty simple, isn't it? Well, we still have to handle all the operations of the CPU but that will be in other section since we still need access to the memory.
typedef struct
{
cpuReg AF;
cpuReg BC;
cpuReg DE;
cpuReg HL;
UINT16 SP;
UINT16 PC;
bool IME;
} CPU;
typedef CPU* pCPU;
I've added some defines to get and set(or reset) the values of the flags:
//Get the flags
#define flagZ ((cpu->AF.b.l & 0x80) >> 7)
#define flagN ((cpu->AF.b.l & 0x40) >> 6)
#define flagH ((cpu->AF.b.l & 0x20) >> 5)
#define flagC ((cpu->AF.b.l & 0x10) >> 4)
//Set and Reset the flags
#define SetZ cpu->AF.b.l |= 0x80
#define ResetZ cpu->AF.b.l &= 0x7F
#define SetN cpu->AF.b.l |= 0x40
#define ResetN cpu->AF.b.l &= 0xBF
#define SetH cpu->AF.b.l |= 0x20
#define ResetH cpu->AF.b.l &= 0xDF
#define SetC cpu->AF.b.l |= 0x10
#define ResetC cpu->AF.b.l &= 0xEF
Then the CPU has to initialize the register values and some memory values that are used as special registers.
In the case of the Monochrome GB the initial values of the base Registers of the CPU are:
cpu->AF.b.h = 0x01; //A
cpu->AF.b.l = 0xB0; //F
cpu->BC.w = 0x0013; //BC
cpu->DE.w = 0x00D8; //DE
cpu->HL.w = 0x014D; //HL
cpu->SP = 0xFFFE; //SP
cpu->PC = 0x0100; //PC
cpu->IME = false; //IME
And we are ready to move on!
No comments:
Post a Comment