For a long time, emulation felt like a mystery to me, so I decided to tackle an emulation project in order to learn how all works and fits together. In this post, I will try to explain my journey and leave some tips and resources if you are interested in starting your own one.

Intro

Goals and Expectations

Starting the project, I had the following goals and expectations:

  • Learning how an emulator, a disassembler and a debugger work.
  • Emulate a gaming console and get to a point in which I could play some games.
  • This is just a fun personal project, so I was not trying to build a world-class emulator capable of running 100% of the games.
  • I find most 8-bit music annoying, so I was not interested in implementing sound unless it was necessary for the rest of the emulator to work.
  • From initial research, I knew this was not a trivial project and that it would require at least one month of work.

Choosing which console to emulate

Apparently, the “Hello Word emulation project” is the CHIP-8 virtual machine, which was used for games such as Pong or Space Invaders.

Chip8

Chip8 - Invaders

However, I’ve never seen or used a CHIP-8 machine, so I didn’t feel any connection or interest implementing it.

Therefore, I decided to go with the second-best option which is the original GameBoy.

Writing a ROM disassembler

Almost all tutorials start by explaining the CPU’s architecture and implementing it. However, in my case, I didn’t want to start working on the CPU if I wasn’t even sure if I would be able to read and understand a ROM. Hence, I decided to start with a very basic disassembler.

The first step was choosing a ROM to disassemble and the GameBoy Bootstrap ROM sounded like a good place to start, especially because it’s small and you can compare your results with a correct disassembly, which you can see in the link provided.

Reading the ROM was not difficult. It turns out that ROMS are not encrypted nor protected in any way. So basically, you just have to read the file one byte at a time and interpret it. In order to do so, I used Gbops, the most accurate™ opcode table for the Game Boy as reference.

Opcodes

Let’s take a look at the information published in the site and how to read it.

Opcodes

Let’s suppose that the first byte in the ROM is 0x03. Then according to the table, the corresponding instruction is INC BC. As for the disassembler, the only important value that we need to know at this point is length. In this case, it’s 1 byte long so we don’t need to read any additional bytes.

Now let’s take a look at a more complicated opcode such as 0xC3, which translates to JP u16.

Opcode 0xC3

In this case, it’s a jump to an unsigned 16-bit memory location and the instruction is 3 bytes long. As you can guess, the two additional bytes are the memory location to which we need to jump to. Something important to know is that if the three-byte sequence is C3 2F 99, this translates into JP 992F (notice that the 16-bit value is stored in reverse order).

Finally, there’s a special case, which is opcode 0xCB (Prefix CB). If you get a 0xCB, you’ll need to read the next byte and perform a lookup in an additional table. For example, let’s suppose that the current byte is 0xCB and the next one is 0x01. Then the instruction we are looking at is RLC C.

Opcodes

Just with this knowledge and some repetitive work mapping opcodes, I was able to get a decently looking disassembly listing and decided to move forward and start working on the CPU.

CPU

This was the most interesting part and also the one that took the longest to complete. It takes a lot of effort to implement almost 500 opcodes, but it is rewarding.

My emulator is implemented in C# (.Net Core). To make my life a little bit easier, I added the following using statements to make datatypes more explicit (at least for me).

using u8 = System.Byte;
using u16 = System.UInt16;

Registers

The CPU has eight 8-bit registers and their purpose is to hold data that can be manipulated by various instructions. These registers are named A, B, C, D, E, F, H and L. Since they are 8-bit registers, they can only hold 8-bit values. However, the GameBoy can “combine” two registers in order to read and write 16-bit values. The valid combinations are AF, BC, DE and HL.

There’s some bit manipulation involved in order to combine 8-bit registers and expose them as 16-bit values. This is how the BC virtual register is implemented as an example:

// 8 bit Real registers
public u8 B;
public u8 C;

// 16 bit Virtual register
public u16 BC {
    get {
        return (u16)((B << 8) | C);
    }

    set {
        B = (u8)((value & 0b_11111111_00000000) >> 8);
        C = (u8)(value & 0b_00000000_11111111);
    }
}

Flags

The F register is a special register because it contains the values of 4 flags that allow the CPU to track certain states. These flags are:

Flag Description Bit position
Z Zero flag, set when the result of a mathematical instruction is zero. 7
N Subtraction flag, set when the instruction is a subtraction. 6
H Half carry flag, set when a mathematical operation makes the lower 4 bits of a byte overflow. 5
C Carry flag, set when a mathematical operation makes a byte overflow. 4

Bits 3, 2, 1, and 0 are always zero.

Again some bit manipulation is needed to get the correct flag value and to set the correct bit without affecting the others.

public bool FlagZ {
    get {
        return (F & (1 << bitZeroPosition)) != 0;
    }

    set {
        var newBit = value ? 1 : 0;
        F = (u8)(F & ~(1 << bitZeroPosition) | (newBit << bitZeroPosition));
    }
}

There are two additional important registers:

  • PC (Program Counter): This is a 16-bit value which points to the memory address of next the instruction to execute.

  • SP (Stack Pointer): The GameBoy supports a stack data structure implemented in memory and this points to the address where the top element is located.

Opcodes again

I briefly explained opcodes in the disassembler section and at that point, we didn’t need to know what the opcode was supposed to do. But we need that knowledge now and the Game Boy: Complete Technical Reference is as very good reference, because it even includes a pseudocode section:

Opcodes

Let’s see the opcode class now.

public delegate void Step();

namespace FrozenBoyCore.Processor {
    public class Opcode {
        public u8 value;     // for instance 0xC3
        public string label; // JP {0:x4}
        public int length;   // in bytes
        public int tcycles;  // clock cycles
        public int mcycles;  // machine cycles
        public Step[] steps; // function array

        public Opcode(u8 value, string label, int length, int tcycles, Step[] steps) {
            this.value = value;
            this.label = label;
            this.length = length;
            this.tcycles = tcycles;
            this.mcycles = tcycles / 4;
            this.steps = steps;
        }
    }
}

Notice there is an array of delegate functions that will hold the opcode logic.

The opcodes are stored in a dictionary of type <u8, Opcode> and this is an example entry. 0x41 is a simple opcode and its purpose is to assign the value of the C register to the B register.

{ 0x41, new Opcode(0x41, "LD B, C", 1,  4, 
            new Step[] { () => { 
                regs.B = regs.C; 
            } })},

The next example is a little bit more interesting.

{ 0xC3, new Opcode(0xC3, "JP ${0:x4}", 3, 16, 
            new Step[] {
                () => { lsb = mmu.Read8(regs.PC++); },
                () => { msb = mmu.Read8(regs.PC++); },
                () => { regs.PC = BitUtils.ToUnsigned16(msb, lsb); 
            } })},

0xC3 is a jump to a fixed 16-bit location. Length is 3 bytes (one for the opcode and two for the 16-bit location). In this case, we are asking the MMU (Memory Management Unit) to read the first 8-bit value and advancing the Program Counter, then the second one and finally combining the two 8-bit values to make the PC point to the address of the new instruction.

You may wonder why the logic is split into several delegate functions and not in a single function. Wouldn’t that be easier? Definitely yes and my first implementation worked that way. However, if you want your emulator to have accurate timing it’s better to do it step by step because it better resembles the way the GameBoy worked. The following section should clarify this.

Timing

The GameBoy has many different hardware components besides the CPU and all of them need to be able to sinchronize their work. For emulation purposes, there might be other approaches, but I’ve found these two:

  • Let the CPU run the show. If we take opcode 0xC3 as an example again, the documentation says that it takes 16 clock cycles to execute the instruction. In this approach, we execute the instruction and then notify all the other components (including the timer) that 16 clocks cycles have elapsed and they have to catch up and do the work corresponding to 16 clock cycles. This approach is simpler and faster, but it’s not very accurate. You can definitely make a working emulator this way, however passing accuracy tests or running games that require precise timing can be a challenge.

  • Let the clock run the show. In this approach, the clock advances one clock cycle and each component performs a small amount of work if needed. For example, in a real GameBoy, reading from memory takes 4 clock cycles. The emulator can simulate this by just waiting 4 clock cycles and then reading the value. If you are interested, page 8 of the Game Boy: Complete Technical Reference explains timing in a lot more detail. My emulator uses this approach and that’s why instructions are split into smaller steps which will take 4 simulated clock cycles to complete.

{ 0xC3, new Opcode(0xC3, "JP ${0:x4}", 3, 16, 
            new Step[] {
                // 4 clock cycles
                () => { lsb = mmu.Read8(regs.PC++); }, 
                // 4 clock cycles
                () => { msb = mmu.Read8(regs.PC++); }, 
                // 4 clock cycles
                () => { regs.PC = BitUtils.ToUnsigned16(msb, lsb);
            } })},

