Chip-8 Emulator*

Hello Emulator.

Chip-8 emulators are a hello world to emulator programming.

* All images are from ROMs loaded in the emulator, these ROMs are not created by me.

  • Type: Tech preview.
  • Team: Solo
  • Main Language: c++, OpenGL (with GLFW & GLAD)
  • Platform: Windows.

This emulator support loading and displaying ROMs of different iterations of the Chip-8 language.

  • Chip-8 ROMs: The original language for the Cosmac VIP and Telmac 1800, with a screen resolution of 64x32.
  • Hires Chip-8 ROMs: An extended version with a screen resolution of 64x64.
  • Super Chip ROMs: A descendant of Chip-8, with a screen resolution of 128x64.

Challenges

Creating this emulator involved writing a small OpenGL based engine, with a game loop and texture drawing. Parsing the ROMs required loading it, interpretting each opcode (a 2 byte code which indicates some action to perform) and passing through the output to the renderer.

Game loop

The game loop has an important function within the emulator. It calls checks the input, calls the emulators update function a few times and draws the texture it received from the emulator. The game loop is limited by framerate.

The emulators Update() method will update the timers, process the current opcode and add to the counter when finished. The opcode is split into a command (the first 4 bits) and command info, the last 12 bits. The command is used to call a function from an array of function objects. Within each opcode function the command info is used to process the commands specifics.

/* UPDATE */
#pragma region UPDATE
// Update tick
void Chip8::Update()
{
    // Count ticks
    ++m_TickCount;

    // Get Opcode
    U16 opcode = (m_Memory[m_RegPC] << 8) | m_Memory[m_RegPC + 1];

    // Process opcode
    U8 command = opcode >> 12;
    U16 commandInfo = opcode & 0x0fff;

    // Timers
    if (m_DelayTimer > 0)
        --m_DelayTimer;
    if (m_SoundTimer > 0)
    {
        --m_SoundTimer;
        if (m_SoundTimer == 0)
            Beep(400, 80);
    }

    // Process command
    m_OperationsArr[command](commandInfo);

    // Check for buffer overflow
    if (m_RegI >= MEMORYSIZE)
        clog << "I-reg TOO HIGH for opcode: " << command << endl;

    // Counter
    m_RegPC += 2;
}
#pragma endregion

Texture setup

Due to the fact that the different descendants of the Chip-8 language target hardware with different screen resolutions, the array used for storing the texture has to be quite extensible. The array has to be able to render 64x32, 64x64 and 128x64 resolutions. There are a few options for handling this situation.

  • Multiple arrays, one for each resolution.
  • One array, only use the part you really need.
  • One array, scale up the pixels. (which is perfectly possible since the screen resolutions are all powers of two)

During research about the matter I found one note on a test ROM (Emutest by HAP) which states that, when not in super chip mode, the scroll opcode should still scroll by one super chip pixel. Which happens to be half a chip-8 pixel. So for this emulator I chose to implement the last option. This means that for a chip-8 draw operation, each pixel will be registered as a 2x2 square.

// Calculate drawing bit and array index.
U16 index = r * TEXTUREWIDTH + c;
index *= multiplier;
U16 drawBit = 1 << ((index % (sizeof(char) * 8)));
index /= sizeof(char) * 8;

// Flip bit if necessary
m_Texture[index] ^= drawBit;
if (m_EmulatorMode == EmulatorMode::Chip8)
{
    // Flip other 3 bits for 2x2 pixels
    m_Texture[index] ^= (drawBit << 1);
    index += TEXTUREWIDTH / (sizeof(char) * spriteWidth);
    m_Texture[index] ^= drawBit;
    m_Texture[index] ^= (drawBit << 1);
}

OpenGL input & window handling

The GLFW library gives access to the window events as well. For key input, I noticed listening to the event was kind of slow, so I opted for using a key check each update from within the game loop. This allows for more responsive input for the emulator.

Some other window events are handled as well. Resizing will correctly resize the quad on which the texture is drawn. Dropping ROMs on the window will try to load them.

Extra info