Skip to content

Commit

Permalink
feat: improved support for OAM DMA
Browse files Browse the repository at this point in the history
Made sure that OAM DMA takes 160 cycles to process.
  • Loading branch information
joamag committed Feb 25, 2024
1 parent 999f66d commit cadbec7
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 28 deletions.
65 changes: 55 additions & 10 deletions src/dma.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::{
consts::{HDMA1_ADDR, HDMA2_ADDR, HDMA3_ADDR, HDMA4_ADDR, HDMA5_ADDR},
consts::{DMA_ADDR, HDMA1_ADDR, HDMA2_ADDR, HDMA3_ADDR, HDMA4_ADDR, HDMA5_ADDR},
warnln,
};

Expand All @@ -15,7 +15,10 @@ pub struct Dma {
length: u16,
pending: u16,
mode: DmaMode,
active: bool,
cycles_dma: u16,
value_dma: u8,
active_dma: bool,
active_hdma: bool,
}

impl Dma {
Expand All @@ -26,7 +29,10 @@ impl Dma {
length: 0x0,
pending: 0x0,
mode: DmaMode::General,
active: false,
cycles_dma: 0x0,
value_dma: 0x0,
active_dma: false,
active_hdma: false,
}
}

Expand All @@ -36,15 +42,20 @@ impl Dma {
self.length = 0x0;
self.pending = 0x0;
self.mode = DmaMode::General;
self.active = false;
self.cycles_dma = 0x0;
self.value_dma = 0x0;
self.active_dma = false;
self.active_hdma = false;
}

pub fn clock(&mut self, _cycles: u16) {}

pub fn read(&mut self, addr: u16) -> u8 {
match addr {
// 0xFF55 — HDMA5: VRAM DMA length/mode/start (CGB only)
HDMA5_ADDR => ((self.pending >> 4) as u8).wrapping_sub(1) | ((!self.active as u8) << 7),
HDMA5_ADDR => {
((self.pending >> 4) as u8).wrapping_sub(1) | ((!self.active_hdma as u8) << 7)
}
_ => {
warnln!("Reading from unknown DMA location 0x{:04x}", addr);
0xff
Expand All @@ -54,6 +65,12 @@ impl Dma {

pub fn write(&mut self, addr: u16, value: u8) {
match addr {
// 0xFF46 — DMA: OAM DMA source address & start
DMA_ADDR => {
self.cycles_dma = 640;
self.value_dma = value;
self.active_dma = true;
}
// 0xFF51 — HDMA1: VRAM DMA source high (CGB only)
HDMA1_ADDR => self.source = (self.source & 0x00ff) | ((value as u16) << 8),
// 0xFF52 — HDMA2: VRAM DMA source low (CGB only)
Expand All @@ -71,7 +88,7 @@ impl Dma {
_ => DmaMode::General,
};
self.pending = self.length;
self.active = true;
self.active_hdma = true;
}
_ => warnln!("Writing to unknown DMA location 0x{:04x}", addr),
}
Expand Down Expand Up @@ -117,12 +134,40 @@ impl Dma {
self.mode = value;
}

pub fn active(&self) -> bool {
self.active
pub fn cycles_dma(&self) -> u16 {
self.cycles_dma
}

pub fn set_cycles_dma(&mut self, value: u16) {
self.cycles_dma = value;
}

pub fn value_dma(&self) -> u8 {
self.value_dma
}

pub fn set_value_dma(&mut self, value: u8) {
self.value_dma = value;
}

pub fn set_active(&mut self, value: bool) {
self.active = value;
pub fn active_dma(&self) -> bool {
self.active_dma
}

pub fn set_active_dma(&mut self, value: bool) {
self.active_dma = value;
}

pub fn active_hdma(&self) -> bool {
self.active_hdma
}

pub fn set_active_hdma(&mut self, value: bool) {
self.active_hdma = value;
}

pub fn active(&self) -> bool {
self.active_dma || self.active_hdma
}
}

Expand Down
39 changes: 22 additions & 17 deletions src/mmu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -263,11 +263,21 @@ impl Mmu {
self.boot_active = value;
}

pub fn clock_dma(&mut self, _cycles: u16) {
pub fn clock_dma(&mut self, cycles: u16) {
if !self.dma.active() {
return;
}

if self.dma.active_dma() {
let cycles_dma = self.dma.cycles_dma().saturating_sub(cycles);
if cycles_dma == 0x0 {
let data = self.read_many((self.dma.value_dma() as u16) << 8, 160);
self.write_many(0xfe00, &data);
self.dma.set_active_dma(false);
}
self.dma.set_cycles_dma(cycles_dma);
}

// @TODO: Implement DMA transfer in a better way, meaning that
// the DMA transfer should respect the timings
//
Expand All @@ -280,16 +290,17 @@ impl Mmu {
// program runs it Normal Speed Mode).
//
// we should also respect General-Purpose DMA vs HBlank DMA

// only runs the DMA transfer if the system is in CGB mode
// this avoids issues when writing to DMG unmapped registers
// that would otherwise cause the system to crash
if self.mode == GameBoyMode::Cgb {
let data = self.read_many(self.dma.source(), self.dma.pending());
self.write_many(self.dma.destination(), &data);
if self.dma.active_hdma() {
// only runs the DMA transfer if the system is in CGB mode
// this avoids issues when writing to DMG unmapped registers
// that would otherwise cause the system to crash
if self.mode == GameBoyMode::Cgb {
let data = self.read_many(self.dma.source(), self.dma.pending());
self.write_many(self.dma.destination(), &data);
}
self.dma.set_pending(0);
self.dma.set_active_hdma(false);
}
self.dma.set_pending(0);
self.dma.set_active(false);
}

pub fn read(&mut self, addr: u16) -> u8 {
Expand Down Expand Up @@ -497,13 +508,7 @@ impl Mmu {
0x40 | 0x60 | 0x70 => {
match addr & 0x00ff {
// 0xFF46 — DMA: OAM DMA source address & start
0x0046 => {
// @TODO must update the data section only after 160 m cycles,
// making this an immediate operation creates issues
debugln!("Going to start DMA transfer to 0x{:x}00", value);
let data = self.read_many((value as u16) << 8, 160);
self.write_many(0xfe00, &data);
}
0x0046 => self.dma.write(addr, value),

// VRAM related write
_ => self.ppu.write(addr, value),
Expand Down
2 changes: 1 addition & 1 deletion src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1305,7 +1305,7 @@ impl State for BessCore {

// need to disable DMA transfer to avoid unwanted
// DMA transfers when loading the state
gb.dma().set_active(false);
gb.dma().set_active_hdma(false);
}

Ok(())
Expand Down

0 comments on commit cadbec7

Please sign in to comment.