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 HDMA, MBC3 and improve the debugger #145

Merged
merged 11 commits into from
Nov 1, 2024
44 changes: 12 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,7 @@

A GameBoy emulator developed by me.

The main goal of this project is to be able to play Pokemon on my own emulator.

## Implemented Cartridges Types
- Rom (No MBC controller)
- MBC1
- MBC3
- MBC5

**More will be added if neccessary (and by neccessary I mean if games I want to play will require them)**
The main goal of this project is to be able to play Pokemon on my own emulator on various platforms.

## Building

Expand Down Expand Up @@ -133,9 +125,15 @@ the UART output will be written to the console.

I think that not all the peripherals I use are implemented in QEMU so I used this mainly to debug boot and CPU initialization problems

## GameBoy
## Development Status

### Implemented Cartridges Types
- Rom (No MBC controller)
- MBC1
- MBC3
- MBC5

### Development Status
### Testing

- CPU - Cycle accurate CPU
- PPU - Cycle accurate fifo PPU
Expand All @@ -153,30 +151,11 @@ I think that not all the peripherals I use are implemented in QEMU so I used thi
- acceptance/ppu/intr_2_oam_ok_timing
- APU passes some of [blargs dmg_sound tests](https://github.com/retrio/gb-test-roms/tree/master/dmg_sound)
- Timer passes most of [mooneye-test-suite](https://github.com/Gekkio/mooneye-test-suite/tree/main/acceptance/timer)

### Games Tested

- Pokemon Red
- Tetris
- Super Mario World
- The Legend of Zelda: A Link to the Past

## GameBoy Color

### Developement Status

- CPU - Full support
- PPU - Kinds of works
- Tests
- [cgb-acid2](https://github.com/mattcurrie/cgb-acid2)
- [MagenTests](https://github.com/alloncm/MagenTests)

### Games Tested

- Pokemon Yellow
- Tetris DX

## Resources

### Gameboy
- [The Pandocs](https://gbdev.io/pandocs/)
- [gbops](https://izik1.github.io/gbops/index.html)
Expand All @@ -193,6 +172,7 @@ I think that not all the peripherals I use are implemented in QEMU so I used thi
- [juj/fbcp-ili9341 as a reference](https://github.com/juj/fbcp-ili9341)
- [Raspberry Pi DMA programming in C](https://iosoft.blog/2020/05/25/raspberry-pi-dma-programming/)
- [Ili9341 docs](https://cdn-shop.adafruit.com/datasheets/ILI9341.pdf)

#### BareMetal RaspberryPi
- [Bare-metal Boot Code for ARMv8-A](http://classweb.ece.umd.edu/enee447.S2021/baremetal_boot_code_for_ARMv8_A_processors.pdf)
- [Low performance Baremetal code Blog post](https://forums.raspberrypi.com/viewtopic.php?t=219212)
Expand All @@ -207,4 +187,4 @@ I think that not all the peripherals I use are implemented in QEMU so I used thi
- [LLD BareMetal tutorial](https://github.com/rockytriton/LLD)
- [Circle baremetal framework as a reference](https://github.com/rsta2/circle)
- [FAT32 specs](https://academy.cba.mit.edu/classes/networking_communications/SD/FAT.pdf)
- [Baremetal SD card on the RPI4 blog post](https://forums.raspberrypi.com/viewtopic.php?t=308089)
- [Baremetal SD card on the RPI4 blog post](https://forums.raspberrypi.com/viewtopic.php?t=308089)
5 changes: 4 additions & 1 deletion core/src/cpu/gb_cpu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,10 @@ impl Default for GbCpu {
impl GbCpu {
pub fn execute_interrupt_request(&mut self, memory:&mut impl Memory, ir: InterruptRequest)->u8{
match ir{
InterruptRequest::Unhalt=>self.halt = false,
InterruptRequest::Unhalt=>{
self.halt = false;
memory.set_halt(false);
},
InterruptRequest::Interrupt(address)=>return self.prepare_for_interrupt(memory, address),
InterruptRequest::None=>{}
}
Expand Down
2 changes: 1 addition & 1 deletion core/src/cpu/opcode_runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ impl GbCpu{
0x27=>daa(self),
0x37=>scf(self),
0x3F=>ccf(self),
0x76=>halt(self),
0xE9=>jump_hl(self),
0xF3=>di(self),
0xF9=>load_sp_hl(self),
Expand Down Expand Up @@ -98,6 +97,7 @@ impl GbCpu{
0x34=>inc_hl(self, memory),
0x35=>dec_hl(self, memory),
0x3A=>ldd_a_hl(self, memory),
0x76=>halt(self, memory),
0x86=>add_a_hl(self, memory),
0x8E=>adc_a_hl(self, memory),
0x96=>sub_a_hl(self, memory),
Expand Down
5 changes: 3 additions & 2 deletions core/src/cpu/opcodes/cpu_control_instructions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@ pub fn scf(cpu:&mut GbCpu)->u8{
return 0;
}

pub fn halt(cpu:&mut GbCpu)->u8{
pub fn halt(cpu:&mut GbCpu, memory: &mut impl Memory)->u8{
cpu.halt = true;

memory.set_halt(true);

// 1 cycles - 1 reading opcode
return 0;
}
Expand Down
108 changes: 61 additions & 47 deletions core/src/debugger/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ mod disassembler;

use std::collections::HashSet;

use crate::{*, machine::gameboy::*, mmu::Memory, cpu::gb_cpu::GbCpu, utils::vec2::Vec2, ppu::{ppu_state::PpuState, gb_ppu::GbPpu}};
use crate::{*, machine::gameboy::*, cpu::gb_cpu::GbCpu, utils::vec2::Vec2, ppu::{ppu_state::PpuState, gb_ppu::GbPpu}};
use self::disassembler::{OpcodeEntry, disassemble};

#[derive(Clone, Copy)]
Expand All @@ -16,10 +16,11 @@ pub enum DebuggerCommand{
Stop,
Step,
Continue,
SkipHalt,
Registers,
Break(u16),
RemoveBreak(u16),
DumpMemory(u16),
Break(u16, u16),
RemoveBreak(u16, u16),
DumpMemory(u16, u16),
Disassemble(u16),
Watch(u16),
RemoveWatch(u16),
Expand All @@ -34,14 +35,15 @@ pub const PPU_BUFFER_SIZE:usize = PPU_BUFFER_HEIGHT * PPU_BUFFER_WIDTH;
pub enum DebuggerResult{
Registers(Registers),
AddedBreak(u16),
HitBreak(u16),
HitBreak(u16, u16),
RemovedBreak(u16),
BreakDoNotExist(u16),
Continuing,
Stepped(u16),
Stopped(u16),
MemoryDump(u16, Vec<MemoryEntry>),
Disassembly(u16, Vec<OpcodeEntry>),
HaltWakeup,
Stepped(u16, u16),
Stopped(u16, u16),
MemoryDump(u16, u16, Vec<u8>),
Disassembly(u16, u16, Vec<OpcodeEntry>),
AddedWatch(u16),
HitWatch(u16, u16),
RemovedWatch(u16),
Expand All @@ -57,35 +59,32 @@ pub struct Registers{
pub de: u16,
pub hl:u16,
pub pc:u16,
pub sp:u16
pub sp:u16,
pub ime: bool
}

impl Registers{
fn new(cpu:&GbCpu)->Self{
Registers { af: cpu.af.value(), bc: cpu.bc.value(), de: cpu.de.value(), hl: cpu.hl.value(), pc: cpu.program_counter, sp: cpu.stack_pointer }
Registers { af: cpu.af.value(), bc: cpu.bc.value(), de: cpu.de.value(), hl: cpu.hl.value(), pc: cpu.program_counter, sp: cpu.stack_pointer, ime: cpu.mie }
}
}

#[derive(Default, Clone, Copy)]
pub struct MemoryEntry{
pub address:u16,
pub value:u8
}

pub struct PpuInfo{
pub ppu_state:PpuState,
pub lcdc:u8,
pub stat:u8,
pub ly:u8,
pub window_pos:Vec2<u8>,
pub background_pos:Vec2<u8>
pub background_pos:Vec2<u8>,
pub vram_bank: u8
}

impl PpuInfo{
fn new<GFX:GfxDevice>(ppu:&GbPpu<GFX>)->Self{
Self {
ppu_state: ppu.state, lcdc: ppu.lcd_control, stat: ppu.stat_register,
ly: ppu.ly_register, window_pos: ppu.window_pos, background_pos: ppu.bg_pos
ly: ppu.ly_register, window_pos: ppu.window_pos, background_pos: ppu.bg_pos,
vram_bank: ppu.vram.get_bank_reg()
}
}
}
Expand All @@ -98,74 +97,77 @@ pub trait DebuggerInterface{

pub struct Debugger<UI:DebuggerInterface>{
ui:UI,
breakpoints:HashSet<u16>
breakpoints:HashSet<(u16, u16)>,
skip_halt: bool
}

impl<UI:DebuggerInterface> Debugger<UI>{
pub fn new(ui:UI)->Self{
Self { ui, breakpoints: HashSet::new() }
Self { ui, breakpoints: HashSet::new(), skip_halt: false }
}

fn recv(&self)->DebuggerCommand{self.ui.recv_command()}
fn send(&self, result: DebuggerResult){self.ui.send_result(result)}

fn should_halt(&self, pc:u16, hit_watch:bool)->bool{
self.check_for_break(pc) || self.ui.should_stop() || hit_watch
fn should_halt(&self, cpu:&GbCpu, bank:u16, hit_watch:bool)->bool{
(self.check_for_break(cpu.program_counter, bank) || self.ui.should_stop() || hit_watch) && !(cpu.halt && self.skip_halt)
}

fn check_for_break(&self, pc:u16)->bool{self.breakpoints.contains(&pc)}
fn check_for_break(&self, pc:u16, bank:u16)->bool{self.breakpoints.contains(&(pc, bank))}

fn add_breakpoint(&mut self, address:u16){_ = self.breakpoints.insert(address)}
fn add_breakpoint(&mut self, address:u16, bank:u16){_ = self.breakpoints.insert((address, bank))}

fn try_remove_breakpoint(&mut self, address:u16)->bool{self.breakpoints.remove(&address)}
fn try_remove_breakpoint(&mut self, address:u16, bank:u16)->bool{self.breakpoints.remove(&(address, bank))}
}

impl_gameboy!{{
pub fn run_debugger(&mut self){
while self.debugger.should_halt(self.cpu.program_counter, self.mmu.mem_watch.hit_addr.is_some()) {
if self.debugger.check_for_break(self.cpu.program_counter){
self.debugger.send(DebuggerResult::HitBreak(self.cpu.program_counter));
while self.debugger.should_halt(&self.cpu, self.get_current_bank(self.cpu.program_counter), self.mmu.mem_watch.hit_addr.is_some()) {
if !self.cpu.halt && self.debugger.skip_halt{
self.debugger.send(DebuggerResult::HaltWakeup);
self.debugger.skip_halt = false;
}
if self.debugger.check_for_break(self.cpu.program_counter, self.get_current_bank(self.cpu.program_counter)){
self.debugger.send(DebuggerResult::HitBreak(self.cpu.program_counter, self.get_current_bank(self.cpu.program_counter)));
}
if let Some(addr) = self.mmu.mem_watch.hit_addr{
self.debugger.send(DebuggerResult::HitWatch(addr, self.cpu.program_counter));
self.mmu.mem_watch.hit_addr = None;
}
match self.debugger.recv(){
DebuggerCommand::Stop=>self.debugger.send(DebuggerResult::Stopped(self.cpu.program_counter)),
DebuggerCommand::Stop=>self.debugger.send(DebuggerResult::Stopped(self.cpu.program_counter, self.get_current_bank(self.cpu.program_counter))),
DebuggerCommand::Step=>{
self.step();
self.debugger.send(DebuggerResult::Stepped(self.cpu.program_counter));
self.debugger.send(DebuggerResult::Stepped(self.cpu.program_counter, self.get_current_bank(self.cpu.program_counter)));
}
DebuggerCommand::Continue=>{
self.debugger.send(DebuggerResult::Continuing);
break;
}
},
DebuggerCommand::SkipHalt => self.debugger.skip_halt = true,
DebuggerCommand::Registers => self.debugger.send(DebuggerResult::Registers(Registers::new(&self.cpu))),
DebuggerCommand::Break(address) => {
self.debugger.add_breakpoint(address);
DebuggerCommand::Break(address, bank) => {
self.debugger.add_breakpoint(address, bank);
self.debugger.send(DebuggerResult::AddedBreak(address));
},
DebuggerCommand::RemoveBreak(address)=>{
let result = match self.debugger.try_remove_breakpoint(address) {
DebuggerCommand::RemoveBreak(address, bank)=>{
let result = match self.debugger.try_remove_breakpoint(address, bank) {
true => DebuggerResult::RemovedBreak(address),
false => DebuggerResult::BreakDoNotExist(address)
};
self.debugger.send(result);
},
DebuggerCommand::DumpMemory(len)=>{
let mut buffer = vec![MemoryEntry::default(); len as usize];
for i in 0..len as usize{
let address = self.cpu.program_counter + i as u16;
buffer[i] = MemoryEntry {
value: self.mmu.read(address, 0),
address,
};
DebuggerCommand::DumpMemory(address, len)=>{
let mut buffer = vec![0; len as usize];
for i in 0..len {
buffer[i as usize] = self.mmu.dbg_read(address + i);
}
self.debugger.send(DebuggerResult::MemoryDump(len, buffer));

self.debugger.send(DebuggerResult::MemoryDump(address, self.get_current_bank(address), buffer));
}
DebuggerCommand::Disassemble(len)=>{
let result = disassemble(&self.cpu, &mut self.mmu, len);
self.debugger.send(DebuggerResult::Disassembly(len, result));
self.debugger.send(DebuggerResult::Disassembly(len, self.get_current_bank(self.cpu.program_counter), result));
},
DebuggerCommand::Watch(address)=>{
self.mmu.mem_watch.add_address(address);
Expand All @@ -185,15 +187,27 @@ impl_gameboy!{{
}
}
}

fn get_current_bank(&self, address:u16)->u16{
return match address{
0..=0x3FFF => 0,
0x4000..=0x7FFF => self.mmu.mem_watch.current_rom_bank_number,
0x8000..=0x9FFF => self.mmu.get_ppu().vram.get_bank_reg() as u16,
0xC000..=0xFDFF => self.mmu.mem_watch.current_ram_bank_number as u16,
_=>0
};
}
}}

pub struct MemoryWatcher{
pub watching_addresses: HashSet<u16>,
pub hit_addr:Option<u16>,
pub current_rom_bank_number: u16,
pub current_ram_bank_number: u8,
}

impl MemoryWatcher{
pub fn new()->Self{Self { watching_addresses: HashSet::new(), hit_addr: None }}
pub fn new()->Self{Self { watching_addresses: HashSet::new(), hit_addr: None, current_rom_bank_number: 0, current_ram_bank_number: 0 }}
pub fn add_address(&mut self, address:u16){_ = self.watching_addresses.insert(address)}
pub fn try_remove_address(&mut self, address:u16)->bool{self.watching_addresses.remove(&address)}
}
24 changes: 15 additions & 9 deletions core/src/machine/mbc_initializer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,21 @@ pub fn initialize_mbc(program:&[u8], save_data:Option<&[u8]>, mode:Option<Mode>)
let mbc_type = program[CARTRIDGE_TYPE_ADDRESS];

let mbc:&'static mut dyn Mbc = match mbc_type{
0x0|0x8=>static_alloc(Rom::new(program_clone,false, None)),
0x9=>static_alloc(Rom::new(program_clone, true, save_data_clone)),
0x1|0x2=>static_alloc(Mbc1::new(program_clone,false, None)),
0x3=>static_alloc(Mbc1::new(program_clone,true, save_data_clone)),
0x11|0x12=>static_alloc(Mbc3::new(program_clone,false,Option::None)),
0x13=>static_alloc(Mbc3::new(program_clone, true, save_data_clone)),
0x19|0x1A=>static_alloc(Mbc5::new(program_clone, false, save_data_clone)),
0x1B=>static_alloc(Mbc5::new(program_clone, true, save_data_clone)),
_=>core::panic!("not supported cartridge: {:#X}",mbc_type)
0x0 |
0x8 => static_alloc(Rom::new(program_clone,false, None)),
0x9 => static_alloc(Rom::new(program_clone, true, save_data_clone)),
0x1 |
0x2 => static_alloc(Mbc1::new(program_clone,false, None)),
0x3 => static_alloc(Mbc1::new(program_clone,true, save_data_clone)),
0xF => static_alloc(Mbc3::new(program_clone, true, None)), // The battery is for the RTC which isnt supported right now
0x10 | // The battery is also used for the RTC which isnt supported right now
0x13 => static_alloc(Mbc3::new(program_clone, true, save_data_clone)),
0x11 |
0x12 => static_alloc(Mbc3::new(program_clone,false,None)),
0x19 |
0x1A => static_alloc(Mbc5::new(program_clone, false, save_data_clone)),
0x1B => static_alloc(Mbc5::new(program_clone, true, save_data_clone)),
_=> core::panic!("not supported cartridge: {:#X}",mbc_type)
};

let cart_compatibility = mbc.get_compatibility_mode();
Expand Down
3 changes: 3 additions & 0 deletions core/src/mmu/carts/mbc1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ impl<'a> Mbc for Mbc1<'a>{
self.ram[(bank as usize * RAM_BANK_SIZE) + address as usize] = value;
}
}

#[cfg(feature = "dbg")]
fn get_bank_number(&self)->u16 { self.get_current_rom_bank() as u16 }
}

impl<'a> Mbc1<'a>{
Expand Down
Loading
Loading