#[derive(Copy, Clone, PartialEq, Eq, Default)]
pub enum DmaType {
#[default]
Ppu,
Apu,
}
#[derive(Copy, Clone, PartialEq, Eq, Default, Debug)]
pub enum DmaState {
#[default]
Disabled,
WaitingForCpuHalt,
Transferring,
}
#[derive(Default)]
pub struct Dma {
state: DmaState,
last_addr: u16,
addr: u16,
data: Option<u8>,
}
impl DmaType {
pub fn toggle(&mut self) {
if *self == DmaType::Ppu {
*self = DmaType::Apu;
} else {
*self = DmaType::Ppu;
}
}
}
impl Dma {
pub fn reset(&mut self) {
*self = Default::default();
}
pub fn initiate_dma(&mut self, addr_page: u8) {
self.addr = (addr_page as u16) << 8;
self.last_addr = self.addr + u8::MAX as u16;
self.state = DmaState::WaitingForCpuHalt;
}
pub fn addr(&self) -> u16 {
self.addr
}
pub fn state(&self) -> DmaState {
self.state
}
pub fn start_transfer(&mut self) {
assert_eq!(self.state, DmaState::WaitingForCpuHalt);
self.state = DmaState::Transferring;
}
pub fn data_pull(&mut self, data: u8) {
self.data = Some(data);
self.addr = self.addr.wrapping_add(1);
}
pub fn data_put(&mut self) -> Option<u8> {
let data_to_write = self.data.take();
if self.addr > self.last_addr {
self.reset();
}
data_to_write
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn dma_transfer() {
let mut dma = Dma::default();
assert_eq!(dma.state, DmaState::Disabled);
let addr_page = 0x1A;
dma.initiate_dma(addr_page);
assert_eq!(dma.state, DmaState::WaitingForCpuHalt);
dma.start_transfer();
for i in 0..=u8::MAX {
assert_eq!(dma.state, DmaState::Transferring);
let expected_addr = 0x1A00 + (i as u16);
assert_eq!(dma.addr(), expected_addr);
let expected_data = u8::MAX - i;
dma.data_pull(expected_data);
let data_to_put = dma.data_put();
assert_eq!(data_to_put, Some(expected_data));
}
assert_eq!(dma.state, DmaState::Disabled);
}
}