Continuing from the previous post, we’ll implement all the remaining codes defined here.

I-related - 0xannn, 0xfx1e

The tests:

static void test_memory_set_i(void **state)
{
    /*
    The test ROM will look like this:
        0x0200 0xa123 # set i to 0x123
        0x0202 0x6102 # set r1 to 0x2
        0x0204 0xf11e # add the contents of r1 to i
    */

    // init
    State chip8State = {.pc = ROM_OFFSET};
    uint8_t memory[MEM_SIZE];
    memset(memory, 0x0, MEM_SIZE * sizeof(uint8_t));
    uint8_t rom[] = {0xa1, 0x23, 0x61, 0x02, 0xf1, 0x1e};
    memcpy(memory + ROM_OFFSET, rom, sizeof(rom));

    // i should be 0 upon initialisation
    assert_int_equal(chip8State.i, 0x0);
    // i should now be 0x123
    processOp(&chip8State, memory);
    assert_int_equal(chip8State.i, 0x123);
    // set the register
    processOp(&chip8State, memory);
    // add to i
    processOp(&chip8State, memory);
    assert_int_equal(chip8State.i, 0x125);
}

The only bit of interest implementation-wise is how we concatenate the operands to form a memory address:

void setI(State *state, uint8_t top, uint8_t bottom)
{
    state->i = (top << 8) | bottom;
    state->pc += 2;
}
void addRegToI(State *state, uint8_t reg)
{
    state->i += state->registers[reg];
    state->pc += 2;
}

PC-related - 0xbnnn

This really isn’t much:

static void test_memory_set_pc(void **state)
{
    /*
    The test ROM will look like this:
        0x0200 0x6002 # set r0 to 0x2
        0x0202 0xb123 # set the program counter to r0 + 0x123
    */

    // init
    State chip8State = {.pc = ROM_OFFSET};
    uint8_t memory[MEM_SIZE];
    memset(memory, 0x0, MEM_SIZE * sizeof(uint8_t));
    uint8_t rom[] = {0x60, 0x02, 0xb1, 0x23};
    memcpy(memory + ROM_OFFSET, rom, sizeof(rom));

    // set the register
    processOp(&chip8State, memory);
    // set i
    processOp(&chip8State, memory);
    assert_int_equal(chip8State.pc, 0x125);
}

And the implementation is equally straight-forward:

void setPC(State *state, uint8_t top, uint8_t bottom)
{
    state->pc = state->registers[0] + ((top << 8) | bottom);
}

Storing/loading registers - 0xfx55, 0xf65

As the latter is the inverse of the former, it makes for a nice test:

static void test_save_load_registers(void **state)
{
    /*
    The test ROM will look like this:
        0x0200 0xa300 # set i to 0x0300
        0x0200 0x6101 # set r1 to 0x1
        0x0200 0x6202 # set r2 to 0x2
        0x0200 0x6302 # set r3 to 0x3
        0x0202 0xf355 # store r0 to r3 at i
        0x0200 0x6109 # set r1 to 0x1
        0x0200 0x6208 # set r2 to 0x2
        0x0200 0x6307 # set r3 to 0x3
        0x0202 0xf365 # load r0 to r3 from i
    */

    // init
    State chip8State = {.pc = ROM_OFFSET};
    uint8_t memory[MEM_SIZE];
    memset(memory, 0x0, MEM_SIZE * sizeof(uint8_t));
    uint8_t rom[] = {0xa3, 0x00, 0x61, 0x01, 0x62, 0x02, 0x63, 0x03, 0xf3, 0x55, 0x61, 0x09, 0x62, 0x08, 0x63, 0x07, 0xf3, 0x65};
    memcpy(memory + ROM_OFFSET, rom, sizeof(rom));

    // set the registers and save
    for (int i = 0; i < 5; i++)
    {
        processOp(&chip8State, memory);
    }
    uint8_t expected[] = {0x0, 0x1, 0x2, 0x3};
    assert_memory_equal(memory + chip8State.i, expected, 4);

    // we now set the registers to something else and load
    for (int i = 0; i < 4; i++)
    {
        processOp(&chip8State, memory);
    }
    for (int i = 0; i < 4; i++)
    {
        assert_int_equal(chip8State.registers[i], i);
    }
}

