Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix buttons & merge errors #35

Merged
merged 5 commits into from
Jun 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion src/cpu.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use registers::Register16Bit;
use registers::{Register16Bit, Register8Bit};

use crate::memory::Memory;

Expand Down Expand Up @@ -54,6 +54,27 @@ impl CPU {
}
}

/// Skip the bootrom
/// Set the registers to the correct values
/// Used for: https://robertheaton.com/gameboy-doctor/
pub fn skip_boot_rom(&mut self) {
self.set_8bit_register(Register8Bit::A, 0x01);
self.set_zero_flag();
self.set_half_carry_flag();
self.set_carry_flag();
self.set_8bit_register(Register8Bit::B, 0x00);
self.set_8bit_register(Register8Bit::C, 0x13);
self.set_8bit_register(Register8Bit::D, 0x00);
self.set_8bit_register(Register8Bit::E, 0xD8);
self.set_8bit_register(Register8Bit::H, 0x01);
self.set_8bit_register(Register8Bit::L, 0x4D);
self.set_16bit_register(Register16Bit::SP, 0xFFFE);
self.set_16bit_register(Register16Bit::PC, 0x0100);
self.memory.boot_rom_enabled = false;
// Set Joypad register
self.memory.write_byte(0xFF00, 0b1111_1111);
}

pub fn load_from_file(&mut self, file: &str, offset: usize) {
self.memory.load_from_file(file, offset);
}
Expand All @@ -63,6 +84,10 @@ impl CPU {
.read_byte(self.get_16bit_register(registers::Register16Bit::PC))
}

pub fn write_memory(&mut self, address: u16, value: u8) {
self.memory.write_byte(address, value);
}

