feat: implement cpu interrupts
This commit is contained in:
parent
fb38ef3f68
commit
558f9e7c72
|
@ -59,9 +59,9 @@ impl Bus {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn step(&mut self, cycles: Cycles) {
|
pub fn step(&mut self, cycles: Cycles) {
|
||||||
let _ = self.timer.step(cycles);
|
self.timer.step(cycles);
|
||||||
let _ = self.sound.step(cycles);
|
self.sound.step(cycles);
|
||||||
let _ = self.ppu.step(cycles);
|
self.ppu.step(cycles);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
84
src/cpu.rs
84
src/cpu.rs
|
@ -1,11 +1,9 @@
|
||||||
use super::bus::Bus;
|
use super::bus::Bus;
|
||||||
use super::instruction::{Cycles, Instruction};
|
use super::instruction::{Cycles, Instruction, JumpCondition};
|
||||||
|
use super::interrupt::{InterruptEnable, InterruptFlag};
|
||||||
use super::ppu::Ppu;
|
use super::ppu::Ppu;
|
||||||
use bitfield::bitfield;
|
use bitfield::bitfield;
|
||||||
use std::{
|
use std::fmt::{Display, Formatter, Result as FmtResult};
|
||||||
fmt::{Display, Formatter, Result as FmtResult},
|
|
||||||
io::Write,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct Cpu {
|
pub struct Cpu {
|
||||||
|
@ -86,16 +84,18 @@ impl Cpu {
|
||||||
|
|
||||||
let cycles = self.execute(instr);
|
let cycles = self.execute(instr);
|
||||||
|
|
||||||
if self.bus.read_byte(0xFF02) == 0x81 {
|
// if self.bus.read_byte(0xFF02) == 0x81 {
|
||||||
let c = self.bus.read_byte(0xFF01) as char;
|
// let c = self.bus.read_byte(0xFF01) as char;
|
||||||
self.bus.write_byte(0xFF02, 0x00);
|
// self.bus.write_byte(0xFF02, 0x00);
|
||||||
|
|
||||||
print!("{}", c);
|
// print!("{}", c);
|
||||||
std::io::stdout().flush().unwrap();
|
// std::io::stdout().flush().unwrap();
|
||||||
}
|
// }
|
||||||
|
|
||||||
self.bus.step(cycles);
|
self.bus.step(cycles);
|
||||||
|
|
||||||
|
self.handle_interrupts();
|
||||||
|
|
||||||
cycles
|
cycles
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -133,6 +133,68 @@ impl Cpu {
|
||||||
pub fn get_ppu(&mut self) -> &mut Ppu {
|
pub fn get_ppu(&mut self) -> &mut Ppu {
|
||||||
&mut self.bus.ppu
|
&mut self.bus.ppu
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn handle_interrupts(&mut self) {
|
||||||
|
if self.ime() {
|
||||||
|
use JumpCondition::Always;
|
||||||
|
|
||||||
|
let mut req: InterruptFlag = self.read_byte(0xFF0F).into();
|
||||||
|
let mut enabled: InterruptEnable = self.read_byte(0xFFFF).into();
|
||||||
|
|
||||||
|
let vector = if req.vblank() && enabled.vblank() {
|
||||||
|
// Handle VBlank Interrupt
|
||||||
|
req.set_vblank(false);
|
||||||
|
enabled.set_vblank(false);
|
||||||
|
|
||||||
|
// INT 40h
|
||||||
|
Some(0x0040)
|
||||||
|
} else if req.lcd_stat() && enabled.lcd_stat() {
|
||||||
|
// Handle LCD STAT Interrupt
|
||||||
|
req.set_lcd_stat(false);
|
||||||
|
enabled.set_lcd_stat(false);
|
||||||
|
|
||||||
|
// INT 48h
|
||||||
|
Some(0x0048)
|
||||||
|
} else if req.timer() && enabled.timer() {
|
||||||
|
// Handle Timer Interrupt
|
||||||
|
req.set_timer(false);
|
||||||
|
enabled.set_timer(false);
|
||||||
|
|
||||||
|
// INT 50h
|
||||||
|
Some(0x0050)
|
||||||
|
} else if req.serial() && enabled.serial() {
|
||||||
|
// Handle Serial Interrupt
|
||||||
|
req.set_serial(false);
|
||||||
|
enabled.set_serial(false);
|
||||||
|
|
||||||
|
// INT 58h
|
||||||
|
Some(0x0058)
|
||||||
|
} else if req.joypad() && enabled.joypad() {
|
||||||
|
// Handle Joypad Interrupt
|
||||||
|
req.set_joypad(false);
|
||||||
|
enabled.set_joypad(false);
|
||||||
|
|
||||||
|
// INT 60h
|
||||||
|
Some(0x0060)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let _ = match vector {
|
||||||
|
Some(register) => {
|
||||||
|
// Write the Changes to 0xFF0F and 0xFFFF registers
|
||||||
|
self.write_byte(0xFF0F, req.into());
|
||||||
|
self.write_byte(0xFFFF, enabled.into());
|
||||||
|
|
||||||
|
// Disable all future interrupts
|
||||||
|
self.set_ime(false);
|
||||||
|
|
||||||
|
self.execute(Instruction::CALL(Always, register))
|
||||||
|
}
|
||||||
|
None => Cycles::new(0), // NO Interrupts were enabled and / or requested
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
|
|
@ -1017,7 +1017,7 @@ impl Instruction {
|
||||||
Cycles::new(4)
|
Cycles::new(4)
|
||||||
}
|
}
|
||||||
Instruction::CALL(cond, nn) => {
|
Instruction::CALL(cond, nn) => {
|
||||||
// CALL cc[y], nn | Store nn on the stack, then store nn in the program counter if cond is met
|
// CALL cc[y], nn | Store pc on the stack, then store nn in the program counter if cond is met
|
||||||
// CALL nn | Store nn on the stack, then store nn in the program counter
|
// CALL nn | Store nn on the stack, then store nn in the program counter
|
||||||
let flags: &Flags = cpu.flags();
|
let flags: &Flags = cpu.flags();
|
||||||
let pc = cpu.register_pair(RegisterPair::PC);
|
let pc = cpu.register_pair(RegisterPair::PC);
|
||||||
|
|
|
@ -9,84 +9,11 @@ pub struct Interrupt {
|
||||||
bitfield! {
|
bitfield! {
|
||||||
pub struct InterruptEnable(u8);
|
pub struct InterruptEnable(u8);
|
||||||
impl Debug;
|
impl Debug;
|
||||||
_vblank, _set_vblank: 0;
|
pub vblank, set_vblank: 0;
|
||||||
_lcd_stat, _set_lcd_stat: 1;
|
pub lcd_stat, set_lcd_stat: 1;
|
||||||
_timer, _set_timer: 2;
|
pub timer, set_timer: 2;
|
||||||
_serial, _set_serial: 3;
|
pub serial, set_serial: 3;
|
||||||
_joypad, _set_joypad: 4;
|
pub joypad, set_joypad: 4;
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Is this the correct behaviour? (I think not)
|
|
||||||
impl InterruptEnable {
|
|
||||||
pub fn vblank(&self) -> bool {
|
|
||||||
self._vblank()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn lcd_stat(&self) -> bool {
|
|
||||||
self._lcd_stat()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn timer(&self) -> bool {
|
|
||||||
self._timer()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn serial(&self) -> bool {
|
|
||||||
self._serial()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn joypad(&self) -> bool {
|
|
||||||
self._joypad()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_vblank(&self, flag: &mut InterruptFlag, value: bool) {
|
|
||||||
let prev = self._vblank();
|
|
||||||
|
|
||||||
if !prev && value {
|
|
||||||
flag.set_vblank(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
flag.set_vblank(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_lcd_stat(&self, flag: &mut InterruptFlag, value: bool) {
|
|
||||||
let prev = self._lcd_stat();
|
|
||||||
|
|
||||||
if !prev && value {
|
|
||||||
flag.set_lcd_stat(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
flag.set_lcd_stat(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_timer(&self, flag: &mut InterruptFlag, value: bool) {
|
|
||||||
let prev = self._timer();
|
|
||||||
|
|
||||||
if !prev && value {
|
|
||||||
flag.set_timer(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
flag.set_timer(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_serial(&self, flag: &mut InterruptFlag, value: bool) {
|
|
||||||
let prev = self._serial();
|
|
||||||
|
|
||||||
if !prev && value {
|
|
||||||
flag.set_serial(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
flag.set_serial(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_joypad(&self, flag: &mut InterruptFlag, value: bool) {
|
|
||||||
let prev = self._joypad();
|
|
||||||
|
|
||||||
if !prev && value {
|
|
||||||
flag.set_joypad(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
flag.set_joypad(value)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Copy for InterruptEnable {}
|
impl Copy for InterruptEnable {}
|
||||||
|
|
|
@ -30,11 +30,6 @@ fn main() -> Result<()> {
|
||||||
|
|
||||||
event_loop.run(move |event, _, control_flow| {
|
event_loop.run(move |event, _, control_flow| {
|
||||||
if let Event::RedrawRequested(_) = event {
|
if let Event::RedrawRequested(_) = event {
|
||||||
let ppu = game_boy.get_ppu();
|
|
||||||
let frame = pixels.get_frame();
|
|
||||||
|
|
||||||
ppu.copy_to_gui(frame);
|
|
||||||
|
|
||||||
if pixels
|
if pixels
|
||||||
.render()
|
.render()
|
||||||
.map_err(|e| anyhow!("pixels.render() failed: {}", e))
|
.map_err(|e| anyhow!("pixels.render() failed: {}", e))
|
||||||
|
@ -57,6 +52,10 @@ fn main() -> Result<()> {
|
||||||
|
|
||||||
// Emulation
|
// Emulation
|
||||||
let _cycles = game_boy.step();
|
let _cycles = game_boy.step();
|
||||||
|
|
||||||
|
let ppu = game_boy.get_ppu();
|
||||||
|
let frame = pixels.get_frame();
|
||||||
|
ppu.copy_to_gui(frame);
|
||||||
window.request_redraw();
|
window.request_redraw();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue