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:
		
							
								
								
									
										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
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user