From 54c8324020124f0df67e51a8841a291c496ed758 Mon Sep 17 00:00:00 2001 From: nchf3 Date: Sun, 23 Apr 2023 21:05:56 +0200 Subject: [PATCH 1/4] update ACID2 test status --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7091cbe..a5e8eab 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ Source files can be found [here](https://github.com/mattcurrie/dmg-acid2). This | test rom | Comment | Result | | -------- | ------- | ------ | -| dmg_acid2 | none | :x: | +| dmg_acid2 | sprite priority follows GB color behaviour | :x: | ## Ressources From 0576b056a0c03a0cbc3f588999f98767e51429d3 Mon Sep 17 00:00:00 2001 From: nchf3 Date: Sun, 23 Apr 2023 21:17:07 +0200 Subject: [PATCH 2/4] add a features section with a to-do list --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index a5e8eab..d9c415c 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,16 @@ Source files can be found [here](https://github.com/mattcurrie/dmg-acid2). This | -------- | ------- | ------ | | dmg_acid2 | sprite priority follows GB color behaviour | :x: | +## Features + +- [X] implement a gameboy emulator which passes all cpu_instr and instr_timing tests +- [X] add support to no_mbc / mbc1 / mbc3 cartridge types +- [X] implement a lightweight debugger +- [X] implement a vram viewer +- [ ] fix sprite priority to pass ACID2 test +- [ ] add possibility to save a game +- [ ] use winit and softbuffer instead of minifb (which is not as stable as expected) + ## Ressources ### General From 81b76372031323d57c8dbc84be5787c2b48587a7 Mon Sep 17 00:00:00 2001 From: nchf3 Date: Sun, 23 Apr 2023 22:06:12 +0200 Subject: [PATCH 3/4] use a generic type + trait bound for peripheral accesses --- src/debug.rs | 1 + src/soc/cpu/mod.rs | 45 ++++++------- src/soc/peripheral/mod.rs | 134 ++++++++++++++++++++++++-------------- 3 files changed, 109 insertions(+), 71 deletions(-) diff --git a/src/debug.rs b/src/debug.rs index 418928d..9c59671 100644 --- a/src/debug.rs +++ b/src/debug.rs @@ -1,4 +1,5 @@ use crate::emulator::{Emulator, EmulatorState, ONE_FRAME_IN_NS, ONE_FRAME_IN_CYCLES}; +use crate::soc::peripheral::IoAccess; use std::time::Instant; use std::io::{stdin, stdout, Write}; diff --git a/src/soc/cpu/mod.rs b/src/soc/cpu/mod.rs index 6fe67ac..f6d19ae 100644 --- a/src/soc/cpu/mod.rs +++ b/src/soc/cpu/mod.rs @@ -7,7 +7,7 @@ use instruction::{ }; use register::Registers; -use crate::soc::peripheral::{Peripheral, VBLANK_VECTOR, LCDSTAT_VECTOR, TIMER_VECTOR}; +use crate::soc::peripheral::{IoAccess, Interrupt, VBLANK_VECTOR, LCDSTAT_VECTOR, TIMER_VECTOR}; use crate::soc::peripheral::nvic::InterruptSources; const RUN_0_CYCLE: u8 = 0; @@ -485,7 +485,7 @@ macro_rules! reset { macro_rules! interrupt_enable { ($enable: ident, $self:ident, $peripheral:expr) => {{ - $peripheral.nvic.master_enable($enable); + $peripheral.master_enable($enable); $self.pc.wrapping_add(1) }}; } @@ -737,7 +737,7 @@ impl Cpu { } } - fn decode(&mut self, instruction_byte: u8, peripheral: &mut Peripheral) -> Option { + fn decode(&mut self, instruction_byte: u8, peripheral: &mut T) -> Option { if Instruction::is_long_instruction(instruction_byte) { let long_instruction_byte = peripheral.read(self.pc.wrapping_add(1)); Instruction::from_long_byte(long_instruction_byte) @@ -746,9 +746,9 @@ impl Cpu { } } - pub fn run(&mut self, peripheral: &mut Peripheral) -> u8 { + pub fn run(&mut self, peripheral: &mut T) -> u8 { // catch interrupt as soon as possible - if peripheral.nvic.is_an_interrupt_to_run() { + if peripheral.is_an_interrupt_to_run() { self.mode = CpuMode::INTERRUPT; } @@ -775,11 +775,11 @@ impl Cpu { CpuMode::INTERRUPT => { // get the interrupt source - if let Some(interrupt_source) = peripheral.nvic.get_interrupt() { + if let Some(interrupt_source) = peripheral.get_interrupt() { // set the cpu in RUN mode to handle interrupt routine self.mode = CpuMode::RUN; // disable interrupts while handling interrupt routine - peripheral.nvic.master_enable(false); + peripheral.master_enable(false); // jump to interrupt routine self.jump_to_interrupt_routine(interrupt_source, peripheral); // 2 NOP (2 cycles) + PUSH (2 cycles) + set PC (1 cycle) @@ -793,7 +793,7 @@ impl Cpu { CpuMode::HALT => { // exit HALT mode if an interrupt is pending - if peripheral.nvic.is_an_interrupt_pending() { + if peripheral.is_an_interrupt_pending() { self.mode = CpuMode::RUN } @@ -809,7 +809,7 @@ impl Cpu { } } - fn jump_to_interrupt_routine(&mut self, interrupt_source: InterruptSources, peripheral: &mut Peripheral) { + fn jump_to_interrupt_routine(&mut self, interrupt_source: InterruptSources, peripheral: &mut T) { self.push(self.pc, peripheral); match interrupt_source { InterruptSources::VBLANK => self.pc = VBLANK_VECTOR, @@ -819,7 +819,7 @@ impl Cpu { } } - fn execute(&mut self, instruction: Instruction, peripheral: &mut Peripheral) -> (u16, u8) { + fn execute(&mut self, instruction: Instruction, peripheral: &mut T) -> (u16, u8) { match instruction { // Arithmetic instructions Instruction::ADD(target) => arithmetic_instruction!(target, self.add, peripheral), @@ -1017,7 +1017,7 @@ impl Cpu { new_value } - fn load(&mut self, input_register: ArithmeticTarget, main_register: IncDecTarget, peripheral: &mut Peripheral) -> (u16, u8) { + fn load(&mut self, input_register: ArithmeticTarget, main_register: IncDecTarget, peripheral: &mut T) -> (u16, u8) { match main_register { IncDecTarget::A => load_input_register!(input_register => a, self, peripheral), IncDecTarget::B => load_input_register!(input_register => b, self, peripheral), @@ -1030,7 +1030,7 @@ impl Cpu { } } - fn load_sp(&mut self, target: SPTarget, peripheral: &mut Peripheral) -> (u16, u8) { + fn load_sp(&mut self, target: SPTarget, peripheral: &mut T) -> (u16, u8) { match target { SPTarget::FROM_SP => ({ let low_byte_address = peripheral.read(self.pc.wrapping_add(1)) as u16; @@ -1071,7 +1071,7 @@ impl Cpu { } } - fn load_store_ram(&mut self, target: RamTarget, load: bool, peripheral: &mut Peripheral) -> (u16, u8) { + fn load_store_ram(&mut self, target: RamTarget, load: bool, peripheral: &mut T) -> (u16, u8) { match target { RamTarget::OneByteAddress => ({ // get address from instruction @@ -1128,7 +1128,7 @@ impl Cpu { } } - fn jump_relative(&mut self, flag: bool, peripheral: &mut Peripheral) -> (u16, u8) { + fn jump_relative(&mut self, flag: bool, peripheral: &mut T) -> (u16, u8) { // get the immediate from memory let immediate_address = self.pc.wrapping_add(1); let immediate = peripheral.read(immediate_address) as i8 as u16; @@ -1142,7 +1142,7 @@ impl Cpu { } } - fn jump_immediate(&mut self, flag: bool, peripheral: &mut Peripheral) -> (u16, u8) { + fn jump_immediate(&mut self, flag: bool, peripheral: &mut T) -> (u16, u8) { // get the immediate from memory let low_immediate = peripheral.read(self.pc.wrapping_add(1)) as u16; let high_immediate = peripheral.read(self.pc.wrapping_add(2)) as u16; @@ -1161,7 +1161,7 @@ impl Cpu { self.registers.read_hl() } - fn pop(&mut self, peripheral: &mut Peripheral) -> u16 { + fn pop(&mut self, peripheral: &mut T) -> u16 { // get stack pointer values let low_stack_address = self.sp; let high_stack_address = self.sp.wrapping_add(1); @@ -1173,7 +1173,7 @@ impl Cpu { low_byte | (high_byte << 8) } - fn push(&mut self, push_data: u16, peripheral: &mut Peripheral) { + fn push(&mut self, push_data: u16, peripheral: &mut T) { // get bytes from data let high_byte = ((push_data & 0xFF00) >> 8) as u8; let low_byte = (push_data & 0x00FF) as u8; @@ -1187,7 +1187,7 @@ impl Cpu { self.sp = self.sp.wrapping_sub(2); } - fn add_sp(&mut self, peripheral: &mut Peripheral) -> u16 { + fn add_sp(&mut self, peripheral: &mut T) -> u16 { let immediate = peripheral.read(self.pc.wrapping_add(1)) as i8 as u16; let result = self.sp.wrapping_add(immediate); @@ -1203,19 +1203,19 @@ impl Cpu { self.pc.wrapping_add(2) } - fn reset(&mut self, addr_to_reset: u8, peripheral: &mut Peripheral) -> u16 { + fn reset(&mut self, addr_to_reset: u8, peripheral: &mut T) -> u16 { // save PC value on the stack self.push(self.pc.wrapping_add(1), peripheral); // return next PC value addr_to_reset as u16 } - fn reti(&mut self, peripheral: &mut Peripheral) -> u16 { - peripheral.nvic.master_enable(true); + fn reti(&mut self, peripheral: &mut T) -> u16 { + peripheral.master_enable(true); self.pop(peripheral) } - fn call(&mut self, flag: bool, peripheral: &mut Peripheral) -> (u16, u8) { + fn call(&mut self, flag: bool, peripheral: &mut T) -> (u16, u8) { // do the call following the flag value if flag { // save the return address on the stack @@ -1469,6 +1469,7 @@ mod cpu_tests { IncDecTarget, JumpTarget, Load16Target, PopPushTarget, ResetTarget, SPTarget, U16Target, }; use crate::cartridge::{Cartridge, CARTRIDGE_TYPE_OFFSET, CARTRIDGE_RAM_SIZE_OFFSET, CARTRIDGE_ROM_SIZE_OFFSET}; + use crate::soc::peripheral::Peripheral; #[test] fn test_add_registers() { diff --git a/src/soc/peripheral/mod.rs b/src/soc/peripheral/mod.rs index 0a9c88c..53f63ea 100644 --- a/src/soc/peripheral/mod.rs +++ b/src/soc/peripheral/mod.rs @@ -5,7 +5,7 @@ pub mod keypad; mod bootrom; use gpu::Gpu; -use nvic::Nvic; +use nvic::{Nvic, InterruptSources}; use timer::Timer; use bootrom::BootRom; use keypad::Keypad; @@ -58,6 +58,22 @@ pub const VBLANK_VECTOR: u16 = 0x40; pub const LCDSTAT_VECTOR: u16 = 0x48; pub const TIMER_VECTOR: u16 = 0x50; +pub trait IoAccess { + fn read(&self, address: u16) -> u8; + + fn write(&mut self, address: u16, data: u8); +} + +pub trait Interrupt { + fn is_an_interrupt_to_run(&self) -> bool; + + fn is_an_interrupt_pending(&self) -> bool; + + fn get_interrupt(&mut self) -> Option; + + fn master_enable(&mut self, enable: bool); +} + pub struct Peripheral { boot_rom: BootRom, cartridge: Cartridge, @@ -124,54 +140,6 @@ impl Peripheral { self.boot_rom.load(boot_rom); } - pub fn read(&self, address: u16) -> u8 { - match address { - ROM_BANK_0_BEGIN..=ROM_BANK_0_END => { - match address { - BOOT_ROM_BEGIN..=BOOT_ROM_END => - if self.boot_rom.get_state() { - self.boot_rom.read(address) - } else { - self.cartridge.read_bank_0(address as usize) - } - _ => self.cartridge.read_bank_0(address as usize) - } - } - ROM_BANK_N_BEGIN..=ROM_BANK_N_END => self.cartridge.read_bank_n(address as usize), - VRAM_BEGIN..=VRAM_END => self.gpu.read_vram(address - VRAM_BEGIN), - EXTERNAL_RAM_BEGIN..=EXTERNAL_RAM_END => self.cartridge.read_ram(address as usize), - WORKING_RAM_BEGIN..=WORKING_RAM_END => self.working_ram[(address - WORKING_RAM_BEGIN) as usize], - ECHO_RAM_BEGIN..=ECHO_RAM_END => self.working_ram[(address - ECHO_RAM_BEGIN) as usize], - OAM_BEGIN..=OAM_END => self.gpu.read_oam((address - OAM_BEGIN) as usize), - IO_REGISTERS_BEGIN..=IO_REGISTERS_END => self.read_io_register(address as usize), - UNUSED_BEGIN..=UNUSED_END => 0, // unused memory - ZERO_PAGE_BEGIN..=ZERO_PAGE_END => self.zero_page[(address - ZERO_PAGE_BEGIN) as usize], - INTERRUPT_ENABLE_REGISTER => self.nvic.get_it_enable(), - } - } - - pub fn write(&mut self, address: u16, data: u8) { - match address { - ROM_BANK_0_BEGIN..=ROM_BANK_0_END => self.cartridge.write_bank_0(address as usize, data), - ROM_BANK_N_BEGIN..=ROM_BANK_N_END => self.cartridge.write_bank_n(address as usize, data), - VRAM_BEGIN..=VRAM_END => self.gpu.write_vram(address - VRAM_BEGIN, data), - EXTERNAL_RAM_BEGIN..=EXTERNAL_RAM_END => self.cartridge.write_ram(address as usize, data), - WORKING_RAM_BEGIN..=WORKING_RAM_END => { - self.working_ram[(address - WORKING_RAM_BEGIN) as usize] = data; - } - ECHO_RAM_BEGIN..=ECHO_RAM_END => { - self.working_ram[(address - ECHO_RAM_BEGIN) as usize] = data; - } - OAM_BEGIN..=OAM_END => self.gpu.write_oam((address - OAM_BEGIN) as usize, data), - IO_REGISTERS_BEGIN..=IO_REGISTERS_END => self.write_io_register(address as usize, data), - UNUSED_BEGIN..=UNUSED_END => { /* Writing to here does nothing */ } - ZERO_PAGE_BEGIN..=ZERO_PAGE_END => { - self.zero_page[(address - ZERO_PAGE_BEGIN) as usize] = data; - } - INTERRUPT_ENABLE_REGISTER => self.nvic.set_it_enable(data), - } - } - fn read_io_register(&self, address: usize) -> u8 { match address { 0xFF00 => self.keypad.get(), @@ -276,6 +244,74 @@ impl Peripheral { } } +impl IoAccess for Peripheral { + fn read(&self, address: u16) -> u8 { + match address { + ROM_BANK_0_BEGIN..=ROM_BANK_0_END => { + match address { + BOOT_ROM_BEGIN..=BOOT_ROM_END => + if self.boot_rom.get_state() { + self.boot_rom.read(address) + } else { + self.cartridge.read_bank_0(address as usize) + } + _ => self.cartridge.read_bank_0(address as usize) + } + } + ROM_BANK_N_BEGIN..=ROM_BANK_N_END => self.cartridge.read_bank_n(address as usize), + VRAM_BEGIN..=VRAM_END => self.gpu.read_vram(address - VRAM_BEGIN), + EXTERNAL_RAM_BEGIN..=EXTERNAL_RAM_END => self.cartridge.read_ram(address as usize), + WORKING_RAM_BEGIN..=WORKING_RAM_END => self.working_ram[(address - WORKING_RAM_BEGIN) as usize], + ECHO_RAM_BEGIN..=ECHO_RAM_END => self.working_ram[(address - ECHO_RAM_BEGIN) as usize], + OAM_BEGIN..=OAM_END => self.gpu.read_oam((address - OAM_BEGIN) as usize), + IO_REGISTERS_BEGIN..=IO_REGISTERS_END => self.read_io_register(address as usize), + UNUSED_BEGIN..=UNUSED_END => 0, // unused memory + ZERO_PAGE_BEGIN..=ZERO_PAGE_END => self.zero_page[(address - ZERO_PAGE_BEGIN) as usize], + INTERRUPT_ENABLE_REGISTER => self.nvic.get_it_enable(), + } + } + + fn write(&mut self, address: u16, data: u8) { + match address { + ROM_BANK_0_BEGIN..=ROM_BANK_0_END => self.cartridge.write_bank_0(address as usize, data), + ROM_BANK_N_BEGIN..=ROM_BANK_N_END => self.cartridge.write_bank_n(address as usize, data), + VRAM_BEGIN..=VRAM_END => self.gpu.write_vram(address - VRAM_BEGIN, data), + EXTERNAL_RAM_BEGIN..=EXTERNAL_RAM_END => self.cartridge.write_ram(address as usize, data), + WORKING_RAM_BEGIN..=WORKING_RAM_END => { + self.working_ram[(address - WORKING_RAM_BEGIN) as usize] = data; + } + ECHO_RAM_BEGIN..=ECHO_RAM_END => { + self.working_ram[(address - ECHO_RAM_BEGIN) as usize] = data; + } + OAM_BEGIN..=OAM_END => self.gpu.write_oam((address - OAM_BEGIN) as usize, data), + IO_REGISTERS_BEGIN..=IO_REGISTERS_END => self.write_io_register(address as usize, data), + UNUSED_BEGIN..=UNUSED_END => { /* Writing to here does nothing */ } + ZERO_PAGE_BEGIN..=ZERO_PAGE_END => { + self.zero_page[(address - ZERO_PAGE_BEGIN) as usize] = data; + } + INTERRUPT_ENABLE_REGISTER => self.nvic.set_it_enable(data), + } + } +} + +impl Interrupt for Peripheral { + fn is_an_interrupt_to_run(&self) -> bool { + self.nvic.is_an_interrupt_to_run() + } + + fn is_an_interrupt_pending(&self) -> bool { + self.nvic.is_an_interrupt_pending() + } + + fn get_interrupt(&mut self) -> Option { + self.nvic.get_interrupt() + } + + fn master_enable(&mut self, enable: bool) { + self.nvic.master_enable(enable); + } +} + #[cfg(test)] mod peripheral_tests { use super::*; From ee55f0eb6b93c9b446a0c4754c0ae0b6fbe71245 Mon Sep 17 00:00:00 2001 From: nchf3 Date: Sun, 23 Apr 2023 22:07:31 +0200 Subject: [PATCH 4/4] update crate name --- Cargo.lock | 14 +++++++------- Cargo.toml | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5ecd0f4..e44aa68 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,13 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "Qoboy" -version = "0.1.0" -dependencies = [ - "minifb", -] - [[package]] name = "autocfg" version = "1.1.0" @@ -337,6 +330,13 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "qoboy" +version = "1.0.0" +dependencies = [ + "minifb", +] + [[package]] name = "quote" version = "1.0.23" diff --git a/Cargo.toml b/Cargo.toml index 4346ffd..a241bc8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] -name = "Qoboy" -version = "0.1.0" +name = "qoboy" +version = "1.0.0" edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html