olympia_engine 0.3.0

Olympia is a gameboy emulator and toolkit, intended to run as a native or web assembly application targeting a cycle count accurate emulation. olympia_engine is the reusable core for both native and wasm.
Documentation
use crate::gameboy::{GameBoy, StepResult};
use crate::instructions::{ALOp, ByteRegisterTarget};
use crate::instructionsn::{ExecutableInstruction, RuntimeOpcode};
use crate::registers;

use alloc::boxed::Box;
use alloc::vec::Vec;

use olympia_derive::OlympiaInstruction;

fn is_add_half_carry(a: u8, b: u8) -> bool {
    0 != (((a & 0xF) + (b & 0xF)) & 0xF0)
}

fn is_sub_half_carry(a: u8, b: u8) -> bool {
    let sub = (a & 0x1F).wrapping_sub(b & 0x0F);
    (sub & 0x10) != (a & 0x10)
}

fn alu_op(gb: &mut GameBoy, op: ALOp, arg: u8) -> u8 {
    let current_value = gb.cpu.read_register_u8(registers::ByteRegister::A);
    match op {
        ALOp::Add => {
            let (new, overflow) = current_value.overflowing_add(arg);
            gb.set_flag_to(registers::Flag::Carry, overflow);
            gb.reset_flag(registers::Flag::AddSubtract);
            gb.set_flag_to(registers::Flag::Zero, new == 0);
            gb.set_flag_to(
                registers::Flag::HalfCarry,
                is_add_half_carry(current_value, arg),
            );
            new
        }
        ALOp::AddCarry => {
            let carry_bit = u8::from(gb.read_flag(registers::Flag::Carry));
            let (tmp, overflow) = current_value.overflowing_add(arg);
            let (new, overflow_carry) = tmp.overflowing_add(carry_bit);
            gb.set_flag_to(registers::Flag::Carry, overflow | overflow_carry);
            gb.reset_flag(registers::Flag::AddSubtract);
            gb.set_flag_to(registers::Flag::Zero, new == 0);
            gb.set_flag_to(
                registers::Flag::HalfCarry,
                is_add_half_carry(current_value, arg + carry_bit),
            );
            new
        }
        ALOp::Sub => {
            let (new, overflow) = current_value.overflowing_sub(arg);
            gb.set_flag_to(registers::Flag::Carry, overflow);
            gb.set_flag(registers::Flag::AddSubtract);
            gb.set_flag_to(registers::Flag::Zero, new == 0);
            gb.set_flag_to(
                registers::Flag::HalfCarry,
                is_sub_half_carry(current_value, arg),
            );
            new
        }
        ALOp::SubCarry => {
            let carry_bit = u8::from(gb.read_flag(registers::Flag::Carry));
            let (tmp, overflow) = current_value.overflowing_sub(arg);
            let (new, overflow_carry) = tmp.overflowing_sub(carry_bit);
            gb.set_flag_to(registers::Flag::Carry, overflow | overflow_carry);
            gb.set_flag(registers::Flag::AddSubtract);
            gb.set_flag_to(registers::Flag::Zero, new == 0);
            gb.set_flag_to(
                registers::Flag::HalfCarry,
                is_sub_half_carry(current_value, arg + carry_bit),
            );
            new
        }
        ALOp::Compare => {
            let (new, overflow) = current_value.overflowing_sub(arg);
            gb.set_flag_to(registers::Flag::Carry, overflow);
            gb.set_flag(registers::Flag::AddSubtract);
            gb.set_flag_to(registers::Flag::Zero, new == 0);
            gb.set_flag_to(
                registers::Flag::HalfCarry,
                is_sub_half_carry(current_value, arg),
            );
            current_value
        }
        ALOp::And => {
            let new = current_value & arg;
            gb.reset_flag(registers::Flag::Carry);
            gb.set_flag(registers::Flag::HalfCarry);
            gb.reset_flag(registers::Flag::AddSubtract);
            gb.set_flag_to(registers::Flag::Zero, new == 0);
            new
        }
        ALOp::Or => {
            let new = current_value | arg;
            gb.reset_flag(registers::Flag::Carry);
            gb.reset_flag(registers::Flag::HalfCarry);
            gb.reset_flag(registers::Flag::AddSubtract);
            gb.set_flag_to(registers::Flag::Zero, new == 0);
            new
        }
        ALOp::Xor => {
            let new = current_value ^ arg;
            gb.reset_flag(registers::Flag::Carry);
            gb.reset_flag(registers::Flag::HalfCarry);
            gb.reset_flag(registers::Flag::AddSubtract);
            gb.set_flag_to(registers::Flag::Zero, new == 0);
            new
        }
    }
}