/// Set the next instruction to be executed
/// This is used for testing
pub fn set_instruction(&mut self, instruction: Instructions) {
Expand Down
76 changes: 40 additions & 36 deletions src/cpu/joypad.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,54 +2,58 @@ use macroquad::prelude::*;

use super::{interrupts::InterruptTypes, CPU};

const JOYPAD_REGISTER: u16 = 0xFF00;

impl CPU {
pub fn update_key_input(&mut self) {
let keys_down = get_keys_down();

let mut output = 0xFF;

let joypad_selects = self.memory.read_byte(0xFF00);

//is button flag on
if (joypad_selects & 0x20) == 0 {
output &= !(1 << 5);
if keys_down.contains(&KeyCode::Enter) {
//start key
output &= !(1 << 3);
} else if keys_down.contains(&KeyCode::Tab) {
//select key
output &= !(1 << 2);
} else if keys_down.contains(&KeyCode::A) {
output &= !(1 << 0);
} else if keys_down.contains(&KeyCode::B) {
output &= !(1 << 1);
}
}

//is direction flag on
if (joypad_selects & 0x10) == 0 {
output &= !(1 << 4);
if keys_down.contains(&KeyCode::Left) {
output &= !(1 << 1);
} else if keys_down.contains(&KeyCode::Right) {
output &= !(1 << 0);
} else if keys_down.contains(&KeyCode::Up) {
output &= !(1 << 2);
} else if keys_down.contains(&KeyCode::Down) {
output &= !(1 << 3);
let previous_data = self.memory.read_byte(JOYPAD_REGISTER);

// Get the relevant bits of the joypad register (Inverted because the buttons are active low)
let selected_buttons = (!previous_data & 0x20) != 0;
let selected_directions = (!previous_data & 0x10) != 0;

let mut output = previous_data;

let key_map = if selected_buttons {
[
(KeyCode::Right, 0),
(KeyCode::Left, 1),
(KeyCode::Up, 2),
(KeyCode::Down, 3),
]
} else if selected_directions {
[
(KeyCode::A, 0),
(KeyCode::B, 1),
(KeyCode::Tab, 2),
(KeyCode::Enter, 3),
]
} else {
return;
};

for (key, bit) in key_map.iter() {
if keys_down.contains(key) {
output &= !(1 << bit);
} else {
output |= 1 << bit;
}
}

// If the joypad selects have changed, we need to set the joypad interrupt flag
if joypad_selects != output {
if previous_data != output {
self.set_interrupt_flag(InterruptTypes::Joypad);
}

self.memory.write_controller_byte(output);
}

pub fn get_mem_reg(& self, address: u16) -> u8 {
self.memory.read_byte(address)
pub fn enable_buttons_debug(&mut self) {
let mut joypad = self.memory.read_byte(JOYPAD_REGISTER);
// Enable button by setting the 5th bit to 0
joypad &= 0b1101_1111;
self.memory.write_controller_byte(joypad);
}

}
124 changes: 84 additions & 40 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@ pub mod memory;
pub mod rendering;

use std::{
f32::consts::E,
thread::sleep,
time::{self, Duration},
f32::consts::E, io::Write, thread::sleep, time::{self, Duration}
};

use macroquad::{prelude::*, ui::root_ui};
Expand All @@ -17,6 +15,7 @@ use rendering::{
tiles::*,
views::*,
};
use simple_log::LogConfigBuilder;

#[macro_use]
extern crate simple_log;
Expand All @@ -29,10 +28,20 @@ use crate::{
// Dots are PPU Cycle conters per Frame
const DOTS_PER_CPU_CYCLE: u32 = 4;
const DOTS_PER_LINE: u32 = 456;
const TIME_PER_FRAME: f32 = 1.0 / 60.0 * 1000.0;

const DUMP_GAMEBOY_DOCTOR_LOG: bool = true;

#[macroquad::main("GB Emulator")]
async fn main() {
simple_log::quick!();
// Set up logging
let config = LogConfigBuilder::builder()
.size(1 * 100)
.roll_count(10)
.level("info")
.output_console()
.build();
simple_log::new(config).unwrap();

// 60Hz
// This is the refresh rate of the Gameboy
Expand Down Expand Up @@ -81,13 +90,24 @@ async fn main() {

// Get start time
let mut last_frame_time = time::Instant::now();
let mut ppu_time = time::Instant::now();
let mut dump_time = time::Instant::now();
let mut frame = 0;

if DUMP_GAMEBOY_DOCTOR_LOG {
cpu.skip_boot_rom();
}

let mut scanline: u8 = 0;
let mut frame_cycles = 0;
let mut ppu_mode: PpuMode = PpuMode::OamScan;

// Open "registers.txt" file for Gameboy Doctor
let mut gb_doctor_file = std::fs::File::create("gameboy_doctor_log.txt").unwrap();
if DUMP_GAMEBOY_DOCTOR_LOG {
cpu.skip_boot_rom();
}

loop {
let instruction = cpu.prepare_and_decode_next_instruction();
log::debug!("🔠 Instruction: {:?}", instruction);
Expand All @@ -105,6 +125,31 @@ async fn main() {
let dot = (frame_cycles) * DOTS_PER_CPU_CYCLE;
cpu.set_lcd_y_coordinate(scanline);

if DUMP_GAMEBOY_DOCTOR_LOG {
// Dump registers to file for Gameboy Doctor like this
// A:00 F:11 B:22 C:33 D:44 E:55 H:66 L:77 SP:8888 PC:9999 PCMEM:AA,BB,CC,DD
let _ = gb_doctor_file.write_all(
format!(
"A:{:02X} F:{:02X} B:{:02X} C:{:02X} D:{:02X} E:{:02X} H:{:02X} L:{:02X} SP:{:04X} PC:{:04X} PCMEM:{:02X},{:02X},{:02X},{:02X}\n",
cpu.get_8bit_register(Register8Bit::A),
cpu.flags_to_u8(),
cpu.get_8bit_register(Register8Bit::B),
cpu.get_8bit_register(Register8Bit::C),
cpu.get_8bit_register(Register8Bit::D),
cpu.get_8bit_register(Register8Bit::E),
cpu.get_8bit_register(Register8Bit::H),
cpu.get_8bit_register(Register8Bit::L),
cpu.get_16bit_register(Register16Bit::SP),
cpu.get_16bit_register(Register16Bit::PC),
cpu.get_memory().read_byte(cpu.get_16bit_register(Register16Bit::PC)),
cpu.get_memory().read_byte(cpu.get_16bit_register(Register16Bit::PC) + 1),
cpu.get_memory().read_byte(cpu.get_16bit_register(Register16Bit::PC) + 2),
cpu.get_memory().read_byte(cpu.get_16bit_register(Register16Bit::PC) + 3),
)
.as_bytes(),
);
}

match ppu_mode {
PpuMode::OamScan => {
if dot % DOTS_PER_LINE >= 80 {
Expand Down Expand Up @@ -142,42 +187,41 @@ async fn main() {
cpu.set_ppu_mode(ppu_mode as u8);
frame_cycles += 1;

// Draw at 60Hz so 60 frames per second
if dot >= DOTS_PER_LINE * 155 {
while last_frame_time.elapsed() < time_per_frame {
// Do nothing
// TODO: Remove active wait
}

// Inform about the time it took to render the frame
root_ui().label(
None,
format!(
"Frame time: {:?} | Target: {:?} | Frame: {:?}",
last_frame_time.elapsed(),
time_per_frame,
frame
)
.as_str(),
);
last_frame_time = time::Instant::now();

// Update Debugging Views
update_atlas_from_memory(&cpu, 16 * 24, &mut tile_atlas, &PALETTE);
update_background_from_memory(&cpu, &mut background_image, &PALETTE, false, false);
background_viewer.draw(&background_image);
tile_viewer.draw(&tile_atlas);

gb_display.draw(&final_image);
next_frame().await;
// Set the VBlank interrupt since we are done with the frame
cpu.set_vblank_interrupt();
frame += 1;

// Dump memory every 3 seconds
if dump_time.elapsed().as_secs() >= 3 {
dump_time = time::Instant::now();
cpu.dump_memory();
if (ppu_time.elapsed().as_millis() as f32) >= TIME_PER_FRAME {
ppu_time = time::Instant::now();

// Draw at 60Hz so 60 frames per second
if dot >= DOTS_PER_LINE * 155 {
// Inform about the time it took to render the frame
root_ui().label(
None,
format!(
"Frame time: {:?} | Target: {:?} | Frame: {:?}",
last_frame_time.elapsed(),
time_per_frame,
frame
)
.as_str(),
);
last_frame_time = time::Instant::now();

// Update Debugging Views
update_atlas_from_memory(&cpu, 16 * 24, &mut tile_atlas, &PALETTE);
update_background_from_memory(&cpu, &mut background_image, &PALETTE, false, false);
background_viewer.draw(&background_image);
tile_viewer.draw(&tile_atlas);

gb_display.draw(&final_image);
next_frame().await;
// Set the VBlank interrupt since we are done with the frame
cpu.set_vblank_interrupt();
frame += 1;

// Dump memory every 3 seconds
if dump_time.elapsed().as_secs() >= 3 {
dump_time = time::Instant::now();
cpu.dump_memory();
}
}
}
}
Expand Down
Loading