fix(dma): initial version of dma transfer now works
This commit is contained in:
parent
d623800005
commit
e8e6c41dbe
112
src/bus.rs
112
src/bus.rs
|
@ -79,8 +79,8 @@ impl Bus {
|
||||||
|
|
||||||
for _ in 0..pending_cycles {
|
for _ in 0..pending_cycles {
|
||||||
if let Some((src_addr, dest_addr)) = self.ppu.dma.clock() {
|
if let Some((src_addr, dest_addr)) = self.ppu.dma.clock() {
|
||||||
let byte = self.read_byte(src_addr);
|
let byte = self.oam_read_byte(src_addr);
|
||||||
self.write_byte(dest_addr, byte);
|
self.oam_write_byte(dest_addr, byte);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -90,11 +90,12 @@ impl Bus {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BusIo for Bus {
|
impl Bus {
|
||||||
fn read_byte(&self, addr: u16) -> u8 {
|
pub fn oam_read_byte(&self, addr: u16) -> u8 {
|
||||||
match addr {
|
match addr {
|
||||||
0x0000..=0x3FFF => {
|
0x0000..=0x7FFF => {
|
||||||
// 16KB ROM bank 00
|
// 16KB ROM bank 00 (ends at 0x3FFF)
|
||||||
|
// and 16KB ROM Bank 01 -> NN (switchable via MB)
|
||||||
if addr < 0x100 {
|
if addr < 0x100 {
|
||||||
if let Some(boot) = self.boot {
|
if let Some(boot) = self.boot {
|
||||||
return boot[addr as usize];
|
return boot[addr as usize];
|
||||||
|
@ -106,11 +107,56 @@ impl BusIo for Bus {
|
||||||
None => panic!("Tried to read from a non-existent cartridge"),
|
None => panic!("Tried to read from a non-existent cartridge"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
0x4000..=0x7FFF => match self.cartridge.as_ref() {
|
0x8000..=0x9FFF => self.ppu.read_byte(addr), // 8KB Video RAM
|
||||||
// 16KB ROM Bank 01 -> NN (switchable via MB)
|
0xA000..=0xBFFF => match self.cartridge.as_ref() {
|
||||||
|
// 8KB External RAM
|
||||||
Some(cart) => cart.read_byte(addr),
|
Some(cart) => cart.read_byte(addr),
|
||||||
None => panic!("Tried to read from a non-existent cartridge"),
|
None => panic!("Tried to read from a non-existent cartridge"),
|
||||||
},
|
},
|
||||||
|
0xC000..=0xCFFF => self.work_ram.read_byte(addr), // 4KB Work RAM Bank 0
|
||||||
|
0xD000..=0xDFFF => self.var_ram.read_byte(addr), // 4KB Work RAM Bank 1 -> N
|
||||||
|
0xE000..=0xFDFF => {
|
||||||
|
// Mirror of 0xC000 to 0xDDFF (ECHO RAM)
|
||||||
|
match addr & 0x1FFF {
|
||||||
|
// 0xE000 ..= 0xEFFF
|
||||||
|
0x0000..=0x0FFF => {
|
||||||
|
// 4KB Work RAM Bank 0
|
||||||
|
self.work_ram.read_byte(addr)
|
||||||
|
}
|
||||||
|
// 0xF000 ..= 0xFDFF
|
||||||
|
0x1000..=0x1DFF => {
|
||||||
|
// 4KB Work RAM Bank 1 -> N
|
||||||
|
self.var_ram.read_byte(addr)
|
||||||
|
}
|
||||||
|
_ => unreachable!("{:#06X} was incorrectly handled by ECHO RAM", addr),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => panic!("OAM Transfer abnormally tried reading from {:#06X}", addr),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn oam_write_byte(&mut self, addr: u16, byte: u8) {
|
||||||
|
self.ppu.oam.write_byte(addr, byte);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BusIo for Bus {
|
||||||
|
fn read_byte(&self, addr: u16) -> u8 {
|
||||||
|
match addr {
|
||||||
|
0x0000..=0x7FFF => {
|
||||||
|
// 16KB ROM bank 00 (ends at 0x3FFF)
|
||||||
|
// and 16KB ROM Bank 01 -> NN (switchable via MB)
|
||||||
|
if addr < 0x100 {
|
||||||
|
if let Some(boot) = self.boot {
|
||||||
|
return boot[addr as usize];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match self.cartridge.as_ref() {
|
||||||
|
Some(cart) => cart.read_byte(addr),
|
||||||
|
None => panic!("Tried to read from a non-existent cartridge"),
|
||||||
|
}
|
||||||
|
}
|
||||||
0x8000..=0x9FFF => {
|
0x8000..=0x9FFF => {
|
||||||
// 8KB Video RAM
|
// 8KB Video RAM
|
||||||
match self.ppu.stat.mode() {
|
match self.ppu.stat.mode() {
|
||||||
|
@ -123,14 +169,8 @@ impl BusIo for Bus {
|
||||||
Some(cart) => cart.read_byte(addr),
|
Some(cart) => cart.read_byte(addr),
|
||||||
None => panic!("Tried to read from a non-existent cartridge"),
|
None => panic!("Tried to read from a non-existent cartridge"),
|
||||||
},
|
},
|
||||||
0xC000..=0xCFFF => {
|
0xC000..=0xCFFF => self.work_ram.read_byte(addr), // 4KB Work RAM Bank 0
|
||||||
// 4KB Work RAM Bank 0
|
0xD000..=0xDFFF => self.var_ram.read_byte(addr), // 4KB Work RAM Bank 1 -> N
|
||||||
self.work_ram.read_byte(addr)
|
|
||||||
}
|
|
||||||
0xD000..=0xDFFF => {
|
|
||||||
// 4KB Work RAM Bank 1 -> N
|
|
||||||
self.var_ram.read_byte(addr)
|
|
||||||
}
|
|
||||||
0xE000..=0xFDFF => {
|
0xE000..=0xFDFF => {
|
||||||
// Mirror of 0xC000 to 0xDDFF (ECHO RAM)
|
// Mirror of 0xC000 to 0xDDFF (ECHO RAM)
|
||||||
match addr & 0x1FFF {
|
match addr & 0x1FFF {
|
||||||
|
@ -152,7 +192,7 @@ impl BusIo for Bus {
|
||||||
use PpuMode::{HBlank, VBlank};
|
use PpuMode::{HBlank, VBlank};
|
||||||
|
|
||||||
match self.ppu.stat.mode() {
|
match self.ppu.stat.mode() {
|
||||||
HBlank | VBlank if !self.ppu.dma.is_active() => self.ppu.oam.read_byte(addr),
|
HBlank | VBlank => self.ppu.oam.read_byte(addr),
|
||||||
_ => 0xFF,
|
_ => 0xFF,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -214,15 +254,9 @@ impl BusIo for Bus {
|
||||||
|
|
||||||
fn write_byte(&mut self, addr: u16, byte: u8) {
|
fn write_byte(&mut self, addr: u16, byte: u8) {
|
||||||
match addr {
|
match addr {
|
||||||
0x0000..=0x3FFF => {
|
0x0000..=0x7FFF => {
|
||||||
// 16KB ROM bank 00
|
// 16KB ROM bank 00 (ends at 0x3FFF)
|
||||||
match self.cartridge.as_mut() {
|
// and 16KB ROM Bank 01 -> NN (switchable via MB)
|
||||||
Some(cart) => cart.write_byte(addr, byte),
|
|
||||||
None => panic!("Tried to write into non-existent cartridge"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
0x4000..=0x7FFF => {
|
|
||||||
// 16KB ROM Bank 01 -> NN (switchable via MB)
|
|
||||||
match self.cartridge.as_mut() {
|
match self.cartridge.as_mut() {
|
||||||
Some(cart) => cart.write_byte(addr, byte),
|
Some(cart) => cart.write_byte(addr, byte),
|
||||||
None => panic!("Tried to write into non-existent cartridge"),
|
None => panic!("Tried to write into non-existent cartridge"),
|
||||||
|
@ -242,14 +276,8 @@ impl BusIo for Bus {
|
||||||
None => panic!("Tried to write into non-existent cartridge"),
|
None => panic!("Tried to write into non-existent cartridge"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
0xC000..=0xCFFF => {
|
0xC000..=0xCFFF => self.work_ram.write_byte(addr, byte), // 4KB Work RAM Bank 0
|
||||||
// 4KB Work RAM Bank 0
|
0xD000..=0xDFFF => self.var_ram.write_byte(addr, byte), // 4KB Work RAM Bank 1 -> N
|
||||||
self.work_ram.write_byte(addr, byte);
|
|
||||||
}
|
|
||||||
0xD000..=0xDFFF => {
|
|
||||||
// 4KB Work RAM Bank 1 -> N
|
|
||||||
self.var_ram.write_byte(addr, byte);
|
|
||||||
}
|
|
||||||
0xE000..=0xFDFF => {
|
0xE000..=0xFDFF => {
|
||||||
// Mirror of 0xC000 to 0xDDFF (ECHO RAM)
|
// Mirror of 0xC000 to 0xDDFF (ECHO RAM)
|
||||||
match addr & 0x1FFF {
|
match addr & 0x1FFF {
|
||||||
|
@ -268,14 +296,16 @@ impl BusIo for Bus {
|
||||||
}
|
}
|
||||||
0xFE00..=0xFE9F => {
|
0xFE00..=0xFE9F => {
|
||||||
// Sprite Attribute Table
|
// Sprite Attribute Table
|
||||||
use PpuMode::{HBlank, VBlank};
|
// use PpuMode::{HBlank, VBlank};
|
||||||
|
|
||||||
match self.ppu.stat.mode() {
|
// FIXME: There is most definitely something wrong with the
|
||||||
HBlank | VBlank if !self.ppu.dma.is_active() => {
|
// PPU Timing
|
||||||
self.ppu.oam.write_byte(addr, byte)
|
//
|
||||||
}
|
// match self.ppu.stat.mode() {
|
||||||
_ => {}
|
// HBlank | VBlank => self.ppu.oam.write_byte(addr, byte),
|
||||||
}
|
// _ => {}
|
||||||
|
// }
|
||||||
|
self.ppu.oam.write_byte(addr, byte)
|
||||||
}
|
}
|
||||||
0xFEA0..=0xFEFF => {} // TODO: As far as I know, writes to here do nothing.
|
0xFEA0..=0xFEFF => {} // TODO: As far as I know, writes to here do nothing.
|
||||||
0xFF00..=0xFF7F => {
|
0xFF00..=0xFF7F => {
|
||||||
|
|
|
@ -114,8 +114,6 @@ impl Cpu {
|
||||||
// self.log_state(handle).unwrap();
|
// self.log_state(handle).unwrap();
|
||||||
// }
|
// }
|
||||||
|
|
||||||
self.handle_interrupts();
|
|
||||||
|
|
||||||
let cycles = match self.halted() {
|
let cycles = match self.halted() {
|
||||||
Some(state) => {
|
Some(state) => {
|
||||||
use HaltState::*;
|
use HaltState::*;
|
||||||
|
@ -141,6 +139,8 @@ impl Cpu {
|
||||||
self.bus.step(cycles);
|
self.bus.step(cycles);
|
||||||
self.bus.step_dma(cycles);
|
self.bus.step_dma(cycles);
|
||||||
|
|
||||||
|
self.handle_interrupts();
|
||||||
|
|
||||||
cycles
|
cycles
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -189,7 +189,7 @@ impl Ppu {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn scan_oam(&mut self) {
|
fn scan_oam(&mut self) {
|
||||||
if self.scan_state.mode() == OamScanMode::Scan && self.dma.is_active() {
|
if self.scan_state.mode() == OamScanMode::Scan {
|
||||||
if !self.window_stat.coincidence() && self.scan_state.count() == 0 {
|
if !self.window_stat.coincidence() && self.scan_state.count() == 0 {
|
||||||
// Determine whether we should draw the window next frame
|
// Determine whether we should draw the window next frame
|
||||||
self.window_stat
|
self.window_stat
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
use crate::instruction::Cycle;
|
use crate::instruction::Cycle;
|
||||||
use std::ops::Range;
|
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone)]
|
#[derive(Debug, Default, Clone)]
|
||||||
pub(crate) struct DmaProcess {
|
pub(crate) struct DmaProcess {
|
||||||
|
@ -10,8 +9,6 @@ pub(crate) struct DmaProcess {
|
||||||
|
|
||||||
impl DmaProcess {
|
impl DmaProcess {
|
||||||
pub(crate) fn clock(&mut self) -> Option<(u16, u16)> {
|
pub(crate) fn clock(&mut self) -> Option<(u16, u16)> {
|
||||||
self.cycle += 1;
|
|
||||||
|
|
||||||
match self.state {
|
match self.state {
|
||||||
DmaState::Pending => {
|
DmaState::Pending => {
|
||||||
self.cycle += 1;
|
self.cycle += 1;
|
||||||
|
@ -26,20 +23,28 @@ impl DmaProcess {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
DmaState::Transferring => {
|
DmaState::Transferring => {
|
||||||
if (self.cycle - 4) % 4 == 0 {
|
self.cycle += 1;
|
||||||
let i = u32::from((self.cycle - 4) / 4) as usize;
|
|
||||||
let dest = &mut self.ctrl.dest;
|
|
||||||
|
|
||||||
match self.ctrl.src.as_mut() {
|
let src_addr = self
|
||||||
Some(src_range) => src_range.nth(i).zip(dest.nth(i)),
|
.ctrl
|
||||||
None => {
|
.src_addr
|
||||||
self.reset();
|
.as_mut()
|
||||||
None
|
.expect("DMA Transfer Attempted without a known source address");
|
||||||
}
|
|
||||||
}
|
let addresses = if (self.cycle - 4) % 4 == 0 {
|
||||||
|
*src_addr += 1;
|
||||||
|
Some((*src_addr, 0xFE00 | (*src_addr & 0x00FF)))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
if self.cycle == 644 {
|
||||||
|
self.reset();
|
||||||
|
|
||||||
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addresses
|
||||||
}
|
}
|
||||||
DmaState::Disabled => None,
|
DmaState::Disabled => None,
|
||||||
}
|
}
|
||||||
|
@ -52,8 +57,7 @@ impl DmaProcess {
|
||||||
fn reset(&mut self) {
|
fn reset(&mut self) {
|
||||||
self.cycle = Cycle::new(0);
|
self.cycle = Cycle::new(0);
|
||||||
self.state = DmaState::Disabled;
|
self.state = DmaState::Disabled;
|
||||||
self.ctrl.src = None;
|
self.ctrl.src_addr = None;
|
||||||
self.ctrl.repr = 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,61 +77,24 @@ impl Default for DmaState {
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub(crate) struct DmaControl {
|
pub(crate) struct DmaControl {
|
||||||
pub(crate) repr: u8,
|
pub(crate) repr: u8,
|
||||||
src: Option<Range<u16>>,
|
src_addr: Option<u16>,
|
||||||
dest: Range<u16>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for DmaControl {
|
impl Default for DmaControl {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
repr: 0,
|
repr: 0,
|
||||||
src: None,
|
src_addr: None,
|
||||||
dest: 0xFE00..0xFE9F,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DmaControl {
|
impl DmaControl {
|
||||||
pub(crate) fn update(&mut self, byte: u8, state: &mut DmaState) {
|
pub(crate) fn update(&mut self, byte: u8, state: &mut DmaState) {
|
||||||
let left = (byte as u16) << 8;
|
let start = (byte as u16) << 8;
|
||||||
let right = (byte as u16) << 8 | 0x009F;
|
|
||||||
|
|
||||||
self.repr = byte;
|
self.repr = byte;
|
||||||
self.src = Some(left..right);
|
self.src_addr = Some(start);
|
||||||
*state = DmaState::Pending;
|
*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));
|
|
||||||
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));
|
|
||||||
assert_eq!(bus.dma.state, DmaState::Pending);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue