Compare commits

...

2 Commits

6 changed files with 191 additions and 135 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
/target /target
*.log *.log
/ignore /ignore
.vscode

View File

@ -1,3 +1,4 @@
use std::env;
use std::ops::RangeInclusive; use std::ops::RangeInclusive;
use crate::utils::join_bytes; use crate::utils::join_bytes;
use crate::rom::{ROM, load_rom}; use crate::rom::{ROM, load_rom};
@ -29,6 +30,21 @@ pub const IO_REGISTERS: RangeInclusive<u16> = 0xFF00..=0xFF7F;
pub const HIGH_RAM: RangeInclusive<u16> = 0xFF80..=0xFFFE; pub const HIGH_RAM: RangeInclusive<u16> = 0xFF80..=0xFFFE;
pub const PREPARE_SPEED_SWITCH_ADDRESS: u16 = 0xFF4D; pub const PREPARE_SPEED_SWITCH_ADDRESS: u16 = 0xFF4D;
enum MemoryMap {
BankZero,
BankSwitchable,
VideoRam,
ExternalRam,
WorkRam1,
WorkRam2,
EchoRam,
SpriteAttributeTable,
NotUsable,
IoRegisters,
HighRam,
InterruptEnable,
}
pub struct Bus { pub struct Bus {
data: [u8; 0x10000], data: [u8; 0x10000],
pub rom: Box<dyn ROM>, pub rom: Box<dyn ROM>,
@ -59,7 +75,8 @@ impl Bus {
}, },
}; };
let info = rom.info().clone(); let info = rom.info().clone();
let cgb_mode = info.cgb_features() || info.cgb_only(); let force_dmg_mode = !env::var("FORCE_DMG").is_err();
let cgb_mode = (info.cgb_features() || info.cgb_only()) && !force_dmg_mode;
let mut bus = Self { let mut bus = Self {
data: [0x00; 0x10000], data: [0x00; 0x10000],
rom, rom,
@ -103,34 +120,53 @@ impl Bus {
bus bus
} }
fn map_address(address: u16) -> MemoryMap {
match address {
0x0000..=0x3FFF => MemoryMap::BankZero,
0x4000..=0x7FFF => MemoryMap::BankSwitchable,
0x8000..=0x9FFF => MemoryMap::VideoRam,
0xA000..=0xBFFF => MemoryMap::ExternalRam,
0xC000..=0xCFFF => MemoryMap::WorkRam1,
0xD000..=0xDFFF => MemoryMap::WorkRam2,
0xE000..=0xFDFF => MemoryMap::EchoRam,
0xFE00..=0xFE9F => MemoryMap::SpriteAttributeTable,
0xFEA0..=0xFEFF => MemoryMap::NotUsable,
0xFF00..=0xFF7F => MemoryMap::IoRegisters,
0xFF80..=0xFFFE => MemoryMap::HighRam,
INTERRUPT_ENABLE_ADDRESS => MemoryMap::InterruptEnable,
}
}
pub fn read(&self, address: u16) -> u8 { pub fn read(&self, address: u16) -> u8 {
if BANK_ZERO.contains(&address) || BANK_SWITCHABLE.contains(&address) || EXTERNAL_RAM.contains(&address) { match Bus::map_address(address) {
return self.rom.read(address); MemoryMap::BankZero | MemoryMap::BankSwitchable | MemoryMap::ExternalRam => self.rom.read(address),
} else if WORK_RAM_1.contains(&address) || WORK_RAM_2.contains(&address) || address == WRAM_BANK_SELECT_ADDRESS { MemoryMap::WorkRam1 | MemoryMap::WorkRam2 | MemoryMap::EchoRam => self.ram.read(address),
return self.ram.read(address); MemoryMap::VideoRam => self.ppu.read_vram_external(address),
} else if ECHO_RAM.contains(&address) { MemoryMap::SpriteAttributeTable => self.ppu.read_oam(address),
return self.ram.read(WORK_RAM_1.min().unwrap() + ((address - ECHO_RAM.min().unwrap()) & 0x1FFF)); MemoryMap::IoRegisters => {
} else if address == INTERRUPT_ENABLE_ADDRESS || address == INTERRUPT_FLAG_ADDRESS { if self.cgb_mode && address == PREPARE_SPEED_SWITCH_ADDRESS {
return self.interrupts.read(address);
} else if VIDEO_RAM.contains(&address) {
return self.ppu.read_vram_external(address);
} else if SPRITE_ATTRIBUTE_TABLE.contains(&address) {
return self.ppu.read_oam(address);
} else if PPU::is_io_register(address) {
return self.ppu.get_register(address);
} else if Sound::is_io_register(address) {
return self.sound.get_register(address);
} else if address == JOYPAD_ADDRESS {
return self.joypad.read(self.data[address as usize]);
} else if Timer::is_io_register(address) {
return self.timer.get_register(address);
} else if address == PREPARE_SPEED_SWITCH_ADDRESS && self.cgb_mode {
let byte = self.data[address as usize]; let byte = self.data[address as usize];
let current_speed = (self.double_speed_mode as u8) << 7; let current_speed = (self.double_speed_mode as u8) << 7;
let prepare_speed_switch = self.prepare_double_speed_mode as u8; let prepare_speed_switch = self.prepare_double_speed_mode as u8;
return (byte & 0b0111_1110) | current_speed | prepare_speed_switch; return (byte & 0b0111_1110) | current_speed | prepare_speed_switch;
} else if address == WRAM_BANK_SELECT_ADDRESS {
return self.ram.read(address);
} else if address == INTERRUPT_FLAG_ADDRESS {
return self.interrupts.read(address);
} else if PPU::is_io_register(address) {
return self.ppu.get_register(address);
} else if Sound::is_io_register(address) {
return self.sound.get_register(address);
} else if Timer::is_io_register(address) {
return self.timer.get_register(address);
} else if address == JOYPAD_ADDRESS {
return self.joypad.read(self.data[address as usize]);
}
return self.data[address as usize];
},
MemoryMap::InterruptEnable => self.interrupts.read(address),
_ => self.data[address as usize],
} }
self.data[address as usize]
} }
pub fn read_16bit(&self, address: u16) -> u16 { pub fn read_16bit(&self, address: u16) -> u16 {
@ -138,47 +174,48 @@ impl Bus {
} }
pub fn write(&mut self, address: u16, data: u8) { pub fn write(&mut self, address: u16, data: u8) {
if address == 0xFF01 { match Bus::map_address(address) {
// print!("{}", data as char); MemoryMap::BankZero | MemoryMap::BankSwitchable | MemoryMap::ExternalRam => self.rom.write(address, data),
} MemoryMap::WorkRam1 | MemoryMap::WorkRam2 | MemoryMap::EchoRam => self.ram.write(address, data),
MemoryMap::VideoRam => self.ppu.write_vram_external(address, data),
if BANK_ZERO.contains(&address) || BANK_SWITCHABLE.contains(&address) || EXTERNAL_RAM.contains(&address) { MemoryMap::SpriteAttributeTable => self.ppu.write_oam(address, data),
self.rom.write(address, data); MemoryMap::IoRegisters => {
} else if address == INTERRUPT_ENABLE_ADDRESS || address == INTERRUPT_FLAG_ADDRESS { if self.cgb_mode && address == PREPARE_SPEED_SWITCH_ADDRESS {
self.interrupts.write(address, data);
} else if WORK_RAM_1.contains(&address) || WORK_RAM_2.contains(&address) || address == WRAM_BANK_SELECT_ADDRESS {
self.ram.write(address, data);
} else if EXTERNAL_RAM.contains(&address) {
self.rom.write(address, data);
} else if ECHO_RAM.contains(&address) {
self.ram.write(WORK_RAM_1.min().unwrap() + ((address - ECHO_RAM.min().unwrap()) & 0x1FFF), data);
} else if Timer::is_io_register(address) {
self.timer.set_register(address, data);
} else if Sound::is_io_register(address) {
self.sound.set_register(address, data);
} else if address == JOYPAD_ADDRESS {
let byte = self.data[address as usize];
self.data[address as usize] = (data & 0b11110000) | (byte & 0b00001111);
} else if VIDEO_RAM.contains(&address) {
return self.ppu.write_vram_external(address, data);
} else if SPRITE_ATTRIBUTE_TABLE.contains(&address) {
return self.ppu.write_oam(address, data);
} else if address == DMA_ADDRESS {
self.ppu.set_register(address, data);
self.dma_transfer(data);
} else if address == HDMA5_ADDRESS {
self.ppu.set_register(address, data);
self.hdma_transfer(data);
} else if PPU::is_io_register(address) {
self.ppu.set_register(address, data);
} else if address == PREPARE_SPEED_SWITCH_ADDRESS && self.cgb_mode {
let current_byte = self.data[address as usize]; let current_byte = self.data[address as usize];
self.prepare_double_speed_mode = (data & 1) == 1; self.prepare_double_speed_mode = (data & 1) == 1;
// bit 7 is read only on cgb mode // bit 7 is read only on cgb mode
self.data[address as usize] = (current_byte & 0b1000_0000) | (data & 0b0111_1111); self.data[address as usize] = (current_byte & 0b1000_0000) | (data & 0b0111_1111);
} else if address == WRAM_BANK_SELECT_ADDRESS {
self.ram.write(address, data);
} else if address == INTERRUPT_FLAG_ADDRESS {
self.interrupts.write(address, data);
} else if PPU::is_io_register(address) {
self.ppu.set_register(address, data);
match address {
DMA_ADDRESS => {
self.ppu.set_register(address, data);
self.dma_transfer(data);
},
HDMA5_ADDRESS => {
self.ppu.set_register(address, data);
self.hdma_transfer(data);
},
_ => {}
}
} else if Sound::is_io_register(address) {
self.sound.set_register(address, data);
} else if Timer::is_io_register(address) {
self.timer.set_register(address, data);
} else if address == JOYPAD_ADDRESS {
let byte = self.data[address as usize];
self.data[address as usize] = (data & 0b11110000) | (byte & 0b00001111);
} else { } else {
self.data[address as usize] = data; self.data[address as usize] = data;
} }
},
MemoryMap::InterruptEnable => self.interrupts.write(address, data),
_ => self.data[address as usize] = data,
};
} }
pub fn write_16bit(&mut self, address: u16, data: u16) { pub fn write_16bit(&mut self, address: u16, data: u16) {

View File

@ -920,7 +920,6 @@ impl CPU {
} }
pub fn handle_interrupt(&mut self, bus: &mut Bus, interrupt: Interrupt) { pub fn handle_interrupt(&mut self, bus: &mut Bus, interrupt: Interrupt) {
// println!("Interrupt: {:?}", interrupt);
bus.interrupts.set(interrupt, false); bus.interrupts.set(interrupt, false);
self.ime = false; self.ime = false;
self.registers.decrement(Register::PC, 3); self.registers.decrement(Register::PC, 3);

View File

@ -394,41 +394,37 @@ impl PPU {
} }
pub fn get_register(&self, address: u16) -> u8 { pub fn get_register(&self, address: u16) -> u8 {
if address >= HDMA1_ADDRESS && address <= HDMA5_ADDRESS { match address {
return match address { HDMA1_ADDRESS..=HDMA5_ADDRESS => match address {
HDMA1_ADDRESS => self.hdma_source.to_be_bytes()[0], HDMA1_ADDRESS => self.hdma_source.to_be_bytes()[0],
HDMA2_ADDRESS => self.hdma_source.to_be_bytes()[1], HDMA2_ADDRESS => self.hdma_source.to_be_bytes()[1],
HDMA3_ADDRESS => self.hdma_destination.to_be_bytes()[0], HDMA3_ADDRESS => self.hdma_destination.to_be_bytes()[0],
HDMA4_ADDRESS => self.hdma_destination.to_be_bytes()[1], HDMA4_ADDRESS => self.hdma_destination.to_be_bytes()[1],
HDMA5_ADDRESS => self.hdma_start, HDMA5_ADDRESS => self.hdma_start,
_ => 0x00, _ => 0x00,
},
0xFF68..=0xFF6B => self.cram_registers[(address as usize) - 0xFF68],
VRAM_BANK_SELECT_ADDRESS => self.get_vram_bank(),
LCD_CONTROL_ADDRESS => self.lcd_control,
LCD_Y_ADDRESS => self.lcd_y,
_ => self.io_registers[(address - 0xFF40) as usize],
} }
} else if address >= 0xFF68 && address <= 0xFF6B {
return self.cram_registers[(address as usize) - 0xFF68];
} else if address == VRAM_BANK_SELECT_ADDRESS {
return self.get_vram_bank();
} else if address == LCD_CONTROL_ADDRESS {
return self.lcd_control;
} else if address == LCD_Y_ADDRESS {
return self.lcd_y;
}
self.io_registers[(address - 0xFF40) as usize]
} }
pub fn set_register(&mut self, address: u16, data: u8) { pub fn set_register(&mut self, address: u16, data: u8) {
if address >= HDMA1_ADDRESS && address <= HDMA5_ADDRESS {
match address { match address {
HDMA1_ADDRESS..=HDMA5_ADDRESS => match address {
HDMA1_ADDRESS => self.hdma_source = (self.hdma_source & 0xFF) | ((data as u16) << 8), HDMA1_ADDRESS => self.hdma_source = (self.hdma_source & 0xFF) | ((data as u16) << 8),
HDMA2_ADDRESS => self.hdma_source = (self.hdma_source & 0xFF00) | (data as u16), HDMA2_ADDRESS => self.hdma_source = (self.hdma_source & 0xFF00) | (data as u16),
HDMA3_ADDRESS => self.hdma_destination = (self.hdma_destination & 0xFF) | ((data as u16) << 8), HDMA3_ADDRESS => self.hdma_destination = (self.hdma_destination & 0xFF) | ((data as u16) << 8),
HDMA4_ADDRESS => self.hdma_destination = (self.hdma_destination & 0xFF00) | (data as u16), HDMA4_ADDRESS => self.hdma_destination = (self.hdma_destination & 0xFF00) | (data as u16),
HDMA5_ADDRESS => self.hdma_start = data, HDMA5_ADDRESS => self.hdma_start = data,
_ => (), _ => (),
}; },
} else if address >= 0xFF68 && address <= 0xFF6B { 0xFF68..=0xFF6B => {
self.cram_registers[(address as usize) - 0xFF68] = data; self.cram_registers[(address as usize) - 0xFF68] = data;
match address {
if address == BCPD_BGPD_ADDRESS { BCPD_BGPD_ADDRESS => {
if self.get_lcd_status(LCDStatus::ModeFlag(LCDStatusModeFlag::TransferringToLCD)) { if self.get_lcd_status(LCDStatus::ModeFlag(LCDStatusModeFlag::TransferringToLCD)) {
return; return;
} }
@ -439,7 +435,8 @@ impl PPU {
if auto_increment { if auto_increment {
self.cram_registers[(BCPS_BGPI_ADDRESS as usize) - 0xFF68] = ((byte + 1) & 0b111111) | ((auto_increment as u8) << 7); self.cram_registers[(BCPS_BGPI_ADDRESS as usize) - 0xFF68] = ((byte + 1) & 0b111111) | ((auto_increment as u8) << 7);
} }
} else if address == OCPD_OBPD_ADDRESS { },
OCPD_OBPD_ADDRESS => {
if self.get_lcd_status(LCDStatus::ModeFlag(LCDStatusModeFlag::TransferringToLCD)) { if self.get_lcd_status(LCDStatus::ModeFlag(LCDStatusModeFlag::TransferringToLCD)) {
return; return;
} }
@ -451,11 +448,12 @@ impl PPU {
self.cram_registers[(OCPS_OBPI_ADDRESS as usize) - 0xFF68] = ((byte + 1) & 0b111111) | ((auto_increment as u8) << 7); self.cram_registers[(OCPS_OBPI_ADDRESS as usize) - 0xFF68] = ((byte + 1) & 0b111111) | ((auto_increment as u8) << 7);
} }
} }
} else if address == VRAM_BANK_SELECT_ADDRESS { _ => {},
return self.set_vram_bank(data); }
} else if address == LCD_Y_ADDRESS { },
return; VRAM_BANK_SELECT_ADDRESS => self.set_vram_bank(data),
} else if address == LCD_CONTROL_ADDRESS { LCD_Y_ADDRESS => {},
LCD_CONTROL_ADDRESS => {
self.lcd_control = data; self.lcd_control = data;
// Check if LCD is being turned on or off // Check if LCD is being turned on or off
self.lcd_enable = get_bit(data, BitIndex::I7); self.lcd_enable = get_bit(data, BitIndex::I7);
@ -465,14 +463,14 @@ impl PPU {
let byte = self.io_registers[LCD_STATUS_ADDRESS as usize - 0xFF40]; let byte = self.io_registers[LCD_STATUS_ADDRESS as usize - 0xFF40];
self.io_registers[LCD_STATUS_ADDRESS as usize - 0xFF40] = byte & 0b11111100; self.io_registers[LCD_STATUS_ADDRESS as usize - 0xFF40] = byte & 0b11111100;
} }
return; },
} else if address == LCD_STATUS_ADDRESS { LCD_STATUS_ADDRESS => {
let address = address - 0xFF40; let address = address - 0xFF40;
let byte = self.io_registers[address as usize]; let byte = self.io_registers[address as usize];
self.io_registers[address as usize] = (data & 0b11111000) | (byte & 0b00000111); self.io_registers[address as usize] = (data & 0b11111000) | (byte & 0b00000111);
} else { },
self.io_registers[address as usize - 0xFF40] = data; _ => self.io_registers[address as usize - 0xFF40] = data,
} };
} }
pub fn force_set_register(&mut self, address: u16, data: u8) { pub fn force_set_register(&mut self, address: u16, data: u8) {

View File

@ -1,3 +1,5 @@
use crate::bus::{ECHO_RAM, WORK_RAM_1};
pub const WRAM_BANK_SELECT_ADDRESS: u16 = 0xFF70; pub const WRAM_BANK_SELECT_ADDRESS: u16 = 0xFF70;
pub trait RAM { pub trait RAM {
@ -17,11 +19,20 @@ impl DMGRAM {
} }
} }
fn parse_echo_ram_address(address: u16) -> u16 {
let mut address = address;
if ECHO_RAM.contains(&address) {
address = WORK_RAM_1.min().unwrap() + ((address - ECHO_RAM.min().unwrap()) & 0x1FFF);
}
address
}
impl RAM for DMGRAM { impl RAM for DMGRAM {
fn read(&self, address: u16) -> u8 { fn read(&self, address: u16) -> u8 {
if address == WRAM_BANK_SELECT_ADDRESS { if address == WRAM_BANK_SELECT_ADDRESS {
return 0xFF; return 0xFF;
} }
let address = parse_echo_ram_address(address);
self.data[(address - 0xC000) as usize] self.data[(address - 0xC000) as usize]
} }
@ -29,6 +40,7 @@ impl RAM for DMGRAM {
if address == WRAM_BANK_SELECT_ADDRESS { if address == WRAM_BANK_SELECT_ADDRESS {
return; return;
} }
let address = parse_echo_ram_address(address);
self.data[(address - 0xC000) as usize] = value; self.data[(address - 0xC000) as usize] = value;
} }
} }
@ -63,6 +75,7 @@ impl RAM for CGBRAM {
if address == WRAM_BANK_SELECT_ADDRESS { if address == WRAM_BANK_SELECT_ADDRESS {
return self.bank; return self.bank;
} }
let address = parse_echo_ram_address(address);
if address <= 0xCFFF { if address <= 0xCFFF {
return self.data[(address - 0xC000) as usize]; return self.data[(address - 0xC000) as usize];
} }
@ -72,7 +85,9 @@ impl RAM for CGBRAM {
fn write(&mut self, address: u16, value: u8) { fn write(&mut self, address: u16, value: u8) {
if address == WRAM_BANK_SELECT_ADDRESS { if address == WRAM_BANK_SELECT_ADDRESS {
return self.switch_bank(value); return self.switch_bank(value);
} else if address <= 0xCFFF { }
let address = parse_echo_ram_address(address);
if address <= 0xCFFF {
return self.data[(address - 0xC000) as usize] = value; return self.data[(address - 0xC000) as usize] = value;
} }
self.data[((address - 0xD000) as usize) + (4096 * (self.bank as usize))] = value; self.data[((address - 0xD000) as usize) + (4096 * (self.bank as usize))] = value;

View File

@ -2,6 +2,7 @@ use crate::emulator::Emulator;
use crate::frames::Frames; use crate::frames::Frames;
use crate::ppu::{WIDTH, HEIGHT}; use crate::ppu::{WIDTH, HEIGHT};
use std::env;
use log::error; use log::error;
use pixels::{wgpu, Pixels, PixelsBuilder, SurfaceTexture}; use pixels::{wgpu, Pixels, PixelsBuilder, SurfaceTexture};
use winit::dpi::LogicalSize; use winit::dpi::LogicalSize;
@ -10,10 +11,13 @@ use winit::event_loop::{ControlFlow, EventLoop};
use winit::window::{Window, WindowBuilder}; use winit::window::{Window, WindowBuilder};
use winit_input_helper::WinitInputHelper; use winit_input_helper::WinitInputHelper;
fn is_fps_unlocked() -> bool {
!env::var("UNLOCK_FPS").is_err()
}
pub fn create_pixels(width: u32, height: u32, window: &Window) -> Pixels { pub fn create_pixels(width: u32, height: u32, window: &Window) -> Pixels {
let window_size = window.inner_size(); let window_size = window.inner_size();
let surface_texture = SurfaceTexture::new(window_size.width, window_size.height, window); let surface_texture = SurfaceTexture::new(window_size.width, window_size.height, window);
// Pixels::new(width, height, surface_texture).unwrap()
PixelsBuilder::new(width, height, surface_texture) PixelsBuilder::new(width, height, surface_texture)
.device_descriptor(wgpu::DeviceDescriptor { .device_descriptor(wgpu::DeviceDescriptor {
limits: wgpu::Limits { limits: wgpu::Limits {
@ -24,7 +28,7 @@ pub fn create_pixels(width: u32, height: u32, window: &Window) -> Pixels {
}, },
..wgpu::DeviceDescriptor::default() ..wgpu::DeviceDescriptor::default()
}) })
.enable_vsync(false) .enable_vsync(!is_fps_unlocked())
.build() .build()
.unwrap() .unwrap()
} }
@ -88,7 +92,9 @@ pub fn start_eventloop() {
frame_counter.reset_timer(); frame_counter.reset_timer();
} }
window.request_redraw(); window.request_redraw();
if !is_fps_unlocked() {
frame_limit.limit(); frame_limit.limit();
}
frame_limit.reset_timer(); frame_limit.reset_timer();
}, },
Event::RedrawRequested(_) => { Event::RedrawRequested(_) => {