Skip to content
Snippets Groups Projects
cpu.rs 19.6 KiB
Newer Older
  • Learn to ignore specific revisions
  • use std::{cell::RefCell, rc::Rc};
    
    
        apu::Apu,
    
        debugln,
    
        gb::GameBoyConfig,
    
        inst::{EXTENDED, INSTRUCTIONS},
    
        ppu::Ppu,
    
        timer::Timer,
    
    pub const PREFIX: u8 = 0xcb;
    
    
    pub struct Cpu {
    
        pub pc: u16,
        pub sp: u16,
        pub a: u8,
        pub b: u8,
        pub c: u8,
        pub d: u8,
        pub e: u8,
        pub h: u8,
        pub l: u8,
    
        zero: bool,
        sub: bool,
        half_carry: bool,
        carry: bool,
    
        halted: bool,
    
    
        /// Reference to the MMU (Memory Management Unit) to be used
        /// for memory bus access operations.
    
    João Magalhães's avatar
    João Magalhães committed
    
        /// Temporary counter used to control the number of cycles
        /// taken by the current or last CPU operation.
        pub cycles: u8,
    
    
        /// The pointer to the parent configuration of the running
        /// Game Boy emulator, that can be used to control the behaviour
        /// of Game Boy emulation.
        gbc: Rc<RefCell<GameBoyConfig>>,
    
    João Magalhães's avatar
    João Magalhães committed
    }
    
    impl Cpu {
    
        pub fn new(mmu: Mmu, gbc: Rc<RefCell<GameBoyConfig>>) -> Self {
    
                pc: 0x0,
                sp: 0x0,
    
                a: 0x0,
                b: 0x0,
                c: 0x0,
                d: 0x0,
                e: 0x0,
                h: 0x0,
                l: 0x0,
    
                ime: false,
    
                zero: false,
                sub: false,
                half_carry: false,
                carry: false,
    
                halted: false,
    
    João Magalhães's avatar
    João Magalhães committed
                cycles: 0,
    
    João Magalhães's avatar
    João Magalhães committed
                gbc,
    
        pub fn reset(&mut self) {
            self.pc = 0x0;
            self.sp = 0x0;
            self.a = 0x0;
            self.b = 0x0;
            self.c = 0x0;
            self.d = 0x0;
            self.e = 0x0;
            self.h = 0x0;
            self.l = 0x0;
            self.ime = false;
            self.zero = false;
            self.sub = false;
            self.half_carry = false;
            self.carry = false;
            self.halted = false;
    
    João Magalhães's avatar
    João Magalhães committed
            self.cycles = 0;
    
        /// Sets the CPU registers and some of the memory space to the
        /// state expected after the Game Boy boot ROM executes, using
        /// these values its possible to skip the boot loading process.
        pub fn boot(&mut self) {
            self.pc = 0x0100;
            self.sp = 0xfffe;
            self.a = 0x01;
            self.b = 0xff;
            self.c = 0x13;
            self.d = 0x00;
            self.e = 0xc1;
            self.h = 0x84;
            self.l = 0x03;
            self.zero = false;
            self.sub = false;
            self.half_carry = false;
            self.carry = false;
    
            // updates part of the MMU state, disabling the
            // boot memory overlap and setting the LCD control
            // register to enabled (required by some ROMs)
            self.mmu.set_boot_active(false);
            self.mmu.write(0xff40, 0x91);
        }
    
    
        pub fn clock(&mut self) -> u8 {
    
            // gathers the PC (program counter) reference that
            // is going to be used in the fetching phase
    
            let pc = self.pc;
    
    
            #[cfg(feature = "debug")]
    
    João Magalhães's avatar
    João Magalhães committed
            if (0x8000..0x9fff).contains(&pc) {
    
    João Magalhães's avatar
    João Magalhães committed
                panic!("Invalid PC area at 0x{:04x}", pc);
            }
    
    
            // @TODO this is so bad, need to improve this by an order
    
            // of magnitude, to be able to have better performance
    
            // in case the CPU execution halted and there's an interrupt
            // to be handled, releases the CPU from the halted state
    
            // this verification is only done in case the IME (interrupt
            // master enable) is disabled, otherwise the CPU halt disabled
            // is going to be handled ahead
    
            if self.halted
    
                && self.mmu.ie != 0x00
    
                && (((self.mmu.ie & 0x01 == 0x01) && self.mmu.ppu().int_vblank())
    
                    || ((self.mmu.ie & 0x02 == 0x02) && self.mmu.ppu().int_stat())
    
                    || ((self.mmu.ie & 0x04 == 0x04) && self.mmu.timer().int_tima())
    
                    || ((self.mmu.ie & 0x08 == 0x08) && self.mmu.serial().int_serial())
    
                    || ((self.mmu.ie & 0x10 == 0x10) && self.mmu.pad().int_pad()))
            {
                self.halted = false;
    
            // checks the IME (interrupt master enable) is enabled and then checks
    
            // if there's any interrupt to be handled, in case there's one, tries
            // to check which one should be handled and then handles it
            // this code assumes that the're no more that one interrupt triggered
            // per clock cycle, this is a limitation of the current implementation
    
            if self.ime && self.mmu.ie != 0x00 {
    
                if (self.mmu.ie & 0x01 == 0x01) && self.mmu.ppu().int_vblank() {
    
                    debugln!("Going to run V-Blank interrupt handler (0x40)");
    
                    self.disable_int();
                    self.push_word(pc);
                    self.pc = 0x40;
    
    
                    // acknowledges that the V-Blank interrupt has been
                    // properly handled
    
                    self.mmu.ppu().ack_vblank();
    
    João Magalhães's avatar
    João Magalhães committed
    
                    // in case the CPU is currently halted waiting
                    // for an interrupt, releases it
                    if self.halted {
                        self.halted = false;
                    }
    
    
    João Magalhães's avatar
    João Magalhães committed
                    return 24;
    
                } else if (self.mmu.ie & 0x02 == 0x02) && self.mmu.ppu().int_stat() {
    
                    debugln!("Going to run LCD STAT interrupt handler (0x48)");
    
                    self.disable_int();
                    self.push_word(pc);
                    self.pc = 0x48;
    
                    // acknowledges that the STAT interrupt has been
                    // properly handled
                    self.mmu.ppu().ack_stat();
    
                    // in case the CPU is currently halted waiting
                    // for an interrupt, releases it
                    if self.halted {
                        self.halted = false;
                    }
    
    
    João Magalhães's avatar
    João Magalhães committed
                    return 24;
    
                } else if (self.mmu.ie & 0x04 == 0x04) && self.mmu.timer().int_tima() {
    
                    debugln!("Going to run Timer interrupt handler (0x50)");
    
    
                    self.disable_int();
                    self.push_word(pc);
                    self.pc = 0x50;
    
                    // acknowledges that the timer interrupt has been
                    // properly handled
                    self.mmu.timer().ack_tima();
    
                    // in case the CPU is currently halted waiting
                    // for an interrupt, releases it
                    if self.halted {
    
                        self.halted = false;
                    }
    
                    return 24;
    
                } else if (self.mmu.ie & 0x08 == 0x08) && self.mmu.serial().int_serial() {
    
                    debugln!("Going to run Serial interrupt handler (0x58)");
    
                    self.disable_int();
                    self.push_word(pc);
                    self.pc = 0x58;
    
                    // acknowledges that the serial interrupt has been
                    // properly handled
                    self.mmu.serial().ack_serial();
    
                    // in case the CPU is currently halted waiting
                    // for an interrupt, releases it
                    if self.halted {
    
                        self.halted = false;
                    }
    
    
    João Magalhães's avatar
    João Magalhães committed
                    return 24;
    
                } else if (self.mmu.ie & 0x10 == 0x10) && self.mmu.pad().int_pad() {
    
                    debugln!("Going to run JoyPad interrupt handler (0x60)");
    
                    self.disable_int();
                    self.push_word(pc);
                    self.pc = 0x60;
    
                    // acknowledges that the pad interrupt has been
                    // properly handled
                    self.mmu.pad().ack_pad();
    
                    // in case the CPU is currently halted waiting
                    // for an interrupt, releases it
                    if self.halted {
                        self.halted = false;
                    }
    
    
    João Magalhães's avatar
    João Magalhães committed
                    return 24;
    
            // in case the CPU is currently in the halted state
            // returns the control flow immediately with the associated
            // number of cycles estimated for the halted execution
            if self.halted {
                return 4;
            }
    
    
            // fetches the current instruction and increments
            // the PC (program counter) accordingly
    
            let mut opcode = self.mmu.read(self.pc);
            self.pc = self.pc.wrapping_add(1);
    
    
            let is_prefix = opcode == PREFIX;
    
    João Magalhães's avatar
    João Magalhães committed
            let inst: &(fn(&mut Cpu), u8, &str);
    
            if is_prefix {
    
                opcode = self.mmu.read(self.pc);
                self.pc = self.pc.wrapping_add(1);
    
    João Magalhães's avatar
    João Magalhães committed
                inst = &EXTENDED[opcode as usize];
    
    João Magalhães's avatar
    João Magalhães committed
                inst = &INSTRUCTIONS[opcode as usize];
    
            #[allow(unused_variables)]
    
    João Magalhães's avatar
    João Magalhães committed
            let (inst_fn, inst_time, inst_str) = inst;
    
            #[cfg(feature = "cpulog")]
    
    João Magalhães's avatar
    João Magalhães committed
            if *inst_str == "! UNIMP !" || *inst_str == "HALT" {
                if *inst_str == "HALT" {
    
                    debugln!("HALT with IE=0x{:02x} IME={}", self.mmu.ie, self.ime);
    
                    "{}\t(0x{:02x})\t${:04x} {}",
    
    João Magalhães's avatar
    João Magalhães committed
                    inst_str,
    
                    opcode,
                    pc,
                    is_prefix
    
            #[cfg(feature = "cpulog")]
    
                let title_str = format!("[0x{:04x}] {}", self.pc - 1, inst_str);
                let inst_time_str = format!("({} cycles)", inst_time);
                let registers_str = format!("[PC=0x{:04x} SP=0x{:04x}] [A=0x{:02x} B=0x{:02x} C=0x{:02x} D=0x{:02x} E=0x{:02x} H=0x{:02x} L=0x{:02x}]",
                self.pc, self.sp, self.a, self.b, self.c, self.d, self.e, self.h, self.l);
    
    João Magalhães's avatar
    João Magalhães committed
                println!(
    
                    "{0: <24} {1: <11} {2: <10}",
                    title_str, inst_time_str, registers_str
    
            #[cfg(feature = "pedantic")]
    
            if self.mmu.boot_active() && self.pc - 1 > 0x08ff {
                panic!("Invalid boot address: {:04x}", self.pc - 1);
            }
    
    João Magalhães's avatar
    João Magalhães committed
            // calls the current instruction and increments the number of
            // cycles executed by the instruction time of the instruction
            // that has just been executed
    
    João Magalhães's avatar
    João Magalhães committed
            self.cycles = 0;
            inst_fn(self);
            self.cycles = self.cycles.wrapping_add(*inst_time);
    
            // returns the number of cycles that the operation
            // that has been executed has taken
    
    João Magalhães's avatar
    João Magalhães committed
            self.cycles
    
        #[inline(always)]
        pub fn mmu(&mut self) -> &mut Mmu {
            &mut self.mmu
        }
    
    
        #[inline(always)]
        pub fn mmu_i(&self) -> &Mmu {
            &self.mmu
        }
    
    
        #[inline(always)]
        pub fn ppu(&mut self) -> &mut Ppu {
            self.mmu().ppu()
        }
    
    
        #[inline(always)]
        pub fn ppu_i(&self) -> &Ppu {
            self.mmu_i().ppu_i()
        }
    
    
        #[inline(always)]
        pub fn apu(&mut self) -> &mut Apu {
            self.mmu().apu()
        }
    
    
        #[inline(always)]
        pub fn apu_i(&self) -> &Apu {
            self.mmu_i().apu_i()
        }
    
    
        #[inline(always)]
        pub fn dma(&mut self) -> &mut Dma {
            self.mmu().dma()
        }
    
        #[inline(always)]
        pub fn dma_i(&self) -> &Dma {
            self.mmu_i().dma_i()
        }
    
    
        #[inline(always)]
        pub fn pad(&mut self) -> &mut Pad {
            self.mmu().pad()
        }
    
        #[inline(always)]
        pub fn pad_i(&self) -> &Pad {
            self.mmu_i().pad_i()
        }
    
    
        #[inline(always)]
        pub fn timer(&mut self) -> &mut Timer {
            self.mmu().timer()
        }
    
        #[inline(always)]
        pub fn timer_i(&self) -> &Timer {
            self.mmu_i().timer_i()
        }
    
    
        #[inline(always)]
        pub fn serial(&mut self) -> &mut Serial {
            self.mmu().serial()
        }
    
    
        #[inline(always)]
        pub fn serial_i(&self) -> &Serial {
            self.mmu_i().serial_i()
        }
    
    
        #[inline(always)]
        pub fn halted(&self) -> bool {
            self.halted
        }
    
    
        #[inline(always)]
    
    João Magalhães's avatar
    João Magalhães committed
        pub fn cycles(&self) -> u8 {
            self.cycles
    
        #[inline(always)]
        pub fn pc(&self) -> u16 {
            self.pc
        }
    
    
        #[inline(always)]
        pub fn set_pc(&mut self, value: u16) {
            self.pc = value;
        }
    
    
        #[inline(always)]
        pub fn sp(&self) -> u16 {
            self.sp
        }
    
    
        #[inline(always)]
        pub fn set_sp(&mut self, value: u16) {
            self.sp = value;
        }
    
    
    João Magalhães's avatar
    João Magalhães committed
        #[inline(always)]
    
        pub fn af(&self) -> u16 {
    
            (self.a as u16) << 8 | self.f() as u16
    
    João Magalhães's avatar
    João Magalhães committed
        }
    
        #[inline(always)]
    
        pub fn bc(&self) -> u16 {
    
            (self.b as u16) << 8 | self.c as u16
    
    João Magalhães's avatar
    João Magalhães committed
        }
    
        #[inline(always)]
    
        pub fn f(&self) -> u8 {
    
            let mut f = 0x0u8;
            if self.zero {
                f |= 0x80;
            }
            if self.sub {
                f |= 0x40;
            }
            if self.half_carry {
                f |= 0x20;
            }
            if self.carry {
                f |= 0x10;
            }
            f
        }
    
    
        #[inline(always)]
        pub fn set_f(&mut self, value: u8) {
            self.zero = value & 0x80 == 0x80;
            self.sub = value & 0x40 == 0x40;
            self.half_carry = value & 0x20 == 0x20;
            self.carry = value & 0x10 == 0x10;
        }
    
        #[inline(always)]
        pub fn set_af(&mut self, value: u16) {
            self.a = (value >> 8) as u8;
            self.set_f(value as u8);
        }
    
    
        #[inline(always)]
    
        pub fn set_bc(&mut self, value: u16) {
    
            self.b = (value >> 8) as u8;
            self.c = value as u8;
        }
    
        #[inline(always)]
    
        pub fn de(&self) -> u16 {
    
            (self.d as u16) << 8 | self.e as u16
        }
    
        #[inline(always)]
    
        pub fn set_de(&mut self, value: u16) {
    
            self.d = (value >> 8) as u8;
            self.e = value as u8;
    
    João Magalhães's avatar
    João Magalhães committed
        }
    
        #[inline(always)]
    
        pub fn hl(&self) -> u16 {
    
            (self.h as u16) << 8 | self.l as u16
        }
    
        #[inline(always)]
    
        pub fn set_hl(&mut self, value: u16) {
    
            self.h = (value >> 8) as u8;
            self.l = value as u8;
    
        #[inline(always)]
        pub fn ime(&self) -> bool {
            self.ime
        }
    
        #[inline(always)]
        pub fn set_ime(&mut self, value: bool) {
            self.ime = value;
        }
    
    
    João Magalhães's avatar
    João Magalhães committed
        #[inline(always)]
    
        pub fn read_u8(&mut self) -> u8 {
    
            let byte = self.mmu.read(self.pc);
    
            self.pc = self.pc.wrapping_add(1);
    
        pub fn read_u16(&mut self) -> u16 {
    
            let byte1 = self.read_u8();
            let byte2 = self.read_u8();
    
    João Magalhães's avatar
    João Magalhães committed
            byte1 as u16 | ((byte2 as u16) << 8)
    
        #[inline(always)]
    
        pub fn push_byte(&mut self, byte: u8) {
    
            self.sp = self.sp.wrapping_sub(1);
    
            self.mmu.write(self.sp, byte);
        }
    
        #[inline(always)]
    
        pub fn push_word(&mut self, word: u16) {
    
            self.push_byte((word >> 8) as u8);
            self.push_byte(word as u8);
        }
    
    
        #[inline(always)]
    
        pub fn pop_byte(&mut self) -> u8 {
    
            let byte = self.mmu.read(self.sp);
    
            self.sp = self.sp.wrapping_add(1);
    
            byte
        }
    
        #[inline(always)]
    
        pub fn pop_word(&mut self) -> u16 {
    
    João Magalhães's avatar
    João Magalhães committed
            self.pop_byte() as u16 | ((self.pop_byte() as u16) << 8)
    
        #[inline(always)]
    
        pub fn zero(&self) -> bool {
    
            self.zero
        }
    
        #[inline(always)]
    
        pub fn set_zero(&mut self, value: bool) {
    
            self.zero = value
    
    João Magalhães's avatar
    João Magalhães committed
        }
    
        #[inline(always)]
    
        pub fn sub(&self) -> bool {
    
            self.sub
        }
    
        #[inline(always)]
    
        pub fn set_sub(&mut self, value: bool) {
    
            self.sub = value;
    
    João Magalhães's avatar
    João Magalhães committed
        }
    
        #[inline(always)]
    
        pub fn half_carry(&self) -> bool {
    
            self.half_carry
        }
    
        #[inline(always)]
    
        pub fn set_half_carry(&mut self, value: bool) {
    
            self.half_carry = value
    
    João Magalhães's avatar
    João Magalhães committed
        }
    
        #[inline(always)]
    
        pub fn carry(&self) -> bool {
    
            self.carry
        }
    
        #[inline(always)]
    
        pub fn set_carry(&mut self, value: bool) {
    
            self.carry = value;
    
    João Magalhães's avatar
    João Magalhães committed
        }
    
        #[inline(always)]
        pub fn halt(&mut self) {
            self.halted = true;
        }
    
    
        #[inline(always)]
        pub fn stop(&mut self) {
    
            let mmu = self.mmu();
            if mmu.switching {
    
        #[inline(always)]
        pub fn enable_int(&mut self) {
    
            self.ime = true;
    
        }
    
        #[inline(always)]
        pub fn disable_int(&mut self) {
    
            self.ime = false;
    
    
        pub fn set_gbc(&mut self, value: Rc<RefCell<GameBoyConfig>>) {
            self.gbc = value;
        }
    
    
    impl Default for Cpu {
        fn default() -> Self {
            let gbc: Rc<RefCell<GameBoyConfig>> = Rc::new(RefCell::new(GameBoyConfig::default()));
    
    João Magalhães's avatar
    João Magalhães committed
            Cpu::new(Mmu::default(), gbc)
    
        }
    }
    
    #[cfg(test)]
    mod tests {
    
        use super::Cpu;
    
    
        #[test]
        fn test_cpu_clock() {
            let mut cpu = Cpu::default();
            cpu.boot();
            cpu.mmu.allocate_default();
    
            // test NOP instruction
            cpu.pc = 0xc000;
            cpu.mmu.write(0xc000, 0x00);
            let cycles = cpu.clock();
            assert_eq!(cycles, 4);
            assert_eq!(cpu.pc, 0xc001);
    
            // test LD A, d8 instruction
            cpu.pc = 0xc000;
            cpu.mmu.write(0xc000, 0x3e);
            cpu.mmu.write(0xc001, 0x42);
            let cycles = cpu.clock();
            assert_eq!(cycles, 8);
            assert_eq!(cpu.pc, 0xc002);
            assert_eq!(cpu.a, 0x42);
    
            // test LD (HL+), A instruction
            cpu.pc = 0xc000;
            cpu.mmu.write(0xc000, 0x22);
            cpu.set_hl(0xc000);
            let cycles = cpu.clock();
            assert_eq!(cycles, 8);
            assert_eq!(cpu.pc, 0xc001);
            assert_eq!(cpu.hl(), 0xc001);
            assert_eq!(cpu.mmu.read(cpu.hl()), 0x42);
    
            // test INC A instruction
            cpu.pc = 0xc000;
            cpu.mmu.write(0xc000, 0x3c);
            cpu.a = 0x42;
            let cycles = cpu.clock();
            assert_eq!(cycles, 4);
            assert_eq!(cpu.pc, 0xc001);
            assert_eq!(cpu.a, 0x43);
    
            // test DEC A instruction
            cpu.pc = 0xc000;
            cpu.mmu.write(0xc000, 0x3d);
            cpu.a = 0x42;
            let cycles = cpu.clock();
            assert_eq!(cycles, 4);
            assert_eq!(cpu.pc, 0xc001);
            assert_eq!(cpu.a, 0x41);
    
            // test LD A, (HL) instruction
            cpu.pc = 0xc000;
            cpu.mmu.write(0xc000, 0x7e);
            cpu.set_hl(0xc001);
            cpu.mmu.write(0xc001, 0x42);
            let cycles = cpu.clock();
            assert_eq!(cycles, 8);
            assert_eq!(cpu.pc, 0xc001);
            assert_eq!(cpu.a, 0x42);
            assert_eq!(cpu.hl(), 0xc001);
    
            // test LD (HL), d8 instruction
            cpu.pc = 0xc000;
            cpu.mmu.write(0xc000, 0x36);
            cpu.set_hl(0xc000);
            cpu.mmu.write(0xc001, 0x42);
            let cycles = cpu.clock();
            assert_eq!(cycles, 12);
            assert_eq!(cpu.pc, 0xc002);
            assert_eq!(cpu.hl(), 0xc000);
            assert_eq!(cpu.mmu.read(cpu.hl()), 0x42);
    
            // test JR n instruction
            cpu.pc = 0xc000;
            cpu.mmu.write(0xc000, 0x18);
            cpu.mmu.write(0xc001, 0x03);
            let cycles = cpu.clock();
            assert_eq!(cycles, 12);
            assert_eq!(cpu.pc, 0xc005);
    
            // test ADD A, d8 instruction
            cpu.pc = 0xc000;
            cpu.mmu.write(0xc000, 0xc6);
            cpu.mmu.write(0xc001, 0x01);
            cpu.a = 0x42;
            let cycles = cpu.clock();
            assert_eq!(cycles, 8);
            assert_eq!(cpu.pc, 0xc002);
            assert_eq!(cpu.a, 0x43);
    
            // test SUB A, d8 instruction
            cpu.pc = 0xc000;
            cpu.mmu.write(0xc000, 0xd6);
            cpu.mmu.write(0xc001, 0x01);
            cpu.a = 0x42;
            let cycles = cpu.clock();
            assert_eq!(cycles, 8);
            assert_eq!(cpu.pc, 0xc002);
            assert_eq!(cpu.a, 0x41);
    
            // test AND A, d8 instruction
            cpu.pc = 0xc000;
            cpu.mmu.write(0xc000, 0xe6);
            cpu.mmu.write(0xc001, 0x0f);
            cpu.a = 0x0a;
            let cycles = cpu.clock();
            assert_eq!(cycles, 8);
            assert_eq!(cpu.pc, 0xc002);
            assert_eq!(cpu.a, 0x0a & 0x0f);
    
            // test OR A, d8 instruction
            cpu.pc = 0xc000;
            cpu.mmu.write(0xc000, 0xf6);
            cpu.mmu.write(0xc001, 0x0f);
            cpu.a = 0x0a;
            let cycles = cpu.clock();
            assert_eq!(cycles, 8);
            assert_eq!(cpu.pc, 0xc002);
            assert_eq!(cpu.a, 0x0a | 0x0f);
    
            // test XOR A, d8 instruction
            cpu.pc = 0xc000;
            cpu.mmu.write(0xc000, 0xee);
            cpu.mmu.write(0xc001, 0x0f);
            cpu.a = 0x0a;
            let cycles = cpu.clock();
            assert_eq!(cycles, 8);
            assert_eq!(cpu.pc, 0xc002);
            assert_eq!(cpu.a, 0x0a ^ 0x0f);
        }
    }