on
Gameboy Emulator Setup
In this post I will cover the CPU and MMU setup I used in my emulator.
I won’t cover all the basic knowledge for how a CPU works. But just to recap. A CPU executes instructions. One at a time. Each instruction is represented by a number. This number is called an operation code or opcode.
A CPU goes through a 3 step process for runnning these opcodes.
- Fetch
- Decode
- Execute
An opcode is fetched from memory, its operation is decoded and it is executed. Then repeat.
So when making an emulator we must be able to follow these 3 steps.
Registers
The Gameboy has the following registers: A, B, C, D, E, H, L, PC, SP, F
- Athrough- Lare general purpose 8 bit registers.
- PCis the program counter. It points to the next opcode to execute.
- SPis the stack pointer. Points to the top of the stack.
- Fis the flag register. Contains the status of the last operation
While all registers (except the F) can be used independently the general purpose 8 bit registers are paired with another to make a 16 bit register.
They are:
AF, BC, DE and HL
Below is how I setup the registers up in code.
class CPU
{
public:
    union Register
    {
        struct {
#ifdef __LITTLEENDIAN__
            uint8_t lo;
            uint8_t hi;
#else
            uint8_t hi;
            uint8_t lo;
#endif
        };
        uint16_t val;
    };
...
private:
    Register af_;
    Register bc_;
    Register de_;
    Register hl_;
    Register sp_;
    Register pc_;
}
To make this code portable I flipped the hi and lo registers depending of endianness.
Fetch, Decode, Execute skeleton code
Take a look at a table of Gameboy Opcodes here. These are the instructions that will eventually be implemented.
// cpu.h
class CPU
{
public:
    void step();
private:    
    void decode1(uint8_t opcode); // decode page 1 opcode
    void decode2(uint8_t opcode); // decode page 2 opcode
};
// cpu.cpp
void CPU::step()
{
    // fetch next opcode
    uint8_t opcode = mmu_.read(pc_.val++);
    // $CB means decode from the second page of instructions
    if (opcode != 0xCB)
    {
        // decode from first page
        decode1(opcode);
    }
    else
    {
        // read the second page opcode
        opcode = mmu_.read(pc_.val++);
        // decode from second page
        decode2(opcode);
    }
}
void CPU::decode1(uint8_t opcode)
{
    // execute
    switch(opcode)
    {
        ...
    }
}
void CPU::decode2(uint8_t opcode)
{
    // execute
    switch(opcode)
    {
        ...
    }
}
Memory Management Unit
The Gameboy doesn’t have a explicit MMU that you need to deal with. This is a convenience class for accessing memory.
At some point I will add support for memory bank switching and this will help kepp things organized.