feat: implement HALT behaviour

note: while the logic is there, the instruction currently does not do
anything because we don't halde it in Cpu::step(). The code that does is
currently commented out and there should be some underlying bugs still
present. Nevertheless it is a good start
This commit is contained in:
Rekai Nyangadzayi Musuka 2021-03-23 23:05:27 -05:00
parent c16f318fd1
commit a82e3d3372
3 changed files with 94 additions and 12 deletions

View File

@ -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 {

View File

@ -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<HaltState>,
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<HaltState> {
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<u8> for Flags {
Self(byte)
}
}
#[derive(Debug, Clone, Copy)]
pub enum HaltState {
ImeSet,
NonePending,
SomePending,
}

View File

@ -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