305 lines
7.7 KiB
Rust
305 lines
7.7 KiB
Rust
use self::gpu::GPU;
|
|
use self::memory::Memory;
|
|
use crate::{processor::instructions::instructions::set, verbose_println};
|
|
use minifb::Window;
|
|
use std::{mem::transmute, time::Duration};
|
|
|
|
pub mod gpu;
|
|
mod instructions;
|
|
pub mod memory;
|
|
mod opcodes;
|
|
|
|
#[derive(PartialEq)]
|
|
pub(crate) enum Flags {
|
|
Zero = 7,
|
|
NSubtract = 6,
|
|
HalfCarry = 5,
|
|
Carry = 4,
|
|
}
|
|
|
|
pub(crate) enum Direction {
|
|
Left,
|
|
Right,
|
|
}
|
|
|
|
pub struct CPU {
|
|
pub memory: Memory,
|
|
pub reg: Registers,
|
|
pub last_instruction: u8,
|
|
pub last_instruction_addr: u16,
|
|
pub window: Window,
|
|
pub gpu: GPU,
|
|
}
|
|
|
|
// Hz
|
|
const CLOCK_SPEED: f64 = 4.194304 * 1000000.;
|
|
const SPEEDUP: f64 = 1.;
|
|
const FF04_SPEED: f64 = 16384.;
|
|
|
|
impl CPU {
|
|
pub fn new(memory: Memory, window: Window) -> Self {
|
|
Self {
|
|
memory,
|
|
reg: Registers::default(),
|
|
last_instruction: 0x0,
|
|
last_instruction_addr: 0x0,
|
|
window,
|
|
gpu: GPU::default(),
|
|
}
|
|
}
|
|
|
|
pub fn exec_next(&mut self) {
|
|
self.last_instruction_addr = self.reg.pc;
|
|
let opcode = self.next_opcode();
|
|
self.last_instruction = opcode;
|
|
|
|
if self.memory.ime_scheduled > 0 {
|
|
self.memory.ime_scheduled = self.memory.ime_scheduled.saturating_sub(1);
|
|
if self.memory.ime_scheduled == 0 {
|
|
self.memory.ime = true;
|
|
}
|
|
}
|
|
|
|
verbose_println!(
|
|
"exec {:#4X} from pc: {:#X}",
|
|
opcode,
|
|
self.last_instruction_addr
|
|
);
|
|
self.memory.user_mode = true;
|
|
let cycles = self.run_opcode(opcode);
|
|
self.memory.user_mode = false;
|
|
self.increment_timers(cycles);
|
|
|
|
let interrupt_cycles = self.handle_interrupts();
|
|
self.increment_timers(interrupt_cycles);
|
|
}
|
|
|
|
fn increment_timers(&mut self, cycles: u8) {
|
|
let secs = (cycles * 4) as f64 / CLOCK_SPEED;
|
|
self.advance_gpu_clock(cycles);
|
|
|
|
self.memory.set(
|
|
0xFF04,
|
|
self.memory
|
|
.get(0xFF04)
|
|
.wrapping_add((FF04_SPEED * secs) as u8),
|
|
);
|
|
let (timer_enabled, timer_rate) = self.timer_scale();
|
|
if timer_enabled {
|
|
let (val, wrap) = self
|
|
.memory
|
|
.get(0xFF05)
|
|
.overflowing_add((secs * timer_rate as f64) as u8);
|
|
if wrap {
|
|
self.memory.set(0xFF05, self.memory.get(0xFF06));
|
|
self.memory.set(0xFF0F, set(self.memory.get(0xFF0F), 2));
|
|
} else {
|
|
self.memory.set(0xFF05, val);
|
|
}
|
|
}
|
|
spin_sleep::sleep(Duration::from_secs_f64(secs / SPEEDUP));
|
|
}
|
|
|
|
fn next_opcode(&mut self) -> u8 {
|
|
let opcode = self.memory.get(self.reg.pc);
|
|
self.reg.pc = self.reg.pc.wrapping_add(0x1);
|
|
return opcode;
|
|
}
|
|
|
|
fn handle_interrupts(&mut self) -> u8 {
|
|
if self.memory.ime {
|
|
let req_and_enabled = self.memory.get(0xFF0F) & self.memory.get(0xFFFF);
|
|
// all interrupts should last 5 cycles?
|
|
if get_bit(req_and_enabled, 0) {
|
|
// vblank
|
|
self.service_interrupt(0x40);
|
|
self.memory
|
|
.set(0xFF0F, clear_bit(self.memory.get(0xFF0F), 0));
|
|
5
|
|
} else if get_bit(req_and_enabled, 1) {
|
|
// lcd stat
|
|
self.service_interrupt(0x48);
|
|
self.memory
|
|
.set(0xFF0F, clear_bit(self.memory.get(0xFF0F), 1));
|
|
5
|
|
} else if get_bit(req_and_enabled, 2) {
|
|
// timer
|
|
self.service_interrupt(0x50);
|
|
self.memory
|
|
.set(0xFF0F, clear_bit(self.memory.get(0xFF0F), 2));
|
|
5
|
|
} else if get_bit(req_and_enabled, 3) {
|
|
// serial
|
|
self.service_interrupt(0x58);
|
|
self.memory
|
|
.set(0xFF0F, clear_bit(self.memory.get(0xFF0F), 3));
|
|
5
|
|
} else if get_bit(req_and_enabled, 4) {
|
|
// joypad
|
|
self.service_interrupt(0x60);
|
|
self.memory
|
|
.set(0xFF0F, clear_bit(self.memory.get(0xFF0F), 4));
|
|
5
|
|
} else {
|
|
0
|
|
}
|
|
} else {
|
|
0
|
|
}
|
|
}
|
|
|
|
fn service_interrupt(&mut self, addr: u16) {
|
|
self.push(self.reg.pc);
|
|
self.reg.pc = addr;
|
|
self.memory.ime = false;
|
|
}
|
|
|
|
fn timer_scale(&self) -> (bool, usize) {
|
|
let timer_control = self.memory.get(0xFF07);
|
|
let timer_enable = get_bit(timer_control, 2);
|
|
let timer_rate = match (get_bit(timer_control, 1), get_bit(timer_control, 0)) {
|
|
(true, true) => 256,
|
|
(true, false) => 64,
|
|
(false, true) => 16,
|
|
(false, false) => 1024,
|
|
};
|
|
(timer_enable, timer_rate)
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy)]
|
|
pub enum Reg8 {
|
|
A,
|
|
B,
|
|
C,
|
|
D,
|
|
E,
|
|
H,
|
|
L,
|
|
}
|
|
|
|
#[derive(Clone, Copy)]
|
|
pub struct Registers {
|
|
pub af: u16,
|
|
pub bc: u16,
|
|
pub de: u16,
|
|
pub hl: u16,
|
|
pub sp: u16,
|
|
pub pc: u16,
|
|
}
|
|
|
|
impl Default for Registers {
|
|
fn default() -> Self {
|
|
// default post-bootrom values
|
|
Self {
|
|
af: 0x00B0,
|
|
// af: 0x01B0,
|
|
bc: 0x0013,
|
|
de: 0x00D8,
|
|
hl: 0x014D,
|
|
sp: 0xFFFE,
|
|
pc: 0x0000,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Registers {
|
|
fn get_8(&self, register: Reg8) -> u8 {
|
|
match register {
|
|
Reg8::A => self.af.get_high(),
|
|
Reg8::B => self.bc.get_high(),
|
|
Reg8::C => self.bc.get_low(),
|
|
Reg8::D => self.de.get_high(),
|
|
Reg8::E => self.de.get_low(),
|
|
Reg8::H => self.hl.get_high(),
|
|
Reg8::L => self.hl.get_low(),
|
|
}
|
|
}
|
|
fn set_8(&mut self, register: Reg8, val: u8) {
|
|
match register {
|
|
Reg8::A => self.af.set_high(val),
|
|
Reg8::B => self.bc.set_high(val),
|
|
Reg8::C => self.bc.set_low(val),
|
|
Reg8::D => self.de.set_high(val),
|
|
Reg8::E => self.de.set_low(val),
|
|
Reg8::H => self.hl.set_high(val),
|
|
Reg8::L => self.hl.set_low(val),
|
|
}
|
|
}
|
|
}
|
|
|
|
trait SplitRegister {
|
|
fn get_low(&self) -> u8;
|
|
fn get_high(&self) -> u8;
|
|
fn set_low(&mut self, val: u8);
|
|
fn set_high(&mut self, val: u8);
|
|
}
|
|
|
|
impl SplitRegister for u16 {
|
|
fn get_low(&self) -> u8 {
|
|
(*self & 0xFF) as u8
|
|
}
|
|
|
|
fn get_high(&self) -> u8 {
|
|
((*self >> 8) & 0xFF) as u8
|
|
}
|
|
|
|
fn set_low(&mut self, val: u8) {
|
|
*self = (*self & !0xff) | val as u16;
|
|
}
|
|
|
|
fn set_high(&mut self, val: u8) {
|
|
*self = (*self & !0xff00) | (val as u16) << 8;
|
|
}
|
|
}
|
|
|
|
fn as_signed(unsigned: u8) -> i8 {
|
|
unsafe {
|
|
return transmute(unsigned);
|
|
}
|
|
}
|
|
|
|
fn get_bit(byte: u8, flag: u8) -> bool {
|
|
let mask = 1 << flag;
|
|
let got = byte & mask;
|
|
return got > 0x0;
|
|
}
|
|
|
|
fn set_or_clear_bit(byte: u8, flag: u8, condition: bool) -> u8 {
|
|
if condition {
|
|
set_bit(byte, flag)
|
|
} else {
|
|
clear_bit(byte, flag)
|
|
}
|
|
}
|
|
|
|
fn set_bit(byte: u8, flag: u8) -> u8 {
|
|
byte | (1 << flag)
|
|
}
|
|
|
|
fn clear_bit(byte: u8, flag: u8) -> u8 {
|
|
byte & (!(1 << flag))
|
|
}
|
|
|
|
fn rotate(byte: u8, direction: &Direction) -> (u8, bool) {
|
|
match direction {
|
|
Direction::Left => {
|
|
let carry = get_bit(byte, 7);
|
|
let r = byte << 1;
|
|
return (r, carry);
|
|
}
|
|
Direction::Right => {
|
|
let carry = get_bit(byte, 0);
|
|
let r = byte >> 1;
|
|
return (r, carry);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn get_rotation_carry(direction: &Direction) -> u8 {
|
|
match direction {
|
|
Direction::Left => 0b1,
|
|
Direction::Right => 0b10000000,
|
|
}
|
|
}
|