feat(dma): implement non-working dma transfer
This commit is contained in:
parent
778e04e645
commit
811a9f9cc9
19
src/bus.rs
19
src/bus.rs
|
@ -68,6 +68,7 @@ impl Bus {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn step(&mut self, cycles: Cycle) {
|
pub fn step(&mut self, cycles: Cycle) {
|
||||||
|
self.step_dma(cycles);
|
||||||
self.ppu.step(cycles);
|
self.ppu.step(cycles);
|
||||||
self.timer.step(cycles);
|
self.timer.step(cycles);
|
||||||
self.sound.step(cycles);
|
self.sound.step(cycles);
|
||||||
|
@ -167,6 +168,7 @@ impl Bus {
|
||||||
0x43 => self.ppu.pos.scroll_x,
|
0x43 => self.ppu.pos.scroll_x,
|
||||||
0x44 => self.ppu.pos.line_y,
|
0x44 => self.ppu.pos.line_y,
|
||||||
0x45 => self.ppu.pos.ly_compare as u8,
|
0x45 => self.ppu.pos.ly_compare as u8,
|
||||||
|
0x46 => self.ppu.dma.ctrl.repr,
|
||||||
0x47 => self.ppu.monochrome.bg_palette.into(),
|
0x47 => self.ppu.monochrome.bg_palette.into(),
|
||||||
0x48 => self.ppu.monochrome.obj_palette_0.into(),
|
0x48 => self.ppu.monochrome.obj_palette_0.into(),
|
||||||
0x49 => self.ppu.monochrome.obj_palette_1.into(),
|
0x49 => self.ppu.monochrome.obj_palette_1.into(),
|
||||||
|
@ -285,6 +287,7 @@ impl Bus {
|
||||||
self.ppu.int.set_lcd_stat(true);
|
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(),
|
0x47 => self.ppu.monochrome.bg_palette = byte.into(),
|
||||||
0x48 => self.ppu.monochrome.obj_palette_0 = byte.into(),
|
0x48 => self.ppu.monochrome.obj_palette_0 = byte.into(),
|
||||||
0x49 => self.ppu.monochrome.obj_palette_1 = byte.into(),
|
0x49 => self.ppu.monochrome.obj_palette_1 = byte.into(),
|
||||||
|
@ -368,3 +371,19 @@ impl Bus {
|
||||||
self.boot.is_some()
|
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 => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -2240,6 +2240,28 @@ impl std::ops::SubAssign<u32> for Cycle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PartialEq<u32> 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<u32> for Cycle {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn div(self, rhs: u32) -> Self::Output {
|
||||||
|
Self::new(self.0 / rhs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<u32> for Cycle {
|
impl From<u32> for Cycle {
|
||||||
fn from(num: u32) -> Self {
|
fn from(num: u32) -> Self {
|
||||||
Self(num)
|
Self(num)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::Cycle;
|
use crate::Cycle;
|
||||||
use crate::GB_HEIGHT;
|
use crate::GB_HEIGHT;
|
||||||
use crate::GB_WIDTH;
|
use crate::GB_WIDTH;
|
||||||
|
use dma::DmaProcess;
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
|
|
||||||
|
@ -9,6 +10,7 @@ use self::types::{
|
||||||
ObjectPaletteId, ObjectSize, Pixels, PpuMode, RenderPriority, TileDataAddress,
|
ObjectPaletteId, ObjectSize, Pixels, PpuMode, RenderPriority, TileDataAddress,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub(crate) mod dma;
|
||||||
mod types;
|
mod types;
|
||||||
|
|
||||||
const VRAM_SIZE: usize = 0x2000;
|
const VRAM_SIZE: usize = 0x2000;
|
||||||
|
@ -39,6 +41,7 @@ pub struct Ppu {
|
||||||
pub vram: Box<[u8; VRAM_SIZE]>,
|
pub vram: Box<[u8; VRAM_SIZE]>,
|
||||||
pub stat: LCDStatus,
|
pub stat: LCDStatus,
|
||||||
pub oam: ObjectAttributeTable,
|
pub oam: ObjectAttributeTable,
|
||||||
|
pub(crate) dma: DmaProcess,
|
||||||
scan_state: OamScanState,
|
scan_state: OamScanState,
|
||||||
fetch: PixelFetcher,
|
fetch: PixelFetcher,
|
||||||
fifo: FifoRenderer,
|
fifo: FifoRenderer,
|
||||||
|
@ -449,6 +452,7 @@ impl Default for Ppu {
|
||||||
fifo: Default::default(),
|
fifo: Default::default(),
|
||||||
obj_buffer: Default::default(),
|
obj_buffer: Default::default(),
|
||||||
window_stat: Default::default(),
|
window_stat: Default::default(),
|
||||||
|
dma: Default::default(),
|
||||||
x_pos: 0,
|
x_pos: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<Range<u16>>,
|
||||||
|
dest: Range<u16>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for DmaControl {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
repr: 0,
|
||||||
|
src: None,
|
||||||
|
dest: 0xFE00..0xFE9F,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DmaControl {
|
||||||
|
fn src(&self) -> Option<&Range<u16>> {
|
||||||
|
self.src.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dest(&self) -> &Range<u16> {
|
||||||
|
&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);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue