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(),
|
0xFF49 => self.ppu.monochrome.obj_palette_1.into(),
|
||||||
0xFF4A => self.ppu.pos.window_y,
|
0xFF4A => self.ppu.pos.window_y,
|
||||||
0xFF4B => self.ppu.pos.window_x,
|
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),
|
_ => 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(),
|
0xFF49 => self.ppu.monochrome.obj_palette_1 = byte.into(),
|
||||||
0xFF4A => self.ppu.pos.window_y = byte,
|
0xFF4A => self.ppu.pos.window_y = byte,
|
||||||
0xFF4B => self.ppu.pos.window_x = byte,
|
0xFF4B => self.ppu.pos.window_x = byte,
|
||||||
|
0xFF4D => {
|
||||||
|
eprintln!(
|
||||||
|
"Writing {:#04X} to {:#06X} is available in CGB Mode only",
|
||||||
|
byte, addr
|
||||||
|
);
|
||||||
|
}
|
||||||
0xFF50 => {
|
0xFF50 => {
|
||||||
// Disable Boot ROM
|
// Disable Boot ROM
|
||||||
if byte != 0 {
|
if byte != 0 {
|
||||||
|
|
70
src/cpu.rs
70
src/cpu.rs
|
@ -1,5 +1,5 @@
|
||||||
use super::bus::Bus;
|
use super::bus::Bus;
|
||||||
use super::instruction::{Cycles, Instruction, JumpCondition};
|
use super::instruction::{Cycles, Instruction};
|
||||||
use super::interrupt::{InterruptEnable, InterruptFlag};
|
use super::interrupt::{InterruptEnable, InterruptFlag};
|
||||||
use super::ppu::Ppu;
|
use super::ppu::Ppu;
|
||||||
use bitfield::bitfield;
|
use bitfield::bitfield;
|
||||||
|
@ -11,6 +11,7 @@ pub struct Cpu {
|
||||||
reg: Registers,
|
reg: Registers,
|
||||||
flags: Flags,
|
flags: Flags,
|
||||||
ime: bool,
|
ime: bool,
|
||||||
|
halted: Option<HaltState>,
|
||||||
state: State,
|
state: State,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,6 +49,20 @@ impl Cpu {
|
||||||
self.ime = enabled;
|
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) {
|
pub fn inc_pc(&mut self) {
|
||||||
self.reg.pc += 1;
|
self.reg.pc += 1;
|
||||||
}
|
}
|
||||||
|
@ -58,11 +73,8 @@ impl Cpu {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Cpu {
|
impl Cpu {
|
||||||
pub fn fetch(&mut self) -> u8 {
|
pub fn fetch(&self) -> u8 {
|
||||||
let opcode = self.bus.read_byte(self.reg.pc);
|
self.bus.read_byte(self.reg.pc)
|
||||||
self.inc_pc();
|
|
||||||
|
|
||||||
opcode
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn decode(&mut self, opcode: u8) -> Instruction {
|
pub fn decode(&mut self, opcode: u8) -> Instruction {
|
||||||
|
@ -74,7 +86,24 @@ impl Cpu {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn step(&mut self) -> Cycles {
|
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();
|
let opcode = self.fetch();
|
||||||
|
self.inc_pc();
|
||||||
|
|
||||||
let instr = self.decode(opcode);
|
let instr = self.decode(opcode);
|
||||||
let cycles = self.execute(instr);
|
let cycles = self.execute(instr);
|
||||||
|
|
||||||
|
@ -120,11 +149,25 @@ impl Cpu {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_interrupts(&mut self) {
|
pub fn handle_interrupts(&mut self) {
|
||||||
if self.ime() {
|
let req = self.read_byte(0xFF0F);
|
||||||
use JumpCondition::Always;
|
let enabled = self.read_byte(0xFFFF);
|
||||||
|
|
||||||
let mut req: InterruptFlag = self.read_byte(0xFF0F).into();
|
if let Some(_) = self.halted() {
|
||||||
let mut enabled: InterruptEnable = self.read_byte(0xFFFF).into();
|
// 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() {
|
let vector = if req.vblank() && enabled.vblank() {
|
||||||
// Handle VBlank Interrupt
|
// Handle VBlank Interrupt
|
||||||
|
@ -407,3 +450,10 @@ impl From<u8> for Flags {
|
||||||
Self(byte)
|
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};
|
use std::{convert::TryFrom, fmt::Debug};
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
@ -577,7 +577,29 @@ impl Instruction {
|
||||||
cpu.set_flags(flags);
|
cpu.set_flags(flags);
|
||||||
Cycles::new(4)
|
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 {
|
Instruction::ADC(target) => match target {
|
||||||
MATHTarget::Register(reg) => {
|
MATHTarget::Register(reg) => {
|
||||||
// ADC A, r[z] | Add register r[z] plus the Carry flag to A
|
// ADC A, r[z] | Add register r[z] plus the Carry flag to A
|
||||||
|
|
Loading…
Reference in New Issue