The implementation was surprisingly straight-forward:

void saveRegisters(State *state, uint8_t reg, uint8_t memory[])
{
    uint16_t memIdx = state->i;
    for (int regIdx=0; regIdx<=reg; regIdx++){
        memory[memIdx++] = state->registers[regIdx];
    }
    state->pc += 2;
}
void loadRegisters(State *state, uint8_t reg, uint8_t memory[])
{
    uint16_t memIdx = state->i;
    for (int regIdx=0; regIdx<=reg; regIdx++){
        state->registers[regIdx] = memory[memIdx++];
    }
    state->pc += 2;
}

Random number - 0xcxnn

Note that in the test we use srand to set the seed for further calls to the rand function - this makes our tests reproducible. And actually once we start debugging actual ROMs, we may want this as an option to make things a little more deterministic.

static void test_rand(void **state)
{
    /*
    The test ROM will look like this:
        0x0200 0xc50f # generate a random number between 0-255 & 0x0f
    */

    // init
    State chip8State = {.pc = ROM_OFFSET};
    uint8_t memory[MEM_SIZE];
    memset(memory, 0x0, MEM_SIZE * sizeof(uint8_t));
    uint8_t rom[] = {0xc5, 0x0f};
    memcpy(memory + ROM_OFFSET, rom, sizeof(rom));

    processOp(&chip8State, memory);
    // set the seed so we always get the same random number
    srand(0xdeadbeef);
    assert_in_range(chip8State.registers[0x5],0x0,0xf);
    assert_int_equal(chip8State.registers[0x5],0x7);
}

Nothing fancy for the implementation - by default rand will generate a number between 0 and RAND_MAX, so we % 256 to ensure we get something in the 0-255 range.

void getRandomNumber(State *state, uint8_t reg, uint8_t mask)
{
    state->registers[reg] = rand() % 256 & mask;
    state->pc += 2;
}

Setting sprites - 0xfx29

The CHIP-8 specification stores a pre-defined set of “sprites” in memory. Those represent an 5x8 bit array where a 1 is a pixel turned on, and 0 turned off - one for each hex character. See here for a visual representation. Note each hex character occupies only half the size (so 5x4).

I’m not entirely sure which offset those should start at so let’s keep all the sprites in a single block starting at SPRITES_OFFSET which we’ll set to 0x0 for now.

void copySpritesToMemory(uint8_t memory[])
{
    uint8_t sprites[] = {
        0xf0,0x90,0x90,0x90,0xf0, //0
        0x20,0x60,0x20,0x20,0x70, //1
        0xf0,0x10,0xf0,0x80,0xf0, //2
        0xf0,0x10,0xf0,0x10,0xf0, //3
        0x90,0x90,0xf0,0x10,0x10, //4
        0xf0,0x80,0xf0,0x10,0xf0, //5
        0xf0,0x80,0xf0,0x90,0xf0, //6
        0xf0,0x10,0x20,0x40,0x40, //7
        0xf0,0x90,0xf0,0x90,0xf0, //8
        0xf0,0x90,0xf0,0x10,0xf0, //9
        0xf0,0x90,0xf0,0x90,0x90, //a
        0xe0,0x90,0xe0,0x90,0xe0, //b
        0xf0,0x80,0x80,0x80,0xf0, //c
        0xe0,0x90,0x90,0x90,0xe0, //d
        0xf0,0x80,0xf0,0x80,0xf0, //e
        0xf0,0x80,0xf0,0x80,0x80, //f
        };

    memcpy(memory + SPRITES_OFFSET, sprites, sizeof(sprites));
}

This will need to be called during the ROM’s initialisation routine but we can handle that later.

Here’s the test:

static void test_set_sprite(void **state)
{
    /*
    The test ROM will look like this:
        0x0200 0xff29 # set i to the location of the sprite for 0xf
    */

    // init
    State chip8State = {.pc = ROM_OFFSET};
    uint8_t memory[MEM_SIZE];
    memset(memory, 0x0, MEM_SIZE * sizeof(uint8_t));
    uint8_t rom[] = {0xff, 0x29};
    copySpritesToMemory(memory);
    memcpy(memory + ROM_OFFSET, rom, sizeof(rom));

    processOp(&chip8State, memory);
    assert_int_equal(chip8State.i, 5*0xf);
    uint8_t spriteF[] = { 0xf0,0x80,0xf0,0x80,0x80 };
    assert_memory_equal(memory + SPRITES_OFFSET + chip8State.i, spriteF, 5);
}

And the implementation:

void setIToSprite(State *state, uint8_t reg)
{
    // a sprite is 5 bytes long and indexing starts at 0 for this interpreter
    state->i = reg*5;
    state->pc += 2;
}

Note we expose copySpritesToMemory in mylib.h so it’s available in the test.

Displaying sprites - 0xdxyn

This is the instruction we need to start displaying things! very excited

It essentially takes (x,y) coordinates and a value n representing how many “rows” we write. The data is what the register I is point to. So for instance if we wanted to display the character 0x0 in the top-left corner, we would set i to the 0x0 sprite address (SPRITES_OFFSET) and as the sprite is 5-rows high, n would be 5.

The second part is that if we flip any pixels whilst doing so (so turning one off or on), we set the register 0xf to 1.

As always, let’s start with a test!

static void test_draw_sprite(void **state)
{
    /*
    The test ROM will look like this:
        0x0200 0xff29 # set i to the location of the sprite for 0xf
        0x0202 0xd005 # draw the 5x8 sprite defined at i at (0,0)
        0x0204 0xd005 # draw the 5x8 sprite defined at i at (0,0)

    Redrawing the same sprite should cause the 0xf register to be set to 1
    */

    // init
    State chip8State = {.pc = ROM_OFFSET};
    uint8_t memory[MEM_SIZE];
    memset(memory, 0x0, MEM_SIZE * sizeof(uint8_t));
    uint8_t rom[] = {0xff, 0x29, 0xd0, 0x05, 0xd0, 0x05};
    copySpritesToMemory(memory);
    memcpy(memory + ROM_OFFSET, rom, sizeof(rom[0]) * 6);

    // set i
    processOp(&chip8State, memory);
    assert_int_equal(chip8State.i, 5*0xf);
    // draw
    processOp(&chip8State, memory);
    // confirm the pixels have been set accordingly
    uint8_t pixels[] = { 0xf0,0x80,0xf0,0x80,0x80 };
    for (int row = 0; row < 5; row++) {
        assert_int_equal(memory[MEM_DISPLAY_START + row*SCREEN_WIDTH/8], pixels[row]);
    }
    // draw flag should be set
    assert_true(chip8State.draw);
    assert_int_equal(chip8State.registers[0xf], 1);
    // simulate a screen refersh by setting the flag back to false
    chip8State.draw = false;
    // draw the same sprite again
    processOp(&chip8State, memory);
    // flag should be unchanged
    assert_false(chip8State.draw);
    assert_int_equal(chip8State.registers[0xf], 0);
}

And here’s the implementation:

void setPixels(State *state, uint8_t x, uint8_t y, uint8_t height, uint8_t memory[])
{
    uint8_t flipped = 0;
    uint16_t idx = state->i;
    uint16_t offset = 0;
    for (int row = 0; row < height; row++) {
        offset = MEM_DISPLAY_START + x + (SCREEN_WIDTH/8)*(y+row);
        flipped += memory[offset] ^ memory[idx];
        memory[offset] = memory[idx++];
    }
    state->draw = flipped ? true : false;
    state->registers[0xf] = state->draw ? 1 : 0;
    state->pc += 2;
}

Since the width of a sprite is always 8 bites we need to move in (SCREEN_WIDTH/8)*(y+row) increments.

From a state-perspective, we set the draw boolean if any bits have been flipped - this will trigger a screen refresh if there has been any changes.

Keyboard - 0xex9e, 0xexa1, 0xfx0a

The first 2 are essentially conditionals on key presses. We can mimic that behaviour in our test by setting the appropriate key registers. I originally misread the spec as this being a conditional on whether key x was pressed - it’s actually whether the key corresponding to the value in rX has been pressed. Subtle, but without it nothing will behave as expected :)

Unlike most of our previous tests, we’ll run this one twice - once with a key depressed, and one with it pressed:

static void test_keyboard(void **state)
{
    /*
    The test ROM will look like this:
        0x0200 0x6001 # set register r0 to 1
        0x0202 0xe19e # skip the next instruction if key pointed to by r1 is down
        0x0204 0xe1a1 # skip the next instruction if key pointed to by r1 is *not* down

    This test is a little different in that state will be changed from the outside.
    */

    // init
    State chip8State = {.pc = ROM_OFFSET};
    uint8_t memory[MEM_SIZE];
    memset(memory, 0x0, MEM_SIZE * sizeof(uint8_t));
    uint8_t rom[] = {0x60, 0x01, 0xe1, 0x9e, 0xe1, 0xa1};
    memcpy(memory + ROM_OFFSET, rom, sizeof(rom));

    // check that key 1 is depressed
    assert_int_equal(chip8State.input[1], false);
    // set the register
    processOp(&chip8State, memory);
    // skip 0x0204 if the key is pressed (which it isn't)
    processOp(&chip8State, memory);
    assert_int_equal(chip8State.pc, 0x204);
    processOp(&chip8State, memory);
    assert_int_equal(chip8State.pc, 0x208);

    // now run through this again but setting the key as pressed
    State chip8State2 = {.pc = ROM_OFFSET};
    chip8State2.input[0x1] = true;

    processOp(&chip8State2, memory);
    // skip 0x0204 if the key is pressed (which it is this time)
    processOp(&chip8State2, memory);
    // key 1 is still pressed so we should proceed to the next instruction
    processOp(&chip8State2, memory);
    assert_int_equal(chip8State2.pc, 0x208);
}

The implementation of one is the opposite of the other:

void jumpIfKeyPressed(State *state, uint8_t reg)
{
    state->pc += state->input[state->registers[reg]] ? 4 : 2;
}
void jumpIfKeyNotPressed(State *state, uint8_t reg)
{
    state->pc += state->input[state->registers[reg]] ? 2 : 4;
}

Adding this to processOp covers the whole 0xe block:

    case (0xe):
    {
        switch (opCodeRight)
        {
        case (0x9e):
            jumpIfKeyPressed(state, opCodeB);
            break;
        case (0xa1):
            jumpIfKeyNotPressed(state, opCodeB);
            break;
        default:
            error = true;
            break;
        }
    }

The last one is a bit trickier as it’s a blocking operation - that is, we need to wait until a key is pressed before we proceed - and whilst we wait, nothing happens. The easiest way to implement this is to simply not increment the program counter until a key is pressed.

static void test_keyboard_blocking(void **state) {
    /*
    The test ROM will look like this:
        0x0200 0xf50a # wait until a key is pressed, store it in r5
    */

    // init
    State chip8State = {.pc = ROM_OFFSET};
    uint8_t memory[MEM_SIZE];
    memset(memory, 0x0, MEM_SIZE * sizeof(uint8_t));
    uint8_t rom[] = {0xf5, 0x0a};
    memcpy(memory + ROM_OFFSET, rom, sizeof(rom[0]) * 2);

    // wait for a key to be pressed
    processOp(&chip8State, memory);
    assert_int_equal(chip8State.pc, ROM_OFFSET);
    // simulate a key getting pressed
    chip8State.input[1] = true;
    // check again
    processOp(&chip8State, memory);
    // we should now proceed to the next instruction
    assert_int_equal(chip8State.pc, 0x202);
    // and register 5 should have value 1
    assert_int_equal(chip8State.registers[0x5], 0x1);
}

Note that the implementation of this function is stateless - if key 1 is already being pressed, this function will store key 1 into the relevant register and proceed. I don’t know if hardware implementations of CHIP-8 support multiple key presses at the same time but for simplicity, we’ll assume not for now.

void waitForKey(State *state, uint8_t reg)
{
    // any key that is pressed if valid
    for (int key=0; key<16;key++) {
        if (state->input[key]) {
            state->registers[reg] = key;
            state->pc += 2;
        }
    }
}

One way I could see working around this is to store some sort of timestamp along with each key press, which would mean making the evaluation loop time-aware. But let’s kick this down the road.

Binary-coded decimal - 0xfx33

I didn’t expect to come across such an instruction. If I=0x300 and register 1 has 0x81 (which is 129 in decimal), we then have the following:

0x300 = 1  
0x301 = 2
0x302 = 9

A small test should suffice:

static void test_bcd(void **state)
{
    /*
    The test ROM will look like this:
        0x0200 0x6181 # set register 1 to 0x81, or 129 in decimal
        0x0202 0xa300 # set i to 0x300
        0x0203 0xf133 # store the binary representation of r1 at I

    Redrawing the same sprite should cause the 0xf register to be set to 1
    */

    // init
    State chip8State = {.pc = ROM_OFFSET};
    uint8_t memory[MEM_SIZE];
    memset(memory, 0x0, MEM_SIZE * sizeof(uint8_t));
    uint8_t rom[] = {0x61, 0x81, 0xa3, 0x0, 0xf1, 0x33};
    copySpritesToMemory(memory);
    memcpy(memory + ROM_OFFSET, rom, sizeof(rom));

    // set register 1
    processOp(&chip8State, memory);
    // set i to 0x300
    processOp(&chip8State, memory);
    assert_int_equal(chip8State.i, 0x300);
    // BCD of 0x81 is 129
    processOp(&chip8State, memory);
    assert_int_equal(memory[chip8State.i], 1);
    assert_int_equal(memory[chip8State.i+1], 2);
    assert_int_equal(memory[chip8State.i+2], 9);
}

And the implementation:

void setIToBCD(State *state, uint8_t reg, uint8_t memory[])
{
    uint8_t val = state->registers[reg];
    for (int offset = 2; offset >= 0; offset--)
    {
        memory[state->i + offset] = val % 10;
        val -= memory[state->i + offset];
        val /= 10;
    }
    state->pc += 2;
}

Crude I know, but it works.

Timers - 0xfx07, 0xfx15, 0xfx18

Mmm - so timing is something we didn’t really cover in our main event loop. Right now our interpreter is simply processing instructions as quickly as it can without any notion of “wall clock time”. What this really means is that if our CPU is slow instructions will be processed slowly, and if it’s super fast your game of Snake will be Game Over before you can even blink.

We’ll sort this out in a separate post. Right now we’ll simply implement stubs that set the registers and do nothing else.

void setRegisterToDelayTimer(State *state, uint8_t reg)
{
    state->registers[reg] = state->delay_timer;
    state->pc += 2;
}
void setDelayTimerFromRegister(State *state, uint8_t reg)
{
    state->delay_timer = state->registers[reg];
    state->pc += 2;
}
void setSoundTimerFromRegister(State *state, uint8_t reg)
{
    state->sound_timer = state->registers[reg];
    state->pc += 2;
}

Conclusion

OK - I think that means we’re ready to go back to our test ROM and start debugging. Hopefully we have implemented all the instructions required with minimal issues (but I know better than thinking it’ll work flawlessly from the get-go :-/)