diff --git a/src/bus.rs b/src/bus.rs index 6bfb063..06713ff 100644 --- a/src/bus.rs +++ b/src/bus.rs @@ -146,6 +146,10 @@ impl Bus { 0xFF49 => self.ppu.monochrome.obj_palette_1.into(), 0xFF4A => self.ppu.pos.window_y, 0xFF4B => self.ppu.pos.window_x, + 0xFF4D => { + eprintln!("Reading from {:#06X} is available in CGB Mode only", addr); + 0x00 + } _ => unimplemented!("Unable to read {:#06X} in I/O Registers", addr), } } @@ -242,6 +246,12 @@ impl Bus { 0xFF49 => self.ppu.monochrome.obj_palette_1 = byte.into(), 0xFF4A => self.ppu.pos.window_y = byte, 0xFF4B => self.ppu.pos.window_x = byte, + 0xFF4D => { + eprintln!( + "Writing {:#04X} to {:#06X} is available in CGB Mode only", + byte, addr + ); + } 0xFF50 => { // Disable Boot ROM if byte != 0 { diff --git a/src/cpu.rs b/src/cpu.rs index cb7e41b..8ae2231 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -1,5 +1,5 @@ use super::bus::Bus; -use super::instruction::{Cycles, Instruction, JumpCondition}; +use super::instruction::{Cycles, Instruction}; use super::interrupt::{InterruptEnable, InterruptFlag}; use super::ppu::Ppu; use bitfield::bitfield; @@ -11,6 +11,7 @@ pub struct Cpu { reg: Registers, flags: Flags, ime: bool, + halted: Option, state: State, } @@ -48,6 +49,20 @@ impl Cpu { self.ime = enabled; } + pub fn halt(&mut self, state: HaltState) { + self.halted = Some(state); + } + + fn resume(&mut self) { + println!("Game Boy resumed from {:?}", self.halted); + + self.halted = None; + } + + pub fn halted(&self) -> Option { + self.halted + } + pub fn inc_pc(&mut self) { self.reg.pc += 1; } @@ -58,11 +73,8 @@ impl Cpu { } impl Cpu { - pub fn fetch(&mut self) -> u8 { - let opcode = self.bus.read_byte(self.reg.pc); - self.inc_pc(); - - opcode + pub fn fetch(&self) -> u8 { + self.bus.read_byte(self.reg.pc) } pub fn decode(&mut self, opcode: u8) -> Instruction { @@ -74,7 +86,24 @@ impl Cpu { } pub fn step(&mut self) -> Cycles { + // if let Some(state) = self.halted() { + // use HaltState::*; + + // match state { + // ImeSet | NonePending => Cycles::new(4), + // SomePending => { + // todo!("Implement HALT Bug"); + // } + // } + // }; + + if self.reg.pc > 0x100 { + self.log_state().unwrap(); + } + let opcode = self.fetch(); + self.inc_pc(); + let instr = self.decode(opcode); let cycles = self.execute(instr); @@ -120,11 +149,25 @@ impl Cpu { } pub fn handle_interrupts(&mut self) { - if self.ime() { - use JumpCondition::Always; + let req = self.read_byte(0xFF0F); + let enabled = self.read_byte(0xFFFF); - let mut req: InterruptFlag = self.read_byte(0xFF0F).into(); - let mut enabled: InterruptEnable = self.read_byte(0xFFFF).into(); + if let Some(_) = self.halted() { + // When we're here either a HALT with IME set or + // a HALT with IME not set and No pending Interrupts was called + + if req & enabled != 0 { + // The if self.ime() below correctly follows the "resuming from HALT" behaviour so + // nothing actually needs to be added here. This is just documentation + // since it's a bit weird why nothing is being done + + self.resume() + } + } + + if self.ime() { + let mut req: InterruptFlag = req.into(); + let mut enabled: InterruptEnable = enabled.into(); let vector = if req.vblank() && enabled.vblank() { // Handle VBlank Interrupt @@ -407,3 +450,10 @@ impl From for Flags { Self(byte) } } + +#[derive(Debug, Clone, Copy)] +pub enum HaltState { + ImeSet, + NonePending, + SomePending, +} diff --git a/src/instruction.rs b/src/instruction.rs index 3e23800..16ec1bf 100644 --- a/src/instruction.rs +++ b/src/instruction.rs @@ -1,4 +1,4 @@ -use super::cpu::{Cpu, Flags, Register, RegisterPair}; +use super::cpu::{Cpu, Flags, HaltState, Register, RegisterPair}; use std::{convert::TryFrom, fmt::Debug}; #[derive(Debug, Copy, Clone)] @@ -577,7 +577,29 @@ impl Instruction { cpu.set_flags(flags); Cycles::new(4) } - Instruction::HALT => todo!("Implement HALT instruction"), + Instruction::HALT => { + // Enter CPU low power consumption mode until interrupt occurs + use HaltState::*; + + let req = cpu.read_byte(0xFF0F); + let enabled = cpu.read_byte(0xFFFF); + + let halt_state = if cpu.ime() { + ImeSet + } else { + if req & enabled != 0 { + SomePending + } else { + NonePending + } + }; + + println!("Game Boy HALTed in {:?}", halt_state); + cpu.halt(halt_state); + + // Though this can actually last forever + Cycles::new(4) + } Instruction::ADC(target) => match target { MATHTarget::Register(reg) => { // ADC A, r[z] | Add register r[z] plus the Carry flag to A