fn targeted_alu(gb: &mut GameBoy, operation: ALOp, src: ByteRegisterTarget) -> StepResult<()> {
    let arg = gb.exec_read_register_target(src)?;
    let result = alu_op(gb, operation, arg);
    gb.write_register_u8(registers::ByteRegister::A, result);
    Ok(())
}

fn literal_alu(gb: &mut GameBoy, operation: ALOp, arg: u8) -> StepResult<()> {
    let result = alu_op(gb, operation, arg);
    gb.write_register_u8(registers::ByteRegister::A, result);
    Ok(())
}

macro_rules! alu_register_target {
    ($name:ident, $opcode:literal, $label:literal, $op:path) => {
        #[derive(Debug, OlympiaInstruction)]
        #[olympia(opcode = $opcode, label=$label)]
        struct $name {
            #[olympia(single, mask = 0xA)]
            src: ByteRegisterTarget,
        }

        impl ExecutableInstruction for $name {
            fn execute(&self, gb: &mut GameBoy) -> StepResult<()> {
                targeted_alu(gb, $op, self.src)
            }
        }
    };
}

alu_register_target!(AddRegisterTarget, 0x1000_0AAA, "ADD", ALOp::Add);
alu_register_target!(AddCarryRegisterTarget, 0x1000_1AAA, "ADC", ALOp::AddCarry);
alu_register_target!(SubRegisterTarget, 0x1001_0AAA, "SUB", ALOp::Sub);
alu_register_target!(SubCarryRegisterTarget, 0x1001_1AAA, "SBC", ALOp::SubCarry);
alu_register_target!(AndRegisterTarget, 0x1010_0AAA, "AND", ALOp::And);
alu_register_target!(XorRegisterTarget, 0x1010_1AAA, "XOR", ALOp::Xor);
alu_register_target!(OrRegisterTarget, 0x1011_0AAA, "OR", ALOp::Or);
alu_register_target!(CompareRegisterTarget, 0x1011_1AAA, "CP", ALOp::Compare);

macro_rules! alu_literal {
    ($name:ident, $opcode:literal, $label:literal, $op:path) => {
        #[derive(Debug, OlympiaInstruction)]
        #[olympia(opcode = $opcode, label=$label)]
        struct $name {
            #[olympia(single)]
            arg: u8,
        }

        impl ExecutableInstruction for $name {
            fn execute(&self, gb: &mut GameBoy) -> StepResult<()> {
                literal_alu(gb, $op, self.arg)
            }
        }
    };
}

alu_literal!(AddLiteral, 0x1100_0110, "ADD", ALOp::Add);
alu_literal!(AddCarryLiteral, 0x1100_1110, "ADC", ALOp::AddCarry);
alu_literal!(SubLiteral, 0x1101_0110, "SUB", ALOp::Sub);
alu_literal!(SubCarryLiteral, 0x1101_1110, "SBC", ALOp::SubCarry);
alu_literal!(AndLiteral, 0x1110_0110, "AND", ALOp::And);
alu_literal!(XorLiteral, 0x1110_1110, "XOR", ALOp::Xor);
alu_literal!(OrLiteral, 0x1111_0110, "OR", ALOp::Or);
alu_literal!(CompareLiteral, 0x1111_1110, "CP", ALOp::Compare);

#[derive(Debug, OlympiaInstruction)]
#[olympia(opcode = 0x00AA_A100, label = "INC")]
struct Increment {
    #[olympia(dest, mask = 0xA)]
    target: ByteRegisterTarget,
}

impl ExecutableInstruction for Increment {
    fn execute(&self, gb: &mut GameBoy) -> StepResult<()> {
        let reg_value = gb.exec_read_register_target(self.target)?;
        let (new, carry) = reg_value.overflowing_add(1);
        gb.set_flag_to(registers::Flag::Zero, new == 0);
        gb.set_flag_to(registers::Flag::Carry, carry);
        gb.reset_flag(registers::Flag::AddSubtract);
        gb.set_flag_to(registers::Flag::HalfCarry, is_add_half_carry(reg_value, 1));
        gb.exec_write_register_target(self.target, new)?;
        Ok(())
    }
}

