diff --git a/src/bus.rs b/src/bus.rs index b1948a4..98bc25a 100644 --- a/src/bus.rs +++ b/src/bus.rs @@ -6,7 +6,7 @@ use crate::utils::{ }; use crate::rom::ROM; use crate::ppu::{PPU, LCDStatus, LCDStatusModeFlag}; -use crate::cpu::{InterruptFlag}; +use crate::cpu::{Interrupt}; pub struct AddressRange { begin: u16, @@ -39,6 +39,7 @@ pub const NOT_USABLE: AddressRange = AddressRange{begin: 0xFEA0, pub const IO_REGISTERS: AddressRange = AddressRange{begin: 0xFF00, end: 0xFF7F}; pub const HIGH_RAM: AddressRange = AddressRange{begin: 0xFF80, end: 0xFFFE}; pub const INTERRUPT_ENABLE_REGISTER: AddressRange = AddressRange{begin: 0xFFFF, end: 0xFFFF}; +pub const INTERRUPT_ENABLE_ADDRESS: u16 = 0xFFFF; pub const INTERRUPT_FLAG_ADDRESS: u16 = 0xFF0F; pub struct Bus { @@ -51,6 +52,7 @@ impl Bus { let game_rom = match ROM::load_file("ignore/tetris.gb".to_string()) { // let game_rom = match ROM::load_file("roms/cpu_instrs.gb".to_string()) { // let game_rom = match ROM::load_file("roms/cpu_instrs_individual/01-special.gb".to_string()) { + // let game_rom = match ROM::load_file("roms/cpu_instrs_individual/02-interrupts.gb".to_string()) { // let game_rom = match ROM::load_file("roms/cpu_instrs_individual/03-op sp,hl.gb".to_string()) { // let game_rom = match ROM::load_file("roms/cpu_instrs_individual/04-op r,imm.gb".to_string()) { // let game_rom = match ROM::load_file("roms/cpu_instrs_individual/05-op rp.gb".to_string()) { @@ -75,7 +77,7 @@ impl Bus { return self.game_rom.read(address); } else if VIDEO_RAM.in_range(address) { if PPU::get_lcd_status(self, LCDStatus::ModeFlag(LCDStatusModeFlag::TransferringToLCD)) { - return 0xFF + return 0xFF; } } else if IO_REGISTERS.in_range(address) { return self.data[address as usize]; @@ -104,7 +106,7 @@ impl Bus { self.data[address as usize] = data; self.data[(WORK_RAM_1.begin() + (address - ECHO_RAM.begin())) as usize] = data; // Copy to the working RAM } else if VIDEO_RAM.in_range(address) { - //if !PPU::get_lcd_status(self, LCDStatus::ModeFlag(LCDStatusModeFlag::TransferringToLCD)) { + // if !PPU::get_lcd_status(self, LCDStatus::ModeFlag(LCDStatusModeFlag::TransferringToLCD)) { self.data[address as usize] = data; // } } else { @@ -118,14 +120,46 @@ impl Bus { self.write(address.wrapping_add(1), bytes[1]); } - pub fn set_flag(&mut self, flag: InterruptFlag, val: bool) { - let byte = self.read(INTERRUPT_FLAG_ADDRESS); - self.write(INTERRUPT_FLAG_ADDRESS, match flag { - InterruptFlag::VBlank => set_bit(byte, val, BitIndex::I0), - InterruptFlag::LCDSTAT => set_bit(byte, val, BitIndex::I1), - InterruptFlag::Timer => set_bit(byte, val, BitIndex::I2), - InterruptFlag::Serial => set_bit(byte, val, BitIndex::I3), - InterruptFlag::Joypad => set_bit(byte, val, BitIndex::I4), + pub fn set_interrupt_master(&mut self, flag: Interrupt, val: bool) { + let byte = self.read(INTERRUPT_ENABLE_ADDRESS); + self.write(INTERRUPT_ENABLE_ADDRESS, match flag { + Interrupt::VBlank => set_bit(byte, val, BitIndex::I0), + Interrupt::LCDSTAT => set_bit(byte, val, BitIndex::I1), + Interrupt::Timer => set_bit(byte, val, BitIndex::I2), + Interrupt::Serial => set_bit(byte, val, BitIndex::I3), + Interrupt::Joypad => set_bit(byte, val, BitIndex::I4), }); } + + pub fn set_interrupt(&mut self, flag: Interrupt, val: bool) { + let byte = self.read(INTERRUPT_FLAG_ADDRESS); + self.write(INTERRUPT_FLAG_ADDRESS, match flag { + Interrupt::VBlank => set_bit(byte, val, BitIndex::I0), + Interrupt::LCDSTAT => set_bit(byte, val, BitIndex::I1), + Interrupt::Timer => set_bit(byte, val, BitIndex::I2), + Interrupt::Serial => set_bit(byte, val, BitIndex::I3), + Interrupt::Joypad => set_bit(byte, val, BitIndex::I4), + }); + } + + pub fn get_interrupt(&mut self, flag: Interrupt) -> bool { + let byte = self.read(INTERRUPT_ENABLE_ADDRESS) & self.read(INTERRUPT_FLAG_ADDRESS); + match flag { + Interrupt::VBlank => get_bit(byte, BitIndex::I0), + Interrupt::LCDSTAT => get_bit(byte, BitIndex::I1), + Interrupt::Timer => get_bit(byte, BitIndex::I2), + Interrupt::Serial => get_bit(byte, BitIndex::I3), + Interrupt::Joypad => get_bit(byte, BitIndex::I4), + } + } + + pub fn get_interrupt_vector(flag: Interrupt) -> u16 { + match flag { + Interrupt::VBlank => 0x40, + Interrupt::LCDSTAT => 0x48, + Interrupt::Timer => 0x50, + Interrupt::Serial => 0x58, + Interrupt::Joypad => 0x60, + } + } } diff --git a/src/cpu.rs b/src/cpu.rs index b4e760a..ebc5397 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -8,7 +8,7 @@ use crate::utils::{ sub_half_carry, add_half_carry_16bit, }; -use crate::bus::Bus; +use crate::bus::{Bus, INTERRUPT_ENABLE_ADDRESS, INTERRUPT_FLAG_ADDRESS}; #[derive(Debug, Copy, Clone)] pub enum Register { @@ -53,7 +53,8 @@ pub enum FlagRegister { Carry, // Set if a carry was ocurrend from the last math operation or if register A is the smaller value when executing the CP instruction } -pub enum InterruptFlag { +#[derive(Debug, Copy, Clone)] +pub enum Interrupt { VBlank, LCDSTAT, Timer, @@ -61,18 +62,6 @@ pub enum InterruptFlag { Joypad, } -impl InterruptFlag { - pub fn get_bit_index(interrupt: InterruptFlag) -> BitIndex { - match interrupt { - InterruptFlag::VBlank => BitIndex::I0, - InterruptFlag::LCDSTAT => BitIndex::I1, - InterruptFlag::Timer => BitIndex::I2, - InterruptFlag::Serial => BitIndex::I3, - InterruptFlag::Joypad => BitIndex::I4, - } - } -} - pub struct Registers { a: u8, f: u8, @@ -837,8 +826,11 @@ impl CPU { } fn increment_cycles(&mut self, cycles: Cycles) { - let Cycles(c) = cycles; - self.cycles.0 += c; + self.cycles.0 += cycles.0; + } + + fn decrement_cycles(&mut self, cycles: Cycles) { + self.cycles.0 -= cycles.0; } pub fn reset_cycles(&mut self) { @@ -876,6 +868,34 @@ impl CPU { ); } + pub fn handle_interrupt(&mut self, bus: &mut Bus, interrupt: Interrupt) { + bus.set_interrupt_master(interrupt, false); + bus.set_interrupt(interrupt, false); + let vector = Bus::get_interrupt_vector(interrupt); + self.exec(Opcode::CALL(OpcodeParameter::U16(vector)), bus); + self.decrement_cycles(Cycles(1)); + } + + pub fn check_interrupt(&mut self, bus: &mut Bus) -> bool { + if bus.get_interrupt(Interrupt::VBlank) { + self.handle_interrupt(bus, Interrupt::VBlank); + return true; + } else if bus.get_interrupt(Interrupt::LCDSTAT) { + self.handle_interrupt(bus, Interrupt::LCDSTAT); + return true; + } else if bus.get_interrupt(Interrupt::Timer) { + self.handle_interrupt(bus, Interrupt::Timer); + return true; + } else if bus.get_interrupt(Interrupt::Serial) { + self.handle_interrupt(bus, Interrupt::Serial); + return true; + } else if bus.get_interrupt(Interrupt::Joypad) { + self.handle_interrupt(bus, Interrupt::Joypad); + return true; + } + return false; + } + pub fn run(&mut self, bus: &mut Bus) { let cycles_start = self.get_cycles(); let program_counter = self.registers.get(Register::PC); @@ -885,11 +905,11 @@ impl CPU { self.log(parameter_bytes); } self.increment_cycles(cycles); - self.exec(opcode, bus); + if !self.check_interrupt(bus) { + self.exec(opcode, bus); + } let cycles_end = self.get_cycles(); - self.set_last_op_cycles(cycles_start, cycles_end); - // self.increment_exec_calls_count(); } pub fn exec(&mut self, opcode: Opcode, bus: &mut Bus) { @@ -1713,12 +1733,12 @@ impl CPU { // Enable interrupts Opcode::EI => { self.registers.increment(Register::PC, 1); - bus.write(0xFFFF, 0xFF); // Disable all interrupts + bus.write(INTERRUPT_ENABLE_ADDRESS, 0xFF); // Disable all interrupts }, // Disable interrupts Opcode::DI => { self.registers.increment(Register::PC, 1); - bus.write(0xFFFF, 0x00); // Disable all interrupts + bus.write(INTERRUPT_ENABLE_ADDRESS, 0x00); // Disable all interrupts }, // Same as enabling interrupts and then executing RET Opcode::RETI => { diff --git a/src/emulator.rs b/src/emulator.rs index 4d4043b..f709742 100644 --- a/src/emulator.rs +++ b/src/emulator.rs @@ -30,8 +30,8 @@ impl Emulator { pub fn run(&mut self, cpu_cycles: Cycles) { self.cpu.reset_cycles(); while self.cpu.get_cycles().0 <= cpu_cycles.0 { - self.ppu.do_cycle(&mut self.bus); self.cpu.run(&mut self.bus); + self.ppu.do_cycles(&mut self.bus, self.cpu.get_last_op_cycles()); } } diff --git a/src/ppu.rs b/src/ppu.rs index b9cd898..3ab0f8f 100644 --- a/src/ppu.rs +++ b/src/ppu.rs @@ -5,7 +5,7 @@ use crate::utils::{ to_bit_index, }; use crate::bus::{Bus, AddressRange, BANK_ZERO, VIDEO_RAM}; -use crate::cpu::{Cycles, InterruptFlag}; +use crate::cpu::{Cycles, Interrupt}; #[derive(Debug, Copy, Clone)] enum Pixel { @@ -87,25 +87,31 @@ impl PPU { self.cycles.0 += cycles.0; } - pub fn do_cycle(&mut self, bus: &mut Bus) { + pub fn do_cycles(&mut self, bus: &mut Bus, cycles: Cycles) { + let mut count = 0; + while count < cycles.0 { + self.cycle(bus); + count += 1; + } + } + + pub fn cycle(&mut self, bus: &mut Bus) { // Mode 1 Vertical blank if PPU::get_lcd_y(bus) >= 144 { - if PPU::get_lcd_y(bus) == 144 { - bus.set_flag(InterruptFlag::VBlank, true); - PPU::set_lcd_status(bus, LCDStatus::ModeFlag(LCDStatusModeFlag::VBlank), true); - } + bus.set_interrupt(Interrupt::VBlank, true); + PPU::set_lcd_status(bus, LCDStatus::ModeFlag(LCDStatusModeFlag::VBlank), true); } else { if self.cycles.0 == 0 { // Mode 2 OAM scan PPU::set_lcd_status(bus, LCDStatus::ModeFlag(LCDStatusModeFlag::SearchingOAM), true); } else if self.cycles.0 == 80 + 1 { // Mode 3 drawing pixel line. This could also last 289 cycles - bus.set_flag(InterruptFlag::LCDSTAT, true); + bus.set_interrupt(Interrupt::LCDSTAT, true); self.draw_line(bus); PPU::set_lcd_status(bus, LCDStatus::ModeFlag(LCDStatusModeFlag::TransferringToLCD), true); } else if self.cycles.0 == 80 + 172 + 1 { // Mode 0 Horizontal blank. This could last 87 or 204 cycles depending on the mode 3 - bus.set_flag(InterruptFlag::LCDSTAT, true); + bus.set_interrupt(Interrupt::LCDSTAT, true); PPU::set_lcd_status(bus, LCDStatus::ModeFlag(LCDStatusModeFlag::HBlank), true); } } @@ -113,7 +119,7 @@ impl PPU { let lyc_compare = PPU::get_lcd_y(bus) == bus.read(LCD_Y_COMPARE_ADDRESS); PPU::set_lcd_status(bus, LCDStatus::LYCInterrupt, lyc_compare); if lyc_compare { - bus.set_flag(InterruptFlag::LCDSTAT, true); + bus.set_interrupt(Interrupt::LCDSTAT, true); } self.increment_cycles(Cycles(1)); @@ -186,15 +192,15 @@ impl PPU { pub fn get_lcd_status(bus: &Bus, status: LCDStatus) -> bool { let byte = bus.read(LCD_STATUS_ADDRESS); match status { - LCDStatus::LYCInterrupt => get_bit(byte, BitIndex::I6), - LCDStatus::Mode2OAMInterrupt => get_bit(byte, BitIndex::I5), + LCDStatus::LYCInterrupt => get_bit(byte, BitIndex::I6), + LCDStatus::Mode2OAMInterrupt => get_bit(byte, BitIndex::I5), LCDStatus::Mode1VBlankInterrupt => get_bit(byte, BitIndex::I4), LCDStatus::Mode0HBlankInterrupt => get_bit(byte, BitIndex::I3), - LCDStatus::LYCFlag => get_bit(byte, BitIndex::I2), + LCDStatus::LYCFlag => get_bit(byte, BitIndex::I2), LCDStatus::ModeFlag(mode) => match mode { - LCDStatusModeFlag::HBlank => (byte & 0b00000011) == 0, - LCDStatusModeFlag::VBlank => (byte & 0b00000011) == 1, - LCDStatusModeFlag::SearchingOAM => (byte & 0b00000011) == 2, + LCDStatusModeFlag::HBlank => (byte & 0b00000011) == 0, + LCDStatusModeFlag::VBlank => (byte & 0b00000011) == 1, + LCDStatusModeFlag::SearchingOAM => (byte & 0b00000011) == 2, LCDStatusModeFlag::TransferringToLCD => (byte & 0b00000011) == 3, }, } @@ -203,15 +209,15 @@ impl PPU { fn set_lcd_status(bus: &mut Bus, status: LCDStatus, val: bool) { let mut byte = bus.read(LCD_STATUS_ADDRESS); byte = match status { - LCDStatus::LYCInterrupt => set_bit(byte, val, BitIndex::I6), - LCDStatus::Mode2OAMInterrupt => set_bit(byte, val, BitIndex::I5), + LCDStatus::LYCInterrupt => set_bit(byte, val, BitIndex::I6), + LCDStatus::Mode2OAMInterrupt => set_bit(byte, val, BitIndex::I5), LCDStatus::Mode1VBlankInterrupt => set_bit(byte, val, BitIndex::I4), LCDStatus::Mode0HBlankInterrupt => set_bit(byte, val, BitIndex::I3), - LCDStatus::LYCFlag => set_bit(byte, val, BitIndex::I2), + LCDStatus::LYCFlag => set_bit(byte, val, BitIndex::I2), LCDStatus::ModeFlag(mode) => match mode { - LCDStatusModeFlag::HBlank => (byte & 0b11111100) | 0, - LCDStatusModeFlag::VBlank => (byte & 0b11111100) | 1, - LCDStatusModeFlag::SearchingOAM => (byte & 0b11111100) | 2, + LCDStatusModeFlag::HBlank => (byte & 0b11111100) | 0, + LCDStatusModeFlag::VBlank => (byte & 0b11111100) | 1, + LCDStatusModeFlag::SearchingOAM => (byte & 0b11111100) | 2, LCDStatusModeFlag::TransferringToLCD => (byte & 0b11111100) | 3, }, };