From 811a9f9cc97a683547b6a5e7398a8edbe6e83ca4 Mon Sep 17 00:00:00 2001 From: Rekai Musuka Date: Fri, 4 Jun 2021 13:47:06 -0500 Subject: [PATCH] feat(dma): implement non-working dma transfer --- src/bus.rs | 19 +++++++ src/instruction.rs | 22 ++++++++ src/ppu.rs | 4 ++ src/ppu/dma.rs | 137 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 182 insertions(+) create mode 100644 src/ppu/dma.rs diff --git a/src/bus.rs b/src/bus.rs index adb19fd..e92995c 100644 --- a/src/bus.rs +++ b/src/bus.rs @@ -68,6 +68,7 @@ impl Bus { } pub fn step(&mut self, cycles: Cycle) { + self.step_dma(cycles); self.ppu.step(cycles); self.timer.step(cycles); self.sound.step(cycles); @@ -167,6 +168,7 @@ impl Bus { 0x43 => self.ppu.pos.scroll_x, 0x44 => self.ppu.pos.line_y, 0x45 => self.ppu.pos.ly_compare as u8, + 0x46 => self.ppu.dma.ctrl.repr, 0x47 => self.ppu.monochrome.bg_palette.into(), 0x48 => self.ppu.monochrome.obj_palette_0.into(), 0x49 => self.ppu.monochrome.obj_palette_1.into(), @@ -285,6 +287,7 @@ impl Bus { self.ppu.int.set_lcd_stat(true); } } + 0x46 => self.ppu.dma.ctrl.update(byte, &mut self.ppu.dma.state), 0x47 => self.ppu.monochrome.bg_palette = byte.into(), 0x48 => self.ppu.monochrome.obj_palette_0 = byte.into(), 0x49 => self.ppu.monochrome.obj_palette_1 = byte.into(), @@ -368,3 +371,19 @@ impl Bus { self.boot.is_some() } } + +impl Bus { + pub(crate) fn step_dma(&mut self, pending: Cycle) { + let pending_cycles: u32 = pending.into(); + + for _ in 0..pending_cycles { + match self.ppu.dma.clock() { + Some((src_addr, dest_addr)) => { + let byte = self.read_byte(src_addr); + self.write_byte(dest_addr, byte); + } + None => {} + } + } + } +} diff --git a/src/instruction.rs b/src/instruction.rs index 2f30dd7..63d85bf 100644 --- a/src/instruction.rs +++ b/src/instruction.rs @@ -2240,6 +2240,28 @@ impl std::ops::SubAssign for Cycle { } } +impl PartialEq for Cycle { + fn eq(&self, other: &u32) -> bool { + self.0 == *other + } +} + +impl std::ops::Div for Cycle { + type Output = Self; + + fn div(self, rhs: Self) -> Self::Output { + Self::new(self.0 / rhs.0) + } +} + +impl std::ops::Div for Cycle { + type Output = Self; + + fn div(self, rhs: u32) -> Self::Output { + Self::new(self.0 / rhs) + } +} + impl From for Cycle { fn from(num: u32) -> Self { Self(num) diff --git a/src/ppu.rs b/src/ppu.rs index 2fae0aa..22f407c 100644 --- a/src/ppu.rs +++ b/src/ppu.rs @@ -1,6 +1,7 @@ use crate::Cycle; use crate::GB_HEIGHT; use crate::GB_WIDTH; +use dma::DmaProcess; use std::collections::VecDeque; use std::convert::TryInto; @@ -9,6 +10,7 @@ use self::types::{ ObjectPaletteId, ObjectSize, Pixels, PpuMode, RenderPriority, TileDataAddress, }; +pub(crate) mod dma; mod types; const VRAM_SIZE: usize = 0x2000; @@ -39,6 +41,7 @@ pub struct Ppu { pub vram: Box<[u8; VRAM_SIZE]>, pub stat: LCDStatus, pub oam: ObjectAttributeTable, + pub(crate) dma: DmaProcess, scan_state: OamScanState, fetch: PixelFetcher, fifo: FifoRenderer, @@ -449,6 +452,7 @@ impl Default for Ppu { fifo: Default::default(), obj_buffer: Default::default(), window_stat: Default::default(), + dma: Default::default(), x_pos: 0, } } diff --git a/src/ppu/dma.rs b/src/ppu/dma.rs new file mode 100644 index 0000000..7b7475f --- /dev/null +++ b/src/ppu/dma.rs @@ -0,0 +1,137 @@ +use crate::instruction::Cycle; +use std::ops::Range; + +#[derive(Debug, Default, Clone)] +pub(crate) struct DmaProcess { + pub(crate) state: DmaState, + cycle: Cycle, + pub(crate) ctrl: DmaControl, +} + +impl DmaProcess { + pub(crate) fn clock(&mut self) -> Option<(u16, u16)> { + self.cycle += 1; + + match self.state { + DmaState::Pending => { + self.cycle += 1; + + // Four Cycles pass before we actually start transferring + // files + + if self.cycle == 4 { + self.state = DmaState::Transferring; + } + + None + } + DmaState::Transferring => { + if (self.cycle - 4) % 4 == 0 { + let i = u32::from((self.cycle - 4) / 4) as usize; + let dest = &mut self.ctrl.dest; + + match self.ctrl.src.as_mut() { + Some(src_range) => src_range.nth(i).zip(dest.nth(i)), + None => { + self.reset(); + None + } + } + } else { + None + } + } + DmaState::Disabled => None, + } + } + + fn reset(&mut self) { + self.cycle = Cycle::new(0); + self.state = DmaState::Disabled; + self.ctrl.src = None; + self.ctrl.repr = 0; + } +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub(crate) enum DmaState { + Disabled, + Pending, + Transferring, +} + +impl Default for DmaState { + fn default() -> Self { + Self::Disabled + } +} + +#[derive(Debug, Clone)] +pub(crate) struct DmaControl { + pub(crate) repr: u8, + src: Option>, + dest: Range, +} + +impl Default for DmaControl { + fn default() -> Self { + Self { + repr: 0, + src: None, + dest: 0xFE00..0xFE9F, + } + } +} + +impl DmaControl { + fn src(&self) -> Option<&Range> { + self.src.as_ref() + } + + fn dest(&self) -> &Range { + &self.dest + } + + pub fn update(&mut self, byte: u8, state: &mut DmaState) { + let left = (byte as u16) << 8 | 0x0000; + let right = (byte as u16) << 8 | 0x009F; + + self.repr = byte; + self.src = Some(left..right); + *state = DmaState::Pending; + } +} + +#[cfg(test)] +mod tests { + use super::{DmaControl, DmaProcess, DmaState}; + + #[derive(Debug, Default, Clone)] + struct MockBus { + dma: DmaProcess, + } + + #[test] + fn dma_control_works() { + let mut dma_ctrl: DmaControl = Default::default(); + let mut state = DmaState::Disabled; + + assert_eq!(dma_ctrl.src(), None); + assert_eq!(*dma_ctrl.dest(), 0xFE00..0xFE9F); + + dma_ctrl.update(0xAB, &mut state); + assert_eq!(dma_ctrl.src(), Some(0xAB00..0xAB9F).as_ref()); + assert_eq!(*dma_ctrl.dest(), 0xFE00..0xFE9F); + } + + #[test] + fn ctrl_update_vs_borrow_checker() { + let mut bus: MockBus = Default::default(); + assert_eq!(bus.dma.state, DmaState::Disabled); + + bus.dma.ctrl.update(0xAB, &mut bus.dma.state); + + assert_eq!(bus.dma.ctrl.src(), Some(0xAB00..0xAB9F).as_ref()); + assert_eq!(bus.dma.state, DmaState::Pending); + } +}