But how do we know what to do in each step? Fortunately, both the opcode guide and the Game Boy: Complete Technical Reference provide this information.

Opcode Timing

You may also notice that the instruction takes 16 clock cycles, but we’ve only declared 12 clock cycles in the Step array. That’s because the fetch step (4 clock cycles) is common to all instructions so it’s not included in the array.

As I’ve mentioned before, implementing 500 opcodes was a lot of work and here are some tips based on my experience:

  • Decide if you want to achieve timing accuracy as early as possible because it will impact your architecture.
  • Get a working emulator source code. The more the better. There are opcodes whose description is quite confusing and checking other project’s implementation will save time and prevent insanity.
  • Typing the opcodes, their length and timing information is boring and error prone. I had way more fun creating a script that scraped the information and dumped it in the format that my opcode dictionary required.

CPU Execution loop

Now that we’ve covered CPU registers and how the opcode logic is handled, let’s see how the CPU execution loop works. Basically, we are going to do this forever:

CPU loop

Interrupts

Interrupts are events such as a “button has been pressed” and there are specific memory locations that hold which kind of interruption has been requested, if handling it is still pending, etc. These memory locations are called memory registers, but it’s just a fancy name and are not actual registers like the CPU ones.

  • IME (boolean) = Main switch to enable or disable all interrupts.
  • IE, 0xFFFF = Granular interrupt enabler. When a bit is set, the corresponding interrupt can be triggered.
  • IF, 0xFF0F = When a bit is set, an interrupt request has happened.

Both IE and IF have the same bit structure:

// Bit position
// 0   Vblank 
// 1   LCD
// 2   Timer 
// 3   Serial Link 
// 4   Joypad 

Let’s take a look at an example. If the timer needs to trigger an interrupt, it has to set IF’s bit 2 to one. As part of the CPU’s execution loop, there’s a step to check if there’s an interrupt pending. But in order for the CPU to handle it, IME must be true and IE’s bit 2 must be turned on as well. If all the conditions are true then the following will happen:

  • IME will be set to false and the corresponding IF bit will be set to zero.
  • The current PC value will be pushed to the stack.
  • PC will be set to a memory address which contains the interrupt handling routine.
  • Once the handling routine is done, PC will be restored and execution will continue.
  • IME will be enabled again.

These are the handling routines addresses:

// ISR addresses
public List<u16> ISR_Address = new List<u16> {
        { 0x0040 },    // Vblank
        { 0x0048 },    // LCD Status
        { 0x0050 },    // TimerOverflow
        { 0x0058 },    // SerialLink
        { 0x0060 } };  // JoypadPress,

Finally, there’s a hardware bug in the GameBoy which is known as the Halt Bug. Explaining it is out of the scope of this post, but at high level, an instruction can be skipped under certain conditions. Here are the details in case you are interested.

Timer

The GameBoy’s CPU includes a 4,194,304 Hz clock. There are two units commonly found in documentation: machine cycles and clock cycles. The equivalence is 1 machine cycle = 4 clock cycles.

The timer uses the following memory registers:

  • DIV (0xFF04): the divider register increments at a fixed rate of 16,384 Hz. This means that we have to increment DIV’s value every 256 clock cycles (4,194,304 / 16,384 = 256).

  • TIMA (0xFF05): the timer register increments at a configurable frequency. In this case, we have to update it every 16, 64, 256 or 1024 clock cycles depending on the frequency set in the TAC register (see how the previous clock cycle value was calculated). When TIMA overflows an interrupt is triggered and it’s value is reset to TMA’s value. TIMA should only be counting if the timer is enabled in the TAC register.

  • TMA (0xFF06): this value is used when TIMA overflows.

  • TAC (0xFF07): Timer Control, it has the following structure:

// Timer Control 
// Bits 1-0 - Input Clock Select
//            00: 4096   Hz 
//            01: 262144 Hz
//            10: 65536  Hz
//            11: 16384  Hz
// Bit  2   - Timer Enable
// 
// Note: The "Timer Enable" bit only affects TIMA, 
// DIV is ALWAYS counting.

In theory, implementing the timer should not have been that difficult because it’s just a matter of incrementing registers, managing overflows and resetting values. However, in practice it was quite hard, due to the fact that there’s a lot of obscure behaviour related to the timer. For example, TIMA is not updated with TMA’s value inmediately and you have to wait some additional clock cycles. In other cases, writing to TAC or DIV also increases TIMA as a side effect.

This is one of those situations in which having an accurate working GameBoy emulator as reference is very valuable. In my case, I checked CoreBoy’s implementation and used a similar approach in my emulator.

Memory

The GameBoy memory layout looks like this:

Memory map

You can check this site if you are interested in a detailed description of each section.

The memory map looks complicated and intimidating, and certainly it is when you are just getting started with your emulator. However, you can get a lot of work done by just declaring a single byte array and not caring about splitting memory into separate data structures. The main restriction is that you’ll only be able to work with 32 Kb ROMS, but that’s good enough to run Tetris, Alleyway, Dr. Mario and some other games.

My only advice is not to access the memory array directly and to create Read Memory and Write Memory functions because you’ll need to implement access logic later.

Eventually, in order to load bigger games, ROM Banking will be needed. At high level, the main idea is that cartridges can include additional memory banks and there’s a mechanism to move the contents of those banks into the GameBoy address space. So basically the game will request the contents of a specific ROM bank to be copied to 0x4000-0x7FFF. If the game needs to, it can later request to swap to a different memory bank.

Memory below 0x8000 is read only, but in order to request a new memory bank, the game will attempt to write to that area following an established protocol that the GameBoy will interpret as a ROM Banking request. A very similar process is used for RAM banks.

The following tutorials explain the whole process in more detail:

Last but not least, the following Memory Bank Controllers exist:

  • MBC1: 2MB ROM (125 banks) and/or up to 32KB RAM
  • MBC2: 256KB ROM (16 banks) and 512x4 bits RAM
  • MBC3: 2MB ROM (128 banks) and/or 32KB RAM (4 banks) and Timer
  • MBC5: 8MB ROM (512 banks) and/or 128KB RAM (16 banks)

The most common one is MBC1 and supporting it allows your emulator to load a very big percentage of the existing games. In my case, I’ve only implemented MBC1 so far.

Testing

By now I had implemented registers, opcodes, the interrupt logic, memory (just as a big array) and a timer. For sure millions of bugs were waiting for me, so I didn’t want to move forward unless I could perform automated testing.

Fortunately, there are testing ROMS available. Blargg tests are especially nice because, unlike other ROMS, they don’t require a working LCD display or graphics implementation to show the results. You’ll get a “Passed” or a message like this in case of a failure:

Failed

In this case, the result shows that 0xCB prefixes 00, 01, 02, 03, 04, 05 and 07 are wrongly implemented.

Most of the time it’s not easy to determine what’s going wrong exactly. In my case, I modified another emulator’s source code and added a log that dumped the CPU and memory state in the same format as my emulator in order to perform comparisons and it worked very well. It’s very obvious, but make sure to select an emulator that passes all Blargg tests. I guess you can imagine I failed to do this the first time. 😉

Most Blargg tests send output to the Serial Link, but some write the results to a specific memory location instead.

  • Serial Link: The serial link works one character at a time. If you detect a value of 0x81 written to address 0xFF02, then log the content of address 0xFF01.

  • Memory Output: While the test is running, memory address 0xA000 will hold 0x80. The result will be a zero terminated sequence of bytes starting at 0xA000.

But how do we load the test ROM in first place? If you take a look at cpu_instrs.gb, you’ll notice that the ROM’s size is 64 Kb. At this point, I had not implemented MBC1 yet, so I couldn’t load it. But fortunately there’s a folder named “individual” that contains individual 32 Kb ROM tests.

The next step is copying the ROM contents to the memory array (starting at position 0) and starting the execution loop. However, registers and some memory locations must be initialized to some specific values. For instance, the Program Counter must be set to 0x100.

Why 0x100 and where did that value come from? When the GameBoy starts, the boot ROM will execute and change the GameBoy’s state, so we have to replicate that state before running the test ROM. Alternatively, you can choose to actually execute the boot ROM, but it will hang waiting for interrupts coming from the pixel processing unit and you won’t be able to move forward unless you implement that module.

Graphics

There are three parts related to graphics:

  1. Pixel Processing Unit (PPU) modes and timing.
  2. PPU tile and sprite rendering.
  3. A graphic library or framework for your language of choice (SDL, SFML, Monogame, etc.)

1.- LCD Modes and timing

The most important thing to understand is that the GameBoy mimics a CRT (cathode-ray tube) television. In those TVs, there was an electron gun firing electrons to a phosphorescent screen in order to display images. The gun moved from left to right, one “pixel” at a time. Once a row was drawn, the gun would move to first “pixel” of the next row. Finally, when the last row had been completed, it would need to go back to the first row. But here’s the thing, it takes a certain amount of time for the gun to move to the next row or even worse to the first row if it’s currently in the last one.

I wish I had found this diagram earlier, because figuring it out from source code or tutorials was not that easy.

LCD_diagram

Let’s try to understand what this means. Memory location 0xFF44, also called LY, holds the row that is currently being processed. There are 3 phases or “modes” that take place in order to fully draw a row (and yes, the numbering is confusing):

  • Mode 2: This phase takes 80 clock cycles and it’s purpose is to fetch assets from memory.
  • Mode 3: In this mode tiles and sprites are rendered. This phase can take from 172 clock cyles to 289 clock cycles depending on the amount of assets to be rendered.
  • Mode 0: This is called H-Blank and happens when it’s time to move to the beginning of the next row. Depending on how many clock cycles were consumed by Mode 3, this mode can take from 87 to 204 clock cycles.
  • Mode 1: This mode is called V-Blank and happens when the last visible row has been processed, which is row 143. There are 10 additional rows, which in total take 4,560 clock cycles to process. After that, we go back to the first row (LY = 0).

In my emulator, I haven’t implemented variable clock cycles for Mode 3 and Mode 0 and just defaulted to 172 and 204 clock cycles, which is the most common case and works fine for most games. But besides that, it’s important to notice that processing a row will always take constant 456 clock cycles.

Why is all this timing related stuff relevant? Because games will expect things to happen at certain times and you can have all sort of visual glitches if you don’t emulate mode timing correctly.

Also important is that interrupts can be trigerred when changing modes and that the current mode is available at memory location 0xFF41, also known as the STAT register. The STAT register also includes other flags like the “coincidence flag”. The game can set a value at memory location 0xFF45, also known as the LYC register. This value corresponds to a row number and it means that the game is interested in knowing when the processing of that specific row starts. If LY and LYC are equal, then the “coincidence flag” is set to 1 and also an interrupt is trigerred if enabled. Games use this functionality in order to change palettes or perform special effects.

Here are some tutorials that contain a more detailed explanation:

Last but not least, checking CoreBoy’s source code helped me implement timing stuff that happens when the LCD is turned on and off.

Finally, if you ever want to play Road Rash, the game relies on a GameBoy bug that you’ll need to emulate as well.

public u8 STAT {
    get => _stat;
    set {
        // undocumented GameBoy bug
        // This is needed by Road Rash
        // http://www.devrs.com/gb/files/faqs.html#GBBugs
        if (mode == MODE_VBLANK || mode == MODE_HBLANK) {
            if (IsLcdEnabled()) {
                intManager.RequestInterruption(InterruptionType.LCD);
            }
        }
        _stat = BitUtils.ChangeBits(_stat, 0b_1111_1000, value);
    }
}

2.- Rendering

The best explanation I could find related to rendering is The Ultimate GameBoy Talk and I truly recommend watching it from 29:13 to 40:22, especially because there are certain aspects such as tile encoding and palette management that are quite convoluted. The tutorials I’ve mentioned earlier also have sections dealing with rendering sprites and tiles.

Instead of replicating all that content here, I will debug a rendering glitch so you can learn how to use BGB’s debugger and also I’ll explain a case that I didn’t find in the tutorials.

When I was testing “Castlevania - The adventure”, the main character certainly looked a little bit strange. It was weird because other games were being emulated correctly.

Castlevania_wrong

BGB is a very good emulator and it comes with an excellent debugger that includes support for viewing Video RAM, so I decided to use it and see what was going on.

OAM memory contains information regarding the sprites that are currently being displayed. For instance, the second entry has the following attributes:

VRAM1 VRAM2

Notice the tile has a number, in this case 0x03. With this number, we can fetch the tile’s data from another memory area that stores all the tiles available for the game to use. In this case, BGB is nice enough to tell us that tile 0x03 is stored at memory address 0x8030.

Let’s see some of the contents stored starting at 0x8000. As you can see, our tile is there and the PPU is rendering it horizontally flipped. That’s because the game can request the PPU to render a tile horizontally or vertically flipped (in the attributes above, X-fip is active).

VRAM3

Now let’s take a look at the screenshot again. If you notice, this particular tile was being rendered too high, even above the character’s head.

