refactor bus and ppu registers, env var to unlock fps

This commit is contained in:
Franco Colmenarez 2023-10-09 22:16:00 -05:00
parent 76a930385d
commit 4b47ae96a1
5 changed files with 190 additions and 135 deletions

View File

@ -1,3 +1,4 @@
use std::env;
use std::ops::RangeInclusive;
use crate::utils::join_bytes;
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 PREPARE_SPEED_SWITCH_ADDRESS: u16 = 0xFF4D;
enum MemoryMap {
BankZero,
BankSwitchable,
VideoRam,
ExternalRam,
WorkRam1,
WorkRam2,
EchoRam,
SpriteAttributeTable,
NotUsable,
IoRegisters,
HighRam,
InterruptEnable,
}
pub struct Bus {
data: [u8; 0x10000],
pub rom: Box<dyn ROM>,
@ -59,7 +75,8 @@ impl Bus {
},
};
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 {
data: [0x00; 0x10000],
rom,
@ -103,34 +120,53 @@ impl 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 {
match Bus::map_address(address) {
MemoryMap::BankZero | MemoryMap::BankSwitchable | MemoryMap::ExternalRam => self.rom.read(address),
MemoryMap::WorkRam1 | MemoryMap::WorkRam2 | MemoryMap::EchoRam => self.ram.read(address),
MemoryMap::VideoRam => self.ppu.read_vram_external(address),
MemoryMap::SpriteAttributeTable => self.ppu.read_oam(address),
MemoryMap::IoRegisters => {
if self.cgb_mode && address == PREPARE_SPEED_SWITCH_ADDRESS {
let byte = self.data[address as usize];
let current_speed = (self.double_speed_mode as u8) << 7;
let prepare_speed_switch = self.prepare_double_speed_mode as u8;
return (byte & 0b0111_1110) | current_speed | prepare_speed_switch;
} else if BANK_ZERO.contains(&address) || BANK_SWITCHABLE.contains(&address) || EXTERNAL_RAM.contains(&address) {
return self.rom.read(address);
} else if WORK_RAM_1.contains(&address) || WORK_RAM_2.contains(&address) || address == WRAM_BANK_SELECT_ADDRESS {
} else if address == WRAM_BANK_SELECT_ADDRESS {
return self.ram.read(address);
} else if ECHO_RAM.contains(&address) {
return self.ram.read(WORK_RAM_1.min().unwrap() + ((address - ECHO_RAM.min().unwrap()) & 0x1FFF));
} else if address == INTERRUPT_ENABLE_ADDRESS || address == INTERRUPT_FLAG_ADDRESS {
} else if address == INTERRUPT_FLAG_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 == 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 {
@ -138,47 +174,48 @@ impl Bus {
}
pub fn write(&mut self, address: u16, data: u8) {
if address == 0xFF01 {
// print!("{}", data as char);
}
match Bus::map_address(address) {
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),
MemoryMap::SpriteAttributeTable => self.ppu.write_oam(address, data),
MemoryMap::IoRegisters => {
if self.cgb_mode && address == PREPARE_SPEED_SWITCH_ADDRESS {
let current_byte = self.data[address as usize];
self.prepare_double_speed_mode = (data & 1) == 1;
// bit 7 is read only on cgb mode
self.data[address as usize] = (current_byte & 0b1000_0000) | (data & 0b0111_1111);
} else if BANK_ZERO.contains(&address) || BANK_SWITCHABLE.contains(&address) || EXTERNAL_RAM.contains(&address) {
self.rom.write(address, data);
} else if address == INTERRUPT_ENABLE_ADDRESS || address == INTERRUPT_FLAG_ADDRESS {
self.interrupts.write(address, data);
} else if WORK_RAM_1.contains(&address) || WORK_RAM_2.contains(&address) || address == WRAM_BANK_SELECT_ADDRESS {
} else if 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 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 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 {
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) {

View File

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

View File

@ -394,41 +394,37 @@ impl PPU {
}
pub fn get_register(&self, address: u16) -> u8 {
if address >= HDMA1_ADDRESS && address <= HDMA5_ADDRESS {
return match address {
match address {
HDMA1_ADDRESS..=HDMA5_ADDRESS => match address {
HDMA1_ADDRESS => self.hdma_source.to_be_bytes()[0],
HDMA2_ADDRESS => self.hdma_source.to_be_bytes()[1],
HDMA3_ADDRESS => self.hdma_destination.to_be_bytes()[0],
HDMA4_ADDRESS => self.hdma_destination.to_be_bytes()[1],
HDMA5_ADDRESS => self.hdma_start,
_ => 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) {
if address >= HDMA1_ADDRESS && address <= HDMA5_ADDRESS {
match address {
HDMA1_ADDRESS..=HDMA5_ADDRESS => match address {
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),
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),
HDMA5_ADDRESS => self.hdma_start = data,
_ => (),
};
} else if address >= 0xFF68 && address <= 0xFF6B {
},
0xFF68..=0xFF6B => {
self.cram_registers[(address as usize) - 0xFF68] = data;
if address == BCPD_BGPD_ADDRESS {
match address {
BCPD_BGPD_ADDRESS => {
if self.get_lcd_status(LCDStatus::ModeFlag(LCDStatusModeFlag::TransferringToLCD)) {
return;
}
@ -439,7 +435,8 @@ impl PPU {
if auto_increment {
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)) {
return;
}
@ -451,11 +448,12 @@ impl PPU {
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;
} else if address == LCD_CONTROL_ADDRESS {
_ => {},
}
},
VRAM_BANK_SELECT_ADDRESS => self.set_vram_bank(data),
LCD_Y_ADDRESS => {},
LCD_CONTROL_ADDRESS => {
self.lcd_control = data;
// Check if LCD is being turned on or off
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];
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 byte = self.io_registers[address as usize];
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) {

View File

@ -1,3 +1,5 @@
use crate::bus::{ECHO_RAM, WORK_RAM_1};
pub const WRAM_BANK_SELECT_ADDRESS: u16 = 0xFF70;
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 {
fn read(&self, address: u16) -> u8 {
if address == WRAM_BANK_SELECT_ADDRESS {
return 0xFF;
}
let address = parse_echo_ram_address(address);
self.data[(address - 0xC000) as usize]
}
@ -29,6 +40,7 @@ impl RAM for DMGRAM {
if address == WRAM_BANK_SELECT_ADDRESS {
return;
}
let address = parse_echo_ram_address(address);
self.data[(address - 0xC000) as usize] = value;
}
}
@ -63,6 +75,7 @@ impl RAM for CGBRAM {
if address == WRAM_BANK_SELECT_ADDRESS {
return self.bank;
}
let address = parse_echo_ram_address(address);
if address <= 0xCFFF {
return self.data[(address - 0xC000) as usize];
}
@ -72,7 +85,9 @@ impl RAM for CGBRAM {
fn write(&mut self, address: u16, value: u8) {
if address == WRAM_BANK_SELECT_ADDRESS {
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;
}
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::ppu::{WIDTH, HEIGHT};
use std::env;
use log::error;
use pixels::{wgpu, Pixels, PixelsBuilder, SurfaceTexture};
use winit::dpi::LogicalSize;
@ -10,10 +11,13 @@ use winit::event_loop::{ControlFlow, EventLoop};
use winit::window::{Window, WindowBuilder};
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 {
let window_size = window.inner_size();
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)
.device_descriptor(wgpu::DeviceDescriptor {
limits: wgpu::Limits {
@ -24,7 +28,7 @@ pub fn create_pixels(width: u32, height: u32, window: &Window) -> Pixels {
},
..wgpu::DeviceDescriptor::default()
})
.enable_vsync(false)
.enable_vsync(!is_fps_unlocked())
.build()
.unwrap()
}
@ -88,7 +92,9 @@ pub fn start_eventloop() {
frame_counter.reset_timer();
}
window.request_redraw();
if !is_fps_unlocked() {
frame_limit.limit();
}
frame_limit.reset_timer();
},
Event::RedrawRequested(_) => {