#[derive(Debug, OlympiaInstruction)]
#[olympia(opcode = 0x00AA_A101, label = "DEC")]
struct Decrement {
    #[olympia(dest, mask = 0xA)]
    target: ByteRegisterTarget,
}

impl ExecutableInstruction for Decrement {
    fn execute(&self, gb: &mut GameBoy) -> StepResult<()> {
        let reg_value = gb.exec_read_register_target(self.target)?;
        let (new, carry) = reg_value.overflowing_sub(1);
        gb.set_flag_to(registers::Flag::Zero, new == 0);
        gb.set_flag_to(registers::Flag::Carry, carry);
        gb.set_flag(registers::Flag::AddSubtract);
        gb.set_flag_to(registers::Flag::HalfCarry, is_sub_half_carry(reg_value, 1));
        gb.exec_write_register_target(self.target, new)?;
        Ok(())
    }
}

#[derive(Debug, OlympiaInstruction)]
#[olympia(opcode = 0x00AA_0011, label = "INC")]
struct Increment16 {
    #[olympia(dest, mask = 0xA)]
    target: registers::StackRegister,
}

impl ExecutableInstruction for Increment16 {
    fn execute(&self, gb: &mut GameBoy) -> StepResult<()> {
        let reg_value = gb.read_register_u16(self.target.into());
        let (new, _carry) = reg_value.overflowing_add(1);
        gb.write_register_u16(self.target.into(), new);
        gb.cycle();
        Ok(())
    }
}

#[derive(Debug, OlympiaInstruction)]
#[olympia(opcode = 0x00AA_1011, label = "DEC")]
struct Decrement16 {
    #[olympia(dest, mask = 0xA)]
    target: registers::StackRegister,
}

impl ExecutableInstruction for Decrement16 {
    fn execute(&self, gb: &mut GameBoy) -> StepResult<()> {
        let reg_value = gb.read_register_u16(self.target.into());
        let (new, _carry) = reg_value.overflowing_sub(1);
        gb.write_register_u16(self.target.into(), new);
        gb.cycle();
        Ok(())
    }
}

#[derive(Debug, OlympiaInstruction)]
#[olympia(opcode = 0x00AA_1001, label = "ADD")]
struct Add16 {
    #[olympia(dest, constant(registers::WordRegister::HL))]
    dest: registers::WordRegister,
    #[olympia(src, mask = 0xA)]
    src: registers::StackRegister,
}

impl ExecutableInstruction for Add16 {
    fn execute(&self, gb: &mut GameBoy) -> StepResult<()> {
        let current_value = gb.read_register_u16(self.dest);
        let value_to_add = gb.read_register_u16(self.src.into());
        let (new, carry) = current_value.overflowing_add(value_to_add);
        gb.set_flag_to(registers::Flag::Zero, new == 0);
        gb.set_flag_to(registers::Flag::Carry, carry);
        gb.reset_flag(registers::Flag::AddSubtract);
        let has_half_carry = (((current_value & 0x0FFF) + (value_to_add & 0x0FFF)) & 0xF000) != 0;
        gb.set_flag_to(registers::Flag::HalfCarry, has_half_carry);
        gb.write_register_u16(self.dest, new);
        gb.cycle();
        Ok(())
    }
}

pub(crate) fn opcodes() -> Vec<(u8, Box<dyn RuntimeOpcode>)> {
    vec![
        AddRegisterTargetOpcode::all(),
        AddCarryRegisterTargetOpcode::all(),
        SubRegisterTargetOpcode::all(),
        SubCarryRegisterTargetOpcode::all(),
        AndRegisterTargetOpcode::all(),
        XorRegisterTargetOpcode::all(),
        OrRegisterTargetOpcode::all(),
        CompareRegisterTargetOpcode::all(),
        AddLiteralOpcode::all(),
        AddCarryLiteralOpcode::all(),
        SubLiteralOpcode::all(),
        SubCarryLiteralOpcode::all(),
        AndLiteralOpcode::all(),
        XorLiteralOpcode::all(),
        OrLiteralOpcode::all(),
        CompareLiteralOpcode::all(),
        IncrementOpcode::all(),
        DecrementOpcode::all(),
        Increment16Opcode::all(),
        Decrement16Opcode::all(),
        Add16Opcode::all(),
    ]
    .into_iter()
    .flatten()
    .collect()
}