aquova.net Github's logo Mastodon's logo Blue Sky's logo Backloggd's logo Pico-8's logo The RSS logo


Chapter XI. Final Misc. Instructions

Return to Index

Previous Chapter

A Register Rotation

Time to clean up the final remaining instructions. While we implemented many bitwise rotation instructions in the 0xCB table, there are actually four in the regular table — 0x07 RLCA, 0x0F RRCA, 0x17 RLA, and 0x1F RRA. These operate the same was as the rotate instructions we've implemented previously, with these operating on the A register. This might seem odd, as we already had rotation instructions for the A register in the 0xCB table, and you'd be right, with the one difference being how the Z flag is set. My guess is that these would be the instructions typically used (as they are fewer bytes per instruction), while the entries in the 0xCB table exist to complete the table pattern. In any case, we'll implement them again, taking care to handle the Z flag differently.


// In cpu/opcodes.rs

// RLCA
// 000C
fn rlca_07(cpu: &mut Cpu) -> u8 {
    cpu.rotate_left(Regs::A, true);
    cpu.set_flag(Flags::Z, false);
    1
}

// RRCA
// 000C
fn rrca_0f(cpu: &mut Cpu) -> u8 {
    cpu.rotate_right(Regs::A, true);
    cpu.set_flag(Flags::Z, false);
    1
}

// RLA
// 000C
fn rla_17(cpu: &mut Cpu) -> u8 {
    cpu.rotate_left(Regs::A, false);
    cpu.set_flag(Flags::Z, false);
    1
}

// RRA
// 000C
fn rra_1f(cpu: &mut Cpu) -> u8 {
    cpu.rotate_right(Regs::A, false);
    cpu.set_flag(Flags::Z, false);
    1
}
            

Modify Carry Flag

Next, there are a pair of instructions that modifies the C flag. Up until now, the flags have only been modified as a side effect of other behavior, but it would be beneficial to manipulate them directly. These instructions are 0x37 SCF "Set Carry Flag", and 0x3F CCF "Compliment Carry Flag". "Compliment" here means to flip all of the bits in a value, so all the 0s become 1s and vice versa.


// In cpu/opcodes.

// SCF
// -001
fn scf_37(cpu: &mut Cpu) -> u8 {
    cpu.set_flag(Flags::N, false);
    cpu.set_flag(Flags::H, false);
    cpu.set_flag(Flags::C, false);
    1
}

// CCF
// -00C
fn ccf_3f(cpu: &mut Cpu) -> u8 {
    let c = cpu.get_flag(Flags::C);
    cpu.set_flag(Flags::N, false);
    cpu.set_flag(Flags::H, false);
    cpu.set_flag(Flags::C, !c);
    1
}
            

Speaking of compliment, there is instruction 0x2F CPL which compliments the value in the A register. The ! operator in Rust is most commonly used for boolean values, when used on an integer it flips all the value's bits, exactly what we want here.


// In cpu/opcodes.rs

// CPL
// -11-
fn cpl_2f(cpu: &mut Cpu) -> u8 {
    let a = cpu.get_r8(Regs::A);
    cpu.set_r8(Regs::A, !a);
    cpu.set_flag(Flags::N, true);
    cpu.set_flag(Flags::H, true);
    1
}
            

Interrupts

I've alluded to the interrupts a few times, and given how important they'll eventually be to the operation of the system, it might be a bit surprising that they haven't played a larger part in the implementation of the CPU. We'll cover them in detail in a later chapter, but as an overview, the interrupts act just as their name implies — they interrupt the CPU's normal behavior to perform some other task. There are several interrupt types with different purposes, such as when the player presses a button or during certain stages of the screen rendering process. When they occur, the CPU pauses what it's doing, executes some interrupt-specific code, then returns back to where it was. For systems such as user input, this is critical, as the Game Boy doesn't have enough processing power to continuously poll for button state. Instead, it executes the game normally, and only handles a button when told to do so.

We'll see how this all fits together soon, but for now, there are three instructions that deal with the interrupt handling. Two of them — 0xF3 DI and 0xFB EI, "Disable Interrupts" and "Enable Interrupts" respectively — simply enable or disable all interrupt requests while the third — 0xD9 RETI — is a combination of RET and EI into a single instruction. There are some situations where the CPU might not want to stop what it's doing to handle an interrupt signal, so the CPU has the option to ignore them if need be. For this, we'll expand our Cpu struct slightly by including a flag for whether we're currently accepting interrupts.


// In cpu/mod.rs

pub struct Cpu {
    pc: u16,
    sp: u16,
    a: u8,
    b: u8,
    c: u8,
    d: u8,
    e: u8,
    f: u8,
    h: u8,
    l: u8,
    irq_enabled: bool,
}

impl Cpu {
    // Unchanged code omitted

    pub fn set_irq(&mut self, enabled: bool) {
        self.irq_enabled = enabled;
    }
}
            

"IRQ" is a commonly used abbreviation for "interrupt request". When it comes time to execute an interrupt, one of the first steps will be to check this boolean flag to see if the CPU is even willing to accept an interrupt request at this time. As said, this can be changed via one of three CPU instructions.


// In cpu/opcodes.rs

// RETI
// ----
fn reti_d9(cpu: &mut Cpu) -> u8 {
    let addr = cpu.pop();
    cpu.set_pc(addr);
    cpu.set_irq(true);
    4
}

// DI
// ----
fn di_f3(cpu: &mut Cpu) -> u8 {
    cpu.set_irq(false);
    4
}

// EI
// ----
fn ei_fb(cpu: &mut Cpu) -> u8 {
    cpu.set_irq(true);
    4
}
            

Halting and Stopping

The Game Boy has two power saving modes, both of which have a CPU instruction to activate them. 0x76 HALT pauses CPU execution until an interrupt occurs, which will resume normal operation. 0x10 STOP also pauses the CPU, but has different conditions for re-awakening. To perfectly honest, the STOP instruction was never used for commercially released hardware, and has shown to have bizarre side effects on real hardware if the utmost care isn't taken (more information here for those curious). Given our goal is a functional but not completely accurate emulator, we're not going to bother handling STOP correctly (or at all), as it's more trouble than it's worth. To implement HALT then, we'll need to add another boolean to our Cpu object.


// In cpu/mod.rs

pub struct Cpu {
    pc: u16,
    sp: u16,
    a: u8,
    b: u8,
    c: u8,
    d: u8,
    e: u8,
    f: u8,
    h: u8,
    l: u8,
    irq_enabled: bool,
    halted: bool,
}

impl Cpu {
    // Unchanged code omitted

    pub fn set_halted(&mut self, halted: bool) {
        self.halted = halted;
    }
}
            

For STOP, we'll simply return the number of cycles and perform no other operation. For HALT, we will need to set the halted boolean in our CPU.


// In cpu/opcodes.rs

// STOP
// ----
fn stop_10(_cpu: &mut Cpu) -> u8 {
    // Do nothing
    1
}

// HALT
// ----
fn halt_76(cpu: &mut Cpu) -> u8 {
    cpu.set_halted(true);
    1
}
            

Unused Instructions

Let's now turn our attention to the largest remaining collection of instructions, those which the reference table lists as "invalid". As their name implies, these instructions are unused. They have no defined purpose and attempting to use them would've gotten you a rejection letter during your Nintendo quality approval process. Thus, while some researchers have attempted to document what actually happens if you try to utilize these instructions, our emulator will throw an assertion, as their usage would indicate a problem in our implementation.


// In cpu/opcodes.rs

fn invalid(_cpu: &mut Cpu) -> u8 {
    panic!("Invalid opcode");
}
            

This new invalid function can be used in the eleven opcode indices without a defined purpose.

DAA

Much of this explanation is based off this excellent article

Our OPCODES table should now only have one todo remaining, that which belongs to instruction 0x27 DAA. I have saved DAA to last because it is quite different than the other instructions we have looked at, and it requires a bit of a math lesson. DAA is an acronym standing for "Decimal Adjust Accumulator", with "Accumulator" in this case referring to our A register. This instruction tells the CPU to take the value in the A register and treat it as a format known as "Binary Coded Decimal" (BCD). By this point in the project, you are very familiar with the typical binary representation of numbers — using powers of two to represent a number.

While normal base 2 is by far the most prevalent way to encode a decimal value into binary, there are other methods, such as instead of converting the whole number, you treat each digit separately. For example, take the decimal number 123. That number takes up a single byte and can be written as 0111 1011 (broken into groups of four bits to make it easier to read). Instead, BCD converts each digit of our decimal value into binary separately. Since there are ten possible decimal digits, that requires four bits, so 123 would require 12 bits to convert to BCD, and would be written as 0001 0010 0011. This is equal to 0x0123, illustrating why BCD has its uses as a more human-readably binary format.

What happens if you perform addition and subtraction in BCD? It actually works more or less fine, as since we've separated all of the decimal values into their own 4-bit chunks, adding them together produces another BCD value. It doesn't work for all values however, as adding larger digits together will either overflow into the next digit's space, or produce a 4-bit subsection larger than 9, and thus no longer a valid BCD value. Some correction is needed to ensure that decimal carrying works properly.

As an example, let's add 17 + 25 together.


  17 |   0001 0111
+ 25 | + 0010 0101
  --     ---------
  42 |   0011 1100
            

Our result should have been 42, which is 0100 0010, but our BCD sum ended up being 0011 1100 which is 3 in the tens place and 12 in the ones place, which is not a valid BCD number. Instead of carrying in the normal binary way, we should have carried a 1 into the upper four bits once we passed nine in the lower nibble. To put it another way, if we cross nine for any BCD digit, we should add one to the next digit, and add six to correctly roll us over (adding six gets us back to 0-9 space).

So what does this have to do with the DAA instruction? The DAA instruction performs that addition correction we just described. It is designed to be used after some other operation has placed a BCD value into the A register, at which point it performs the needed corrections to ensure the A register is still holding a valid and accurate BCD value. These corrections utilize the H, C, and N flags to make the proper adjustments, and require DAA to be called immediately after the addition/subtraction operation to ensure the flags are still correct.

To do so, the CPU needs to perform the same checks as we did in our example. Utilizing both the carry/half carry flags along with seeing if any BCD "digit" over or underflowed, the appropriate corrections can be made. You're welcome to make your own attempt at this implementation, but if you're having troubles, one is shown below.


// In cpu/opcodes.rs

// DAA
// Z-0C
fn daa_27(cpu: &mut Cpu) -> u8 {
    let mut a = cpu.get_r8(Regs::A) as i32;

    if cpu.get_flag(Flags::N) {
        if cpu.get_flag(Flags::H) {
            a = (a - 6) & 0xFF;
        }
        if cpu.get_flag(Flags::C) {
            a -= 0x60;
        }
    } else {
        if cpu.get_flag(Flags::H) || (a & 0x0F) > 0x09 {
            a += 0x06;
        }
        if cpu.get_flag(Flags::C) || a > 0x9F {
            a +=  0x60;
        }
    }

    if (a & 0x100) == 0x100 {
        cpu.set_flag(Flags::C, true);
    }
    a &= 0xFF;
    cpu.set_r8(Regs::A, a as u8);
    cpu.set_flag(Flags::Z, a == 0);
    cpu.set_flag(Flags::H, false);
    1
}
            

Conclusion

With DAA in place, the entire CPU instruction set has been implemented. Ensure that your OPCODES table is entirely filled out and resembles something like this.


