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:
parent
c16f318fd1
commit
a82e3d3372
10
src/bus.rs
10
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 {
|
||||
|
|
70
src/cpu.rs
70
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<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,
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue