use crate::core::error::Result;
use crate::core::state::{Sink, Source};
use super::frame::Region;
const ENABLE_LATCH_CYCLES: u64 = 3;
const FETCH_SPACING_CYCLES: u64 = 3;
const IMPLICIT_STOP_DELAY: u64 = 2;
const NTSC_RATES: [u16; 16] = [
428, 380, 340, 320, 286, 254, 226, 214, 190, 160, 142, 128, 106, 84, 72, 54,
];
const PAL_RATES: [u16; 16] = [
398, 354, 316, 298, 276, 236, 210, 198, 176, 148, 132, 118, 98, 78, 66, 50,
];
pub const fn rates(region: Region) -> [u16; 16] {
match region {
Region::Ntsc | Region::Dendy => NTSC_RATES,
Region::Pal => PAL_RATES,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DmaKind {
Load,
Reload,
Abort,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DmaRequest {
pub kind: DmaKind,
pub addr: u16,
pub serial: u64,
pub at: u64,
pub not_before: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct Pending {
kind: DmaKind,
serial: u64,
at: u64,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Dmc {
region: Region,
irq_enabled: bool,
loop_flag: bool,
rate_index: u8,
output: u8,
addr_reg: u8,
len_reg: u8,
cur_addr: u16,
bytes_remaining: u16,
buffer: Option<u8>,
shift: u8,
bits_remaining: u8,
silence: bool,
timer: u16,
irq: bool,
dma: Option<Pending>,
enabled_at: u64,
last_fetch_at: u64,
stopped_at: Option<u64>,
next_serial: u64,
}
impl Dmc {
pub const fn new(region: Region) -> Dmc {
Dmc {
region,
irq_enabled: false,
loop_flag: false,
rate_index: 0,
output: 0,
addr_reg: 0,
len_reg: 0,
cur_addr: 0,
bytes_remaining: 0,
buffer: None,
shift: 0,
bits_remaining: 8,
silence: true,
timer: 0,
irq: false,
dma: None,
enabled_at: 0,
last_fetch_at: 0,
stopped_at: None,
next_serial: 1,
}
}
#[inline]
pub const fn output(&self) -> u8 {
self.output
}
#[inline]
pub const fn irq(&self) -> bool {
self.irq
}
pub fn clear_irq(&mut self) {
self.irq = false;
}
#[inline]
pub const fn active(&self) -> bool {
self.bytes_remaining > 0
}
#[inline]
pub const fn bytes_remaining(&self) -> u16 {
self.bytes_remaining
}
#[inline]
pub fn rate_cycles(&self) -> u16 {
rates(self.region)[usize::from(self.rate_index)]
}
#[inline]
fn reload(&self) -> u16 {
self.rate_cycles() / 2 - 1
}
pub fn write_control(&mut self, value: u8) {
self.irq_enabled = value & 0x80 != 0;
self.loop_flag = value & 0x40 != 0;
self.rate_index = value & 0x0F;
if !self.irq_enabled {
self.irq = false;
}
}
pub fn write_output(&mut self, value: u8) {
self.output = value & 0x7F;
}
pub fn write_address(&mut self, value: u8) {
self.addr_reg = value;
}
pub fn write_length(&mut self, value: u8) {
self.len_reg = value;
}
fn restart(&mut self) {
self.cur_addr = 0xC000u16.wrapping_add(u16::from(self.addr_reg) * 64);
self.bytes_remaining = u16::from(self.len_reg) * 16 + 1;
}
pub fn set_enabled(&mut self, enabled: bool, now: u64) {
if enabled {
if self.bytes_remaining == 0 {
self.enabled_at = now + ENABLE_LATCH_CYCLES;
self.restart();
}
self.schedule(DmaKind::Load, now);
} else {
self.bytes_remaining = 0;
self.stopped_at = Some(now);
}
}
fn schedule(&mut self, kind: DmaKind, now: u64) {
let has_bytes = self.bytes_remaining > 0 || self.stopped_at.is_some_and(|at| now < at);
if self.dma.is_none() && self.buffer.is_none() && has_bytes {
self.dma = Some(Pending {
kind,
serial: self.next_serial,
at: now,
});
self.next_serial = self.next_serial.wrapping_add(1);
}
}
pub fn dma_request(&self) -> Option<DmaRequest> {
self.dma.map(|p| DmaRequest {
kind: p.kind,
addr: self.cur_addr,
serial: p.serial,
at: p.at,
not_before: self
.enabled_at
.max(self.last_fetch_at + FETCH_SPACING_CYCLES),
})
}
pub fn dma_is_pending(&self, serial: u64) -> bool {
self.dma.is_some_and(|p| p.serial == serial)
}
pub fn dma_withdraw(&mut self, serial: u64) {
if self.dma_is_pending(serial) {
self.dma = None;
}
}
pub fn dma_complete(&mut self, serial: u64, byte: u8, now: u64) -> bool {
if !self.dma_is_pending(serial) {
return false;
}
self.dma = None;
self.last_fetch_at = now + 1;
self.buffer = Some(byte);
self.cur_addr = if self.cur_addr == 0xFFFF {
0x8000
} else {
self.cur_addr + 1
};
if self.bytes_remaining == 0 {
return true;
}
self.bytes_remaining -= 1;
if self.bytes_remaining == 0 {
if self.loop_flag {
self.restart();
} else {
self.stopped_at = Some(now + IMPLICIT_STOP_DELAY);
if self.irq_enabled {
self.irq = true;
}
}
}
true
}
pub fn tick_timer(&mut self, now: u64) {
if self.timer == 0 {
self.timer = self.reload();
self.clock_output(now);
} else {
self.timer -= 1;
}
}
fn clock_output(&mut self, now: u64) {
if !self.silence {
if self.shift & 1 != 0 {
if self.output <= 125 {
self.output += 2;
}
} else if self.output >= 2 {
self.output -= 2;
}
}
self.shift >>= 1;
self.bits_remaining -= 1;
if self.bits_remaining == 0 {
self.bits_remaining = 8;
match self.buffer.take() {
Some(byte) => {
self.silence = false;
self.shift = byte;
}
None => self.silence = true,
}
self.schedule(DmaKind::Reload, now);
if self.dma.is_none()
&& self.bytes_remaining == 0
&& self.stopped_at.is_some_and(|at| at <= now && now - at <= 2)
{
self.dma = Some(Pending {
kind: DmaKind::Abort,
serial: self.next_serial,
at: now,
});
self.next_serial = self.next_serial.wrapping_add(1);
}
}
}
pub fn reset_warm(&mut self) {
self.output &= 1;
self.bytes_remaining = 0;
self.buffer = None;
self.silence = true;
self.bits_remaining = 8;
self.irq = false;
self.dma = None;
}
pub fn save(&self, w: &mut dyn Sink) -> Result<()> {
w.write_bool(self.irq_enabled)?;
w.write_bool(self.loop_flag)?;
w.write_u8(self.rate_index)?;
w.write_u8(self.output)?;
w.write_u8(self.addr_reg)?;
w.write_u8(self.len_reg)?;
w.write_u16(self.cur_addr)?;
w.write_u16(self.bytes_remaining)?;
match self.buffer {
Some(byte) => {
w.write_bool(true)?;
w.write_u8(byte)?;
}
None => {
w.write_bool(false)?;
w.write_u8(0)?;
}
}
w.write_u8(self.shift)?;
w.write_u8(self.bits_remaining)?;
w.write_bool(self.silence)?;
w.write_u16(self.timer)?;
w.write_bool(self.irq)?;
match self.dma {
Some(p) => {
w.write_u8(match p.kind {
DmaKind::Load => 1,
DmaKind::Reload => 2,
DmaKind::Abort => 3,
})?;
w.write_u64(p.serial)?;
}
None => {
w.write_u8(0)?;
w.write_u64(0)?;
}
}
w.write_u64(self.next_serial)?;
w.write_u64(self.dma.map_or(0, |p| p.at))?;
w.write_u64(self.enabled_at)?;
w.write_u64(self.last_fetch_at)
}
pub fn load<'a>(&mut self, r: &mut dyn Source<'a>) -> Result<()> {
self.irq_enabled = r.read_bool()?;
self.loop_flag = r.read_bool()?;
self.rate_index = r.read_u8()? & 0x0F;
self.output = r.read_u8()? & 0x7F;
self.addr_reg = r.read_u8()?;
self.len_reg = r.read_u8()?;
self.cur_addr = r.read_u16()?;
self.bytes_remaining = r.read_u16()?;
let filled = r.read_bool()?;
let byte = r.read_u8()?;
self.buffer = if filled { Some(byte) } else { None };
self.shift = r.read_u8()?;
self.bits_remaining = r.read_u8()?;
self.silence = r.read_bool()?;
self.timer = r.read_u16()?;
self.irq = r.read_bool()?;
let kind = r.read_u8()?;
let serial = r.read_u64()?;
self.next_serial = r.read_u64()?;
let at = r.read_u64().unwrap_or(0);
self.enabled_at = r.read_u64().unwrap_or(0);
self.last_fetch_at = r.read_u64().unwrap_or(0);
self.dma = match kind {
1 => Some(Pending {
kind: DmaKind::Load,
serial,
at,
}),
2 => Some(Pending {
kind: DmaKind::Reload,
serial,
at,
}),
3 => Some(Pending {
kind: DmaKind::Abort,
serial,
at,
}),
_ => None,
};
Ok(())
}
}