const OPCODES: [fn(&mut Cpu) -> u8; 256] = [
//  0x00,    0x01,   0x02,   0x03,    0x04,    0x05,    0x06,    0x07,    0x08,   0x09,    0x0A,   0x0B,      0x0C,    0x0D,    0x0E,   0x0F
    nop_00,  ld_01,  ld_02,  inc_03,  inc_04,  dec_05,  ld_06,   rlca_07, ld_08,  add_09,  ld_0a,  dec_0b,    inc_0c,  dec_0d,  ld_0e,  rrca_0f, // 0x00
    stop_10, ld_11,  ld_12,  inc_13,  inc_14,  dec_15,  ld_16,   rla_17,  jr_18,  add_19,  ld_1a,  dec_1b,    inc_1c,  dec_1d,  ld_1e,  rra_1f,  // 0x10
    jr_20,   ld_21,  ld_22,  inc_23,  inc_24,  dec_25,  ld_26,   daa_27,  jr_28,  add_29,  ld_2a,  dec_2b,    inc_2c,  dec_2d,  ld_2e,  cpl_2f,  // 0x20
    jr_30,   ld_31,  ld_32,  inc_33,  inc_34,  dec_35,  ld_36,   scf_37,  jr_38,  add_39,  ld_3a,  dec_3b,    inc_3c,  dec_3d,  ld_3e,  ccf_3f,  // 0x30
    ld_40,   ld_41,  ld_42,  ld_43,   ld_44,   ld_45,   ld_46,   ld_47,   ld_48,  ld_49,   ld_4a,  ld_4b,     ld_4c,   ld_4d,   ld_4e,  ld_4f,   // 0x40
    ld_50,   ld_51,  ld_52,  ld_53,   ld_54,   ld_55,   ld_56,   ld_57,   ld_58,  ld_59,   ld_5a,  ld_5b,     ld_5c,   ld_5d,   ld_5e,  ld_5f,   // 0x50
    ld_60,   ld_61,  ld_62,  ld_63,   ld_64,   ld_65,   ld_66,   ld_67,   ld_68,  ld_69,   ld_6a,  ld_6b,     ld_6c,   ld_6d,   ld_6e,  ld_6f,   // 0x60
    ld_70,   ld_71,  ld_72,  ld_73,   ld_74,   ld_75,   halt_76, ld_77,   ld_78,  ld_79,   ld_7a,  ld_7b,     ld_7c,   ld_7d,   ld_7e,  ld_7f,   // 0x70
    add_80,  add_81, add_82, add_83,  add_84,  add_85,  add_86,  add_87,  adc_88, adc_89,  adc_8a, adc_8b,    adc_8c,  adc_8d,  adc_8e, adc_8f,  // 0x80
    sub_90,  sub_91, sub_92, sub_93,  sub_94,  sub_95,  sub_96,  sub_97,  sbc_98, sbc_99,  sbc_9a, sbc_9b,    sbc_9c,  sbc_9d,  sbc_9e, sbc_9f,  // 0x90
    and_a0,  and_a1, and_a2, and_a3,  and_a4,  and_a5,  and_a6,  and_a7,  xor_a8, xor_a9,  xor_aa, xor_ab,    xor_ac,  xor_ad,  xor_ae, xor_af,  // 0xA0
    or_b0,   or_b1,  or_b2,  or_b3,   or_b4,   or_b5,   or_b6,   or_b7,   cp_b8,  cp_b9,   cp_ba,  cp_bb,     cp_bc,   cp_bd,   cp_be,  cp_bf,   // 0xB0
    ret_c0,  pop_c1, jp_c2,  jp_c3,   call_c4, push_c5, add_c6,  rst_c7,  ret_c8, ret_c9,  jp_ca,  prefix_cb, call_cc, call_cd, adc_ce, rst_cf,  // 0xC0
    ret_d0,  pop_d1, jp_d2,  invalid, call_d4, push_d5, sub_d6,  rst_d7,  ret_d8, reti_d9, jp_da,  invalid,   call_dc, invalid, sbc_de, rst_df,  // 0xD0
    ld_e0,   pop_e1, ld_e2,  invalid, invalid, push_e5, and_e6,  rst_e7,  add_e8, jp_e9,   ld_ea,  invalid,   invalid, invalid, xor_ee, rst_ef,  // 0xE0
    ld_f0,   pop_f1, ld_f2,  di_f3,   invalid, push_f5, or_f6,   rst_f7,  ld_f8,  ld_f9,   ld_fa,  ei_fb,     invalid, invalid, cp_fe,  rst_ff,  // 0xF0
];
            

Also delete the todo function, as it no longer should be used anywhere. The execute function inside of opcodes.rs is now fully functional, so our final step should be to call it. Return to cpu/mod.rs and add a new tick function. This function will eventually be the "heartbeat" of the entire emulator. When told to tick, the CPU will fetch and execute the next instruction (or do nothing if halted).

The execute function does return a cycles count, but we're not quite ready to utilize it, so for now we'll leave it hanging and return to it later. Likewise, tick will eventually need to return a boolean, to let its frontend caller know if its time to render a frame or not. For now, we'll just have it always return false.


// In cpu/mod.rs

// Unchanged code omitted

impl Cpu {
    pub fn tick(&mut self) -> bool {
        let cycles = if self.halted { 1 } else { opcodes::execute(self) };
        false
    }
}
            

We also need to update the constructor for our Cpu object. When we first added it, I mentioned that the initial values for our CPU registers aren't obvious. You would think that they would all be set to 0x00 when a game begins to run, but that's not quite the case. All Game Boys have a small, 256 byte piece of code embedded in the system that runs at start up. This boot ROM performs verification of the game cartridge and scrolls the Nintendo boot graphic with the "ding" noise. Because this piece of code always runs and always leaves the Game Boy in the same initial state at the beginning of game execution, we can just pretend that it ran and initialized our system to those conditions. It also allows us to avoid including and running proprietary Nintendo code and graphics. Here then is the constructor for our Cpu object, complete with the "magic values" for the registers and RAM that all Game Boy titles begin with.


// In cpu/mod.rs

impl Cpu {
    // Unchanged code omitted
    pub fn new() -> Self {
        let mut cpu = Self {
            pc: 0x0100,
            sp: 0xFFFE,
            a: 0x01,
            b: 0x00,
            c: 0x13,
            d: 0x00,
            e: 0xD8,
            f: 0xB0,
            h: 0x01,
            l: 0x4D,
            irq_enabled: false,
            halted: false,
            bus: Bus::new(),
        };

        // Magic values for RAM initialization
        cpu.write_ram(0xFF10, 0x80);
        cpu.write_ram(0xFF11, 0xBF);
        cpu.write_ram(0xFF12, 0xF3);
        cpu.write_ram(0xFF14, 0xBF);
        cpu.write_ram(0xFF16, 0x3F);
        cpu.write_ram(0xFF19, 0xBF);
        cpu.write_ram(0xFF1A, 0x7F);
        cpu.write_ram(0xFF1B, 0xFF);
        cpu.write_ram(0xFF1C, 0x9F);
        cpu.write_ram(0xFF1E, 0xBF);
        cpu.write_ram(0xFF20, 0xFF);
        cpu.write_ram(0xFF23, 0xBF);
        cpu.write_ram(0xFF24, 0x77);
        cpu.write_ram(0xFF25, 0xF3);
        cpu.write_ram(0xFF26, 0xF1); // 0xF0 for SGB
        cpu.write_ram(0xFF40, 0x91);
        cpu.write_ram(0xFF47, 0xFC);
        cpu.write_ram(0xFF48, 0xFF);
        cpu.write_ram(0xFF49, 0xFF);

        cpu
    }
}
            

These magic values aren't obvious by any means, but these are the changes that the bootloader makes, and this is the state all Game Boys expect themselves to be in when a game starts running. One interesting thing to note is that the PC doesn't start at 0x0000, but instead at 0x0100, meaning that the entry point to all Game Boy games needs to be placed at that address in ROM. If you plan on continuing your emulator after this tutorial and implementing Super Game Boy functionality, its bootloader sets RAM address 0xFF26 as 0xF0, not 0xF1, hence the comment.

Despite implementing roughly 500 different instructions, we've yet to test if any of them work as they should. Fortunately, the CPU should actually be quite straight-forward to test. We cycle through all the different operations, providing a series of different inputs and ensure that the registers, flags, and cycle count all match what they should. While you're welcome to implement a test suite yourself, it might be somewhat tricky to know what the correct values actually are for each instruction. Fortunately, the Game Boy community has created just a test suite in the form of authentic Game Boy ROMs. These "Blargg tests" (named after their author) will check the results of a wide range of CPU inputs, verifying that the result is correct, even for some uncommon edge cases. We'll focus on the tests for the CPU, although there are other, more sophisticated ones for testing things like system timing.

Unfortunately, while these tests are a great tool, our emulator is not ready to actually run them. As I said, they're to be run as an actual Game Boy game, which means they require at least some basic system functionality to begin. This includes a functional CPU, which we hopefully have now, but also the RAM, cartridge loading, and some of the video rendering needs to be complete as well. It will be our immediate goal to run these CPU verification tests, so next we will turn our attention to completing these three sub-systems.

Next Chapter