Castlevania_wrong2

After a while, I noticed something a little suspicious: there’s an empty tile just before the tile we are analyzing. And then I remembered that games can ask the PPU to work with 8x8 tiles or alternatively with 8x16 tiles. Of all games I’ve tested, “Castlevania - The Adventure” is the only one that wants to work with 8x16 tiles. The fix was quite easy, if I found a reference in OAM memory to an odd tile number, then I just had to make sure to draw the previous one as well.

Castlevania_right

3.- Graphics library

A simple approach is using a library that supports rendering information stored in a byte array. The main idea is that the PPU will write the current frame information to the array and the library will handle displaying it. Of course each pixel will need to be stored in a format like RGBA or something similar.

In my case, I tried SDL and SFML, but Monogame was the easiest to setup. Just in case, keep in mind that user input handling will be needed later (or sound someday) so it’s better to choose a framework that supports it.

Running at constant frames per second is also important, especially because you’ll notice a huge difference in the speed of the emulator when you switch from Debug to Release builds.

Monogame has a function named Draw that by default is called 60 times per second. The main idea is to make the emulation move forward by the exact amout of cycles needed to achieve those 60 FPS. Something like this:

// This is called by the Draw function
// The gameboy can execute 4194304 clock cycles every second
// 4194304 / 60 = 69905
private const int CYCLES = 69905;

private void UpdateWorld() {
    int cyclesThisUpdate = 0;

    while (cyclesThisUpdate < CYCLES) {
        // Step moves the clock one clock cycle forward
        cyclesThisUpdate += gameboy.Step();
    }
}

More testing

Once graphics support is ready, then other test suites become available. MoonEye’s ROMS are also very good and extensive. These ROMS output the test’s results to the LCD and you’ll get something like this:

MoonEye

If you want to do automated testing, the most common approach is taking a screenshot of the success message and computing a hash. Then when you run your tests, you have to periodically take a screenshot, compute a hash and compare it to the expected one.

This idea can be extended even further and test actual games. For instance, if the game does something interesting at some point, you can screenshot it and test it automatically later. For example, Zelda requires a correct coincidence flag implementation for the sea and the rain to look this way:

Zelda

Same goes for the way in which “Castlevania II” displays it’s logo animation.

Castlevania II

Other modules

JoyPad

The following tutorial was the most helpful, especially because the author took the effort to layout how the JoyPad was wired on a real GameBoy (which explains why working with it is convoluted).

GameBoy emulation in Javscript: Input

Just as a side note, usually you would expect 1 to mean pressed and 0 to mean unpressed, but not in the GameBoy’s case.

Finally, I hit a bug while testing Space Invaders. The game was detecting the emulator as a Super GameBoy. This is related to 0xFF00 being used as the JoyPad register on the GameBoy and as the Communication register on a SGB. The solution can be found here.

You don’t need to implement all the Serial Link functionality, just enough for games not to complain. 😉

The full specification can be found here. However, for basic emulation, what you’ll want to do is reply 0xff in the SB register (0xFF01) which means “no other GameBoy connected”.

Surprising glitches

While testing games, I found glitches that were related to causes that surprised me:

  • Dr. Mario had collision detection problems. As you can see, there’s a column of gray pills that should have dissapeared. I thought the error was related to rendering or timing, but it was due to not protecting memory writes below 0x8000.

DrMario

  • Alleyway would skip the main menu, go directly to game play and then the pad refused to move. The reason is that you need to emulate the Serial Link for the game to work properly.

  • Space Invaders thought I was emulating a SuperGameBoy (see the JoyPad section).

Space Invaders SGB

  • Road Rash relies on an undocumented GameBoy bug and will halt if you’ve not implemented it. See the LCD Modes and timing section.

Future Work

  • There are games such as Pokemon that require MBC3 support, so they won’t work for now.
  • I haven’t implement Load State/Save State functionality in order to save game progress.
  • Emulating the GameBoy Color does not seem to be that complicated after all this work, so maybe I’ll add support for it.

Final thoughts

Working on this project was a blast!!! It’s not a trivial effort, but playing on a console that you’ve built from scratch is pretty rewarding.

If you want to take a look at the source code, it’s available here. Keep in mind that this is just a personal project so there might be lots of bugs, glitches and unsupported games. Just in case, the project includes some automated tests that expect game ROMs to be present, but I can’t distribute them.

Thanks for reading!!!