Compare commits

...

3 Commits

Author SHA1 Message Date
f90900a4c1 Enabling lcd and vblank flag registers 2021-10-28 22:13:23 -05:00
c401726da5 update readme 2021-10-28 19:42:15 -05:00
7f9fdc9935 PPU timings 2021-10-28 19:38:30 -05:00
6 changed files with 151 additions and 72 deletions

View File

@ -6,13 +6,12 @@ This is just a fun project I'm making for learning and practice purposes. If you
Any help or suggestion is welcome! Any help or suggestion is welcome!
## TODO ## TODO
- [x] CPU Registers implementation - [x] CPU implementation
- [x] CPU Instructions implementations
- [ ] Interrupts - [ ] Interrupts
- [ ] Timing - [ ] Timing
- [ ] PPU implementations - [ ] PPU implementations
- [ ] Gameboy boot ROM - [ ] Gameboy boot ROM
- [ ] Render the pixels - [x] Render the pixels
- [ ] Gameboy Color compatibility - [ ] Gameboy Color compatibility
- [ ] Sound - [ ] Sound
- [ ] Web Assembly support (because this is a Rust project and it has to support Web Assembly) - [ ] Web Assembly support (because this is a Rust project and it has to support Web Assembly)

View File

@ -1,5 +1,12 @@
use crate::utils::{join_bytes}; use crate::utils::{
get_bit,
set_bit,
BitIndex,
join_bytes
};
use crate::rom::ROM; use crate::rom::ROM;
use crate::ppu::{PPU, LCDStatus, LCDStatusModeFlag};
use crate::cpu::{InterruptFlag};
pub struct AddressRange { pub struct AddressRange {
begin: u16, begin: u16,
@ -32,6 +39,7 @@ pub const NOT_USABLE: AddressRange = AddressRange{begin: 0xFEA0,
pub const IO_REGISTERS: AddressRange = AddressRange{begin: 0xFF00, end: 0xFF7F}; pub const IO_REGISTERS: AddressRange = AddressRange{begin: 0xFF00, end: 0xFF7F};
pub const HIGH_RAM: AddressRange = AddressRange{begin: 0xFF80, end: 0xFFFE}; 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_REGISTER: AddressRange = AddressRange{begin: 0xFFFF, end: 0xFFFF};
pub const INTERRUPT_FLAG_ADDRESS: u16 = 0xFF0F;
pub struct Bus { pub struct Bus {
game_rom: ROM, game_rom: ROM,
@ -40,7 +48,8 @@ pub struct Bus {
impl Bus { impl Bus {
pub fn new() -> Self { pub fn new() -> Self {
let game_rom = match ROM::load_file("ignore/dmg-acid2.gb".to_string()) { 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/01-special.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/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/04-op r,imm.gb".to_string()) {
@ -64,12 +73,12 @@ impl Bus {
pub fn read(&self, address: u16) -> u8 { pub fn read(&self, address: u16) -> u8 {
if BANK_ZERO.in_range(address) || BANK_SWITCHABLE.in_range(address) { if BANK_ZERO.in_range(address) || BANK_SWITCHABLE.in_range(address) {
return self.game_rom.read(address); return self.game_rom.read(address);
} else if IO_REGISTERS.in_range(address) { } else if VIDEO_RAM.in_range(address) {
return match address { if PPU::get_lcd_status(self, LCDStatus::ModeFlag(LCDStatusModeFlag::TransferringToLCD)) {
0xFF44 => 0x90, return 0xFF
0xFF4D => 0xFF,
_ => self.data[address as usize],
} }
} else if IO_REGISTERS.in_range(address) {
return self.data[address as usize];
} }
self.data[address as usize] self.data[address as usize]
} }
@ -94,8 +103,13 @@ impl Bus {
} else if ECHO_RAM.in_range(address) { } else if ECHO_RAM.in_range(address) {
self.data[address as usize] = data; self.data[address as usize] = data;
self.data[(WORK_RAM_1.begin() + (address - ECHO_RAM.begin())) as usize] = data; // Copy to the working RAM 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)) {
self.data[address as usize] = data;
// }
} else {
self.data[address as usize] = 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) {
@ -103,4 +117,15 @@ impl Bus {
self.write(address, bytes[0]); self.write(address, bytes[0]);
self.write(address.wrapping_add(1), bytes[1]); 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),
});
}
} }

View File

@ -806,8 +806,6 @@ pub enum Opcode {
PrefixCB(Box<Opcode>), PrefixCB(Box<Opcode>),
IllegalInstruction, IllegalInstruction,
} }
// Frequency un Hz
const FREQUENCY: f64 = 4194.304;
// Store cycles in M // Store cycles in M
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]

View File

@ -20,8 +20,8 @@ impl Emulator {
} }
pub fn draw(&mut self, frame: &mut [u8]) { pub fn draw(&mut self, frame: &mut [u8]) {
self.ppu.draw_background(&self.bus); // self.ppu.draw_background(&mut self.bus);
let ppu_frame = self.ppu.get_rgba_frame(&self.bus); let ppu_frame = self.ppu.get_rgba_frame();
for (i, pixel) in frame.chunks_exact_mut(4).enumerate() { for (i, pixel) in frame.chunks_exact_mut(4).enumerate() {
pixel.copy_from_slice(&ppu_frame[i]); pixel.copy_from_slice(&ppu_frame[i]);
} }
@ -30,6 +30,7 @@ impl Emulator {
pub fn run(&mut self, cpu_cycles: Cycles) { pub fn run(&mut self, cpu_cycles: Cycles) {
self.cpu.reset_cycles(); self.cpu.reset_cycles();
while self.cpu.get_cycles().0 <= cpu_cycles.0 { while self.cpu.get_cycles().0 <= cpu_cycles.0 {
self.ppu.do_cycle(&mut self.bus);
self.cpu.run(&mut self.bus); self.cpu.run(&mut self.bus);
} }
} }

View File

@ -5,8 +5,7 @@ use crate::utils::{
to_bit_index, to_bit_index,
}; };
use crate::bus::{Bus, AddressRange, BANK_ZERO, VIDEO_RAM}; use crate::bus::{Bus, AddressRange, BANK_ZERO, VIDEO_RAM};
use crate::cpu::{Cycles}; use crate::cpu::{Cycles, InterruptFlag};
use rand::Rng;
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
enum Pixel { enum Pixel {
@ -46,8 +45,10 @@ pub enum LCDStatus {
ModeFlag(LCDStatusModeFlag), ModeFlag(LCDStatusModeFlag),
} }
pub const WIDTH: u32 = 160; pub const LCD_WIDTH: u32 = 160;
pub const HEIGHT: u32 = 144; pub const LCD_HEIGHT: u32 = 144;
pub const WIDTH: u32 = LCD_WIDTH;
pub const HEIGHT: u32 = LCD_HEIGHT;
pub const FRAME_BUFFER_LENGTH: u32 = WIDTH * HEIGHT; pub const FRAME_BUFFER_LENGTH: u32 = WIDTH * HEIGHT;
const LCD_CONTROL_ADDRESS: u16 = 0xFF40; const LCD_CONTROL_ADDRESS: u16 = 0xFF40;
@ -74,10 +75,69 @@ impl PPU {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
cycles: Cycles(0), cycles: Cycles(0),
rgba_frame: [[0, 0, 0xFF, 0]; FRAME_BUFFER_LENGTH as usize], rgba_frame: [[0xFF, 0xFF, 0xFF, 0]; FRAME_BUFFER_LENGTH as usize],
} }
} }
pub fn reset_cycles(&mut self) {
self.cycles.0 = 0;
}
pub fn increment_cycles(&mut self, cycles: Cycles) {
self.cycles.0 += cycles.0;
}
pub fn do_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);
}
} 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);
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);
PPU::set_lcd_status(bus, LCDStatus::ModeFlag(LCDStatusModeFlag::HBlank), true);
}
}
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);
}
self.increment_cycles(Cycles(1));
// Horizontal scan completed
if self.cycles.0 > 456 {
self.reset_cycles();
PPU::set_lcd_y(bus, PPU::get_lcd_y(bus) + 1);
// Frame completed
if PPU::get_lcd_y(bus) > 153 {
PPU::set_lcd_y(bus, 0);
}
}
}
fn get_lcd_y(bus: &Bus) -> u8 {
bus.read(LCD_Y_ADDRESS)
}
fn set_lcd_y(bus: &mut Bus, val: u8) {
bus.write(LCD_Y_ADDRESS, val);
}
fn get_scroll_x(bus: &Bus) -> u8 { fn get_scroll_x(bus: &Bus) -> u8 {
bus.read(SCROLL_X_ADDRESS) bus.read(SCROLL_X_ADDRESS)
} }
@ -94,36 +154,36 @@ impl PPU {
bus.write(SCROLL_Y_ADDRESS, val); bus.write(SCROLL_Y_ADDRESS, val);
} }
fn get_lcd_control(bus: &Bus, control: LCDControl) -> bool { pub fn get_lcd_control(bus: &Bus, control: LCDControl) -> bool {
let byte = bus.read(LCD_CONTROL_ADDRESS); let byte = bus.read(LCD_CONTROL_ADDRESS);
match control { match control {
LCDControl::DisplayEnable => get_bit(byte, BitIndex::I7), LCDControl::DisplayEnable => get_bit(byte, BitIndex::I7),
LCDControl::WindowTileMapAddress => get_bit(byte, BitIndex::I6), LCDControl::WindowTileMapAddress => get_bit(byte, BitIndex::I6),
LCDControl::WindowEnable => get_bit(byte, BitIndex::I5), LCDControl::WindowEnable => get_bit(byte, BitIndex::I5),
LCDControl::BackgroundWindowTileAddress => get_bit(byte, BitIndex::I4), LCDControl::BackgroundWindowTileAddress => get_bit(byte, BitIndex::I4),
LCDControl::BackgroundTileMapAddress => get_bit(byte, BitIndex::I3), LCDControl::BackgroundTileMapAddress => get_bit(byte, BitIndex::I3),
LCDControl::ObjectSize => get_bit(byte, BitIndex::I2), LCDControl::ObjectSize => get_bit(byte, BitIndex::I2),
LCDControl::ObjectEnable => get_bit(byte, BitIndex::I1), LCDControl::ObjectEnable => get_bit(byte, BitIndex::I1),
LCDControl::BackgroundPriority => get_bit(byte, BitIndex::I0), LCDControl::BackgroundPriority => get_bit(byte, BitIndex::I0),
} }
} }
fn set_lcd_control(bus: &mut Bus, control: LCDControl, val: bool) { fn set_lcd_control(bus: &mut Bus, control: LCDControl, val: bool) {
let mut byte = bus.read(LCD_CONTROL_ADDRESS); let mut byte = bus.read(LCD_CONTROL_ADDRESS);
byte = match control { byte = match control {
LCDControl::DisplayEnable => set_bit(byte, val, BitIndex::I7), LCDControl::DisplayEnable => set_bit(byte, val, BitIndex::I7),
LCDControl::WindowTileMapAddress => set_bit(byte, val, BitIndex::I6), LCDControl::WindowTileMapAddress => set_bit(byte, val, BitIndex::I6),
LCDControl::WindowEnable => set_bit(byte, val, BitIndex::I5), LCDControl::WindowEnable => set_bit(byte, val, BitIndex::I5),
LCDControl::BackgroundWindowTileAddress => set_bit(byte, val, BitIndex::I4), LCDControl::BackgroundWindowTileAddress => set_bit(byte, val, BitIndex::I4),
LCDControl::BackgroundTileMapAddress => set_bit(byte, val, BitIndex::I3), LCDControl::BackgroundTileMapAddress => set_bit(byte, val, BitIndex::I3),
LCDControl::ObjectSize => set_bit(byte, val, BitIndex::I2), LCDControl::ObjectSize => set_bit(byte, val, BitIndex::I2),
LCDControl::ObjectEnable => set_bit(byte, val, BitIndex::I1), LCDControl::ObjectEnable => set_bit(byte, val, BitIndex::I1),
LCDControl::BackgroundPriority => set_bit(byte, val, BitIndex::I0), LCDControl::BackgroundPriority => set_bit(byte, val, BitIndex::I0),
}; };
bus.write(LCD_CONTROL_ADDRESS, byte); bus.write(LCD_CONTROL_ADDRESS, byte);
} }
fn get_lcd_status(bus: &Bus, status: LCDStatus) -> bool { pub fn get_lcd_status(bus: &Bus, status: LCDStatus) -> bool {
let byte = bus.read(LCD_STATUS_ADDRESS); let byte = bus.read(LCD_STATUS_ADDRESS);
match status { match status {
LCDStatus::LYCInterrupt => get_bit(byte, BitIndex::I6), LCDStatus::LYCInterrupt => get_bit(byte, BitIndex::I6),
@ -158,6 +218,35 @@ impl PPU {
bus.write(LCD_STATUS_ADDRESS, byte); bus.write(LCD_STATUS_ADDRESS, byte);
} }
pub fn draw_line(&mut self, bus: &Bus) {
let lcd_y = PPU::get_lcd_y(bus);
if lcd_y as u32 >= LCD_HEIGHT {
return;
}
let mut lcd_x: u8 = 0;
while (lcd_x as u32) < LCD_WIDTH {
let y = lcd_y.wrapping_add(PPU::get_scroll_y(bus));
let x = lcd_x.wrapping_add(PPU::get_scroll_x(bus));
let index_x = x as u16 / 8;
let index_y = (y as u16 / 8) * 32;
let index = index_x + index_y;
let tile_line = (y).rem_euclid(8) * 2;
let tile_number = bus.read(0x9800 + index as u16) as u16;
let addr = 0x8000 + tile_line as u16 + (tile_number * 16);
let tile_byte_1 = bus.read(addr);
let tile_byte_2 = bus.read(addr + 1);
let pixels = PPU::get_byte_pixels(tile_byte_1, tile_byte_2);
for pixel in pixels {
let idx = lcd_x as usize + (lcd_y as usize * LCD_WIDTH as usize);
self.rgba_frame[idx] = PPU::get_rgba(pixel);
lcd_x += 1;
}
}
}
fn get_pixel(two_bit_pixel: u8) -> Pixel { fn get_pixel(two_bit_pixel: u8) -> Pixel {
match two_bit_pixel { match two_bit_pixel {
0x00 => Pixel::White, 0x00 => Pixel::White,
@ -177,38 +266,6 @@ impl PPU {
} }
} }
pub fn draw_background(&mut self, bus: &Bus) {
let mut idx = 0;
// let mut tile_line: u16 = 0;
let mut lcd_y: u8 = 0;
while lcd_y < 144 {
let mut lcd_x: u8 = 0;
while lcd_x < 160 {
let y = lcd_y.wrapping_add(PPU::get_scroll_y(bus));
let x = lcd_x.wrapping_add(PPU::get_scroll_x(bus));
let index_x = (x as u16 / 8);
let index_y = (y as u16 / 8) * 32;
let index = index_x + index_y;
let tile_line = (y).rem_euclid(8) * 2;
let index_byte = (bus.read(0x9800 + index as u16) as u16) * 16;
let tile_byte_1 = bus.read(0x8000 + tile_line as u16 + index_byte);
let tile_byte_2 = bus.read(0x8000 + tile_line as u16 + index_byte + 1);
let pixels = PPU::get_byte_pixels(tile_byte_1, tile_byte_2);
for pixel in pixels {
self.rgba_frame[idx] = PPU::get_rgba(pixel);
idx += 1;
}
lcd_x += 8;
}
lcd_y += 1;
// tile_line += 2;
}
}
fn get_byte_pixels(byte1: u8, byte2: u8) -> [Pixel; 8] { fn get_byte_pixels(byte1: u8, byte2: u8) -> [Pixel; 8] {
let mut pixels: [Pixel; 8] = [Pixel::White; 8]; let mut pixels: [Pixel; 8] = [Pixel::White; 8];
pixels[0] = PPU::get_pixel(((get_bit(byte1, BitIndex::I7) as u8) << 1) | (get_bit(byte2, BitIndex::I7) as u8)); pixels[0] = PPU::get_pixel(((get_bit(byte1, BitIndex::I7) as u8) << 1) | (get_bit(byte2, BitIndex::I7) as u8));
@ -222,7 +279,7 @@ impl PPU {
pixels pixels
} }
pub fn get_rgba_frame(&self, bus: &Bus) -> &[[u8; 4]; FRAME_BUFFER_LENGTH as usize] { pub fn get_rgba_frame(&self) -> &[[u8; 4]; FRAME_BUFFER_LENGTH as usize] {
&self.rgba_frame &self.rgba_frame
} }
} }

View File

@ -38,7 +38,6 @@ pub fn start_eventloop() {
let mut emulator = Emulator::new(); let mut emulator = Emulator::new();
let mut count: usize = 0;
event_loop.run(move |event, _, control_flow| { event_loop.run(move |event, _, control_flow| {
// Handle input events // Handle input events
if input.update(&event) { if input.update(&event) {
@ -66,7 +65,7 @@ pub fn start_eventloop() {
emulator.run(Cycles(70224)); emulator.run(Cycles(70224));
emulator.draw(pixels.get_frame()); emulator.draw(pixels.get_frame());
thread::sleep(time::Duration::from_millis(14)); // thread::sleep(time::Duration::from_millis(14));
window.request_redraw(); window.request_redraw();
}, },
Event::RedrawRequested(_) => { Event::RedrawRequested(_) => {